


[{"content":" Gin 简介 # Gin是用Go语言编写的一个HTTP Web框架，它是一个轻量级的、高性能的框架，特别适合开发API服务。作为目前Go生态中最受欢迎的Web框架之一，Gin以其极高的性能和生产级的可靠性而闻名。\nGin诞生于2014年，由Manu Martinez-Almeida创建，设计初衷是为了构建一个比Martini更快、更轻量级的Web框架。经过多年发展，Gin已经成为Go语言社区最活跃的项目之一。\n官网：https://gin-gonic.com/\nGin框架的核心特点 # 高性能：Gin采用了定制版的HttpRouter（一个高性能的HTTP请求路由器），路由性能极高。基准测试表明，Gin的路由性能是大多数同类框架的40倍。 轻量级：Gin的核心非常精简，只提供路由、中间件、参数获取和验证等关键功能，其他功能可通过中间件或扩展包引入，这让Gin保持了高度的灵活性。 高效的JSON解析：Gin内置了高效的JSON解析和验证功能，使API开发更加便捷。 强大的中间件机制：Gin提供了优雅的中间件支持，可以轻松实现日志记录、错误处理、认证授权等功能。 丰富的上下文功能：Gin的Context包含了请求的所有信息，并提供了多种便捷方法来处理请求和响应。 可扩展性：Gin设计为高度可扩展，可以轻松集成第三方库和服务。 内置错误管理：Gin提供了优雅的错误处理机制，使开发者能够更好地控制错误流程。 安装 # go get -u github.com/gin-gonic/gin 创建简单的 Gin 应用 # package main import ( \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { // 创建默认的 Gin 引擎 engine := gin.Default() // 定义一个GET请求路由 engine.GET(\u0026#34;/test\u0026#34;, func(context *gin.Context) { context.JSON(http.StatusOK, gin.H{ \u0026#34;message\u0026#34;: \u0026#34;OK\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Gin\u0026#34;, }) }) // 启动服务器 engine.Run(\u0026#34;:8080\u0026#34;) } gin.Default()：创建一个带有默认中间件（Logger和Recovery）的引擎实例。 engine.GET()：定义了一个处理GET请求的路由/test，当访问这个路径时，将返回一个JSON响应。 engine.Run(\u0026quot;:8080\u0026quot;)：Run方法启动HTTP服务器，默认监听0.0.0.0:8080端口。 请求生命周期 # Gin中请求的处理流程如下：\n客户端发起HTTP请求 Gin接收请求并创建Context对象 路由匹配，找到对应的处理器 按顺序执行中间件链（前置处理） 执行路由处理器 按相反顺序执行中间件链（后置处理） 返回响应给客户端 请求阶段 执行内容 作用 初始化 创建Context对象 封装请求和响应 路由匹配 查找Trie树 确定处理函数 中间件前置处理 按注册顺序执行中间件的c.Next()前代码 认证、日志等前置操作 处理器执行 执行路由对应的处理函数 业务逻辑处理 中间件后置处理 按注册相反顺序执行中间件的c.Next()后代码 响应处理、资源清理等 响应返回 将处理结果返回客户端 完成HTTP响应 Context对象 # gin.Context是Gin框架的核心部分，它封装了HTTP请求和响应，并提供了许多便捷方法。主要功能包括：\n参数获取（路径参数、查询参数、表单数据、JSON数据等） 响应输出（JSON、XML、HTML、文件等） 中间件控制（Next、Abort等） 错误管理 元数据存储 func MyHandler(c *gin.Context) { // 获取请求信息 path := c.FullPath() method := c.Request.Method header := c.GetHeader(\u0026#34;Content-Type\u0026#34;) // 处理请求参数 id := c.Param(\u0026#34;id\u0026#34;) name := c.Query(\u0026#34;name\u0026#34;) // 设置响应 c.JSON(200, gin.H{ \u0026#34;path\u0026#34;: path, \u0026#34;method\u0026#34;: method, \u0026#34;header\u0026#34;: header, \u0026#34;id\u0026#34;: id, \u0026#34;name\u0026#34;: name, }) } **注意：**Context对象仅在请求周期内有效，不要将其存储在全局变量或goroutine中长期使用，否则可能导致内存泄漏或并发安全问题。\n请求与响应处理 # 参数获取 # 在Web应用中，我们需要从各种来源获取请求参数，包括URL路径、查询字符串、表单数据、JSON数据等。Gin框架提供了丰富的API来简化这一过程。\n常用的绑定方法包括：\nShouldBind: 根据Content-Type自动选择绑定方法 ShouldBindJSON: 绑定JSON数据 ShouldBindXML: 绑定XML数据 ShouldBindQuery: 仅绑定查询参数 ShouldBindUri: 绑定URL路径参数 Gin支持从多种来源获取参数：\n路径参数：URL路径中的动态部分，如 /user/:id 查询参数：URL中的查询字符串，如 /search?q=keyword 表单数据：通过POST、PUT等方法提交的表单数据 JSON/XML数据：请求体中的JSON或XML格式数据 文件上传：multipart/form-data格式的文件数据 Cookie：HTTP请求中的Cookie数据 Header：HTTP请求头 参数类型 获取方法 使用场景 路径参数 c.Param(\u0026quot;id\u0026quot;) RESTful资源标识 查询参数 c.Query(\u0026quot;page\u0026quot;) 分页、筛选、排序 表单数据 c.PostForm(\u0026quot;name\u0026quot;) 表单提交 JSON数据 c.ShouldBindJSON(\u0026amp;obj) API请求体 文件上传 c.FormFile(\u0026quot;file\u0026quot;) 上传功能 Cookie数据 c.Cookie(\u0026quot;token\u0026quot;) 会话管理 Header数据 c.GetHeader(\u0026quot;Authorization\u0026quot;) 认证信息 获取路径参数 # 路径参数是URL路径中的动态部分，使用冒号定义：\nrouter.GET(\u0026#34;/user/:id\u0026#34;, func(c *gin.Context) { id := c.Param(\u0026#34;id\u0026#34;) c.JSON(200, gin.H{\u0026#34;id\u0026#34;: id}) }) 路径参数也可以使用结构化绑定方式获取，使用c.ShouldBindUri(\u0026amp;obj)，这在需要类型转换和验证时特别有用。\n获取查询参数 # 查询参数是URL中问号后面的键值对：\nrouter.GET(\u0026#34;/search\u0026#34;, func(c *gin.Context) { // 获取查询参数，如果不存在则使用默认值 keyword := c.DefaultQuery(\u0026#34;q\u0026#34;, \u0026#34;\u0026#34;) page := c.DefaultQuery(\u0026#34;page\u0026#34;, \u0026#34;1\u0026#34;) // 另一种写法 limit, exists := c.GetQuery(\u0026#34;limit\u0026#34;) if !exists { limit = \u0026#34;10\u0026#34; } c.JSON(200, gin.H{ \u0026#34;keyword\u0026#34;: keyword, \u0026#34;page\u0026#34;: page, \u0026#34;limit\u0026#34;: limit, }) }) 获取表单数据 # 表单数据通常通过POST方法提交：\nrouter.POST(\u0026#34;/form\u0026#34;, func(c *gin.Context) { // 获取表单数据 username := c.PostForm(\u0026#34;username\u0026#34;) password := c.DefaultPostForm(\u0026#34;password\u0026#34;, \u0026#34;\u0026#34;) // 检查字段是否存在 age, exists := c.GetPostForm(\u0026#34;age\u0026#34;) if !exists { age = \u0026#34;0\u0026#34; } c.JSON(200, gin.H{ \u0026#34;username\u0026#34;: username, \u0026#34;password\u0026#34;: password, \u0026#34;age\u0026#34;: age, }) }) 获取JSON数据 # 处理JSON格式的请求体：\nrouter.POST(\u0026#34;/json\u0026#34;, func(c *gin.Context) { // 定义接收结构 var json struct { Username string `json:\u0026#34;username\u0026#34;` Password string `json:\u0026#34;password\u0026#34;` Email string `json:\u0026#34;email\u0026#34;` } // 解析JSON数据 if err := c.ShouldBindJSON(\u0026amp;json); err != nil { c.JSON(400, gin.H{\u0026#34;error\u0026#34;: err.Error()}) return } c.JSON(200, gin.H{ \u0026#34;username\u0026#34;: json.Username, \u0026#34;email\u0026#34;: json.Email, }) }) 尽量使用ShouldBindJSON而非BindJSON，因为前者在验证失败时会返回错误而不是直接中止请求，让您能更灵活地处理错误。\n自动参数绑定 # Gin提供了多种绑定方法，可以自动将请求数据绑定到Go结构体：\ntype LoginForm struct { Username string `form:\u0026#34;username\u0026#34; json:\u0026#34;username\u0026#34; binding:\u0026#34;required\u0026#34;` Password string `form:\u0026#34;password\u0026#34; json:\u0026#34;password\u0026#34; binding:\u0026#34;required\u0026#34;` } router.POST(\u0026#34;/login\u0026#34;, func(c *gin.Context) { var form LoginForm // 根据Content-Type自动选择绑定方法 if err := c.ShouldBind(\u0026amp;form); err != nil { c.JSON(400, gin.H{\u0026#34;error\u0026#34;: err.Error()}) return } c.JSON(200, gin.H{\u0026#34;message\u0026#34;: \u0026#34;登录成功\u0026#34;, \u0026#34;username\u0026#34;: form.Username}) }) 参数验证 # Gin集成了validator库，支持通过标签进行数据验证。\n常用的验证标签：\nrequired: 字段必须存在 min, max: 字符串长度或数字范围 email, url, uuid: 特定格式 oneof: 枚举值 gte, lte, gt, lt: 大于等于、小于等于、大于、小于 验证标签可以组合使用，如binding:\u0026quot;required,min=8,max=20\u0026quot;表示该字段必须存在，且长度在8到20之间。\n您还可以使用binding:\u0026quot;-\u0026quot;来完全跳过某个字段的验证。\ntype RegisterForm struct { Username string `json:\u0026#34;username\u0026#34; binding:\u0026#34;required,min=4,max=20\u0026#34;` Password string `json:\u0026#34;password\u0026#34; binding:\u0026#34;required,min=8\u0026#34;` Email string `json:\u0026#34;email\u0026#34; binding:\u0026#34;required,email\u0026#34;` Age int `json:\u0026#34;age\u0026#34; binding:\u0026#34;required,gte=18,lte=130\u0026#34;` } router.POST(\u0026#34;/register\u0026#34;, func(c *gin.Context) { var form RegisterForm if err := c.ShouldBindJSON(\u0026amp;form); err != nil { c.JSON(400, gin.H{\u0026#34;error\u0026#34;: err.Error()}) return } // 验证通过，处理注册逻辑 c.JSON(200, gin.H{\u0026#34;message\u0026#34;: \u0026#34;注册成功\u0026#34;}) }) 响应格式化 # Gin提供多种响应输出方式：\n// JSON响应 c.JSON(200, gin.H{\u0026#34;message\u0026#34;: \u0026#34;success\u0026#34;}) // XML响应 c.XML(200, gin.H{\u0026#34;message\u0026#34;: \u0026#34;success\u0026#34;}) // YAML响应 c.YAML(200, gin.H{\u0026#34;message\u0026#34;: \u0026#34;success\u0026#34;}) // 字符串响应 c.String(200, \u0026#34;Hello %s\u0026#34;, name) // HTML响应 c.HTML(200, \u0026#34;template.html\u0026#34;, gin.H{\u0026#34;title\u0026#34;: \u0026#34;Gin\u0026#34;}) // 文件响应 c.File(\u0026#34;local/file.txt\u0026#34;) // 重定向 c.Redirect(302, \u0026#34;https://example.com\u0026#34;) JSON响应 # 返回JSON格式数据：\nrouter.GET(\u0026#34;/json\u0026#34;, func(c *gin.Context) { // 使用gin.H（map[string]interface{}的别名） c.JSON(200, gin.H{ \u0026#34;message\u0026#34;: \u0026#34;success\u0026#34;, \u0026#34;data\u0026#34;: gin.H{ \u0026#34;name\u0026#34;: \u0026#34;John\u0026#34;, \u0026#34;age\u0026#34;: 30, \u0026#34;skills\u0026#34;: []string{\u0026#34;Go\u0026#34;, \u0026#34;Docker\u0026#34;, \u0026#34;Kubernetes\u0026#34;}, }, }) // 或者使用结构体 type User struct { Name string `json:\u0026#34;name\u0026#34;` Age int `json:\u0026#34;age\u0026#34;` Skills []string `json:\u0026#34;skills\u0026#34;` } user := User{ Name: \u0026#34;John\u0026#34;, Age: 30, Skills: []string{\u0026#34;Go\u0026#34;, \u0026#34;Docker\u0026#34;, \u0026#34;Kubernetes\u0026#34;}, } c.JSON(200, gin.H{ \u0026#34;message\u0026#34;: \u0026#34;success\u0026#34;, \u0026#34;data\u0026#34;: user, }) }) XML响应 # 返回XML格式数据：\nrouter.GET(\u0026#34;/xml\u0026#34;, func(c *gin.Context) { type User struct { Name string `xml:\u0026#34;name\u0026#34;` Age int `xml:\u0026#34;age\u0026#34;` Skills []string `xml:\u0026#34;skills\u0026gt;skill\u0026#34;` } user := User{ Name: \u0026#34;John\u0026#34;, Age: 30, Skills: []string{\u0026#34;Go\u0026#34;, \u0026#34;Docker\u0026#34;, \u0026#34;Kubernetes\u0026#34;}, } c.XML(200, user) }) YAML响应 # 返回YAML格式数据：\nrouter.GET(\u0026#34;/yaml\u0026#34;, func(c *gin.Context) { c.YAML(200, gin.H{ \u0026#34;message\u0026#34;: \u0026#34;success\u0026#34;, \u0026#34;user\u0026#34;: gin.H{ \u0026#34;name\u0026#34;: \u0026#34;John\u0026#34;, \u0026#34;age\u0026#34;: 30, \u0026#34;skills\u0026#34;: []string{\u0026#34;Go\u0026#34;, \u0026#34;Docker\u0026#34;, \u0026#34;Kubernetes\u0026#34;}, }, }) }) HTML响应 # 返回HTML页面：\nrouter.LoadHTMLGlob(\u0026#34;templates/*\u0026#34;) router.GET(\u0026#34;/html\u0026#34;, func(c *gin.Context) { c.HTML(200, \u0026#34;index.html\u0026#34;, gin.H{ \u0026#34;title\u0026#34;: \u0026#34;Gin框架\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;欢迎学习Gin框架\u0026#34;, }) }) 文件响应 # 返回文件内容：\nrouter.GET(\u0026#34;/file\u0026#34;, func(c *gin.Context) { c.File(\u0026#34;static/file.txt\u0026#34;) }) // 指定文件名下载 router.GET(\u0026#34;/download\u0026#34;, func(c *gin.Context) { c.FileAttachment(\u0026#34;static/file.txt\u0026#34;, \u0026#34;download.txt\u0026#34;) }) 流式响应 # 流式响应是实现实时通知、大文件下载和长轮询等功能的关键技术。在Gin中，可以通过控制Writer的刷新时机来实现数据的分块传输，减少首字节时间(TTFB)并提升用户体验。\n处理大文件或长时间运行的操作：\nrouter.GET(\u0026#34;/stream\u0026#34;, func(c *gin.Context) { // 设置响应头 c.Writer.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;text/event-stream\u0026#34;) c.Writer.Header().Set(\u0026#34;Cache-Control\u0026#34;, \u0026#34;no-cache\u0026#34;) c.Writer.Header().Set(\u0026#34;Connection\u0026#34;, \u0026#34;keep-alive\u0026#34;) // 刷新缓冲区 c.Writer.Flush() // 模拟流式数据 for i := 0; i \u0026lt; 10; i++ { c.SSEvent(\u0026#34;message\u0026#34;, gin.H{\u0026#34;value\u0026#34;: i}) c.Writer.Flush() time.Sleep(time.Second) } }) 自定义响应 # 自定义HTTP响应的状态码、头部和内容：\nrouter.GET(\u0026#34;/custom\u0026#34;, func(c *gin.Context) { // 设置状态码 c.Status(201) // 设置头部 c.Header(\u0026#34;X-Custom-Header\u0026#34;, \u0026#34;value\u0026#34;) // 写入响应体 c.Writer.Write([]byte(\u0026#34;自定义响应内容\u0026#34;)) }) 错误处理机制 # 良好的错误处理对于构建可靠的Web应用至关重要。Gin提供了多种机制来处理和返回错误：\n1、c.Error(err)：向当前上下文添加错误，这些错误会被存储在c.Errors切片中，可以稍后由中间件处理。\nif err := doSomething(); err != nil { c.Error(err) // 记录错误 c.JSON(500, gin.H{\u0026#34;message\u0026#34;: \u0026#34;Internal error\u0026#34;}) } 2、错误中间件：用于集中处理应用程序中的错误。\nfunc ErrorHandler() gin.HandlerFunc { return func(c *gin.Context) { c.Next() // 执行后续中间件和处理函数 // 检查是否有错误 if len(c.Errors) \u0026gt; 0 { // 处理错误 err := c.Errors.Last() c.JSON(500, gin.H{\u0026#34;error\u0026#34;: err.Error()}) } } } 3、panic恢复：Gin的Recovery中间件可以捕获panic并返回500错误。\nr := gin.New() r.Use(gin.Recovery()) // 捕获panic并返回500 自定义错误处理中间件 # 始终设计统一的错误响应格式，包含错误代码、错误信息和请求标识符，这有助于API调用者理解错误并有利于问题排查。\n创建统一的错误处理中间件：\nfunc ErrorHandler() gin.HandlerFunc { return func(c *gin.Context) { c.Next() // 检查是否有错误 if len(c.Errors) \u0026gt; 0 { // 获取最后一个错误 err := c.Errors.Last() // 根据错误类型返回不同的响应 switch err.Type { case gin.ErrorTypeBind: c.JSON(400, gin.H{ \u0026#34;error\u0026#34;: \u0026#34;请求参数错误\u0026#34;, \u0026#34;details\u0026#34;: err.Err.Error(), }) case gin.ErrorTypePrivate: // 业务逻辑错误 c.JSON(500, gin.H{ \u0026#34;error\u0026#34;: \u0026#34;服务器内部错误\u0026#34;, \u0026#34;request_id\u0026#34;: c.GetString(\u0026#34;RequestID\u0026#34;), }) default: c.JSON(500, gin.H{ \u0026#34;error\u0026#34;: \u0026#34;未知错误\u0026#34;, \u0026#34;message\u0026#34;: err.Err.Error(), }) } // 中止后续处理 c.Abort() } } } 错误类型与上下文错误 # Gin提供了多种错误类型和上下文错误方法：\nrouter.POST(\u0026#34;/login\u0026#34;, func(c *gin.Context) { var form LoginForm // 绑定错误会自动添加到c.Errors if err := c.ShouldBindJSON(\u0026amp;form); err != nil { c.AbortWithError(400, err).SetType(gin.ErrorTypeBind) return } // 业务逻辑错误 if !isValidUser(form.Username, form.Password) { err := errors.New(\u0026#34;用户名或密码错误\u0026#34;) c.Error(err).SetType(gin.ErrorTypePrivate) return } c.JSON(200, gin.H{\u0026#34;message\u0026#34;: \u0026#34;登录成功\u0026#34;}) }) Gin的内置日志系统 # Gin使用自己的简单日志系统，基于标准库但添加了一些增强功能：\n// 默认日志输出到标准输出 r := gin.Default() // 使用默认的Logger和Recovery中间件 // 禁用控制台颜色 gin.DisableConsoleColor() // 强制控制台颜色 gin.ForceConsoleColor() // 写入文件 f, _ := os.Create(\u0026#34;gin.log\u0026#34;) gin.DefaultWriter = io.MultiWriter(f, os.Stdout) // 同时写入文件和控制台 // 设置日志格式 r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { return fmt.Sprintf(\u0026#34;%s - [%s] \\\u0026#34;%s %s %s %d %s \\\u0026#34;%s\\\u0026#34; %s\\\u0026#34;\\n\u0026#34;, param.ClientIP, param.TimeStamp.Format(time.RFC1123), param.Method, param.Path, param.Request.Proto, param.StatusCode, param.Latency, param.Request.UserAgent(), param.ErrorMessage, ) })) 基本项目结构 # 在Gin项目中，合理相关代码不仅能提高代码可读性，还能促进团队协作和项目维护。以下是一种常见的分层架构：\nbookstore-api/ ├── cmd/ │ └── server/ │ └── main.go # 应用程序入口 ├── configs/ │ └── config.yaml # 配置文件 ├── internal/ │ ├── api/ # API层(控制器) │ │ ├── handlers/ # 请求处理器 │ │ │ ├── auth.go # 认证相关处理器 │ │ │ ├── book.go # 图书相关处理器 │ │ │ └── user.go # 用户相关处理器 │ │ ├── middlewares/ # 中间件 │ │ │ ├── auth.go # 认证中间件 │ │ │ ├── cors.go # 跨域中间件 │ │ │ ├── logger.go # 日志中间件 │ │ │ └── recovery.go # 恢复中间件 │ │ └── routes/ # 路由定义 │ │ └── routes.go # API路由配置 │ ├── config/ # 配置加载 │ │ └── config.go # 配置结构和加载函数 │ ├── domain/ # 领域层 │ │ ├── models/ # 模型定义 │ │ │ ├── book.go # 图书模型 │ │ │ └── user.go # 用户模型 │ │ └── repositories/ # 存储接口 │ │ ├── book.go # 图书存储接口 │ │ └── user.go # 用户存储接口 │ ├── infrastructure/ # 基础设施层 │ │ ├── database/ # 数据库相关 │ │ │ └── mysql.go # MySQL连接和初始化 │ │ └── persistence/ # 持久化实现 │ │ ├── mysql/ # MySQL实现 │ │ │ ├── book.go # 图书存储实现 │ │ │ └── user.go # 用户存储实现 │ │ └── cache/ # 缓存实现(可选) │ └── services/ # 服务层 │ ├── auth.go # 认证服务 │ ├── book.go # 图书服务 │ └── user.go # 用户服务 ├── pkg/ # 公共包 │ ├── jwt/ # JWT工具 │ │ └── jwt.go # JWT生成和验证 │ ├── utils/ # 工具函数 │ │ ├── hash.go # 密码哈希 │ │ └── validator.go # 数据验证 │ └── errors/ # 错误处理 │ └── errors.go # 自定义错误 ├── go.mod # Go模块定义 └── go.sum # 依赖校验 这种结构清晰地分离了关注点，使代码组织更加清晰：\ncmd 包含应用程序的入口点 configs 存放配置文件 internal 包含应用程序内部代码，不会被外部导入 pkg 包含可被外部项目复用的代码 实用技巧 # Gin的调试模式与生产模式 # Gin有两种运行模式：调试模式和生产模式。\n设置调试模式（默认）：\ngin.SetMode(gin.DebugMode) 设置生产模式：\ngin.SetMode(gin.ReleaseMode) 在生产环境中，一定要使用ReleaseMode，这不仅能提高性能，还能避免暴露敏感的调试信息。\n使用热重载提高开发效率 # 使用air工具可以实现热重载，提高开发效率：\n# 安装air go install github.com/air-verse/air@latest # 在项目目录下初始化air配置 air init # 使用air运行项目 air 这样，每当你修改代码并保存，应用会自动重新编译和启动。\n统一响应格式 # package dto import \u0026#34;net/http\u0026#34; // Response API 统一响应格式 type Response struct { Code int `json:\u0026#34;code\u0026#34;` Success bool `json:\u0026#34;success\u0026#34;` Message *string `json:\u0026#34;message,omitempty\u0026#34;` Data *any `json:\u0026#34;data,omitempty\u0026#34;` Error *string `json:\u0026#34;error,omitempty\u0026#34;` } // NewResponse 创建响应 func NewResponse(code int, success bool, message *string, data *any, error *string) *Response { return \u0026amp;Response{ Code: code, Success: success, Message: message, Data: data, Error: error, } } // SuccessResponse 成功响应 func SuccessResponse(data *any, message *string) *Response { return NewResponse(http.StatusOK, true, message, data, nil) } // ErrorResponse 错误响应 func ErrorResponse(message, error *string) *Response { return NewResponse(http.StatusInternalServerError, false, message, nil, error) } ","date":"2025-07-30","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/1bd46fa5/85f58d14/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eGin 简介 \n    \u003cdiv id=\"gin-简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#gin-%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eGin是用Go语言编写的一个HTTP Web框架，它是一个轻量级的、高性能的框架，特别适合开发API服务。作为目前Go生态中最受欢迎的Web框架之一，Gin以其极高的性能和生产级的可靠性而闻名。\u003c/p\u003e","title":"1、简介","type":"posts"},{"content":" 什么是TypeScript # TypeScript（简称：TS）是 JavaScript 的超集（JS 有的 TS 都有）。\nTypeScript = Type + JavaScript（在 JS 基础之上，为 JS 添加了类型支持）。\nTypeScript 是微软开发的开源编程语言，可以在任何运行 JavaScript 的地方运行。\nDemo # 环境安装 # 由于Node.js和浏览器，只认识 JS 代码，不认识 TS 代码。需要先将 TS 代码转化为 JS 代码，然后才能运行。\nnpm install -g typescript 验证是否安装成功：tsc –v（查看 typescript 的版本）。\nHelloWorld # 创建index.ts，并编写代码\nvar message:string = \u0026#34;Hello World\u0026#34; console.log(message) 使用命令tsc index.ts进行编译，编译后会在同目录中出现index.js。\n使用命令node index.js执行。\n简化TS执行步骤 # 每次修改代码后，都要重复执行两个命令，才能运行 TS 代码，太繁琐。\n编译tsc index.ts 执行node index.js 使用 ts-node 包，直接在 Node.js 中执行 TS 代码。\nnpm install -g ts-node 安装完成后可以使用ts-node index.ts直接执行ts代码\n@types/node # Node.js 本身以纯 JavaScript 实现，核心模块（例如 fs、http、crypto）在运行时动态暴露 API。\nTypeScript 编译器若想准确推导这些 API，就必须提前知道它们的形状。\n声明文件（.d.ts）正是用来描述这层“形状”的专用语法。\n所以我们如果想在 ts 中使用 Node.js 中声明的变量、方法不报错，那么就需要安装这个包\nnpm install --save-dev @types/node ","date":"2025-05-27","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/6d366d27/266f89ac/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是TypeScript \n    \u003cdiv id=\"什么是typescript\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%aftypescript\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eTypeScript（简称：TS）是 JavaScript 的超集（JS 有的 TS 都有）。\u003c/p\u003e","title":"1、TypeScript","type":"posts"},{"content":" Cobra 基础概念 # Cobra 是一个可以创建强大的现代 CLI 应用程序的库，它还提供了一个可以生成应用和命令文件的程序的命令行工具：cobra-cli。有许多大型项目都是用 cobra 来构建他们的应用程序，例如：kubernetes、Docker、Etcd、Rkt、Hugo 等。Cobra 具有很多特性，一些核心特性如下：\n可以构建基于子命令的 CLI，并支持支持嵌套子命令。例如：app server，app fetch。 可以通过 cobra-cli init appname \u0026amp; cobra-cli add cmdname 轻松生成应用和子命令。 智能化命令建议（app srver… did you mean app server?）。 自动生成命令和标志的 help 文本，并能自动识别 -h ， --help 等标志。 自动为你的应用程序生成 bash、zsh、fish 和 powershell 自动补全脚本。 支持命令别名、自定义帮助、自定义用法等。 可以与 viper、pflag 紧密集成，用于构建 12-factor 应用程序。 核心概念与架构设计 # Cobra 建立在 commands、arguments 和 flags 结构之上。commands 代表命令，arguments 代表非选项参数，flags 代表选项参数（也叫标志）。一个好的应用程序应该是易懂的，用户可以清晰的知道如何去使用这个应用程序。应用程序通常遵循如下模式：APPNAME VERB NOUN --ADJECTIVE 或者 APPNAME COMMAND ARG --FLAG，例如：\ngit clone URL --bare # clone 是一个命令，URL 是一个非选项参数，bare 是一个选项参数 Cobra采用分层的命令树架构，核心由cobra.Command结构体组成，每个命令可以包含子命令、标志参数、执行逻辑。其核心架构图如下：\n命令结构体剖析 # cobra.Command是Cobra的核心数据结构，关键字段包括：\ntype Command struct { Use string // 命令使用语法（如\u0026#34;create user\u0026#34;） Short string // 简短描述 Long string // 详细描述 Args func(args []string, cmd *Command) error // 参数验证函数 Run func(cmd *Command, args []string) // 命令执行函数 Flags pflag.FlagSet // 标志参数集合 SubCommands []*Command // 子命令列表 // 其他字段：Completion、Hooks、Annotations等 } 命令树构建原则 # 单一职责：每个子命令专注于单一功能（如todo add/todo list） 层级深度：建议不超过3层子命令，避免用户记忆负担 参数作用域：标志参数可以在当前命令或继承自父命令 帮助信息：自动生成的帮助基于命令树结构动态渲染 核心交互流程 # 用户输入解析流程如下：\n输入解析 识别根命令 匹配子命令链 解析标志参数 验证参数合法性 执行Run函数 输出结果或错误 cobra-cli # Cobra 提供了一个 cobra-cli 命令，用来初始化一个应用程序并为其添加命令，方便我们开发基于 Cobra 的应用。cobra-cli 命令安装方法如下：\ngo install github.com/spf13/cobra-cli@latest cobra-cli 命令提供了 4 个子命令：\ninit：初始化一个 cobra 应用程序； add：给通过 cobra init 创建的应用程序添加子命令； completion：为指定的 shell 生成命令自动补全脚本； help：打印任意命令的帮助信息。 cobra-cli 命令还提供了一些全局的参数：\n-a, --author：指定 Copyright 版权声明中的作者； --config：指定 cobra 配置文件的路径； -l, --license：指定生成的应用程序所使用的开源协议，内置的有：GPLv2, GPLv3, LGPL, AGPL, MIT, 2-Clause BSD, 3-Clause BSD； --viper：使用 viper 作为命令行参数解析工具，默认为 true。 基本使用 # 在构建 cobra 应用时，我们可以自行组织代码目录结构，但 cobra 建议如下目录结构：\n. ├── cmd │ └── root.go │ └── add.go │ └── xxx.go ├── go.mod └── main.go 生成项目基本结构 # # 创建项目目录 mkdir cli-demo \u0026amp;\u0026amp; cd cli-demo # 初始化go mod go mod init ygang.top/cli-demo # 生成 cobra 项目基本结构 cobra-cli init 生成的root.go\npackage cmd import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;github.com/spf13/cobra\u0026#34; ) // rootCmd 表示调用时不带任何子命令的基本命令 var rootCmd = \u0026amp;cobra.Command{ Use: \u0026#34;cli-demo\u0026#34;, Short: \u0026#34;您的应用程序的简要说明\u0026#34;, Long: `一个跨越多行的较长描述，可能包含使用应用程序的示例和用法。例如： Cobra是Go的一个CLI库，它为应用程序提供支持。 此应用程序是生成所需文件的工具，快速创建Cobra应用程序。`, Run: func(cmd *cobra.Command, args []string) { fmt.Println(\u0026#34;Use \u0026#39;cli-demo --help\u0026#39; for available commands\u0026#34;) }, } // Execute 将所有子命令添加到根命令中，并适当设置标志。 // 这是由main.main()调用的。 func Execute() { err := rootCmd.Execute() if err != nil { os.Exit(1) } } func init() { // 注册标志参数 rootCmd.Flags().BoolP(\u0026#34;toggle\u0026#34;, \u0026#34;t\u0026#34;, false, \u0026#34;Help message for toggle\u0026#34;) } 生成的main.go\npackage main import \u0026#34;ygang.top/cli-demo/cmd\u0026#34; func main() { cmd.Execute() } 添加子命令 # package cmd import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/spf13/cobra\u0026#34; ) var helloCmd = \u0026amp;cobra.Command{ Use: \u0026#34;hello \u0026lt;name\u0026gt;\u0026#34;, Short: \u0026#34;Say Hello for \u0026lt;name\u0026gt;\u0026#34;, Long: \u0026#34;Say Hello for \u0026lt;name\u0026gt;\u0026#34;, Args: cobra.ExactArgs(1), // 必须有一个参数 Run: func(cmd *cobra.Command, args []string) { // 解析标志 turn, err := cmd.Flags().GetBool(\u0026#34;turn\u0026#34;) if err != nil { fmt.Println(err) return } if turn { fmt.Printf(\u0026#34;%s Hello !!!\\n\u0026#34;, args[0]) } else { fmt.Printf(\u0026#34;Hello %s !!!\\n\u0026#34;, args[0]) } }, } func init() { rootCmd.AddCommand(helloCmd) // 添加命令专属标志 helloCmd.Flags().BoolP(\u0026#34;turn\u0026#34;, \u0026#34;t\u0026#34;, false, \u0026#34;can turn output\u0026#34;) } 常用API # Command # Args # 在使用命令的过程中，经常会传入非选项参数，并且需要对这些非选项参数进行验证，cobra 提供了机制来对非选项参数进行验证。可以使用 Command 的 Args 字段来验证非选项参数。\n函数 描述 NoArgs 如果存在任何非选项参数，该命令将报错 ArbitraryArgs 该命令将接受任何非选项参数 OnlyValidArgs 如果有任何非选项参数不在 Command的 ValidArgs字段中，该命令将报错 MinimumNArgs(int) 如果没有至少 N 个非选项参数，该命令将报错 MaximumNArgs(int) 如果有多于 N 个非选项参数，该命令将报错 ExactArgs(int) 如果非选项参数个数不为 N，该命令将报错 ExactValidArgs(int) 如果非选项参数的个数不为 N，或者非选项参数不在 Command的 ValidArgs字段中，该命令将报错 RangeArgs(min, max) 如果非选项参数的个数不在 min和 max之间，该命令将报错 var cmd = \u0026amp;cobra.Command{ Short: \u0026#34;hello\u0026#34;, Args: cobra.MinimumNArgs(1), // 使用内置的验证函数 Run: func(cmd *cobra.Command, args []string) { fmt.Println(\u0026#34;Hello, World!\u0026#34;) }, } 自定义验证\nvar cmd = \u0026amp;cobra.Command{ Short: \u0026#34;hello\u0026#34;, // Args: cobra.MinimumNArgs(10), // 使用内置的验证函数 Args: func(cmd *cobra.Command, args []string) error { // 自定义验证函数 if len(args) \u0026lt; 1 { return errors.New(\u0026#34;requires at least one arg\u0026#34;) } if myapp.IsValidColor(args[0]) { return nil } return fmt.Errorf(\u0026#34;invalid color specified: %s\u0026#34;, args[0]) }, Run: func(cmd *cobra.Command, args []string) { fmt.Println(\u0026#34;Hello, World!\u0026#34;) }, } Help # 在使用应用程序时，我们需要知道该应用程序的调用方法，所以需要有一个 Help 命令或者选项参数，Cobra 的强大之处也在于所有我们需要的功能 cobra 都已经帮我们实现好了。\n我们也可以定义自己的 help 命令。使用如下函数，可以定义 help 命令：\ncmd.SetHelpCommand(cmd *Command) cmd.SetHelpFunc(f func(*Command, []string)) cmd.SetHelpTemplate(s string) Flag # Cobra支持三种标志类型：\nPersistent Flags：对所有子命令生效（在父命令注册） Local Flags：仅对当前命令生效（在子命令注册） Deprecated Flags：标记为废弃的标志（支持过渡兼容） 绑定标志 # helloCmd.Flags().BoolP(\u0026#34;turn\u0026#34;, \u0026#34;t\u0026#34;, false, \u0026#34;can turn output\u0026#34;) 还有一种绑定的方式，可以直接使用变量去接收标志的值\nvar b bool helloCmd.Flags().BoolVarP(\u0026amp;b, \u0026#34;turn\u0026#34;, \u0026#34;t\u0026#34;, false, \u0026#34;can turn output\u0026#34;) 标志解析 # // 在命令Run函数中获取标志值 urgent, _ := cmd.Flags().GetBool(\u0026#34;urgent\u0026#34;) configPath, _ := cmd.Flags().GetString(\u0026#34;config\u0026#34;) 必选标志 # helloCmd.Flags().BoolP(\u0026#34;turn\u0026#34;, \u0026#34;t\u0026#34;, false, \u0026#34;can turn output\u0026#34;) helloCmd.MarkFlagRequired(\u0026#34;turn\u0026#34;) // 设置必选 ","date":"2025-05-22","externalUrl":null,"permalink":"/posts/2d3150b2/a56b86e6/eaa2cb74/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eCobra 基础概念 \n    \u003cdiv id=\"cobra-基础概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#cobra-%e5%9f%ba%e7%a1%80%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eCobra 是一个可以创建强大的现代 CLI 应用程序的库，它还提供了一个可以生成应用和命令文件的程序的命令行工具：\u003ccode\u003ecobra-cli\u003c/code\u003e。有许多大型项目都是用 cobra 来构建他们的应用程序，例如：kubernetes、Docker、Etcd、Rkt、Hugo 等。Cobra 具有很多特性，一些核心特性如下：\u003c/p\u003e","title":"1、Cobra","type":"posts"},{"content":"Go语言中的 strconv 包为我们提供了字符串和基本数据类型之间的转换功能\nstrconv 包中常用的函数包括 Atoi()、Itia()、parse 系列函数、format 系列函数、append 系列函数\nstring 与 int 类型之间的转换 # Itoa()：整型转字符串 # func Itoa(i int) string package main import ( \u0026#34;fmt\u0026#34; \u0026#34;strconv\u0026#34; ) func main() { i := 100 a := strconv.Itoa(i) fmt.Println(a) } Atoi()：字符串转整型 # func Atoi(s string) (i int, err error) //有两个返回值，i 为转换成功的整型，err 在转换成功是为空转换失败时为相应的错误信息 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;strconv\u0026#34; ) func main() { a := \u0026#34;100\u0026#34; i, err := strconv.Atoi(a) if err != nil { fmt.Println(\u0026#34;转换失败\u0026#34;) } else { fmt.Println(i) } } Parse系列函数 # Parse 系列函数用于将字符串转换为指定类型的值，其中包括 ParseBool()、ParseFloat()、ParseInt()、ParseUint()。\nParseBool() # ParseBool() 函数用于将字符串转换为 bool 类型的值，除了特定字符串外，其余值均返回错误\ntrue 1、t、T、true、True、TRUE false 0、f、F、false、False、FALSE func ParseBool(str string) (value bool, err error) package main import ( \u0026#34;fmt\u0026#34; \u0026#34;strconv\u0026#34; ) func main() { s := \u0026#34;0\u0026#34; b, err := strconv.ParseBool(s) if err != nil { fmt.Println(\u0026#34;转换失败\u0026#34;) } else { fmt.Println(b) } } ParseInt() # ParseInt() 函数用于返回字符串表示的整数值（可以包含正负号）\nfunc ParseInt(s string, base int, bitSize int) (i int64, err error) base 指定进制，取值范围是 2 到 36。如果 base 为 0，则会从字符串前置判断，0x是 16 进制，0是 8 进制，否则是 10 进制。 bitSize 指定结果必须能无溢出赋值的整数类型，0、8、16、32、64 分别代表 int、int8、int16、int32、int64。 返回的 err 是 *NumErr 类型的，如果语法有误，err.Error = ErrSyntax，如果结果超出类型范围 err.Error = ErrRange。 ParseUnit() # ParseUint() 函数的功能类似于 ParseInt() 函数，但 ParseUint() 函数不接受正负号，用于无符号整型\nfunc ParseUint(s string, base int, bitSize int) (n uint64, err error) ParseFloat() # ParseFloat() 函数用于将一个表示浮点数的字符串转换为 float 类型\nfunc ParseFloat(s string, bitSize int) (f float64, err error) 如果 s 合乎语法规则，函数会返回最为接近 s 表示值的一个浮点数（使用 IEEE754 规范舍入）。 bitSize 指定了返回值的类型，32 表示 float32，64 表示 float64； 返回值 err 是 *NumErr 类型的，如果语法有误 err.Error=ErrSyntax，如果返回值超出表示范围err.Error= ErrRange。 Format 系列函数 # Format 系列函数实现了将给定类型数据格式化为字符串类型的功能，其中包括 FormatBool()、FormatInt()、FormatUint()、FormatFloat()。\nFormatBool() # FormatBool() 函数可以一个 bool 类型的值转换为对应的字符串类型\nfunc FormatBool(b bool) string FormatInt() # FormatInt() 函数用于将整型数据转换成指定进制并以字符串的形式返回\nfunc FormatInt(i int64, base int) string 参数 i 必须是 int64 类型，参数 base 为进制，必须在 2 到 36 之间，返回结果中会使用小写字母a到z表示大于 10 的数字\nFormatUint() # FormatUint() 函数与 FormatInt() 函数的功能类似，但是参数 i 必须是无符号的 uint64 类型\nfunc FormatUint(i uint64, base int) string FormatFloat() # func FormatFloat(f float64, fmt byte, prec, bitSize int) string fmt 表示格式可以设置为如下值： f表示 -ddd.dddd，常见小数格式 b表示 -ddddp±ddd，指数为二进制 e表示 -d.dddde±dd 十进制指数 E表示 -d.ddddE±dd 十进制指数 g表示指数很大时用e格式，否则f格式 G表示指数很大时用E格式，否则f格式。 prec 控制精度（排除指数部分）：当参数 fmt 为f、e、E时，它表示小数点后的数字个数；当参数 fmt 为g、G时，它控制总的数字个数。如果 prec 为 -1，则代表使用最少数量的、但又必需的数字来表示 f。 bitSize 表示参数 f 的来源类型（32 表示 float32、64 表示 float64），会据此进行舍入。 Append 系列函数 # Append 系列函数用于将指定类型转换成字符串后追加到一个切片中，其中包含 AppendBool()、AppendFloat()、AppendInt()、AppendUint()。\nAppend 系列函数和 Format 系列函数的使用方法类似，只不过是将转换后的结果追加到一个切片中。\n转义字符 # strconv包还提供了处理引号和转义序列的函数：\n// 添加双引号 s := strconv.Quote(\u0026#34;Hello, \\\u0026#34;世界\\\u0026#34;\u0026#34;) fmt.Println(s) // \u0026#34;\\\u0026#34;Hello, \\\\\\\u0026#34;世界\\\\\\\u0026#34;\\\u0026#34;\u0026#34; // 去除双引号 unquoted, err := strconv.Unquote(s) fmt.Println(unquoted, err) // Hello, \u0026#34;世界\u0026#34; \u0026lt;nil\u0026gt; // 处理Go语法中的字符串字面量 s = strconv.QuoteToASCII(\u0026#34;Hello, 世界\u0026#34;) fmt.Println(s) // \u0026#34;\\\u0026#34;Hello, \\\\u4e16\\\\u754c\\\u0026#34;\u0026#34; ","date":"2025-05-22","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/12d682f6/ff28f648/","section":"文章","summary":"\u003cp\u003eGo语言中的 \u003ccode\u003estrconv\u003c/code\u003e 包为我们提供了字符串和基本数据类型之间的转换功能\u003c/p\u003e","title":"1、strconv","type":"posts"},{"content":" Wails简介 # Wails 是一个可让您使用 Go 和 Web 技术编写桌面应用的项目。\n将它看作为 Go 的快并且轻量的 Electron 替代品。 您可以使用 Go 的灵活性和强大功能，结合丰富的现代前端，轻松的构建应用程序。\n后面的笔记将基于 Wails 2.10。\n官方文档：https://wails.io/zh-Hans/docs/introduction\n功能 # 原生菜单、对话框、主题和半透明 Windows、macOS 和 Linux 支持 内置 Svelte、React、Preact、Vue、Lit 和 Vanilla JS 模板 从 JavaScript 轻松调用 Go 方法 自动生成 Go 结构体到 TypeScript 模型 Windows 上无需 CGO 或外部 DLL 利用Vite的强大功能进行实时开发模式 强大的 CLI 可轻松创建、构建和打包应用程序 丰富的运行时库 使用 Wails 构建的应用程序符合 Apple 和 Microsoft Store 的要求 快速启动模板 # Wails 附带许多预配置模板，可帮助您快速启动并运行应用程序。\n提供以下框架的模板：Svelte、React、Vue、Preact、Lit 和 Vanilla。\n每个模板均提供 JavaScript 和 TypeScript 版本。\n原生 # Wails 使用专门构建的库来处理窗口、菜单、对话框等本机元素，因此您可以构建美观、功能丰富的桌面应用程序。\n它不嵌入浏览器，因此运行时占用空间较小。相反，它重用了平台的原生渲染引擎。在 Windows 上，它使用的是基于 Chromium 构建的全新 Microsoft Webview2 库。\nGo 和 JavaScript 互操作 # Wails 会自动将您的 Go 方法提供给 JavaScript，以便您可以从前端通过名称调用它们！它甚至会为您的 Go 方法使用的结构体生成 TypeScript 模型，以便您可以在 Go 和 JavaScript 之间传递相同的数据结构。\n运行时库 # Wails 为 Go 和 JavaScript 提供了一个运行时库，它可以处理现代应用程序需要的很多东西，比如事件、日志记录、对话框等。\n实时开发体验 # 自动重新构建 # 当您在“开发”模式下运行您的应用程序时，Wails 会将您的应用程序构建为原生桌面应用程序，但会从磁盘读取您的资源。 它将检测您的 Go 代码的任何更改并自动重新构建和重新启动您的应用程序。\n自动重新加载 # 当检测到对您的应用程序资产的更改时，您正在运行的应用程序将“重新加载”，几乎立即反映您的更改\n在浏览器中开发您的应用程序 # 如果您更喜欢在浏览器中调试和开发，那么 Wails 可以满足您的需求。 正在运行的应用程序还有一个网络服务器，它将在连接到它的任何浏览器中运行您的应用程序。 当您的资源在磁盘上发生变化时，它会刷新。\n可用于生产的原生二进制文件 # 当您准备好最终构建应用程序时，CLI 会将其编译为单个可执行文件，并将所有资源捆绑到其中。\n在 Windows 和 MacOS 上，可以创建原生包进行分发。\n打包过程中使用的资源（图标、info.plist、清单文件等）是您项目的一部分，可以进行自定义，让您完全掌控应用程序的构建方式。\n安装 # 支持的平台 # Windows 10/11 AMD64/ARM64 MacOS 10.15+ AMD64 用于开发，MacOS 10.13+ 用于发布 MacOS 11.0+ ARM64 Linux AMD64/ARM64 依赖 # Wails 有许多安装前需要的常见依赖项：\nGo 1.21+（macOS 15+ 需要 Go 1.23.3+） NPM（Node 15+） 安装命令 # go install github.com/wailsapp/wails/v2/cmd/wails@latest 检查 # wails doctor 创建项目 # Wails内置 Svelte、React 、Preact 、Vue、Lit 和 Vanilla JS 的模板，后面我们使用Vue创建\n# 基于 JavaScript wails init -n myproject -t vue # 基于 TypeScript wails init -n myproject -t vue-ts 项目结构 # ├── build/ │ ├── appicon.png │ ├── darwin/ │ └── windows/ ├── frontend/ ├── go.mod ├── go.sum ├── main.go └── wails.json /main.go - 主应用 /frontend/ - 前端项目文件 /build/ - 项目构建目录 /build/appicon.png - 应用程序图标 /build/darwin/ - Mac 特定的项目文件 /build/windows/ - Windows 特定的项目文件 /wails.json - 项目配置 /go.mod - Go module 文件 /go.sum - Go module 校验文件 frontend 目录没有特定于 Wails 的内容，可以是您选择的任何前端项目。\nbuild 目录在构建过程中使用。 这些文件可以修改以自定义您的构建。 如果从 build 目录中删除文件，将重新生成默认版本。\ngo.mod 中的默认模块名称是“changeme”。 您应该将其更改为更合适的内容。\n运行项目 # 您可以通过从项目目录运行 wails dev 在开发模式下运行您的应用程序。 这将执行以下操作：\n构建您的应用程序并运行它 将您的 Go 代码绑定到前端，以便可以从 JavaScript 调用它 使用Vite的强大功能，将监视您的 Go 文件中的修改并在更改时重新构建/重新运行 启动一个网络服务器通过浏览器为您的应用程序提供服务。 这使您可以使用自己喜欢的浏览器扩展。 你甚至可以从控制台调用你的 Go 代码。 工作原理 # Wails 应用程序是一个带有一个 webkit 前端的标准的 Go 应用程序。 应用程序的 Go 部分由应用程序代码和一个运行时库组成， 该库提供了许多有用的操作，例如控制应用程序窗口。 前端是一个 webkit 窗口，将显示前端资源。 前端还可以使用运行时库的 JavaScript 版本。 最后，可以将 Go 方法绑定到前端，这些将显示为可以调用的 JavaScript 方法，就像它们是原生 JavaScript 方法一样。\n主应用程序 # 主应用程序由对 wails.Run() 的调用组成。 它接受描述应用程序窗口大小、窗口标题、要使用的资源等应用程序配置。 基本应用程序可能如下所示：\npackage main import ( \u0026#34;context\u0026#34; \u0026#34;embed\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/wailsapp/wails/v2\u0026#34; \u0026#34;github.com/wailsapp/wails/v2/pkg/options\u0026#34; \u0026#34;github.com/wailsapp/wails/v2/pkg/options/assetserver\u0026#34; ) // App struct type App struct { ctx context.Context } // NewApp creates a new App application struct func NewApp() *App { return \u0026amp;App{} } // startup is called when the app starts. The context is saved // so we can call the runtime methods func (a *App) startup(ctx context.Context) { a.ctx = ctx } // Greet returns a greeting for the given name func (a *App) Greet(name string) string { return fmt.Sprintf(\u0026#34;Hello %s, It\u0026#39;s show time!\u0026#34;, name) } //go:embed all:frontend/dist var assets embed.FS func main() { // Create an instance of the app structure app := NewApp() // Create application with options err := wails.Run(\u0026amp;options.App{ Title: \u0026#34;wails-demo\u0026#34;, Width: 1024, Height: 768, AssetServer: \u0026amp;assetserver.Options{ Assets: assets, }, BackgroundColour: \u0026amp;options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, Bind: []interface{}{ app, }, }) if err != nil { println(\u0026#34;Error:\u0026#34;, err.Error()) } } Title - 应该出现在窗口标题栏中的文本 Width \u0026amp; Height - 窗口的尺寸 Assets - 应用程序的前端资产 OnStartup - 创建窗口并即将开始加载前端资源时的回调 OnShutdown - 应用程序即将退出时的回调 Bind - 我们希望向前端暴露的一部分结构体实例 资产 # Assets 选项是必须的，因为您不能拥有没有前端资产的 Wails 应用程序。 这些资产可以是您希望在 Web 应用程序中找到的任何文件 - html、js、css、svg、png 等。 不需要生成资源包 - 普通文件即可。 当应用程序启动时，它将尝试从您的资产中加载 index.html，并且那时起前端基本上将作为浏览器工作。 值得注意的是 embed.FS 对文件所在的位置没有要求。 嵌入路径很可能使用了相对于您的主应用程序代码的嵌套目录，例如 frontend/dist：\n//go:embed all:frontend/dist var assets embed.FS 启动时，Wails 将遍历嵌入的文件，寻找包含的 index.html。 所有其他资源将相对于该目录加载。\n由于可用于生产的二进制文件使用包含在 embed.FS 中的文件，因此应用程序不需要附带任何外部文件。\n在开发模式下使用 wails dev 命令，资产从磁盘加载，任何更改都会导致“实时重新加载”。 资产的位置将从 embed.FS 推断。\n应用程序生命周期回调 # 在即将加载前端 index.html 之前，会对应用启动回调（OnStartup）中提供的函数进行调用。 一个标准的 Go context 被传递给这个方法。 调用运行时需要此 context ，因此标准模式是在此方法中保存对它的引用。 还有一个前端 Dom 加载完成回调（OnDomReady），相当于 JavaScript 中的body onload事件。 还可以通过设置应用关闭前回调（OnBeforeClose）选项来控制窗口关闭（或应用程序退出）事件。 在应用程序关闭之前，以同样的方式调用应用退出回调（OnShutdown）。 方法绑定 # Bind 选项是 Wails 应用程序中最重要的参数选项之一。 它指定向前端暴露哪些结构体方法。\n当应用程序启动时，它会检查 Bind 字段中列出的结构体实例， 确定哪些方法是公开的（以大写字母开头），并生成前端可以调用的这些方法的 JavaScript 版本。\n您可以绑定任意数量的结构体。 只需确保创建它的实例并将其传递给 Bind。\n当您运行 wails dev（或 wails generate module）时，将生成一个前端模块，其中包含以下内容：\n所有绑定方法的 JavaScript 绑定 所有绑定方法的 TypeScript 声明 绑定方法用作输入或输出的所有 Go 结构的 TypeScript 声明 前端 # 前端是由 webkit 渲染的文件集合。 这就合二为一的浏览器和网络服务器。 您可以使用的框架或库1几乎没有限制。 前端和 Go 代码之间的主要交互点是：\n调用绑定的 Go 方法 调用运行时方法 调用绑定的 Go 方法 # 当您使用 wails dev 运行应用程序时，它将自动在名为 wailsjs/go 的目录中为您的结构体生成 JavaScript 绑定（您也可以通过运行 wails generate module 来执行此操作）。 生成的文件反映了应用程序中的包名称。\nexport function Greet(arg1: string): Promise\u0026lt;string\u0026gt;; 生成的方法返回一个 Promise 成功的调用将导致 Go 调用的第一个返回值被传递给 resolve 处理程序。\n不成功的调用是当 Go 方法的第二个返回值具有错误类型时，将错误实例传递回调用者。 这通过 reject 处理程序传回的。\n所有数据类型都在 Go 和 JavaScript 之间正确转换。 包括结构体。 如果您从 Go 调用返回一个结构体，它将作为 JavaScript 类返回到您的前端。\n**注意：**结构体字段必须具有有效的 json 标签，以包含在生成的 TypeScript 中。\n调用运行时方法 # JavaScript 运行时位于window.runtime并包含许多方法来执行各种任务，例如发出事件或执行日志记录操作：\nwindow.runtime.EventsEmit(\u0026#34;my-event\u0026#34;, 1); ","date":"2025-05-22","externalUrl":null,"permalink":"/posts/2d3150b2/9f158c96/f88084c8/0d6bc9cc/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eWails简介 \n    \u003cdiv id=\"wails简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#wails%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eWails 是一个可让您使用 Go 和 Web 技术编写桌面应用的项目。\u003c/p\u003e","title":"1、Wails","type":"posts"},{"content":" GoLang # gopher是一种生活在加拿大的小动物，go的吉祥物就是这个小动物， 它的中文名叫做囊地鼠，他们最大的特点就是挖洞速度特别快，当然可能不止是挖洞啦。\nGo 编程语言是Google公司的一个开源项目，它使程序员更具生产力。Go 语言具有很强的表达能力，它简洁、清晰而高效。得益于其并发机制， 用它编写的程序能够非常有效地利用多核与联网的计算机，其新颖的类型系统则使程序结构变得灵活而模块化。 Go 代码编译成机器码不仅非常迅速，还具有方便的垃圾收集机制和强大的运行时反射机制。 它是一个快速的、静态类型的编译型语言，感觉却像动态类型的解释型语言。(摘取自官网)\nGo语言（或 Golang）起源于 2007 年，并在 2009 年正式对外发布。Go 是非常年轻的一门语言，它的主要目标是兼具Python 等动态语言的开发速度和 C/C++ 等编译型语言的性能与安全性。\nGo语言是编程语言设计的又一次尝试，是对类C语言的重大改进，它不但能让你访问底层操作系统，还提供了强大的网络编程和并发编程支持。Go语言的用途众多，可以进行网络编程、系统编程、并发编程、分布式编程。\n此外，很多重要的开源项目都是使用Go语言开发的，其中包括 Docker、Go-Ethereum、Thrraform 和 Kubernetes。\n创始人 # Ken Thompson 贝尔实验室 Unix 团队成员，C语言、Unix 和 Plan 9 的创始人之一，在 20 世纪 70 年代，设计并实现了最初的 UNIX 操作系统，仅从这一点说，他对计算机科学的贡献怎么强调都不过分。他还与 Rob Pike 合作设计了 UTF-8 编码方案。 Rob Pike Go语言项目总负责人，贝尔实验室 Unix 团队成员，除帮助设计 UTF-8 外，还帮助开发了分布式多用户操作系统 Plan 9、Inferno 操作系统和 Limbo 编程语言，并与人合著了《The Unix Programming Environment》，对 UNIX 的设计理念做了正统的阐述。 Robert Griesemer 就职于 Google，参与开发 Java HotSpot 虚拟机，对语言设计有深入的认识，并负责 Chrome 浏览器和 Node.js 使用的 Google V8 JavaScript引擎的代码生成部分。 思想 # Less can be more\n大道至简，小而蕴真 让事情变得复杂很容易，让事情变得简单才难 深刻的工程文化 核心特性 # 1、并发编程 # Go语言在并发编程方面比绝大多数语言要简洁不少，这一点是其最大亮点之一，也是其在未来进入高并发高性能场景的重要筹码。\n不同于传统的多进程或多线程，golang的并发执行单元是一种称为goroutine的协程。\n由于在共享数据场景中会用到锁，再加上GC，其并发性能有时不如异步复用IO模型，因此相对于大多数语言来说，golang的并发编程简单比并发性能更具卖点。\n在当今这个多核时代，并发编程的意义不言而喻。当然，很多语言都支持多线程、多进程编程，但遗憾的是，实现和控制起来并不是那么令人感觉轻松和愉悦。Golang不同的是，语言级别支持协程goroutine并发（协程又称微线程，比线程更轻量、开销更小，性能更高），操作起来非常简单，语言级别提供关键字go用于启动协程，并且在同一台机器上可以启动成千上万个协程。协程经常被理解为轻量级线程，一个线程可以包含多个协程，共享堆不共享栈。协程间一般由应用程序显式实现调度，上下文切换无需下到内核层，高效不少。协程间一般不做同步通讯，而golang中实现协程间通讯有两种：1、共享内存型，即使用全局变量加mutex锁来实现数据共享；2、消息传递型，即使用一种独有的channel机制进行异步通讯。\n对比Java的多线程和GO的协程实现，明显更直接、简单。这就是GO的魅力所在，以简单、高效的方式解决问题，关键字go，或许就是GO语言最重要的标志。\n2、内存回收(GC) # 从C到C++，从程序性能的角度来考虑，这两种语言允许程序员自己管理内存，包括内存的申请和释放等。因为没有垃圾回收机制所以C/C++运行起来速度很快，但是随着而来的是程序员对内存使用上的很谨小慎微的考虑。因为哪怕一点不小心就可能会导致“内存泄露”使得资源浪费或者“野指针”使得程序崩溃等，尽管C++11后来使用了智能指针的概念，但是程序员仍然需要很小心的使用。后来为了提高程序开发的速度以及程序的健壮性，Java和C#等高级语言引入了GC机制，即程序员不需要再考虑内存的回收等，而是由语言特性提供垃圾回收器来回收内存。但是随之而来的可能是程序运行效率的降低。\nGC过程是：先stop the world，扫描所有对象判活，把可回收对象在一段bitmap区中标记下来，接着立即start the world，恢复服务，同时起一个专门gorountine回收内存到空闲list中以备复用，不物理释放。物理释放由专门线程定期来执行。\nGC瓶颈在于每次都要扫描所有对象来判活，待收集的对象数目越多，速度越慢。一个经验值是扫描10w个对象需要花费1ms，所以尽量使用对象少的方案，比如我们同时考虑链表、map、slice、数组来进行存储，链表和map每个元素都是一个对象，而slice或数组是一个对象，因此slice或数组有利于GC。\nGC性能可能随着版本不断更新会不断优化，这块没仔细调研，团队中有HotSpot开发者，应该会借鉴jvm gc的设计思想，比如分代回收、safepoint等。\n内存自动回收，再也不需要开发人员管理内存 开发人员专注业务实现，降低了心智负担 只需要new分配内存，不需要释放 3、内存分配 # 初始化阶段直接分配一块大内存区域，大内存被切分成各个大小等级的块，放入不同的空闲list中，对象分配空间时从空闲list中取出大小合适的内存块。内存回收时，会把不用的内存重放回空闲list。空闲内存会按照一定策略合并，以减少碎片。\n4、编译 # 编译涉及到两个问题：编译速度和依赖管理\n目前Golang具有两种编译器，一种是建立在GCC基础上的Gccgo，另外一种是分别针对64位x64和32位x86计算机的一套编译器(6g和8g)。\n依赖管理方面，由于golang绝大多数第三方开源库都在github上，在代码的import中加上对应的github路径就可以使用了，库会默认下载到工程的pkg目录下。\n另外，编译时会默认检查代码中所有实体的使用情况，凡是没使用到的package或变量，都会编译不通过。这是golang挺严谨的一面。\n5、网络编程 # 由于golang诞生在互联网时代，因此它天生具备了去中心化、分布式等特性，具体表现之一就是提供了丰富便捷的网络编程接口，比如socket用net.Dial(基于tcp/udp，封装了传统的connect、listen、accept等接口)、http用http.Get/Post()、rpc用client.Call(‘class_name.method_name’, args, \u0026amp;reply)，等等。\n6、函数多返回值 # 在C，C++中，包括其他的一些高级语言是不支持多个函数返回值的。但是这项功能又确实是需要的，所以在C语言中一般通过将返回值定义成一个结构体，或者通过函数的参数引用的形式进行返回。而在Go语言中，作为一种新型的语言，目标定位为强大的语言当然不能放弃对这一需求的满足，所以支持函数多返回值是必须的。\n函数定义时可以在入参后面再加(a,b,c)，表示将有3个返回值a、b、c。这个特性在很多语言都有，比如python。\n这个语法糖特性是有现实意义的，比如我们经常会要求接口返回一个三元组（errno,errmsg,data），在大多数只允许一个返回值的语言中，我们只能将三元组放入一个map或数组中返回，接收方还要写代码来检查返回值中包含了三元组，如果允许多返回值，则直接在函数定义层面上就做了强制，使代码更简洁安全。\n7、语言交互性 # 在Go语言中直接重用了大部份的C模块，这里称为Cgo。Cgo允许开发者混合编写C语言代码，然后Cgo工具可以将这些混合的C代码提取并生成对于C功能的调用包装代码。开发者基本上可以完全忽略这个Go语言和C语言的边界是如何跨越的。\ngolang可以和C程序交互，但不能和C++交互。可以有两种替代方案：1、先将c++编译成动态库，再由go调用一段c代码，c代码通过dlfcn库动态调用动态库（记得export LD_LIBRARY_PATH）；2、使用swig。\n8、异常处理 # golang不支持try…catch这样的结构化的异常解决方式，因为觉得会增加代码量，且会被滥用，不管多小的异常都抛出。golang提倡的异常处理方式是：\n普通异常：被调用方返回error对象，调用方判断error对象。 严重异常：指的是中断性panic（比如除0），使用defer…recover…panic机制来捕获处理。严重异常一般由golang内部自动抛出，不需要用户主动抛出，避免传统try…catch写得到处都是的情况。当然，用户也可以使用panic(‘xxxx’)主动抛出，只是这样就使这一套机制退化成结构化异常机制了。 9、其他特性 # 类型推导：类型定义：支持var abc = 10这样的语法，让golang看上去有点像动态类型语言，但golang实际上是强类型的，前面的定义会被自动推导出是int类型。\n作为强类型语言，隐式的类型转换是不被允许的，记住一条原则：让所有的东西都是显式的。简单来说，Go是一门写起来像动态语言，有着动态语言开发效率的静态语言。\n一个类型只要实现了某个interface的所有方法，即可实现该interface，无需显式去继承。\nGo编程规范推荐每个Interface只提供一到两个的方法。这样使得每个接口的目的非常清晰。另外Go的隐式推导也使得我们组织程序架构的时候更加灵活。在写JAVA／C++程序的时候，我们一开始就需要把父类／子类／接口设计好，因为一旦后面有变更，修改起来会非常痛苦。而Go不一样，当你在实现的过程中发现某些方法可以抽象成接口的时候，你直接定义好这个接口就OK了，其他代码不需要做任何修改，编译器的自动推导会帮你做好一切。\n不能循环引用：即如果a.go中import了b，则b.go要是import a会报import cycle not allowed。好处是可以避免一些潜在的编程危险，比如a中的func1()调用了b中的func2()，如果func2()也能调用func1()，将会导致无限循环调用下去。\ndefer机制：在Go语言中，提供关键字defer，可以通过该关键字指定需要延迟执行的逻辑体，即在函数体return前或出现panic时执行。这种机制非常适合善后逻辑处理，比如可以尽早避免可能出现的资源泄漏问题。\n可以说，defer是继goroutine和channel之后的另一个非常重要、实用的语言特性，对defer的引入，在很大程度上可以简化编程，并且在语言描述上显得更为自然，极大的增强了代码的可读性。\n“包”的概念：和python一样，把相同功能的代码放到一个目录，称之为包。包可以被其他包引用。main包是用来生成可执行文件，每个程序只有一个main包。包的主要用途是提高代码的可复用性。通过package可以引入其他包。\n编程规范：GO语言的编程规范强制集成在语言中，比如明确规定花括号摆放位置，强制要求一行一句，不允许导入没有使用的包，不允许定义没有使用的变量，提供gofmt工具强制格式化代码等等。奇怪的是，这些也引起了很多程序员的不满，有人发表GO语言的XX条罪状，里面就不乏对编程规范的指责。要知道，从工程管理的角度，任何一个开发团队都会对特定语言制定特定的编程规范，特别像Google这样的公司，更是如此。GO的设计者们认为，与其将规范写在文档里，还不如强制集成在语言里，这样更直接，更有利用团队协作和工程管理。\n交叉编译：比如说你可以在运行 Linux 系统的计算机上开发运行 Windows 下运行的应用程序。这是第一门完全支持 UTF-8 的编程语言，这不仅体现在它可以处理使用 UTF-8 编码的字符串，就连它的源码文件格式都是使用的 UTF-8 编码。Go 语言做到了真正的国际化！\n适合用来做什么 # 云原生应用：Docker、Kubernetes、Etcd等核心工具均用Go编写 微服务架构：适合开发高并发、分布式的API服务 DevOps工具：Prometheus、Grafana等监控工具 分布式系统：分布式数据库、消息队列、缓存系统 网络编程：代理服务器、负载均衡、网关 安装sdk # 下载 # Windows # 官网：https://golang.google.cn/\nmsi可以直接安装到windows系统，全程下一步完事\nMacOS # brew install go 目录结构 # 目录名 说明 api 每个版本的 api 变更差异 bin go 源码包编译出的编译器（go）、文档工具（godoc）、格式化工具（gofmt） doc 英文版的 Go 文档 lib 引用的一些库文件 misc 杂项用途的文件，例如Android平台的编译、git 的提交钩子等 pkg Windows 平台编译好的中间文件 src 标准库的源码 test 测试用例 验证安装 # 执行命令：go version\n环境变量 # GOROOT # GOROOT也就是golang的安装目录根路径，配置到环境变量就好了\n如果要使用go命令，需要将%GOROOT%/bin配置到系统环境变量中\nGOPATH # 在Go 1.11之前，所有Go代码必须放在GOPATH下。从Go 1.11开始，引入了Go Modules，使项目可以放在任意位置。不过，了解GOPATH仍然很重要。\n注意：如果需要在命令行中直接调用GOPATH中install的工具，就需要将$GOPATH/bin配置到环境变量PATH中。\n要查看你当前的GOPATH设置，可以运行：go env GOPATH\nGOPATH通常包含三个子目录：\nsrc：存放源代码 pkg：存放编译后的包文件 bin：存放可执行文件 GOPROXY # proxy 顾名思义就是代理服务器的意思。Go语言在 1.13 版本之后 GOPROXY 默认值为https://proxy.golang.org，由于国内的网络有防火墙的存在，这导致有些Go语言的第三方包我们无法直接通过go get命令获取。GOPROXY 是Go语言官方提供的一种通过中间代理商来为用户提供包下载服务的方式。要使用 GOPROXY 只需要设置环境变量 GOPROXY 即可。\n目前公开的代理服务器的地址有：\ngoproxy.io goproxy.cn（推荐）由国内的七牛云提供。 Windows 下设置 GOPROXY 的命令为：\ngo env -w GOPROXY=https://goproxy.cn MacOS 或 Linux 下设置 GOPROXY 的命令为：\nexport GOPROXY=https://goproxy.cn 测试配置 # 执行命令：go env\nGo工程结构 # GOPATH模式（弃用） # 注意：此项目管理方式为最早的go1.0时所使用，现在不推荐使用，原因是：GOPATH模式下没有版本控制的概念，在执行 go get 的时候，获取的永远是最新的依赖包，下载到GOPATH/src，如果你有两个工程依赖一个包的v1和v2版本，则会发生冲突，因为 GOPATH 模式下两个工程内依赖的导入路径都是一样的，因此两个工程获取的都是v2版本。\n前面搭建Go语言开发环境时添加了环境变量 GOPATH，项目的构建主要是靠它来实现的。就是说，如果想要构建一个项目，就需要将这个项目的目录添加到 GOPATH 中，多个项目之间可以使用;分隔。\n如果不配置 GOPATH，即使处于同一目录，代码之间也无法通过绝对路径相互调用。\n--GOPATH --src //所有源代码都存放到这个文件 --project1 //目录一般为包名称 --xx.go //源码文件 --main.go //源码文件 --project2 --xx.go //源码文件 --bin --pkg //自动生成,不需要创建 一个Go语言项目的目录一般包含以下三个子目录：\nsrc 目录：放置项目和库的源文件； 用于以包（package）的形式组织并存放 Go 源文件，这里的包与 src 下的每个子目录是一一对应。例如，若一个源文件被声明属于 log 包，那么它就应当保存在 src/log 目录中。 并不是说 src 目录下不能存放 Go 源文件，一般在测试或演示的时候也可以把 Go 源文件直接放在 src 目录下，但是这么做的话就只能声明该源文件属于 main 包了。正常开发中还是建议大家把 Go 源文件放入特定的目录中。 包是Go语言管理代码的重要机制，其作用类似于Java中的 package 和 C/C++的头文件。Go 源文件中第一段有效代码必须是package \u0026lt;包名\u0026gt; 的形式，如 package hello。 另外需要注意的是，Go语言会把通过go get命令获取到的库源文件下载到 src 目录下对应的文件夹当中。 pkg 目录：放置编译后生成的包/库的归档文件； 用于存放通过go install 命令安装某个包后的归档文件。归档文件是指那些名称以“.a”结尾的文件。 该目录与 GOROOT 目录（也就是Go语言的安装目录）下的 pkg 目录功能类似，区别在于这里的 pkg 目录专门用来存放项目代码的归档文件。 编译和安装项目代码的过程一般会以代码包为单位进行，比如 log 包被编译安装后，将生成一个名为 log.a 的归档文件，并存放在当前项目的 pkg 目录下。 bin 目录：放置编译后生成的可执行文件。 与 pkg 目录类似，在通过go install 命令完成安装后，保存由 Go 命令源文件生成的可执行文件。在类 Unix 操作系统下，这个可执行文件的名称与命令源文件的文件名相同。而在 Windows 操作系统下，这个可执行文件的名称则是命令源文件的文件名加 .exe 后缀。 社区推荐结构 # 由于现在的go项目不需要再放在GOPATH下了，目前社区最推荐的项目结构为：https://github.com/golang-standards/project-layout，虽然它并不是官方和社区的规范，但因为组织方式比较合理，被很多 Go 开发人员接受。\n应用开发目录 # web：前端代码存放目录，主要用来存放 Web 静态资源，服务端模板和单页应用（SPAs）。\ncmd：一个项目中可以会有多个组件（模块），对应的命令源码文件存放在这里。\ninternal：存放私有的库源码文件。你不希望在其他应用和库中被导入，可以将这些代码放在这里，因为在引入其它项目 internal 下的包时，Go 语言会在编译时报错。\npkg：该目录中存放可以被外部应用使用的库源码文件，其他项目可以直接通过 import 导入这里的代码。\nvendor：项目依赖，可通过 go mod vendor 创建。需要注意的是，如果是一个 Go 库，不要提交 vendor 依赖包。\nthird_party：外部帮助工具，分支代码或其他第三方应用（例如 Swagger UI）。比如我们 fork 了一个第三方 go 包，并做了一些小的改动，我们可以放在目录 /third_party/forked 下。\n应用测试目录 # test：用于存放其他外部测试应用和测试数据。/test 目录的构建方式比较灵活：对于大的项目，有一个数据子目录是有意义的。例如，如果需要 Go 忽略该目录中的内容，可以使用 /test/data 或 /test/testdata 目录。需要注意的是，Go 也会忽略以.或_开头的目录或文件。这样在命名测试数据目录方面，可以具有更大的灵活性。 应用部署目录 # configs：这个目录用来配置文件模板或默认配置。 deployments：用来存放 Iaas、PaaS 系统和容器编排部署配置和模板（Docker-Compose，Kubernetes/Helm，Mesos，Terraform，Bosh）。在一些项目，特别是用 Kubernetes 部署的项目中，这个目录可能命名为 deploy。 init：存放初始化系统（systemd，upstart，sysv）和进程管理配置文件（runit，supervisord）。比如 sysemd 的 unit 文件。这类文件，在非容器化部署的项目中会用到。 项目管理目录 # Makefile：一个 Go 项目在其根目录下应该有一个 Makefile 工具，用来对项目进行命令管理，Makefile 通常用来执行静态代码检查、单元测试、编译等功能。 scripts：该目录主要用来存放脚本文件，实现构建、安装、分析等不同功能。通常可以考虑包含以下 3 个目录： /scripts/make-rules：用来存放 makefile 文件，实现 /Makefile 文件中的各个功能。Makefile 有很多功能，为了保持它的简洁，我建议你将各个功能的具体实现放在 /scripts/make-rules 文件夹下 /scripts/lib：shell 库，用来存放 shell 脚本。一个大型项目中有很多自动化任务，比如发布、更新文档、生成代码等，所以要写很多 shell 脚本，这些 shell 脚本会有一些通用功能，可以抽象成库，存放在 /scripts/lib 目录下，比如 logging.sh，util.sh 等。 /scripts/install：如果项目支持自动化部署，可以将自动化部署脚本放在此目录下。如果部署脚本简单，也可以直接放在 /scripts 目录下。 build：这里存放安装包和持续集成相关的文件。这个目录下有 3 个大概率会使用到的目录，在设计目录结构时可以考虑进去。 /build/package：存放容器（Docker）、系统（deb, rpm, pkg）的包配置和脚本。 /build/ci：存放 CI的配置文件和脚本。 /build/docker：存放子项目各个组件的 Dockerfile 文件。 tools：存放这个项目的支持工具。这些工具可导入来自 /pkg 和 /internal 目录的代码。 githooks：Git 钩子。比如，我们可以将 commit-msg 存放在该目录。 assets：项目使用的其他资源 (图片、CSS、JavaScript 等)。 website：如果你不使用 Github 页面，则在这里放置项目的网站数据。 应用文档目录 # README.md：项目的 README 文件一般包含了项目的介绍、功能、快速安装和使用指引、详细的文档链接以及开发指引等。 docs：存放设计文档、开发文档和用户文档等（除了 godoc 生成的文档）。推荐存放以下几个子目录： /docs/devel/{en-US,zh-CN}：存放开发文档、hack 文档等。 /docs/guide/{en-US,zh-CN}: 存放用户手册，安装、quickstart、产品文档等，分为中文文档和英文文档。 /docs/images：存放图片文件。 CONTRIBUTING.md：开源就绪的项目，用来说明如何贡献代码，如何开源协同等等。CONTRIBUTING.md 不仅能够规范协同流程，还能降低第三方开发者贡献代码的难度。 api：存放的是当前项目对外提供的各种不同类型的 API 接口定义文件，其中可能包含类似 /api/protobuf-spec、/api/thrift-spec、/api/http-spec、openapi、swagger 的目录，这些目录包含了当前项目对外提供和依赖的所有 API 文件。 LICENSE：版权文件可以是私有的，也可以是开源的。常用的开源协议有：Apache 2.0、MIT、BSD、GPL、Mozilla、LGPL。 CHANGELOG：当项目有更新时，为了方便了解当前版本的更新内容或者历史更新内容，需要将更新记录存放到 CHANGELOG 目录。 examples：存放应用程序或者公共包的示例代码 建议 # 不要使用src目录：在默认情况下，Go 语言的项目都会被放置到 $GOPATH/src 目录下（GOPATH模式的时候）。这个目录中存放着所有代码，如果我们在自己的项目中使用 /src 目录，这个包的导入路径中就会出现两个 src。\n对于小型项目，可以考虑先包含 cmd、pkg、internal 3 个目录，其他目录后面按需创建。\nIDE # Goland # VsCode # 使用需要安装插件\n安装后重启vscode，使用ctrl+shift+p，搜索go:install/update tools，全选进行安装\n安装完成后，可以看到bin目录下出现这些可执行工具\nHelloWorld # 创建工程 # mkdir helloworld cd helloworld go mod init ygang.top/helloworld main.go # //这是一个main包 //这是main包的包注释 package main // 声明 main 包 import ( \u0026#34;fmt\u0026#34; // 导入 fmt 包，打印字符串是需要用到 ) func main() { // 声明 main 主函数 fmt.Println(\u0026#34;Hello World!\u0026#34;) // 打印 Hello World! } package # Go语言以“包”作为管理单位，每个 Go 源文件必须先声明它所属的包，格式如下：\npackage name package 是声明包名的关键字，name 为包的名字\n一个目录下的同级文件属于同一个包。 包名可以与其目录名不同。 main 包是Go语言程序的入口包，一个Go语言程序必须有且仅有一个 main 包。如果一个程序没有 main 包，那么编译时将会出错，无法生成可执行文件。 import # 在包声明之后，是 import 语句，用于导入程序中所依赖的包，导入的包名使用双引号\u0026quot;\u0026quot;包围，如果一个import导入多个包，需要用()包围，并且每个包名占一行，格式如下：\nimport \u0026#34;name\u0026#34; import( \u0026#34;name1\u0026#34; \u0026#34;name2\u0026#34; ) 注意，导入的包中不能含有代码中没有使用到的包，否则Go编译器会报编译错误，例如 imported and not used: \u0026quot;xxx\u0026quot;\nmain 函数 # main 函数，它是Go语言程序的入口函数，也即程序启动后运行的第一个函数。main 函数只能声明在 main 包中，不能声明在其他包中，并且，一个 main 包中也必须有且仅有一个 main 函数。\nfunc 函数名 (参数列表) (返回值列表){ 函数体 } 函数名：由字母、数字、下画线_组成，其中，函数名的第一个字母不能为数字，并且，在同一个包内，函数名称不能重名。 参数列表：一个参数由参数变量和参数类型组成，例如func foo(a int, b string)。 返回值列表：可以是返回值类型列表，也可以是参数列表那样变量名与类型的组合，函数有返回值时，必须在函数体中使用 return 语句返回。 函数体：能够被重复调用的代码片段。 注意：Go语言函数的左大括号{必须和函数名称在同一行，否则会报错。\n运行和编译 # Go语言是编译型的静态语言（和C语言一样），所以在运行Go语言程序之前，先要将其编译成二进制的可执行文件。\n可以通过Go语言提供的go build或者go run命令对Go语言程序进行编译：\ngo build命令可以将Go语言程序代码编译成二进制的可执行文件，但是需要我们手动运行该二进制文件； go run main.go命令则更加方便，它会在编译后直接运行Go语言程序，编译过程中会产生一个临时文件，但不会生成可执行文件，这个特点很适合用来调试程序。 语法 # 行分隔符 # 在 Go 程序中，一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号;结尾，因为这些工作都将由 Go 编译器自动完成。\n如果你打算将多个语句写在同一行，它们则必须使用;人为区分，但在实际开发中并不鼓励这种做法。\nfmt.Println(\u0026#34;Hello, World!\u0026#34;) fmt.Println(\u0026#34;你好\u0026#34;) 注释 # go 中的注释，主要包括了包注释、命令注释、类型注释、函数注释、变量/常量注释部分。\n单行注释是最常见的注释形式，可以在任何地方使用以//开头的单行注释；多行注释也叫块注释，以/*开头，并以 */ 结尾。\n// 单行注释 /* 多行 注释 */ 包注释 # 每个程序包（Package）都应该有一个包注释，该注释介绍了整个Package相关的信息，并且通常设定了对Package的期望效果。\n// Package path implements utility routines for manipulating slash-separated // paths. // // The path package should only be used for paths separated by forward // slashes, such as the paths in URLs. This package does not deal with // Windows paths with drive letters or backslashes; to manipulate // operating system paths, use the [path/filepath] package. package path go doc与godoc # go doc是Go自带的命令，可以通过go doc packageName查看包的说明\ngodoc需要额外下载并安装\ngo get -u golang.org/x/tools/cmd/godoc go install golang.org/x/tools/cmd/godoc@latest 可以使用godoc -http=:6060查看GO API文档，如果在项目目录中使用，还包含了项目的API文档\n标识符 # 标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(A-Z和a-z)数字(0-9)、下划线_组成的序列，但是首个字符不能是数字，标识符不可以使用关键字或保留字\nGo的标识符支持中文，但是不建议使用中文！ Go标识符严格区分大小写！ Go 代码中会使用到的 25 个关键字或保留字\nbreak default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var 除了以上关键字，Go 语言还有 36 个预定义标识符\nappend bool byte cap close complex complex64 complex128 uint16 copy false float32 float64 imag int int8 int16 uint32 int32 int64 iota len make new nil panic uint64 print println real recover string true uint uint8 uintptr 命名规范 # 包和文件目录命名规范 # 包是代码组织的基本单元。Go语言中的包名称应该是简洁、有意义的，并且应该是小写字母。包名称应该是唯一的，能够清晰地反映出包所提供的功能。\n包名统一小写，下划线分隔，不要混合大小写（同一个项目下是不允许存在同名的包（package）的。每个包的导入路径（import path）在整个项目中必须是唯一的）\n// 推荐： package order package order_server // 不推荐： package My_Package package MyPackage package Mypackage 文件名称命名规范 # Go语言中的文件名称应该与包名称保持一致。通常情况下，使用小写字母和单词之间的下划线来命名文件。\n// 推荐： order_service.go // 不推荐： OrderService.go orderservice.go 变量和常量命名规范 # **局部变量/常量使用小驼峰命名法：**即首个单词小写，后续单词首字母大写。\n// 推荐： var orderPrice string // 不推荐： var order_price string **全局变量/常量使用大驼峰命名法：**即首个单词大写，后续单词首字母大写。\n// 推荐： var MongoClient string // 不推荐： var Mongo_Client string 函数和方法命名规范 # **公有方法使用大驼峰命名法：**函数和方法名同样使用驼峰命名法。\n// 推荐： func OrderSum() int // 不推荐： func order_sum() int **私有方法使用小驼峰命名法：**函数和方法名同样使用驼峰命名法。\n// 推荐： func orderSum() int // 不推荐： func order_sum() int 结构体和接口命名规范 # 使用大驼峰命名法: 结构体和接口名同样应该使用驼峰命名法\n// 推荐： // 结构体 type OrderDTO struct {} // 接口 type OrderInterface interface {} // 不推荐： // 结构体 type orderDTO struct {} // 接口 type orderInterface interface {} API文档地址 # 中文文档：https://studygolang.com/pkgdoc\n","date":"2025-05-19","externalUrl":null,"permalink":"/posts/2d3150b2/02a0817f/1ad3dbef/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eGoLang \n    \u003cdiv id=\"golang\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#golang\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"img\"\n    data-zoom-src=\"/posts/2d3150b2/02a0817f/1ad3dbef/image/go128.png\"\n    src=\"/posts/2d3150b2/02a0817f/1ad3dbef/image/go128.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"1、GoLang概述","type":"posts"},{"content":"Go语言的net/http包不仅提供了HTTP服务器功能，还包含了功能强大的HTTP客户端API，让我们能够轻松地与各种Web服务进行交互。\n简单请求 # Get # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { resp, err := http.Get(\u0026#34;https://www.baidu.com/\u0026#34;) if err != nil { fmt.Println(err) } defer resp.Body.Close() // 响应码 fmt.Println(resp.Status) // 响应头 fmt.Println(resp.Header) // 响应体 result, _ := io.ReadAll(resp.Body) fmt.Println(string(result)) } Post # package main import ( \u0026#34;bytes\u0026#34; \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { data := map[string]any{ \u0026#34;name\u0026#34;: \u0026#34;lucy\u0026#34;, \u0026#34;age\u0026#34;: 18, } j, _ := json.Marshal(data) resp, _ := http.Post(\u0026#34;http://localhost:8080/test\u0026#34;, \u0026#34;application/json\u0026#34;, bytes.NewReader(j)) bs, _ := io.ReadAll(resp.Body) fmt.Println(string(bs)) } 其他方法 # 使用net/http包来发送http请求，支持的方法有：\nconst ( MethodGet = \u0026#34;GET\u0026#34; MethodHead = \u0026#34;HEAD\u0026#34; MethodPost = \u0026#34;POST\u0026#34; MethodPut = \u0026#34;PUT\u0026#34; MethodPatch = \u0026#34;PATCH\u0026#34; // RFC 5789 MethodDelete = \u0026#34;DELETE\u0026#34; MethodConnect = \u0026#34;CONNECT\u0026#34; MethodOptions = \u0026#34;OPTIONS\u0026#34; MethodTrace = \u0026#34;TRACE\u0026#34; ) 示例：\n// POST请求 resp, err := http.Post(\u0026#34;https://httpbin.org/post\u0026#34;, \u0026#34;application/json\u0026#34;, strings.NewReader(`{\u0026#34;name\u0026#34;:\u0026#34;gopher\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;hello\u0026#34;}`)) // PUT请求 req, err := http.NewRequest(http.MethodPut, \u0026#34;https://httpbin.org/put\u0026#34;, strings.NewReader(`{\u0026#34;name\u0026#34;:\u0026#34;gopher\u0026#34;,\u0026#34;status\u0026#34;:\u0026#34;updated\u0026#34;}`)) client := \u0026amp;http.Client{} resp, err := client.Do(req) // DELETE请求 req, err := http.NewRequest(http.MethodDelete, \u0026#34;https://httpbin.org/delete?id=123\u0026#34;, nil) resp, err := client.Do(req) 请求头与请求参数 # 设置请求头和URL参数是常见需求：\n// 创建请求 req, err := http.NewRequest(\u0026#34;GET\u0026#34;, \u0026#34;https://api.example.com/users\u0026#34;, nil) if err != nil { log.Fatal(err) } // 添加请求头 req.Header.Add(\u0026#34;Authorization\u0026#34;, \u0026#34;Bearer token123456\u0026#34;) req.Header.Add(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) req.Header.Add(\u0026#34;User-Agent\u0026#34;, \u0026#34;GoHTTPClient/1.0\u0026#34;) // 添加URL参数 q := req.URL.Query() q.Add(\u0026#34;page\u0026#34;, \u0026#34;1\u0026#34;) q.Add(\u0026#34;limit\u0026#34;, \u0026#34;10\u0026#34;) q.Add(\u0026#34;sort\u0026#34;, \u0026#34;created_at\u0026#34;) req.URL.RawQuery = q.Encode() // 发送请求 client := \u0026amp;http.Client{} resp, err := client.Do(req) // ...处理响应 处理HTTP响应 # 读取响应体 # 处理HTTP响应的核心是正确读取响应体。前面我们已经看到了使用io.ReadAll的方式，但还有其他模式：\n// 分块读取大型响应 resp, err := http.Get(\u0026#34;https://example.com/large-file\u0026#34;) if err != nil { log.Fatal(err) } defer resp.Body.Close() // 使用缓冲读取 buf := make([]byte, 1024) for { n, err := resp.Body.Read(buf) if err == io.EOF { break // 读取完成 } if err != nil { log.Fatal(err) } // 处理读取的部分数据 fmt.Printf(\u0026#34;读取了 %d 字节的数据\\n\u0026#34;, n) fmt.Printf(\u0026#34;数据片段: %s\\n\u0026#34;, buf[:n]) } 对于结构化数据（如JSON），可以直接解码：\npackage main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { resp, err := http.Get(\u0026#34;https://api.github.com/users/golang\u0026#34;) if err != nil { log.Fatal(err) } defer resp.Body.Close() var user struct { Login string `json:\u0026#34;login\u0026#34;` ID int `json:\u0026#34;id\u0026#34;` Name string `json:\u0026#34;name\u0026#34;` Bio string `json:\u0026#34;bio\u0026#34;` Public struct { Repos int `json:\u0026#34;public_repos\u0026#34;` } `json:\u0026#34;public_repos\u0026#34;` } if err := json.NewDecoder(resp.Body).Decode(\u0026amp;user); err != nil { log.Fatal(err) } fmt.Printf(\u0026#34;用户信息: %+v\\n\u0026#34;, user) } 处理状态码 # resp, err := http.Get(\u0026#34;https://example.com/api/resource\u0026#34;) if err != nil { log.Fatal(err) } defer resp.Body.Close() switch resp.StatusCode { case http.StatusOK, http.StatusCreated, http.StatusAccepted: // 请求成功 fmt.Println(\u0026#34;请求成功!\u0026#34;) // 处理响应... case http.StatusNotFound: fmt.Println(\u0026#34;资源不存在\u0026#34;) case http.StatusUnauthorized, http.StatusForbidden: fmt.Println(\u0026#34;认证或授权失败\u0026#34;) case http.StatusInternalServerError: fmt.Println(\u0026#34;服务器错误\u0026#34;) default: fmt.Printf(\u0026#34;未预期的状态码: %d\\n\u0026#34;, resp.StatusCode) } 处理响应头 # resp, err := http.Get(\u0026#34;https://example.com\u0026#34;) if err != nil { log.Fatal(err) } defer resp.Body.Close() // 获取特定响应头 contentType := resp.Header.Get(\u0026#34;Content-Type\u0026#34;) fmt.Printf(\u0026#34;内容类型: %s\\n\u0026#34;, contentType) // 遍历所有响应头 fmt.Println(\u0026#34;所有响应头:\u0026#34;) for name, values := range resp.Header { for _, value := range values { fmt.Printf(\u0026#34;%s: %s\\n\u0026#34;, name, value) } } // 检查响应是否支持压缩 if resp.Header.Get(\u0026#34;Content-Encoding\u0026#34;) == \u0026#34;gzip\u0026#34; { // 处理gzip压缩内容 reader, err := gzip.NewReader(resp.Body) if err != nil { log.Fatal(err) } defer reader.Close() // 从解压缩的reader读取内容 body, err := io.ReadAll(reader) // ... } 高级HTTP客户端配置 # 默认客户端 # 当你需要自定义http请求而无需自定义客户端时，可以使用http包提供的默认客户端来实现。\nclient := \u0026amp;http.Client{} // 使用自定义客户端发送请求 resp, err := client.Get(\u0026#34;https://example.com\u0026#34;) // ... 自定义HTTP客户端 # client := \u0026amp;http.Client{ // 设置超时时间 Timeout: 10 * time.Second, // 自定义重定向策略 CheckRedirect: func(req *http.Request, via []*http.Request) error { // 限制最多重定向3次 if len(via) \u0026gt;= 3 { return fmt.Errorf(\u0026#34;停止重定向：已达到最大重定向次数\u0026#34;) } return nil }, } // 使用自定义客户端发送请求 resp, err := client.Get(\u0026#34;https://example.com\u0026#34;) // ... 传输层配置(Transport) # transport := \u0026amp;http.Transport{ // 代理设置 Proxy: http.ProxyURL(\u0026amp;url.URL{ Scheme: \u0026#34;http\u0026#34;, Host: \u0026#34;proxy.example.com:8080\u0026#34;, }), // TLS配置 TLSClientConfig: \u0026amp;tls.Config{ InsecureSkipVerify: false, // 生产环境请设为false MinVersion: tls.VersionTLS12, }, // 连接池设置 MaxIdleConns: 100, MaxIdleConnsPerHost: 10, IdleConnTimeout: 90 * time.Second, // 拨号设置 DialContext: (\u0026amp;net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, // HTTP/2支持 ForceAttemptHTTP2: true, } client := \u0026amp;http.Client{ Transport: transport, Timeout: 15 * time.Second, } // 使用配置后的客户端 resp, err := client.Get(\u0026#34;https://example.com\u0026#34;) // ... 请求上下文(Context) # 使用context可以实现请求取消和超时控制：\nimport ( \u0026#34;context\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;time\u0026#34; ) func main() { // 创建一个5秒超时的上下文 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 确保取消函数被调用 // 创建带上下文的请求 req, err := http.NewRequestWithContext(ctx, \u0026#34;GET\u0026#34;, \u0026#34;https://api.example.com/data\u0026#34;, nil) if err != nil { log.Fatal(err) } // 发送请求 resp, err := http.DefaultClient.Do(req) if err != nil { // 检查是否因超时而失败 if ctx.Err() == context.DeadlineExceeded { log.Println(\u0026#34;请求超时\u0026#34;) } else { log.Printf(\u0026#34;请求失败: %v\u0026#34;, err) } return } defer resp.Body.Close() // 处理响应 // ... } 高级HTTP请求 # 提交表单数据 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;net/url\u0026#34; ) func main() { // 创建表单数据 formData := url.Values{ \u0026#34;username\u0026#34;: {\u0026#34;gopher\u0026#34;}, \u0026#34;email\u0026#34;: {\u0026#34;gopher@example.com\u0026#34;}, \u0026#34;message\u0026#34;: {\u0026#34;Hello from Go!\u0026#34;}, } // 发送表单 resp, err := http.PostForm(\u0026#34;https://httpbin.org/post\u0026#34;, formData) if err != nil { log.Fatal(err) } defer resp.Body.Close() // 处理响应 body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Printf(\u0026#34;响应: %s\\n\u0026#34;, body) } multipart/form-data和文件上传 # 上传文件需要使用multipart/form-data格式：\npackage main import ( \u0026#34;bytes\u0026#34; \u0026#34;io\u0026#34; \u0026#34;log\u0026#34; \u0026#34;mime/multipart\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; \u0026#34;path/filepath\u0026#34; ) func main() { // 打开要上传的文件 file, err := os.Open(\u0026#34;example.jpg\u0026#34;) if err != nil { log.Fatal(err) } defer file.Close() // 创建multipart写入器 body := \u0026amp;bytes.Buffer{} writer := multipart.NewWriter(body) // 创建文件表单字段 part, err := writer.CreateFormFile(\u0026#34;file\u0026#34;, filepath.Base(file.Name())) if err != nil { log.Fatal(err) } // 复制文件内容到表单字段 if _, err = io.Copy(part, file); err != nil { log.Fatal(err) } // 添加额外的文本字段 writer.WriteField(\u0026#34;description\u0026#34;, \u0026#34;示例图片上传\u0026#34;) writer.WriteField(\u0026#34;category\u0026#34;, \u0026#34;测试\u0026#34;) // 完成写入 writer.Close() // 创建请求 req, err := http.NewRequest(\u0026#34;POST\u0026#34;, \u0026#34;https://httpbin.org/post\u0026#34;, body) if err != nil { log.Fatal(err) } // 设置Content-Type头，指定boundary req.Header.Set(\u0026#34;Content-Type\u0026#34;, writer.FormDataContentType()) // 发送请求 client := \u0026amp;http.Client{} resp, err := client.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close() // 处理响应 // ... } 处理cookies # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;net/http/cookiejar\u0026#34; ) func main() { // 创建HTTP客户端，启用cookie jar jar, err := cookiejar.New(nil) if err != nil { log.Fatal(err) } client := \u0026amp;http.Client{ Jar: jar, } // 发送第一个请求，服务器可能会设置cookie resp, err := client.Get(\u0026#34;https://httpbin.org/cookies/set?name=value\u0026#34;) if err != nil { log.Fatal(err) } resp.Body.Close() // 关闭但不处理响应 // 发送第二个请求，Jar自动附带上一个响应的cookie resp, err = client.Get(\u0026#34;https://httpbin.org/cookies\u0026#34;) if err != nil { log.Fatal(err) } defer resp.Body.Close() // 读取响应，应当显示服务器收到的cookie body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Printf(\u0026#34;响应: %s\\n\u0026#34;, body) // 手动添加cookie到请求 req, err := http.NewRequest(\u0026#34;GET\u0026#34;, \u0026#34;https://httpbin.org/cookies\u0026#34;, nil) if err != nil { log.Fatal(err) } req.AddCookie(\u0026amp;http.Cookie{ Name: \u0026#34;custom_cookie\u0026#34;, Value: \u0026#34;custom_value\u0026#34;, }) resp, err = client.Do(req) // ... } 构建可重用的HTTP客户端 # 对于重复调用同一API，创建专用客户端结构是最佳实践：\n// APIClient 封装API调用功能 type APIClient struct { BaseURL string HTTPClient *http.Client Token string } // NewAPIClient 创建新的API客户端 func NewAPIClient(baseURL, token string) *APIClient { return \u0026amp;APIClient{ BaseURL: baseURL, HTTPClient: \u0026amp;http.Client{ Timeout: 10 * time.Second, }, Token: token, } } // Get 发送GET请求到指定路径 func (c *APIClient) Get(path string, params map[string]string) ([]byte, error) { // 构建完整URL u, err := url.Parse(c.BaseURL) if err != nil { return nil, err } u.Path = path // 添加查询参数 q := u.Query() for key, value := range params { q.Add(key, value) } u.RawQuery = q.Encode() // 创建请求 req, err := http.NewRequest(\u0026#34;GET\u0026#34;, u.String(), nil) if err != nil { return nil, err } // 添加认证头 req.Header.Add(\u0026#34;Authorization\u0026#34;, \u0026#34;Bearer \u0026#34;+c.Token) req.Header.Add(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) // 发送请求 resp, err := c.HTTPClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() // 检查状态码 if resp.StatusCode \u0026lt; 200 || resp.StatusCode \u0026gt;= 300 { return nil, fmt.Errorf(\u0026#34;API错误: %d %s\u0026#34;, resp.StatusCode, resp.Status) } // 读取并返回响应体 return io.ReadAll(resp.Body) } // Post 发送POST请求到指定路径 func (c *APIClient) Post(path string, data interface{}) ([]byte, error) { // 将数据编码为JSON jsonData, err := json.Marshal(data) if err != nil { return nil, err } // 创建请求 u, err := url.Parse(c.BaseURL) if err != nil { return nil, err } u.Path = path req, err := http.NewRequest(\u0026#34;POST\u0026#34;, u.String(), bytes.NewBuffer(jsonData)) if err != nil { return nil, err } // 添加头 req.Header.Add(\u0026#34;Authorization\u0026#34;, \u0026#34;Bearer \u0026#34;+c.Token) req.Header.Add(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) // 发送请求 resp, err := c.HTTPClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() // 检查状态码 if resp.StatusCode \u0026lt; 200 || resp.StatusCode \u0026gt;= 300 { return nil, fmt.Errorf(\u0026#34;API错误: %d %s\u0026#34;, resp.StatusCode, resp.Status) } // 读取并返回响应体 return io.ReadAll(resp.Body) } 性能优化与最佳实践 # 连接池与客户端重用 # Go的http.Client内部使用连接池机制来提高性能：\n一些关键的性能优化点：\n避免为每个请求创建新的http.Client 配置适当的空闲连接数量和超时时间 确保始终关闭响应体，即使不读取它 // 创建一个全局的HTTP客户端，整个应用程序共享 var client = \u0026amp;http.Client{ Timeout: 10 * time.Second, Transport: \u0026amp;http.Transport{ MaxIdleConns: 100, // 连接池中的最大空闲连接数 MaxIdleConnsPerHost: 10, // 每个主机的最大空闲连接数 IdleConnTimeout: 90 * time.Second, // 空闲连接超时时间 }, } // 在应用中重复使用此客户端 func fetchURL(url string) ([]byte, error) { resp, err := client.Get(url) if err != nil { return nil, err } defer resp.Body.Close() return io.ReadAll(resp.Body) } 请求超时设置 # client := \u0026amp;http.Client{ // 设置整体请求超时（包括连接、重定向和读取响应体） Timeout: 10 * time.Second, Transport: \u0026amp;http.Transport{ // 仅连接建立的超时 DialContext: (\u0026amp;net.Dialer{ Timeout: 5 * time.Second, }).DialContext, // TLS握手超时 TLSHandshakeTimeout: 5 * time.Second, // 响应头超时 ResponseHeaderTimeout: 5 * time.Second, // 空闲连接超时 IdleConnTimeout: 90 * time.Second, }, } 并发请求处理 # func fetchConcurrent(urls []string) []Result { ch := make(chan Result, len(urls)) var wg sync.WaitGroup // 限制最大并发数 semaphore := make(chan struct{}, 10) for _, url := range urls { wg.Add(1) go func(url string) { defer wg.Done() // 获取信号量 semaphore \u0026lt;- struct{}{} defer func() { \u0026lt;-semaphore }() // 发送请求 resp, err := http.Get(url) if err != nil { ch \u0026lt;- Result{URL: url, Error: err} return } defer resp.Body.Close() // 读取响应 body, err := io.ReadAll(resp.Body) if err != nil { ch \u0026lt;- Result{URL: url, Error: err} return } ch \u0026lt;- Result{URL: url, Body: body} }(url) } // 等待所有请求完成并关闭通道 go func() { wg.Wait() close(ch) }() // 收集结果 var results []Result for result := range ch { results = append(results, result) } return results } type Result struct { URL string Body []byte Error error } HTTP/2支持 # Go的net/http包自动支持HTTP/2，但可以通过配置优化\nclient := \u0026amp;http.Client{ Transport: \u0026amp;http.Transport{ // 启用HTTP/2 ForceAttemptHTTP2: true, // HTTP/2连接设置 MaxIdleConnsPerHost: 100, }, } 响应体流式处理 # // 下载大文件 func downloadLargeFile(url, destPath string) error { // 创建目标文件 out, err := os.Create(destPath) if err != nil { return err } defer out.Close() // 发送GET请求 resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() // 检查状态码 if resp.StatusCode != http.StatusOK { return fmt.Errorf(\u0026#34;下载失败，状态码: %d\u0026#34;, resp.StatusCode) } // 创建进度条（可选） fileSize := resp.ContentLength progress := 0 counter := \u0026amp;WriteCounter{ Total: fileSize, } // 使用io.Copy将响应体直接流式复制到文件 _, err = io.Copy(out, io.TeeReader(resp.Body, counter)) if err != nil { return err } return nil } // WriteCounter 用于跟踪下载进度 type WriteCounter struct { Total int64 Downloaded int64 } func (wc *WriteCounter) Write(p []byte) (int, error) { n := len(p) wc.Downloaded += int64(n) wc.PrintProgress() return n, nil } func (wc *WriteCounter) PrintProgress() { if wc.Total \u0026lt;= 0 { fmt.Printf(\u0026#34;\\r下载中: %d bytes\u0026#34;, wc.Downloaded) return } percentage := float64(wc.Downloaded) / float64(wc.Total) * 100 fmt.Printf(\u0026#34;\\r下载进度: %.2f%% (%d/%d bytes)\u0026#34;, percentage, wc.Downloaded, wc.Total) } 上传文件 # 当你需要上传文件时，可以通过multipart.Writer来实现。\nfunc main() { filePath := \u0026#34;go.mod\u0026#34; multipartWriter, reqBody, err := newUploadFile(filePath) if err != nil { fmt.Println(err) return } req, err := http.NewRequest(http.MethodPost, \u0026#34;http://localhost:8080/file\u0026#34;, reqBody) if err != nil { fmt.Println(\u0026#34;create request failed:\u0026#34;, err) return } req.Header.Set(\u0026#34;Content-Type\u0026#34;, multipartWriter.FormDataContentType()) resp, err := http.DefaultClient.Do(req) if err != nil { fmt.Println(\u0026#34;send request failed:\u0026#34;, err) return } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { fmt.Println(\u0026#34;read response failed:\u0026#34;, err) return } fmt.Println(\u0026#34;upload file succeed:\u0026#34;, string(respBody)) } func newUploadFile(filePath string) (*multipart.Writer, *bytes.Buffer, error) { var reqBody bytes.Buffer multipartWriter := multipart.NewWriter(\u0026amp;reqBody) part, err := multipartWriter.CreateFormFile(\u0026#34;upload\u0026#34;, filepath.Base(filePath)) if err != nil { return nil, nil, fmt.Errorf(\u0026#34;create form file failed: %v\u0026#34;, err) } file, err := os.Open(filePath) if err != nil { return nil, nil, fmt.Errorf(\u0026#34;open file failed: %v\u0026#34;, err) } defer file.Close() _, err = io.Copy(part, file) if err != nil { return nil, nil, fmt.Errorf(\u0026#34;copy file content failed: %v\u0026#34;, err) } err = multipartWriter.Close() if err != nil { return nil, nil, fmt.Errorf(\u0026#34;close multipart writer failed: %v\u0026#34;, err) } return multipartWriter, \u0026amp;reqBody, nil } 下载文件 # 通过http接口下载文件的实现特别简单，将响应体数据写入本地文件即可。\n下载文件到download目录下，从响应头中的Content-Disposition字段提取文件名：\nfunc main() { resp, err := http.Get(\u0026#34;http://localhost:8080/file\u0026#34;) if err != nil { fmt.Println(\u0026#34;http get failed:\u0026#34;, err) return } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { fmt.Println(\u0026#34;read response body failed:\u0026#34;, err) return } filename := filenameFromRespHeader(resp.Header) err = os.WriteFile(path.Join(\u0026#34;download\u0026#34;, filename), respBody, os.ModePerm) if err != nil { return } fmt.Println(\u0026#34;http get succeed:\u0026#34;, string(respBody)) } func filenameFromRespHeader(header http.Header) string { r := regexp.MustCompile(`^.*filename=\u0026#34;(.*)\u0026#34;`) s := header.Get(\u0026#34;Content-Disposition\u0026#34;) ss := r.FindStringSubmatch(s) if len(ss) \u0026gt;= 2 { return ss[1] } return \u0026#34;unknown\u0026#34; } ","date":"2025-05-15","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/8b430461/9d16eb02/","section":"文章","summary":"\u003cp\u003eGo语言的\u003ccode\u003enet/http\u003c/code\u003e包不仅提供了HTTP服务器功能，还包含了功能强大的HTTP客户端API，让我们能够轻松地与各种Web服务进行交互。\u003c/p\u003e","title":"1、http客户端","type":"posts"},{"content":" database # Go 语言通过内置的 database/sql 包支持关系型数据库的操作，同时也支持通过第三方库与 NoSQL 数据库进行交互。你可以通过标准的 SQL 操作与关系型数据库（如 MySQL、PostgreSQL）进行交互，也可以使用专门的库来连接 NoSQL 数据库（如 MongoDB、Redis）。\ndatabase/sql 包通过提供统一的编程接口，实现了对不同数据库驱动的抽象。\n原理 # Driver 接口定义：database/sql/driver 包中定义了一个 Driver 接口，该接口用于表示一个数据库驱动。驱动开发者需要实现该接口来提供与特定数据库的交互能力。 Driver 注册：驱动开发者需要在程序初始化阶段，通过调用 database/sql 包提供的 sql.Register() 方法将自己的驱动注册到 database/sql 中。这样，database/sql 就能够识别和使用该驱动。 数据库连接池管理：database/sql 维护了一个数据库连接池，用于管理数据库连接。当通过 sql.Open() 打开一个数据库连接时，database/sql 会在合适的时机调用注册的驱动来创建一个具体的连接，并将其添加到连接池中。连接池会负责连接的复用、管理和维护工作，并且这是并发安全的。 统一的编程接口：database/sql 定义了一组统一的编程接口供用户使用，如 Prepare()、Exec() 和 Query() 等方法，用于准备 SQL 语句、执行 SQL 语句和执行查询等操作。这些方法会接收参数并调用底层驱动的相应方法来执行实际的数据库操作。 接口方法的实现：驱动开发者需要实现 database/sql/driver 中定义的一些接口方法，以此来支持上层 database/sql 包提供的 Prepare()、Exec() 和 Query() 等方法，以提供底层数据库的具体实现。当 database/sql 调用这些方法时，实际上会调用注册的驱动的相应方法来执行具体的数据库操作。 常用方法 # 数据库连接 # 方法名 描述 示例 sql.Open() 打开数据库连接 db, err := sql.Open(\u0026quot;mysql\u0026quot;, dsn) db.Ping() 测试数据库连接是否有效 err = db.Ping() db.Close() 关闭数据库连接 defer db.Close() 连接池配置 # 方法名 描述 示例 db.SetMaxOpenConns(max int) 设置最大打开连接数（默认无限制），避免数据库过载。 db.SetMaxOpenConns(10) db.SetMaxIdleConns(max int) 设置最大空闲连接数（默认2），减少短连接频繁创建开销。 db.SetMaxIdleConns(2) db.SetConnMaxLifetime(d time.Duration) 设置连接最大存活时间，避免数据库主动断开（如 MySQL 的 wait_timeout）。 db.SetConnMaxLifetime(3 * time.Second) db.SetConnMaxIdleTime(d time.Duration) Go 1.15+新增，设置空闲连接最大保留时间，防止长期闲置占用资源。 db.SetConnMaxIdleTime(3 * time.Second) 事务方法 # 方法名 描述 示例 db.Begin() 开始一个事务 tx, err := db.Begin() tx.Rollback() 回滚事务 tx.Rollback() tx.Commit() 提交事务 err = tx.Commit() 查询和执行方法 # 方法名 描述 示例 tx.Exec() 执行不返回结果的SQL语句，用于CREATE、INSERT、UPDATE、DELETE等操作 tx.Exec(\u0026quot;create table ...\u0026quot;) tx.Query() 执行返回多行结果的SQL查询 rows, err := tx.Query(\u0026quot;select ...\u0026quot;) tx.QueryRow() 执行返回单行结果的SQL查询 tx.QueryRow(\u0026quot;select ...\u0026quot;) stmt.Exec() 使用预处理语句执行SQL语句 stmt.Exec(\u0026quot;f\u0026quot;, \u0026quot;g\u0026quot;) 预处理语句 # 方法名 描述 示例 tx.Prepare() 创建预处理语句 stmt, err := tx.Prepare(...) stmt.Close() 关闭预处理语句 defer stmt.Close() 查询结果处理 # 方法名 描述 示例 rows.Next() 逐行迭代查询结果 rows.Next() rows.Scan() 将当前行的列值赋值给变量 rows.Scan(\u0026amp;s1, \u0026amp;s2) rows.Err() 检查查询和迭代过程中的错误 rows.Err() rows.Close() 关闭结果集，释放相关资源 defer rows.Close() 结果集 # 新增、更新、删除时返回的结果集接口定义如下：\ntype Result interface { // 返回数据库响应命令生成的整数。 // 通常，当插入新行时，这将来自“自动递增”列。 // 并非所有数据库都支持此功能，而且此类语句的语法也各不相同。 LastInsertId() (int64, error) // 返回受更新、插入或删除影响的行数。 // 并非每个数据库或数据库驱动程序都支持此功能。 RowsAffected() (int64, error) } 数据库驱动 # 常用驱动 # MySQL # github.com/go-sql-driver/mysql\nOracle # github.com/godror/godror\nPostgreSQL # github.com/lib/pq\nMongoDB # go.mongodb.org/mongo-driver/mongo\nSQLite3 # modernc.org/sqlite 作为一个较新的库，它的社区支持和文档可能不如 go-sqlite3 庞大。 由于它是纯 Go 实现，它在一些特殊场景下可能会出现一些性能瓶颈，但这些问题通常会在未来的版本中逐步得到解决。 github.com/mattn/go-sqlite3 这是 Go 中最常用的 SQLite 库之一，社区活跃，维护频繁。 因为它依赖于 SQLite 的 C 库，它的功能和性能通常比其他 Go SQLite 库更为可靠。 需要 C 编译器，因此在某些系统或平台上（特别是轻量级系统或容器环境）可能会遇到安装和配置的问题。 下载驱动 # # mysql驱动 go get github.com/go-sql-driver/mysql 加载驱动 # 由于驱动在init方法中通过调用 database/sql 包提供的 sql.Register() 方法将自己的驱动注册到 database/sql 中。所以我们只需要匿名引入即可。\nmysql驱动内部的注册方法如下：\nfunc init() { if driverName != \u0026#34;\u0026#34; { sql.Register(driverName, \u0026amp;MySQLDriver{}) } } 加载驱动：\nimport _ \u0026#34;github.com/go-sql-driver/mysql\u0026#34; 连接数据库 # package main import ( \u0026#34;database/sql\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; _ \u0026#34;github.com/go-sql-driver/mysql\u0026#34; // 仅导入驱动，不直接使用 ) func main() { // 数据源名称（DSN）格式：用户名:密码@协议(地址)/数据库?参数=值 dsn := \u0026#34;user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4\u0026amp;parseTime=True\u0026#34; // 打开数据库连接 db, err := sql.Open(\u0026#34;mysql\u0026#34;, dsn) if err != nil { log.Fatal(\u0026#34;连接数据库失败:\u0026#34;, err) } defer db.Close() // 确保在函数退出时关闭数据库连接 // 验证连接 if err := db.Ping(); err != nil { log.Fatal(\u0026#34;Ping数据库失败:\u0026#34;, err) } fmt.Println(\u0026#34;成功连接到数据库!\u0026#34;) } 基本查询操作 # 执行简单查询 # func queryExample(db *sql.DB) { // 查询单行 var name string var age int row := db.QueryRow(\u0026#34;SELECT name, age FROM users WHERE id = ?\u0026#34;, 1) err := row.Scan(\u0026amp;name, \u0026amp;age) if err != nil { if err == sql.ErrNoRows { fmt.Println(\u0026#34;未找到记录\u0026#34;) return } log.Fatal(\u0026#34;查询失败:\u0026#34;, err) } fmt.Printf(\u0026#34;用户: %s, 年龄: %d\\n\u0026#34;, name, age) } 查询多行数据 # func queryMultipleRows(db *sql.DB) { // 查询多行 rows, err := db.Query(\u0026#34;SELECT id, name, age FROM users WHERE age \u0026gt; ?\u0026#34;, 18) if err != nil { log.Fatal(\u0026#34;查询失败:\u0026#34;, err) } defer rows.Close() // 非常重要：确保关闭rows // 遍历结果集 for rows.Next() { var id int var name string var age int if err := rows.Scan(\u0026amp;id, \u0026amp;name, \u0026amp;age); err != nil { log.Fatal(\u0026#34;Scan失败:\u0026#34;, err) } fmt.Printf(\u0026#34;ID: %d, 名称: %s, 年龄: %d\\n\u0026#34;, id, name, age) } // 检查遍历过程中的错误 if err := rows.Err(); err != nil { log.Fatal(\u0026#34;结果集遍历失败:\u0026#34;, err) } } 修改数据操作 # 插入数据 # func insertExample(db *sql.DB) { // 插入单条记录 result, err := db.Exec( \u0026#34;INSERT INTO users (name, age, email) VALUES (?, ?, ?)\u0026#34;, \u0026#34;张三\u0026#34;, 30, \u0026#34;zhangsan@example.com\u0026#34;, ) if err != nil { log.Fatal(\u0026#34;插入失败:\u0026#34;, err) } // 获取自增ID id, err := result.LastInsertId() if err != nil { log.Fatal(\u0026#34;获取最后插入ID失败:\u0026#34;, err) } // 获取受影响行数 rows, err := result.RowsAffected() if err != nil { log.Fatal(\u0026#34;获取受影响行数失败:\u0026#34;, err) } fmt.Printf(\u0026#34;成功插入记录，ID: %d, 受影响行数: %d\\n\u0026#34;, id, rows) } 更新数据 # func updateExample(db *sql.DB) { // 更新记录 result, err := db.Exec( \u0026#34;UPDATE users SET age = ?, email = ? WHERE id = ?\u0026#34;, 31, \u0026#34;zhangsan_new@example.com\u0026#34;, 1, ) if err != nil { log.Fatal(\u0026#34;更新失败:\u0026#34;, err) } // 获取受影响行数 rows, err := result.RowsAffected() if err != nil { log.Fatal(\u0026#34;获取受影响行数失败:\u0026#34;, err) } fmt.Printf(\u0026#34;成功更新记录，受影响行数: %d\\n\u0026#34;, rows) } 删除数据 # func deleteExample(db *sql.DB) { // 删除记录 result, err := db.Exec(\u0026#34;DELETE FROM users WHERE id = ?\u0026#34;, 1) if err != nil { log.Fatal(\u0026#34;删除失败:\u0026#34;, err) } // 获取受影响行数 rows, err := result.RowsAffected() if err != nil { log.Fatal(\u0026#34;获取受影响行数失败:\u0026#34;, err) } fmt.Printf(\u0026#34;成功删除记录，受影响行数: %d\\n\u0026#34;, rows) } 预处理语句（Prepared Statements） # 预处理语句是指在数据库中提前编译和优化的SQL语句模板，可以在之后多次重复使用。预处理语句的主要优点如下：\n提高效率：数据库可以提前编译和优化预处理语句，减少每次执行SQL时的解析时间，特别是在需要多次执行相同SQL语句时。 防止SQL注入：通过参数化的SQL语句，用户输入的数据不会直接嵌入到SQL语句中，降低了SQL注入的风险。 减少网络开销：在需要多次执行相同的SQL语句时，客户端只需要发送参数，不需要每次都发送完整的SQL语句，减少网络通信的数据量。 在Go语言中，使用预处理语句的基本步骤如下：\n准备预处理语句：使用tx.Prepare()方法创建一个预处理语句对象stmt。 执行预处理语句：使用stmt.Exec()方法执行预处理语句，传递参数。 关闭预处理语句：执行完毕后，使用stmt.Close()方法释放相关资源。 // 创建预处理语句 stmt, err := Db.Prepare(\u0026#34;insert into student (obj_id,name,age) values(?,?,?)\u0026#34;) if err != nil { fmt.Println(err) } //传参并执行 insertResult, err := stmt.Exec(\u0026#34;1234\u0026#34;, \u0026#34;lucy\u0026#34;, 18) if err != nil { fmt.Println(err) } fmt.Println(insertResult.LastInsertId()) fmt.Println(insertResult.RowsAffected()) 事务处理 # 事务允许将多个数据库操作作为一个原子单元执行，要么全部成功，要么全部失败。\nfunc transactionExample(db *sql.DB) { // 开始事务 tx, err := db.Begin() if err != nil { log.Fatal(\u0026#34;开始事务失败:\u0026#34;, err) } // 使用defer和recover处理异常，确保事务正确结束 defer func() { if r := recover(); r != nil { tx.Rollback() log.Printf(\u0026#34;发生异常，事务回滚: %v\u0026#34;, r) } }() // 执行第一个操作 _, err = tx.Exec(\u0026#34;INSERT INTO accounts (user_id, balance) VALUES (?, ?)\u0026#34;, 1, 1000) if err != nil { tx.Rollback() log.Fatal(\u0026#34;第一个操作失败，事务回滚:\u0026#34;, err) return } // 执行第二个操作 _, err = tx.Exec(\u0026#34;UPDATE accounts SET balance = balance - ? WHERE user_id = ?\u0026#34;, 200, 1) if err != nil { tx.Rollback() log.Fatal(\u0026#34;第二个操作失败，事务回滚:\u0026#34;, err) return } // 执行第三个操作 _, err = tx.Exec(\u0026#34;INSERT INTO transactions (user_id, amount, type) VALUES (?, ?, ?)\u0026#34;, 1, 200, \u0026#34;withdrawal\u0026#34;) if err != nil { tx.Rollback() log.Fatal(\u0026#34;第三个操作失败，事务回滚:\u0026#34;, err) return } // 提交事务 err = tx.Commit() if err != nil { log.Fatal(\u0026#34;提交事务失败:\u0026#34;, err) return } fmt.Println(\u0026#34;事务成功完成!\u0026#34;) } 处理NULL值 # 数据库中的NULL值需要使用特殊的类型处理：\nimport ( \u0026#34;database/sql\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;time\u0026#34; ) func nullValueExample(db *sql.DB) { // 声明可能包含NULL的变量 var ( id int name string age sql.NullInt64 email sql.NullString createdAt sql.NullTime ) // 查询可能包含NULL值的记录 row := db.QueryRow(\u0026#34;SELECT id, name, age, email, created_at FROM users WHERE id = ?\u0026#34;, 1) err := row.Scan(\u0026amp;id, \u0026amp;name, \u0026amp;age, \u0026amp;email, \u0026amp;createdAt) if err != nil { log.Fatal(\u0026#34;查询失败:\u0026#34;, err) } // 安全地访问可能为NULL的值 fmt.Printf(\u0026#34;ID: %d, 名称: %s\\n\u0026#34;, id, name) if age.Valid { fmt.Printf(\u0026#34;年龄: %d\\n\u0026#34;, age.Int64) } else { fmt.Println(\u0026#34;年龄: NULL\u0026#34;) } if email.Valid { fmt.Printf(\u0026#34;邮箱: %s\\n\u0026#34;, email.String) } else { fmt.Println(\u0026#34;邮箱: NULL\u0026#34;) } if createdAt.Valid { fmt.Printf(\u0026#34;创建时间: %v\\n\u0026#34;, createdAt.Time) } else { fmt.Println(\u0026#34;创建时间: NULL\u0026#34;) } } 常见错误 # 未关闭资源：如rows.Close()、stmt.Close() 忽略错误检查：尤其是rows.Err() 在循环中频繁准备语句：应在循环外准备，循环内执行 不正确的事务处理：没有处理提交或回滚 连接泄漏：比如使用了Query但没有迭代完所有结果 最佳实践 # 始终使用参数化查询：防止SQL注入攻击 // 好的做法 db.Query(\u0026#34;SELECT * FROM users WHERE name = ?\u0026#34;, username) // 不安全的做法 db.Query(\u0026#34;SELECT * FROM users WHERE name = \u0026#39;\u0026#34; + username + \u0026#34;\u0026#39;\u0026#34;) 正确处理连接：设置合适的连接池参数 使用合适的查询函数： Query: 返回多行 QueryRow: 返回单行 Exec: 不返回行的操作（INSERT/UPDATE/DELETE） 使用事务确保数据完整性：在需要原子操作时使用事务 处理NULL值：使用sql.NullXXX类型处理可能为NULL的列 结构化应用代码：将数据库操作封装到专门的包或函数中 ","date":"2025-05-14","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/db9fe107/203a4502/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003edatabase \n    \u003cdiv id=\"database\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#database\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eGo 语言通过内置的 \u003ccode\u003edatabase/sql\u003c/code\u003e 包支持关系型数据库的操作，同时也支持通过第三方库与 NoSQL 数据库进行交互。你可以通过标准的 SQL 操作与关系型数据库（如 MySQL、PostgreSQL）进行交互，也可以使用专门的库来连接 NoSQL 数据库（如 MongoDB、Redis）。\u003c/p\u003e","title":"1、database","type":"posts"},{"content":"单例模式（Singleton）确保一个类只有一个实例，并提供对该实例的全局访问点。\nGo语言实现单例模式的有四种方式，分别是懒汉式、饿汉式、双重检查和 sync.Once。\n懒汉式 # 非线程安全 # package main type Apple struct { } //私有变量 var apple *Apple // GetAppleInstants 获取Apple实例 func GetAppleInstants() *Apple { if apple == nil { apple = new(Apple) } return apple } 线程安全 # 双重检查锁 # package main import \u0026#34;sync\u0026#34; //锁对象 var lock sync.Mutex type Apple struct { } //私有变量 var apple *Apple // GetAppleInstants 获取Apple实例 func GetAppleInstants() *Apple { if apple == nil { lock.Lock() defer lock.Unlock() if apple == nil { apple = new(Apple) } } } sync.Once # 通过 sync.Once 来确保创建对象的方法只执行一次\npackage main import \u0026#34;sync\u0026#34; type Apple struct { } //once对象 var once sync.Once //私有变量 var apple *Apple // GetAppleInstants 获取Apple实例 func GetAppleInstants() *Apple { once.Do(func() { apple = new(Apple) }) } sync.Once 内部本质上也是双重检查的方式，但在写法上会比自己写双重检查更简洁，以下是 Once 的源码\nfunc (o *Once) Do(f func()) { //判断是否执行过该方法，如果执行过则不执行 if atomic.LoadUint32(\u0026amp;o.done) == 1 { return } // Slow-path. o.m.Lock() defer o.m.Unlock() //进行加锁，再做一次判断，如果没有执行，则进行标志已经扫行并调用该方法 if o.done == 0 { defer atomic.StoreUint32(\u0026amp;o.done, 1) f() } } 饿汉式 # 使用全局变量 # package main type Apple struct { } //私有变量 var apple = new(Apple) // GetAppleInstants 获取Apple实例 func GetAppleInstants() *Apple { return apple } 使用init函数 # package main type Apple struct { } //私有变量 var apple *Apple func init() { apple = new(Apple) } // GetAppleInstants 获取Apple实例 func GetAppleInstants() *Apple { return apple } ","date":"2025-05-14","externalUrl":null,"permalink":"/posts/2d3150b2/6742e0c3/d6eeb5cd/","section":"文章","summary":"\u003cp\u003e单例模式（Singleton）确保一个类只有一个实例，并提供对该实例的全局访问点。\u003c/p\u003e","title":"1、单例模式","type":"posts"},{"content":" 结构体 # Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性，结构体是类型中带有成员的复合类型，属于值类型。\nGo 语言中的类型可以被实例化，使用new()或\u0026amp;可以实例化该类型、获取实例的指针。\n结构体成员是由一系列的成员变量构成，这些成员变量也被称为字段。字段有以下特性：\n字段拥有自己的类型和值 字段名必须唯一 字段的类型也可以是结构体，甚至是字段所在结构体的类型 同样对于可见性，结构体的名称、结构体字段、结构体方法的名称首字母大小写规则和方法、变量名称规则相同。\n定义 # 使用关键字type可以将各种基本类型定义为自定义类型，基本类型包括int、string、bool等。\n结构体是一种复合的基本类型，通过type定义为自定义类型后，使结构体更便于使用。\ntype structName struct { filed1Name filed1Type filed2Name filed2Type filed3Name filed3Type } structName：表示自定义结构体的名称，在同一个包内不能重复。 struct{}：表示结构体类型，type structName struct{}可以理解为将 struct{} 结构体定义为structName的类型。 filed1Name、filed2Name：表示结构体字段名，结构体中的字段名必须唯一。 filed1Type、filed2Type：表示结构体各个字段的类型。 实例化 # type Stu struct { name string age int } 使用var # 由于结构体属于基本数据类型（值类型），所以在声明时，系统会自动对结构体以及结构体字段赋零值\n使用var关键字\nvar s Stu fmt.Println(s) // main.Stu{name:\u0026#34;\u0026#34;, age:0} 使用new # sp := new(Stu) s := *sp fmt.Printf(\u0026#34;%#v\\n\u0026#34;, s) // main.Stu{name:\u0026#34;\u0026#34;, age:0} 初始化并赋初始值 # 可以在大括号中以键值对或成员声明顺序的方式进行赋值\ns1 := Stu{} fmt.Printf(\u0026#34;%#v\\n\u0026#34;, s1) // main.Stu{name:\u0026#34;\u0026#34;, age:0} s2 := Stu{\u0026#34;lucy\u0026#34;, 18} fmt.Printf(\u0026#34;%#v\\n\u0026#34;, s2) // main.Stu{name:\u0026#34;lucy\u0026#34;, age:18} s3 := Stu{name: \u0026#34;tom\u0026#34;, age: 25} fmt.Printf(\u0026#34;%#v\\n\u0026#34;, s3) // main.Stu{name:\u0026#34;tom\u0026#34;, age:25} // 这种方法也可以直接获取结构体指针 s := \u0026amp;Stu{\u0026#34;lucy\u0026#34;, 18} fmt.Printf(\u0026#34;%T\\n\u0026#34;, s) // *main.Stu fmt.Printf(\u0026#34;%#v\\n\u0026#34;, s) // \u0026amp;main.Stu{name:\u0026#34;lucy\u0026#34;, age:18} 读写字段 # 结构体变量和结构体指针都可以使用操作符.对进行读写操作\npackage main import \u0026#34;fmt\u0026#34; type Stu struct { name string age int } func main() { s := Stu{\u0026#34;lucy\u0026#34;, 18} s.name = \u0026#34;tom\u0026#34; fmt.Println(s) // {tom 18} sp := \u0026amp;s sp.name = \u0026#34;lily\u0026#34; fmt.Println(s) // {lily 18} } 结构体方法与接收者 # 在Go语言中，没有类的概念但是可以给结构体、自定义类型定义方法。Go 方法是作用在接收者（receiver）上的一个函数，接受者就是结构体、自定义类型的实例，类似于其他语言中的this或self。\n使用接收者，就可以直接在实例后使用.来调用方法，并且操作实例\n非本地类型不可定义方法，也就是不可以给别的包的类型定义方法。\npackage main import ( \u0026#34;fmt\u0026#34; ) type Stu struct { name string age int } // 定义结构体Stu的方法 func (s *Stu) ShowInfo() { fmt.Printf(\u0026#34;the user name is %v, age is %d\\n\u0026#34;, s.name, s.age) } func main() { s := Stu{\u0026#34;lucy\u0026#34;, 18} s.ShowInfo() // the user name is lucy, age is 18 } 接收器变量：接收器中的参数变量名在命名时，官方建议使用接收器类型名的第一个小写字母，而不是 self、this 之类的命名。例如，Socket 类型的接收器变量应该命名为s，Connector 类型的接收器变量应该命名为c等。 接收器类型：接收器类型和参数类似，可以是指针类型和非指针类型，但是不可以使用接口类型 注意：接收器类型为非指针，相当于复制了一份接收器实例值，所以如果需要修改接收器实例值，那么需要使用指针类型 方法名、参数列表、返回参数：格式与函数定义一致。 toString # 在Java里面，实例的信息字符串一般由toString方法提供，在go里面，是由String() string函数提供，其原理也是实现了接口的方法\npackage main import ( \u0026#34;fmt\u0026#34; ) type Stu struct { name string age int } func (s Stu) String() string { return fmt.Sprintf(\u0026#34;the user name is %v, age is %d\\n\u0026#34;, s.name, s.age) } func main() { s := Stu{\u0026#34;lucy\u0026#34;, 18} fmt.Println(s) // the user name is lucy, age is 18 fmt.Println(\u0026amp;s) // the user name is lucy, age is 18 } 内嵌类型和结构体 # 内嵌类型 # 结构体可以包含一个或多个匿名（或内嵌）字段，即这些字段没有显式的名字，只有字段的类型是必须的，此时类型也就是字段的名字。\npackage main import \u0026#34;fmt\u0026#34; type Stu struct { int string } func main() { s := Stu{18, \u0026#34;lucy\u0026#34;} fmt.Println(s.int, s.string) // 18 lucy } 内嵌结构体（重要） # Go语言中的继承是通过内嵌或组合来实现的\n同样地结构体也是一种数据类型，所以它也可以作为一个匿名字段来使用。内层结构体被简单的插入或者内嵌进外层结构体，外层结构体实例可以直接访问内嵌结构体成员。这个简单的“继承”机制提供了一种方式，使得可以从另外一个或一些类型继承部分或全部实现。\n内嵌结构体可以对外层的结构体进行功能增强，外层结构体可以直接调用内层的方法\npackage main import \u0026#34;fmt\u0026#34; type Father struct { name string age int } type Chil struct { id int Father } func main() { c := Chil{111, Father{\u0026#34;lucy\u0026#34;, 18}} fmt.Printf(\u0026#34;id：%d，name：%s，age：%d\u0026#34;, c.id, c.name, c.age) } Go语言的结构体内嵌有如下特性：\n内嵌的结构体可以直接访问其成员变量 嵌入结构体的成员，可以通过外部结构体的实例直接访问。如果结构体有多层嵌入结构体，结构体实例访问任意一级的嵌入结构体成员时都只用给出字段名，而无须像传统结构体字段一样，通过一层层的结构体字段访问到最终的字段。 内嵌结构体的字段名是它的类型名 内嵌结构体字段仍然可以使用详细的字段进行一层层访问，内嵌结构体的字段名就是它的类型名 结构体中，不可以出现同名字段，包括内嵌的结构体字段 Go 中的面向对象编程 # Go与传统面向对象语言的区别 # 特性 传统面向对象语言 Go语言 类 有类的概念，作为对象的蓝图 没有类，使用结构体定义数据结构 构造函数 专门的构造方法 使用普通函数创建和初始化结构体 继承 通过类继承实现代码复用和多态 没有继承，使用组合和接口 多态 通过继承和方法重写实现 通过接口实现 封装 通过访问修饰符控制可见性 通过大小写控制可见性 方法重载 支持同名不同参数的方法 不支持方法重载 异常处理 通常使用try-catch机制 使用多返回值和错误处理 范型（Go 1.18前） 大多支持 不支持，使用接口和反射 构造函数 # Go没有专门的构造函数，但可以创建返回初始化结构体的函数，这是一种常见的惯例：\n构造函数的优势：\n提供参数验证和默认值 确保结构体正确初始化 封装复杂的初始化逻辑 可以返回接口而非具体类型，提高灵活性 // NewPerson作为Person的构造函数 func NewPerson(firstName, lastName string, age int, address string) *Person { // 可以在这里进行参数验证 if age \u0026lt; 0 { age = 0 } return \u0026amp;Person{ FirstName: firstName, LastName: lastName, Age: age, Address: address, } } func main() { // 使用构造函数创建实例 p := NewPerson(\u0026#34;张\u0026#34;, \u0026#34;三\u0026#34;, 30, \u0026#34;北京市海淀区\u0026#34;) fmt.Printf(\u0026#34;%+v\\n\u0026#34;, *p) } Go OOP最佳实践 # 在Go中实现面向对象编程时，应该遵循一些最佳实践，确保代码的可读性、可维护性和性能。\n设计原则 # 优先使用组合：优先使用组合而非模拟继承。\n// 不推荐：模拟继承 type Animal struct { Name string } func (a Animal) Speak() string { return \u0026#34;某种声音\u0026#34; } type Dog struct { Animal // 试图模拟继承 Breed string } // 推荐：明确组合 type Animal struct { Name string } func (a Animal) Speak() string { return \u0026#34;某种声音\u0026#34; } type Dog struct { Animal Animal // 清晰表明这是组合 Breed string } func (d Dog) Speak() string { return \u0026#34;汪汪\u0026#34; } 接口应该小而精确：定义最小可行的接口。\n// 不推荐：过大的接口 type FileProcessor interface { Open(filename string) error Read() ([]byte, error) Process() error Write(data []byte) error Close() error } // 推荐：小而专注的接口 type Reader interface { Read() ([]byte, error) } type Writer interface { Write(data []byte) error } type Processor interface { Process() error } // 可以组合使用 type FileHandler struct { // ... } func (f *FileHandler) Read() ([]byte, error) { // 实现读取 } func (f *FileHandler) Write(data []byte) error { // 实现写入 } func (f *FileHandler) Process() error { // 实现处理 } 避免接口污染：不要在结构体上定义不需要的方法仅为了满足接口。\n// 不推荐：为了满足接口添加不必要的方法 type Logger interface { Log(message string) LogError(err error) LogWarning(message string) } type SimpleLogger struct{} func (l SimpleLogger) Log(message string) { fmt.Println(message) } // 这些方法仅为了满足接口 func (l SimpleLogger) LogError(err error) { l.Log(err.Error()) // 只是转发到Log } func (l SimpleLogger) LogWarning(message string) { l.Log(\u0026#34;WARNING: \u0026#34; + message) // 只是转发到Log } // 推荐：使用适配器模式 type Logger interface { Log(message string) LogError(err error) LogWarning(message string) } type SimpleLogger struct{} func (l SimpleLogger) Log(message string) { fmt.Println(message) } // 使用适配器满足复杂接口 type LoggerAdapter struct { logger SimpleLogger } func (a LoggerAdapter) Log(message string) { a.logger.Log(message) } func (a LoggerAdapter) LogError(err error) { a.logger.Log(err.Error()) } func (a LoggerAdapter) LogWarning(message string) { a.logger.Log(\u0026#34;WARNING: \u0026#34; + message) } 适应Go的思维方式 # 要在Go中成功应用面向对象编程，需要调整思维方式：\n放弃继承思维，转向组合思维。 设计小而精确的接口，而非大型类层次结构。 关注类型的行为（方法），而非它继承自什么。 优先考虑数据和行为的分离，而非强制捆绑。 使用显式代码而非魔法和隐式行为。 ","date":"2025-05-13","externalUrl":null,"permalink":"/posts/2d3150b2/0e0cf2f4/57ece345/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e结构体 \n    \u003cdiv id=\"结构体\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%bb%93%e6%9e%84%e4%bd%93\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eGo 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性，结构体是类型中带有成员的\u003cstrong\u003e复合类型\u003c/strong\u003e，属于\u003cstrong\u003e值类型\u003c/strong\u003e。\u003c/p\u003e","title":"1、结构体","type":"posts"},{"content":" uuid简介 # 通用唯一识别码（英语：universally unique identifier，简称uuid）是一种软件建构的标准，亦为自由软件基金会组织在分散式计算环境领域的一部份。\nuuid的目的，是让分散式系统中的所有元素，都能有唯一的辨识信息，而不需要通过中央控制端来做辨识信息的指定。\n如此一来，每个人都可以创建不与其它人冲突的uuid。\n在这样的情况下，就不需考虑数据库创建时的名称重复问题。目前最广泛应用的uuid，是微软公司的全局唯一标识符（guid），而其他重要的应用，则有linux ext2/ext3文件系统、luks加密分区、gnome、kde、mac os x等等。\n目前，golang中并没有uuid的标准库，所以需要借助第三方库。\ngithub.com/google/uuid # 谷歌出品的go语言uuid库\ngo get github.com/google/uuid package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/google/uuid\u0026#34; ) func main() { u := uuid.New() fmt.Println(u.String()) //32a64afc-a9ff-4555-8f8d-24b9316483f6 fmt.Println(u.ID()) //849758972 } ","date":"2025-05-08","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/29ede47b/6316670e/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003euuid简介 \n    \u003cdiv id=\"uuid简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#uuid%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e通用唯一识别码（英语：universally unique identifier，简称uuid）是一种软件建构的标准，亦为自由软件基金会组织在分散式计算环境领域的一部份。\u003c/p\u003e","title":"1、UUID","type":"posts"},{"content":"Go 语言生态中，GUI 一直是短板，更别说跨平台的 GUI 了。fyne向前迈了一大步。fyne 是 Go 语言编写的跨平台的 UI 库，它可以很方便地移植到手机设备上。fyne使用上非常简单，同时它还提供fyne命令打包静态资源和应用程序。我们先简单介绍基本控件和布局，然后介绍如何发布一个fyne应用程序。\n快速使用 # 由于fyne包含一些 C/C++ 的代码，所以需要gcc编译工具。在 Linux/Mac OSX 上，gcc基本是标配，在 windows上有 3 种方式安装gcc工具链：\nMSYS2 + MingW-w64：https://www.msys2.org/； TDM-GCC：https://jmeubank.github.io/tdm-gcc/download/； Cygwin：https://www.cygwin.com/。 安装fyne\ngo get -u fyne.io/fyne 简单窗口\npackage main import ( \u0026#34;fyne.io/fyne\u0026#34; \u0026#34;fyne.io/fyne/app\u0026#34; \u0026#34;fyne.io/fyne/widget\u0026#34; ) func main() { // 创建应用程序对象 app := app.New() // 创建一个新窗口 win := app.NewWindow(\u0026#34;Hello World\u0026#34;) // 设置窗口内容 win.SetContent(widget.NewLabel(\u0026#34;Hello World\u0026#34;)) // 设置窗口大小 win.Resize(fyne.NewSize(500, 500)) // 等价于 win.Show() app.Run() win.ShowAndRun() } fyne包结构划分 # fyne将功能划分到多个子包中：\nfyne.io/fyne：提供所有fyne应用程序代码共用的基础定义，包括数据类型和接口； fyne.io/fyne/app：提供创建应用程序的 API； fyne.io/fyne/canvas：提供Fyne使用的绘制 API； fyne.io/fyne/dialog：提供对话框组件； fyne.io/fyne/layout：提供多种界面布局； fyne.io/fyne/widget：提供多种组件，fyne所有的窗体控件和交互元素都在这个子包中。 ","date":"2025-05-08","externalUrl":null,"permalink":"/posts/2d3150b2/9f158c96/afc23a3a/29dbf716/","section":"文章","summary":"\u003cp\u003eGo 语言生态中，GUI 一直是短板，更别说跨平台的 GUI 了。\u003ccode\u003efyne\u003c/code\u003e向前迈了一大步。\u003ccode\u003efyne\u003c/code\u003e 是 Go 语言编写的\u003cstrong\u003e跨平台的\u003c/strong\u003e UI 库，它可以很方便地移植到手机设备上。\u003ccode\u003efyne\u003c/code\u003e使用上非常简单，同时它还提供\u003ccode\u003efyne\u003c/code\u003e命令打包静态资源和应用程序。我们先简单介绍基本控件和布局，然后介绍如何发布一个\u003ccode\u003efyne\u003c/code\u003e应用程序。\u003c/p\u003e","title":"1、介绍","type":"posts"},{"content":" Java 图形化界面 # Java使用AWT和Swing相关的类可以完成图形化界面编程，其中AWT的全称是抽象窗口工具集(Abstract Window Toolkit),它是sun公司最早提供的GUI库，这个GUI库提供了一些基本功能，但这个GUI库的功能比较有限，所以后来sun公司又提供了Swing库。通过使用AWT和Swing提供的图形化界面组件库，java的图形化界面编程非常简单，程序只需要依次创建所需的图形组件，并以合适的方式将这些组件组织在一起，就可以开发出非常美观的用户界面。\nAWT简介 # 当 JDK 1.0发布时， Sun 提供了 一套基本的GUI类库，这个GUI类库希望可以在所有平台下都能运行 ，这套基本类库被称为\u0026quot;抽象窗口工具集 CAbstract Window Toolkit )\u0026quot;，它为Java应用程序提供了基本的图形组件 。 AWT是窗口框架，它从不同平台的窗口系统中抽取出共同组件 ， 当程序运行时，将这些组件的创建和动作委托给程序所在的运行平台 。 简而言之 ，当使用 AWT 编写图形界面应用时， 程序仅指定了界面组件的位置和行为，并未提供真正的实现，JVM调用操作系统本地的图形界面来创建和平台 一致的对等体 。\n使用AWT创建的图形界面应用和所在的运行平台有相同的界面风格 ， 比如在 Windows 操作系统上，它就表现出 Windows 风格 ; 在 UNIX 操作系统上，它就表现出UNIX 风格 。 Sun 希望采用这种方式来实现 \u0026quot; Write Once, Run Anywhere \u0026quot; 的目标 。\nAWT继承体系 # 所有和 AWT 编程相关的类都放在 java.awt 包以及它的子包中， AWT 编程中有两个基类：Component 和 MenuComponent。\nComponent：代表一个能以图形化方式显示出来，并可与用户交互的对象，例如 Button 代表一个按钮，TextField 代表 一个文本框等； MenuComponent：则代表图形界面的菜单组件，包括 MenuBar (菜单条)、 Menultem (菜单项)等子类。 其中 Container 是一种特殊的 Component，它代表一种容器，可以装载普通的 Component。\nAWT中还有一个非常重要的接口叫LayoutManager ，如果一个容器中有多个组件，那么容器就需要使用LayoutManager来管理这些组件的布局方式。\nContainer容器 # Container继承体系 # Window：是可以独立存在的顶级窗口，默认使用BorderLayout管理其内部组件布局; Panel：可以容纳其他组件，但不能独立存在，它必须内嵌其他容器中使用，默认使用FlowLayout管理其内部组件布局； ScrollPane ：是一个带滚动条的容器，它也不能独立存在，默认使用 BorderLayout 管理其内部组件布局； 常见API # Component作为基类，提供了如下常用的方法来设置组件的大小、位置、可见性等。\n方法签名 方法功能 setLocation(int x, int y) 设置组件的位置。 setSize(int width, int height) 设置组件的大小。 setBounds(int x, int y, int width, int height) 同时设置组件的位置、大小。 setVisible(Boolean b): 设置该组件的可见性。 Container作为容器根类，提供了如下方法来访问容器中的组件。\n方法签名 方法功能 Component add(Component comp) 向容器中添加其他组件 (该组件既可以是普通组件，也可以 是容器) ， 并返回被添加的组件 。 Component getComponentAt(int x, int y): 返回指定点的组件 。 int getComponentCount(): 返回该容器内组件的数量 。 Component[] getComponents(): 返回该容器内的所有组件 。 Window # public class FrameDemo { public static void main(String[] args) { // 创建窗口 Frame frame = new Frame(\u0026#34;这是窗口名称\u0026#34;); // 设置窗口大小和位置 frame.setBounds(100,100,400,400); // 设置窗口可见 frame.setVisible(true); } } Panel # public class PanelDemo { public static void main(String[] args) { // 创建窗口 Frame frame = new Frame(\u0026#34;这是窗口名称\u0026#34;); // 设置窗口大小和位置 frame.setBounds(100,100,400,400); // 创建面板 Panel panel = new Panel(); // 设置面板颜色 panel.setBackground(Color.red); // 添加组件 panel.add(new Label(\u0026#34;密码\u0026#34;)); // 添加组件 panel.add(new TextField(20)); // 添加面板到窗口 frame.add(panel); // 设置窗口可见 frame.setVisible(true); } } ScrollPane # public class ScrollPanelDemo { public static void main(String[] args) { Frame frame = new Frame(\u0026#34;ScrollPanelDemo\u0026#34;); frame.setBounds(100,100,200,200); // 创建滚动面板，并指定默认开启滚动条 ScrollPane scrollPane = new ScrollPane(ScrollPane.SCROLLBARS_ALWAYS); scrollPane.add(new TextField(\u0026#34;你好 AWT ！\u0026#34;)); frame.add(scrollPane); frame.setVisible(true); } } LayoutManager布局管理器 # 之前，我们介绍了Component中有一个方法 setBounds() 可以设置当前容器的位置和大小，但是我们需要明确一件事，如果我们手动的为组件设置位置和大小的话，就会造成程序的不通用性，例如\nLabel label = new Label(\u0026#34;你好，世界\u0026#34;); 创建了一个lable组件，很多情况下，我们需要让lable组件的宽高和“你好，世界”这个字符串自身的宽高一致，这种大小称为最佳大小。由于操作系统存在差异，例如在windows上，我们要达到这样的效果，需要把该Lable组件的宽和高分别设置为100px,20px,但是在Linux操作系统上，可能需要把Lable组件的宽和高分别设置为120px,24px，才能达到同样的效果。\n为了解决这个问题，Java提供了LayoutManager布局管理器，可以根据运行平台来自动调整组件大小，程序员不用再手动设置组件的大小和位置了，只需要为容器选择合适的布局管理器即可。\nLayoutManager继承体系 # FlowLayout # 在 FlowLayout 布局管理器中，组件像水流一样向某方向流动 (排列) ，遇到障碍(边界)就折回，重头开始排列 。在默认情况下， FlowLayout 布局管理器从左向右排列所有组件，遇到边界就会折回下一行重新开始。\n构造方法 方法功能 FlowLayout() 使用默认的对齐方式及默认的垂直间距、水平间距创建 FlowLayout 布局管理器。 FlowLayout(int align) 使用指定的对齐方式及默认的垂直间距、水平间距创建 FlowLayout 布局管理器。 FlowLayout(int align,int hgap,int vgap) 使用指定的对齐方式及指定的垂直问距、水平间距创建FlowLayout 布局管理器。 align ：参数应该使用FlowLayout类的静态常量 : FlowLayout. LEFT 、 FlowLayout. CENTER 、 FlowLayout. RIGHT ，默认是左对齐。\nhgap/vgap：组件中间距通过整数设置，单位是像素，默认是5个像素。\npublic class FlowLayoutDemo { public static void main(String[] args) { Frame frame = new Frame(\u0026#34;FlowLayoutDemo\u0026#34;); frame.setBounds(100,100,400,200); // 设置流式布局，从左到右排列，并设置左右间距为2，上下间距为2 frame.setLayout(new FlowLayout(FlowLayout.LEFT,2,2)); for (int i = 0; i \u0026lt; 10; i++){ frame.add(new Button(\u0026#34;按钮\u0026#34;+i)); } frame.setVisible(true); } } BorderLayout # BorderLayout 将容器分为 EAST 、 SOUTH 、 WEST 、 NORTH 、 CENTER五个区域，普通组件可以被放置在这 5 个区域的任意一个中 。\n当改变使用 BorderLayout 的容器大小时， NORTH 、 SOUTH 和 CENTER区域水平调整，而 EAST 、 WEST 和 CENTER 区域垂直调整。使用BorderLayout 有如下两个注意点:\n当向使用 BorderLayout 布局管理器的容器中添加组件时 ，需要指定要添加到哪个区域中。如果没有指定添加到哪个区域中，则默认添加到中间区域中； 如果向同一个区域中添加多个组件时，后放入的组件会覆盖先放入的组件； 如果不往某个区域中放入组件，那么该区域不会空白出来，而是会被其他区域占用； 构造方法 方法功能 BorderLayout() 使用默认的水平间距、垂直 间距创建 BorderLayout 布局管理器 。 BorderLayout(int hgap,int vgap): 使用指定的水平间距、垂直间距创建 BorderLayout 布局管理器。 public class BorderLayoutDemo { public static void main(String[] args) { Frame frame = new Frame(\u0026#34;BorderLayoutDemo\u0026#34;); frame.setBounds(100,100,300,300); // 设置边界布局，并设置左右间距为2，上下间距为2 frame.setLayout(new BorderLayout(2,2)); frame.add(new Button(\u0026#34;NORTH\u0026#34;),BorderLayout.NORTH); frame.add(new Button(\u0026#34;SOUTH\u0026#34;),BorderLayout.SOUTH); frame.add(new Button(\u0026#34;EAST\u0026#34;),BorderLayout.EAST); frame.add(new Button(\u0026#34;WEST\u0026#34;),BorderLayout.WEST); frame.add(new Button(\u0026#34;CENTER\u0026#34;),BorderLayout.CENTER); frame.setVisible(true); } } GridLayout # GridLayout 布局管理器将容器分割成纵横线分隔的网格 ， 每个网格所占的区域大小相同。当向使用 GridLayout 布局管理器的容器中添加组件时， 默认从左向右、 从上向下依次添加到每个网格中 。\n与 FlowLayout不同的是，放置在 GridLayout 布局管理器中的各组件的大小由组件所处的区域决定(每 个组件将自动占满整个区域) 。\n构造方法 方法功能 GridLayout(int rows,in t cols) 采用指定的行数、列数，以及默认的横向间距、纵向间距将容器分割成多个网格 GridLayout(int rows,int cols,int hgap,int vgap) 采用指定的行数、列数 ，以及指定的横向间距 、 纵向间距将容器分割成多个网格。 public class GridLayoutDemo { public static void main(String[] args) { Frame frame = new Frame(\u0026#34;GridLayoutDemo\u0026#34;); frame.setBounds(100,100,200,300); Panel top = new Panel(); top.add(new TextField(25)); Panel bottom = new Panel(); bottom.setLayout(new GridLayout(5,3)); bottom.add(new Button(\u0026#34;+\u0026#34;)); bottom.add(new Button(\u0026#34;-\u0026#34;)); bottom.add(new Button(\u0026#34;*\u0026#34;)); for (int i = 0; i \u0026lt; 10; i++){ bottom.add(new Button(\u0026#34;\u0026#34; + i)); } bottom.add(new Button(\u0026#34;=\u0026#34;)); frame.add(top, BorderLayout.NORTH); frame.add(bottom, BorderLayout.CENTER); frame.setVisible(true); } } GridBagLayout # GridBagLayout 布局管理器的功能最强大，但也最复杂，与 GridLayout 布局管理器不同的是，在GridBagLayout 布局管理器中，一个组件可以跨越一个或多个网格，并可以设置各网格的大小互不相同，从而增加了布局的灵活性。当窗口的大小发生变化时，GridBagLayout 布局管理器也可以准确地控制窗口各部分的拉伸 。\n由于在GridBagLayout 布局中，每个组件可以占用多个网格，此时，我们往容器中添加组件的时候，就需要具体的控制每个组件占用多少个网格，java提供的GridBagConstaints类，与特定的组件绑定，可以完成具体大小和跨越性的设置。\nGridBagConstraints 属性如下：\n属性 含义 gridx 设置受该对象控制的GUI组件左上角所在网格的横向索引 gridy 设置受该对象控制的GUI组件左上角所在网格的纵向索引 gridwidth 设置受该对象控制的 GUI 组件横向跨越多少个网格GridBagContraints.REMAINDER：表明当前组件是横向最后一个组件GridBagConstraints.RELATIVE：表明当前组件是横向倒数第二个组件。 gridheight 设置受该对象控制的 GUI 组件纵向跨越多少个网格GridBagContraints.REMAINDER：表明当前组件是纵向最后一个组件GridBagConstraints.RELATIVE：表明当前组件是纵向倒数第二个组件。 fill 当\u0026quot;显示区域\u0026quot;大于\u0026quot;组件\u0026quot;的时候,如何调整组件 ：GridBagConstraints.NONE：GUI 组件不扩大GridBagConstraints.HORIZONTAL：GUI 组件水平扩大 以 占据空白区域GridBagConstraints.VERTICAL：GUI 组件垂直扩大以占据空白区域GridBagConstraints.BOTH：GUI 组件水平 、 垂直同时扩大以占据空白区域. ipadx 设置受该对象控制的 GUI 组件横向内部填充的大小，即在该组件最小尺寸的基础上还需要增大多少. ipady 设置受该对象控制的 GUI 组件纵向内部填充的大小，即在该组件最小尺寸的基础上还需要增大多少. insets 设置受该对象控制 的 GUI 组件的外部填充的大小 ， 即该组件边界和显示区域边界之间的距离 . weightx 设置受该对象控制 的 GUI 组件占据多余空间的水平比例， 假设某个容器的水平线上包括三个 GUI 组件， 它们的水平增加比例分别是1、2、3，但容器宽度增加 60 像素时，则第一个组件宽度增加 10 像素 ， 第二个组件宽度增加 20 像素，第三个组件宽度增加 30 像 素。 如 果其增 加比例为 0 ， 则 表示不会增加 。 weighty 设置受该对象控制的 GUI 组件占据多余空间的垂直比例 anchor 设置受该对象控制的 GUI 组件在其显示区域中的定位方式:GridBagConstraints.CENTER (中间 )GridBagConstraints.NORTH (上中 ) GridBagConstraints.NORTHWEST (左上角)GridBagConstraints.NORTHEAST (右上角)GridBagConstraints.SOUTH (下中) GridBagConstraints.SOUTHEAST (右下角)GridBagConstraints.SOUTHWEST (左下角)GridBagConstraints.EAST (右中) GridBagConstraints.WEST (左中) GridBagLayout 使用步骤如下：\n创建GridBagLaout布局管理器对象，并给容器设置该布局管理器对象；\n创建GridBagConstraints对象，并设置该对象的控制属性：\ngridx：用于指定组件在网格中所处的横向索引； gridy：用于执行组件在网格中所处的纵向索引； gridwidth：用于指定组件横向跨越多少个网格； gridheight：用于指定组件纵向跨越多少个网格； 调用GridBagLayout对象的setConstraints(Component c,GridBagConstraints gbc)方法，把即将要添加到容器中的组件c和GridBagConstraints对象关联起来；\n把组件添加到容器中；\npublic class GridBagLayoutDemo { public static void main(String[] args) { Frame frame = new Frame(\u0026#34;GridBagLayoutDemo\u0026#34;); GridBagLayout gbl = new GridBagLayout(); frame.setLayout(gbl); GridBagConstraints gbc = new GridBagConstraints(); // 设置所有的GridBagConstraints对象的fill属性为GridBagConstraints.BOTH // 当有空白区域时，组件自动扩大占满空白区域 gbc.fill = GridBagConstraints.BOTH; // 设置GridBagConstraints对象的weightx设置为1,表示横向扩展比例为1 gbc.weightx = 1; Button button1 = new Button(\u0026#34;Button1\u0026#34;); Button button2 = new Button(\u0026#34;Button2\u0026#34;); Button button3 = new Button(\u0026#34;Button3\u0026#34;); addGBLComponent(frame,button1,gbl,gbc); addGBLComponent(frame,button2,gbl,gbc); addGBLComponent(frame,button3,gbl,gbc); Button button4 = new Button(\u0026#34;Button4\u0026#34;); // 把GridBagConstraints的gridwidth设置为GridBagConstraints.REMAINDER // 则表明当前组件是横向最后一个组件 gbc.gridwidth = GridBagConstraints.REMAINDER; addGBLComponent(frame,button4,gbl,gbc); Button button5 = new Button(\u0026#34;Button5\u0026#34;); // 由于gridwidth的值还是GridBagConstraints.REMAINDER，所以button5也会占满横向空白区域 addGBLComponent(frame,button5,gbl,gbc); // 设置button6和button7的纵向向扩展比例为1 gbc.weighty=1; // 把GridBagConstaints的gridheight和gridwidth设置为2，表示纵向和横向会占用两个网格 gbc.gridwidth = 2; gbc.gridheight = 2; Button button6 = new Button(\u0026#34;Button6\u0026#34;); Button button7 = new Button(\u0026#34;Button7\u0026#34;); addGBLComponent(frame,button6,gbl,gbc); addGBLComponent(frame,button7,gbl,gbc); // 调用pack方法，让frame的大小适应组件的大小 frame.pack(); frame.setVisible(true); } public static void addGBLComponent(Container container,Component component,GridBagLayout gbl,GridBagConstraints gbc) { gbl.setConstraints(component,gbc); container.add(component); } } CardLayout # CardLayout 布局管理器以时间而非空间来管理它里面的组件，它将加入容器的所有组件看成一叠卡片（每个卡片其实就是一个组件），每次只有最上面的那个 Component 才可见。就好像一副扑克牌，它们叠在一起，每次只有最上面的一张扑克牌才可见。\n方法名称 方法功能 CardLayout() 创建默认的 CardLayout 布局管理器。 CardLayout(int hgap,int vgap) 通过指定卡片与容器左右边界的间距hgap 、上下边界vgap的间距来创建 CardLayout 布局管理器. first(Container target) 显示target 容器中的第一张卡片. last(Container target) 显示target 容器中的最后一张卡片. previous(Container target) 显示target 容器中的前一张卡片. next(Container target) 显示target 容器中的后一张卡片. show(Container taget,String name) 显 示 target 容器中指定名字的卡片. public class CardLayoutDemo { public static void main(String[] args) { Frame frame = new Frame(\u0026#34;CardLayoutDemo\u0026#34;); // 创建一个Panel容器p1，并设置其布局管理器为CardLayout,用来存放多张卡片 Panel p1 = new Panel(); CardLayout cardLayout = new CardLayout(); p1.setLayout(cardLayout); // 添加5张卡片 for (int i = 1; i \u0026lt;= 5; i++) { String name = \u0026#34;Button - \u0026#34; + i; p1.add(name,new Button(name)); } // 创建一个Panel容器p2，用来存放上下翻页按钮 Panel p2 = new Panel(); // 创建按钮，并添加点击事件 ActionListener actionListener = new ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { String command = e.getActionCommand(); switch (command){ case \u0026#34;上一张\u0026#34;: cardLayout.previous(p1); break; case \u0026#34;下一张\u0026#34;: cardLayout.next(p1); break; case \u0026#34;第一张\u0026#34;: cardLayout.first(p1); break; case \u0026#34;最后一张\u0026#34;: cardLayout.last(p1); break; case \u0026#34;第三张\u0026#34;: cardLayout.show(p1,\u0026#34;Button - 3\u0026#34;); break; } } }; Button b1 = new Button(\u0026#34;上一张\u0026#34;); Button b2 = new Button(\u0026#34;下一张\u0026#34;); Button b3 = new Button(\u0026#34;第一张\u0026#34;); Button b4 = new Button(\u0026#34;最后一张\u0026#34;); Button b5 = new Button(\u0026#34;第三张\u0026#34;); b1.addActionListener(actionListener); b2.addActionListener(actionListener); b3.addActionListener(actionListener); b4.addActionListener(actionListener); b5.addActionListener(actionListener); p2.add(b1); p2.add(b2); p2.add(b3); p2.add(b4); p2.add(b5); frame.add(p1,BorderLayout.CENTER); frame.add(p2,BorderLayout.SOUTH); frame.pack(); frame.setVisible(true); } } BoxLayout # 为了简化开发，Swing 引入了 一个新的布局管理器 : BoxLayout 。 BoxLayout 可以在垂直和水平两个方向上摆放 GUI 组件， BoxLayout 提供了如下一个简单的构造器：\n方法名称 方法功能 BoxLayout(Container target, int axis) 指定创建基于 target 容器的 BoxLayout 布局管理器，该布局管理器里的组件按 axis 方向排列。其中 axis 有 BoxLayout.X_AXIS（横向）和 BoxLayout.Y_AXIS （纵向）两个方向。 public class BoxLayoutDemo { public static void main(String[] args) { Frame frame = new Frame(\u0026#34;BoxLayoutDemo\u0026#34;); // 创建BoxLayout布局管理器，并指定容器为上面的frame对象，指定组件排列方向为横向 BoxLayout boxLayout = new BoxLayout(frame,BoxLayout.X_AXIS); frame.setLayout(boxLayout); frame.add(new Button(\u0026#34;Button1\u0026#34;)); frame.add(new Button(\u0026#34;Button2\u0026#34;)); frame.pack(); frame.setVisible(true); } } 在java.swing包中，提供了一个新的容器类Box，该容器的默认布局管理器就是BoxLayout,大多数情况下，使用Box容器去容纳多个GUI组件，然后再把Box容器作为一个组件，添加到其他的容器中，从而形成整体窗口布局。\nBox类方法名称 方法功能 static Box createHorizontalBox() 创建一个水平排列组件的 Box 容器 。 static Box createVerticalBox() 创建一个垂直排列组件的 Box 容器 。 public class BoxLayoutDemo { public static void main(String[] args) { //创建Frame对象 Frame frame = new Frame(\u0026#34;BoxLayoutDemo\u0026#34;); //创建一个横向的Box,并添加两个按钮 Box hBox = Box.createHorizontalBox(); hBox.add(new Button(\u0026#34;水平按钮一\u0026#34;)); hBox.add(new Button(\u0026#34;水平按钮二\u0026#34;)); //创建一个纵向的Box，并添加两个按钮 Box vBox = Box.createVerticalBox(); vBox.add(new Button(\u0026#34;垂直按钮一\u0026#34;)); vBox.add(new Button(\u0026#34;垂直按钮二\u0026#34;)); //把box容器添加到frame容器中 frame.add(hBox,BorderLayout.NORTH); frame.add(vBox); //设置frame最佳大小并可见 frame.pack(); frame.setVisible(true); } } 在原有的组件需要间隔的地方，添加间隔即可，而每个间隔可以是一个组件，只不过该组件没有内容，仅仅起到一种分隔的作用。\nBox类方法名称 方法功能 static Component createHorizontalGlue() 创建一条水平 Glue (可在两个方向上同时拉伸的间距) static Component createVerticalGlue() 创建一条垂直 Glue (可在两个方向上同时拉伸的间距） static Component createHorizontalStrut(int width) 创建一条指定宽度(宽度固定了，不能拉伸)的水平Strut (可在垂直方向上拉伸的间距) static Component createVerticalStrut(int height) 创建一条指定高度(高度固定了，不能拉伸)的垂直Strut (可在水平方向上拉伸的间距) public class BoxLayoutDemo { public static void main(String[] args) { //创建Frame对象 Frame frame = new Frame(\u0026#34;BoxLayoutDemo\u0026#34;); //创建一个横向的Box,并添加两个按钮 Box hBox = Box.createHorizontalBox(); hBox.add(new Button(\u0026#34;水平按钮一\u0026#34;)); hBox.add(Box.createHorizontalGlue());//两个方向都可以拉伸的间隔 hBox.add(new Button(\u0026#34;水平按钮二\u0026#34;)); hBox.add(Box.createHorizontalStrut(10));//水平间隔固定，垂直间方向可以拉伸 hBox.add(new Button(\u0026#34;水平按钮3\u0026#34;)); //创建一个纵向的Box，并添加两个按钮 Box vBox = Box.createVerticalBox(); vBox.add(new Button(\u0026#34;垂直按钮一\u0026#34;)); vBox.add(Box.createVerticalGlue());//两个方向都可以拉伸的间隔 vBox.add(new Button(\u0026#34;垂直按钮二\u0026#34;)); vBox.add(Box.createVerticalStrut(10));//垂直间隔固定，水平方向可以拉伸 vBox.add(new Button(\u0026#34;垂直按钮三\u0026#34;)); //把box容器添加到frame容器中 frame.add(hBox, BorderLayout.NORTH); frame.add(vBox); //设置frame最佳大小并可见 frame.pack(); frame.setVisible(true); } } AWT中常用组件 # 组件名 功能 Button 按钮 Canvas 用于绘图的画布 Checkbox 复选框组件（也可当做单选框组件使用） CheckboxGroup 用于将多个Checkbox 组件组合成一组， 一组 Checkbox 组件将只有一个可以被选中，即全部变成单选框组件 Choice 下拉选择框 Frame 窗口 ， 在 GUI 程序里通过该类创建窗口 Label 标签类，用于放置提示性文本 List JU表框组件，可以添加多项条目 Panel 不能单独存在基本容器类，必须放到其他容器中 Scrollbar 滑动条组件。如果需要用户输入位于某个范围的值 ， 就可以使用滑动条组件 ，比如调 色板中设置 RGB 的三个值所用的滑动条。当创建一个滑动条时，必须指定它的方向、初始值、 滑块的大小、最小值和最大值。 ScrollPane 带水平及垂直滚动条的容器组件 TextArea 多行文本域 TextField 单行文本框 public class BaseComponentDemo { private Frame frame = new Frame(\u0026#34;BaseComponentDemo\u0026#34;); private TextArea textArea = new TextArea(\u0026#34;textArea\u0026#34;,10,30); private TextField textField = new TextField(\u0026#34;textField\u0026#34;,10); private CheckboxGroup cbg = new CheckboxGroup(); private Checkbox boy = new Checkbox(\u0026#34;男\u0026#34;,cbg,true); private Checkbox girl = new Checkbox(\u0026#34;女\u0026#34;,cbg,false); private Button button = new Button(\u0026#34;确定\u0026#34;); private Choice choice = new Choice(); private List list = new List(11); private void init(){ Panel center = new Panel(); Panel bottom = new Panel(); Panel right = new Panel(); Box verticalBox = Box.createVerticalBox(); verticalBox.add(textArea); verticalBox.add(textField); center.add(verticalBox); Box horizontalBox = Box.createHorizontalBox(); horizontalBox.add(boy); horizontalBox.add(girl); choice.add(\u0026#34;Java\u0026#34;); choice.add(\u0026#34;C++\u0026#34;); choice.add(\u0026#34;Python\u0026#34;); horizontalBox.add(choice); horizontalBox.add(button); bottom.add(horizontalBox); list.add(\u0026#34;Windows\u0026#34;); list.add(\u0026#34;Linux\u0026#34;); list.add(\u0026#34;MacOS\u0026#34;); right.add(list); frame.add(center,BorderLayout.CENTER); frame.add(bottom,BorderLayout.SOUTH); frame.add(right,BorderLayout.EAST); frame.pack(); frame.setVisible(true); } public static void main(String[] args) { new BaseComponentDemo().init(); } } 菜单组件 # 菜单组件名称 功能 MenuBar 菜单条，菜单的容器 。 Menu 菜单组件，菜单项的容器。它也是Menultem的子类，所以可作为菜单项使用。 PopupMenu 上下文菜单组件（右键菜单组件） Menultem 菜单项组件 。 CheckboxMenuItem 复选框菜单项组件 窗口菜单 # public class MenuDemo { private Frame frame = new Frame(\u0026#34;MenuDemo\u0026#34;); private MenuBar menuBar = new MenuBar(); private TextArea textArea = new TextArea(10,30); private void loadMenu() { Menu fileMenu = new Menu(\u0026#34;文件\u0026#34;); MenuItem openItem = new MenuItem(\u0026#34;打开\u0026#34;); MenuItem saveItem = new MenuItem(\u0026#34;保存\u0026#34;); fileMenu.add(openItem); fileMenu.add(saveItem); Menu editMenu = new Menu(\u0026#34;编辑\u0026#34;); MenuItem cutItem = new MenuItem(\u0026#34;剪切\u0026#34;); MenuItem copyItem = new MenuItem(\u0026#34;复制\u0026#34;); MenuItem pasteItem = new MenuItem(\u0026#34;粘贴\u0026#34;); editMenu.add(cutItem); editMenu.add(copyItem); editMenu.add(pasteItem); Menu helpMenu = new Menu(\u0026#34;帮助\u0026#34;); MenuItem aboutItem = new MenuItem(\u0026#34;关于\u0026#34;); helpMenu.add(aboutItem); editMenu.add(helpMenu); menuBar.add(fileMenu); menuBar.add(editMenu); frame.setMenuBar(menuBar); // 监听打开菜单 openItem.addActionListener(e -\u0026gt; { FileDialog fd_load = new FileDialog(frame, \u0026#34;选择文件\u0026#34;, FileDialog.LOAD); fd_load.setVisible(true); System.out.println(\u0026#34;文件路径：\u0026#34; + fd_load.getDirectory() + fd_load.getFile()); }); } private void init(){ loadMenu(); frame.add(textArea); frame.pack(); frame.setVisible(true); } public static void main(String[] args) { new MenuDemo().init(); } } 右键菜单 # public class PopupMenuDemo { private Frame frame = new Frame(\u0026#34;PopupMenuDemo\u0026#34;); //创建PopupMenu菜单 private PopupMenu popupMenu = new PopupMenu(); //创建菜单条 private MenuItem commentItem = new MenuItem(\u0026#34;注释\u0026#34;); private MenuItem cancelItem = new MenuItem(\u0026#34;取消注释\u0026#34;); private MenuItem copyItem = new MenuItem(\u0026#34;复制\u0026#34;); private MenuItem pasteItem = new MenuItem(\u0026#34;保存\u0026#34;); //创建一个文本域 private TextArea ta = new TextArea(\u0026#34;Hello\u0026#34;, 6, 40); //创建一个Panel private Panel panel = new Panel(); public void init(){ //把菜单项添加到PopupMenu中 popupMenu.add(commentItem); popupMenu.add(cancelItem); popupMenu.add(copyItem); popupMenu.add(pasteItem); //设置panel大小 panel.setPreferredSize(new Dimension(300,100)); //把PopupMenu添加到panel中 panel.add(popupMenu); //为panel注册鼠标事件 panel.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { // 判断当前鼠标操作是不是触发PopupMenu的操作 if (e.isPopupTrigger()){ popupMenu.show(panel,e.getX(),e.getY()); } } }); //把ta添加到frame中间区域中 frame.add(ta); //把panel添加到frame底部 frame.add(panel,BorderLayout.SOUTH); //设置frame最佳大小，并可视； frame.pack(); frame.setVisible(true); } public static void main(String[] args) { new PopupMenuDemo().init(); } } 对话框Dialog # Dialog 是 Window 类的子类，是一个容器类，属于特殊组件。对话框是可以独立存在的顶级窗口， 因此用法与普通窗口的用法几乎完全一样，但是使用对话框需要注意下面两点：\n对话框通常依赖于其他窗口，就是通常需要有一个父窗口； 对话框有非模态（non-modal）和模态（modal）两种，当模态对话框被打开后，该模式对话框总是位于它的父窗口之上，在模态对话框被关闭之前，父窗口无法获得焦点。 方法名称 方法功能 Dialog(Frame owner, String title, boolean modal) 创建一个对话框对象： owner：当前对话框的父窗口 ；title：当前对话框的标题；modal：当前对话框是否是模态对话框，true/false public class DialogDemo { public static void main(String[] args) { Frame frame = new Frame(\u0026#34;DialogDemo\u0026#34;); Dialog d1 = new Dialog(frame,\u0026#34;模态对话框\u0026#34;,true); Dialog d2 = new Dialog(frame,\u0026#34;非模态对话框\u0026#34;,false); d1.setBounds(100,100,300,200); d2.setBounds(100,100,300,200); Button b1 = new Button(\u0026#34;模态对话框\u0026#34;); Button b2 = new Button(\u0026#34;非模态对话框\u0026#34;); b1.addActionListener(e -\u0026gt; { d1.setVisible(true); }); b2.addActionListener(e -\u0026gt; { d2.setVisible(true); }); frame.add(b1,BorderLayout.NORTH); frame.add(b2,BorderLayout.SOUTH); frame.pack(); frame.setVisible(true); } } FileDialog # Dialog 类还有 一个子类：FileDialog ，它代表一个文件对话框，用于打开或者保存文件，需要注意的是FileDialog无法指定模态或者非模态，这是因为 FileDialog 依赖于运行平台的实现，如果运行平台的文件对话框是模态的，那么 FileDialog 也是模态的；否则就是非模态的 。\n方法名称 方法功能 FileDialog(Frame parent, String title, int mode) 创建一个文件对话框。parent：指定父窗口； title：对话框标题； mode：文件对话框类型，指定为FileDialog.load用于打开文件，指定为FileDialog.SAVE用于保存文件 String getDirectory() 获取被打开或保存文件的所在目录 String getFile() 获取被打开或保存文件的文件名 public class FileDialogDemo { public static void main(String[] args) { Frame frame = new Frame(\u0026#34;FileDialogDemo\u0026#34;); FileDialog fd_load = new FileDialog(frame, \u0026#34;选择文件\u0026#34;, FileDialog.LOAD); // 只能打开 jpg、png fd_load.setFilenameFilter((dir, name) -\u0026gt; { return name.endsWith(\u0026#34;.jpg\u0026#34;) || name.endsWith(\u0026#34;.png\u0026#34;); }); FileDialog fd_save = new FileDialog(frame, \u0026#34;保存文件\u0026#34;, FileDialog.SAVE); Button b1 = new Button(\u0026#34;选择文件\u0026#34;); b1.addActionListener(e -\u0026gt; { fd_load.setVisible(true); System.out.println(\u0026#34;文件路径：\u0026#34; + fd_load.getDirectory() + fd_load.getFile()); }); Button b2 = new Button(\u0026#34;保存文件\u0026#34;); b2.addActionListener(e -\u0026gt; { fd_save.setVisible(true); System.out.println(\u0026#34;文件路径：\u0026#34; + fd_save.getDirectory() + fd_save.getFile()); }); frame.add(b1,BorderLayout.NORTH); frame.add(b2,BorderLayout.SOUTH); frame.pack(); frame.setVisible(true); } } 事件处理 # 前面介绍了如何放置各种组件，从而得到了丰富多彩的图形界面，但这些界面还不能响应用户的任何操作。比如单击前面所有窗口右上角的X按钮，但窗口依然不会关闭。因为在 AWT 编程中 ，所有用户的操作，都必须都需要经过一套事件处理机制来完成，而 Frame 和组件本身并没有事件处理能力 。\n具体的使用步骤如下：\n创建事件源组件对象； 自定义类，实现xxxListener接口，重写方法； 创建事件监听器对象(自定义类对象) 调用事件源组件对象的addXxxListener()方法完成注册监听 public class EventDemo { public static void main(String[] args) { Frame frame = new Frame(\u0026#34;EventDemo\u0026#34;); // 添加窗口关闭事件 frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { System.exit(0); } }); TextField textField = new TextField(30); Button button = new Button(\u0026#34;Click Me\u0026#34;); // 添加点击事件 button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { textField.setText(\u0026#34;Button Clicked\u0026#34;); } }); frame.add(textField, BorderLayout.NORTH); frame.add(button, BorderLayout.SOUTH); frame.pack(); frame.setVisible(true); } } 常见事件和事件监听器 # 事件监听器必须实现事件监听器接口， AWT 提供了大量的事件监听器接口用于实现不同类型的事件监听器，用于监听不同类型的事件 。 AWT 中提供了丰富的事件类，用于封装不同组件上所发生的特定操作， AWT 的事件类都是 AWTEvent 类的子类， AWTEvent是 EventObject 的子类。\n事件 # AWT把事件分为了两大类：\n低级事件：这类事件是基于某个特定动作的事件。比如进入、点击、拖放等动作的鼠标事件，再比如得到焦点和失去焦点等焦点事件。 高级事件：这类事件并不会基于某个特定动作，而是根据功能含义定义的事件。 低级事件 触发时机 ComponentEvent 组件事件 ， 当 组件尺寸发生变化、位置发生移动、显示/隐藏状态发生改变时触发该事件。 ContainerEvent 容器事件 ， 当容器里发生添加组件、删除组件时触发该事件 。 WindowEvent 窗口事件， 当窗 口状态发生改变 ( 如打开、关闭、最大化、最小化)时触发该事件 。 FocusEvent 焦点事件 ， 当组件得到焦点或失去焦点 时触发该事件 。 KeyEvent 键盘事件 ， 当按键被按下、松开、单击时触发该事件。 MouseEvent 鼠标事件，当进行单击、按下、松开、移动鼠标等动作 时触发该事件。 PaintEvent 组件绘制事件 ， 该事件是一个特殊的事件类型 ， 当 GUI 组件调用 update/paint 方法来呈现自身时触发该事件，该事件并非专用于事件处理模型 。 高级事件 触发时机 ActionEvent 动作事件 ，当按钮、菜单项被单击，在 TextField 中按 Enter 键时触发 AjustmentEvent 调节事件，在滑动条上移动滑块以调节数值时触发该事件。 ltemEvent 选项事件，当用户选中某项， 或取消选中某项时触发该事件 。 TextEvent 文本事件， 当文本框、文本域里的文本发生改变时触发该事件。 事件监听器 # 不同的事件需要使用不同的监听器监听，不同的监听器需要实现不同的监听器接口， 当指定事件发生后 ， 事件监听器就会调用所包含的事件处理器（实例方法）来处理事件 。\n事件类别 描述信息 监听器接口名 ActionEvent 激活组件 ActionListener ItemEvent 选择了某些项目 ItemListener MouseEvent 鼠标移动 MouseMotionListener MouseEvent 鼠标点击等 MouseListener KeyEvent 键盘输入 KeyListener FocusEvent 组件收到或失去焦点 FocusListener AdjustmentEvent 移动了滚动条等组件 AdjustmentListener ComponentEvent 对象移动缩放显示隐藏等 ComponentListener WindowEvent 窗口收到窗口级事件 WindowListener ContainerEvent 容器中增加删除了组件 ContainerListener TextEvent 文本字段或文本区发生改变 TextListener ","date":"2025-05-06","externalUrl":null,"permalink":"/posts/3ab7256e/4e1989d8/b85dd164/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJava 图形化界面 \n    \u003cdiv id=\"java-图形化界面\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#java-%e5%9b%be%e5%bd%a2%e5%8c%96%e7%95%8c%e9%9d%a2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eJava使用AWT和Swing相关的类可以完成图形化界面编程，其中AWT的全称是抽象窗口工具集(Abstract Window Toolkit),它是sun公司最早提供的GUI库，这个GUI库提供了一些基本功能，但这个GUI库的功能比较有限，所以后来sun公司又提供了Swing库。通过使用AWT和Swing提供的图形化界面组件库，java的图形化界面编程非常简单，程序只需要依次创建所需的图形组件，并以合适的方式将这些组件组织在一起，就可以开发出非常美观的用户界面。\u003c/p\u003e","title":"1、AWT","type":"posts"},{"content":" Electron # Electron 基于 Chromium 和 Node.js, 让你可以使用 HTML, CSS 和 JavaScript 构建应用。\n中文官网：https://www.electronjs.org/zh/\n常见的桌面GUI工具介绍 # 名称 语言 优点 缺点 QT C++ 跨平台、性能好、生态好 依赖多，程序包大 PyQT Python 底层集成度高、易上手 授权问题 WPF C# 类库丰富、扩展灵活 只支持Windows，程序包大 WinForm C# 性能好，组件丰富，易上手 只支持Windows，UI差 Swing Java 基于AWT，组件丰富 性能差，UI一般 NW.js JS 跨平台性好，界面美观 底层交互差、性能差，包大 Electron JS 相比NW发展更好 底层交互差、性能差，包大 CEF C++ 性能好，灵活集成，UI美观 占用资源多，包大 环境 # 必须安装node、npm\nDemo # 创建应用程序 # 1、创建脚手架 # mkdir demo \u0026amp;\u0026amp; cd demo npm init 执行完npm init后，会提示输入信息，除了entry point这个选项必须为main.js外，其余随意\n2、安装electron包 # 由于网络原因，安装electron时可能会卡住，需要在项目根路径创建.npmrc文件，并写入镜像信息\nelectron_mirror = https://npmmirror.com/mirrors/electron/ 或者执行命令：export electron_mirror = https://npmmirror.com/mirrors/electron/\n然后再安装\nnpm install --save-dev electron 由于electron本身没有热启动的功能，所以需要引入依赖来实现\nnpm install --save-dev nodemon 3、配置启动命令 # 修改package.json的main标签，启动文件为main.js\n在package.json的scripts标签下，添加\u0026quot;start\u0026quot;: \u0026quot;electron .\u0026quot;，开发环境使用npm start打开应用\n{ \u0026#34;main\u0026#34;: \u0026#34;main.js\u0026#34;, \u0026#34;scripts\u0026#34;: { \u0026#34;start\u0026#34;: \u0026#34;electron .\u0026#34;, \u0026#34;warm_start\u0026#34;: \u0026#34;nodemon --exec electron . . --watch ./ --ext .js,.html,.css,.vue\u0026#34; //热启动 } } 4、创建页面index.html # \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --\u0026gt; \u0026lt;meta http-equiv=\u0026#34;Content-Security-Policy\u0026#34; content=\u0026#34;default-src \u0026#39;self\u0026#39;; script-src \u0026#39;self\u0026#39;\u0026#34;\u0026gt; \u0026lt;meta http-equiv=\u0026#34;X-Content-Security-Policy\u0026#34; content=\u0026#34;default-src \u0026#39;self\u0026#39;; script-src \u0026#39;self\u0026#39;\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Hello World!\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;Hello World!\u0026lt;/h1\u0026gt; We are using Node.js \u0026lt;span id=\u0026#34;node-version\u0026#34;\u0026gt;\u0026lt;/span\u0026gt;, Chromium \u0026lt;span id=\u0026#34;chrome-version\u0026#34;\u0026gt;\u0026lt;/span\u0026gt;, and Electron \u0026lt;span id=\u0026#34;electron-version\u0026#34;\u0026gt;\u0026lt;/span\u0026gt;. \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 5、编写main.js # /** 此处需要引入两个模块 * app 模块，它控制应用程序的事件生命周期。 * BrowserWindow 模块，它创建和管理应用程序窗口。 */ const {app,BrowserWindow} = require(\u0026#39;electron\u0026#39;) function creatWindow(){ win = new BrowserWindow({ width: 1200, height: 800 }) //加载html文件 win.loadFile(\u0026#34;index.html\u0026#34;) //加载链接 win.loadURL(\u0026#34;https://www.ygang.top\u0026#34;) //开启开发者工具 win.webContents.openDevTools(); } app.whenReady().then(() =\u0026gt; { creatWindow() }) 此时，使用npm start命令，可以打开窗口啦\n程序运行在Web环境，可以按照正常的前端写法来写，index.html中，不需要引入main.js，其余的js正常引入使用就可以\n打包 # Packager # a、安装打包工具 # cnpm install -g electron-packager b、打包命令 # electron-packager ./ \u0026#39;HelloWorld\u0026#39; --platform=win32 --arch=x64 --icon=icon.ico --out=./out --asar --app-version=0.0.1 --overwrite --electron-version 16.0.1 参数说明：\n./：被打包的项目路径，当前目录\n'HelloWorld'：打包后的项目名\n--platform：打包哪个平台的应用，可选win32，linux，darwin，mas，这个也可以换为--all，打包全平台\n--arck：系统位数，x86，x64\n--icon：应用图标\n--out：打包文件输出目录\n--app-version：应用版本\n-asar：可以不加，影响打包后源码格式\n--overwrite：是否覆盖原来打包的\n--electron-version：Electron版本\n可以将打包命令，配置在package.json文件scripts中\n{ \u0026#34;scripts\u0026#34;: { \u0026#34;package\u0026#34;: \u0026#34;electron-packager ./ \u0026#39;HelloWorld\u0026#39; --platform=win32 --arch=x64 --icon=icon.ico --out=./out --asar --app-version=0.0.1 --overwrite --electron-version 16.0.1\u0026#34; } } c、执行打包命令 # npm run package 主进程和渲染进程 # 主进程 每个 Electron 应用都有一个单一的主进程，作为应用程序的入口点（主进程具有唯一性）。任何 Electron 应用程序的入口都是 main.js 文件（使用vue 的 electron 插件则是background.js），负责控制应用的生命周期、创建和管理窗口、与操作系统进行交互等。 主进程在 Node.js 环境中运行，它具有 require 模块和使⽤所有 Node.js API 的能力。 主进程的核心：使用 BrowserWindow 来创建和管理应用程序窗口。 渲染进程 每个 BrowserWindow 实例都对应⼀个单独的渲染进程。 一个 Electron 窗口可以包含一个或多个渲染进程，每个渲染进程负责渲染网页内容并执行网页中的 JavaScript 代码。（关系类似于浏览器、浏览器中的标签页） 运行在渲染器进程中的代码，必须遵守网页标准。这意味着 渲染进程无权直接访问 require 或 使用 任何 Node.js API。 渲染进程主要负责呈现用户界面、响应用户交互、执行网页中的业务逻辑等。 注意：主进程使用 BrowserWindow 创建实例，主进程销毁后，对应的渲染进程会被终止。主进程与渲染进程通过 IPC 方式（事件驱动）进行通信。\n主进程事件生命周期 # // 当所有窗口关闭 app.on(\u0026#39;window-all-closed\u0026#39;, () =\u0026gt; { console.log(\u0026#39;window-all-closed\u0026#39;) // 对于 MacOS 系统 -\u0026gt; 关闭窗口时，不会直接退出应用 if (process.platform !== \u0026#39;darwin\u0026#39;) { app.quit() } }) // 当应用退出 app.on(\u0026#39;quit\u0026#39;, () =\u0026gt; { console.log(\u0026#39;quit\u0026#39;) }) app.whenReady().then(() =\u0026gt; { createWindow() // 在MacOS下，当全部窗口关闭，点击 dock 图标，窗口再次打开。 app.on(\u0026#39;activate\u0026#39;, () =\u0026gt; { if (BrowserWindow.getAllWindows().length === 0) { createWindow() } }) }) preload.js # 预加载（preload）脚本在 Electron 应用中起着重要的桥梁作用，它允许渲染进程安全地与主进程进行交互，同时增强了应用的安全性和性能。\n预加载（preload）脚本在渲染器进程加载之前加载，并有权访问两个 渲染器全局 (例如 window 和 document) 和 Node.js 环境。\n预加载（preload）脚本是运行在渲染器进程中的，但它是在网页内容加载之前执行的。 这意味着它具有比普通渲染器更高的权限，可以访问 Node.js API ，同时也可以与网页内容进行更安全的交互。\n创建一个名为 preload.js 的新脚本如下\n// contextBridge：在隔离的上下文中创建一个安全的、双向的、同步的桥梁。 const {contextBridge} = require(\u0026#39;electron\u0026#39;) // 暴露数据给渲染进程 contextBridge.exposeInMainWorld(\u0026#39;MyApi\u0026#39;, { version: () =\u0026gt; { return process.version; }, versions: process.versions, num: 666 }) 在主线程main.js中引⼊ preload.js\nconst { app, BrowserWindow } = require(\u0026#39;electron\u0026#39;) // 导入 Node.js 的 path 模块 const path = require(\u0026#39;node:path\u0026#39;) // 修改已有的 createWindow() 方法 function createWindow() { const win = new BrowserWindow({ width: 500, // 窗口宽度 height: 300, // 窗口高度 autoHideMenuBar: true, // 隐藏菜单栏 webPreferences: { // 此处只能使用绝对路径 preload: path.join(__dirname, \u0026#39;preload.js\u0026#39;) } }); // 在窗口中加载一个远程页面 win.loadFile(\u0026#39;./pages/index.html\u0026#39;); } 在渲染进程中，即可通过 window 对象来访问\n主进程与渲染进程通信 # 我们通过上述的preload.js发现，我已经可以在preload.js中生成方法并访问 Node.js 的 API，那为什么还需要看这个主进程与渲染进程的通信呢？\n注意： 预加载（preload）脚本只能访问部分 Node.js API，但是主进程可以访问全部API。此时，需要使用进程通信。\nipcMain和ipcRenderer # main.js，主进程\nconst fs = require(\u0026#39;fs\u0026#39;) const {ipcMain} = require(\u0026#39;electron\u0026#39;) function newFile(content){ fs.writeFile(\u0026#39;D:\\\\桌面\\\\electron.txt\u0026#39;,content,() =\u0026gt; {}) return \u0026#39;success\u0026#39; } ipcMain.handle(\u0026#39;new-file-event\u0026#39;,async (e,arg) =\u0026gt; { return newFile(arg) }) preload.js，需要将ipcRenderer对象绑定到window对象上，供渲染进程使用\nconst {contextBridge,ipcRenderer} = require(\u0026#39;electron\u0026#39;) contextBridge.exposeInMainWorld(\u0026#39;ipcRenderer\u0026#39;,ipcRenderer) index.js，渲染进程\ndocument.getElementById(\u0026#39;btn\u0026#39;).addEventListener(\u0026#39;click\u0026#39;,() =\u0026gt; { window.ipcRenderer.invoke(\u0026#39;new-file-event\u0026#39;,\u0026#39;hahaha\u0026#39;).then((msg) =\u0026gt; { console.log(msg) }) }) remote模块 # 如果需要在渲染进程中，使用主进程的对象，那么需要使用remote模块\n如果是electron13以后，那么需要手动加入remote包\nnpm install --save @electron/remote 主进程共享：\nvar electron = require(\u0026#39;electron\u0026#39;) // 引入electron模块 var app = electron.app // 创建electron引用 var BrowserWindow = electron.BrowserWindow; // 创建窗口引用 process.env[\u0026#39;ELECTRON_DISABLE_SECURITY_WARNINGS\u0026#39;] = \u0026#39;true\u0026#39;; //关闭安全检查 var mainWindow = null ; // 声明要打开的主窗口 app.on(\u0026#39;ready\u0026#39;,()=\u0026gt;{ mainWindow = new BrowserWindow({}) // 初始化remote require(\u0026#39;@electron/remote/main\u0026#39;).initialize() require(\u0026#39;@electron/remote/main\u0026#39;).enable(mainWindow.webContents) mainWindow.loadFile(\u0026#39;index.html\u0026#39;) // 加载那个页面 // 分享主进程的数据 global.shareObject = { mainWindow: mainWindow, Menu:electron.Menu }; }) 渲染进程引入：\n// 获取主进程全局变量 const shareObject = require(\u0026#39;@electron/remote\u0026#39;).getGlobal(\u0026#34;shareObject\u0026#34;) var Menu = shareObject.Menu; var mainWindow = shareObject.mainWindow; 主进程 # App # 路径 # 如果需要获取 Electron exe 程序的所在目录\nprocess.cwd() 事件 # before-quit # 在应用程序退出之前触发\napp.on(\u0026#39;before-quit\u0026#39;, (e) =\u0026gt; { console.log(\u0026#39;App is quiting\u0026#39;) e.preventDefault() }) browser-window-blur # browserWindow 失去焦点时触发\napp.on(\u0026#39;browser-window-blur\u0026#39;, (e) =\u0026gt; { console.log(\u0026#39;App unfocused\u0026#39;) }) browser-window-focus # app.on(\u0026#39;browser-window-focus\u0026#39;, (e) =\u0026gt; { console.log(\u0026#39;App focused\u0026#39;) }) 方法 # app.quit() # 退出整个应用\napp.quit() app.getPath(name) # 获取系统目录\u0026quot;home\u0026quot; | \u0026quot;appData\u0026quot; | \u0026quot;userData\u0026quot; | \u0026quot;sessionData\u0026quot; | \u0026quot;temp\u0026quot; | \u0026quot;exe\u0026quot; | \u0026quot;module\u0026quot; | \u0026quot;desktop\u0026quot; | \u0026quot;documents\u0026quot; | \u0026quot;downloads\u0026quot; | \u0026quot;music\u0026quot; | \u0026quot;pictures\u0026quot; | \u0026quot;videos\u0026quot; | \u0026quot;recent\u0026quot; | \u0026quot;logs\u0026quot; | \u0026quot;crashDumps\u0026quot;\napp.getPath(\u0026#39;home\u0026#39;) BrowserWindow # 创建并控制浏览器窗口，new的时候就会创建显示\n创建一个窗口 # const win = new BrowserWindow({ options }) win.loadFile(\u0026#39;index.html\u0026#39;) //options属性 width 整数型 (可选) - 窗口的宽度（以像素为单位）。 默认值为 800 height 整数型 (可选) - 窗口的高度（以像素为单位）。 默认值为 600 x Interger (可选) - (必选 如果使用了y) 窗口相对于屏幕左侧的偏移量。 默认值为将窗口居中 y Integer (可选) - (必选 如果使用了x) 窗口相对于屏幕顶端的偏移量。 默认值为将窗口居中 useContentSize boolean (可选) - 根据内容大小调整窗口大小。默认值为 false center boolean (可选) - 窗口是否在屏幕居中. 默认值为 false minWidth（可选）-窗口的最小宽度。默认为0 默认值为 0 minHeight Integer(可选) - 窗口的最小高度。 默认值为 0 maxWidth Integer(可选)-窗口的最大宽度。 默认值不限 maxHeight Integer (可选) - 窗口的最大高度。 默认值不限 resizable boolean (可选) - 窗口大小是否可调整。 默认值为 true movable boolean (可选) macOS Windows - 窗口是否可移动。 在 Linux 上未实现。 默认值为 true minimizable boolean (可选) macOS Windows - 窗口是否可最小化。 在 Linux 上未实现。 默认值为 true maximizable boolean (可选) macOS Windows - 窗口是否最大化。 在 Linux 上未实现。 默认值为 true closable boolean (可选) macOS Windows - 窗口是否可关闭。 在 Linux 上未实现。 默认值为 true alwaysOnTop boolean (可选) - 窗口是否永远在别的窗口的上面。 默认值为 false fullscreen boolean (可选) - 窗口是否全屏. 当明确设置为 false 时，在 macOS 上全屏的按钮将被隐藏或禁用. 默认值为 false. kiosk boolean (可选) - 窗口是否进入kiosk模式。 默认值为 false. titlestring(可选) - 默认窗口标题 默认为\u0026#34;Electron\u0026#34;。 如果由loadURL()加载的HTML文件中含有标签\u0026lt;title\u0026gt;，此属性将被忽略。 icon (NativeImage | string) (可选) - 窗口图标。 在 Windows 上推荐使用 ICO 图标来获得最佳的视觉效果, 默认使用可执行文件的图标. show boolean (可选) - 窗口是否在创建时显示。 默认值为 true。 frame boolean (可选) - 设置为 false 时可以创建一个无边框窗口 默认值为 true。 parent BrowserWindow (可选) - 指定父窗口 默认值为 null. backgroundColor string (可选) - 窗口背景色，格式为 Hex, RGB, RGBA, HSL, HSLA 或 CSS 命名颜色。 hasShadow boolean (可选) - 窗口是否有阴影. 默认值为 true。 opacity number (可选) macOS Windows - 设置窗口的初始透明度，在 0.0（全透明）和 1.0（完全不透明）之间 优雅的显示窗口 # // 否则new的时候就会显示窗口 let mainWindow = new BrowserWindow({ show: false }) // 页面加载完毕才会显示 mainWindow.once(\u0026#39;ready-to-show\u0026#39;, () =\u0026gt; { mainWindow.show() }) 无边框窗口 # mainWindow = new BrowserWindow({ frame: false }) 让页面可拖拽，这个属性也可以加在其他dom元素上\n\u0026lt;body style=\u0026#34;user-select: none; -webkit-app-region:drag;\u0026#34;\u0026gt; no-drag 修复下面控件的bug\n\u0026lt;input style=\u0026#34;-webkit-app-region: no-drag;\u0026#34; type=\u0026#34;range\u0026#34; name=\u0026#34;range\u0026#34; min=\u0026#34;0\u0026#34; max=\u0026#34;10\u0026#34;\u0026gt; 显示红绿灯\nmainWindow = new BrowserWindow({ titleBarStyle: \u0026#39;hidden\u0026#39; // or hiddenInset 距离红绿灯更近 }) 方法 # loadURL和loadHtml # 设置当前窗口加载的资源\nshow和hide # 显示或隐藏窗口\ngetAllWindows # //静态方法，获取所有窗口 let allWindows = BrowserWindow.getAllWindows() maximize和minimize # 窗口最大化和最小化\n保持窗口状态 # 保持窗口最后的状态（位置、大小）\nnpm install electron-win-state --save const WinState = require(\u0026#39;electron-win-state\u0026#39;).default const winState = new WinState({ defaultWidth: 1200, defaultHeight: 800 }) function creatWindow(){ let win = new BrowserWindow({ ...winState.winOptions, // 注意：使用winState管理就不要指定大小了 // width: 1200, // height: 800, }) // winState管理当前窗口 winState.manage(win) win.loadFile(\u0026#34;index.html\u0026#34;) } webContents # webContents 是 EventEmitter 的实例，负责渲染和控制网页，是 BrowserWindow 对象的一个属性。\nmainWindow.webContents\ngetAllWebContents # 返回 WebContents[] - 所有 WebContents 实例的数组。 包含所有Windows，webviews，opened devtools 和 devtools 扩展背景页的 web 内容\nconst {app, BrowserWindow, webContents} = require(\u0026#39;electron\u0026#39;) console.log(webContents.getAllWebContents()) 事件 # did-finish-load # 资源全部加载完成调用\nlet wc = mainWindow.webContents wc.on(\u0026#39;did-finish-load\u0026#39;, () =\u0026gt; { console.log(\u0026#39;Conent fully loaded\u0026#39;) }) dom-ready # Dom完全加载完成调用\nwc.on(\u0026#39;dom-ready\u0026#39;, () =\u0026gt; { console.log(\u0026#39;DOM Ready\u0026#39;) }) context-menu # 监听右键点击事件\nwc.on(\u0026#39;context-menu\u0026#39;, (e, params) =\u0026gt; { console.log(`Context menu opened on: ${params.mediaType} at x:${params.x}, y:${params.y}`) }) executeJavaScript # 在当前窗口执行js\nwc.on(\u0026#39;context-menu\u0026#39;, (e, params) =\u0026gt; { wc.executeJavaScript(`alert(\u0026#39;${params.selectionText}\u0026#39;)`) }) dialog # 显示用于打开和保存文件、警报等的本机系统对话框\nconst {dialog} = require(\u0026#39;electron\u0026#39;) showOpenDialog # 打开文件的dialog\ndialog.showOpenDialog({ buttonLabel: \u0026#39;选择\u0026#39;, defaultPath: app.getPath(\u0026#39;desktop\u0026#39;), properties: [\u0026#39;multiSelections\u0026#39;, \u0026#39;createDirectory\u0026#39;, \u0026#39;openFile\u0026#39;, \u0026#39;openDirectory\u0026#39;] }).then((result)=\u0026gt; { console.log(result) }) showSaveDialog # 保存文件的dialog\ndialog.showSaveDialog({}).then(result =\u0026gt; { console.log(result.filePath) }) showMessageBox # 显示消息对话窗\n//Yes、No显示的位置会在下面 const answers = [ \u0026#39;an1\u0026#39;,\u0026#39;an2\u0026#39;, \u0026#39;an3\u0026#39;, \u0026#39;Yes\u0026#39;, \u0026#39;No\u0026#39;,] dialog.showMessageBox({ title: \u0026#39;Message Box\u0026#39;, message: \u0026#39;Please select an option\u0026#39;, detail: \u0026#39;Message details.\u0026#39;, buttons: answers }).then(({response}) =\u0026gt; { console.log(`User selected: ${answers[response]}`) }) 快捷键 # 快捷键：定义键盘快捷键。\n系统快捷键：在应用程序没有键盘焦点时，监听键盘事件。\n快捷键可以包含多个功能键和一个键码的字符串，由符号+结合，用来定义你应用中的键盘快捷键\n快捷方式使用 register 方法在 globalShortcut 模块中注册。\nglobalShortcut 模块可以在操作系统中注册/注销全局快捷键, 以便可以为操作定制各种快捷键。\n注意: 快捷方式是全局的; 即使应用程序没有键盘焦点, 它也仍然在持续监听键盘事件。 在应用程序模块发出 ready 事件之前, 不应使用此模块。\nglobalShortcut.register(\u0026#39;CommandOrControl+M\u0026#39;, () =\u0026gt; { win.maximize() }) #特殊功能键 Command (缩写为Cmd) Control (缩写为Ctrl) CommandOrControl (缩写为 CmdOrCtrl) Alt Option AltGr Shift Super Space Tab 大写锁定（Capslock） 数字锁定（Numlock） Menu # 设置系统菜单\nconst {Menu} = require(\u0026#39;electron\u0026#39;) const menu = Menu.buildFromTemplate([ { label: \u0026#39;Electron\u0026#39;, submenu: [ { label: \u0026#39;Item 1\u0026#39;}, { label: \u0026#39;Item 2\u0026#39;, submenu: [ { label: \u0026#39;Sub Item 1\u0026#39;} ]}, { label: \u0026#39;Item 3\u0026#39;}, ] }, { label: \u0026#39;Edit\u0026#39;, submenu: [ { role: \u0026#39;undo\u0026#39;}, { role: \u0026#39;redo\u0026#39;}, { role: \u0026#39;copy\u0026#39;}, { role: \u0026#39;paste\u0026#39;}, ] }, { label: \u0026#39;Actions\u0026#39;, submenu: [ { label: \u0026#39;DevTools\u0026#39;, role: \u0026#39;toggleDevTools\u0026#39; }, { role: \u0026#39;toggleFullScreen\u0026#39; }, { label: \u0026#39;Greet\u0026#39;, click: () =\u0026gt; { console.log(\u0026#39;Hello from Main Menu\u0026#39;) }, accelerator: \u0026#39;Shift+Alt+G\u0026#39; } ] } ]) Menu.setApplicationMenu(menu) 托盘 # const {Tray} = require(\u0026#39;electron\u0026#39;) // 定义托盘右键菜单 let trayMenu = Menu.buildFromTemplate([ { label: \u0026#39;Item 1\u0026#39; }, { label: \u0026#39;退出\u0026#39;, role: \u0026#39;quit\u0026#39; } ]) // 声明全局变量，避免被垃圾回收掉 let tray = null; function createTray() { // 托盘图标 tray = new Tray(\u0026#39;test.jpg\u0026#39;) // 鼠标悬浮托盘描述 tray.setToolTip(\u0026#39;Tray details\u0026#39;) // 托盘的点击事件 tray.on(\u0026#39;click\u0026#39;, e =\u0026gt; { //当按住shift点击 if (e.shiftKey) { app.quit() } else { //窗口显示或隐藏 win.isVisible() ? win.hide() : win.show() } }) tray.setContextMenu(trayMenu) } function creatWindow(){ // 创建窗口时创建托盘 createTray() ... } 渲染进程 # 剪贴板 # 在系统剪贴板上进行复制和粘贴操作。\n在主进程（main process）和渲染进程（renderer process）上均可用。\nconst {clipboard} = require(\u0026#39;electron\u0026#39;) // 返回字符串 - 剪贴板中的内容为纯文本 clipboard.readText() // 将文本写入系统剪贴板 clipboard.writeText(text) desktopCapturer # 访问可用于从桌面捕获音频和视频的媒体源信息。\n注意：目前只能在主进程中调用\nconst {desktopCapturer} = require(\u0026#39;electron\u0026#39;) desktopCapturer.getSources({ types: [\u0026#39;window\u0026#39;, \u0026#39;screen\u0026#39;], thumbnailSize: { width: 1728, height: 1117 } }).then(async sources =\u0026gt; { //sources是一个数组 console.log(sources) return sources for (const source of sources) { // sources是一个数组 console.log(sources) // 获取一个NativeImage对象 let str = sources[0].thumbnail.crop({ x: 0, y: 30, width: 1200, height: 1170 }) return str.toDataURL() } }) NativeImage # 使用 PNG 或 JPG 文件创建托盘、dock和应用程序图标。\n在 Electron 内, 那些需要图片的 API 可以传递两种参数, 一种是文件路径, 一种是 NativeImage 实例对象。 空的图片对象将被 null 参数替代\n// 从剪贴板获取图片 clipboard.readImage() ","date":"2025-04-28","externalUrl":null,"permalink":"/posts/bafd68f1/8e24967e/98b21ca2/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eElectron \n    \u003cdiv id=\"electron\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#electron\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eElectron 基于 Chromium 和 Node.js, 让你可以使用 HTML, CSS 和 JavaScript 构建应用。\u003c/p\u003e","title":"1、入门","type":"posts"},{"content":" 模型上下文协议 MCP # 随着人工智能迈向更复杂的应用场景，单一模型的局限性逐渐显现，而多模型协同与上下文感知的需求日益迫切。从对话系统需要理解用户的历史语境，到跨模态任务要求无缝整合文本、图像等多源数据，AI 的发展正呼唤一种全新的协作范式。\n模型上下文协议（Model Context Protocol, MCP）正是在这一背景下崭露头角。作为一种专为模型间上下文传递设计的标准化协议，MCP 不仅提升了系统的连贯性和智能化水平，还为开发者提供了一个灵活、高效的工具来应对日益增长的计算挑战。\nMCP 协议由 Anthropic 于 2024 年 11 月正式推出并开源。作为一家由前 OpenAI 研究人员创立的公司，Anthropic 以其在可解释性和安全 AI 系统方面的专长而闻名。\nMCP 的设计初衷是创建一个开放协议，标准化 AI 模型与外部数据源及工具的交互方式，从而解决传统集成的碎片化问题。\nMCP 的灵感部分来源于 USB-C 的类比：如同 USB-C 通过统一接口连接多种设备，MCP 旨在为 AI 应用提供一个“即插即用”的上下文管理框架。\nMCP 架构 # 模型上下文协议（Model Context Protocol, MCP）的核心设计遵循客户端-服务器架构，这一架构允许一个宿主应用程序与多个服务器建立连接，从而实现灵活的上下文传递与功能扩展。\n通常而言，MCP 的技术框架围绕三个关键组件构建：主机（Host）、客户端（Client）和服务器（Server）。这些组件共同协作，形成了一个高效、可扩展的生态系统，为 AI 模型与外部资源之间的动态交互提供了坚实的基础。\n简单来说，用户在 MCP Host 上发出指令，Host 通过 MCP Client 和 MCP Server 沟通，最终实现功能并返回结果给用户。\nMCP Host（宿主/主机）：用户操作 MCP 服务的平台，比如 AI 聊天工具或专业 IDE。 管理客户端：生成并管理多个客户端（会话），控制安全性和权限。 协调 AI：决定 AI（LLM）使用哪些数据或功能，并向服务器发送请求。 整合信息：汇总各客户端从服务器获取的数据，供 AI 优化使用。 MCP Client（客户端）：连接 Host 与 Server，负责数据交互与通信。 专用通信：一个客户端只与一个服务器交互，例如“数据库客户端”或“翻译客户端”。 JSON-RPC 交互：使用 initialize、call_tool、read_resource 等方法与服务器通信。 事件处理：接收服务器的日志通知等，并反馈给主机。 实现要点 客户端实现较简单，主要包括 ClientSession（协议层）和传输模块（如 stdio_client、sse_client、websocket_client）。 ClientSession 负责生成和发送 JSON-RPC 消息，处理核心操作（如 initialize、tools/call）。 传输层使用 stdio 或 SSE 交换 JSON 消息。 MCP Server（服务端）：实际提供功能和数据支持的程序，比如访问文件、调用数据库、调用API。 提供工具和资源：公开功能（如翻译、数据库查询、文件读写）供客户端调用。 隔离操作：为安全起见，仅处理自身权限内的资源，不直接访问主机或其他服务器数据。 可选 AI 交互：通过客户端向主机的 LLM 请求额外处理（例如，翻译服务器请求其他语言处理）。 实现要点 提供低级 Server 类和高级 FastMCP框架。 Server使用装饰器（如 @server.call_tool、@server.list_tools）定义请求处理逻辑，管理“哪个方法调用哪个函数”。 ServerSession 为每个客户端连接生成会话，处理 JSON-RPC 消息并调用注册的处理函数。 FastMCP 自动配置 SSE 路由和工具管理，简化开发。 传输层与客户端类似，使用 stdio 或 SSE。 设计原则 # MCP 的设计遵循以下原则：\n服务器简洁：服务器专注单一功能，复杂协调由主机负责。 高组合性：通过通用协议，服务器如积木般可自由组合，扩展 AI 功能。 权限隔离：服务器仅操作自身权限内的资源，主机负责全局管理。 渐进扩展：核心功能保持最小化，通过“功能协商”（capability negotiation）逐步添加扩展，兼顾后向兼容性。 ","date":"2025-04-28","externalUrl":null,"permalink":"/posts/ffe909ab/2fd62ad9/9717c9f3/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e模型上下文协议 MCP \n    \u003cdiv id=\"模型上下文协议-mcp\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%a8%a1%e5%9e%8b%e4%b8%8a%e4%b8%8b%e6%96%87%e5%8d%8f%e8%ae%ae-mcp\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e随着人工智能迈向更复杂的应用场景，单一模型的局限性逐渐显现，而多模型协同与上下文感知的需求日益迫切。从对话系统需要理解用户的历史语境，到跨模态任务要求无缝整合文本、图像等多源数据，AI 的发展正呼唤一种全新的协作范式。\u003c/p\u003e","title":"1、模型上下文协议MCP","type":"posts"},{"content":" EventEmitter # 在 Node.js 中，事件驱动编程主要通过 EventEmitter 类来实现。\nEventEmitter 是一个内置类，位于 events 模块中，通过继承 EventEmitter，你可以创建自己的事件发射器，并注册和触发事件。\n通过这种机制，Node.js 可以高效地处理异步任务，即使在单线程的环境下也能实现并发处理。\nNode.js 里面的许多对象都会分发事件：一个 net.Server 对象会在每次有新连接时触发一个事件， 一个 fs.readStream 对象会在文件被打开的时候触发一个事件。 所有这些产生事件的对象都是 events.EventEmitter 的实例。\n// 引入 events 模块 var events = require(\u0026#39;events\u0026#39;); // 创建 eventEmitter 事件对象 var eventEmitter = new events.EventEmitter(); // 注册事件 eventEmitter.on(\u0026#39;my_event\u0026#39;, function() { console.log(\u0026#39;my_event 事件触发\u0026#39;); }); // 触发事件 eventEmitter.emit(\u0026#39;my_event\u0026#39;) 继承 EventEmitter # 大多数时候我们不会直接使用 EventEmitter，而是在对象中继承它，包括 fs、net、http 在内的，只要是支持事件响应的核心模块都是 EventEmitter 的子类。\nclass MyEmitter extends EventEmitter {} const customEmitter = new MyEmitter(); customEmitter.on(\u0026#39;customEvent\u0026#39;, () =\u0026gt; { console.log(\u0026#39;Custom event fired\u0026#39;); }); customEmitter.emit(\u0026#39;customEvent\u0026#39;); 常用方法 # 方法 描述 addListener(event, listener) 为指定事件添加一个监听器到监听器数组的尾部。 on(event, listener) 为指定事件注册一个监听器，接受一个字符串 event 和一个回调函数。server.on('connection', function (stream) { console.log('someone connected!'); }); once(event, listener) 为指定事件注册一个单次监听器，即 监听器最多只会触发一次，触发后立刻解除该监听器。server.once('connection', function (stream) { console.log('Ah, we have our first user!'); }); removeListener(event, listener) 移除指定事件的某个监听器，监听器必须是该事件已经注册过的监听器。它接受两个参数，第一个是事件名称，第二个是回调函数名称。var callback = function(stream) { console.log('someone connected!'); }; server.on('connection', callback); server.removeListener('connection', callback); removeAllListeners([event]) 移除所有事件的所有监听器， 如果指定事件，则移除指定事件的所有监听器。 setMaxListeners(n) 默认情况下， EventEmitters 如果你添加的监听器超过 10 个就会输出警告信息。 setMaxListeners 函数用于改变监听器的默认限制的数量。 listeners(event) 返回指定事件的监听器数组。 emit(event, [arg1], [arg2], [...]) 按监听器的顺序执行执行每个监听器，如果事件有注册监听返回 true，否则返回 false。 error 事件 # EventEmitter 定义了一个特殊的事件 error，它包含了错误的语义，我们在遇到异常的时候通常会触发 error 事件。\n当 error 被触发时，EventEmitter 规定如果没有响应的监听器，Node.js 会把它当作异常，退出程序并输出错误信息。\n我们一般要为会触发 error 事件的对象设置监听器，避免遇到错误后整个程序崩溃。\nconst EventEmitter = require(\u0026#34;events\u0026#34;); class MyEmitter extends EventEmitter{} class MyError extends Error{ constructor(message){ super(message) Error.captureStackTrace(this,this.constructor) } } var myEmitter = new MyEmitter() myEmitter.on(\u0026#39;error\u0026#39;,(e) =\u0026gt; { console.log(e) }) myEmitter.emit(\u0026#39;error\u0026#39;,new MyError(\u0026#34;错误啦\u0026#34;)) console.log(\u0026#34;程序还在执行。。。\u0026#34;) // MyError: 错误啦 // at Object.\u0026lt;anonymous\u0026gt; (/Users/yanggang/develop/nodejs-project/nodejs-demo/index.js:18:24) // at Module._compile (node:internal/modules/cjs/loader:1198:14) // at Object.Module._extensions..js (node:internal/modules/cjs/loader:1252:10) // at Module.load (node:internal/modules/cjs/loader:1076:32) // at Function.Module._load (node:internal/modules/cjs/loader:911:12) // at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12) // at node:internal/main/run_main_module:22:47 // 程序还在执行 ","date":"2025-04-27","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/65df2b3b/0eb26b3b/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eEventEmitter \n    \u003cdiv id=\"eventemitter\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#eventemitter\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在 Node.js 中，事件驱动编程主要通过 EventEmitter 类来实现。\u003c/p\u003e","title":"1、EventEmitter","type":"posts"},{"content":" 什么是NodeJS # 简单的说 Node.js 就是运行在服务端的 JavaScript。\nNode.js 是一个基于Chrome JavaScript 运行时建立的一个平台。\nNode.js是一个事件驱动I/O服务端JavaScript环境，基于Google的V8引擎，V8引擎执行Javascript的速度非常快，性能非常好。\n安装 # 官网下载：https://nodejs.org/en/download/\n注意：Linux 上安装 Node.js 需要安装 Python 2.6 或 2.7 ，不建议安装 Python 3.0 以上版本\nwindows安装，下载msi，双击打开，选择安装目录，一路下一步即可\n测试 # 一般安装完node，会自动配置环境变量，如果没有，就将node的根目录配置到path即可\n1、在桌面创建一个test.js的文件，编写如下代码\nconsole.log(\u0026#34;Hello World!\u0026#34;); 2、在桌面打开命令行窗口，执行命令node test.js\nREPL # Node.js REPL(Read Eval Print Loop:交互式解释器) 表示一个电脑的环境，类似 Window 系统的终端或 Unix/Linux shell，我们可以在终端中输入命令，并接收系统的响应。\nNode 自带了交互式解释器，可以执行以下任务：\n读取 - 读取用户输入，解析输入的 Javascript 数据结构并存储在内存中 执行 - 执行输入的数据结构 打印 - 输出结果 循环 - 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出 通过命令：node进入终端\nvscode编写nodejs # 需要在项目的根目录执行命令，安装代码提示插件\nnpm install --save-dev @types/node ","date":"2025-04-27","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/79502aa8/cf4953e8/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是NodeJS \n    \u003cdiv id=\"什么是nodejs\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%afnodejs\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e简单的说 Node.js 就是运行在服务端的 JavaScript。\u003c/p\u003e","title":"1、NodeJS","type":"posts"},{"content":" try-catch-finally # try 语句允许我们定义在执行时进行错误测试的代码块。\ncatch 语句允许我们定义当 try 代码块发生错误时，所执行的代码块。\nJavaScript 语句 try 和 catch 是成对出现的。\ntry { ... //异常的抛出 } catch(e) { ... //异常的捕获与处理 } finally { ... //结束处理 } throw # throw 可以创建并抛出异常。\n异常可以是 JavaScript 字符串、数字、逻辑值或对象。\nfunction myFunction() { var message, x; message = document.getElementById(\u0026#34;message\u0026#34;); message.innerHTML = \u0026#34;\u0026#34;; x = document.getElementById(\u0026#34;demo\u0026#34;).value; try { if(x == \u0026#34;\u0026#34;) throw \u0026#34;值为空\u0026#34;; if(isNaN(x)) throw \u0026#34;不是数字\u0026#34;; x = Number(x); if(x \u0026lt; 5) throw \u0026#34;太小\u0026#34;; if(x \u0026gt; 10) throw \u0026#34;太大\u0026#34;; } catch(err) { message.innerHTML = \u0026#34;错误: \u0026#34; + err; } } 自定义异常 # 虽然我们可以 throw 各种对象作为异常，但是还是推荐抛出自定义异常类，这样信息更全。\n在 Node.js 中，自定义异常是一种常见的做法，它可以帮助你更好地控制错误处理和提供更具体的错误信息。自定义异常可以通过继承 JavaScript 的内置 Error 对象来实现。这样做的好处是你可以添加额外的属性或方法，以便在捕获异常时提供更多上下文信息。\n继承 Error 类：创建一个新的类，并让它继承自 Error 类。 添加构造函数：在构造函数中调用 super() 来调用父类的构造函数，并传递一个描述性的错误消息。 添加额外的属性：你可以添加任何需要的属性来存储额外的信息，比如错误代码、状态码等。 class UserNotFoundException extends Error { constructor(message = \u0026#34;用户未找到\u0026#34;) { super(message); // 调用父类的构造函数 this.name = \u0026#34;UserNotFoundException\u0026#34;; // 错误名称 this.statusCode = 404; // HTTP 状态码 // 确保错误堆栈是正确的，一般不需要写 Error.captureStackTrace(this, this.constructor); } } // 使用示例 try { throw new UserNotFoundException(\u0026#34;指定的用户ID不存在\u0026#34;); } catch (error) { if (error instanceof UserNotFoundException) { console.error(`错误名称: ${error.name}, 状态码: ${error.statusCode}, 消息: ${error.message}`); } else { console.error(\u0026#34;未知错误:\u0026#34;, error); } } ","date":"2025-04-27","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/0d08345a/47e69f85/1ec11caf/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003etry-catch-finally \n    \u003cdiv id=\"try-catch-finally\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#try-catch-finally\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003etry\u003c/strong\u003e 语句允许我们定义在执行时进行错误测试的代码块。\u003c/p\u003e","title":"1、异常处理","type":"posts"},{"content":" 介绍 # JavaScript是世界上最流行的脚本语言，因为你在电脑、手机、平板上浏览的所有的网页，以及无数基于HTML5的手机App，交互逻辑都是由JavaScript驱动的\nJavaScript历史 # 要了解JavaScript，我们首先要回顾一下JavaScript的诞生。\n在上个世纪的1995年，当时的网景公司正凭借其Navigator浏览器成为Web时代开启时最著名的第一代互联网公司。\n由于网景公司希望能在静态HTML页面上添加一些动态效果，于是叫Brendan Eich这哥们在两周之内设计出了JavaScript语言。\n为什么起名叫JavaScript？原因是当时Java语言非常红火，所以网景公司希望借Java的名气来推广，但事实上JavaScript除了语法上有点像Java，其他部分基本上没啥关系。\nECMAScript # 因为网景开发了JavaScript，一年后微软又模仿JavaScript开发了JScript，为了让JavaScript成为全球标准，几个公司联合ECMA（European Computer Manufacturers Association）组织定制了JavaScript语言的标准，被称为ECMAScript标准。\n所以简单说来就是，ECMAScript是一种语言标准，而JavaScript是网景公司对ECMAScript标准的一种实现。\n那为什么不直接把JavaScript定为标准呢？因为JavaScript是网景的注册商标。\n不过大多数时候，我们还是用JavaScript这个词。如果你遇到ECMAScript这个词，简单把它替换为JavaScript就行了。\nJavaScript版本 # 由于 JavaScript的标准——ECMAScript在不断发展，最新版ECMAScript 6标准（简称ES6）已经在2015年6月正式发布了，所以，讲到JavaScript的版本，实际上就是说它实现了ECMAScript标准的哪个版本。\n由于浏览器在发布时就确定了JavaScript的版本，加上很多用户还在使用IE6（已经die了）这种古老的浏览器，这就导致你在写JavaScript的时候，要照顾一下老用户，不能一上来就用最新的ES6标准写，否则，老用户的浏览器是无法运行新版本的JavaScript代码的。\n基本语法 # JavaScript的语法和Java语言类似，每个语句以;结束，语句块用{...}。但是，JavaScript并不强制要求在每个语句的结尾加;，浏览器中负责执行JavaScript代码的引擎会自动在每个语句的结尾补上;。\n注意：在有些情况下，让引擎自动添加;，会改变程序语义，所以我们还是自己加;\nvar msg = \u0026#39;Hello World\u0026#39;; console.log(msg); 注释 # //声明了一个变量msg var msg = \u0026#39;Hello World\u0026#39;; /* * 在控制台打印 * Hello World */ console.log(msg); ","date":"2025-04-25","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/0d08345a/1403736b/83c4d2fd/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e介绍 \n    \u003cdiv id=\"介绍\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%8b%e7%bb%8d\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eJavaScript是世界上最流行的脚本语言，因为你在电脑、手机、平板上浏览的所有的网页，以及无数基于HTML5的手机App，交互逻辑都是由JavaScript驱动的\u003c/p\u003e","title":"1、JavaScript","type":"posts"},{"content":" conda分为三类 # conda：是一种通用包管理系统，旨在构建和管理任何语言和任何类型的软件。举个例子：包管理与pip的使用类似，环境管理则允许用户方便地安装不同版本的python并可以快速切换。 Anaconda：则是一个打包的集合，里面预装好了conda、某个版本的python、众多packages、科学计算工具等等，就是把很多常用的不常用的库都给你装好了。 Miniconda：，顾名思义，它只包含最基本的内容——python与conda，以及相关的必须依赖项，对于空间要求严格的用户，Miniconda是一种选择。就只包含最基本的东西，其他的库得自己装 为什么使用Miniconda # 安装python便捷。无论是在win还是linux环境下，miniconda都是傻瓜式的安装方式，一路next即可，而不需要编译源码。 方便的python库管理。conda在安装python包时会自动处理依赖包，只需要点确定就可以了。尤其是在安装opencv，TensorFlow等依赖较为复杂的包的时候，使用pip会需要手动处理很多依赖项，编译很多软件包，而conda不需要。conda甚至可以修改当前python的版本。 方便的python虚拟环境管理。conda命令可以方便的创建和删除python的虚拟环境。否则需要自己安装virtualenv等python包。 Anaconda作为一个python发行版携带了很多科学计算的python包，但它太大了，有些包也不需要。而miniconda就小得多，只携带了conda所必须的依赖包。 安装 # 下载地址：https://www.anaconda.com/download/success\nwindows # 安装比较简单，没什么说的\n安装完成后需要配置环境变量到系统PATH\n安装路径\\Miniconda3 安装路径\\Miniconda3\\Scripts 安装路径\\Miniconda3\\Library\\bin 输入命令conda -V验证即可\n配置镜像 # 参考：https://mirrors.tuna.tsinghua.edu.cn/help/anaconda/\n各系统都可以通过修改用户目录下的 .condarc 文件来使用 TUNA 镜像源。Windows 用户无法直接创建名为 .condarc 的文件，可先执行 conda config --set show_channel_urls yes 生成该文件之后再修改。\nchannels: - defaults show_channel_urls: true default_channels: - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2 custom_channels: conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud msys2: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud bioconda: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud menpo: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud pytorch-lts: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud simpleitk: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud 运行 conda clean -i 清除索引缓存，保证用的是镜像站提供的索引。\n查看镜像：conda config --show-sources。\n也可以通过命令添加镜像：conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/\n常用命令 # #查看conda版本，验证是否安装 conda --version #更新至最新版本，也会更新其它相关包 conda update conda #更新所有包 conda update --all #更新指定的包 conda update package_name #创建名为env_name的新环境,并指定python环境为3.8 conda create -n env_name package_name python=3.8 #切换至env_name环境 conda activate env_name #退出环境 conda deactivate #显示所有已经创建的环境 conda info -e conda env list #复制old_env_name为new_env_name conda create --name new_env_name --clone old_env_name #删除环境 conda remove --name env_name –all #查看所有已经安装的包 conda list #在当前环境中安装包 conda install package_name #在指定环境中安装包 conda install --name env_name package_name #删除指定环境中的包 conda remove --name env_name package #删除当前环境中的包 conda remove package #采用conda remove --name 删除环境失败时，可采用这种方法 conda env remove -n env_name 导出导入conda环境 # 导出 # conda env export --no-builds --from-history \u0026gt; environment.yaml 这个命令会将环境配置导出为一个 YAML 文件，你可以使用这个文件来在其他地方或相同机器上重新创建该环境。\n--no-builds：不生成 build 编号，防止不同系统冲突\n--from-history：导出时只会生成自己手动安装的包，不包含依赖的包，防止不同系统的依赖冲突\n导入 # conda env create -f environment.yaml ","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/b730e80e/8bb60b7b/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003econda分为三类 \n    \u003cdiv id=\"conda分为三类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#conda%e5%88%86%e4%b8%ba%e4%b8%89%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003econda：是一种通用包管理系统，旨在构建和管理任何语言和任何类型的软件。举个例子：包管理与pip的使用类似，环境管理则允许用户方便地安装不同版本的python并可以快速切换。\u003c/li\u003e\n\u003cli\u003eAnaconda：则是一个打包的集合，里面预装好了conda、某个版本的python、众多packages、科学计算工具等等，就是把很多常用的不常用的库都给你装好了。\u003c/li\u003e\n\u003cli\u003eMiniconda：，顾名思义，它只包含最基本的内容——python与conda，以及相关的必须依赖项，对于空间要求严格的用户，Miniconda是一种选择。就只包含最基本的东西，其他的库得自己装\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003ch2 class=\"relative group\"\u003e为什么使用Miniconda \n    \u003cdiv id=\"为什么使用miniconda\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bd%bf%e7%94%a8miniconda\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e安装python便捷。无论是在win还是linux环境下，miniconda都是傻瓜式的安装方式，一路next即可，而不需要编译源码。\u003c/li\u003e\n\u003cli\u003e方便的python库管理。conda在安装python包时会自动处理依赖包，只需要点确定就可以了。尤其是在安装opencv，TensorFlow等依赖较为复杂的包的时候，使用pip会需要手动处理很多依赖项，编译很多软件包，而conda不需要。conda甚至可以修改当前python的版本。\u003c/li\u003e\n\u003cli\u003e方便的python虚拟环境管理。conda命令可以方便的创建和删除python的虚拟环境。否则需要自己安装virtualenv等python包。\u003c/li\u003e\n\u003cli\u003eAnaconda作为一个python发行版携带了很多科学计算的python包，但它太大了，有些包也不需要。而miniconda就小得多，只携带了conda所必须的依赖包。\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003ch2 class=\"relative group\"\u003e安装 \n    \u003cdiv id=\"安装\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%89%e8%a3%85\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e下载地址：https://www.anaconda.com/download/success\u003c/p\u003e","title":"1、conda","type":"posts"},{"content":" datetime # datetime是Python处理日期和时间的标准库\ndatetime模块为日期和时间处理同时提供了简单和复杂的方法。\n支持日期和时间算法的同时，实现的重点放在更有效的处理和格式化输出\ndatetime模块定义了以下几个类：\n类名称 描述 datetime.date 表示日期，常用的属性有：year, month和day datetime.time 表示时间，常用属性有：hour, minute, second, microsecond datetime.datetime 表示日期时间 datetime.timedelta 表示两个date、time、datetime实例之间的时间间隔，分辨率（最小单位）可达到微秒，主要用于做时间加减 datetime.tzinfo 时区相关信息对象的抽象基类。它们由datetime和time类使用，以提供自定义时间的而调整 datetime.timezone Python 3.2中新增的功能，实现tzinfo抽象基类的类，表示与UTC的固定偏移量 datetime类 # 静态属性和方法 # \u0026#34;属性\u0026#34; datetime.min #datetime类所能表示的最小时间。 datetime.max #datetime类所能表示的最大时间。 datetime.resolution #datetime类表示时间的最小单位，这里是1微秒； \u0026#34;方法\u0026#34; datetime.today() # 返回一个表示当前本地时间的datetime对象； datetime.now() # 返回一个表示当前本地时间的datetime对象，如果提供了参数tz，则获取tz参数所指时区的本地时间； datetime.utcnow() # 返回一个当前utc时间的datetime对象；#格林威治时间 datetime.fromtimestamp(timestamp) # 根据时间戮创建一个datetime对象，参数tz指定时区信息； datetime.utcfromtimestamp(timestamp) # 根据时间戮创建一个datetime对象； datetime.combine(date,time) # 根据date对象和time对象，创建一个datetime对象； datetime.strptime(date_string,format) # 将格式字符串转换为datetime对象； 实例属性和方法 # dt = datetime() \u0026#34;属性\u0026#34; dt.year、dt.month、dt.day # 获取年、月、日； dt.hour、dt.minute、dt.second、dt.microsecond # 获取时、分、秒、微秒； \u0026#34;方法\u0026#34; dt.date() # 获取date对象； dt.time() # 获取time对象； dt.replace() # 传入指定的year或month或day或hour或minute或second或microsecond，生成一个新日期datetime对象，但不改变原有的datetime对象； dt.timetuple() # 返回时间元组struct_time格式的日期； dt.utctimetuple() # 返回时间元组struct_time格式的日期； # 这个没什么用 dt.toordinal() # 返回1年1月1日开始至今的天数； # 了解就行，用处不大 dt.weekday() # 返回weekday，如果是星期一，返回0；如果是星期2，返回1，以此类推； dt.isoweekday() # 返回weekday，如果是星期一，返回1；如果是星期2，返回2，以此类推； dt.isocalendar() # 返回(year,week,weekday)格式的元组； dt.isoformat() # 返回固定格式如\u0026#39;YYYY-MM-DD HH:MM:SS’的字符串； dt.ctime() # 返回一个日期时间的C格式字符串，等效于time.ctime(time.mktime(dt.timetuple()))； # 了解就行，用处不大 dt.strftime(format) # 传入任意格式符，可以输出任意格式的日期表示形式。 dt.timestamp() # 返回该时间的timestamp（秒） 使用 # strftime() # 用于将时间转换为固定格式的字符串\nfrom datetime import datetime # 获取当前时间 dt = datetime.now() # 转换为指定格式字符串 str1 = dt.strftime(\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;) str2 = dt.strftime(\u0026#39;%y-%m-%d %H:%M:%S\u0026#39;) print(str1) print(str2) \u0026#39;\u0026#39;\u0026#39; 2022-02-18 09:55:14 22-02-18 09:55:14 \u0026#39;\u0026#39;\u0026#39; strptime() # 用于将时间字符串，按照格式转换为datetime实例\nfrom datetime import datetime dt = datetime.strptime(\u0026#39;2011,2,3,4,5,6\u0026#39;,\u0026#39;%Y,%m,%d,%H,%M,%S\u0026#39;) print(dt) \u0026#39;\u0026#39;\u0026#39; 2011-02-03 04:05:06 \u0026#39;\u0026#39;\u0026#39; datetime() # datetime的__new__方法，用于生成指定时间的datetime实例\nfrom datetime import datetime dt = datetime(2011,2,3,4,5,6) print(dt) \u0026#39;\u0026#39;\u0026#39; 2011-02-03 04:05:06 \u0026#39;\u0026#39;\u0026#39; timestamp() # 在计算机中，时间实际上是用数字表示的。我们把1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time，记为0（1970年以前的时间timestamp为负数），当前时间就是相对于epoch time的秒数，称为timestamp（时间戳）。在java等语言中，timestamp取的是整数毫秒数，在python中，timstamp是浮点型秒数\ndatetime to timestamp # from datetime import datetime dt = datetime(2021,2,3,4,5,6) ts = dt.timestamp() print(ts) print(type(ts)) \u0026#39;\u0026#39;\u0026#39; 1612296306.0 \u0026lt;class \u0026#39;float\u0026#39;\u0026gt; \u0026#39;\u0026#39;\u0026#39; timestamp to datetime # from datetime import datetime dt = datetime.fromtimestamp(1612296306.0) print(dt) \u0026#39;\u0026#39;\u0026#39; 2021-02-03 04:05:06 \u0026#39;\u0026#39;\u0026#39; date类 # 静态属性和方法 # \u0026#34;属性\u0026#34; max # date对象能够表示的最大时间； min # date对象能够表示的最小时间； resolution # date对象表示时间的最小单位，这里指的是天； \u0026#34;方法\u0026#34; today() # 返回本地时间的一个date对象； fromtimestamp(timestamp) # 给定一个时间戳，返回一个date对象；这个函数很有用 实例属性和方法 # d = date() \u0026#34;属性\u0026#34; d.year、d.month、d.day # 年、月、日； \u0026#34;方法\u0026#34; d.replace(year=,month=,day=) # 生成一个新的日期对象，用参数指定的年，月，日代替原有对象中的属性。(原有对象仍保持不变) d.timetuple() # 返回时间元组struct_time格式的日期； d.weekday() # 返回weekday，如果是星期一，返回0；如果是星期2，返回1，以此类推； d.isoweekday() # 返回weekday，如果是星期一，返回1；如果是星期2，返回2，以此类推； d.isocalendar() # 返回(year,week,weekday)格式的元组； d.isoformat() # 返回格式如\u0026#39;YYYY-MM-DD\u0026#39;的字符串； d.strftime(format) # 传入任意格式符，可以输出任意格式的日期表示形式； time类 # 静态属性和方法 # time.min # time类所能表示的最小时间。 time.max # time类所能表示的最大时间。 time.resolution # time类表示时间的最小单位，这里是1微秒； 实例属性和方法 # t = time() \u0026#34;属性\u0026#34; t.hour、t.minute、t.second、t.microsecond # 时、分、秒、微秒； \u0026#34;方法\u0026#34; t.replace(hour=,minute=,second=,microsecond=) # 生成一个新的时间对象，用参数指定的时、分、秒、微秒代替原有对象中的属性。(原有对象仍保持不变) t.isoformat() # 返回型如\u0026#34;HH:MM:SS\u0026#34;格式的字符串时间表示； t.strftime(format) # 传入任意格式符，可以输出任意格式的时间表示形式； timedelta类 # 使用timedelta可以很方便的在日期上做天days，小时hour，分钟minute，秒second，毫秒millisecond，微妙的时间计算microsecond。如果要进行年、月的加减，则需要另外的办法。\n但是这个类的使用，一定要结合date类的对象 或 datetime类的对象使用。也就是说，一定是基于这两个类的对象，进行时间的加、减。\n注意：timedelta不能单独和time类的对象结合使用\n和date类结合 # date类主要是用于处理年、月、日的，因此对该对象进行时间的加、减，主要是做“日(天数)”的加减。\nfrom datetime import date,timedelta d = date(2021,2,3) print(\u0026#39;今天：%s\u0026#39; % d) yest = d + timedelta(days = -1) print(\u0026#39;昨天：%s\u0026#39; % yest) tomo = d + timedelta(days = 1) print(\u0026#39;明天：%s\u0026#39; % tomo) \u0026#39;\u0026#39;\u0026#39; 今天：2021-02-03 昨天：2021-02-02 明天：2021-02-04 \u0026#39;\u0026#39;\u0026#39; 和datetime类结合 # datetime类主要是用于处理年、月、日、时、分、秒、毫秒、微妙的，因此对该对象进行时间的加、减，主要做“日(天数)”、“时”、“分”、“秒”、“毫秒”、“微秒”、的加减。\nfrom datetime import datetime,timedelta dt = datetime(2022,2,2,2,2,2) print(\u0026#39;现在：%s\u0026#39; % dt) dt1 = dt + timedelta(hours = 1) print(\u0026#39;一小时后：%s\u0026#39; % dt1) dt2 = dt + timedelta(hours = -1) print(\u0026#39;一小时前：%s\u0026#39; % dt2) dt3 = dt + timedelta(seconds = 1) print(\u0026#39;一秒钟后：%s\u0026#39; % dt3) dt4 = dt + timedelta(seconds = -1) print(\u0026#39;一秒钟前：%s\u0026#39; % dt4) dt5 = dt + timedelta(minutes = 1) print(\u0026#39;一分钟后：%s\u0026#39; % dt5) dt6 = dt + timedelta(minutes = -1) print(\u0026#39;一分钟前：%s\u0026#39; % dt6) dt7 = dt + timedelta(days = 1) print(\u0026#39;一天后：%s\u0026#39; % dt7) dt8 = dt + timedelta(minutes = -1) print(\u0026#39;一天前：%s\u0026#39; % dt8) \u0026#39;\u0026#39;\u0026#39; 现在：2022-02-02 02:02:02 一小时后：2022-02-02 03:02:02 一小时前：2022-02-02 01:02:02 一秒钟后：2022-02-02 02:02:03 一秒钟前：2022-02-02 02:02:01 一分钟后：2022-02-02 02:03:02 一分钟前：2022-02-02 02:01:02 一天后：2022-02-03 02:02:02 一天前：2022-02-02 02:01:02 \u0026#39;\u0026#39;\u0026#39; 计算两个日期相隔天数 # date提供方法__sub__和__rsub__两个方法，计算天数，返回的是timedelta实例\nfrom datetime import date d1 = date(2021,2,3) d2 = date(2022,3,5) #sub计算的是d2 - d1的天数 delta1 = d2.__sub__(d1) print(\u0026#39;%s和%s相隔%s天\u0026#39; % (d2,d1,delta1.days)) #rsub计算的是d1 - d2的天数 delta2 = d2.__rsub__(d1) print(\u0026#39;%s和%s相隔%s天\u0026#39; % (d2,d1,delta2.days)) \u0026#39;\u0026#39;\u0026#39; 2022-03-05和2021-02-03相隔395天 2022-03-05和2021-02-03相隔-395天 \u0026#39;\u0026#39;\u0026#39; ","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/9766939c/93fd4982/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003edatetime \n    \u003cdiv id=\"datetime\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#datetime\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003edatetime是Python处理日期和时间的标准库\u003c/p\u003e","title":"1、datetime","type":"posts"},{"content":" pyinstaller # 在创建了独立应用（自包含该应用的依赖包）之后，还可以使用 PyInstaller 将 Python程序生成可直接运行的程序，这个程序就可以被分发到对应的 Windows、Linux或 Mac OS 平台上运行。\nPython 默认并不包含 PyInstaller 模块，因此需要自行安装 PyInstaller 模块。\npip install pyinstaller 注意：建议使用 pip 在线安装的方式来安装 PyInstaller 模块，不要使用离线包的方式来安装，因为 PyInstaller 模块还依赖其他模块，pip 在安装 PyInstaller 模块时会先安装它的依赖模块。\n打包 # 在项目目录下，使用PyInstaller命令来打包您的Python脚本。假设您的主程序文件名为main.py，则可以使用以下命令\npyinstaller --onefile main.py 这里的--onefile选项指示PyInstaller生成一个单独的可执行文件，而不是一个包含多个文件的文件夹。PyInstaller还支持许多其他选项，如--icon来指定应用程序的图标，--windowed或--noconsole来避免在Windows上打开命令行窗口等。\nPyInstaller完成打包后，会在dist目录下生成可执行文件（或文件夹，如果您没有使用--onefile选项）。进入dist目录，您应该能看到一个名为main（或您指定的名称，如果使用了--name选项）的可执行文件。\nOptions # 参数名 描述 示例 类型 必须 默认值 --onefile 将所有依赖文件打包为单个可执行文件。 pyinstaller --onefile example.py bool 否 False --noconsole 隐藏控制台窗口（适用于 GUI 应用）。 pyinstaller --noconsole gui_app.py bool 否 False --name 指定生成的可执行文件名称。 pyinstaller --name=my_app example.py str 否 源文件名（无扩展） --icon 设置可执行文件的图标（需提供 .ico 文件）。 pyinstaller --icon=app_icon.ico example.py str 否 None --add-data 捆绑额外文件（如配置文件、静态资源等）。 pyinstaller --add-data \u0026quot;config.yaml;.\u0026quot; example.py str 否 None --clean 清理之前的构建缓存，强制重新打包。 pyinstaller --clean example.py bool 否 False --hidden-import 手动添加隐藏的依赖模块（通常是自动检测遗漏的依赖）。 pyinstaller --hidden-import=requests example.py list 否 自动检测 --windowed GUI 应用打包 pyinstaller --windowed example.py bool 否 False 捆绑额外文件 # 例如将配置文件 config.yaml 一起捆绑进可执行文件\npyinstaller --onefile --add-data \u0026#34;config.yaml;.\u0026#34; example_script.py 格式为：\u0026quot;源路径;目标路径\u0026quot;，目标路径使用.表示与exe同级目录。\n注意：在 Windows 系统中，路径分隔符用分号 ;，而在 macOS/Linux 中使用冒号 :。\n打包后资源文件会被解压到临时目录，直接使用原始路径将导致错误！需通过以下方法获取正确路径\nimport sys import os def resource_path(relative_path): \u0026#34;\u0026#34;\u0026#34; 获取资源的绝对路径 \u0026#34;\u0026#34;\u0026#34; if hasattr(sys, \u0026#39;_MEIPASS\u0026#39;): base_path = sys._MEIPASS else: base_path = os.path.abspath(\u0026#34;.\u0026#34;) return os.path.join(base_path, relative_path) # 示例：加载图片 img_path = resource_path(\u0026#34;1.jpg\u0026#34;) image = pygame.image.load(img_path) # 示例：加载字体 font_path = resource_path(\u0026#34;HarmonyOS_Sans_SC_Black.ttf\u0026#34;) font = pygame.font.Font(font_path, 24) spec # 打包过程中，PyInstaller 会生成一个 .spec 文件。这个文件包含了 PyInstaller 的配置信息，其中包含了构建过程的所有配置信息。你可以修改这个文件来定制打包过程。\n# main.spec # -*- mode: python ; coding: utf-8 -*- block_cipher = None a = Analysis([\u0026#39;main.py\u0026#39;], pathex=[\u0026#39;/path/to/your/script\u0026#39;], # 项目的路径 binaries=[], datas=[(\u0026#39;data/*.txt\u0026#39;, \u0026#39;data\u0026#39;)], # 资源文件 hiddenimports=[\u0026#39;some_module\u0026#39;], # 隐藏导入模块 hookspath=[], runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False) pyz = PYZ(a.pure) exe = EXE(pyz, a.scripts, a.binaries, a.zipfiles, a.datas, name=\u0026#39;main\u0026#39;, debug=False, strip=False, upx=True, console=False) # 设置是否显示控制台 一般情况下，我们的.spec文件会比这个更多的内容，虽然原则上.spec文件支持跨平台的配置，不过我们在实际中往往根据不同的平台配置特定的.spec文件。\n你可以手动修改 .spec 文件来添加资源文件、修改导入模块、定制输出路径等。\n你可以通过编辑.spec 文件，在EXE、COLLECT和BUNDLE块下添加一个name= ，为PyInstaller提供一个更好的名字，以便为应用程序（和dist 文件夹）使用。\nEXE下的名字是可执行文件的名字，BUNDLE下的名字是应用程序包的名字。\n修改完成后，执行以下命令来重新打包：\n.spec文件本身可以编写python的代码的，例如对于不同平台的图标处理\nimport sys import os from pathlib import Path if sys.platform == \u0026#34;win32\u0026#34;: icon = \u0026#34;app/images/app.ico\u0026#34; elif sys.platform == \u0026#34;darwin\u0026#34;: icon = \u0026#34;app/images/app.icns\u0026#34; ","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/2e132ccd/0d621c73/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003epyinstaller \n    \u003cdiv id=\"pyinstaller\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#pyinstaller\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在创建了独立应用（自包含该应用的依赖包）之后，还可以使用 PyInstaller 将 Python程序生成可直接运行的程序，这个程序就可以被分发到对应的 Windows、Linux或 Mac OS 平台上运行。\u003c/p\u003e","title":"1、pyinstaller","type":"posts"},{"content":" Python 的图形化界面 # 程序的用户交互界面，英文称之为 UI (user interface)\n当一个应用的 UI 比较复杂的时候，命令行方式就不便用户使用了，这时我们需要图形界面。\n如果用 Python 语言开发 跨平台 的图形界面的程序，主要有3种选择：\nTkinter\n基于Tk的Python库，这是Python官方采用的标准库，优点是作为Python标准库、稳定、发布程序较小。 缺点是控件相对较少。 wxPython\n基于wxWidgets的Python库，优点是控件比较丰富。 缺点是稳定性相对差点、文档少、用户少。 PySide2/PySide6、PyQt5/PyQt6\n基于Qt 的Python库，优点是控件比较丰富、跨平台体验好、文档完善、用户多。 缺点是库比较大，发布出来的程序比较大。 如果要开发小工具，界面比较简单，可以采用Tkinter。\n如果是发布功能比较多的正式产品，采用基于Qt的 PySide2/PySide6、PyQt5/PyQt6。\nPySide 和 PyQt 区别 # PySide2/PySide6、PyQt5/PyQt6 都是基于著名的 Qt 库。\nQt库里面有非常强大的图形界面开发库，但是Qt库是C++语言开发的，PySide2、PyQt5可以让我们通过Python语言使用Qt。\n但是 PySide、PyQt 这两者有什么区别呢？\n可以形象地这样说： PySide 是Qt的 亲儿子 ， PyQt 是Qt还没有亲儿子之前的收的 义子 （Riverbank Computing这个公司开发的）。\n两个库的使用对程序员来说，差别很小：它们的调用接口几乎一模一样。\n如果你的程序是PyQt5开发的，往往只要略作修改，比如把导入的名字从 PyQt5 换成 PySide2 就行了。反之亦然。\n许可证方面也有区别：\n‌PyQt‌：采用GPL（GNU通用公共许可证）或商业许可证。如果你使用PyQt开发商业应用但不想开源，需要付费购买商业授权。对于开源项目，GPL是免费的，但要求你的项目也必须是开源的‌。 ‌PySide‌：使用LGPL（宽松通用公共许可证）。这允许在闭源项目中免费使用PySide6，只需遵守LGPL的要求，如动态链接等‌。 安装 # PySide6 / PySide2 # pip install pyside2 pip install pyside6 安装 PySide2， 要求 Python版本 \u0026lt;= 3.10\n安装 Pyside6， 要求 Python版本 \u0026gt;= 3.10\nPyQt6 / PyQt5 # pip install pyqt5-tools pip install pyqt6-tools 开发环境配置 # 安装完上面的依赖后，我们需要在 IDE 中进行配置\n首先我们安装完 pyside6 后，可以使用pip show pyside6查看安装路径\nVsCode # 安装 Qt for python 插件，安装完成后，打开插件的设置\n配置如下的三项\nrcc # pyside6-rcc作用是转换资源文件.qrc为.py，第一次接触可以先忽视。\n路径在python 环境的 bin 目录下\nuic # pyside6-uic作用是将QT designer设计出来的.ui文件，转换为.py文件。\n路径和上面的 rcc 在同级目录中。\ndesigner # QT designer是图形化界面设计UI的。\n执行pip show pyside6，进入Location/PySide6，里面有个Designer.exe（Windows）Designer.app（Mac）\nWindows 直接配置这个Designer.exe的路径就可以。\nMac 需要配置Designer.app/Contents/MacOS/Designer的路径。\nPyCharm # 在Settings - Tools - External Tools中分别添加三个工具\ndesigner # Program：和上面 VsCode 配置的 designer 路径一样 Arguments：固定为$FilePath$ Working directory：固定为$ProjectFileDir$ rcc # Program：和上面 VsCode 配置的 rcc 路径一样 Arguments：固定为$FilePath$ -o $FileDir$/$FileNameWithoutExtension$_rc.py Working directory：固定为$ProjectFileDir$ uic # Program：和上面 VsCode 配置的 uic 路径一样 Arguments：固定为$FilePath$ -o $FileDir$/$FileNameWithoutExtension$_ui.py Working directory：固定为$ProjectFileDir$ 自动 uic # 有的时候我们希望当我们编辑.ui文件的时候，自动实时生成.py文件，那么就可以利用 PyCharm 的File Watchers功能\n打开Settings - Tools - File Watchers中，新增一个QT Ui File Auto Conver的 Watcher即可。\nHelloWorld # from PySide6.QtWidgets import QApplication,QMainWindow,QLabel application = QApplication([]) main_window = QMainWindow() main_window.resize(500,400) main_window.move(300,300) label = QLabel(parent=main_window,text=\u0026#39;Hello World!\u0026#39;) label.move(10,10) main_window.show() application.exec() QApplication 提供了整个图形界面程序的底层管理功能，比如：\n初始化、程序入口参数的处理，用户事件（对界面的点击、输入、拖拽）分发给各个对应的控件，等等。\n既然QApplication要做如此重要的初始化操作，所以，我们必须在任何界面控件对象创建前，先创建它。\n对于上面的代码，我们可以按照规范编码的形式进行编写，即每一个自定义窗口为一个类\nfrom PySide6.QtWidgets import QApplication, QMainWindow, QLabel class MainWindow(QMainWindow): def __init__(self): super().__init__() self.lable = QLabel(parent=self, text=\u0026#39;Hello World!\u0026#39;) self.lable.move(10, 10) application = QApplication([]) main_window = MainWindow() main_window.resize(500,400) main_window.move(300,300) main_window.show() application.exec() ","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/901ba1e3/bda20da5/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003ePython 的图形化界面 \n    \u003cdiv id=\"python-的图形化界面\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#python-%e7%9a%84%e5%9b%be%e5%bd%a2%e5%8c%96%e7%95%8c%e9%9d%a2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e程序的用户交互界面，英文称之为 UI (user interface)\u003c/p\u003e","title":"1、PySide6","type":"posts"},{"content":" 什么是python # Python简单含义：Python是一门动态数据类型、面向对象的解释型语言，主要用于人工智能的各个领域，如机器学习、爬虫与数据分析、深度学习、计算机视觉等。\nPython 是一门开源免费的脚本编程语言，它不仅简单易用，而且功能强大。\nPython 是一门推崇“极简主义”的编程语言，阅读优秀的 Python 程序就像阅读一段英文，非专业人士也可以使用 Python。\nPython 是一种解释型语言： 这意味着开发过程中没有了编译这个环节。类似于PHP和Perl语言。\nPython 是交互式语言： 这意味着，您可以在一个 Python 提示符 \u0026raquo;\u0026gt; 后直接执行代码。\nPython 是面向对象语言: 这意味着Python支持面向对象的风格或代码封装在对象的编程技术。\nPython 是初学者的语言：Python 对初级程序员而言，是一种伟大的语言，它支持广泛的应用程序开发，从简单的文字处理到 WWW 浏览器再到游戏。\npython的特点 # Python 是一门开源免费的脚本编程语言，它不仅简单易用，而且功能强大。\nPython 是一门推崇“极简主义”的编程语言，阅读优秀的 Python 程序就像阅读一段英文，非专业人士也可以使用 Python。\nPython 是一种解释型语言： 这意味着开发过程中没有了编译这个环节。类似于PHP和Perl语言。\nPython 是交互式语言： 这意味着，您可以在一个 Python 提示符 \u0026raquo;\u0026gt; 后直接执行代码。\nPython 是面向对象语言: 这意味着Python支持面向对象的风格或代码封装在对象的编程技术。\nPython 是初学者的语言：Python 对初级程序员而言，是一种伟大的语言，它支持广泛的应用程序开发，从简单的文字处理到 WWW 浏览器再到游戏。\nPython环境安装 # 1、下载 # 官网下载：https://www.python.org/\n2、安装 # 安装包双击后，可以选择app to Path，自动添加到环境变量，或者，将python的安装路径配置到Path环境变量中，然后将python安装路径下Scripts文件夹的路径也配置到Path环境变量中\nPIP # pip是一个现代的、通用的python包管理工具，提供了对python包的查找、安装、卸载功能，便于我们对于python资源包的管理\n一般情况下，包总是被安装在python\\lib\\site-packages\\包名\\\n1、安装 # 安装python的时候，会自动下载和安装PIP\n安装完python，可以使用pip -V命令，查看\n2、配置 # 永久修改pip数据源 # 如果不想每个包都要自己添加数据源，那么可以设置统一的数据源\nWindows\n#1、在windows资源管理器进入路径 %APPDATA% #2、这个路径下新建pip文件夹 #3、新建pip.ini文件，添加以下内容 [global] timeout = 6000 index-url = http://mirrors.aliyun.com/pypi/simple trusted-host = mirrors.aliyun.com #4、cmd中使用命令查看是否配置成功 pip config list Linux、Mac\n#1、在~下，建立.pip/pip.conf vim ~/.pip/pip.conf #2、添加配置 [global] timeout = 6000 index-url = http://mirrors.aliyun.com/pypi/simple trusted-host = mirrors.aliyun.com #3、查看配置 pip config list 常用的数据源 # http://mirrors.aliyun.com/pypi/simple/ # 阿里云 https://pypi.mirrors.ustc.edu.cn/simple/ # 中国科技大学 http://pypi.douban.com/simple/ # 豆瓣(douban) https://pypi.tuna.tsinghua.edu.cn/simple/ # 清华大学 http://pypi.mirrors.ustc.edu.cn/simple/ # 中国科学技术大学 3、pip常用命令 # install # #安装指定的包，默认为最新版本，install可以简写为i pip install [包名] #安装指定包的指定版本，通过使用==, \u0026gt;=, \u0026lt;=, \u0026gt;, \u0026lt; 来指定一个版本号 pip install [包名]==[版本号] #从某个源下载某个包，--index-url可以简写为-i pip install [包名] --index-url [国内源地址] #通过requirements文件批量安装软件包 pip install -r [requirements文件路径] #更新指定的包，--upgrade可以简写为-U pip install --upgrade [包名] #pip升级自己 pip install --upgrade pip #离线安装whl，--no-index：不检查包索引中可用信息，--find-links：从指定的目录下找离线包 pip install --no-index --find-link=pandas pandas uninstall # #卸载指定包 pip uninstall [包名] #通过requirements文件批量卸载 pip uninstall -r [requirements文件路径] freeze # #显示已经安装的包，以requirements文件的格式显示 pip freeze #显示已经安装的包，以requirements文件的格式输出到指定路径 pip freeze \u0026gt; [requirements文件路径] list # #列出所有安装的库 pip list #列出所有过期的库，--outdated可以简写为-o pip list --outdated show # #显示包所在目录及信息(例如版本号、摘要信息、官网、作者、作者邮箱等等) pip show [] IDE # pycharm # 所有版本下载：https://www.jetbrains.com/pycharm/download/other.html\n安装后，最好设置每个文件的描述注释，方便阅读\n# _*_coding: utf-8 _*_ # @Time: ${DATE} ${TIME} # @Author: yhgh # @Describe: vscode # 需要安装插件python，优化开发体验\n如果出现输出框中文乱码，需要在系统环境变量添加如下变量，重启vscode即可\n变量名：PYTHONIOENCODING 变量值：UTF8 Python的编码规范 # 缩进 # 每一行代码开始前的空白区域，用来表示代码之间的包含和层次关系。 1个缩进 = 4个空格 用以在Python中标明代码的层次关系 和java不同，缩进是Python语言中表明程序框架的唯一手段 空行和空格 # 在每个类、函数定义或一段完整的功能代码之后增加一个空行 在运算符两侧各增加一个空格，逗号后面增加一个空格，让代码适当松散一点，不要过于密集 标识符命名 # 必须以英文字母、汉字或下划线开头，不能以数字开头 名字中可以包含汉字（最好不要）、英文字母、数字和下划线，不能有空格或任何标点符号 不能使用保留字（关键字） 不建议使用系统内置的模块名、类型名或函数名以及已导入的模块名及其成员名作变量名或者自定义函数名 标识符对大小写敏感，python和Python是两个不同的名字 python的保留字（关键字）（33个） # #查看python的关键字 import keyword print(keyword.kwlist) 续行 # 尽量不要写过长的语句，应尽量保证一行代码不超过屏幕宽度 如果语句确实太长而超过屏幕宽度，最好在行尾使用续行符\\表示下一行代码仍属于本条语句，或者用圆括号把多行代码括起来表示是一条语句 expression1 = 1 + 2 + 3\\ + 4 + 5 expression2 = (1 + 2 + 3 + 4 + 5) 注释 # 单行注释 # # 这是一个demo程序 print(\u0026#34;Hello World!\u0026#34;) 多行注释 # \u0026#39;\u0026#39;\u0026#39; 这是一个demo程序 这是第二行注释 \u0026#39;\u0026#39;\u0026#39; print(\u0026#34;Hello World!\u0026#34;) 源代码说明 # 由于Python源代码也是一个文本文件，所以，当你的源代码中包含中文的时候，在保存源代码时，就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时，为了让它按UTF-8编码读取，我们通常在文件开头写上这两行：\n#!/usr/bin/python3 # -*- coding: utf-8 -*- \u0026#34;Is My Test Model\u0026#34; __author__ = \u0026#39;yhgh\u0026#39; 第一行：Shebang行（也称为Hashbang），注释是为了告诉Linux/OS X系统，这是一个Python可执行程序，python解释器的路径，Windows系统会忽略这个注释；\n第二行：注释是为了告诉Python解释器，按照UTF-8编码读取源代码，否则，你在源代码中写的中文输出可能会有乱码；\n第三行：是一个字符串，表示模块的文档注释，任何模块代码的第一个字符串都被视为模块的文档注释；\n第四行：使用__author__变量把作者写进去\n","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/aae761a6/f4e189aa/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是python \n    \u003cdiv id=\"什么是python\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%afpython\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003ePython简单含义\u003c/strong\u003e：\u003ccode\u003ePython是一门动态数据类型、面向对象的解释型语言，主要用于人工智能的各个领域，如机器学习、爬虫与数据分析、深度学习、计算机视觉等。\u003c/code\u003e\u003c/p\u003e","title":"1、Python语言概述","type":"posts"},{"content":"Tkinter是Tk GUI工具包的Python绑定包。它是Tk GUI工具包的标准Python接口，并且是Python的业界标准GUI工具包。Tkinter同时也包含在Python的Linux、Microsoft Windows和Mac OS X标准库中。Tkinter的名字来自Tk Interface。\nTkinter 是 Python 内置的包，可以使用python -m tkinter会弹出一个简单的 Tk 界面窗口，还会显示当前安装的 Tcl/Tk 版本。\nHello World # 创建一个文件hello_world.py，并写入如下代码\n#导入Tkinter模块，并用别名tk引用它。 import tkinter as tk #创建一个顶级窗口（root window），这是整个GUI程序的基础。 root = tk.Tk() #创建一个标签（Label），设置其初始文本为\u0026#34;Click the button to say hello!\u0026#34;，并将其添加到根窗口中。 lable = tk.Label(root,text=\u0026#34;Click the button to say hello!\u0026#34;) #pack()方法用于将控件放置在父容器中，并自动调整它们的大小和位置。 lable.pack() #定义一个函数say_hello，当按钮被点击时，这个函数会被调用。 这个函数会更新标签（label）的文本为\u0026#34;Hello World!\u0026#34;。 def say_hello(): lable.config(text=\u0026#39;Hello World!\u0026#39;) #创建一个按钮（Button），设置其文本为\u0026#34;SAY Hello\u0026#34;，并将其命令属性设置为say_hello函数。这意味着当用户点击此按钮时，say_hello函数将被调用。 button = tk.Button(root, text=\u0026#34;Say Hello\u0026#34;, command=say_hello) button.pack() #最后，进入主循环。在此过程中，程序会持续监听用户的操作，如点击按钮等，并作出相应的响应。 root.mainloop() 执行python hello_world.py，会看到创建的窗口\n自定义字体颜色 # import tkinter as tk root = tk.Tk() # 自定义字体 custom_font = (\u0026#39;Arial\u0026#39;, 14, \u0026#39;bold\u0026#39;) # 应用字体、自定义背景颜色 lable = tk.Label(root,text=\u0026#34;Click the button to say hello!\u0026#34;,font=custom_font,bg=\u0026#39;red\u0026#39;) lable.pack() def say_hello(): lable.config(text=\u0026#39;Hello World!\u0026#39;) # 应用字体、自定义背景颜色 button = tk.Button(root, text=\u0026#34;Say Hello\u0026#34;, command=say_hello,font=custom_font,bg=\u0026#39;red\u0026#39;) button.pack() root.mainloop() 字体名称 大小 粗体样式 颜色 Arial、 宋体 1~100 bold red 、green 、blue 组件 # 在 tkinter 中，有 21 个核⼼组件，它们提供 了GUI开发的完整功能，因为使⽤频率较⾼。\n这 21 个核⼼组件是 : Label、Button、Entry、Menu、 Radiobutton 、Checkbutton、Text、Image、Canvas、Frame、LabelFrame、Toplevel、 Listbox、Menubutton、Message、OptionMenu、PaneWindow 、 Scale 、Scrollbar 、Spinbox、Bitmap。\n组件的使用：\n各个组件都有相应的类，我们可以通过⾯向对象的⽅式去使⽤它们。 这些组件的使⽤也很相似，在实例化这些组件的时候，第⼀个参数都是⽗窗⼝或者⽗组件，后⾯跟着的就是该组件的⼀些属性，⽐如上⾯我们学到的 Label 的 text属性和 background 属性。 多个组件的位置控制⽅式也很相似，我们可以⽤ pack ⽅法来进⾏简单的布局。 组件也会有些⽅法是共⽤的，⽐如 configure ⽅法来设置属性等等。 窗口 # Tkinter 的窗口是基于对象的，主要包含以下两种类型的窗口：\n主窗口（Main Window）：由 Tkinter.Tk() 构造函数创建，是程序的主要窗口。\n子窗口（Toplevel Window）：由 Tkinter.Toplevel() 构造函数创建，可以从主窗口中独立出来。\n窗口常用属性 # title：设置窗口的标题。 geometry：设置窗口的尺寸和位置。 minsize 和 maxsize：设置窗口的最小和最大尺寸。 resizable：设置窗口是否可调整大小。 iconphoto：设置窗口的图标。 创建子窗口 # import tkinter as tk # 主窗口 root = tk.Tk() root.title(\u0026#39;主窗口\u0026#39;) # 子窗口 top_level = tk.Toplevel() top_level.title(\u0026#39;子窗口\u0026#39;) root.mainloop() 常用方法 # 下边这一系列方法用于与窗口管理器进行交互。他们可以被 Tk（主窗口）进行调用，同样也适用于 Toplevel（子窗口）。\n设置窗口尺寸 # root.geometry(\u0026#34;widthxheight+x+y\u0026#34;) # 例如：\u0026#34;400x300+100+200\u0026#34; 设置窗口标题 # root.title(\u0026#34;窗口标题\u0026#34;) 设置窗口图标 # root.iconphoto(False, tk.PhotoImage(file=\u0026#34;icon.png\u0026#34;)) 设置窗口是否可调整大小 # root.resizable(width=False, height=True) # 宽度不可调整，高度可调整 窗口事件循环 # 在 Tkinter 中，窗口需要进入事件循环才能正常工作。事件循环通过 mainloop() 方法启动：\nroot.mainloop() 关闭窗口 # root.destroy() root.quit()导致mainloop退出。解释器仍然完整无缺，所有小部件也一样。如果调用此函数，则可以使代码在调用之后执行root.mainloop()，并且该代码可以与小部件交互（例如，从输入小部件获取值）。\nroot.destroy()将破坏所有小部件并退出mainloop。调用之后的任何代码root.mainloop()都将运行，但是任何访问任何窗口小部件的尝试（例如，从条目窗口小部件获取值）都将失败，因为该窗口小部件不再存在。\n","date":"2025-04-15","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/a879fe06/f0757be2/","section":"文章","summary":"\u003cp\u003eTkinter是Tk GUI工具包的Python绑定包。它是Tk GUI工具包的标准Python接口，并且是Python的业界标准GUI工具包。Tkinter同时也包含在Python的Linux、Microsoft Windows和Mac OS X标准库中。Tkinter的名字来自Tk Interface。\u003c/p\u003e","title":"1、Tkinter","type":"posts"},{"content":" 使用模块 # 编写main.py\n#!/usr/bin/python3 # -*- coding: utf-8 -*- \u0026#34;Is My Test Model\u0026#34; __author__ = \u0026#39;yhgh\u0026#39; # 引入sys模块 import sys print(\u0026#39;命令行参数为\u0026#39;,sys.argv) print(\u0026#39;python路径为\u0026#39;,sys.path) #命令行执行 python .\\main.py lucy tom lily \u0026gt;\u0026gt;\u0026gt;\u0026gt; #命令行参数为 [\u0026#39;.\\\\main.py\u0026#39;, \u0026#39;lucy\u0026#39;, \u0026#39;tom\u0026#39;, \u0026#39;lily\u0026#39;] #python路径为 [\u0026#39;E:\\\\pythonproject\\\\demo\u0026#39;, \u0026#39;E:\\\\python\\\\python36\\\\python36.zip\u0026#39;, \u0026#39;E:\\\\python\\\\python36\\\\DLLs\u0026#39;, \u0026#39;E:\\\\python\\\\python36\\\\lib\u0026#39;, \u0026#39;E:\\\\python\\\\python36\u0026#39;, \u0026#39;E:\\\\python\\\\python36\\\\lib\\\\site-packages\u0026#39;] import sys 引入 python 标准库中的 sys.py 模块；这是引入某一模块的方法。 sys.argv 是一个包含命令行参数的列表，第一个永远为python文件名。 sys.path 包含了一个 Python 解释器自动查找所需模块的路径的列表。 import # 语法：import [model_name1],[model_name2]，import [model_name] as 别名\nmodel.py\n#!/usr/bin/python3 # -*- coding: utf-8 -*- \u0026#34;Is My Test Model\u0026#34; __author__ = \u0026#39;yhgh\u0026#39; def add(num1,num2): return num1 + num2 def subtract(num1,num2): return num1 - num2 main.py\n#!/usr/bin/python3 # -*- coding: utf-8 -*- \u0026#34;Is My Test Model For Import\u0026#34; __author__ = \u0026#39;yhgh\u0026#39; #引入模块 import model #使用模块中的函数 print(model.add(1,2)) print(model.subtract(1,2)) 当解释器遇到 import 语句，如果模块在当前的搜索路径就会被导入。\n搜索路径是一个解释器会先进行搜索的所有目录的列表，一般为当前项目路径\n一个模块只会被导入一次，不管你执行了多少次import。这样可以防止导入模块被一遍又一遍地执行。\nfrom-import # Python 的 from 语句让你从模块中导入一个指定的部分到当前命名空间中\n语法：from [model_name] import [name1],[name2]\nmain.js\n#!/usr/bin/python3 # -*- coding: utf-8 -*- \u0026#34;Is My Test Model For Import\u0026#34; __author__ = \u0026#39;yhgh\u0026#39; #引入模块 from model import add,subtract #使用模块中的函数 print(add(1,2)) print(subtract(1,2)) __name__和__main__属性 # 一个模块直接执行的时候，其主程序将运行，此时__name__的值为__main__。如果被作为一个模块引入时，那么此时这个模块的__name__值为模块名。\n在实际开发中，当一个开发人员编写完一个模块后，为了让模块能够在项目中达到想要的效果，这个开发人员会自行在py文件中添加一些测试方法，但是在被引用后，则不希望这些方法再执行，我们就可以通过__name__去判断。\nmodel.js\nif __name__ == \u0026#39;__main__\u0026#39;: print(\u0026#39;程序本身运行\u0026#39;) else: print(\u0026#39;我作为一个模块\u0026#39;) main.js\nimport model \u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt; 我作为一个模块 __all__属性 # 如果一个模块文件中有__all__变量，当使用from xxx import *导入时，只能导入这个列表中的元素\n__all__ = [\u0026#39;test1\u0026#39;] def test1(): print(\u0026#39;test1\u0026#39;) def test2(): print(\u0026#39;test2\u0026#39;) 作用域 # 在一个模块中，我们可能会定义很多函数和变量，但有的函数和变量我们希望给别人使用，有的函数和变量我们希望仅仅在模块内部使用\n在Python中，是通过_前缀来实现的私有的函数和变量，private函数和变量“不应该”被直接引用，而不是“不能”被直接引用，是因为Python并没有一种方法可以完全限制访问private函数或变量，但是，从编程习惯上不应该引用private函数或变量。\n包 # 从物理上看，包就是一个文件夹，在该文件夹下包含了一个__init__.py文件，该文件夹可用于包含多个模块文件从逻辑上看，包的本质依然是模块\n包的作用：当我们的模块文件越来越多时,包可以帮助我们管理这些模块, 包的作用就是包含多个模块，但包的本质依然是模块\n自定义包 # ① 新建包my_package\n② 新建包内模块：my_module1 和 my_module2\n③ 模块内代码如下\n在 Pycharm 中，通过 New 下面的 Python Package 新建的包内部会自动创建__init__.py文件，这个文件控制着包的导入行为\n导入包 # 方式一 # # 导入方式 import 包名.模块名 # 使用包内的函数 包名.模块名.函数 方式二 # 注意：必须在__init__.py文件中添加__all__ = []，控制允许导入的模块列表\n# 导入方式 from 包名 import * # 使用包内的函数 模块名.函数 ","date":"2025-04-09","externalUrl":null,"permalink":"/posts/1b37041b/a64b122d/91f8d7b5/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e使用模块 \n    \u003cdiv id=\"使用模块\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bd%bf%e7%94%a8%e6%a8%a1%e5%9d%97\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e编写\u003ccode\u003emain.py\u003c/code\u003e\u003c/p\u003e","title":"1、模块、包","type":"posts"},{"content":" 下载和安装 # 官网：https://www.blender.org/\n中文官网：https://www.blendercn.org/\n这两个网站都可以下载。\n后面的文档都是基于 4.2.8 版本。\n界面 # 控制3D视图视角 # 默认情况下，操作如下：\n按住鼠标中键：旋转视图 按住（shift + 鼠标中键）：拖动视图 滚动鼠标中键：缩放视图 同样的，使用鼠标左键按住右上角的这几个按钮也可以达到上面的效果\n倒数第二个摄像机，就是摄像机视图，也就是最后看到的样子\n控制3D物体 # 左上角的按钮分别对应了，移动（快捷键G）、旋转（快捷键R）、缩放（快捷键S）、变换（快捷键T）\n这些操作在激活的状态下，可以按（X、Y、Z）选择控制方向。\n按住键盘的Alt加上面的任何快捷键即可撤销控制。\n删除物体 # 点击物体后，按键盘delete或按X。\n新建物体 # Shift + a调出物体菜单\n隐藏物体 # H：隐藏选中物体\nAlt + H：撤销隐藏\nShift + H：隐藏选中物体意外以外的物体\n复制物体 # Shift + D\n基本3D建模工作流程 # 建模（Modeling）、布光（Lighting）、材质（Texturing）、渲染（Rendering）\n游标和原点 # 游标 # 作用一：游标在哪里，新建的物体就会出现在哪里\n使用左上角游标工具或者Shift + 鼠标右键即可选择游标位置 使用Shift + C即可快速将游标恢复到世界原点 作用二：作为物体变换的中心点\n原点 # 原点就是物体中的黄色的点，可以影响物体变换的位置\n可以在右上角选项中选择仅影响原点，后续的变换就是对原点的变换\n","date":"2025-04-04","externalUrl":null,"permalink":"/posts/69064821/f1464f3a/eaddf050/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e下载和安装 \n    \u003cdiv id=\"下载和安装\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%8b%e8%bd%bd%e5%92%8c%e5%ae%89%e8%a3%85\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e官网：https://www.blender.org/\u003c/p\u003e","title":"1、Blender","type":"posts"},{"content":" Gradle # Gradle构建工具是一个快速、可靠和适应性强的开源构建自动化工具，具有优雅和可扩展的声明性构建语言，Gradle包含许多优势：\nGradle是JVM平台最受欢迎的构建系统，也是Android和Kotlin多平台项目的默认选择，它拥有丰富的社区插件生态系统。 Gradle可以使用其内置函数、第三方插件或自定义构建逻辑自动执行各种软件构建场景。 Gradle提供了一种高级、声明式和富有表现力的构建语言，使阅读和编写构建逻辑变得很轻松。 Gradle快速、可扩展，可以构建任意规模和复杂度的项目。 Gradle产生可靠的结果，同时受益于增量构建、构建缓存和并行执行等优化。 如果想下载Spring、SpringBoot等Spring家族的源码，基本上基于Gradle构建的 Gradle支持Android、Java、Kotlin Multiplatform、Groovy、Scala、Javascript和C/C++等热门语言的构建工作\n安装 # 在开始安装之前，首先确保您已经安装好Java环境（使用java --version命令进行查看）版本不能低于Java 8\n访问官方网站：https://docs.gradle.org/current/userguide/installation.html#installation\n按照官网教程下载压缩包，并配置环境变量\n配置完成后，使用命令gradle -v可查看版本\n配置Gradle本地仓库 # 特别注意：这里我们接着再配置GRALE_USER_HOME环境变量：GRALE_USER_HOME相当于配置Gradle本地仓库位置和GradleWrapper缓存目录。\nGradle本地仓库可以和Maven本地仓库目录一致，但是不建议，因为两者包管理方式不一样\n配置Gradle镜像 # 在GRADLE_HOME下的init.d目录中创建文件：init.gradle\n写入如下配置\nallprojects { repositories { mavenLocal() maven { name \u0026#34;Alibaba\u0026#34; ; url \u0026#34;https://maven.aliyun.com/repository/public\u0026#34; } // 私服配置 // maven { // url = uri(\u0026#34;http://your-company-repo-url/repository/maven-releases/\u0026#34;) // credentials { // username = \u0026#39;your-username\u0026#39; // 用户名 // password = \u0026#39;your-password\u0026#39; // 密码 // } // } maven { name \u0026#34;Bstek\u0026#34; ; url \u0026#34;https://nexus.bsdn.org/content/groups/public/\u0026#34; } mavenCentral() } buildscript { repositories { maven { name \u0026#34;Alibaba\u0026#34; ; url \u0026#39;https://maven.aliyun.com/repository/public\u0026#39; } maven { name \u0026#34;Bstek\u0026#34; ; url \u0026#39;https://nexus.bsdn.org/content/groups/public/\u0026#39; } maven { name \u0026#34;M2\u0026#34; ; url \u0026#39;https://plugins.gradle.org/m2/\u0026#39; } } } } allprojects：表示全局配置 repositories：项目需要依赖的jar包，会从repositories 指定的仓库去下载 mavenLocal：从本地 Maven 仓库查找依赖，需要电脑配置到Maven的环境变量，变量名为 M2_HOME maven：自定义 Maven 仓库镜像 mavenCentral：Maven 中央仓库 buildscript：是给 build.gradle 使用的，即如果构建脚本本身需要一些依赖，它就会去这个指定的仓库去下载 创建Gradle项目 # Gradle项目目录结构 # .gradle：Gradle自动生成的项目缓存目录。 .idea：这个是IDEA的项目配置目录，跟Gradle生成的没关系，无视掉就行。 app：存放整个项目的源代码、测试等，这里面就是我们写代码的地方了。 build.gradle.kts：项目的gradle构建脚本。 src：存放源代码和测试代码。 main：编写所有项目核心代码。 test：编写项目测试代码。 gradle：包含JAR文件和Gradle Wrapper的配置。 gradlew：适用于macOS和Linux的使用Gradle Wrapper执行构建的脚本（这里的版本就是GradleWrapper指定的版本） gradlew.bat：适用于Windows的使用Gradle Wrapper执行构建的脚本。 settings.gradle.kts：定义子项目列表的项目配置文件，也是最关键的设置文件。 build：封装编译后的字节码、打成的包Jar、War、测试报告等信息 Gradle中的常用指令 # 命令 说明 gradle clean 清空 build 目录 gradle classes 编译业务代码和配置文件 gradle test 编译测试代码，生成测试报告 gradle build 构建项目 gradle build -x test 跳过测试构建项目 需要注意的是：gradle的指令要在含有build.gradle或build.gradle.kts的目录执行 。\ngradle 和 gradlew 的区别 # 创建一个 Gradle 项目，会发现项目目录中有gradlew和gradlew.bat两个文件，那么使用这两个文件执行命令和使用gradle命令的区别如下：\ngradle 和 gradlew 都用于执行Gradle构建任务，但它们之间有一些关键区别\ngradle\n系统级安装：gradle 命令需要你在系统上手动安装Gradle。这意味着你需要自己管理Gradle版本。\n全局配置：由于是系统级安装，所有使用 gradle 命令的项目将共享相同的Gradle版本，除非你明确地为每个项目指定不同的版本。\n版本冲突：如果不同的项目需要不同版本的Gradle，使用 gradle 可能会导致版本冲突。\ngradlew（Gradle Wrapper）\n项目级安装：gradlew（Gradle Wrapper）是一个项目级的工具，它自动下载你项目所需的正确版本的Gradle。这意味着你不需要在系统上手动安装Gradle。\n版本隔离：由于每个项目都有自己的Gradle Wrapper，因此不同项目可以轻易地使用不同版本的Gradle，而不会互相影响。\n便于协作：使用Gradle Wrapper意味着所有开发者和CI/CD环境都将使用相同版本的Gradle，这使得构建更加可预测和可重复。\n自动化和便捷性：由于Gradle Wrapper自动管理Gradle版本，因此它特别适用于自动化构建和持续集成。\n总结 # 使用 gradlew 通常更为推荐，因为它提供了更好的版本管理和项目隔离。 如果你是项目的唯一开发者，并且确定所有项目都将使用相同版本的Gradle，那么使用 gradle 也是可以的 项目配置文件 # settings.gradle # 配置文件settings.gradle是整个Gradle项目的入口点：\n配置文件用于定义所有的子项目，并让它们参与到构建中，Gradle支持单项目和多项目的构建：\n对于单个项目构建，设置文件是可选的。 对于多项目构建，设置文件必须存在，并在其中定义所有子项目。 配置文件可以使用Groovy语言（名称为settings.gradle）或是Kotlin语言（名称为settings.gradle.kts）编写，后面我们一律采用Kotlin语言，设置文件通常位于项目的根目录中\n一个标准的Gradle设置文件按照以下样式进行编写，这里使用Kotlin语言介绍：\nrootProject.name = \u0026#34;root-project\u0026#34; //rootProject对象代表当前这个项目，其name属性就是当前项目的名称 include(\u0026#34;sub-project-a\u0026#34;) //所有的子项目使用include()函数进行添加，如果没有子项目可以不写 include(\u0026#34;sub-project-b\u0026#34;) include(\u0026#34;sub-project-c\u0026#34;) 当找到设置文件settings.gradle(.kts)时，Gradle会实例化一个Settings对象，我们可以通过此对象来声明要包含在构建中的所有项目，包括我们项目名称的声明也是通过它来完成\nsettings.rootProject.name = \u0026#34;untitled\u0026#34; //可以省略掉settings直接使用其提供的属性 rootProject.name = \u0026#34;untitled\u0026#34; 其中，Settings对象包含以下常用属性：\n名称 描述 buildCache 项目构建所用缓存配置。 plugins 用于设置的插件。 rootDir 项目构建的根目录，根目录是整个项目目录最外层。 rootProject 构建的根项目。 settings 返回设置对象。 在Gradle中，和Maven一样也分为插件和依赖，我们可以在settings.gradle.kts中可以为所有的项目进行统一配置，比如要修改获取插件的仓库位置\npluginManagement { //使用pluginManagement函数配置插件仓库列表 repositories { //在repositories函数中配置需要用的仓库 gradlePluginPortal() //Gradle插件仓库 google() //Google插件仓库 } } 我们也可以修改为国内的阿里云镜像\npluginManagement { repositories { //手动指定maven仓库地址，修改URL地址 maven { setUrl(\u0026#34;https://maven.aliyun.com/repository/public/\u0026#34;) } } } 同样的，对于所有的依赖，也可以直接配置为国内的阿里云镜像仓库\ndependencyResolutionManagement { //依赖解析管理中可以配置全局依赖仓库 repositories { //只不过这种方式目前还在孵化阶段，可能会在未来某个版本移除 maven { setUrl(\u0026#34;https://maven.aliyun.com/repository/public/\u0026#34;) } } } 不过，除了在settings.gradle.kts中配置三方仓库之外，我们更推荐在build.gradle.kts中对仓库进行配置。\nbuild.gradle # 针对于单个项目的构建文件，其中gradle.build文件就是对应的构建配置，这里我们使用的是Kotlin语言，因此项目中会存在一个gradle.build.kts文件\nplugins { kotlin(\u0026#34;jvm\u0026#34;) version \u0026#34;1.8.0\u0026#34; application } group = \u0026#34;top.ygang\u0026#34; version = \u0026#34;1.0-SNAPSHOT\u0026#34; repositories { mavenCentral() } dependencies { testImplementation(kotlin(\u0026#34;test\u0026#34;)) } tasks.test { useJUnitPlatform() } kotlin { jvmToolchain(8) } application { mainClass.set(\u0026#34;MainKt\u0026#34;) } 可以看到，在这个配置文件中存在大量的Lambda语句，这也使得整个配置文件写起来更加简介美观，所以说虽然Gradle依赖于JVM平台，但是仅支持Kotlin和Groovy，它们相比Java在语法上存在更大的优势，更适合编写这种类似脚本一样的配置文件。\n对于设置文件中包含的每个项目，Gradle都会为其创建一个Project实例对象，我们可以直接在build.gradle.kts中使用\ngroup = \u0026#34;top.ygang\u0026#34; version = \u0026#34;1.0-SNAPSHOT\u0026#34; //本质上就是project的属性 project.group = \u0026#34;top.ygang\u0026#34; //本质上就是project的属性 project.version = \u0026#34;1.0-SNAPSHOT\u0026#34; 在此对象中，包含以下常见属性\n名称 类型 描述 name String 项目目录的名称。 path String 该项目的完全限定名称。 description String 该项目的描述。 dependencies DependencyHandler 配置项目的依赖列表。 repositories RepositoryHandler 配置项目的依赖仓库。 layout ProjectLayout 通过此对象来访问项目中的关键位置。 group Object 项目的组。 version Object 项目的版本。 plugins # 最顶上的plugins函数，后面的Lambda中编写了当前项目需要使用到的插件\n一般情况下，我们普通的Java项目可以直接使用java插件，它能够直接完成编译和打包Java代码\nplugins { id(\u0026#34;java\u0026#34;) } // 或者这样写 plugins { java } 如果我们需要将项目打包为一个可执行的文件，也可以使用application插件，它包含java插件的全部功能，同样支持编译和打包Java代码，并且支持生成可执行的应用程序\nplugins { id(\u0026#34;application\u0026#34;) } java { //configure\u0026lt;JavaApplication\u0026gt; 也可以直接写成 java 这个扩展函数，效果一样 targetCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17 } application { //同configure\u0026lt;JavaApplication\u0026gt; mainClass = \u0026#34;top.ygang.Main\u0026#34; //配置主类 } group和version # 设置了当前项目所属的组名称和版本\ngroup = \u0026#34;top.ygang\u0026#34; version = \u0026#34;1.0-SNAPSHOT\u0026#34; repositories # 所有依赖的仓库配置，默认使用的是Maven中心仓库\nrepositories { mavenCentral() } tasks.test # 任务相关配置，这里对test任务进行了相关配置\ntasks.test { useJUnitPlatform() } ","date":"2025-03-25","externalUrl":null,"permalink":"/posts/b3f5e6ce/b7344362/86f14092/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eGradle \n    \u003cdiv id=\"gradle\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#gradle\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eGradle构建工具是一个快速、可靠和适应性强的开源构建自动化工具，具有优雅和可扩展的声明性构建语言，Gradle包含许多优势：\u003c/p\u003e","title":"1、Gradle","type":"posts"},{"content":" Kotlin # Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言，被称之为 Android 世界的Swift，由 JetBrains 设计开发并开源。\nKotlin 可以编译成Java字节码，也可以编译成 JavaScript，方便在没有 JVM 的设备上运行。\n在Google I/O 2017中，Google 宣布 Kotlin 成为 Android 官方开发语言。\n为什么选择 Kotlin # 简洁: 大大减少样板代码的数量。 安全: 避免空指针异常等整个类的错误。 互操作性: 充分利用 JVM、Android 和浏览器的现有库。 工具友好: 可用任何 Java IDE 或者使用命令行构建。 提供了多种方式在多个平台间复用代码。 安装环境 # IDE # Kotlin 已包含在每个IntelliJ IDEA与 Android Studio版本中了。\n可下载并安装这两个 IDE 之一来开始使用 Kotlin。\nHello World # package top.ygang.kotlindemo fun main(args: Array\u0026lt;String\u0026gt;){ println(\u0026#34;Hello World！\u0026#34;); } 基础语法 # 包的定义和导入 # 在之前，无论我们创建的是Kotlin源文件还是Kotlin类文件，都是在默认的包下进行的，也就是直接在kotlin/src目录创建的。\n但是有些时候，我们可能希望将一些模块按功能进行归类，而不是所有的kt文件都挤在一起，这个时候我们就需要用到包了，也就是在kotlin/src目录中创建的子目录。\n所有不直接在默认包目录中的kt文件，必须在顶部声明所属的包，比如Test.kt放在kotlin/com/test这个包中，因此顶部必须使用package关键字进行包声明package com.test。\n当我们使用其他包中kt文件定义的类或函数时，会直接提示未解析的引用，这是因为默认情况下只有同包内的内容可以相互使用，而现在我们使用的是其他包中的内容，我们需要先进行导入操作import com.test.User、import com.test.myFunc\n这样，我们在导入之后就可以正常使用了，当然，如果一个包中定义的内容太多，我们需要大量使用，也可以使用*一次性导入全部内容import com.test.*\n定义 # package org.example fun printMessage() { /*……*/ } class Message { /*……*/ } kotlin源文件不需要相匹配的目录和包，源文件可以放在任何文件目录。\n源文件所有内容（无论是类还是函数）都包含在该包内。 所以上例中 printMessage() 的全名是 org.example.printMessage， 而 Message 的全名是 org.example.Message。\n如果没有指定包，默认为default包。\n导入 # 有多个包会默认导入到每个 Kotlin 文件中：\nkotlin.* kotlin.annotation.* kotlin.collections.* kotlin.comparisons.* kotlin.io.* kotlin.ranges.* kotlin.sequences.* kotlin.text.* 根据目标平台还会导入额外的包：\nJVM: java.lang.* kotlin.jvm.* JS: kotlin.js.* 除了默认导入之外，每个文件可以包含它自己的导入（import）指令。\n可以导入一个单个名称：\nimport org.example.Message // 现在 Message 可以不用限定符访问 也可以导入一个作用域下的所有内容：包、类、对象等:\nimport org.example.* // “org.example”中的一切都可访问 如果出现名字冲突，可以使用 as 关键字在本地重命名冲突项来消歧义：\nimport org.example.Message // Message 可访问 import org.test.Message as TestMessage // TestMessage 代表“org.test.Message” 关键字 import 并不仅限于导入类；也可用它来导入其他声明：\n顶层函数及属性 在对象声明中声明的函数和属性 枚举常量 程序入口 # Kotlin 应用程序的入口点是 main 函数：\nfun main() { println(\u0026#34;Hello world!\u0026#34;) } main 的另一种形式接受可变数量的 String 参数：\nfun main(args: Array\u0026lt;String\u0026gt;) { println(args.contentToString()) } 注释 # // 单行注释 /** * 文档注释 */ /* * 多行注释 */ 编码规范 # 目录结构 # 在纯 Kotlin 项目中，推荐的目录结构遵循省略了公共根包的包结构。例如，如果项目中的所有代码都位于 org.example.kotlin 包及其子包中，那么 org.example.kotlin 包的文件应该直接放在源代码根目录下，而 org.example.kotlin.network.socket 中的文件应该放在源代码根目录下的 network/socket 子目录中。\n源文件名称 # 如果 Kotlin 文件包含单个类或接口（以及可能相关的顶层声明），那么文件名应该与该类的名称相同，并追加 .kt 扩展名。 这适用于所有类型的类和接口。 如果文件包含多个类或只包含顶层声明， 那么选择一个描述该文件所包含内容的名称，并以此命名该文件。 使用首字母大写的驼峰风格（也称为 Pascal 风格）， 例如 ProcessDeclarations.kt。\n多平台项目 # 在多平台项目中，平台特有的源代码集中包含顶层声明的文件应具有与该源代码集名称相关联的后缀。 例如：\njvmMain/kotlin/Platform.jvm.kt androidMain/kotlin/Platform.android.kt iosMain/kotlin/Platform.ios.kt 对于公共源代码集，包含顶层声明的文件不应该有后缀。 例如，commonMain/kotlin/Platform.kt.\n命名规则 # 在 Kotlin 中，包名与类名的命名规则非常简单：\n包的名称总是小写且不使用下划线（org.example.project）。 通常不鼓励使用多个词的名称，但是如果确实需要使用多个词，可以将它们连接在一起或使用驼峰风格（org.example.myProject）。 类与对象的名称以大写字母开头并使用驼峰风格。 函数、属性与局部变量的名称以小写字母开头、使用驼峰风格而不使用下划线。 ","date":"2025-03-21","externalUrl":null,"permalink":"/posts/b3f5e6ce/129ca5af/2990462d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eKotlin \n    \u003cdiv id=\"kotlin\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#kotlin\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eKotlin 是一种在 Java 虚拟机上运行的静态类型编程语言，被称之为 Android 世界的Swift，由 JetBrains 设计开发并开源。\u003c/p\u003e","title":"1、Kotlin语言概述","type":"posts"},{"content":" 泛型类 # 例如我们有一个类，负责统计分数，但是分数的类型不确定，可以是 String 的也可以是 Int 的，这个时候就需要使用泛型类。\nfun main() { var s1 = Score\u0026lt;Int\u0026gt;(\u0026#34;张三\u0026#34;,18,100) var s2 = Score\u0026lt;String\u0026gt;(\u0026#34;李四\u0026#34;,19,\u0026#34;优秀\u0026#34;) } class Score\u0026lt;T\u0026gt;(var name: String,var age: Int,var score: T) 泛型并不是每个类只能存在一个，我们可以一次性定义多个类型参数\nclass Test\u0026lt;K, V\u0026gt;(val key: K, val value: V) ","date":"2025-03-21","externalUrl":null,"permalink":"/posts/b3f5e6ce/1e30d624/d2940648/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e泛型类 \n    \u003cdiv id=\"泛型类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%b3%9b%e5%9e%8b%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e例如我们有一个类，负责统计分数，但是分数的类型不确定，可以是 String 的也可以是 Int 的，这个时候就需要使用泛型类。\u003c/p\u003e","title":"1、泛型","type":"posts"},{"content":" uni-app # uni-app 是一个使用Vue.js开发所有前端应用的框架，开发者编写一套代码，可发布到iOS、Android、Web（响应式）、以及各种小程序（微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝）、快应用等多个平台。\n创建项目 # 通过 HBuilder IDE 创建 # HBuilderX是通用的前端开发工具，但为uni-app做了特别强化。\nuni-app自带的模板有 默认的空项目模板、Hello uni-app 官方组件和API示例，还有一个重要模板是 uni ui项目模板，日常开发推荐使用该模板，已内置大量常用组件。\n通过 vue-cli 创建 # 创建 vue3 项目\n# js npx degit dcloudio/uni-preset-vue#vite my-vue3-project # ts npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project 创建 vue2 项目\nvue create -p dcloudio/uni-preset-vue my-project 跨端注意 # 每个端有每个端的特点，有的能被抹平，有的不可能被抹平。\n注意：跨端，不是把web的习惯迁移到全平台。而是按照uni的写法，然后全平台使用。\nuni-app组成和跨端原理 # 基本语言和开发规范 # uni-app代码编写，基本语言包括js、vue、css。以及ts、scss等css预编译器。\n在app端，还支持原生渲染的nvue，以及可以编译为kotlin和swift的uts。\nDCloud还提供了使用js编写服务器代码的uniCloud云引擎。所以只需掌握js，你可以开发web、Android、iOS、各家小程序以及服务器等全栈应用。\n为了实现多端兼容，综合考虑编译速度、运行性能等因素，uni-app 约定了如下开发规范：\n页面文件遵循Vue 单文件组件 (SFC) 规范，即每个页面是一个.vue文件 组件标签靠近小程序规范 接口能力（JS API）靠近小程序规范，但需将前缀 wx、my 等替换为 uni 数据绑定及事件处理同 Vue.js 规范，同时补充了应用生命周期及页面的生命周期 如需兼容app-nvue平台，建议使用flex布局进行开发 uni-app分编译器和运行时（runtime）。uni-app能实现一套代码、多端运行，是通过这2部分配合完成的。\n编译器将开发者的代码进行编译，编译的输出物由各个终端的runtime进行解析，每个平台（Web、Android App、iOS App、各家小程序）都有各自的runtime。\n编译器 # 编译器运行在电脑开发环境。一般是内置在HBuilderX工具中，也可以使用独立的cli版。 开发者按uni-app规范编写代码，由编译器将开发者的代码编译生成每个平台支持的特有代码 在web平台，将.vue文件编译为js代码。与普通的vue cli项目类似 在微信小程序平台，编译器将.vue文件拆分生成wxml、wxss、js等代码 在app平台，将.vue文件编译为js代码。进一步，如果涉及uts代码： 在Android平台，将.uts文件编译为kotlin代码 在iOS平台，将.uts文件编译为swift代码 编译器分vue2版和vue3版 vue2版：基于webpack实现 vue3版：基于Vite实现，性能更快 编译器支持条件编译，即可以指定某部分代码只编译到特定的终端平台。从而将公用和个性化融合在一个工程中。 // #ifdef App console.log(\u0026#34;这段代码只有在App平台才会被编译进去。非App平台编译后没有这段代码\u0026#34;) // #endif 运行时（runtime） # runtime不是运行在电脑开发环境，而是运行在真正的终端上。\nuni-app在每个平台（Web、Android App、iOS App、各家小程序）都有各自的runtime。这是一个比较庞大的工程。\n在小程序端，uni-app的runtime，主要是一个小程序版的vue runtime，页面路由、组件、api等方面基本都是转义。 在web端，uni-app的runtime相比普通的vue项目，多了一套ui库、页面路由框架、和uni对象（即常见API封装） 在App端，uni-app的runtime更复杂，可以先简单理解为DCloud也有一套小程序引擎，打包app时将开发者的代码和DCloud的小程序打包成了apk或ipa。当然，事实上DCloud确实有小程序引擎产品，供原生应用实现小程序化。 uni-app runtime包括3部分：基础框架、组件、API。\n基础框架： 包括语法、数据驱动、全局文件、应用管理、页面管理、js引擎、渲染和排版引擎等 在web和小程序上，不需要uni-app提供js引擎和排版引擎，直接使用浏览器和小程序的即可。但app上需要uni-app提供 App的js引擎：App-Android上，uni-app的js引擎是v8，App-iOS是jscore App的渲染引擎：同时提供了2套渲染引擎，.vue页面文件由webview渲染，原理与小程序相同；.nvue页面文件由原生渲染，原理与react native相同。开发者可以根据需要自主选择渲染引擎。 组件： runtime中包括的组件只有基础组件，如\u0026lt;view\u0026gt;、\u0026lt;button\u0026gt;等。扩展组件不包含在uni-app的runtime中，而是下载到用户的项目代码中。（这些组件都是vue组件） 为了降低开发者的学习成本，uni-app的内置基础组件命名规范与小程序基本相同。 这几十个组件不管在哪个平台，已被处理为均有一致表现。 在小程序端，uni-app基础组件会直接转义为小程序自己的内置组件。在小程序的runtime中不占体积。 在web和android、iOS端，这几十个组件都在uni-app的runtime中，会占用一定体积，相当于内置了一套ui库。 组件的扩展： 有了几十个基础组件，大多数扩展组件也都是基于这些基础组件封装的。比如官方提供的扩展ui库uni ui。 在web平台，for web的各种ui库（如elementUI）也可以使用，但这些库由于操作了dom，无法跨端在app和小程序中使用。 在App平台，uni-app也支持使用原生编程语言来自行扩展原生组件，比如原生的地图、ar等。 uni-app同时支持将微信自定义组件运行到微信小程序、web、app这3个平台。注意微信自定义组件不是vue组件。 API： uni-app runtime内置了大量常见的、跨端的API，比如联网(uni.request)、读取存储(uni.getStorage) 同时uni-app不限制各端原生平台的API调用。开发者可以在uni-app框架中无限制的调用该平台所有能使用的API。即，在小程序平台，小程序的所有API都可以使用；在web平台，浏览器的所有API都可使用；在iOS和Android平台，os的所有API都可以使用。 也就是说，使用uni-app的标准API，可以跨端使用。但对于不跨端的部分，仍可以调用该端的专有API。由于常见的API都已经被封装内置，所以日常开发时，开发者只需关注uni标准API，当需要调用特色端能力时在条件编译里编写特色API调用代码。 ext API：web和app的runtime体积不小，如果把小程序的所有API等内置进去会让开发者的最终应用体积变大。所以有部分不常用的API被剥离为ext API。虽然仍然是uni.开头，但需要单独下载插件到项目下 小程序平台：uni对象会转为小程序的自有对象，比如在微信小程序平台，编写uni.request等同于wx.request。那么所有wx.的API都可以这样使用。 web平台：window、dom等浏览器专用API仍可以使用 app平台：除了uni.的API，还可以使用plus.的API、Native.js，以及通过uts编写原生插件，或者使用java和objectC编写原生插件。这些原生插件调用os的API并封装给js使用。 由于历史沿革，DCloud在开发app时有：5+App、wap2app、uni-app等3种模式。这3种方式的runtime在底层能力上是公用的，所有uni-app可以调用5+（也就是plus.xxx）的API。虽然都可以使用5+的系统能力，但uni-app的逻辑层运行在js层，渲染层是webview和原生nvue双选。而5+不区分逻辑层和渲染层，全部运行在webview里，在性能上5+不及uni-app。 逻辑层和渲染层分离 # 在web平台，逻辑层（js）和渲染层（html、css），都运行在统一的webview里。\n但在小程序和app端，逻辑层和渲染层被分离了。\n分离的核心原因是性能。过去很多开发者吐槽基于webview的app性能不佳，很大原因是js运算和界面渲染抢资源导致的卡顿。\n不管小程序还是app，逻辑层都独立为了单独的js引擎，渲染层仍然是webview（app上也支持纯原生渲染）。\n所以注意小程序和app的逻辑层都不支持浏览器专用的window、dom等API。app只能在渲染层操作window、dom，即renderjs。\nuni-app 工程 # 一个 uni-app 工程，就是一个 Vue 项目。\nHbuilderX 1.9.0+ 支持在根目录创建 ext.json、sitemap.json 等小程序需要的文件。 ┌─uniCloud 云空间目录，支付宝小程序云为uniCloud-alipay，阿里云为uniCloud-aliyun，腾讯云为uniCloud-tcb（详见uniCloud） │─components 符合vue组件规范的uni-app组件目录 │ └─comp-a.vue 可复用的a组件 ├─utssdk 存放uts文件（已废弃） ├─pages 业务页面文件存放的目录 │ ├─index │ │ └─index.vue index页面 │ └─list │ └─list.vue list页面 ├─static 存放应用引用的本地静态资源（如图片、视频等）的目录，注意：静态资源都应存放于此目录 ├─uni_modules 存放uni_module 详见 ├─platforms 存放各平台专用页面的目录，详见 ├─nativeplugins App原生语言插件 详见 ├─nativeResources App端原生资源目录 │ ├─android Android原生资源目录 详见 | └─ios iOS原生资源目录 详见 ├─hybrid App端存放本地html文件的目录，详见 ├─wxcomponents 存放微信小程序、QQ小程序组件的目录，详见 ├─mycomponents 存放支付宝小程序组件的目录，详见 ├─swancomponents 存放百度小程序组件的目录，详见 ├─ttcomponents 存放抖音小程序、飞书小程序组件的目录，详见 ├─kscomponents 存放快手小程序组件的目录，详见 ├─jdcomponents 存放京东小程序组件的目录，详见 ├─unpackage 非工程代码，一般存放运行或发行的编译结果 ├─main.js Vue初始化入口文件 ├─App.vue 应用配置，用来配置App全局样式以及监听 应用生命周期 ├─pages.json 配置页面路由、导航条、选项卡等页面类信息，详见 ├─manifest.json 配置应用名称、appid、logo、版本等打包信息，详见 ├─AndroidManifest.xml Android原生应用清单文件 详见 ├─Info.plist iOS原生应用配置文件 详见 └─uni.scss 内置的常用样式变量 static目录 # 为什么需要static这样的目录？ uni-app编译器根据pages.json扫描需要编译的页面，并根据页面引入的js、css合并打包文件。 对于本地的图片、字体、视频、文件等资源，如果可以直接识别，那么也会把这些资源文件打包进去，但如果这些资源以变量的方式引用， 比如：\u0026lt;image :src=\u0026quot;url\u0026quot;\u0026gt;\u0026lt;/image\u0026gt;，甚至可能有更复杂的函数计算，此时编译器无法分析。\n那么有了static目录，编译器就会把这个目录整体复制到最终编译包内。这样只要运行时确实能获取到这个图片，就可以显示。\n当然这也带来一个注意事项，如果static里有一些没有使用的废文件，也会被打包到编译包里，造成体积变大。\n另外注意，static目录支持特殊的平台子目录，比如web、app、mp-weixin等，这些目录存放专有平台的文件，这些平台的文件在打包其他平台时不会被包含。\n非 static 目录下的文件（vue组件、js、css 等）只有被引用时，才会被打包编译。\ncss、less/scss 等资源不要放在 static 目录下，建议这些公用的资源放在自建的 common 目录下。\nstatic目录和App原生资源目录有关系吗？ uni-app支持App原生资源目录nativeResources，下面有assets、res等目录。但和static目录没有关系。\nstatic目录下的文件，在app第一次启动时，解压到了app的外部存储目录（external-path）。（uni-app x 从3.99+不再解压）\n所以注意控制static目录的大小，太大的static目录和太多文件，会造成App安装后第一次启动变慢。\n","date":"2025-03-17","externalUrl":null,"permalink":"/posts/bafd68f1/f2b2596e/3e8c7af6/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003euni-app \n    \u003cdiv id=\"uni-app\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#uni-app\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003euni-app\u003c/code\u003e 是一个使用Vue.js开发所有前端应用的框架，开发者编写一套代码，可发布到iOS、Android、Web（响应式）、以及各种小程序（微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝）、快应用等多个平台。\u003c/p\u003e","title":"1、uni-app","type":"posts"},{"content":" Spring Boot # 概念 # Spring Boot 基于 Spring 开发，Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能，只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说，它并不是用来替代 Spring 的解决方案，而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想，默认帮我们进行了很多设置，多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置（例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等），Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。\n特征 # 为所有Spring开发者更快的入门 开箱即用，提供各种默认配置来简化项目配置 内嵌式Servlet容器（Tomcat、Jetty 或者 Undertow）简化Web项目 没有冗余代码生成和XML配置的要求 Spring Initializr # Spring官方提供了Spring Initializr：https://start.spring.io/，帮助快速的搭建Spring Boot工程\nHello World # pom.xml # \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;project xmlns=\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u0026gt; \u0026lt;modelVersion\u0026gt;4.0.0\u0026lt;/modelVersion\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;springboot-demo\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;packaging\u0026gt;jar\u0026lt;/packaging\u0026gt; \u0026lt;!-- 一般情况下使用Springboot官方提供的启动器父依赖统一管理springboot各组件版本 --\u0026gt; \u0026lt;parent\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-parent\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.7.5\u0026lt;/version\u0026gt; \u0026lt;/parent\u0026gt; \u0026lt;properties\u0026gt; \u0026lt;maven.compiler.source\u0026gt;8\u0026lt;/maven.compiler.source\u0026gt; \u0026lt;maven.compiler.target\u0026gt;8\u0026lt;/maven.compiler.target\u0026gt; \u0026lt;/properties\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;!-- web场景启动器 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;build\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;!-- springboot打包插件 --\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/build\u0026gt; \u0026lt;/project\u0026gt; 启动类 # package top.ygang.springbootdemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ApplicationStart { public static void main(String[] args) { SpringApplication.run(ApplicationStart.class,args); } } 注意：启动类一般要放在工程的顶级目录中（相对于其他类）\nController # package top.ygang.springbootdemo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class DemoController { @GetMapping(\u0026#34;/helloworld\u0026#34;) public String helloworld(){ return \u0026#34;Hello Spring Boot!\u0026#34;; } } 访问 # spring-boot-starter-web的默认端口为：8080\nSpringBoot配置文件 # Springboot默认的配置文件名为：application.yml或application.properties\napplication.properties # #端口号 server.port=8080 #项目名，可以不写，默认localhost:8080就可以访问项目 server.servlet.context-path=/app #配置静态资源访问路径，resources文件夹下 spring.resources.static-locations=classpath:/templates/ #前缀后缀 spring.mvc.view.prefix=/ spring.mvc.view.suffix=.jsp #数据库连接池 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource # spring.datasource.driverClassName=org.postgresql.Driver # 初始化时建立物理连接的个数 spring.datasource.druid.initial-size=5 # 最小连接池数量 spring.datasource.druid.min-idle=5 # 最大连接池数量 spring.datasource.druid.max-active=20 # 获取连接时最大等待时间，单位毫秒 spring.datasource.druid.max-wait=60000 # 申请连接的时候检测，如果空闲时间大于timeBetweenEvictionRunsMillis，执行validationQuery检测连接是否有效。 spring.datasource.druid.test-while-idle=true # 既作为检测的间隔时间又作为testWhileIdel执行的依据 spring.datasource.druid.time-between-eviction-runs-millis=60000 # 销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时，关闭当前连接 spring.datasource.druid.min-evictable-idle-time-millis=30000 spring.datasource.url=jdbc:mysql://localhost:3306/mana?useUnicode=true\u0026amp;characterEncoding=utf8\u0026amp;serverTimezone=Asia/Shanghai\u0026amp;useSSL=false spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver #redis配置 spring.redis.host = localhost spring.redis.password = eduask spring.redis.port = 6379 #分页拦截器，为true先设定数据库中查询的范围，再根据条件查询 pagehelper.reasonable=false #mapper.xml扫描，如果mapper.xml和mapper.java在一个包里，那么注解MapperScan就可以了 mybatis.mapper-locations=classpath*:top.ygang.mapper/*.xml YML # 全名**：YAML Ain\u0026rsquo;t Markup Language**\n以数据为中心，比json、xml等更适合做配置文件\n基本语法 # key:[空格]value：表示一对键值对（空格必须有） 以**空格的缩进（不可以使用TAB缩进）**来控制层级关系，只要是左对齐的一列数据，都是同一个层级的 属性和值也是大小写敏感 值的写法 # 字面量 # 普通的值（数字，字符串，布尔） k: v：字面直接来写； 字符串默认不用加上单引号或者双引号 \u0026quot;\u0026quot;：双引号；不会转义字符串里面的特殊字符；特殊字符会作为本身想表示的意思 name: \u0026quot;zhangsan \\n lisi\u0026quot; 输出：zhangsan 换行 lisi ''：单引号；会转义特殊字符，特殊字符最终只是一个普通的字符串数据 name: 'zhangsan \\n lisi' 输出：zhangsan \\n lisi 对象、Map # 在下一行来写对象的属性和值的关系；注意缩进\nfriends: lastName: zhangsan age: 20 #行内写法： friends: {lastName: zhangsan,age: 18} 数组、集合（List、Set） # 用- value表示数组中的一个元素\npets: - cat - dog - pig application.yml文件 # #端口号 server: port: 8080 servlet: context-path: /app #配置thymeleaf的视图解析器ThymeleafViewResolver spring: thymeleaf: prefix: classpath:/templates/ suffix: .html mode: HTML5 cache: false encoding: UTF-8 #redis配置 redis: host: localhost password: eduask port: 6379 #数据库配置 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/manage?useUnicode=true\u0026amp;characterEncoding=utf8\u0026amp;serverTimezone=Asia/Shanghai\u0026amp;useSSL=false username: root password: 123456 #数据库连接池 druid: # 初始化时建立物理连接的个数 initial-size: 5 # 最小连接池数量 min-idle: 5 # 最大连接池数量 max-active: 20 # 获取连接时最大等待时间，单位毫秒 max-wait: 60000 # 申请连接的时候检测，如果空闲时间大于timeBetweenEvictionRunsMillis，执行validationQuery检测连接是否有效。 test-while-idle: true # 既作为检测的间隔时间又作为testWhileIdel执行的依据 time-between-eviction-runs-millis: 60000 #销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时，关闭当前连接 min-evictable-idle-time-millis: 30000 servlet: #文件上传配置 multipart: enabled: true #单个文件的大小 max-file-size: 20MB #限制总上传文件的大小 max-request-size: 20MB #分页拦截器 pagehelper: reasonable: false #日志 logging: level: root: info top.ygang: debug org.springframework.security: warn 区分开发和生产环境配置文件 # 在resource下新建三个配置文件：application.yml（通用），application-dev.yml（开发），application-prod.yml（生产）\n使用方式1，在application.yml中指定当前使用的配置文件类型\nspring: profiles: active: dev 使用方式2，在jar包启动的使用，添加命令，指定使用的配置文件类型\njava -jar test.jar --spring.profiles.active=dev 读取配置文件的信息 # 1、读取单个参数 # spring: application: name: test 在java bean代码中读取name属性值，使用@Value注解\n@Value(\u0026#34;${spring.application.name}\u0026#34;) private String name; 2、读取同一节点下的多个属性（对象） # minio: endpoint: localhost:9000 accesskey: minioadmin secretkey: minioadmin 使用@ConfigurationProperties的配置类用于读取配置文件，在spring容器启动时，就会在yml中读取\n@Component @Data @ConfigurationProperties(prefix = \u0026#34;minio\u0026#34;) public class MinioConfig{ private String endpoint; private String accesskey; private String secretkey; } spring-boot-configuration-processor # 有的时候，我们自己开发一个模块，希望我们的Bean对应的配置文件项会有提示，例如\n就可以引入该依赖，搭配注解@ConfigurationProperties使用\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-configuration-processor\u0026lt;/artifactId\u0026gt; \u0026lt;optional\u0026gt;true\u0026lt;/optional\u0026gt; \u0026lt;/dependency\u0026gt; 引入后，要求Bean必须添加@Component注解，并且属性必须有public的getter、setter，例如\n@ConfigurationProperties(prefix = \u0026#34;student\u0026#34;) @Component public class Student { /** * 编号 */ private String id = \u0026#34;001\u0026#34;; /** * 姓名 */ private String name = \u0026#34;lucy\u0026#34;; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } 然后就可以build project，会在target/classes/META-INF目录下生成spring-configuration-metadata.json文件，里面有对改配置项的说明\n{ \u0026#34;groups\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;student\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;top.ygang.springbootdemo.Student\u0026#34;, \u0026#34;sourceType\u0026#34;: \u0026#34;top.ygang.springbootdemo.Student\u0026#34; } ], \u0026#34;properties\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;student.id\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;java.lang.String\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;编号\u0026#34;, \u0026#34;sourceType\u0026#34;: \u0026#34;top.ygang.springbootdemo.Student\u0026#34; }, { \u0026#34;name\u0026#34;: \u0026#34;student.name\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;java.lang.String\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;姓名\u0026#34;, \u0026#34;sourceType\u0026#34;: \u0026#34;top.ygang.springbootdemo.Student\u0026#34; } ], \u0026#34;hints\u0026#34;: [] } 场景启动器 # 解决的问题 # 在SpringBoot出现之前，如果我们想使用SpringMVC来构建我们的web项目，必须要做的几件事情如下：\n首先项目中需要引入SpringMVC的依赖 在web.xml中注册SpringMVC的 DispatcherServlet，并配置url映射 编写 springmcv-servlet.xml，在其中配置SpringMVC中几个重要的组件，处理映射器（HandlerMapping）、处理适配器（HandlerAdapter）、视图解析器（ViewResolver） 在 applicationcontext.xml文件中引入 springmvc-servlet.xml文件 … 以上这几步只是配置好了SpringMVC，如果我们还需要与数据库进行交互，就要在application.xml中配置数据库连接池DataSource，如果需要数据库事务，还需要配置TransactionManager…\n这就是使用Spring框架开发项目带来的一些的问题：\n依赖导入问题： 每个项目都需要来单独维护自己的依赖，在项目中使用到什么功能就需要引入什么样的依赖。手动导入依赖容易出错，且无法统一集中管理 配置繁琐： 在引入依赖之后需要做繁杂的配置，并且这些配置是每个项目来说都是必要的，例如web.xml配置（Listener配置、Filter配置、Servlet配置）、log4j配置、数据库连接池配置等等。这些配置重复且繁杂，在不同的项目中需要进行多次重复开发，这在很大程度上降低了我们的开发效率 而在SpringBoot出现之后，它为我们提供了一个强大的功能来解决上述的两个痛点，这就是SpringBoot的starters（场景启动器）。\nSpring Boot通过将我们常用的功能场景抽取出来，做成的一系列场景启动器，这些启动器帮我们导入了实现各个功能所需要依赖的全部组件，我们只需要在项目中引入这些starters，相关场景的所有依赖就会全部被导入进来，并且我们可以抛弃繁杂的配置，仅需要通过配置文件来进行少量的配置就可以使用相应的功能。\n原理 # 在导入的starter之后，SpringBoot主要帮我们完成了两件事情\n相关组件的自动导入 相关组件的自动配置 SpringApplication.run() # SpringApplication.run()方法主要完成了以下功能：\n创建SpringApplication对象；在对象初始化时保存事件监听器，容器初始化类以及判断是否为web应用，保存包含main方法的主配置类。 调用run方法；准备spring的上下文，完成容器的初始化，创建，加载等。会在不同的时机触发监听器的不同事件。 @SpringBootApplication # 这个注解作为一个Springboot工程的开始，也是Springboot的核心注解，它有如下两个核心的元注解\n@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication {} @SpringBootConfiguration # 这个注解有一个元注解@Configuration，也就是标志了这个类是一个配置类，并且注入Spring容器进行管理\n@EnableAutoConfiguration # 这个注解是Springboot开启自动配置的核心注解\n@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = \u0026#34;spring.boot.enableautoconfiguration\u0026#34;; Class\u0026lt;?\u0026gt;[] exclude() default {}; String[] excludeName() default {}; } @AutoConfigurationPackage的作用是将main包下的所有组件注册进容器 @Import({AutoConfigurationImportSelector.class})的作用是加载自动装配类：AutoConfigurationImportSelector，自动装配的核心功能也就是通过这个类实现的 AutoConfigurationImportSelector有一个关键的方法如下\nprotected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { //第1步：判断自动装配开关是否打开 if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } //第2步：用于获取注解中的exclude和excludeName。 //获取注解属性 AnnotationAttributes attributes = getAttributes(annotationMetadata); //第3步：获取需要自动装配的所有配置类，读取META-INF/spring.factories //读取所有预配置类 List\u0026lt;String\u0026gt; configurations = getCandidateConfigurations(annotationMetadata, attributes); //第4步：符合条件加载 //去掉重复的配置类 configurations = removeDuplicates(configurations); //执行 Set\u0026lt;String\u0026gt; exclusions = getExclusions(annotationMetadata, attributes); //校验 checkExcludedClasses(configurations, exclusions); //删除 configurations.removeAll(exclusions); //过滤 configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); //创建自动配置的对象 return new AutoConfigurationEntry(configurations, exclusions); } 第一步判断自动装配开关是否打开：默认spring.boot.enableautoconfiguration = true，可在application.properties或application.yml中设置\nspring.factories中为Spring官方提供的所有自动配置类的候选列表，不光是这个依赖下的META-INF/spring.factories被读取到，所有Spring Boot Starter下的META-INF/spring.factories都会被读取到。\n这些自动配置类并不是一定会生效，而是要符合一定的条件，而条件就是根据自动配置类上的@ConditionOnXXX来判断，只有所有注解都满足条件，则这个类才会生效。\nSpring Boot 提供的条件注解如下：\n@ConditionalOnBean：当容器里有指定 Bean 的条件下 @ConditionalOnMissingBean：当容器里没有指定 Bean 的情况下 @ConditionalOnSingleCandidate：当指定 Bean 在容器中只有一个，或者虽然有多个但是指定首选 Bean @ConditionalOnClass：当类路径下有指定类的条件下 @ConditionalOnMissingClass：当类路径下没有指定类的条件下 @ConditionalOnProperty：指定的属性是否有指定的值 @ConditionalOnResource：类路径是否有指定的值 @ConditionalOnExpression：基于 SpEL 表达式作为判断条件 @ConditionalOnJava：基于 Java 版本作为判断条件 @ConditionalOnJndi：在 JNDI 存在的条件下差在指定的位置 @ConditionalOnNotWebApplication：当前项目不是Web项目的条件下 @ConditionalOnWebApplication：当前项目是Web项目的条件下 可以在Springboot配置文件中加入debug: true来开启调试日志，确定有哪些AutoConfiguration生效，日志信息主要包括以下\nPositive matches：自动配置类启用的 Negative matches：没有启动 Exclusions：被排除的 Unconditional classes：没有限定条件的 SpringBoot启动大概流程 # Application.run()，完成对Spring容器的初始化，并且开启启动类注解的功能@SpringBootApplication，最核心的就是自动装配功能 当SpringBoot启动后，如果spring.boot.enableautoconfiguration = true，则开始执行自动装配 扫描所有classpath://META-INF/spring.factories，获取所有自动配置类 根据自动配置类（AutoConfiguration）上的注解@ConditionalXXX过滤出需要加载的自动配置类，并进行加载 需要加载的自动配置类注入Spring容器后，开始根据类上的注解例如：@EnableConfigurationProperties、@ComponentScan，读取配置文件、加载Bean等，完成模块功能 自定义场景启动器 # 命名规范 # 官方定义的场景启动器命名格式为：spring-boot-starter-xxx，例如spring-boot-starter-web\n自定义或第三方的场景启动器命名格式为：xxx-spring-boot-starter，例如mybatis-spring-boot-starter\n自定义接口监听模块 # 该自定义模块要实现引入后自动在控制台输出接口调用情况\n1、创建maven项目mylistener-spring-boot-starter\n2、引入依赖\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;project xmlns=\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u0026gt; \u0026lt;modelVersion\u0026gt;4.0.0\u0026lt;/modelVersion\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mylistener-spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;packaging\u0026gt;jar\u0026lt;/packaging\u0026gt; \u0026lt;parent\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-parent\u0026lt;/artifactId\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;version\u0026gt;2.2.0.RELEASE\u0026lt;/version\u0026gt; \u0026lt;/parent\u0026gt; \u0026lt;properties\u0026gt; \u0026lt;maven.compiler.source\u0026gt;8\u0026lt;/maven.compiler.source\u0026gt; \u0026lt;maven.compiler.target\u0026gt;8\u0026lt;/maven.compiler.target\u0026gt; \u0026lt;/properties\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;!-- 包含很多与自动配置相关的注解的定义，必须要引入 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-autoconfigure\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- 编写自定义配置类需要 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-configuration-processor\u0026lt;/artifactId\u0026gt; \u0026lt;optional\u0026gt;true\u0026lt;/optional\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- 功能需要 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-aop\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;/project\u0026gt; 3、创建配置类读取配置文件\n@Component @ConfigurationProperties(prefix = \u0026#34;mylistener\u0026#34;) public class MylistenerProperties { /** * 输出日期格式 */ private String dateformat = \u0026#34;yyyy-MM-dd HH:mm:ss\u0026#34;; public String getDateformat() { return dateformat; } public void setDateformat(String dateformat) { this.dateformat = dateformat; } } 4、创建切面类，监听所有GET方法\n@Aspect @Component public class GetAdvice { public GetAdvice() { System.out.println(\u0026#34;GetAdvice加载进入IOC容器！！！\u0026#34;); } @Autowired private MylistenerProperties mylistenerProperties; @Pointcut(\u0026#34;@annotation(org.springframework.web.bind.annotation.GetMapping)\u0026#34;) public void pc(){} @Before(\u0026#34;pc()\u0026#34;) public void before(JoinPoint jp){ SimpleDateFormat dateFormat = new SimpleDateFormat(mylistenerProperties.getDateformat()); MethodSignature signature = (MethodSignature) jp.getSignature(); Method method = signature.getMethod(); System.out.println(dateFormat.format(new Date()) + \u0026#34; 方法：\u0026#34; + method.getName() + \u0026#34;被执行！！！\u0026#34;); } } 5、创建自动装配类，完成自动装配\n@Configuration // 限定自动装配类生效条件：当为Web应用时加载 @ConditionalOnWebApplication // 加载配置文件，并注入Bean到容器 @EnableConfigurationProperties({ MylistenerProperties.class }) // 扫描所有切面，注入Spring容器 @ComponentScan(\u0026#34;top.ygang.mylistener.advice\u0026#34;) public class MylistenerAutoConfiguration { } 6、在resources目录下创建META-INF/spring.factories文件，使SpringBoot可以找到自动装配类\norg.springframework.boot.autoconfigure.EnableAutoConfiguration=\\ top.ygang.mylistener.MylistenerAutoConfiguration 7、打包、安装：mvn clean install\n8、在上面的Hello World项目中，引入模块组件\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mylistener-spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 9、启动项目，由于已经在配置文件中配置了：debug: true，所以可以看到所有AutoConfiguration的加载情况，可以看到模块组件已经被加载\n10、调用接口http://localhost:8080/helloworld，看到控制台输出\n常见问题 # mybatis无法找到xml映射文件 # 此时分为两种情况，一种是xml映射文件没有和mapper接口放在一起，而是单独放在别的目录，例如resouces下，可以通过mybatis配置项进行配置\nmybatis: mapper-locations: classpath*:/mapper/**/*.xml 另一种情况是，SpringBoot项目打jar包后，没有将xml文件打进jar，可以通过pom.xml配置\n\u0026lt;build\u0026gt; \u0026lt;resources\u0026gt; \u0026lt;resource\u0026gt; \u0026lt;directory\u0026gt;src/main/java\u0026lt;/directory\u0026gt; \u0026lt;includes\u0026gt; \u0026lt;include\u0026gt;**/*.xml\u0026lt;/include\u0026gt; \u0026lt;/includes\u0026gt; \u0026lt;/resource\u0026gt; \u0026lt;resource\u0026gt; \u0026lt;directory\u0026gt;src/main/resources\u0026lt;/directory\u0026gt; \u0026lt;/resource\u0026gt; \u0026lt;/resources\u0026gt; \u0026lt;/build\u0026gt; 配置文件中密码加密 # Spring Boot中使用ENC（Environment-Neutral Configuration）主要是为了将配置信息从应用程序代码中分离出来，以提高安全性和可维护性。ENC的主要优点包括：\n安全性增强: 敏感信息（如数据库密码、API密钥等）不应硬编码在代码中，而是应该使用加密的方式存储在配置文件中，然后通过ENC进行解密和使用，从而减少泄露风险。 可维护性: 将配置信息与代码分离，使得配置可以独立地修改和管理，而不需要重新编译和部署应用程序。这样可以降低维护成本，并使应用程序更易于管理。 灵活性: 使用ENC可以根据不同的环境（开发、测试、生产等）提供不同的配置，而不需要修改应用程序代码，从而提高了部署的灵活性和可移植性。 pmo.xml依赖\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.github.ulisesbocchio\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jasypt-spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.0.5\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 配置加密盐值\njasypt: encryptor: # password值任意，最好随机字符 password: hhX4FzbwcT 工具类使用\nimport org.jasypt.encryption.pbe.StandardPBEStringEncryptor; public class JasyptTest { /** * 加密盐值，使用完成后进行删除，或者不能提交到`生产环境`，比如： */ private final static String PASSWORD = \u0026#34;hhX4FzbwcT\u0026#34;; public static void main(String[] args) { PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); SimpleStringPBEConfig config = new SimpleStringPBEConfig(); // 用于设置加密密钥。密钥是用于加密和解密字符串的关键信息。 config.setPassword(PASSWORD); // 加密算法的名称,jasypt-3.0.5版本后默认的加密方式 config.setAlgorithm(\u0026#34;PBEWITHHMACSHA512ANDAES_256\u0026#34;); // 用于设置加密时迭代次数的数量，增加迭代次数可以使攻击者更难进行密码破解。 config.setKeyObtentionIterations(\u0026#34;1000\u0026#34;); // 加密器池的大小。池是一组加密器实例，可确保加密操作的并发性。 config.setPoolSize(\u0026#34;1\u0026#34;); // 用于设置JCE（Java Cryptography Extension）提供程序的名称。 config.setProviderName(\u0026#34;SunJCE\u0026#34;); // 用于设置生成盐的类名称。在此配置中，我们使用了org.jasypt.salt.RandomSaltGenerator，表示使用随机生成的盐。 config.setSaltGeneratorClassName(\u0026#34;org.jasypt.salt.RandomSaltGenerator\u0026#34;); // 用于设置Jasypt使用的初始化向量（IV）生成器的类名。初始化向量是在加密过程中使用的一个固定长度的随机数，用于加密数据块，使每个数据块的加密结果都是唯一的。在此配置中，我们使用了org.jasypt.iv.RandomIvGenerator类，该类是一个随机生成器，用于生成实时随机IV的实例。这样可以确保每次加密的IV都是唯一的，从而增加加密强度。 config.setIvGeneratorClassName(\u0026#34;org.jasypt.iv.RandomIvGenerator\u0026#34;); // 指定加密输出类型。在此配置中，我们选择了base64输出类型。 config.setStringOutputType(\u0026#34;base64\u0026#34;); encryptor.setConfig(config); // 明文1 String name_encrypt = \u0026#34;root\u0026#34;; // 明文2 String password_encrypt = \u0026#34;123456\u0026#34;; // 明文加密 String encrypt1 = encryptor.encrypt(name_encrypt); String encrypt2 = encryptor.encrypt(password_encrypt); System.out.println(\u0026#34;明文加密1：\u0026#34; + encrypt1); System.out.println(\u0026#34;明文加密2：\u0026#34; + encrypt2); // 密文解密 String decrypt1 = encryptor.decrypt(encrypt1); String decrypt2 = encryptor.decrypt(encrypt2); System.out.println(\u0026#34;密文解密1：\u0026#34; + decrypt1); System.out.println(\u0026#34;密文解密2：\u0026#34; + decrypt2); } } 加密配置使用\nsys: name: ENC(Yt36hceu3xGXEzrz2jCPjvalaXQ5yIHE04SVT6lIkcktrxqtBZrlivkAkA9/9oZ2) password: ENC(0Ci6irPOko9IG+hBZJAGoguIuE52gF/XiigCV4DwLm6NfkoyvV4Etgc9FzKK3MYl) 测试\n@RestController public class TestController { @Value(\u0026#34;${sys.name}\u0026#34;) private String name; @Value(\u0026#34;${sys.password}\u0026#34;) private String password; @GetMapping(\u0026#34;/test\u0026#34;) public void test() { System.out.println(\u0026#34;name = \u0026#34; + name); System.out.println(\u0026#34;password = \u0026#34; + password); } } 原理\n1、在jasypt-spring-boot-starter包的自动配置类JasyptSpringBootAutoConfiguration中通过@Import注解引入了EnableEncryptablePropertiesConfiguration配置类，该类中environment参数储存了yaml文件元数据；\n2、在postProcessBeanFactory方法中，调用environment.getPropertySources()方法获取yaml配置项目；\n3、通过convertPropertySources进行数据转换，修改yaml中配置项值，具体实现为调用instantiatePropertySource()方法，在该方法中，匹配propertySource instanceof MapPropertySource，转化为EncryptableMapPropertySourceWrapper对象；\n4、EncryptableMapPropertySourceWapper类中，通过getProperty(name)方法进行匹配，匹配成功后调用resolver.resolvePropertyValue进行ENC配置的解密并且替换原有的propertySource值；\n","date":"2025-02-12","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/4f5b7444/435b8349/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eSpring Boot \n    \u003cdiv id=\"spring-boot\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#spring-boot\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e概念 \n    \u003cdiv id=\"概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003eSpring Boot 基于 Spring 开发，Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能，只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说，它并不是用来替代 Spring 的解决方案，而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以\u003cstrong\u003e约定大于配置的核心思想\u003c/strong\u003e，默认帮我们进行了很多设置，多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置（例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等），Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。\u003c/p\u003e","title":"1、SpringBoot","type":"posts"},{"content":" 数据库概述 # 数据库分为关系型数据库，非关系型数据库\n关系型数据库：MySQL，SqlServer，Oracle，DB2，pgSQL\n非关系数据库：MongDB，Redis，Hbase，ElasticSearch\n关系型数据库的缺陷 # 关系型数据库关注于表与表之间，数据与数据之间的关系 关系型数据库在查询数据时，效率相对而言比较低下（从硬盘中读取数据） 关系型数据库表在设计的时候，结构化太固定，太单一（数据类型固定） 关系型数据库在动态修改表结构，数据结构时是非常麻烦的（代价非常高昂） 非关系型数据库的优势 # 性能较高，主要体现在它几乎都是直接从内存中获取数据 key - value结构简单，变化难度较小，只需要修改value的结构即可 数据类型丰富，既可以支持字符串，也可以支持对象，还可以支持视频，图片，音频 关系型数据库和非关系型数据库的区别（面试题） # 关系型数据库，它的结构是以表的方式进行存储，数据是以行，列的方式进行展示，它更关注的表和表，数据和数据之间的关系；非关系型数据库，它的结构大多都是以及k-v结构的方式来进行存储，数据没有固定的结构，它更多的是关注数据的存储 关系型数据库的数据通常是存储在硬盘中的文件，存储数据都是在操作该文件；而非关系型数据库的数据，通常都是存储在内存中的，当然，它们也有它们自己的持久化机制 Redis的概述 # Redis属于非关系型数据库（NoSQL）。它们不保证关系数据的ACID特性。 Redis是一个基于key-value，基于内存的，可以持久化的数据库，支持分布式，而且Redis是单线程的（避免了线程切换对CPU的消耗），而且是基于NIO的 NoSQL != NOT SQL，而是NOT Only SQL 系统中需要具备维护关系的关系数据库，也要有专门维护数据的非关系数据库 非关系型数据库实际上是对关系型的一种补充 Redis的优势 # 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 丰富的数据类型 – Redis支持二进制格式的 String, List, Hash, Set 及 Ordered Set数据类型等操作 原子性 – Redis的所有操作都是原子性的，意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务，即原子性，通过MULTI和EXEC指令包起来 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性 数据类型 # 此处的数据类型说的是value的数据，key的类型始终为String\n基础数据类型 # String List Set Zset（有序集合） Hash 扩展数据类型 # GEO（地理信息定位） Bitmaps（位图） HypeLogLog Redis的使用场景 # 做为应用程序的状态服务器，例如：将JWT所产生的令牌进行存储，session 做为高热点数据的缓存服务器，例如：淘宝：产品类型，微博：明星发布的动态 做系统分析后的数据的存储服务器，例如：报表数据，游戏（战力榜，等级榜，充值榜……） 具体场景 # 缓存，毫无疑问这是Redis当今最为人熟知的使用场景。再提升服务器性能方面非常有效 排行榜，如果使用传统的关系型数据库来做这个事儿，非常的麻烦，而利用Redis的SortSet数据结构能够非常方便搞定 计算器/限速器，利用Redis中原子性的自增操作，我们可以统计类似用户点赞数、用户访问数等，这类操作如果用MySQL，频繁的读写会带来相当大的压力；限速器比较典型的使用场景是限制某个用户访问某个API的频率，常用的有抢购时，防止用户疯狂点击带来不必要的压力 好友关系，利用集合的一些命令，比如求交集、并集、差集等。可以方便搞定一些共同好友、共同爱好之类的功能 简单消息队列，除了Redis自身的发布/订阅模式，我们也可以利用List来实现一个队列机制，比如：到货通知、邮件发送之类的需求，不需要高可靠，但是会带来非常大的DB压力，完全可以用List来完成异步解耦 Session共享，以PHP为例，默认Session是保存在服务器的文件中，如果是集群服务，同一个用户过来可能落在不同机器上，这就会导致用户频繁登陆；采用Redis保存Session后，无论用户落在那台机器上都能够获取到对应的Session信息 电商秒杀场景，优惠劵抢购场景 发送短信验证码（服务器存储验证码，用户提交收到的验证码，进行比对） ","date":"2024-12-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/caadfbf9/c5ba7f15/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e数据库概述 \n    \u003cdiv id=\"数据库概述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%95%b0%e6%8d%ae%e5%ba%93%e6%a6%82%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e数据库分为关系型数据库，非关系型数据库\u003c/p\u003e","title":"1、Redis","type":"posts"},{"content":" 加密方式 # 对称加密：客户端和服务器共用同一个密钥，可以用于加密一段内容，可以用于解密这段内容。 优点：加解密效率高。 缺点：安全性方面可能存在一些问题，因为密钥存放在客户端有被窃取的风险 对称加密的代表算法有：AES、DES等。 非对称加密：它将密钥分成了两种（公钥和私钥）。公钥通常存放在客户端，私钥通常存放在服务器。使用公钥加密的数据只有用私钥才能解密，反过来使用私钥加密的数据也只有用公钥才能解密。 优点：是安全性更高，因为客户端发送给服务器的加密信息只有用服务器的私钥才能解密，因此不用担心被别人破解。 缺点：加解密的效率相比于对称加密要差很多。 非对称加密的代表算法有：RSA、ElGamal等。 HTTPS # HTTPS （全称：Hypertext Transfer Protocol Secure），是以安全为目标的 HTTP 通道，在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性。HTTPS 在HTTP 的基础下加入SSL，HTTPS 的安全基础是 SSL，因此加密的详细内容就需要 SSL。 HTTPS 存在不同于 HTTP 的默认端口及一个加密/身份验证层（在 HTTP与TCP之间）。这个系统提供了身份验证与加密通讯方法。它被广泛用于万维网上安全敏感的通讯，例如交易支付等方面。\n相关概念 # key：一般指私钥 CSR（Certificate Signing Request）：即证书签名申请，这不是证书，这是要求CA给证书签名的一种正式申请，该申请包含申请证书的实体的公钥及持有人的国家、邮件、域名等信息。该数据将成为证书的一部分。CSR始终使用它携带的公钥所对应的私钥进行签名。 CRT、CER（Certificate）：证书 PEM：证书或私钥的Base64文本存储格式，可以单独存放证书或私钥，也可以同时存放证书和私钥 DER：证书或私钥的二进制存储格式，可以单独存放证书或私钥，也可以同时存放证书和私钥 JKS：JAVA的keytools证书工具支持的证书和私钥格式，它包含了公钥和私钥，并且可以通过keytool工具来将公钥和私钥导出。 HTTPS的理解思路 # 普通HTTP协议流程如下：\n没有任何加密手段，网站发送的消息hello经过中间路由被篡改为world\n如果使用对称加密流程如下：\n此时有个问题，浏览器和服务器之间在第一次通信的时候，怎么共享密钥A，必定需要明文传递，容易被中间路由捕获\n如果引入非对称加密，私钥保存在服务器、公钥发送给浏览器，浏览器再使用公钥对对称加密密钥A进行加密后发送给服务器，服务器使用私钥进行解密，即可获取本次会话的密钥A，流程如下：\n但是这样的话，如果浏览器获取服务器首次发送的公钥被掉包，那么就会引起问题\n此时就引出了，CA机构，CA机构专门用于给各个网站签发数字证书，保证浏览器可以安全地获得各个网站的公钥。\n从CA机构获取数据就安全了吗，不会被掉包吗？\n其实，世界上的网站是无限多的，而CA机构总共就那么几家。任何正版操作系统都会将所有主流CA机构的公钥内置到操作系统当中，所以我们不用额外获取，解密时只需遍历系统中所有内置的CA机构的公钥，只要有任何一个公钥能够正常解密出数据，就说明它是合法的。\nhttps流程 # ","date":"2024-12-09","externalUrl":null,"permalink":"/posts/b18f3aef/7b41ff88/2eac8e74/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e加密方式 \n    \u003cdiv id=\"加密方式\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8a%a0%e5%af%86%e6%96%b9%e5%bc%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e对称加密\u003c/strong\u003e：客户端和服务器共用同一个密钥，可以用于加密一段内容，可以用于解密这段内容。\n\u003cul\u003e\n\u003cli\u003e优点：加解密效率高。\u003c/li\u003e\n\u003cli\u003e缺点：安全性方面可能存在一些问题，因为密钥存放在客户端有被窃取的风险\u003c/li\u003e\n\u003cli\u003e对称加密的代表算法有：AES、DES等。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e非对称加密\u003c/strong\u003e：它将密钥分成了两种（公钥和私钥）。公钥通常存放在客户端，私钥通常存放在服务器。使用公钥加密的数据只有用私钥才能解密，反过来使用私钥加密的数据也只有用公钥才能解密。\n\u003cul\u003e\n\u003cli\u003e优点：是安全性更高，因为客户端发送给服务器的加密信息只有用服务器的私钥才能解密，因此不用担心被别人破解。\u003c/li\u003e\n\u003cli\u003e缺点：加解密的效率相比于对称加密要差很多。\u003c/li\u003e\n\u003cli\u003e非对称加密的代表算法有：RSA、ElGamal等。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eHTTPS \n    \u003cdiv id=\"https\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#https\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eHTTPS （全称：Hypertext Transfer Protocol Secure），是以安全为目标的 HTTP 通道，在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性。HTTPS 在HTTP 的基础下加入SSL，HTTPS 的安全基础是 SSL，因此加密的详细内容就需要 SSL。 HTTPS 存在不同于 HTTP 的默认端口及一个加密/身份验证层（在 HTTP与TCP之间）。这个系统提供了身份验证与加密通讯方法。它被广泛用于万维网上安全敏感的通讯，例如交易支付等方面。\u003c/p\u003e","title":"1、HTTPS","type":"posts"},{"content":" Swagger在线文档技术 # 现在来讲，最主流的开发模式：前后端分离 前端编写页面，以及完成后端接口的调用 后端：提供数据接口，以及需要提交接口所对应的接口文档 参照的示例 # 文档，要么是以网站的方式，或者以Word文档的方式存在，一定不是以“嘴遁”的方式存在。但是这就对后端程序员，增加了额外的工作量\n在线文档 # 后端人员只需在Java代码写点注解，对应的框架就可以自动生成相关的接口文档描述 Swagger就是这样的技术 使用 # 依赖 # \u0026lt;!--接口文档自动生成--\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;io.springfox\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;springfox-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.0.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 配置文件 # swagger: enable: true application-name: ${spring.application.name} application-version: 1.0.0 application-description: 项目描述 name: === url: === email: === 配置类 # /** * @描述 * @创建人 yhgh * @创建时间 2023/11/3 10:08 */ @Configuration @Data @ConfigurationProperties(\u0026#34;swagger\u0026#34;) // 读取配置文件 @EnableOpenApi // 启动OpenApi public class SwaggerConfiguration { /** * 是否开启swagger，生产环境一般关闭，所以这里定义一个变量 */ private Boolean enable; /** * 项目应用名 */ private String applicationName; /** * 项目版本信息 */ private String applicationVersion; /** * 项目描述信息 */ private String applicationDescription; /** * 姓名 */ private String name; /** * 网址 */ private String url; /** * 邮箱 */ private String email; @Bean public Docket docket() { return new Docket(DocumentationType.OAS_30) .pathMapping(\u0026#34;/\u0026#34;) // 定义是否开启swagger，false为关闭，可以通过变量控制，线上关闭 .enable(enable) //配置api文档元信息 .apiInfo(apiInfo()) // 选择哪些接口作为swagger的doc发布 .select() // .apis() 控制哪些接口暴露给swagger， // RequestHandlerSelectors.any() 所有都暴露 // RequestHandlerSelectors.basePackage(\u0026#34;top.ygang.*\u0026#34;) 指定包位置 // withMethodAnnotation(ApiOperation.class) 标记有这个注解ApiOperation .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title(applicationName) .description(applicationDescription) .contact(new Contact(\u0026#34;测试swagger\u0026#34;, \u0026#34;https://www.baidu.com\u0026#34;, \u0026#34;444012836@qq.com\u0026#34;)) .version(applicationVersion) .build(); } } 注解 # //对类声明 @Api(value = \u0026#34;测试接口\u0026#34;, tags = \u0026#34;用户管理相关的接口\u0026#34;, description = \u0026#34;用户测试接口\u0026#34;) //对方法声明 @ApiOperation(value = \u0026#34;添加用户\u0026#34;, notes = \u0026#34;添加用户\u0026#34;) //对参数声明 @ApiImplicitParam(name = \u0026#34;s\u0026#34;, value = \u0026#34;新增用户数据\u0026#34;) //多参数声明 @ApiImplicitParams({ @ApiImplicitParam(name = \u0026#34;emp\u0026#34;, value = \u0026#34;人员对象，需要有账号和密码\u0026#34;), @ApiImplicitParam(name = \u0026#34;1111\u0026#34;, value = \u0026#34;人员对象，需要有账号和密码\u0026#34;) }) 跨域问题 # @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(\u0026#34;swagger-ui.html\u0026#34;) .addResourceLocations(\u0026#34;classpath:/META-INF/resources/\u0026#34;); registry.addResourceHandler(\u0026#34;/webjars/**\u0026#34;) .addResourceLocations(\u0026#34;classpath:/META-INF/resources/webjars/\u0026#34;); } ","date":"2024-12-05","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/63e90e89/9b62ffad/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eSwagger在线文档技术 \n    \u003cdiv id=\"swagger在线文档技术\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#swagger%e5%9c%a8%e7%ba%bf%e6%96%87%e6%a1%a3%e6%8a%80%e6%9c%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e现在来讲，最主流的开发模式：前后端分离\u003c/li\u003e\n\u003cli\u003e前端编写页面，以及完成后端接口的调用\u003c/li\u003e\n\u003cli\u003e后端：提供数据接口，以及需要提交接口所对应的接口文档\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003e参照的示例 \n    \u003cdiv id=\"参照的示例\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8f%82%e7%85%a7%e7%9a%84%e7%a4%ba%e4%be%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"202109181345597\"\n    data-zoom-src=\"/posts/3ab7256e/3f5635d6/63e90e89/9b62ffad/image/202109181345597.png\"\n    src=\"/posts/3ab7256e/3f5635d6/63e90e89/9b62ffad/image/202109181345597.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"1、Swagger","type":"posts"},{"content":" 问题 # 有一个Java的服务部署在Windows Server 2012R2上面，发现程序中的定时任务莫名其妙的就停止执行了，但是系统没有任何报错信息。\n重启服务后可以暂时恢复这个问题。\n在关闭服务的时候，按Ctrl + C，发现程序会快速输出很多日志，并且定时任务恢复执行，于是判断该问题出现在CMD的这个窗口上。\n解决方法 # 在CMD的设置中，将快速编辑模式取消勾选即可。\n","date":"2024-11-20","externalUrl":null,"permalink":"/posts/cdbc5e74/7b52924b/6e55ec87/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e问题 \n    \u003cdiv id=\"问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e有一个Java的服务部署在Windows Server 2012R2上面，发现程序中的定时任务莫名其妙的就停止执行了，但是系统没有任何报错信息。\u003c/p\u003e","title":"1、WindowsServer快速编辑模式导致程序阻塞","type":"posts"},{"content":" 隐藏文件 # 显示隐藏文件command+shift+.，或者使用如下命令，可以切换系统默认显示隐藏文件\n# 默认显示隐藏文件 defaults write com.apple.finder AppleShowAllFiles -bool true # 默认不显示隐藏文件 defaults write com.apple.finder AppleShowAllFiles -bool false ","date":"2024-11-06","externalUrl":null,"permalink":"/posts/26654e7b/5a80d994/67d83da3/","section":"文章","summary":"\u003ch3 class=\"relative group\"\u003e隐藏文件 \n    \u003cdiv id=\"隐藏文件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%9a%90%e8%97%8f%e6%96%87%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e显示隐藏文件\u003ccode\u003ecommand+shift+.\u003c/code\u003e，或者使用如下命令，可以切换系统默认显示隐藏文件\u003c/p\u003e","title":"1、MacOS","type":"posts"},{"content":" Mybatis # Mybatis是一个优秀的基于java的持久层框架，它内部封装了jdbc，使开发者只需要关注sql语句本身，而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。 MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation迁移到了google code，并且改名为MyBatis 。2013年11月迁移到Github。 iBATIS一词来源于internet和abatis的组合，是一个基于Java的持久层框架。 iBATIS提供的持久层框架包括SQL Maps和Data Access Objects（DAO） 基于ORM（对象关系映射）思想的一个框架 MyBatis 是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解，将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。 官网地址：https://blog.mybatis.org/ 优点 # 简单易学：本身就很小且简单。没有任何第三方依赖，最简单安装只要两个jar文件+配置几个sql映射文件；易于学习，易于使用，通过文档和源代码，可以比较完全的掌握它的设计思路和实现 灵活：mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里，便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求 解除sql与程序代码的耦合：通过提供DAO层，将业务逻辑和数据访问逻辑分离，使系统的设计更清晰，更易维护，更易单元测试。sql和代码的分离，提高了可维护性 提供映射标签，支持对象与数据库的orm字段关系映射 提供对象关系映射标签，支持对象关系组建维护 提供xml标签，支持编写动态sql 缺点 # 编写sql语句工作量大，尤其字段多，关联表多 sql语句依赖于数据库，导致数据库一致性差，不可以更换数据库 框架还是比较简陋，功能尚有缺失，虽然简化了数据绑定代码，但是整个底层数据库查询实际还是要自己写，工作量大，而且不太容易适应快速数据库修改 二级缓存机制不佳 简单使用 # 1、导入mybatis依赖 # \u0026lt;!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.mybatis\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mybatis\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.5.11\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 2、导入目标数据库的对应驱动 # 目前使用的是mysql5.7.28\n\u0026lt;!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;mysql\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mysql-connector-java\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;5.1.49\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 3、创建表 # CREATE TABLE `student` ( `obj_id` varchar(70) NOT NULL COMMENT \u0026#39;主键\u0026#39;, `name` varchar(255) DEFAULT NULL COMMENT \u0026#39;姓名\u0026#39;, `age` int(11) DEFAULT NULL COMMENT \u0026#39;年龄\u0026#39;, PRIMARY KEY (`obj_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 4、创建表对应实体类 # 注意：一般放在entity包下，体现该类为数据表实体\n一般命名要求：表名和实体类类名相同，实体类首字母大写；表字段名和属性名相同\npublic class Student { private String obj_id; private String name; private String age; // getter、setter } 5、编写持久层接口 # 注意：一般放在dao包下，体现该接口为数据表持久层接口\npublic interface StudentDao { /** * 查询全部学生 * @return */ List\u0026lt;Student\u0026gt; selectAll(); } 6、编写映射xml文件 # 注意：一般放在mapper包中，体现是持久层接口的映射文件；如果是Maven项目，一般放在resources/mapper包中。文件名一般与持久层接口名保持一致，例如：StudentDao.xml\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; ?\u0026gt; \u0026lt;!DOCTYPE mapper PUBLIC \u0026#34;-//mybatis.org//DTD Mapper 3.0//EN\u0026#34; \u0026#34;http://mybatis.org/dtd/mybatis-3-mapper.dtd\u0026#34;\u0026gt; \u0026lt;!--namespace用于区别不同映射文件的SQL语句，必须是唯一的，一般是对应持久层接口的全限定名--\u0026gt; \u0026lt;mapper namespace=\u0026#34;top.ygang.mybatisdemo.dao.StudentDao\u0026#34;\u0026gt; \u0026lt;!-- id作为SQL语句的唯一标识，对应接口中的方法名 --\u0026gt; \u0026lt;!-- resultType：返回对象的类全名 --\u0026gt; \u0026lt;select id=\u0026#34;selectAll\u0026#34; resultType=\u0026#34;top.ygang.mybatisdemo.entity.Student\u0026#34;\u0026gt; select * from student \u0026lt;/select\u0026gt; \u0026lt;/mapper\u0026gt; 7、创建核心配置文件 # 命名固定为：mybatis-config.xml。并且放在src目录；如果是Maven项目，放在resources目录\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; ?\u0026gt; \u0026lt;!DOCTYPE configuration PUBLIC \u0026#34;-//mybatis.org//DTD Config 3.0//EN\u0026#34; \u0026#34;http://mybatis.org/dtd/mybatis-3-config.dtd\u0026#34;\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;settings\u0026gt; \u0026lt;!-- 日志输入类，使用默认的标准输出 --\u0026gt; \u0026lt;setting name=\u0026#34;logImpl\u0026#34; value=\u0026#34;STDOUT_LOGGING\u0026#34;/\u0026gt; \u0026lt;/settings\u0026gt; \u0026lt;!-- 配置环境，可以包含多个环境，default属性值指定使用哪个环境节点配置 --\u0026gt; \u0026lt;environments default=\u0026#34;dev\u0026#34;\u0026gt; \u0026lt;!-- 环境节点的配置 --\u0026gt; \u0026lt;environment id=\u0026#34;dev\u0026#34;\u0026gt; \u0026lt;!-- 事务管理器 --\u0026gt; \u0026lt;transactionManager type=\u0026#34;JDBC\u0026#34;/\u0026gt; \u0026lt;!-- 数据源，type：设置数据源的类型，pooled为使用默认连接池 --\u0026gt; \u0026lt;dataSource type=\u0026#34;POOLED\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;driver\u0026#34; value=\u0026#34;com.mysql.jdbc.Driver\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;url\u0026#34; value=\u0026#34;jdbc:mysql://localhost:3306/test\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;username\u0026#34; value=\u0026#34;root\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;password\u0026#34; value=\u0026#34;123456\u0026#34;/\u0026gt; \u0026lt;/dataSource\u0026gt; \u0026lt;/environment\u0026gt; \u0026lt;/environments\u0026gt; \u0026lt;!-- 管理sql映射文件 --\u0026gt; \u0026lt;mappers\u0026gt; \u0026lt;!-- 持久层接口映射文件路径 --\u0026gt; \u0026lt;mapper resource=\u0026#34;mapper/StudentDao.xml\u0026#34;/\u0026gt; \u0026lt;/mappers\u0026gt; \u0026lt;/configuration\u0026gt; 7、进行使用 # public class Start { public static void main(String[] args) throws Exception{ // 读取核心配置文件 InputStream inputStream = Resources.getResourceAsStream(\u0026#34;mybatis-config.xml\u0026#34;); // 由mybatis读核心配置文件,将读到的的信息缓存在SqlSessionFactory工厂对象中 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 从工厂中获取SqlSession对象，该对象可以进行数据库交互 SqlSession sqlSession = sqlSessionFactory.openSession(); // 获取持久层接口实现类对象 StudentDao studentDao = sqlSession.getMapper(StudentDao.class); // 调用方法，执行sql List\u0026lt;Student\u0026gt; students = studentDao.selectAll(); System.out.println(students); // 关闭资源，释放数据库链接 sqlSession.close(); } } 少量SQL # 在少量简单SQL的情况下，我们并不需要特别声明一个持久层接口的映射文件，我们可以使用mybatis提供的注解@Select，@Update，@Delete，@Insert直接在持久层接口上面进行绑定sql。\n例如上面的例子，我们的StudentDao接口中只有一个sql，而且简单，我们就不需要写StudentDao.xml ，而是将StudentDao修改下\npublic interface StudentDao { /** * 查询全部学生 * @return */ @Select(\u0026#34;select * from student\u0026#34;) List\u0026lt;Student\u0026gt; selectAll(); } 由于我们没有了StudentDao.xml，所以核心配置文件也需要改一下\n\u0026lt;mappers\u0026gt; \u0026lt;!-- 持久层接口映射文件路径 --\u0026gt; \u0026lt;!-- \u0026lt;mapper resource=\u0026#34;mapper/StudentDao.xml\u0026#34;/\u0026gt; --\u0026gt; \u0026lt;!-- 持久层接口全限定名 --\u0026gt; \u0026lt;mapper class=\u0026#34;top.ygang.mybatisdemo.dao.StudentDao\u0026#34;/\u0026gt; \u0026lt;/mappers\u0026gt; 参数的获取 # 在MyBatis中，我们可以使用#{}或${}来获取接口形参，二者的区别在于\n#{}：是预编译，底层调用的是PreparedStatement，会在预处理之前把参数部分用一个占位符?替换，可以防止SQL注入，适用于值的操作 ${}：底层调用的是Statement，只是简单的字符串替换，SQL语句拼接，不能防止SQL注入，对于字符串的部分需要加单引号，如${a}，如果拼接的是表名，列名，关键字，那么使用此方式最合适 单参数 # 接口只有一个参数的情况下，我们直接可以使用#{fieldName}获取\n//接口 GoodType findById(int id); \u0026lt;!--xml映射文件--\u0026gt; \u0026lt;select id=\u0026#34;findById\u0026#34;\u0026gt; select * from goodtype where id=#{id} \u0026lt;/select\u0026gt; 多参数 # 方式一：通过在xml映射文件标签中参数以arg0-argn按照形参的顺序\n//接口 boolean update1(String name,int id); \u0026lt;!--xml映射文件--\u0026gt; \u0026lt;update id=\u0026#34;update1\u0026#34;\u0026gt; update goodbyte set tname=#{arg0} where id=#{arg1} \u0026lt;/update\u0026gt; 方式二：通过在xml映射文件标签中参数以param1-paramn按照形参的顺序\n//接口 boolean update2(String name,int id); \u0026lt;!--xml映射文件--\u0026gt; \u0026lt;update id=\u0026#34;update2\u0026#34;\u0026gt; update goodbyte set tname=#{param1} where id=#{param2} \u0026lt;/update\u0026gt; 方式三：采用在接口形参前添加注解@Param的方式**（常用）**\n//接口 boolean update3(@Param(\u0026#34;na\u0026#34;)String name,@Param(\u0026#34;i\u0026#34;)int id); \u0026lt;!--xml映射文件--\u0026gt; \u0026lt;update id=\u0026#34;update3\u0026#34;\u0026gt; update goodbyte set tname=#{n} where id=#{i} \u0026lt;/update\u0026gt; 方式四：如果多个参数是同一个自定义对象的属性，使用对象\n//接口 boolean update4(GoodType goodType); \u0026lt;!--xml映射文件，#{xx}：xx为属性名--\u0026gt; \u0026lt;update id=\u0026#34;update4\u0026#34;\u0026gt; update goodbyte set tname=#{tname} where id=#{tid} \u0026lt;/update\u0026gt; 方式五：使用Map集合的方式。缺点是，在编写sql语句的时候，无法确定map集合的key\n//接口 boolean update5(Map\u0026lt;String,Object\u0026gt; map); \u0026lt;!--xml映射文件,#{xx}：xx为map集合中对应的key--\u0026gt; \u0026lt;update id=\u0026#34;update5\u0026#34;\u0026gt; update goodbyte set tname=#{key1} where id=#{key2} \u0026lt;/update\u0026gt; 核心配置文件 # 这个顺序是mybatis-config.xml中，configuration的子标签声明顺序，固定不可变\nproperties settings typeAliases typeHandlers objectFactory objectWrapperFactory reflectorFactory plugins environments databaseIdProvider mappers \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; ?\u0026gt; \u0026lt;!DOCTYPE configuration PUBLIC \u0026#34;-//mybatis.org//DTD Config 3.0//EN\u0026#34; \u0026#34;http://mybatis.org/dtd/mybatis-3-config.dtd\u0026#34;\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;!-- The content of element type \u0026#34;configuration\u0026#34; must match \u0026#34;(properties?,settings?,typeAliases?,typeHandlers?, objectFactory?,objectWrapperFactory?, reflectorFactory?,plugins?,environments?, databaseIdProvider?,mappers?)\u0026#34;. --\u0026gt; \u0026lt;!--引入properties文件，此后就可以在当前文件中使用${}的方式访问value--\u0026gt; \u0026lt;properties resource=\u0026#34;jdbc.properties\u0026#34;\u0026gt;\u0026lt;/properties\u0026gt; \u0026lt;!--日志实现类，STDOUT_LOGGING为默认标准输出日志--\u0026gt; \u0026lt;setting name=\u0026#34;logImpl\u0026#34; value=\u0026#34;STDOUT_LOGGING\u0026#34;/\u0026gt; \u0026lt;!-- typeAliases:设置类型别名，即为某个具体的类型（全类名）设置一个别名 在MyBatis的范围中，就可以使用别名表示一个具体的类型 --\u0026gt; \u0026lt;typeAliases\u0026gt; \u0026lt;!-- typeAlias标签属性： type:设置需要起别名的标签 alias:设置某个类型的别名 （有默认的别名，默认别名就是类名（不是全类名噢），且不区分大小写） --\u0026gt; \u0026lt;!--\u0026lt;typeAlias type=\u0026#34;top.ygang.entity.Student\u0026#34; alias=\u0026#34;Student\u0026#34;\u0026gt;\u0026lt;/typeAlias\u0026gt;--\u0026gt; \u0026lt;!--通过包设置类型别名，指定包下所有的类型将全部拥有默认的别名，即类名且不区分大小写--\u0026gt; \u0026lt;!-- \u0026lt;package name=\u0026#34;top.ygang.entity\u0026#34;/\u0026gt; --\u0026gt; \u0026lt;/typeAliases\u0026gt; \u0026lt;!-- environments:配置连接数据库的环境 属性： default:设置默认使用环境的id --\u0026gt; \u0026lt;environments default=\u0026#34;development\u0026#34;\u0026gt; \u0026lt;!-- environment:设置一个具体的连接数据库的环境 属性: id:设置环境的唯一标识，不能重复 --\u0026gt; \u0026lt;environment id=\u0026#34;development\u0026#34;\u0026gt; \u0026lt;!-- transactionManager:设置事务管理器 属性: type:设置事务的方式 type=\u0026#34;JDBC|MANAGED\u0026#34; JDBC:表示使用JDBC中原生的事务管理方式 MANAGED:被管理，例如Spring，mybatis的事务都交给Spring来管理 --\u0026gt; \u0026lt;transactionManager type=\u0026#34;JDBC\u0026#34;/\u0026gt; \u0026lt;!-- dataSource：设置数据源 属性： type:设置数据源的类型 type:\u0026#34;POOLED|UNPOOLED|JNDI\u0026#34; POOLED:表示使用数据库连接池 UNPOOLED:不使用数据库连接池 JNDI:使用上下文中的数据源 --\u0026gt; \u0026lt;dataSource type=\u0026#34;POOLED\u0026#34;\u0026gt; \u0026lt;!--\u0026lt;property name=\u0026#34;driver\u0026#34; value=\u0026#34;com.mysql.jdbc.Driver\u0026#34;/\u0026gt;--\u0026gt; \u0026lt;!--\u0026lt;property name=\u0026#34;url\u0026#34; value=\u0026#34;jdbc:mysql://localhost:3306/test?serverTimezone=UTC\u0026#34;/\u0026gt;--\u0026gt; \u0026lt;!--\u0026lt;property name=\u0026#34;username\u0026#34; value=\u0026#34;root\u0026#34;/\u0026gt;--\u0026gt; \u0026lt;!--\u0026lt;property name=\u0026#34;password\u0026#34; value=\u0026#34;123456\u0026#34;/\u0026gt;--\u0026gt; \u0026lt;!--配置信息通过${}读取jdbc.properties中的配置信息进行替换--\u0026gt; \u0026lt;property name=\u0026#34;driver\u0026#34; value=\u0026#34;${jdbc.driver}\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;url\u0026#34; value=\u0026#34;${jdbc.url}\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;username\u0026#34; value=\u0026#34;${jdbc.username}\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;password\u0026#34; value=\u0026#34;${jdbc.password}\u0026#34;/\u0026gt; \u0026lt;/dataSource\u0026gt; \u0026lt;/environment\u0026gt; \u0026lt;/environments\u0026gt; \u0026lt;!--引入映射文件--\u0026gt; \u0026lt;mappers\u0026gt; \u0026lt;!--\u0026lt;mapper resource=\u0026#34;mappers/UserMapper.xml\u0026#34;/\u0026gt;--\u0026gt; \u0026lt;!-- 以包的方式引入映射文件，但是必须满足两个条件： 1、mapper接口和映射文件所在的包必须保持一致 2、mapper接口的名字必须和映射文件的名字保持一致 --\u0026gt; \u0026lt;!-- \u0026lt;package name=\u0026#34;com.atguigu.mybatis.mapper\u0026#34;/\u0026gt; --\u0026gt; \u0026lt;/mappers\u0026gt; \u0026lt;/configuration\u0026gt; 配置log4j # 1、引入依赖\n\u0026lt;!-- https://mvnrepository.com/artifact/log4j/log4j --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;log4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;log4j\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.17\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 2、配置日志实现\n\u0026lt;setting name=\u0026#34;logImpl\u0026#34; value=\u0026#34;LOG4J\u0026#34;/\u0026gt; 3、CLASSPATH下添加log4j.properties\nTypeHandler # 无论是 MyBatis 在预处理语句（PreparedStatement）中设置一个参数时，还是从结果集中取出一个值时， 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。\n默认的TypeHandlers # 自定义类型转换器 # 例如现在表里的某个字段是int类型，我们需要将他转换成Boolean类型。当大于等于0时为true；小于0时为false，我们就需要自定义类型转换器\n1、继承BaseTypeHandler，并且使用注解@MappedJdbcTypes指定该转换器处理的数据库字段类型；使用@MappedTypes指明该转换器处理的Java类型\n// DB字段类型 @MappedJdbcTypes(JdbcType.INTEGER) // Java类型 @MappedTypes(Boolean.class) public class MyTypeHandler extends BaseTypeHandler\u0026lt;Boolean\u0026gt; { /** * 增删改调用，从Java类型转DB类型 */ @Override public void setNonNullParameter(PreparedStatement preparedStatement, int columnIndex, Boolean aBoolean, JdbcType jdbcType) throws SQLException { preparedStatement.setObject(columnIndex,aBoolean ? 1 : -1); } /** * 查询调用，从DB类型转Java类型 */ @Override public Boolean getNullableResult(ResultSet resultSet, String columnName) throws SQLException { int val = resultSet.getInt(columnName); return val \u0026gt;= 0; } /** * 查询调用，从DB类型转Java类型 */ @Override public Boolean getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException { int val = resultSet.getInt(columnIndex); return val \u0026gt;= 0; } /** * 查询调用，从DB类型转Java类型 */ @Override public Boolean getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException { int val = callableStatement.getInt(columnIndex); return val \u0026gt;= 0; } } 配置转换器 # 全局配置 # 这种配置方法将会在整个项目中生效\n\u0026lt;typeHandlers\u0026gt; \u0026lt;typeHandler handler=\u0026#34;top.ygang.mybatisdemo.MyTypeHandler\u0026#34;/\u0026gt; \u0026lt;/typeHandlers\u0026gt; 局部配置 # 对于查询，可以在resultMap的result标签中配置\n\u0026lt;resultMap id=\u0026#34;result_map\u0026#34; type=\u0026#34;top.ygang.mybatisdemo.entity.Student\u0026#34;\u0026gt; \u0026lt;result typeHandler=\u0026#34;top.ygang.mybatisdemo.MyTypeHandler\u0026#34; column=\u0026#34;age\u0026#34; property=\u0026#34;age\u0026#34;/\u0026gt; \u0026lt;/resultMap\u0026gt; 对于增删改\n可以在insert、delete、update标签中配置\nupdate student set name = \u0026#39;tom\u0026#39; where obj_id = #{id,typeHandler=top.ygang.mybatisdemo.MyTypeHandler} MaBatis映射文件中的标签 # MyBatis加载 # MyBatis根据对关联对象查询的select语句的执行时机，分为三种类型：直接加载（默认）、侵入式延迟加载、深度延迟加载。\n懒加载（延迟加载） # 就是按需加载，我们需要什么的时候再去进行什么操作。而且先从单表查询，需要时再从关联表去关联查询，能大大提高数据库性能，因为查询单表要比关联查询多张表速度要快。\n要求 # 1、必须是分布查询\n2、关联关系使用association（对一）或collection（对多）\n侵入式延迟加载 # 执行主加载对象的查询时，不会执行对关联对象的查询，只有在访问主加载对象详情时或关联对象详情时，才会执行关联对象的查询\n\u0026lt;!--在setting节点中加入--\u0026gt; \u0026lt;setting name=\u0026#34;lazyLoadingEnabled\u0026#34; value=\u0026#34;true\u0026#34;/\u0026gt; \u0026lt;setting name=\u0026#34;aggressiveLazyLoading\u0026#34; value=\u0026#34;true\u0026#34;/\u0026gt; 深度延迟加载（推荐） # 执行主加载对象的查询时，不会执行对关联对象的查询，访问主加载对象详情时也不会执行对关联对象的查询，只有在访问关联对象的详情时，才会执行关联对象的查询\n\u0026lt;!--在setting节点中加入--\u0026gt; \u0026lt;setting name=\u0026#34;lazyLoadingEnabled\u0026#34; value=\u0026#34;true\u0026#34;/\u0026gt; \u0026lt;setting name=\u0026#34;aggressiveLazyLoading\u0026#34; value=\u0026#34;false\u0026#34;/\u0026gt; 开启延迟 # （可选）在\u0026lt;association\u0026gt;或\u0026lt;collection\u0026gt;中添加**fetchType = \u0026ldquo;lazy\u0026rdquo;**属性开启延迟，但这里默认为开启，可以不加\n缓存 # 引入缓存之后，第一次去查询数据库，第二次以后，如果数据库的数据没有发生改变，我们就不去查询数据库，我们查缓存。使用缓存之后，就减少了查询数据库的次数，从而提高了查询效率\nmybatis中的缓存可以分为一级缓存和二级缓存\n哪些数据使用放在缓存中，哪些数据不适合放在缓存中 # 1）不频繁更新的数据可以放在缓存中，频繁发生更新的数据，不要放在缓存中\n2）财务数据不要放在缓存中，不要的数据可以放在缓存中\n一级缓存 # 一级缓存mybatis自带，不要额外配置，属于sqlSession级别的缓存（和SqlSession相关的），默认开启\n验证 # Goods find1 = goodsDaoMapper.findByGid(1); System.out.println(find1.getDname()); System.out.println(\u0026#34;**********************************\u0026#34;); Goods find2 = goodsDaoMapper.findByGid(1); System.out.println(find2.getDname()); 结果日志\nLogging initialized using \u0026#39;class org.apache.ibatis.logging.stdout.StdOutImpl\u0026#39; adapter. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. Opening JDBC Connection Created connection 798244209. Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2f943d71] ==\u0026gt; Preparing: select * from goods where gid=? ==\u0026gt; Parameters: 1(Integer) \u0026lt;== Columns: gid, dname, buyprice, saleprice, remarks, tid, picPath, quantity, state \u0026lt;== Row: 1, java, 50, 80, 11111, 1, img/1.jpg, 1000, 1 \u0026lt;== Total: 1 java ********************************** java Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2f943d71] Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@2f943d71] Returned connection 798244209 to pool. 两次相同的查询，只向数据库发送了一次SQL语句，说明，第二次查询是从缓存中查询，并不是从数据库查询\n一级缓存失效 # 情况一 # 第一次查询的数据和第二次查询的数据不一样\n情况二 # 如果查询的是同样的数据，调用了更新方法（增删改DML）的方法\n情况三 # 调用了刷新方法\nsqlSession.clearCache();\n情况四 # SqlSession对象不一样\n情况五 # （select标签中）flushCache=\u0026quot;true\u0026quot;属性\n二级缓存 # 如果系统需要二级缓存，那么需要人为配置，二级别缓存属于Mapper级别、SqlSessionFactory级别的缓存\n将查询出的数据保存在二级缓存中需要通过调用sqlSession的commit方法才可以进行保存在二级缓存\n主要用来解决一级缓存不能跨会话（SqlSession）共享的问题，范围是namespace 级别的，可以被多个SqlSession 共享（只要是同一个接口里面的相同方法，都可以共享），生命周期和应用同步\n如果你的MyBatis使用了二级缓存，并且你的Mapper和select语句也配置使用了二级缓存，那么在执行select查询的时候，MyBatis会先从二级缓存中取输入，其次才是一级缓存，即MyBatis查询数据的顺序是：二级缓存 —\u0026gt; 一级缓存 —\u0026gt; 数据库。\n配置二级缓存的步骤 # 1、在核心配置文件的\u0026lt;setting\u0026gt;节点中添加\n\u0026lt;setting name=\u0026#34;cacheEnabled\u0026#34; value=\u0026#34;true\u0026#34;\u0026gt;\u0026lt;/setting\u0026gt; 2、在xml映射文件中，配置\n\u0026lt;!-- 可选属性： type=\u0026#34;哪种缓存机制\u0026#34; size=\u0026#34;缓存的资源数量\u0026#34; eviction=\u0026#34;缓存回收策略\u0026#34; flushInterval=\u0026#34;刷新的间隔\u0026#34; readOnly=\u0026#34;只读true\\false\u0026#34; true:会给所有的查询的用户，返回同一个的java对象 false:会给每一个查询的用户，返回同一个java对象的克隆的副本，也就是不同的对象 --\u0026gt; \u0026lt;cache\u0026gt;\u0026lt;/cache\u0026gt; 3、封装类实现序列化接口java.io.Serializable\n要求 # 1、二级缓存是将对象通过序列化流存储到磁盘文件，需要封装类实现序列化，如果是多表联合查询，那么关联表对应的实体类也需要序列化\n2、两个SqlSession对象必须是同一个SqlSessionFactory创建\n验证 # SqlSession sqlSession1 = factory.openSession(); SqlSession sqlSession2 = factory.openSession(); GoodsDaoMapper mapper1 = sqlSession1.getMapper(GoodsDaoMapper.class); GoodsDaoMapper mapper2 = sqlSession2.getMapper(GoodsDaoMapper.class); Goods byGid1 = mapper1.findByGid(1); System.out.println(byGid1.getDname()); sqlSession1.commit(); System.out.println(\u0026#34;***********************************\u0026#34;); Goods byGid2 = mapper2.findByGid(1); System.out.println(byGid2.getDname()); sqlSession1.close(); sqlSession2.close(); 结果日志\nLogging initialized using \u0026#39;class org.apache.ibatis.logging.stdout.StdOutImpl\u0026#39; adapter. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. Cache Hit Ratio [dao.GoodsDaoMapper]: 0.0 Opening JDBC Connection Created connection 1297149880. Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4d50efb8] ==\u0026gt; Preparing: select * from goods where gid=? ==\u0026gt; Parameters: 1(Integer) \u0026lt;== Columns: gid, dname, buyprice, saleprice, remarks, tid, picPath, quantity, state \u0026lt;== Row: 1, java, 50, 80, 11111, 1, img/1.jpg, 1000, 1 \u0026lt;== Total: 1 java *********************************** Cache Hit Ratio [dao.GoodsDaoMapper]: 0.5 java Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4d50efb8] Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4d50efb8] Returned connection 1297149880 to pool. 同一SqlSessionFactory对象，所产生的不同的SqlSession对象，也可以共享缓存\n缓存穿透 # 缓存穿透是指缓存和数据库中都没有的数据，而用户不断发起请求，如发起为id为“-1”的数据或id为特别大不存在的数据查询。这时的用户很可能是攻击者，攻击会导致数据库压力过大。\n解决方法 # 1、使用Redis替换mybatis缓存，即使查询出来的数据为null，也会存入缓存\n2、使用布隆过滤器，对一些不存在的条件进行过滤\n缓存击穿 # 缓存击穿是指缓存中没有但数据库中有的数据（一般是缓存时间到期），这时由于并发用户特别多，同时读缓存没读到数据，又同时去数据库去取数据，引起数据库压力瞬间增大，造成过大压力\n解决方法 # 1、设置热点数据缓存永不过期\n2、利用代理或装饰模式，对mybatis源码进行切入，对数据库查询进行加锁\n3、使用mybatis的拦截器，对源码中的query方法进行加锁\n缓存雪崩 # 缓存雪崩是指缓存中数据大批量到过期时间，而查询数据量巨大，引起数据库压力过大甚至down机。和缓存击穿不同的是， 缓存击穿指并发查同一条数据，缓存雪崩是不同数据都过期了，很多数据都查不到从而查数据库。\n解决方法 # 1、缓存数据的过期时间设置随机，防止同一时间大量数据过期现象发生。\n2、如果缓存数据库是分布式部署，将热点数据均匀分布在不同的缓存数据库中。\n3、设置热点数据永远不过期\n","date":"2024-11-05","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/79c2bc57/7f965dec/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eMybatis \n    \u003cdiv id=\"mybatis\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#mybatis\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eMybatis是一个优秀的基于java的持久层框架，它内部封装了jdbc，使开发者只需要关注sql语句本身，而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。\u003c/li\u003e\n\u003cli\u003eMyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation迁移到了google code，并且改名为MyBatis 。2013年11月迁移到Github。\u003c/li\u003e\n\u003cli\u003eiBATIS一词来源于\u003ccode\u003einternet\u003c/code\u003e和\u003ccode\u003eabatis\u003c/code\u003e的组合，是一个基于Java的持久层框架。\u003c/li\u003e\n\u003cli\u003eiBATIS提供的持久层框架包括SQL Maps和Data Access Objects（DAO）\u003c/li\u003e\n\u003cli\u003e基于ORM（对象关系映射）思想的一个框架\u003c/li\u003e\n\u003cli\u003eMyBatis 是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解，将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。\u003c/li\u003e\n\u003cli\u003e官网地址：https://blog.mybatis.org/\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003e优点 \n    \u003cdiv id=\"优点\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bc%98%e7%82%b9\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e简单易学\u003c/strong\u003e：本身就很小且简单。没有任何第三方依赖，最简单安装只要两个jar文件+配置几个sql映射文件；易于学习，易于使用，通过文档和源代码，可以比较完全的掌握它的设计思路和实现\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e灵活\u003c/strong\u003e：mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里，便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e解除sql与程序代码的耦合\u003c/strong\u003e：通过提供DAO层，将业务逻辑和数据访问逻辑分离，使系统的设计更清晰，更易维护，更易单元测试。sql和代码的分离，提高了可维护性\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e提供映射标签\u003c/strong\u003e，支持对象与数据库的orm字段关系映射\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e提供对象关系映射标签\u003c/strong\u003e，支持对象关系组建维护\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e提供xml标签\u003c/strong\u003e，支持编写动态sql\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003e缺点 \n    \u003cdiv id=\"缺点\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%bc%ba%e7%82%b9\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e编写sql语句工作量大\u003c/strong\u003e，尤其字段多，关联表多\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003esql语句依赖于数据库\u003c/strong\u003e，导致数据库一致性差，不可以更换数据库\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e框架还是比较简陋，功能尚有缺失\u003c/strong\u003e，虽然简化了数据绑定代码，但是整个底层数据库查询实际还是要自己写，工作量大，而且不太容易适应快速数据库修改\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e二级缓存机制不佳\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003e简单使用 \n    \u003cdiv id=\"简单使用\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%ae%80%e5%8d%95%e4%bd%bf%e7%94%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003e1、导入mybatis依赖 \n    \u003cdiv id=\"1导入mybatis依赖\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1%e5%af%bc%e5%85%a5mybatis%e4%be%9d%e8%b5%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.mybatis\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003emybatis\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e3.5.11\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e2、导入目标数据库的对应驱动 \n    \u003cdiv id=\"2导入目标数据库的对应驱动\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#2%e5%af%bc%e5%85%a5%e7%9b%ae%e6%a0%87%e6%95%b0%e6%8d%ae%e5%ba%93%e7%9a%84%e5%af%b9%e5%ba%94%e9%a9%b1%e5%8a%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cp\u003e目前使用的是\u003ccode\u003emysql5.7.28\u003c/code\u003e\u003c/p\u003e","title":"1、Mybatis","type":"posts"},{"content":" JVM的空间 # JVM有一个称为垃圾回收器的低级线程，这个线程在后台不断地运行，自动寻找在Java程序中不再被使用的对象，并释放这些对象的内存。这种内存回收的过程被称为垃圾回收（Garbage Collection）\n堆的内存分配： 堆是按照代为单位进行分配，具体分配为：新生代，老年代，持久代（元空间） 所以现在的堆：新生代，老年代 空间比例 # 新生代中空间比例：Eden:form:to = 8:1:1\n新生代和老年代的比例：young:old = 1:2\n垃圾回收的目的 # 为了清理内存空间，给新生的对象腾位置 防止内存溢出和内存泄露 垃圾回收的算法 # 判断是否可回收的算法 # 引用计数器：引用计数是垃圾收集器中的早期策略。在这种方法中，堆中每个对象实例都有一个引用计数。当一个对象被创建时，就将该对象实例分配给一个变量，该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时，计数加1（a = b,则b引用的对象实例的计数器+1），但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时，对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时，它引用的任何对象实例的引用计数器减1。缺点就是无法检测出循环引用。如父对象有一个对子对象的引用，子对象反过来引用父对象。这样，他们的引用计数永远不可能为0。 可达性算法：可达性分析算法是从离散数学中的图论引入的，程序把所有的引用关系看作一张图，从一个节点GC ROOT开始，寻找对应的引用节点，找到这个节点以后，继续寻找这个节点的引用节点，当所有的引用节点寻找完毕之后，剩余的节点则被认为是没有被引用到的节点，即无用的节点，无用的节点将会被判定为是可回收的对象。 回收算法 # 标记-清除算法：是最基础的算法，分为“标记”和“清除”两个阶段：首先标记出所有需要回收的对象，在标记完成后统一回收掉所有被标记的对象。它主要由两个缺点：一个是效率问题，标记和清除过程的效率都不高；另一个是空间问题，标记清除之后会产生大量不连续的内存碎片，空间碎片太多可能会导致当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。 标记-整理算法：标记无用对象，让所有存活的对象都向一端移动，然后直接清除掉端边界以外的内存 复制算法：就是将对象在转移时，copy复制一份，同样要记录复制次数 分代算法：这个算法就是使用上面三种算法进行的，根据对象存活周期的不同将内存划分为几块，一般是新生代和老年代，新生代基本采用复制算法，老年代采用标记整理算法 GC相关API # finalize() # Object类的方法protected GC在回收对象之前调用该方法 调用对象身上的finalize()方法，进行销毁对象 final、finally、finalize()的区别 # final：关键字，用来修饰类、方法和变量。修饰类，该类不可继承；修饰方法，该方法不可被重写；修饰变量或属性：该变量或属性值不可修改\nfinally：使用在异常处理机制中，生命在finally中的代码一定会被执行，即使catch()中又出现了异常、try()和catch()中有return语句\nfinalize()：用于垃圾回收中，可以调用对象的finalize()方法，销毁对象\nSystem.gc()，Runtime.gc() # System.gc()底层调用的还是Runtime.gc() 程序员可以通过System.gc()或者Runtime.gc()，通知JVM开始回收垃圾。这也是程序员唯一可以参与垃圾回收的地方，一般调用完之后，再调用System.runFinalization方法，因为个方法都是建议GC回收 注意：System.gc()或者Runtime.gc()并不一定就可以让JVM进行GC回收，原因是JVM回收有它自己的条件，即Eden满，或老年代满 内存泄漏和内存溢出 # 内存溢出 out of memory # 是指程序在申请内存时，没有足够的内存空间供其使用，出现out of memory\n内存泄露 memory leak # 是指程序在申请内存后，无法释放已申请的内存空间，一次内存泄露危害可以忽略，但内存泄露堆积后果很严重，无论多少内存,迟早会被占光\n另外，内存泄漏memory leak最终会导致内存溢出out of memory\n内存泄漏 # 1、长生命周期对象持有短生命周期对象，例如：\npublic class Demo{ private Object object; public void setObject(){ object = new Object(); } } /* 此时执行了setObject方法后 * 如果Demo的实例没有被回收，那么object对象会一直存在与内存中 */ 2、集合中的内存泄露，我们有的时候会声明静态的集合对象，而集合中的每个元素指向一个对象，集合不会被回收，导致里面元素指向的对象也不会被回收\n3、连接中的内存泄漏，对于这些连接对象（数据库链接、网络连接、IO连接），除非我们显示的调用了close方法，否则这个对象不会被GC回收\nSystem.gc和System.runFinalization区别 # System.gc(); 告诉垃圾收集器打算进行垃圾收集，而垃圾收集器进不进行收集是不确定的 System.runFinalization(); 强制调用已经失去引用的对象的finalize方法 强制垃圾收集 Java代码的优化 # 1、几种for循环的区别 # //这种for循环，会在每一轮循环之前重新计算list的size for(int i=0;i\u0026lt;list.size();i++) //这种for循环，只在第一次循环前计算长度，性能较高 for(int i=0,len=list.size();i\u0026lt;len;i++) 2、String字符串的相加 # 简单的字符串相加，结果每次都会创建一个新的对象，对于频繁进行相加操作的字符串，不建议使用String，而是使用StringBuffer或StringBuider\n3、尽可能使用局部变量 # 非必要情况，不要使用全局变量或实例变量，局部变量随着方法的结束，就会准备进行回收\n4、及时关闭流、连接 # 5、尽量使用懒加载策略，使用的时候再创建 # 6、慎用异常，因为异常的抛出之前，也需要创建对象 # jvisualvm的使用 # 一、启动jvisualvm # 1.1、找到jdk安装目录中的jvisualvm.exe，并启动\n二、安装Visual GC插件 # 2.1、打开面板上的工具-插件 # 2.2、安装Visual GC插件 # 2.3、如果安装失败，可以手动安装 # 进入插件中心，在右边选择自己的jdk版本：https://visualvm.github.io/pluginscenters.html\n点击进去，下载插件\n选择自己下载的插件，进行安装\n三、查看GC # JVM中GC的执行过程 # 1、新创建的对象，会优先放入新生代的Eden区\n2、GC执行，会将所有Eden区的存活对象，复制到From区，清空Eden区\n3、GC再次执行，回收完无用对象后，会将From区的对象全部复制到To区，清空From区\n4、GC再次执行，回收完无用对象后，会将To区的对象全部复制到From区，清空To区，3、4步的作用是为了减少内存空间碎片，也就是防止内存不连续，导致内存溢出\n5、GC反复执行3、4步，如果对象执行3、4超过15次或To区满了，会将所有对象移入老年代，S区（From和To）\n","date":"2024-09-24","externalUrl":null,"permalink":"/posts/3ab7256e/ebb2f342/deef9e5d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJVM的空间 \n    \u003cdiv id=\"jvm的空间\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jvm%e7%9a%84%e7%a9%ba%e9%97%b4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eJVM有一个称为垃圾回收器的低级线程，这个线程在后台不断地运行，自动寻找在Java程序中不再被使用的对象，并释放这些对象的内存。这种内存回收的过程被称为垃圾回收（Garbage Collection）\u003c/p\u003e","title":"1、JVM与GC","type":"posts"},{"content":" Mysql的逻辑架构 # 连接层 # 客户端连接器（Client Connectors）：提供与MySQL服务器建立连接的支持。目前几乎支持所有主流的服务端编程技术，例如常见的 Java、C、Python、.NET等，它们通过各自API与MySQL建立连接。\n服务层 # 服务层是MySQL Server的核心，主要包含系统管理和控制工具、连接池、SQL接口、解析器、查询优化器和缓存六个部分。\n系统管理和控制工具（Management Services \u0026amp; Utilities）：例如备份恢复、安全管理、集群管理等。 连接池（Connection Pool）：负责存储和管理客户端与数据库的连接，一个线程负责管理一个连接。 SQL 接口（SQL Interface）：用于接受客户端发送的各种SQL命令，并且返回用户需要查询的结果。比如DML、DDL、存储过程、视图、触发器等。 解析器（Parser）：负责将请求的SQL解析生成一个解析树。然后根据一些MySQL规则进一步检查解析树是否合法。 查询优化器（Optimizer）：当解析树通过解析器语法检查后，将交由优化器将其转化成执行计划，然后与存储引擎交互。 缓存（Cache\u0026amp;Buffffer）： 缓存机制是由一系列小缓存组成的。比如表缓存，记录缓存，权限缓存，引擎缓存等。如果查询缓存有命中的查询结果，查询语句就可以直接去查询缓存中取数据。 引擎层 # 可插拔存储引擎层（Pluggable Storage Engines）：使用可插拔存储引擎架构，能够在运行的时候动态加载或者卸载这些存储引擎。存储引擎负责MySQL中的数据存储的与提取，与底层的系统文件进行交互。MySQL 存储引擎是插件式的，服务器中的查询执行引擎通过接口与存储引擎进行通信 ，接口屏蔽了不同的存储引擎之间的差异。现在有很多的存储引擎，常见的就是MyISAM和InnoDB。\n查看MySQL中现在提供的存储引擎，在Support列中，YES表示当前版本支持这个存储引擎；DEFAULT表示该引擎是默认的引擎，即InnoDB。\nshow engines; 查看MySQL现在默认使用的存储引擎\nshow variables like \u0026#39;%storage_engine%\u0026#39;; 引擎 说明 InnoDB MySQL默认的存储引擎，支持ACID事务，行锁。目前说目前最稳定的引擎。 MyISAM MyISAM版本早起版本存储引擎，无事务支持，表锁级别，5.6,5.7版本有些表还是这个引擎，系统表也存在引擎。 Memory 将所有数据存储在内存中，以便在需要快速查找非关键数据的环境中进行快速访问。这个引擎以前称为HEAP引擎。 CSV CSV表允许以CSV格式导入或转储数据，以便与读写相同格式的脚本和应用程序交换数据。因为CSV表没有索引，所以在正常操作过程中，通常将数据保存在InnoDB表中，只在导入或导出阶段使用CSV表。 Archive 紧凑的、未索引的表用于存储和检索大量很少引用的历史、归档或安全审计信息。 Blackhole MySQL在5.x系列提供了Blackhole引擎：“黑洞”，不存储数据，但会正常的记录下Binlog，而且这些Binlog还会被正常的同步到Slave上。binlog分发引擎。 NDB 分片方式实现数据冗余，分片机制，表会在集群进程之间被复制，企业版特有引擎。 MRG_MYISAM 对一系列相同的MyISAM表进行逻辑分组，并将引用为一个对象。适合于数据仓库等环境。 Federated 从多个物理服务器创建一个逻辑数据库，类似dblink。 Example 该引擎在MySQL源代码中作为示例引擎，演示如何开始编写新的存储引擎。 MyISAM引擎和InnoDB引擎简单对比：\nMyISAM引擎 InnoDB引擎 外键 不支持 支持 事务 不支持 支持 行表锁 表锁，不适合高并发 行锁，适合高并发 缓存 只缓存索引 缓存索引和真实数据 表空间 小 大 关注点 性能，偏读 事务 默认安装 是 是 存储层 # 系统文件层（File System）：该层负责将数据库的数据和日志存储在文件系统上，并完成与存储引擎的交互，是文件的物理存储层。主要包含日志，数据文件，配置文件，pid 文件，socket 文件等。\nInnoDB引擎执行顺序 # MySQL日志 # 查询当前日志记录的状况 # show variables like \u0026#39;%log%\u0026#39;; MySQL的日志分类 # MySQL Server # General Log（查询日志）：当客户端连接或断开连接时，服务器将信息写入此日志，并记录从客户端收到的每个 SQL 语句。 使用场景：当遇到一些事务或者有关sql执行情况问题时候可以开启查看。 # 查看general log状态以及位置 show variables like \u0026#39;%general_log%\u0026#39;; # 临时开启 set global general_log=on; Error Log（错误日志）：启动、运行或停止mysqld时遇到的问题记录的日志。错误日志包含mysqld 启动和关闭次数的记录。它还包含诊断消息，例如服务器启动和关闭期间以及服务器运行期间发生的错误、警告和注释。例如，如果mysqld注意到需要自动检查或修复一个表，它会在错误日志中写入一条消息。 使用场景：在整个mysql启动或运行出问题时候查找原因。 # 查看error log位置 show variables like \u0026#39;log_error\u0026#39;; Binary Log（二进制日志）：二进制日志包含描述数据库更改的事件，例如表创建操作或表数据更改（不包含数据查询）。二进制日志还包含有关每个语句花费更新数据多长时间的信息。 使用场景：做数据库主从以及市面上一些数据同步工具实现原理就是通过二进制日志，做数据恢复操作需要使用二进制日志。 三种文件模式 STATMENT（5.7以前默认）：记录的是修改SQL语句，日志文件小，节约I0，提高性能，但是准确性差，对一些系统函数不能准确复制或不能复制。 ROW（5.7以后默认）：记录的是每行变更的实际数据，准确性强，能准确复制数据的变更，但是日志文件大，占用较大的网络10和磁盘lO。 MIXED：一般的复制使用STATEMENT模式保存binlog，对于STATEMENT模式无法复制的操作使用ROW模式保存binlog # 查看binary log状态 show global variables like \u0026#39;%binlog%\u0026#39;; # 查看binary log位置 show variables like \u0026#39;%log_bin%\u0026#39;; # 查看bin log列表 show binary logs; # 查看正在写入的bin log show master status; # 清空所有bin log reset master; # 查看bin log内容 show binlog events in \u0026#39;binlog.000001\u0026#39;; Relay Log（中继日志）：主从复制过程中使用的一种日志类型，在从服务器上记录主服务器上所有的二进制日志（Binary log）的信息 使用场景：查看主从复制的情况。 #查看relay log日志位置 show variables like \u0026#39;%relay_log%\u0026#39;; Slow Query Log（慢查询日志）：慢速查询日志由执行时间超过指定时间（long_query_time）并且查询扫描行数大于（min_examined_row_limit）的 SQL 语句组成。 使用场景：开启后排查系统执行时间过长的SQL。默认是超过十秒的查询。 # 查看slow query log位置以及状态 show variables like \u0026#39;%query_log%\u0026#39;; # 临时开启 set global slow_query_log=on; # 查看sql超时时间 SHOW VARIABLES LIKE \u0026#39;%long_query_time%\u0026#39;; # 设置sql超时时间 set global long_query_time=3; InnoDB Engine # 慢查询日志 # 默认没有开启\n临时开启，重启后失效 # SET GLOBAL slow_query_log=on; # 开启了慢查询日志 SET GLOBAL long_query_time=3; # 设置阈值3秒 SHOW VARIABLES LIKE \u0026#39;%slow_query_log%\u0026#39;; 永久开启 # [mysqld] # Slow logging. slow-query-log=1 slow_query_log_file=/var/lib/mysql/slow_query.log long_query_time=3 测试 # SELECT SLEEP(4); 执行后，可以在/var/lib/mysql/slow_query.log中看见慢查询日志\nExplain # 可以模拟优化器执行SQL语句，从而知道Mysql是如何处理你的SQL语句的，分析你的SQL语句或者表结构的性能瓶颈\nid # 验证表的读取和加载顺序，select查询的序列号，包含一组数字，表示查询中，执行select子句或操作表的顺序\n不同id的执行顺序 id相同 执行顺序从上向下 id不同 如果是子查询，id的序号会递增，id值越大优先级越高，越先被执行 id有相同也有不同 序号大的先执行，序号相同的，顺序执行 select_type # 查询的类型，主要是用于区别普通查询，联合查询，子查询等的复杂查询\nselect type 解释 测试sql simple 简单的select select * from tbl_student\n简单的查询，查询中不包含子查询或者UNION。 primary 需要union或者子查询 EXPLAIN #年龄大于平均年龄的同学\nSELECT * FROM tblstudent WHERE stuage\u0026gt;(\tSELECT AVG(stuage) FROM tblstudent t1) 查询中若包含任何复杂的子部分，最外层则被标记为PRIMARY,一般Primary也是最后被加载的那个 subquery 在select或者Where包含的子查询 上面的SQL就有subquery derived 派生表 在From列表中，包含的子查询被标记为derived,MYSQL会递归执行这些子查询，把结果放在哪临时表（变量内存交换）里面。\nselect * from (select * from tb_student) t\n#省市区 注意t1是一个虚表EXPLAIN\tselect t2.* from (SELECT cid FROM zone WHERE zname=\u0026lsquo;未央区\u0026rsquo;) t1,city t2\twhere t1.cid=t2.cid union union 如第二个select出现在union之后，则被标记为UNION\n若union包含在From子句的子查询中 ，外层select将被标记为Derived select * from tb_student union select * from tb_student union result union结果集 两种结果的合并，临时表\nselect * from tb_student union select * from tb_student depend subquery 类似depend union select (select name from test.tb_student a where a.id=b.id) from test.tb_student b dependent union 查询与外部相关 （mysql优化器会将in优化成exists） select * from tb_student where id in(select id from tb_student union select id from tb_student) select * from tb_student a where EXISTS (select 1 from tb_student where id = a.id union select id from tb_student where id = a.id) type # System 表只有一行记录(等于系统表)，这是const类型的特例，平时不会出现，可以忽略不计 const 表示通过索引一次就找到了，const用于比较primary key或者unique索引，因为只匹配一行数据，所以很快，如将主键置于where列表中，Mysql就能将该查询转换为一个常量 eq_ref 唯一性索引扫描，对于每个索引键，表中只有一条记录与之匹配。常见于主键或者唯一索引 ref 非唯一性索引扫描，返回匹配某个单独值的所有行，简单说，用索引查出了多条记录 range 只检索给定范围的行，使用一个索引来选择行，key列显示是用来那个索引，一般就是在where句中出现了between, \u0026lt; , \u0026gt; , in等的查询 index Full Index Scan全索引扫描 ,index只遍历索引树，通常比All快，有些情况，索引会被加载到内存中。读索引肯定比权标扫描要快 All Full Table Scan 全表扫描 查询效率的高低\nsystem\u0026gt;const\u0026gt;eq_ref\u0026gt;ref\u0026gt;range\u0026gt;index\u0026gt;ALL\n一般来说，保证查询至少达到range级别，最好达到ref级别\npossible_keys\u0026amp;key # possible_keys # 显示可能应用在这张表中的索引，一个或多个\n查询设计到的字段上若存在索引，则该索引将被列出。但不一定被查询实际使用。\nkey # 实际使用到的索引，如果为null，则没有使用索引\n查询中若是用了覆盖索引，则该索引仅出现在key列表中\nkey_len # 表示索引中使用的字节数，可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下，长度越短越好\nKey_len显示的值为索引字段的最大可能长度，并非实际使用长度，即key_len是根据表定义计算而得，不是通过表内检索出的\nref # 显示索引的那一列被使用了，如果可能的话，是一个常数。那些列或常量被用于查找索引列上的值\nrows # 根据表统计信息及索引选用情况，大致估算出找到所需的记录所需要读取的行数。\n每张表有多少行被优化器查询\nextra # 包含不适合在其他列中显示，但是十分重要的信息\nUsing fileSort文件内排序 在使用order by关键字的时候，如果待排序的内容不能由所使用的索引直接完成排序的话，那么mysql有可能就要进行文件排序 using temporary内部临时表 需要把数据先拷贝到临时表，最后再删除。也非常的慢，最好不要发生。如果要发生，要确保数据量 使用临时表保存中间结果，Mysql在对查询结果排序时使用临时表。常见于排序order by和分组查询group by，往往见于统计分析 using index select操作中使用了覆盖索引，Covering Index，避免访问了表的数据行，效率高 如果同时出现using where ,表明索引被用来执行索引键值的查找 using where 使用了条件查询 using join buffer 使用了链接缓存 impossible where where子句的值总是false，不能用来获取任何元素 Using index condition 查询的列不全在索引中，where条件中是一个前导列的范围 索引优化 # 单表 # 例如，有商品表goods\n执行sql语句，查询所有状态为1且价格大于20的商品，并对库存进行排序\nEXPLAIN SELECT * FROM goods WHERE gstate=1 AND gprice\u0026gt;20 ORDER BY gcount 发现结果为，此时不仅类型为全表扫描，而且进行了文件内排序\n所以，建立索引，因为查询使用到的字段为gprice，gcount，gstate，所以添加这三个字段的联合索引\nCREATE INDEX goods_idx_all ON goods(gstate,gprice,gcount) 再次执行sql语句\nEXPLAIN SELECT * FROM goods WHERE gstate=1 AND gprice\u0026gt;20 ORDER BY gcount 发现此时，虽然命中索引了，而且type也为range，但是还是进行了文件内排序\n如果将sql语句中的范围查询改为固定常量\nEXPLAIN SELECT * FROM goods WHERE gstate=1 AND gprice=20 ORDER BY gcount 发现结果正常，没有文件内排序了，所以是范围查询引起的文件内排序，原因是：Btree的工作原理，在创建索引的时候，会先对gstate进行排序，如果gstate相同，对gprice进行排序，如果gprice相同，对gcount进行排序，而gprice处于联合索引中间的位置，如果进行范围查询，也就是range，那么范围查询字段后面的索引将失效\n解决方法是\n需要范围查询的字段，不参与联合索引的创建\n多表 # EXPLAIN SELECT * FROM orders LEFT JOIN goods ON orders.git=goods.git 执行结果，orders表，进行了全表扫描，而goods表，因为有主键索引，索引命中\n索引的建立规则\n如果使用left join，那么关联字段的索引建立在右表上，right join，索引建立在左表上\n索引失效的解决 # 1、最好全值匹配，索引怎么建立的，就怎么用，使用and连接的查询语句，可以不与索引顺序相同，因为sql优化器会自动进行优化\n2、最佳左前缀法，查询时，要从索引建立的最左前列（第一列）开始，不可以跳过中间的列，例如\n建立索引\nCREATE INDEX goods_idx_all ON goods(gcount,gstate,gprice) 只查询前两列，gcount、gstate，结果正常\nEXPLAIN SELECT * FROM goods WHERE gcount=20 AND gstate=1 只查询第一列，gcount，结果正常\nEXPLAIN SELECT * FROM goods WHERE gcount=20 只查询第二列和第三列，gstate、gprice，进行了全表扫描，因为中间跳过了索引的第一列\nEXPLAIN SELECT * FROM goods WHERE gstate=1 AND gprice=35 只查询第一列和第三列，gcount、gprice，虽然使用了索引，但是索引的长度只使用了一个索引，也就是第一列的索引，并没有使用第三列的索引\nEXPLAIN SELECT * FROM goods WHERE gcount=20 AND gprice=35 3、不在索引列上做任何的操作（计算、函数、类型转换），会导致索引失效，全表扫描\n4、不在索引列查询条件前面使用范围查询（\u0026gt;，\u0026lt;，between and），也就是范围查询后的索引全部失效\n5、查询结果尽量覆盖索引，也就是，尽量只查询建立索引的字段，少使用select *\n6、在MySQL中使用除等于=以外的运算符，会导致索引失效，在MySQL8以后，查询计划的type为all\n7、is null和is not null会导致索引失效\n8、like中通配符位置除了在右边，其他都会索引失效，即like '%M'、like '%M%'\n9、字符串不加单引号，会索引失效，MySQL引擎会进行类型转换，和第三条意思一样\n10、使用or连接，会导致索引失效\n","date":"2024-09-20","externalUrl":null,"permalink":"/posts/a6ece75f/8b824d9b/6930a0a9/4fe0997e/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eMysql的逻辑架构 \n    \u003cdiv id=\"mysql的逻辑架构\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#mysql%e7%9a%84%e9%80%bb%e8%be%91%e6%9e%b6%e6%9e%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"20210606204741\"\n    data-zoom-src=\"/posts/a6ece75f/8b824d9b/6930a0a9/4fe0997e/image/20210606204741.png\"\n    src=\"/posts/a6ece75f/8b824d9b/6930a0a9/4fe0997e/image/20210606204741.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"1、MySQL优化","type":"posts"},{"content":"C# 是一个现代的、通用的、面向对象的编程语言，它是由微软（Microsoft）开发的，由 Ecma 和 ISO 核准认可的。\nC# 是由 Anders Hejlsberg 和他的团队在 .Net 框架开发期间开发的。\nC# 是专为公共语言基础结构（CLI）设计的。CLI 由可执行代码和运行时环境组成，允许在不同的计算机平台和体系结构上使用各种高级语言。\n环境准备 # Windows开发 # 推荐使用Visual Studio，微软的旗舰IDE（集成开发环境），提供全面的开发工具集，支持C#以及其他多种编程语言。\n下载地址：https://visualstudio.microsoft.com/zh-hans/downloads/\n对于C#开发， 确保选中.NET desktop development组件（.NET 桌面开发组件），这将包含C#语言支持和Windows桌面开发所需的工具。\nMac或Linux开发 # 下载并安装.NET环境：https://dotnet.microsoft.com/en-us/download\n如果需要卸载.NET环境，使用如下脚本即可\n#!/usr/bin/env bash # # Copyright (c) .NET Foundation and contributors. All rights reserved. # Licensed under the MIT license. See LICENSE file in the project root for full license information. # DIR=\u0026#34;$( cd \u0026#34;$( dirname \u0026#34;${BASH_SOURCE[0]}\u0026#34; )\u0026#34; \u0026amp;\u0026amp; pwd )\u0026#34; current_userid=$(id -u) if [ $current_userid -ne 0 ]; then echo \u0026#34;$(basename \u0026#34;$0\u0026#34;) uninstallation script requires superuser privileges to run\u0026#34; \u0026gt;\u0026amp;2 exit 1 fi # this is the common suffix for all the dotnet pkgs dotnet_pkg_name_suffix=\u0026#34;com.microsoft.dotnet\u0026#34; dotnet_install_root=\u0026#34;/usr/local/share/dotnet\u0026#34; dotnet_path_file=\u0026#34;/etc/paths.d/dotnet\u0026#34; dotnet_tool_path_file=\u0026#34;/etc/paths.d/dotnet-cli-tools\u0026#34; remove_dotnet_pkgs(){ installed_pkgs=($(pkgutil --pkgs | grep $dotnet_pkg_name_suffix)) for i in \u0026#34;${installed_pkgs[@]}\u0026#34; do echo \u0026#34;Removing dotnet component - \\\u0026#34;$i\\\u0026#34;\u0026#34; \u0026gt;\u0026amp;2 pkgutil --force --forget \u0026#34;$i\u0026#34; done } remove_dotnet_pkgs [ \u0026#34;$?\u0026#34; -ne 0 ] \u0026amp;\u0026amp; echo \u0026#34;Failed to remove dotnet packages.\u0026#34; \u0026gt;\u0026amp;2 \u0026amp;\u0026amp; exit 1 echo \u0026#34;Deleting install root - $dotnet_install_root\u0026#34; \u0026gt;\u0026amp;2 rm -rf \u0026#34;$dotnet_install_root\u0026#34; rm -f \u0026#34;$dotnet_path_file\u0026#34; rm -f \u0026#34;$dotnet_tool_path_file\u0026#34; echo \u0026#34;dotnet packages removal succeeded.\u0026#34; \u0026gt;\u0026amp;2 exit 0 HelloWorld # 创建一个目录，然后在该目录内执行命令：dotnet new console，在当前目录中创建一个控制台项目\n然后在Program.cs中，写入如下代码\nusing System; namespace HelloWorldApplication { class Program { static void Main(string[] args) { /* 这是一个 多行注释 */ // 这一行是注释 Console.WriteLine(\u0026#34;Hello World\u0026#34;); Console.ReadKey(); } } } 然后执行命令dotnet run即可看到输出。\n程序的第一行 using System; - using 关键字用于在程序中包含 System 命名空间。 一个程序一般包含有多个 using 语句。 下一行是 namespace 声明。一个 namespace 里包含了一系列的类。HelloWorldApplication 命名空间包含了类 Program。 下一行是 class 声明。类 Program 包含了程序使用的数据和方法声明。类一般包含多个方法。方法定义了类的行为。在这里，Program 类只有一个 Main 方法。 下一行定义了 Main 方法，是所有 C# 程序的 入口点。Main 方法说明当执行时类将做什么动作。 下一行 /*...*/ 将会被编译器忽略，且它会在程序中添加额外的 注释。 Main 方法通过语句Console.WriteLine(\u0026quot;Hello World\u0026quot;); 指定了它的行为。 WriteLine 是一个定义在 System 命名空间中的 Console 类的一个方法。该语句会在屏幕上显示消息 Hello World。 最后一行 Console.ReadKey(); 是针对 VS.NET 用户的。这使得程序会等待一个按键的动作，防止程序从 Visual Studio .NET 启动时屏幕会快速运行并关闭。 与 Java 不同的是，文件名可以不同于类的名称。\n顶级语句 # 在 C# 9.0 版本中，引入了顶级语句（Top-Level Statements）的概念，这是一种新的编程范式，允许开发者在文件的顶层直接编写语句，而不需要将它们封装在方法或类中。\n特点：\n无需类或方法：顶级语句允许你直接在文件的顶层编写代码，无需定义类或方法。 文件作为入口点：包含顶级语句的文件被视为程序的入口点，类似于 C# 之前的 Main 方法。 自动 Main 方法：编译器会自动生成一个 Main 方法，并将顶级语句作为 Main 方法的主体。 支持局部函数：尽管不需要定义类，但顶级语句的文件中仍然可以定义局部函数。 更好的可读性：对于简单的脚本或工具，顶级语句提供了更好的可读性和简洁性。 适用于小型项目：顶级语句非常适合小型项目或脚本，可以快速编写和运行代码。 与现有代码兼容：顶级语句可以与现有的 C# 代码库一起使用，不会影响现有代码。 命名空间 # 命名空间的设计目的是提供一种让一组名称与其他名称分隔开的方式。在一个命名空间中声明的类的名称与另一个命名空间中声明的相同的类的名称不冲突\n定义 # namespace namespace_name { // 代码声明 } using 关键字 # using 关键字表明程序使用的是给定命名空间中的名称。例如，我们在程序中使用 System 命名空间，其中定义了类 Console。我们可以只写：\nConsole.WriteLine (\u0026#34;Hello there\u0026#34;); 我们可以写完全限定名称，如下：\nSystem.Console.WriteLine(\u0026#34;Hello there\u0026#34;); 一个文件中可以使用多个命名空间\nusing System; using first_space; using second_space; namespace first_space { class abc { public void func() { Console.WriteLine(\u0026#34;Inside first_space\u0026#34;); } } } namespace second_space { class efg { public void func() { Console.WriteLine(\u0026#34;Inside second_space\u0026#34;); } } } class TestClass { static void Main(string[] args) { abc fc = new abc(); efg sc = new efg(); fc.func(); sc.func(); Console.ReadKey(); } } ","date":"2024-09-12","externalUrl":null,"permalink":"/posts/5ab2821f/8a6d363a/","section":"文章","summary":"\u003cp\u003eC# 是一个现代的、通用的、面向对象的编程语言，它是由微软（Microsoft）开发的，由 Ecma 和 ISO 核准认可的。\u003c/p\u003e","title":"1、C#","type":"posts"},{"content":" Unity # Unity是一套具有完善体系与编辑器的跨平台游戏开发工具，也可以称之为游戏引擎。\n游戏引擎是指一些编写好的可重复利用的代码与开发游戏所用的各功能编辑器。\nUnity目前已超过50%的游戏引擎市场占有率。\nUnity引擎优势 # 基于C#编程，拥有易上手、高安全性的特性。 独特的面向组件游戏开发思想让游戏开发更加简单易复用 拥有十分成熟的所见即所得开发编辑器 拥有良好生态圈，商城中包含大量成熟的功能脚本与资源 强大的跨平台特性，可以制作PC、主机、手机、AR、VR等多平台游戏 下载与安装 # 官网：https://unity.com/cn\n下载Unity Hub，注册Unity账号\n然后安装编辑器，推荐安装LTS长期支持版，这里选择2022.3.42f1c1\nMac环境安装 # 因为Visual Studio for Mac在2024年8月31日停用，所以我们将主要介绍Visual Stuiod Code的使用。\n微软在2023年8月4日发布了Unity for Visual Stuio Code扩展，代替了以前的Debugger for Unity扩展。\n以下安装和设置分为两个部分： Visual Stuiod Code中安装.Net SDK 和 Unity扩展（只需要设置一次） 在Unity项目中升级Visual Studio Editor（每个项目都需要） .NET环境与VSCode插件安装 # .NET环境安装：https://dotnet.microsoft.com/en-us/download\nVSCode扩展安装：Unity\n升级Visual Studio Editor # 将Unity项目的脚本编辑器设置为VSCode\n在Unity中双击脚本，就可以打开Visual Studio Code，点击F5就可以构建项目了。\n在第一次构建时，会有如下提示\nEnable debugging for this session：只针对本次Unity Editor有效，关闭Editor后失效。 Enable debugging for all projects：将项目设置为debug模式，即使再打开Editor依然有效。 新建项目 # 编辑器安装完成之后，就可以开始创建项目了，点击（项目 - 新项目）\n项目模板 # 项目模板是基于不同类型项目的共同最佳实践而提供预选的设置。这些设置针对Unity支持的所有平台上的2D和3D项目进行了优化。\n项目模板的区别 # 从类型上来看，可以分为核心模板（Core）、示例模板（Sample）、学习模板（Learning），核心模板是空项目，是不带示例内容的，而示例模板则包括一些示例的场景，学习模板则是一个完整的项目。 从2D/3D角度 ，可以分为2D和3D项目。 渲染管线角度可以分为内置渲染管线，URP和HDRP三种。 渲染管线 适用的项目模板 内置渲染管线（Built-in Render Pipeline）：默认渲染管线，适用于原型设计或者对画质要求不高的游戏，适用于低端设备。 2D(Built-in Render Pipeline)、3D(Built-in Render Pipeline)、2DMobile, 3D Mobile 通用渲染管线（URP）：对平台支持广泛，在性能上也有较好表现，适合移动端、桌面端、网页端游戏。 Universal 2D、Universal 3D 高清渲染管线（HDRP）：适合大型3A游戏，对画面有高清渲染需求，适合桌面游戏和主机游戏项目。 High Definition 3D 项目结构 # Assets：存放项目所有资源的主要文件夹，就是资源面板里的内容 Library：临时文件和缓存文件夹，由Unity自动生成，包括资源索引、元数据等。一般情况下不需要修改或管理。若出现问题，可以尝试删除让Unity重新生成 Logs：Unity编辑器和运行时的日志文件夹，由Unity自动生成，包含详细的运行日志以及错误信息，不建议删除 Packages：Unity Package Manager（UPM）使用的文件夹，包含引用的各种包和资源。不建议删除 ProjectSettings：包含项目的配置文件，例如输入设置、编辑器设置等，不建议删除 Temp：临时文件夹，用于存放在编辑和运行时可能需要的临时数据，不建议删除 UserSettings：存放用户特定设置的文件夹，如布局、偏好等。不建议删除，但如果没有特定的用户设置需要保留，可以删除该文件夹中的内容，Unity会在需要时重新生成 .vsconfig：Visual Studio Code的配置文件，定义项目在VS Code中的设置和依赖项。如果不使用Visual Studio Code作为IDE，或者不需要特定的配置文件，可以删除这个文件 Assembly-CSharp.csproj 和 Assembly-CSharp-Editor.csproj：这两个是Unity项目的C#项目文件，分别用于项目运行时代码和编辑器扩展的代码。不建议删除，如果不使用C#进行编码或者不需要在Visual Studio中打开项目，可以删除这两个文件 .sln：Visual Studio生成的解决方案文件，用于整合Unity项目和C#项目。不建议删除，如果不使用Visual Studio或其他支持的IDE进行开发，可以删除该文件 编辑器窗口布局 # Hierarchy ：层级窗口 Scene： 场景窗口，3D视图窗 Game ：游戏播放窗口 Inspector ：检查器窗口，属性窗口 Project ：项目窗口 Console： 控制台窗口 编辑器操作 # 3D视图操作 # 按住鼠标中键：平移视图\n按住右键：围绕视图中心点旋转\n按住右键可以使用（W、S、D、A、Q、E）来移动视图\n**注意：**Unity创建物体的时候默认会生成在视图的中心点，而不是世界原点，可以选中物体后使用快捷键F来将视图中心点移动到物体\n物体操作 # 移动、旋转、缩放等功能，可以使用（Q、W、E、R）等快捷键，操作模式如下\nPivot：轴心 / Center 中心点 Global：世界坐标系 / Local 局部坐标系 透视和正交 # 透视视图 Perspective，近大远小 正交视图 Orthographic，又称等距视图 Isometric 透视图下，物体近大远小。正交视图下，物体的显示与距离无关，可以通过右上角坐标下面的Persp（透视）和ISO（正交）进行切换\n其他操作 # 复制：（Ctrl + D）\n删除：（Delete、Ctrl + back）\n聚焦：选中物体按（F）\n基本概念 # 场景（Scene） # 一个场景可以理解为一个环境，由许多的物体搭建而成，Unity将这些物体称游戏对象GameObject。\n游戏对象（GameObject） # 游戏对象是Unity中的基本实体，可以是图片、文本、三维物体等。是一种容器，可以挂载组件，游戏对象至少要有Mesh Filter（网格组件）和Mesh Renderer（渲染组件）才能渲染，即在编辑面板、游戏面板中看到。\n组件（Component） # 在Unity中，游戏物体是不具备任何功能的，如果想要为其添加功能，那么就需要为它添加该功能的组件，而每一个组件其实就是一个引擎内部的组件脚本或是由自己编写的组件脚本。也就是说，一个游戏物体( Game Object）会包含多个组件( Component )，每一个组件又是一个组件脚本。\n例如这个Cube物体，实际上就是（空物体 + Mesh Filter网格组件 + Mesh Renderer渲染组件 + Box Collider碰撞器组件）构成的。\n脚本（Script） # 脚本也是一种组件，用来定义逻辑的代码文件，通常使用C#编写，并附加到游戏对象上以控制其行为。\n预制体/预制件（Prefab） # 一个可重复使用的游戏对象模板，允许在项目中创建、配置和存储游戏对象的副本。直接从Hierarchy面板中拖动到资源面板即可形成预制体。\n材质（Material） # 定义了游戏对象表面的外观和光照效果，常与纹理（Texture）结合使用，纹理就是附加到物体表面的贴图。\n动画（Animation） # 用于控制游戏对象的运动和变换，包括动画和场景中的其他动画效果。\n物理（Physics） # 涉及到碰撞、重力和其他物理行为，使游戏对象具有真实的物理特性。\n","date":"2024-09-12","externalUrl":null,"permalink":"/posts/69064821/af96a2fa/862b827f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eUnity \n    \u003cdiv id=\"unity\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#unity\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eUnity是一套具有完善体系与编辑器的跨平台游戏开发工具，也可以称之为游戏引擎。\u003c/p\u003e","title":"1、Unity","type":"posts"},{"content":" Cocos Creator # Cocos Creator一个完整的游戏开发解决方案，包含了轻量高效的跨平台游戏引擎，以及能让你更快速开发游戏所需要的各种图形界面工具。完全为引擎定制打造，包含从设计、开发、预览、调试到发布的整个工作流所需的全功能一体化编辑器。\nCocos Creator 目前支持发布游戏到 Web、iOS、Android、各类\u0026quot;小游戏\u0026quot;、PC 客户端等平台，真正实现一次开发，全平台运行。\n官网：https://www.cocos.com/\n工作流程 # 功能特性 # 脚本中可以轻松声明可以在编辑器中随时调整的数据属性，对参数的调整可以由设计人员独立完成。 支持智能画布适配和免编程元素对齐的 UI 系统，可以完美适配任意分辨率的设备屏幕。 动画系统，支持动画轨迹预览和复杂曲线编辑功能。 使用动态语言支持的脚本开发，包括 JavaScript 和 TypeScript，使得动态调试和移动设备远程调试变得异常轻松。 底层由 C++ 内核和 JS 两套内核组成，在享受脚本化开发的便捷时，保持原生级别的轻量和高性能。 脚本组件化和开放式的插件系统为开发者在不同深度上提供了定制工作流的方法，编辑器可以大尺度调整来适应不同团队和项目的需求。 HelloWorld # 1、下载安装Cocos Dashboard，然后安装Cocos Creator\n2、首页新建项目，选择Empty(3D)，填写项目名称以及项目保存路径，然后点击创建并打开\n3、由于我们使用TypeScript编写脚本，所以在（偏好设置-程序管理器）中配置默认脚本编辑器，选择VsCode安装路径。还有浏览器以及微信开发者工具等。\n4、按command+s保存当前场景，保存在assets下面\n5、创建一个空节点，名字随便默认就行\n5、在assets下面创建一个Typescript脚本，名字默认就行\n6、将刚创建的脚本添加到场景节点中\n7、双击脚本NewComponent，在VsCode中打开，在start中添加代码\nimport { _decorator, Component, Node } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; @ccclass(\u0026#39;NewComponent\u0026#39;) export class NewComponent extends Component { start() { console.log(\u0026#34;Hello World\u0026#34;) } update(deltaTime: number) { } } 8、上面选择浏览器运行，并点击运行\n目录结构 # assets：资源目录 用来放置游戏中所有的本地资源、脚本和第三方库文件。只有在 assets 目录下的内容才能显示在 资源管理器 中。 build：构建目录（在构建某平台后会生成该目录） 在使用编辑器主菜单中的（项目 - 构建发布）使用默认发布路径发布项目后，编辑器会在项目路径下创建 build 目录，并存放所有目标平台的构建工程。 library：导入的资源目录 library 是将 assets 中的资源导入后生成的，在这里文件的结构和资源的格式将被处理成最终游戏发布时需要的形式。 当 library 丢失或损坏的时候，只要删除整个 library 文件夹再打开项目，就会重新生成资源库。 local：日志文件目录 包含该项目的本机上的配置信息，包括编辑器面板布局、窗口大小、位置等信息。开发者不需要关心这里的内容。 profiles：编辑器配置 包含编辑器的配置信息，包括各目标平台的构建配置信息、场景配置信息等。 extensions：扩展插件文件夹 用于放置此项目的自定义扩展插件。如果需要手动安装扩展插件，可以手动创建该文件夹。如需卸载扩展插件，在 extensions 中删除对应的文件夹即可。 settings：项目设置 保存特定项目相关的设置，如 项目设置 面板中相关的配置信息等。如果需要在不同开发者之间同步项目设置，请将 settings 目录加入到版本控制。 temp：临时文件目录 用于缓存一些 Cocos Creator 在本地的临时文件。这个文件夹可以在关闭 Cocos Creator 后手动删除，开发者不需要关心这里面的内容。 package.json：项目配置 package.json 文件和 assets 文件夹一起，作为验证 Cocos Creator 项目合法性的标志，只有包括了这两个内容的文件夹才能作为 Cocos Creator 项目打开。开发者不需要关心里面的内容。 版本控制 # Cocos Creator 在新建项目时，会自动生成 .gitignore 文件，用于排除不应该提交到 git 仓库的文件。如果开发者使用其它版本控制系统，或者需要提交项目到其它地方，应该注意只需要提交 assets、extensions、settings、package.json，或其它手动添加的关联文件。\n组织assets目录 # 项目整个开发资源，包括本地资源、脚本和第三方库文件都在assets中，所以我们要合理的组织assets中的子目录\nScenes：存放所有场景文件（.scene） Animations：存放动画资源 Prefabs：存放预制件（.prefab） Scripts：存放所有脚本文件（.ts, .js） Res：存放所有静态资源文件，例如图片、模型等 VsCode自动编译 # 在VsCode更改代码后，我们需要去Cocos中刷新编译，页面才能刷新，非常不方便，解决方法如下\n安装插件Run On Save，并且在项目根目录创建.vscode/settings.json，写入如下配置\n{ \u0026#34;emeraldwalk.runonsave\u0026#34;: { \u0026#34;commands\u0026#34;: [ { \u0026#34;match\u0026#34;: \u0026#34;.*\u0026#34;, \u0026#34;cmd\u0026#34;: \u0026#34;curl http://localhost:7456/asset-db/refresh\u0026#34; } ] } } ","date":"2024-08-30","externalUrl":null,"permalink":"/posts/69064821/92082869/a049b9b4/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eCocos Creator \n    \u003cdiv id=\"cocos-creator\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#cocos-creator\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eCocos Creator一个完整的游戏开发解决方案，包含了轻量高效的跨平台游戏引擎，以及能让你更快速开发游戏所需要的各种图形界面工具。完全为引擎定制打造，包含从设计、开发、预览、调试到发布的整个工作流所需的全功能一体化编辑器。\u003c/p\u003e","title":"1、CocosCreator","type":"posts"},{"content":" java.lang.System # System类提供的public static long currentTimeMillis()用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。\nlong time = System.currentTimeMillis(); java.util.Timer # Timer 和 TimerTask 用于在后台线程中调度任务的 java.util 类。 TimerTask 负责任务的执行， Timer 负责任务的调度。\n构造方法 # Timer()：创建一个定时器 Timer(boolean isDaemon)：创建一个定时器，指定其线程是否为守护线程 Timer(String name)：创建一个定时器，指定线程名 Timer(String name,boolean isDaemon)：创建一个定时器，并且指定其线程是否为守护线程 常用方法 # cancel()：终止计时器，丢弃所有已安排的任务 purge()：从任务队列中移除所有已取消的任务 schedule(TimerTask task,Date time)：在指定时间执行指定任务 schedule(TimerTask task,Date firstTiime,long period)：在指定时间执行指定任务，并按照固定延迟重复执行 schedule(TimerTask task,long delay)：在指定延迟后执行一次指定任务 schedule(TimerTask task,long delay,long period)：在指定延迟后执行指定任务，并按照固定延迟重复执行 scheduleAtFixedRate(TimerTask task,Date firstTiime,long period)：在指定时间执行指定任务，并按照固定速率重复执行 scheduleAtFixedRate(TimerTask task,long delay,long period)：在指定延迟后执行指定任务，并按照固定速率重复执行 Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { System.out.println(new Date()); } },0,2000); java.util.Date # java.util.Date是用来处理日期、时间的一个类，并且自带toString()方法\n构造方法 # Date()：创建一个对应当前时间的Date对象 Date(long date)：创建指定毫秒数的Date对象 常用方法 # toString()：把此 Date 对象转换为以下形式的字符串： dow mon dd hh:mm:ss zzz yyyy，显示当前的年、月、日、时、分、秒 。 其中： dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat)，zzz是时间标准 getTime()：返回自1970年1月1日00:00:00GMT以来此 Date 对象表示的毫秒数（时间戳） boolean after(Date date)：若当调用此方法的Date对象在指定日期之后返回true，否则返回false。 boolean before(Date date)：若当调用此方法的Date对象在指定日期之前返回true，否则返回false。 java.sql.Date # java.sql.Date类为java.util.Date类的子类，为数据库的日期类型。\n规范化的java.sql.Date只包含年月日信息，时分秒毫秒都会清零，也就是说，如果你是2017-09-17 15:15:25这样的时间点存取数据，那么存在数据库中的值就是：2017-09-17 00:00:00\njava.text.SimpleDateFormat # 构造方法 # public SimpleDateFormate()：使用默认模式和日期格式 public SimpleDateFormate(String pattern)：使用给定的模式和默认的日期格式 常用方法 # public final String format(Date date)：可以将Date类型的日期，转化为自定格式的字符串 public Date parse(String source)：可以将字符串格式的日期，转化为Date类型的日期 格式化编码 # 字符 描述 实例 G 纪元标记 AD y 四位年份 2001 M 月份 July or 07 d 一个月的日期 10 h A.M./P.M. (1~12)格式小时 12 H 一天中的小时 (0~23) 22 m 分钟数 30 s 秒数 55 S 毫秒数 234 E 星期几 Tuesday D 一年中的日子 360 F 一个月中第几周的周几 2 (second Wed. in July) w 一年中第几周 40 W 一个月中第几周 1 a A.M./P.M. 标记 PM k 一天中的小时(1~24) 24 K A.M./P.M. (0~11)格式小时 10 z 时区 Eastern Standard Time Demo # import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class DateTest { public static void main(String[] args) throws ParseException { //Date类可以获取当前的日期 Date d1 = new Date(); //因为Date类有toString方法，所以可以直接输出 System.out.println(d1); //SimpleDateFormat类的format方法，可以将Date类型的日期，转化为自定格式的字符串 //HH返回的是24小时制的时间 //hh返回的是12小时制的时间 //其中yy-MM-dd HH-mm-ss中的字母格式固定 SimpleDateFormat s1 = new SimpleDateFormat(\u0026#34;yyyy年MM月dd日 HH时mm分ss秒\u0026#34;); String str1 = s1.format(d1); System.out.println(str1); //SimpleDateFormat类的parse方法，可以将固定格式的字符串日期，转化为Date类型 String str2 = \u0026#34;2020-3-12 18-50-44\u0026#34;; SimpleDateFormat s2 = new SimpleDateFormat(\u0026#34;yyyy-MM-dd HH-mm-ss\u0026#34;); Date d2 = s2.parse(str2); System.out.println(d2); } } 输出 Tue Mar 30 12:18:27 CST 2021 2021年03月30日 12时18分27秒 Thu Mar 12 18:50:44 CST 2020 java.util.Calendar # Calendar类是一个抽象类，不可以被实例化，在程序中需要调用其静态方法getInstance()来得到一个Calendar对象，然后根据这个对象再调用其他的方法，例如：Calendar calendar = Calendar.getInstance();，一个Calendar的实例是系统时间的抽象表示。\nCalendar常量 # Calendar.YEAR //表示年份 Calendar.MONTH //表示月份，月份是从0开始计的，0-11 Calendar.DATE //表示当前时间为多少号（日历式的多少号） Calendar.HOUR //表示小时（12小时制） Calendar.HOUR_OF_DAY //表示小时（24小时制） Calendar.MINUTE //表示分钟数 Calendar.SECOND //表示秒数 Calendar.MILLENSECOND //表示毫秒数 Calendar.WEEK_OF_MONTH //当前时间是所在当前月的第几个星期（日历式的第几周） Calendar.WEEK_OF_YEAR //当前时间是所在当前年的第几个星期 Calendar.DAY_OF_WEEK_IN_MONTH //当前时间是所在当前月的第几个星期，以月份天数为标准，一个月的1号为第一周，8号为第二周 Calendar.DAY_OF_WEEK //一周七天中，当前时间是一周中的第几天,第一天是星期天，使用时需要-1 Calendar.DAY_OF_YEAR //表示一年中的第几天 Calendar.DAY_OF_MONTH //表示一个月中的第几天，结果等同于Calendar.DATE Calendar.AM_PM //表示是上午还是下午 Calendar.SUNDAY //周日 Calendar.MONDAY //周一 Calendar.TUESDAY //周二 Calendar.WEDNSDAY //周三 Calendar.THURSDAY //周四 Calendar.FRIDAY //周五 Calendar.SATURDAY //周六 常用方法 # public int get(int filed)：根据日历常量，获取其对应的值 public final void set(int year,int month,int day)：设置时间为指定的年，月，日 public final void set(int year, int month, int date, int hourOfDay, int minute,int second)：设置年月日时分秒 public void set(int field, int value)：设置指定日历常量的值 public abstract void add(int field,int amount)：将指定的日历常量增加amount（负数则为减） Date getTime()：转java.util.Date void setTime(Date date)：根据Date设置Calendar long getTimeInMillis()：获取时间戳（毫秒） 与Date互转 # Calendar instance = Calendar.getInstance(); // Calendar转Date Date time = instance.getTime(); // Date转Calendar instance.setTime(time); JDK1.8新API # JDK 1.0中包含了一个java.util.Date类，但是它的大多数方法已经在JDK1.1引入Calendar类之后被弃用了。而Calendar并不比Date好多少。它们面临的问题是：\n可变性：像日期和时间这样的类应该是不可变的。 偏移性：Date中的年份是从1900开始的，而月份都从0开始。 格式化：格式化只对Date有用，Calendar则不行。 它们也不是线程安全的；不能处理闰秒等。 Java 8 吸收了Joda-Time的精华，以一个新的开始为 Java 创建优秀的 API。 新的 java.time中包含了所有关于本地日期（LocalDate）、本地时（LocalTime）、本地日期时间（LocalDateTime）、时区（ZonedDateTime）和持续时间（Duration）的类。 历史悠久的Date类新增了toInstant()方法， 用于把Date转换成新的表示形式。这些新增的本地化时间日期 API 大大简化了日期时间和本地化的管理。\nLocalDate、LocalTime、LocalDateTime # java.time.LocalDate、java.time.LocalTime、java.time.LocalDateTime 类是其中较重要的几个类，它们的实例是不可变的对象，分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间，并不包含当前的时间信息，也不包含与时区相关的信息。\nISO-8601 # ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法，也就是公历。\n常用方法 # now()/now(ZoneId zone)：静态方法，根据当前时间创建对象/指定时区的对象 of()：静态方法，根据指定日期/时间创建对象 getDayOfMonth()/getDayOfYear()：获得月份天数(1-31) /获得年份天数(1-366) getDayOfWeek()：获得星期几(返回一个 DayOfWeek 枚举值) getMonth()：获得月份, 返回一个 Month 枚举值 getMonthValue() / getYear()：获得月份(1-12) /获得年份 getHour()/getMinute()/getSecond()：获得当前对象对应的小时、分钟、秒 withDayOfMonth()：将月份天数修改为指定的值并返回新的对象 withDayOfYear()：将年份天数修改为指定的值并返回新的对象 withMonth()：将月份修改为指定的值并返回新的对象 withYear()：将年份修改为指定的值并返回新的对象 plusDays()：向当前对象添加几天 plusWeeks()：向当前对象添加几周 plusMonths()：向当前对象添加几个月 plusYears()：向当前对象添加几年 plusHours()：向当前对象添加几小时 minusMonths()：从当前对象减去几月 minusWeeks()：从当前对象减去几周 minusDays()：从当前对象减去几天 minusYears()：从当前对象减去几年 minusHours()：从当前对象减去几小时 Demo # LocalDateTime dateTime = LocalDateTime.now(); System.out.println(dateTime); //2023-05-23T11:30:28.315 int year = dateTime.getYear(); System.out.println(year); // 2023 DayOfWeek dayOfWeek = dateTime.getDayOfWeek(); System.out.println(dayOfWeek.name()); // TUESDAY System.out.println(dayOfWeek.getValue()); // 2 LocalDateTime localDateTime = dateTime.minusDays(30); System.out.println(localDateTime); // 2023-04-23T11:30:28.315 LocalDateTime of = LocalDateTime.of(1998, 1, 20, 12, 30, 30, 30); System.out.println(of); // 1998-01-20T12:30:30.000000030 java.time.Instant # Instant：时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间戳。\n它只是简单的表示自1970年1月1日0时0分0秒（UTC）开始的秒数。因为java.time包是基于纳秒计算的，所以Instant的精度可以达到纳秒级。\n常用方法 # now()：静态方法，返回默认UTC时区的Instant类的对象 ofEpochMilli(long epochMilli)：静态方法，返回在1970-01-01 00:00:00基础上加上指定毫秒数之后的Instant类的对象 atOffset(ZoneOffset offset)：结合即时的偏移来创建一个OffsetDateTime（加减时间） toEpochMilli()：返回1970-01-01 00:00:00到当前时间的毫秒数，即为时间戳 Instant now = Instant.now(); System.out.println(now); // 2023-05-23T03:41:05.227Z long l = now.toEpochMilli(); System.out.println(l); // 1684813287231 OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(10)); System.out.println(offsetDateTime); // 2023-05-23T13:41:05.227Z Instant instant = Instant.ofEpochMilli(1684813265227L); System.out.println(instant); // 2023-05-23T03:41:05.227Z Date与LocalDate、LocalTime、LocalDateTime转换 # Instant也常用于Date与LocalDate、LocalTime、LocalDateTime转换\nDate date = new Date(); System.out.println(date); // Tue May 23 11:45:59 CST 2023 Instant instant = date.toInstant(); LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.of(\u0026#34;UTC+8\u0026#34;)); System.out.println(localDateTime); // 2023-05-23T11:45:59.654 LocalDateTime localDateTime = LocalDateTime.now(); System.out.println(localDateTime); // 2023-05-23T11:51:01.196 ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of(\u0026#34;UTC+8\u0026#34;)); Instant instant = zonedDateTime.toInstant(); Date from = Date.from(instant); System.out.println(from); // Tue May 23 11:51:01 CST 2023 java.time.format.DateTimeFormatter # 用来格式化LocalDate、LocalTime、LocalDateTime\n格式化 # 预定义的标准格式：ISO_LOCAL_DATE_TIME；ISO_LOCAL_DATE；ISO_LOCAL_TIME 本地化相关的格式：ofLocalizedDateTime(FormatStyle.LONG) 自定义的格式：ofPattern(“yyyy-MM-dd hh:mm:ss”) 常用方法 # ofPattern(String pattern)：静态方法 ， 返回一个指定字符串格式的DateTimeFormatter format(TemporalAccessor t)：格式化一个日期、时间，返回字符串 parse(CharSequence text)：将指定格式的字符序列解析为一个日期、时间 Demo # LocalDateTime now = LocalDateTime.now(); DateTimeFormatter f1 = DateTimeFormatter.ISO_DATE_TIME; String str = f1.format(now); System.out.println(str); // 2023-05-23T12:01:07.894 TemporalAccessor parse = f1.parse(str); LocalDateTime from = LocalDateTime.from(parse); System.out.println(from); // 2023-05-23T12:01:07.894 DateTimeFormatter SDF = DateTimeFormatter.ofPattern(\u0026#34;yyyy-MM-dd HH:mm:ss\u0026#34;); String str = SDF.format(now); System.out.println(str); // 2023-05-23 12:01:07 java.time.Duration # Duration 是 Java 中表示时间段的类，用于计算两个时间点之间的间隔。它包含秒（seconds）和纳秒（nanos）两个部分，可以表示毫秒及更小的时间单位。与 Java 中的其他时间类不同，Duration 不包含毫秒这个属性。\n常用方法 # Duration.of(long duration)：这个方法用于创建一个表示给定持续时间的Duration对象，单位为纳秒。持续时间可以是从零到Long.MAX_VALUE之间的任何值。\nDuration.of(long amount, TemporalUnit unit)：用于创建表示特定时间单位的持续时间对象。该方法接受两个参数：amount：表示持续时间的长整型数值。unit：表示时间单位的 TemporalUnit 枚举类型或其子类。\nDuration.ofDays(long days)：这个方法用于创建一个表示给定天数的Duration对象。\nDuration.ofHours(long hours)：这个方法用于创建一个表示给定小时数的Duration对象。\nDuration.ofMinutes(long minutes)：这个方法用于创建一个表示给定分钟数的Duration对象。\nDuration.ofSeconds(long seconds)：这个方法用于创建一个表示给定秒数的Duration对象。\nDuration.ofMillis(long millis)：这个方法用于创建一个表示给定毫秒数的Duration对象。\nDuration.ofNanos(long nanos)：这个方法用于创建一个表示给定纳秒数的Duration对象。\nDuration.between(LocalDateTime start, LocalDateTime end)：用于计算两个 LocalDateTime 对象之间的持续时间。该方法接受两个参数：start：表示起始时间的 LocalDateTime 对象。end：表示结束时间的 LocalDateTime 对象，返回值是Duration对象；\nDuration.get(...)：此方法返回在给定单位中的持续时间。它接受一个java.time.temporal.TemporalUnit参数，并返回该单位的数量。例如，如果你使用java.time.temporal.ChronoUnit.SECONDS，此方法将返回持续时间中的秒数。\nDuration.plus(...)：用于将当前Duration对象与另一个Duration对象相加，返回一个新的Duration对象，表示两个时间段的总和。\nDuration.minus(...)：用于从一个Duration对象中减去另一个Duration对象。它返回一个新的Duration对象，表示当前Duration对象与减去的那一个之间的差值。\nDuration.toDays()：将Duration对象转换为天数。它返回一个整数值，表示时间段中包含的天数。除此之外，还有toHours()、toMinutes()、toSeconds()、toMillis()、toNanos()。\n","date":"2024-05-27","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/409de243/38ca1886/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003ejava.lang.System \n    \u003cdiv id=\"javalangsystem\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#javalangsystem\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eSystem\u003c/code\u003e类提供的\u003ccode\u003epublic static long currentTimeMillis()\u003c/code\u003e用来返回当前时间与\u003ccode\u003e1970年1月1日0时0分0秒\u003c/code\u003e之间以\u003cstrong\u003e毫秒\u003c/strong\u003e为单位的时间差。\u003c/p\u003e","title":"1、时间日期","type":"posts"},{"content":" 什么是c++ # C++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言，支持过程化编程、面向对象编程和泛型编程。\nC++ 被认为是一种中级语言，它综合了高级语言和低级语言的特点。\nC++ 是由 Bjarne Stroustrup 于 1979 年在新泽西州美利山贝尔实验室开始设计开发的。C++ 进一步扩充和完善了 C 语言，最初命名为带类的C，后来在 1983 年更名为 C++。\nC++ 是 C 的一个超集，事实上，任何合法的 C 程序都是合法的 C++ 程序。\n注意：使用静态类型的编程语言是在编译时执行类型检查，而不是在运行时执行类型检查。\nc++的组成 # 标准的 C++ 由三个重要部分组成：\n核心语言，提供了所有构件块，包括变量、数据类型和常量，等等。 C++ 标准库，提供了大量的函数，用于操作文件、字符串等。 标准模板库（STL），提供了大量的方法，用于操作数据结构等。 cpp三大特性 # 封装 # 把客观的事务抽象成一个类（将数据和方法打包在一起，加以权限的区分，达到保护并安全使用数据的目的）\n继承 # 继承所表达的是类之间相关的关系，这种关系使得对象可以继承另外一类对象的特征和能力\n多态 # 多态性可以简单地概括为“一个接口，多种方法”，字面意思为多种形态。程序在运行时才决定调用的函数，它是面向对象编程领域的核心概念\nANSI 标准 # ANSI 标准是为了确保 C++ 的便携性 —— 您所编写的代码在 Mac、UNIX、Windows、Alpha 计算机上都能通过编译。\n由于 ANSI 标准已稳定使用了很长的时间，所有主要的 C++ 编译器的制造商都支持 ANSI 标准。\n环境设置 # 常用编译器 # 1、Visual Studio # 微软出品，官网：https://visualstudio.microsoft.com/zh-hans/\n编写HelloWorld # 1、创建项目 # 2、创建源文件 # 3、编写代码 # #include\u0026lt;iostream\u0026gt; using namespace std; int main() { cout \u0026lt;\u0026lt; \u0026#34;HelloWorld!\u0026#34; \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); // 实现按任意键继续 return 0; } 4、运行代码 # 注释 # c++和java一样，都有单行注释和多行注释\n//i input 输入 o output输出 stream流 输入输出流头文件（类似stdio.h） #include\u0026lt;iostream\u0026gt; //std(标准) 使用标准的命名空间 using namespace std; /* 这是程序的入口main函数 每一个cpp程序里面都会有且只有一个main函数 */ int main() { //输出语句 cout \u0026lt;\u0026lt; \u0026#34;HelloWorld!\u0026#34; \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } cpp程序结构 # 头文件 # #include; 预编译指令，引入头文件 iostream\n防止头文件重复包含，在.h头文件第一行声明：#pragma once\n命名空间 # #include\u0026lt;iostream\u0026gt; // 程序使用std基本命名空间，是为了可以使用cout和endl int main(){ std::cout \u0026lt;\u0026lt; \u0026#34;你好\u0026#34; \u0026lt;\u0026lt; std::endl; return 0; } 在 c++中，名称（name）可以是符号常量、变量、函数、结构、枚举、类和对象等等。工程越大，名称互相冲突性的可能性越大。另外使用多个厂商的类库时，也可能导致名称冲突。为了避免，在大规模程序的设计中，以及在程序员使用各种各样的 C++库时，这些标识符的命名发生冲突，标准 C++引入关键字 namespace（命名空间/名字空间/名称空间），可以更好地控制标识符的作用域。\n#include \u0026lt;iostream\u0026gt; using namespace std; //命名空间只可以在全局范围定义，不可以在函数里面定义 namespace A{ int a = 10; string test(){ return \u0026#34;命名空间A的test方法\u0026#34;; } } namespace B{ int a = 20; string test(){ return \u0026#34;命名空间B的test方法\u0026#34;; } } int main(){ // 使用作用域运算符，可以调用指定命名空间的属性或函数 cout \u0026lt;\u0026lt; A::a \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; A::test() \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; B::a \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; B::test() \u0026lt;\u0026lt; endl; return 0; } 无名命名空间 # 只可以在本文件内使用\n#include \u0026lt;iostream\u0026gt; using namespace std; namespace{ int a = 10; } int main(){ cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; return 0; } 命名空间别名 # #include \u0026lt;iostream\u0026gt; using namespace std; namespace A{ int a = 10; } int main(){ namespace B = A; cout \u0026lt;\u0026lt; B::a \u0026lt;\u0026lt; endl; return 0; } using # #include \u0026lt;iostream\u0026gt; using namespace std; namespace A{ int a = 10; } int main(){ //声明本文件从当前位置开始使用命名空间A //但是这么使用一定要注意，容易引起冲突 using namespace A; //如果已经在文件中声明使用命名空间，就可以不写作用域运算符 cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; return 0; } main函数 # ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/d8245fe1/09e70634/c2ec0c28/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是c++ \n    \u003cdiv id=\"什么是c\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%afc\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eC++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言，支持过程化编程、面向对象编程和泛型编程。\u003c/p\u003e","title":"1、c++语言概述","type":"posts"},{"content":" 什么是C # C 语言是一种通用的高级语言，1972年，贝尔实验室的丹尼斯·里奇（Dennis Ritch ）和肯·汤普逊（Ken Thompson ）在开发UNIX操作系统时设计了C语言。\n在 1978 年，布莱恩·柯林汉（Brian Kernighan）和丹尼斯·里奇（Dennis Ritchie）制作了 C 的第一个公开可用的描述，现在被称为 K\u0026amp;R 标准。\nC语言是可移植方面的佼佼者。从8位微处理器到克雷超级计算机，许多计算机体系结构都可以使用C编译器（C编译器是把C代码转换成计算机内部指令的程序）。\nC语言广泛应用于以下开发场景：操作系统、语言编译器、汇编器、文本编辑器、后台打印服务(Print Spooler)、网络驱动器、现代程序、数据库、语言解释器、实体工具\nC语言优点 # 易于学习。 结构化语言。 它产生高效率的程序。 它可以处理底层的活动。 它可以在多种计算机平台上编译。 关于C # C 语言是为了编写 UNIX 操作系统而被发明的。 C 语言是以 B 语言为基础的，B 语言大概是在 1970 年被引进的。 C 语言标准（ANSI/ISO、C89、C90、ANSI C）是于 1989 年由美国国家标准协会ANSI（American National Standard Institute）制定的。 截至 1973 年，UNIX 操作系统完全使用 C 语言编写。 目前，C 语言是最广泛使用的系统程序设计语言。 大多数先进的软件都是使用 C 语言实现的。 当今最流行的 Linux 操作系统和 RDBMS MySQL 都是使用 C 语言编写的。 C语言标准 # C89/C90 # 美国国家标准协会（ANSI）于1983年组建了一个委员会（X3J11），开发了一套新标准，并于1989年正式公布。该标准（ANSI C）定义了C语言和C标准库。国际标准化组织于1990年采用了这套C标准（ISO C）。ISO C和ANSI C是完全相同的标准。ANSI/ISO标准的最终版本通常叫作C89（因为ANSI于1989年批准该标准）或C90 （因为ISO于1990年批准该标准）。另外，由于ANSI先公布C标准，因此业界人士通常使用ANSI C。\n在该委员会制定的指导原则中，最有趣的可能是**：保持C的精神**，委员会在表述这一精神时列出了以下几点：\n信任程序员； 不要妨碍程序员做需要做的事； 保持语言精练简单； 只提供一种方法执行一项操作； 让程序运行更快，即使不能保证其可移植性。 C99 # 1994年，ANSI/ISO联合委员会（C9X委员会）开始修订C标准，最终发布了C99标准。\nC11 # 标准委员会在2007年承诺C标准的下一个版本是C1X，2011年终于发布了C11标准。\nC程序 # 编写C程序的步骤 # 定义程序的目标：在动手写程序之前，要在脑中有清晰的思路。想要程序去做什么首先自己要明确自己想做什么，思考你的程序需要哪些信息，要进行哪些计算和控制，以及程序应该要报告什么信息。在这一步骤中，不涉及具体的计算机语言，应该用一般术语来描述问题。 设计程序：对程序应该完成什么任务有概念性的认识后，就应该考虑如何用程序来完成它。例如，用户界面应该是怎样的？如何组织程序？目标用户是谁？准备花多长时间来完成这个程序？ 编写代码：设计好程序后，就可以编写代码来实现它。也就是说，把你设计的程序翻译成C语言。这里是真正需要使用C语言的地方。可以把思路写在纸上，但是最终还是要把代码输入计算机。 编译：生成一个用户可以运行的可执行文件，其中包含着计算机能理解的机器码。编译器还会检查C语言程序是否有效。如果C编译器发现错误，就不生成可执行文件并报错。 运行程序：运行可执行程序。 测试和调试程序：检查程序是否按照你所设计的思路运行。 维护和修改代码：创建完程序后，你发现程序有错，或者想扩展程序的用途，这时就要修改程序。 HelloWorld # 一个 C 语言程序，可以写在一个或多个扩展名为 .c 的文本文件中，例如，hello.c\n#include \u0026lt;stdio.h\u0026gt; int main() { /* 我的第一个 C 程序 */ printf(\u0026#34;Hello, World! \\n\u0026#34;); return 0; } 基本结构\n所有的 C 语言程序都需要包含main()函数。 代码从main()函数开始执行。 /* */：用于注释说明。 printf()：用于格式化输出到屏幕。printf()函数在stdio.h头文件中声明。 stdio.h：是一个头文件 (标准输入输出头文件) , #include是一个预处理命令，用来引入头文件。 当编译器遇到printf()函数时，如果没有找到 stdio.h头文件，会发生编译错误。 return 0;：用于表示退出程序。 工程、项目 # 程序是一个比较宽泛的称呼，它可以细分为很多种类，例如：\n有的程序不带界面，完全是“黑屏”的，只能输入一些字符或者命令，称为控制台程序（Console Application），例如 Windows 下的 cmd.exe，Linux 或 Mac OS 下的终端（Terminal）。 有的程序带界面，看起来很漂亮，能够使用鼠标点击，称为GUI程序（Graphical User Interface Program），例如 QQ、迅雷、Chrome 等。 有的程序不单独出现，而是作为其它程序的一个组成部分，普通用户很难接触到它们，例如静态库、动态库等。 不同的程序对应不同的工程类型（项目类型），使用 IDE 时必须选择正确的工程类型才能创建出我们想要的程序。换句话说，IDE 包含了多种工程类型，不同的工程类型会创建出不同的程序。\n不同的工程类型本质上是对 IDE 中各个参数的不同设置；我们也可以创建一个空白的工程类型，然后自己去设置各种参数（不过一般不这样做）。\n控制台程序对应的工程类型为Win32控制台程序（Win32 Console Application），GUI 程序对应的工程类型为Win32程序（Win32 Application）。\n基本语法 # 标记（Tokens） # C 程序由各种标记组成，标记可以是关键字、标识符、常量、字符串值，或者是一个符号\nprintf(\u0026#39;Hello World!\u0026#39;); printf // 标识符 ( // 符号 \u0026#34;Hello, W3Cschool! \\n\u0026#34; // 字符串值 ) // 符号 ; // 分号是语句结束符 在 C 程序中，分号是语句结束符。也就是说，每个语句必须以分号结束。它表明一个逻辑实体的结束。\n注释 # //C99新增了这种风格的注释，普遍用于C++和Java。这 /* 多行 注释 */ 标识符 # C 标识符是用来标识变量、函数，或任何其他用户自定义项目的名称。 一个标识符以字母 A-Z 或a-z 或下划线_ 开始，后跟零个或多个字母、下划线和数字（0-9）。 C 标识符内不允许出现标点字符，比如 @、$ 和 %。 C 是区分大小写的编程语言。 关键字 # 下表列出了 C 中的保留字。这些保留字不能作为常量名、变量名或其他标识符名称。\nauto else long switch break enum register typedef case extern return union char float short unsigned const for signed void continue goto sizeof volatile default if static while do int struct _Packed double ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/f571508d/fa8095d0/7cad3221/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是C \n    \u003cdiv id=\"什么是c\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%afc\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eC 语言是一种通用的高级语言，1972年，贝尔实验室的丹尼斯·里奇（Dennis Ritch ）和肯·汤普逊（Ken Thompson ）在开发UNIX操作系统时设计了C语言。\u003c/p\u003e","title":"1、C语言","type":"posts"},{"content":" JavaEE # Java EE（现在称为Jakarta EE）是Java平台的一个扩展，专注于开发和运行企业级应用程序。它提供了一组标准规范和API，用于构建可扩展、安全、可靠且高性能的分布式应用程序。Java EE涵盖了广泛的功能领域，包括Web应用程序开发、持久化、消息传递、事务管理、安全性等。\nJava EE提供了一整套的规范，其中包括：\nServlet：用于处理Web请求和生成动态内容的Java组件。 JSP（JavaServer Pages）：基于XML和Java的模板引擎，用于在服务器上生成动态Web页面。 EJB（Enterprise JavaBeans）：用于开发分布式、事务性的企业级应用程序的组件模型。 JPA（Java Persistence API）：用于对象关系映射（ORM）和持久化的API。 JMS（Java Message Service）：用于在分布式系统中进行异步消息传递的API。 JTA（Java Transaction API）：用于管理分布式事务的API。 JavaMail：用于发送和接收电子邮件的API。 JavaWeb # Java Web，是指使用Java技术开发的Web应用程序。Java Web应用程序通常基于Java EE平台，并利用Java EE提供的规范和API来构建Web功能。 web包括**：web服务器和web客户端**两部分。 Java在客户端的应用有java applet不过现在使用的很少，Java在服务器端的应用非常的丰富。 Java的Web框架虽然各不相同，但基本也都是遵循特定的路数的：使用Servlet或者Filter拦截请求，使用MVC的思想设计架构，使用约定，XML或 Annotation实现配置，运用Java面向对象的特点，面向抽象实现请求和响应的流程，支持Jsp，Freemarker，Velocity等视图。 Java Web开发流程 # 确定开发环境：选择适合的Java IDE（集成开发环境），如Eclipse、NetBeans或IntelliJ IDEA。确保已安装并配置了Java EE开发环境，包括Java EE SDK和相应的应用服务器。 创建Web项目：使用IDE创建一个新的Java EE Web项目。选择合适的项目模板和配置参数，包括Web容器和部署目标。 编写Servlet和JSP代码：根据应用程序的需求，编写Servlet和JSP代码来处理Web请求、生成动态内容和与后端系统交互。可以使用Servlet API、JSP、JSTL和EL来开发功能。 配置部署描述符（web.xml）：在项目的web.xml文件中配置Servlet、过滤器、监听器等组件，并定义Web应用程序的行为和特性。这包括URL映射、初始化参数、会话管理等。 构建和部署应用程序：使用构建工具（如Maven）构建Web应用程序，并将生成的WAR（Web Archive）文件部署到应用服务器中。服务器将加载并执行应用程序，使其可访问。 测试和调试：在应用服务器上运行Web应用程序，并进行测试和调试，确保其功能和性能符合预期。可以使用IDE提供的调试工具进行代码调试和错误修复。 Web项目 # 根据可请求资源的（服务器提供资源）类型的不同，分静态web项目和动态web项目\n静态Web项目 # 项目里面的资源只有静态资源，例如.html、.js、.css、.jpg等\n在静态Web程序中，客户端使用Web浏览器经过网络连接到服务器上，使用HTTP协议发起一个请求（Request），告诉服务器需要得到哪个静态资源，所有的请求交给Web服务器，之后WEB服务器根据用户的需要，从文件系统（存放了所有静态页面的磁盘）取出内容。之后通过Web服务器返回给客户端，客户端接收到内容之后经过浏览器渲染解析，得到显示的效果。\n静态Web项目的特点\n静态网站是最初的建站方式，浏览者所看到的每个页面是建站者上传到服务器上的一个 html （ htm ）文件，这种网站每增加、删除、修改一个页面，都必须重新对服务器的文件进行一次下载上传。网页内容一经发布到网站服务器上，无论是否有用户访问，每个静态网页的内容都是保存在网站服务器上的，也就是说，静态网页是实实在在保存在服务器上的文件，每个网页都是一个独立的文件；\n静态网页的内容相对稳定，因此容易被搜索引擎检索；\n静态网页没有数据库的支持，在网站制作和维护方面工作量较大，因此当网站信息量很大时完全依靠静态网页制作方式比较困难；\n静态网页的交互性较差，在功能方面有较大的限制。如：不能实现用户注册和用户登录的功能\n动态Web项目 # 项目中除了静态资源，还有例如.jsp、.php、.asp/.aspx等 动态WEB中，程序依然使用客户端和服务端，客户端依然使用浏览器通过网络连接到服务器上，使用HTTP协议发起请求（Request），现在的所有请求都先经过一个WEB Server来处理。 如果客户端请求的是静态资源，则将请求直接转交给WEB服务器，之后WEB服务器从文件系统中取出内容，发送回客户端浏览器进行解析执行。 如果客户端请求的是动态资源（.jsp、.asp/.aspx、.php），则先将请求转交给WEB Container(WEB容器)，在WEB Container中连接数据库，从数据库中取出数据等一系列操作后动态拼凑页面的展示内容，拼凑页面的展示内容后，把所有的展示内容交给WEB服务器，之后通过WEB服务器将内容发送回客户端浏览器进行解析执行。 动态Web的特点 交互性：网页会根据用户的要求和选择而动态地改变和响应，浏览器作为客户端，成为一个动态交流的桥梁，动态网页的交互性也是今后 Web 发展的潮流。 自动更新：即无须手动更新 HTML 文档，便会自动生成新页面，可以大大节省工作量。 因时因人而变：即当不同时间、不同用户访问同一网址时会出现不同页面。 Web服务器 # 服务器：指安装了服务器软件的计算机\n服务器软件：软件，下载安装即可使用\n服务器软件的作用 # 可以接受用户的请求，可以对请求进行处理，然后进行响应。 它可以部署web项目，可以通过浏览器访问的方式，去访问web项目中的资源 JavaWeb常用的服务器 # Tomcat（Apache）：是Apache 软件基金会（Apache Software Foundation）的Jakarta 项目中的一个核心项目，最新的Servlet 和JSP 规范总是能在Tomcat 中得到体现，因为Tomcat 技术先进、性能稳定，而且免费，因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可，成为目前比较流行的Web 应用服务器。 Weblogic（Oracle）：用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器。将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的开发、集成、部署和管理之中。 JBOSS（Redhat）：是一个管理EJB的容器和服务器。但是JBoss核心服务不包含支持servlet和JSP的Web容器，但是能够很好的与Tomcat绑定使用。 Jetty（Webtide）：Jetty是一个开源的Servlet容器，它基于Java的Web容器，例如JSP和servlet提供运行环境。Jetty是使用Java语言编写的，他的API以一组JAR包的形式发布。 Tomcat # 官网：https://tomcat.apache.org/\n目录结构 # bin： 专门用来存放Tomcat服务器的可执行程序 conf： 专门用来存放Tomcat服务器的配置文件 lib： 专门用来存放Tomcat服务器的jar包 logs： 专门用来存放Tomcat服务器运行时输出的日志信息 temp： 专门用来存放Tomcat服务器运行时产生的临时数据 webapps： 专门用来存放部署的web工程 work： Tomcat工作时的目录，用来存放Tomcat运行时 jsp 翻译为 servlet 的源码，和Session钝化的目录 使用 # 前提条件 # tomcat内置的配置文件中，需要寻找JAVA_HOME的环境变量，所以使用前必须配置JDK路径至环境变量，可执行命令查看是否配置\nwindows：echo %JAVA_HOME% linux：$JAVA_HOME 启动 # 方式一：进入tomcat\\bin，启动startup.bat（windows）或startup.sh（linux） 方式二：进入tomcat\\bin，执行命令catalina run 优势：可以看到启动时错误信息 关闭 # tomcat/bin/shutdown.bat 找到tomcat进程，杀进程 常用配置 # 修改Tomcat的端口号 # tomcat的默认端口号是：8080\n编辑tomcat/conf/server.xml，Connector节点port属性即为tomcat端口号，修改后需要重启tomcat才可生效\n\u0026lt;Connector port=\u0026#34;8080\u0026#34; protocol=\u0026#34;HTTP/1.1\u0026#34; connectionTimeout=\u0026#34;20000\u0026#34; redirectPort=\u0026#34;8443\u0026#34; URIEncoding=\u0026#34;UTF-8\u0026#34;/\u0026gt; 解决页面中文乱码 # #修改tomcat/conf/server.xml中URIEncoding \u0026lt;Connector port=\u0026#34;8080\u0026#34; protocol=\u0026#34;HTTP/1.1\u0026#34; connectionTimeout=\u0026#34;20000\u0026#34; redirectPort=\u0026#34;8443\u0026#34; URIEncoding=\u0026#34;UTF-8\u0026#34;/\u0026gt; #修改tomcat/bin/catalina.bat,在第二行添加如下 @echo off set JAVA_OPTS=-Dfile.encoding=UTF-8 解决控制台输出乱码 # 由于tomcat默认的日志输出是UTF-8，如果在windows的dos窗口下会出现中文乱码\n可以修改tomcat/conf/logging.properties中的编码格式为GBK即可\nava.util.logging.ConsoleHandler.encoding = GBK 修改webapps中项目的访问路径 # 在webapps中，每一个目录即为一个项目，项目的访问路径默认为目录名\nROOT目录的项目访问路径默认为/\n可以修改tomcat/conf/server.xml文件，在节点Server/Engine/Host中添加Context指定项目的访问路径\n\u0026lt;Host name=\u0026#34;localhost\u0026#34; appBase=\u0026#34;webapps\u0026#34; unpackWARs=\u0026#34;true\u0026#34; autoDeploy=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;!-- 配置webapps/myproject项目的访问路径为/ --\u0026gt; \u0026lt;Context path=\u0026#34;\u0026#34; docBase=\u0026#34;myproject\u0026#34; reloadable=\u0026#34;true\u0026#34; /\u0026gt; \u0026lt;Valve className=\u0026#34;org.apache.catalina.valves.AccessLogValve\u0026#34; directory=\u0026#34;logs\u0026#34; prefix=\u0026#34;localhost_access_log\u0026#34; suffix=\u0026#34;.txt\u0026#34; pattern=\u0026#34;%h %l %u %t \u0026amp;quot;%r\u0026amp;quot; %s %b\u0026#34; /\u0026gt; \u0026lt;/Host\u0026gt; IDEA配置TomCat # IDEA/Settings/Build,Execution,Deployment/Application Servers\nIDEA创建JavaWeb项目 # 非maven项目 # 创建项目 # 点击File/New/Project，创建普通java项目\n右键项目，选择Add Framework Support，添加框架支持\n选择Java EE/Web Application\n创建后的目录结构为\nsrc：目录存放自己自己编写的java代码 web：目录专门用来存放web工程的资源文件，如 html、css、js等 WEB-INF：目录是一个受服务器保护的目录，浏览器无法直接访问到此目录的内容 通常会在WEB-INF目录下创建 lib目录，用来存放第三方的jar包（IDEA中需要自己配置导入） web.xml：它是整个动态web工程的配置部署描述文件，可以在这配置很多web工程的组件，比如：Servlet程序、Filter过滤器、Listener监听器、Session超时等 配置tomcat # 点击Add Configuration，进入Run/Debug Configurations面板\n点击左上角的+，选择Tomcat/local\n点击Deployment，点击Deploy at the server startup下的+，选择Artifact\n点击Apply，点击OK\n添加Servlet依赖 # 进入Project Structure，如下操作\n选择Tomcat提供的Servlet依赖即可\n添加war包打包方式 # 进入Project Structure，如下操作，选择Archive\n选择Build\n选择刚才创建的Artifact即可\nmaven项目 # 点击File/New/Project，创建普通Java Enterprise项目\n目录结构如下\njava：目录存放自己自己编写的java代码 webapp：目录专门用来存放web工程的资源文件，如 html、css、js等 WEB-INF：目录是一个受服务器保护的目录，浏览器无法直接访问到此目录的内容 web.xml：它是整个动态web工程的配置部署描述文件，可以在这配置很多web工程的组件，比如：Servlet程序、Filter过滤器、Listener监听器、Session超时等 Web项目的部署 # 什么是部署 # 将web项目交给服务器来进行管理\nwar包 # war包是 Sun 提出的一种 web 应用程序格式。它与 jar 类似，是很多文件的压缩包。war 包中的文件按照一定目录结构来组织。\n一般其根目录下包含有.html和.jsp文件，或者包含有这两种文件的目录，另外还有WEB-INF目录。通常在WEB-INF目录下含有一个web.xml文件和一个 classes目录。web.xml是这个应用的配置文件，而classes目录下则包含编译好的 servlet 类和 jsp，或者 servlet 所依赖的其他类（如 JavaBean）。通常这些所依赖的类也可以打包成 jar 包放在WEB-INF下的lib目录下。\n可以使用jar命令打包war包，一样的步骤，只是后缀war而已。\nwar包放置在tomcat的webapps目录下后，启动tomcat，tomcat会自动解压war。\n部署方式 # 方式一 # 将web项目或war包，放入到tomcat\\webapps文件夹中，然后开启tomcat服务器，打开浏览器，输入localhost:8080/项目名/资源名\n方式二 # 在路径tomcat/conf/Catalina/localhost，在localhost中，建立配置文件：（xxx.xml），内容如下\n\u0026lt;Context path=\u0026#34;工程的访问路径\u0026#34; docBase=\u0026#34;工程目录路径\u0026#34;/\u0026gt; 如果没有此目录，可以自行创建或者启动一次tomcat就会自动创建\n此方法的工程不需要放在webapps中，可以放置在磁盘任何地方\nweb.xml # \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;web-app xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xmlns=\u0026#34;http://java.sun.com/xml/ns/javaee\u0026#34; xsi:schemaLocation=\u0026#34;http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd\u0026#34; id=\u0026#34;WebApp_ID\u0026#34; version=\u0026#34;3.0\u0026#34;\u0026gt; \u0026lt;/web-app\u0026gt; web.xml是Java Web项目的配置文件，一般的Web工程都会用到web.xml来配置，主要用来配置Listener，Filter，Servlet等。\n但需要注意的是：web.xml并不是必须的，一个web工程可以没有web.xml文件\nweb.xml文件的作用包括：\n配置Servlet：您可以在web.xml文件中定义和配置Servlet，包括Servlet的名称、类名、URL映射、初始化参数等。Servlet是用于处理Web请求和生成动态内容的Java类。 配置过滤器（Filter）：过滤器用于在请求到达Servlet之前或响应返回给客户端之前执行一些预处理或后处理操作。您可以在web.xml中配置过滤器，包括过滤器的名称、类名、URL映射、初始化参数等。 配置监听器（Listener）：监听器用于监听Web应用程序中发生的事件，例如Web应用程序的启动和销毁、会话的创建和销毁等。您可以在web.xml中配置监听器，包括监听器的类名和配置参数。 配置错误页面：您可以在web.xml中定义错误页面，用于在发生错误时显示自定义的错误信息。可以配置不同类型的错误和对应的错误页面。 配置会话管理：您可以在web.xml中配置会话管理器，以定义会话的超时时间、会话跟踪方式等。 其他配置项：web.xml还可以包含其他配置项，如欢迎文件列表、MIME类型映射、安全约束等。 servlet # 定义了一个名为HelloServlet的Servlet，并将其映射到URL模式为/hello的请求\n\u0026lt;servlet\u0026gt; \u0026lt;servlet-name\u0026gt;HelloServlet\u0026lt;/servlet-name\u0026gt; \u0026lt;servlet-class\u0026gt;com.example.HelloServlet\u0026lt;/servlet-class\u0026gt; \u0026lt;/servlet\u0026gt; \u0026lt;servlet-mapping\u0026gt; \u0026lt;servlet-name\u0026gt;HelloServlet\u0026lt;/servlet-name\u0026gt; \u0026lt;url-pattern\u0026gt;/hello\u0026lt;/url-pattern\u0026gt; \u0026lt;/servlet-mapping\u0026gt; filter # 定义了一个名为LoggingFilter的过滤器，并将其应用于所有请求。\n\u0026lt;filter\u0026gt; \u0026lt;filter-name\u0026gt;LoggingFilter\u0026lt;/filter-name\u0026gt; \u0026lt;filter-class\u0026gt;com.example.LoggingFilter\u0026lt;/filter-class\u0026gt; \u0026lt;/filter\u0026gt; \u0026lt;filter-mapping\u0026gt; \u0026lt;filter-name\u0026gt;LoggingFilter\u0026lt;/filter-name\u0026gt; \u0026lt;url-pattern\u0026gt;/*\u0026lt;/url-pattern\u0026gt; \u0026lt;/filter-mapping\u0026gt; welcome-file-list # 指定了在访问根目录时，默认显示的文件（例如index.html或index.jsp）。\n\u0026lt;welcome-file-list\u0026gt; \u0026lt;welcome-file\u0026gt;index.html\u0026lt;/welcome-file\u0026gt; \u0026lt;welcome-file\u0026gt;index.jsp\u0026lt;/welcome-file\u0026gt; \u0026lt;/welcome-file-list\u0026gt; error-page # 指定了当发生404错误时，显示error404.html页面\n\u0026lt;error-page\u0026gt; \u0026lt;error-code\u0026gt;404\u0026lt;/error-code\u0026gt; \u0026lt;location\u0026gt;/error404.html\u0026lt;/location\u0026gt; \u0026lt;/error-page\u0026gt; display-name # 用于指定Web应用程序的显示名称，也就是根路径\n\u0026lt;display-name\u0026gt;myweb\u0026lt;/display-name\u0026gt; context-param # 用于定义全局的上下文参数，供整个Web应用程序使用\n\u0026lt;context-param\u0026gt; \u0026lt;param-name\u0026gt;database-url\u0026lt;/param-name\u0026gt; \u0026lt;param-value\u0026gt;jdbc:mysql://localhost:3306/mydb\u0026lt;/param-value\u0026gt; \u0026lt;/context-param\u0026gt; mime-mapping # 用于将文件扩展名映射到MIME类型，指示浏览器如何处理特定类型的文件\n\u0026lt;mime-mapping\u0026gt; \u0026lt;extension\u0026gt;pdf\u0026lt;/extension\u0026gt; \u0026lt;mime-type\u0026gt;application/pdf\u0026lt;/mime-type\u0026gt; \u0026lt;/mime-mapping\u0026gt; session-config # 用于配置会话管理相关的参数，如会话超时时间和Cookie配置\n\u0026lt;session-config\u0026gt; \u0026lt;session-timeout\u0026gt;30\u0026lt;/session-timeout\u0026gt; \u0026lt;cookie-config\u0026gt; \u0026lt;name\u0026gt;SESSIONID\u0026lt;/name\u0026gt; \u0026lt;path\u0026gt;/\u0026lt;/path\u0026gt; \u0026lt;http-only\u0026gt;true\u0026lt;/http-only\u0026gt; \u0026lt;/cookie-config\u0026gt; \u0026lt;/session-config\u0026gt; security-constraint # 用于定义安全约束，限制对某些资源的访问。可以指定受保护的URL模式、所需的角色等\n\u0026lt;security-constraint\u0026gt; \u0026lt;web-resource-collection\u0026gt; \u0026lt;web-resource-name\u0026gt;Protected Area\u0026lt;/web-resource-name\u0026gt; \u0026lt;url-pattern\u0026gt;/admin/*\u0026lt;/url-pattern\u0026gt; \u0026lt;http-method\u0026gt;GET\u0026lt;/http-method\u0026gt; \u0026lt;/web-resource-collection\u0026gt; \u0026lt;auth-constraint\u0026gt; \u0026lt;role-name\u0026gt;admin\u0026lt;/role-name\u0026gt; \u0026lt;/auth-constraint\u0026gt; \u0026lt;/security-constraint\u0026gt; login-config # 用于配置Web应用程序的登录设置，包括登录页面和错误页面。\n\u0026lt;login-config\u0026gt; \u0026lt;auth-method\u0026gt;FORM\u0026lt;/auth-method\u0026gt; \u0026lt;form-login-config\u0026gt; \u0026lt;form-login-page\u0026gt;/login.jsp\u0026lt;/form-login-page\u0026gt; \u0026lt;form-error-page\u0026gt;/login-error.jsp\u0026lt;/form-error-page\u0026gt; \u0026lt;/form-login-config\u0026gt; \u0026lt;/login-config\u0026gt; jsp-config # 用于配置JSP引擎的相关设置，如JSP编译选项、标签库等\n\u0026lt;jsp-config\u0026gt; \u0026lt;taglib\u0026gt; \u0026lt;taglib-uri\u0026gt;http://example.com/mytags\u0026lt;/taglib-uri\u0026gt; \u0026lt;taglib-location\u0026gt;/WEB-INF/mytags.tld\u0026lt;/taglib-location\u0026gt; \u0026lt;/taglib\u0026gt; \u0026lt;/jsp-config\u0026gt; ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/fb007bea/0172a8d7/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJavaEE \n    \u003cdiv id=\"javaee\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#javaee\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eJava EE（现在称为Jakarta EE）是Java平台的一个扩展，专注于开发和运行企业级应用程序。它提供了一组标准规范和API，用于构建可扩展、安全、可靠且高性能的分布式应用程序。Java EE涵盖了广泛的功能领域，包括Web应用程序开发、持久化、消息传递、事务管理、安全性等。\u003c/p\u003e","title":"1、JavaWeb与Tomcat","type":"posts"},{"content":" Linux # Linux 内核最初只是由芬兰人林纳斯·托瓦兹（Linus Torvalds）在赫尔辛基大学上学时出于个人爱好而编写的。\nLinux 是一套免费使用和自由传播的类Unix（并不是基于Unix）操作系统，是一个基于 POSIX 和 UNIX 的多用户、多任务、支持多线程和多 CPU 的操作系统。\nLinux 能运行主要的 UNIX 工具软件、应用程序和网络协议。它支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想，是一个性能稳定的多用户网络操作系统。\nLinux发行版 # Linux 的发行版说简单点就是将 Linux 内核与应用软件做一个打包。\n目前市面上较知名的发行版有：Ubuntu、RedHat、CentOS、Debian、Fedora、SuSE、OpenSUSE、Arch Linux、SolusOS 等。\nLinux的应用领域 # 今天各种场合都有使用各种 Linux 发行版，从嵌入式设备到超级计算机，并且在服务器领域确定了地位，通常服务器使用 LAMP（Linux + Apache + MySQL + PHP）或 LNMP（Linux + Nginx+ MySQL + PHP）组合。\n目前 Linux 不仅在家庭与企业中使用，并且在政府中也很受欢迎。\n巴西联邦政府由于支持 Linux 而世界闻名。 有新闻报道俄罗斯军队自己制造的 Linux 发布版的，做为 G.H.ost 项目已经取得成果。 印度的 Kerala 联邦计划在向全联邦的高中推广使用 Linux。 在西班牙的一些地区开发了自己的 Linux 发布版，并且在政府与教育领域广泛使用，如 Extremadura 地区的 gnuLinEx 和 Andalusia 地区的 Guadalinex。 葡萄牙同样使用自己的 Linux 发布版 Caixa Mágica，用于 Magalh?es 笔记本电脑和 e-escola 政府软件。 法国和德国同样开始逐步采用 Linux。 和Windows的区别 # 目前国内 Linux 更多的是应用于服务器上，而桌面操作系统更多使用的是 Windows。主要区别如下\n比较 Windows Linux 界面 界面统一，外壳程序固定所有 Windows 程序菜单几乎一致，快捷键也几乎相同 图形界面风格依发布版不同而不同，可能互不兼容。GNU/Linux 的终端机是从 UNIX 传承下来，基本命令和操作方法也几乎一致。 驱动程序 驱动程序丰富，版本更新频繁。默认安装程序里面一般包含有该版本发布时流行的硬件驱动程序，之后所出的新硬件驱动依赖于硬件厂商提供。对于一些老硬件，如果没有了原配的驱动有时很难支持。另外，有时硬件厂商未提供所需版本的 Windows 下的驱动，也会比较头痛。 由志愿者开发，由 Linux 核心开发小组发布，很多硬件厂商基于版权考虑并未提供驱动程序，尽管多数无需手动安装，但是涉及安装则相对复杂，使得新用户面对驱动程序问题（是否存在和安装方法）会一筹莫展。但是在开源开发模式下，许多老硬件尽管在Windows下很难支持的也容易找到驱动。HP、Intel、AMD 等硬件厂商逐步不同程度支持开源驱动，问题正在得到缓解。 使用 使用比较简单，容易入门。图形化界面对没有计算机背景知识的用户使用十分有利。 图形界面使用简单，容易入门。文字界面，需要学习才能掌握。 学习 系统构造复杂、变化频繁，且知识、技能淘汰快，深入学习困难。 系统构造简单、稳定，且知识、技能传承性好，深入学习相对容易。 软件 每一种特定功能可能都需要商业软件的支持，需要购买相应的授权。 大部分软件都可以自由获取，同样功能的软件选择较少。 Linux的目录结构 # /bin：bin 是 Binaries (二进制文件) 的缩写, 这个目录存放着最经常使用的命令。\n/boot：这里存放的是启动 Linux 时使用的一些核心文件，包括一些连接文件以及镜像文件。\n**/dev **：dev 是 Device(设备) 的缩写, 该目录下存放的是 Linux 的外部设备，在 Linux 中访问设备的方式和访问文件的方式是相同的。\n/etc：etc 是 Etcetera(等等) 的缩写,这个目录用来存放所有的系统管理所需要的配置文件和子目录。\n/home：用户的主目录（除了超级管理员root），在 Linux 中，每个用户都有一个自己的目录，一般该目录名是以用户的账号命名的，如上图中的 alice、bob 和 eve。\n/lib：lib 是 Library(库) 的缩写这个目录里存放着系统最基本的动态连接共享库，其作用类似于 Windows 里的 DLL 文件。几乎所有的应用程序都需要用到这些共享库。\n/lost+found：这个目录一般情况下是空的，当系统非法关机后，这里就存放了一些文件。\n/media：linux 系统会自动识别一些设备，例如U盘、光驱等等，当识别后，Linux 会把识别的设备挂载到这个目录下。\n/mnt：系统提供该目录是为了让用户临时挂载别的文件系统的，我们可以将光驱挂载在 /mnt/ 上，然后进入该目录就可以查看光驱里的内容了。\n/opt：opt 是 optional(可选) 的缩写，这是给主机额外安装软件所摆放的目录。比如你安装一个ORACLE数据库则就可以放到这个目录下。默认是空的。\n/proc：proc 是 Processes(进程) 的缩写，/proc 是一种伪文件系统（也即虚拟文件系统），存储的是当前内核运行状态的一系列特殊文件，这个目录是一个虚拟的目录，它是系统内存的映射，我们可以通过直接访问这个目录来获取系统信息。这个目录的内容不在硬盘上而是在内存里，我们也可以直接修改里面的某些文件，比如可以通过下面的命令来屏蔽主机的ping命令，使别人无法ping你的机器：\necho 1 \u0026gt; /proc/sys/net/ipv4/icmp_echo_ignore_all /root：该目录为系统管理员，也称作超级权限者的用户主目录。\n/sbin：s 就是 Super User 的意思，是 Superuser Binaries (超级用户的二进制文件) 的缩写，这里存放的是系统管理员使用的系统管理程序。\n/selinux：这个目录是 Redhat/CentOS 所特有的目录，Selinux 是一个安全机制，类似于 windows 的防火墙，但是这套机制比较复杂，这个目录就是存放selinux相关的文件的。\n/srv：该目录存放一些服务启动之后需要提取的数据。\n/sys：这是 Linux2.6 内核的一个很大的变化。该目录下安装了 2.6 内核中新出现的一个文件系统 sysfs 。sysfs 文件系统集成了下面3种文件系统的信息：针对进程信息的 proc 文件系统、针对设备的 devfs 文件系统以及针对伪终端的 devpts 文件系统。该文件系统是内核设备树的一个直观反映。当一个内核对象被创建的时候，对应的文件和目录也在内核对象子系统中被创建。\n/tmp：tmp 是 temporary(临时) 的缩写这个目录是用来存放一些临时文件的。\n/usr：usr 是 unix shared resources(共享资源) 的缩写，这是一个非常重要的目录，用户的很多应用程序和文件都放在这个目录下，类似于 windows 下的 program files 目录。\n/usr/bin：系统用户使用的应用程序。\n/usr/sbin：超级用户使用的比较高级的管理程序和系统守护程序。\n/usr/src：内核源代码默认的放置目录。\n/var：var 是 variable(变量) 的缩写，这个目录中存放着在不断扩充着的东西，我们习惯将那些经常被修改的目录放在这个目录下。包括各种日志文件。\n/run：是一个临时文件系统，存储系统启动以来的信息。当系统重启时，这个目录下的文件应该被删掉或清除。如果你的系统上有 /var/run 目录，应该让它指向 run。\n在 Linux 系统中，有几个目录是比较重要的，平时需要注意不要误删除或者随意更改内部文件。\n/etc： 上边也提到了，这个是系统中的配置文件，如果你更改了该目录下的某个文件可能会导致系统不能启动。 /bin, /sbin, /usr/bin, /usr/sbin: 这是系统预设的执行文件的放置目录，比如 ls 就是在 /bin/ls 目录下的。值得提出的是，/bin, /usr/bin 是给系统用户使用的指令（除root外的通用户），而/sbin, /usr/sbin 则是给 root 使用的指令。 /var： 这是一个非常重要的目录，系统上跑了很多程序，那么每个程序都会有相应的日志产生，而这些日志就被记录到这个目录下，具体在 /var/log 目录下，另外 mail 的预设放置也是在这里。 ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/26654e7b/1a145064/b8a1e89c/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eLinux \n    \u003cdiv id=\"linux\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#linux\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eLinux 内核最初只是由芬兰人林纳斯·托瓦兹（Linus Torvalds）在赫尔辛基大学上学时出于个人爱好而编写的。\u003c/p\u003e","title":"1、Linux","type":"posts"},{"content":" Maven # Maven 是一个项目管理工具，它包含了一个项目对象模型 (POM： Project Object Model)，一组标准集合，一个项目生命周期(Project Lifecycle)，一个依赖管理系统(Dependency Management System)，和用来运行定义在生命周期阶段中插件(plugin)目标的逻辑 。\n解决的问题 # 我们需要引用各种jar包，尤其是比较大的工程，引用的jar包往往有几十个乃至上百个，每用到一种jar包，都需要手动引入工程目录，而且经常遇到各种让人抓狂的jar包冲突，版本冲突。 我们写好的java文件，需要通过javac编译成class文件才能交给JVM运行。这项工作可以由各种集成开发工具帮我们完成Eclipse、IDEA等都可以将代码即时编译。 世界上没有不存在bug的代码，因此写完了代码，我们还要写一些单元测试，然后一个个的运行来检验代码质量。 再优雅的代码也是要被其他人使用的。我们后面还需要把代码与各种配置文件、资源整合到一起，进行打包。如果是web项目，还需要将之发布到服务器。 Maven解决了以上问题，可以把你从上面的繁琐工作中解放出来，能帮你构建工程，管理 jar包，编译代码，还能帮你自动运行单元测试，打包，生成报表，甚至能帮你部署项目，生成 Web 站点。\nMaven核心功能 # 依赖管理 # 传统的 WEB 项目中，我们必须将工程所依赖的 jar 包复制到工程中，导致了工程的变得很大。 Maven项目中不直接将jar包导入到工程中，而是通过在pom.xml文件中添加所需jar包的坐标。项目运行时，通过读取坐标到一个专门用于存放jar包仓库（Maven仓库）找到相应的jar包。 例如我们有两个项目都需要使用mysql-connector-java-8.0.13.jar，如果没有Maven，我们就需要将这个jar文件copy到两个项目中；使用Maven以后，我们只需要下载这个jar文件到Maven仓库，然后两个项目都可以使用同一份jar依赖。 项目构建 # 指的是项目从编译、测试、打包、安装，部署整个过程都交给maven进行管理，这个过程称为构建。整个过程，maven可以一键执行。\nMaven仓库 # 本地仓库 仓库在本地磁盘上，maven通过pom.xml中的依赖坐标，先从本地仓库中寻找，如果没有找到就会从远程仓库中寻找并下载，并且保存在本地仓库中，下次使用就不需要下载了。 远程仓库 中央仓库 由Maven官方团队维护，例如http://repo1.maven.org/maven2，由于国内下载速度很慢，所以我们一般使用阿里仓库 第三方仓库 由公司或其他组织搭建的私服，例如阿里的Maven仓库：https://maven.aliyun.com/repository/central/ 安装和配置 # 1、官网下载 # 官网地址：http://maven.apache.org/download.cgi\n由于Maven使用Java语言开发，具有跨平台性，所以Maven不区分操作系统\n解压后放到无中文，无空格的目录下\n目录结构\nbin：存放了 maven 的命令，比如mvn tomcat7:run\nboot：存放了一些 maven 本身的引导程序，如类加载器等\nconf：存放了 maven 的一些配置文件，如setting.xml文件\nlib：存放了 maven 本身运行所需的一些 jar 包\n2、配置环境变量 # 将maven安装目录配置到环境变量MAVEN_HOME\n在path中添加%MAVEN_HOME%\\bin\n注意：Maven的运行依赖于JAVA_HOME\n配置完成后，可以使用命令：mvn -v验证\n3、配置本地仓库路径 # maven/conf/settings.xml\n\u0026lt;localRepository\u0026gt;E:/Maven/maven-jar\u0026lt;/localRepository\u0026gt; 4、配置镜像下载 # 由于默认服务器在国外，所以配置镜像可以提升下载速度\n阿里镜像\n\u0026lt;mirror\u0026gt; \u0026lt;id\u0026gt;alimaven\u0026lt;/id\u0026gt; \u0026lt;mirrorOf\u0026gt;central\u0026lt;/mirrorOf\u0026gt; \u0026lt;name\u0026gt;aliyun maven\u0026lt;/name\u0026gt; \u0026lt;!--老地址 \u0026lt;url\u0026gt;http://maven.aliyun.com/nexus/content/groups/public/\u0026lt;/url\u0026gt; --\u0026gt; \u0026lt;!--新地址--\u0026gt; \u0026lt;url\u0026gt;https://maven.aliyun.com/repository/central/\u0026lt;/url\u0026gt; \u0026lt;/mirror\u0026gt; 5、初始化仓库 # mvn help:system\n首次执行mvn help:system命令，Maven相关工具自动帮我们到Maven中央仓库下载缺省的或者Maven中央仓库更新的各种配置文件和类库（jar包)到Maven本地仓库中。\n下载完各种文件后，mvn help:system命令会打印出所有的Java系统属性和环境变量，这些信息对我们日常的编程工作很有帮助。\nMaven工程的目录结构 # 注意：maven工程必须按照约定的目录结构创建\nsrc main java：存放java源代码 resources：项目资源文件，例如各类配置文件 test java：测试程序源码 resources：测试资源文件 pom.xml：项目对象模型，主要用来描述项目是如何组成的及构建的 常用命令 # # 查看maven版本 mvn -version # 或者使用 mvn -v # 初始化仓库 mvn help:sysetem # 清除target目录 mvn clean # 编译项目 mvn compile # 运行测试代码 mvn test # 项目打包 mvn package # maven支持组合命令使用，例如先清除target再打包 mvn clean package # maven可以使用-Dparam=value给插件传参 # 例如可以使用-Dmaven.test.skip=true，给maven-surefire-plugin插件传参，不执行测试用例，也不编译测试用例类 mvn package -Dmaven.test.skip=true # 发布到本地仓库 mvn install # 发布到本地仓库及远程仓库 mvn deploy # 生成站点文档 mvn site Maven的生命周期 # Maven内置了三个声明周期：clean、default、site，分别对应了三种不同的处理项目的过程。在每个生命周期的过程中都会有很多的阶段（phase），这些阶段按照既定的顺序执行来完成一个项目的构建，由于Maven的声明周期是抽象的，也就是说，阶段本身不具备处理项目的能力。所以各个阶段的工作需要由插件（plugin）来完成，一个插件通常可以完成一个或者多个阶段的工作。每个阶段的工作对应插件中的一个目标（goal）。不同的插件结合起来，就完成了整个项目的构建，一个阶段也可以绑定一个或多个插件目标。\nMaven中内置了三套生命周期（依据不同的目的，定义了三种处理项目的过程）\nclean lifecycle 负责清理项目 default lifecycle 负责项目的构建、发布 site lifecycle 负责生成项目站点，即API文档信息网站 阶段（phase） # 每个生命周期由若干不同的**阶段（Phase）**组成。阶段代表了生命周期的特殊步骤。\nmaven的每个阶段都可以使用mvn phasename来进行命令行调用，但是用连字符（pre-*、post-*、或 process-*）命名的阶段通常不会从命令行直接调用。这些阶段对构建进行排序，在构建之外生成没有用处的中间结果。\nclean lifecycle # clean 生命周期包含以下阶段：\npre-clean：在清理之前完成一些所需的工作； clean：删除之前构建出的所有文件； post-clean：在清理之后完成一些所需的工作。 default lifecycle # default 生命周期包含了实际构建时需要执行的所有步骤，是最重要的生命周期。该生命周期包含以下阶段：\nvalidate：验证项目是否正确，所有必要信息是否可用； initialize：初始化构建状态，例如设置 property 或创建目录； generate-sources：生成要包含在编译中的任何源代码； process-sources：处理源代码，例如替换所有引用的值； generate-resources：生成要包含在包中的资源； process-resources：将资源复制并处理到目标目录中，准备打包； compile：编译项目的源代码； process-classes：对编译生成的文件进行后期处理，例如对 Java 类进行字节码增强； generate-test-sources：生成要包含在编译中的任何测试源代码； process-test-sources：处理测试源代码，例如替换所有引用的值； generate-test-resources：为测试创建资源； process-test-resources：将资源复制并处理到测试目标目录中； test-compile：将测试源代码编译到测试目标目录中； process-test-classes：对测试编译生成的文件进行后期处理，例如对 Java 类进行字节码增强； test：使用合适的单元测试框架运行测试。这些测试不应要求打包或部署代码； prepare-package：在实际打包之前，执行准备打包所需的任何操作。这通常会生成已处理但尚未打包的所有文件和目录; package：获取编译后的代码，并将其打包为可分发的格式，如 JAR； pre-integration-test：在执行集成测试之前执行所需的操作。这可能涉及一些事项，如设置所需环境等； integration-test：如有必要，处理包并将其部署到可以运行集成测试的环境中； post-integration-test：执行集成测试后执行所需的操作。这可能包括清理环境； verify：运行任何检查以验证包是否有效并符合质量标准； install：将包安装到本地仓库中，作为本地其他项目中的依赖项使用； deploy：在集成或发布环境中完成，将最终包复制到远程仓库，以便与其他开发人员和项目共享。 常用的maven命令，在执行的时候，其实也就是执行了一部分阶段，例如\nmvn compile：执行从validate至compile阶段，完成编译主源代码编译 mvn package：执行从validate至package阶段，完成打包 mvn install：执行从validate至install阶段，完成包安装到本地仓库 mvn deploy：执行从validate至deploy阶段，完成包部署到中心库中 site lifecycle # pre-site：在生成实际的项目站点之前执行所需的流程； site：生成项目的站点文档； post-site：执行完成站点生成所需的流程； site-deploy：将生成的站点文档部署到指定的 web 服务器。 IDE集成Maven # Idea # Eclipse # ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/4ee304f3/bdf3b952/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eMaven \n    \u003cdiv id=\"maven\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#maven\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eMaven 是一个项目管理工具，它包含了一个项目对象模型 (POM： Project Object Model)，一组标准集合，一个项目生命周期(Project Lifecycle)，一个依赖管理系统(Dependency Management System)，和用来运行定义在生命周期阶段中插件(plugin)目标的逻辑 。\u003c/p\u003e","title":"1、Maven","type":"posts"},{"content":" Spring概述 # Spring框架是03年由Rod Johnson创建 该框架的主要作用：让我们的应用程序满足“高内聚，低耦合”，并始终遵循面向接口编程的思想，来做到松散耦合 Spring不是一个功能性框架，主要解决的是业务逻辑层和其他各层的耦合问题 Spring框架核心 IOC（容器），把创建、销毁对象的过程交给Spring进行管理 AOP（面向切面编程），可以在不修改源代码的情况下，进行功能的增强 Spring的特点 # 免费开源，功能不够可以自己去扩展 它使用IOC容器，管理项目中的所有的组件，以及维护组件之间的关系 它为企业应用开发，或者互联网应用开发提供了一套非常完整的解决方案 是一个轻量级的开源的JavaEE的框架 Spring的优点 # 方便程序解耦，简化开发 （高内聚低耦合） 它的底层实现采用的是：工厂 + 反射 + 配置文件 它可以帮助程序员去创建组件的实例 它可以帮助程序员去管理组件之间的依赖关系 AOP编程的支持 Spring提供面向切面编程，可以方便的实现对程序进行权限拦截、运行监控等功能 声明式事务的支持 只需要通过配置就可以完成对事务的管理，而无需手动编程 方便程序的测试 Spring对Junit4支持，可以通过注解方便的测试Spring程序 方便集成各种优秀框架 Spring不排斥各种优秀的开源框架，其内部提供了对各种优秀框架（如：Struts、Hibernate、MyBatis、Quartz等）的直接支持 降低JavaEE API的使用难度 Spring 对JavaEE开发中非常难用的一些API（JDBC、JavaMail、远程调用等），都提供了封装，使这些API应用难度大大降低 Spring体系结构 # 1、Core Container（核心容器） # spring-beans、spring-core模块：是Spring框架的核心模块，包含了控制反转（Inversion of Control, IOC）和依赖注入（Dependency Injection，DI）。BeanFactory使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离。但BeanFactory实例化后并不会自动实例化Bean，只有当Bean被使用时，BeanFactory才会对该 Bean 进行实例化与依赖关系的装配。\nspring-context模块：构架于核心模块之上，扩展了BeanFactory，为它添加了Bean生命周期控制、框架事件体系及资源加载透明化等功能。此外，该模块还提供了许多企业级支持，如邮件访问、远程访问、任务调度等，ApplicationContext是该模块的核心接口，它的超类是BeanFactory。与BeanFactory不同，ApplicationContext实例化后会自动对所有的单实例Bean进行实例化与依赖关系的装配，使之处于待用状态。\nspring-spel模块：是统一表达式语言（EL）的扩展模块，可以查询、管理运行中的对象，同时也可以方便地调用对象方法，以及操作数组、集合等。它的语法类似于传统EL，但提供了额外的功能，最出色的要数函数调用和简单字符串的模板函数。EL的特性是基于Spring产品的需求而设计的，可以非常方便地同Spring IOC进行交互。\n2、AOP # spring-aop模块：是Spring的另一个核心模块，是AOP主要的实现模块。作为继OOP后对程序员影响最大的编程思想之一，AOP极大地拓展了人们的编程思路。Spring以JVM的动态代理技术为基础，设计出了一系列的AOP横切实现，比如前置通知、返回通知、异常通知等。同时，Pointcut接口可以匹配切入点，可以使用现有的切入点来设计横切面，也可以扩展相关方法根据需求进行切入。\nspring-aspects模块：集成自AspectJ框架，主要是为Spring提供多种AOP实现方法。\nspring-instrument模块：是基于Java SE中的java.lang.instrument进行设计的，应该算AOP的一个支援模块，主要作用是在JVM启用时生成一个代理类，程序员通过代理类在运行时修改类的字节，从而改变一个类的功能，实现AOP。\nsprint-messaging模块：是从 Spring4 开始新加入的一个模块，主要职责是为 Spring 框架集成一些基础的报文传送应用。\n3、Data Access/Integration（数据访问与集成） # spring-jdbc模块：是Spring 提供的JDBC抽象框架的主要实现模块，用于简化Spring JDBC操作。主要提供JDBC模板方式、关系数据库对象化方式、SimpleJdbc方式、事务管理来简化JDBC编程，主要实现类有JdbcTemplate、SimpleJdbcTemplate及NamedParameterJdbcTemplate。\nspring-tx模块：是Spring JDBC事务控制实现模块。Spring对事务做了很好的封装，通过它的AOP配置，可以灵活地在任何一层配置。但是在很多需求和应用中，直接使用JDBC事务控制还是有优势的。事务是以业务逻辑为基础的，一个完整的业务应该对应业务层里的一个方法，如果业务操作失败，则整个事务回滚，所以事务控制是应该放在业务层的。持久层的设计则应该遵循一个很重要的原则：保证操作的原子性，即持久层里的每个方法都应该是不可分割的。在使用Spring JDBC控制事务时，应该注意其特殊性。\nspring-orm模块：是ORM框架支持模块，主要集成 Hibernate，Java Persistence API（JPA）和 Java Data Objects（JDO）用于资源管理、数据访问对象（DAO）的实现和事务策略。\nspring-oxm模块：主要提供一个抽象层以支撑OXM（OXM是Object-to-XML-Mapping的缩写，它是一个O/M-mapper，将Java对象映射成XML数据，或者将XML数据映射成Java对象），例如JAXB、Castor、XMLBeans、JiBX和XStream等。\nspring-jms模块：它是Spring 4新加入的一个模块，主要职责是为Spring 框架集成一些基础的报文传送应用。\n4、Web # spring-web模块：为Spring提供了最基础的Web支持，主要建立在核心容器之上，通过Servlet或者Listeners来初始化IoC容器，也包含一些与Web相关的支持。\nspring-webmvc模块：是一个Web-Servlet模块，实现了Spring MVC（Model-View- Controller）的Web应用。\nspring-websocket模块：是与Web前端进行全双工通信的协议。\nspring-webflux模块：是一个新的非堵塞函数式 Reactive Web 框架，可以用来建立异步的、非阻塞的、事件驱动的服务，并且扩展性非常好。\n5、Test # spring-test模块：主要为测试提供支持，使得在不需要将程序发布到应用服务器或者连接到其他设施的情况下能够进行一些集成测试或者其他测试，这对于任何企业都是非常重要的。\nSpring常用接口 # BeanFactory和ApplicationContext接口的联系 # ==BeanFactory接口== 一般我们我们不会进行使用，是Spring里面一个内部使用的接口 ==ApplicationContext接口== ApplicationContext接口继承了BeanFactory接口，它们两个的实现类都可以成为Spring容器 Spring容器实际上就是一个超大型的工厂，它的底层：工厂 + 反射 BeanFactory提供了容器的所有功能，那怎么又有一个ApplicationContext，原因是： BeanFactory在产生实例的时候，只会在调用getBean()方式时，才产生实例 ApplicationContext在创建容器实例时，就开始初始化创建所有的组件的实例 我们一般用的更多的是ApplicationContext来作为容器 ApplicationContext除了实现了BeanFactory的所有功能之外，还扩展了很多其他功能：支持i18n（国际化）支持任务调度，支持邮件服务，支持WebSocket…… ApplicationContext接口实现类 # 实现类有： ClassPathXmlApplicationContext FileSystemXmlApplicationContext AnnotationConfigApplicationContext …… 区别： ClassPathXmlApplicationContext使用相对路径加载applicationContext.xml配置文件 FileSystemXmlApplicationContext使用绝对路径加载applicationContext.xml配置文件 AnnotationConfigApplicationContext提供注解支持 但是他们在管理组件，和维护组件的方式上，都是一样的，没有什么区别 在代码中如何启动容器 ApplicationContext context = new ClassPathXmlApplicationContext(\u0026#34;applicationContext.xml\u0026#34;); 配置文件 # 当使用Spring框架进行应用程序开发时，可以使用XML配置文件来定义和组织应用程序的各个组件。XML配置文件充当了Spring框架的核心配置文件，它包含了应用程序的各种配置元素，例如Bean定义、依赖注入、AOP配置、数据源配置等。\n一般来说，XML配置文件的文件名为：applicationContext.xml\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; standalone=\u0026#34;no\u0026#34;?\u0026gt; \u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\u0026#34;\u0026gt; \u0026lt;/beans\u0026gt; Spring配置文件引用其他配置 # 例如现在有一个外部配置文件，other.properties，在其中指定了bean的初始化属性值\nuser.id=1 在Spring配置文件中，加载外部配置文件\n\u0026lt;context:property-placeholder location=\u0026#34;user.properties\u0026#34;/\u0026gt; 使用${}即可引入相应的值\n\u0026lt;bean id=\u0026#34;user\u0026#34; class=\u0026#34;top.ygang.entity.User\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;uid\u0026#34; value=\u0026#34;${user.id}\u0026#34;\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 简单使用 # 1、创建一个maven项目\n2、导入spring-context依赖，即可使用spring的IOC功能，在pom.xml文件中\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-context\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;5.2.9.RELEASE\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 3、定义需要的JavaBean，并提供getter、setter\npublic class User { private String name; private String age; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } @Override public String toString() { return \u0026#34;User{\u0026#34; + \u0026#34;name=\u0026#39;\u0026#34; + name + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, age=\u0026#39;\u0026#34; + age + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#39;}\u0026#39;; } } 5、针对接口进行编写实现类，并完成面向接口编程\n6、编写applicationContext.xml配置文件，在配置文件中，使用标记来声明需要被容器管理的Bean\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; standalone=\u0026#34;no\u0026#34;?\u0026gt; \u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\u0026#34;\u0026gt; \u0026lt;!-- id是组件在容器中唯一标识，class是组件在容器的类的全类名 --\u0026gt; \u0026lt;bean id=\u0026#34;userBean\u0026#34; class=\u0026#34;org.example.bean.UserBean\u0026#34;\u0026gt;\u0026lt;/bean\u0026gt; \u0026lt;/beans\u0026gt; 7、测试\n相较于传统的new User()的方式，我们只需要从容器中获取Bean实例即可\n// 读取配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext(\u0026#34;applicationContext.xml\u0026#34;); // 根据beanId获取bean User user = applicationContext.getBean(\u0026#34;user\u0026#34;, User.class); System.out.println(user); // 根据bean的Class获取bean User bean = applicationContext.getBean(User.class); System.out.println(bean); ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/c70ae83e/0f995af4/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eSpring概述 \n    \u003cdiv id=\"spring概述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#spring%e6%a6%82%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eSpring框架是03年由Rod Johnson创建\u003c/li\u003e\n\u003cli\u003e该框架的主要作用：让我们的应用程序满足“高内聚，低耦合”，并始终遵循面向接口编程的思想，来做到松散耦合\u003c/li\u003e\n\u003cli\u003eSpring不是一个功能性框架，主要解决的是业务逻辑层和其他各层的耦合问题\u003c/li\u003e\n\u003cli\u003eSpring框架核心\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eIOC（容器）\u003c/strong\u003e，把创建、销毁对象的过程交给Spring进行管理\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAOP（面向切面编程）\u003c/strong\u003e，可以在不修改源代码的情况下，进行功能的增强\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eSpring的特点 \n    \u003cdiv id=\"spring的特点\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#spring%e7%9a%84%e7%89%b9%e7%82%b9\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e免费开源，功能不够可以自己去扩展\u003c/li\u003e\n\u003cli\u003e它使用IOC容器，管理项目中的所有的组件，以及维护组件之间的关系\u003c/li\u003e\n\u003cli\u003e它为企业应用开发，或者互联网应用开发提供了一套非常完整的解决方案\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e是一个轻量级的开源的JavaEE的框架\u003c/strong\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003ch2 class=\"relative group\"\u003eSpring的优点 \n    \u003cdiv id=\"spring的优点\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#spring%e7%9a%84%e4%bc%98%e7%82%b9\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e方便程序解耦，简化开发 （高内聚低耦合）\n\u003cul\u003e\n\u003cli\u003e它的底层实现采用的是：工厂 + 反射 + 配置文件\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e它可以帮助程序员去创建组件的实例\u003c/li\u003e\n\u003cli\u003e它可以帮助程序员去管理组件之间的依赖关系\u003c/li\u003e\n\u003cli\u003eAOP编程的支持\n\u003cul\u003e\n\u003cli\u003eSpring提供面向切面编程，可以方便的实现对程序进行权限拦截、运行监控等功能\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e声明式事务的支持\n\u003cul\u003e\n\u003cli\u003e只需要通过配置就可以完成对事务的管理，而无需手动编程\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e方便程序的测试\n\u003cul\u003e\n\u003cli\u003eSpring对Junit4支持，可以通过注解方便的测试Spring程序\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e方便集成各种优秀框架\n\u003cul\u003e\n\u003cli\u003eSpring不排斥各种优秀的开源框架，其内部提供了对各种优秀框架（如：Struts、Hibernate、MyBatis、Quartz等）的直接支持\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e降低JavaEE API的使用难度\n\u003cul\u003e\n\u003cli\u003eSpring 对JavaEE开发中非常难用的一些API（JDBC、JavaMail、远程调用等），都提供了封装，使这些API应用难度大大降低\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eSpring体系结构 \n    \u003cdiv id=\"spring体系结构\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#spring%e4%bd%93%e7%b3%bb%e7%bb%93%e6%9e%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20220413162522550\"\n    data-zoom-src=\"/posts/3ab7256e/3f5635d6/c70ae83e/0f995af4/image/image-20220413162522550.png\"\n    src=\"/posts/3ab7256e/3f5635d6/c70ae83e/0f995af4/image/image-20220413162522550.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"1、Spring","type":"posts"},{"content":" SpringMVC # Spring Web MVC，Spring七大功能之一，就是这里springMVC框架，它主要是一种采用MVC设计模式的表现层框架\n它同样跟Servlet一样，遵循：请求 - 响应模式；同样使用HTTP通讯协议，完成前后端的数据交互\nServlet存在的缺陷 # 太过于依赖Servlet API，强制要求必须继承HTTPServlet\n太过于依赖tomcat容器了，这就导致开发或测试非常的麻烦\n太过于依赖具体的页面技术，例如：JSP\nSpring MVC的优点 # 采用MVC架构模式，以及服务到工作者的架构模式，将表现层同过多种内置组件定义得更加清晰，让页面控制器显示的更加精炼 Spring MVC是Spring框架的7大功能之一，天然支持Spring 由于Spring MVC配置多，但更简单、独立（按需配置） 代码的可重用性很高。通常Spring MVC的配置文件，几乎可以支持任何系统的配置） 扩展性高。Spring MVC依旧是免费开源的产品，如果功能不够，可以修改源码 MVC设计模式 # MVC模式（Model-View-Controller）是软件工程中的一种软件架构模式，把软件系统分为三个基本部分：模型（Model）、视图（View）和控制器（Controller）。MVC模式最早为Trygve Reenskaug提出，为施乐帕罗奥多研究中心（Xerox PARC）的Smalltalk语言发明的一种软件设计模式。MVC可对程序的后期维护和扩展提供了方便，并且使程序某些部分的重用提供了方便。而且MVC也使程序简化，更加直观。\n控制器（Controller）：对请求进行处理，负责请求转发； 视图（View）：界面设计人员进行图形界面设计； 模型（Model）：程序编写程序应用的功能（实现算法等等）、数据库管理； Spring MVC的主要组件 # DispatcherServlet（前端、核心控制器） Spring的MVC框架是围绕DispatcherServlet来设计的，它用来处理所有的HTTP请求和响应。此模块不需要程序员开发。 是整个流程控制的中心，由它调用其它组件处理用户的请求 的存在降低了组件之间的耦合性,系统扩展性提高。由框架实现 HandlerMapping（处理器映射器） HandlerMapping负责根据用户请求的url找到Handler即处理器，springmvc提供了不同的映射器实现不同的映射方式，根据一定的规则去查找，例如：xml配置方式，实现接口方式，注解方式等。由框架实现 Handler（处理器） Handler 是继DispatcherServlet前端控制器的后端控制器，在DispatcherServlet的控制下Handler对具体的用户请求进行处理。由于Handler涉及到具体的用户业务请求，所以一般情况需要程序员根据业务需求开发Handler。 HandlAdapter（处理器适配器） 通过HandlerAdapter对处理器进行执行，这是适配器模式的应用，通过扩展适配器可以对更多类型的处理器进行执行。由框架实现。 ModelAndView（模型与视图） 是springmvc的封装对象，将model和view封装在一起。 ViewResolver（视图解析器） ViewResolver负责将处理结果生成View视图，ViewResolver首先根据逻辑视图名解析成物理视图名即具体的页面地址，再生成View视图对象，最后对View进行渲染将处理结果通过页面展示给用户。 View（视图） 是springmvc的封装对象，是一个接口, springmvc框架提供了很多的View视图类型，包括：jspView、pdfView、freemarkerView等。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户，需要由程序员根据业务需求开发具体的页面。 页面控制器 # Spring MVC的详细架构图 # 用户发送请求至前端控制器DispatcherServlet； DispatcherServlet收到请求后，调用HandlerMapping处理器映射器，请求获取Handle； 处理器映射器根据请求url找到具体的处理器，生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet； DispatcherServlet 调用 HandlerAdapter处理器适配器； HandlerAdapter 经过适配调用 具体处理器(Handler，也叫后端控制器)； Handler执行完成返回ModelAndView； HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet； DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析； ViewResolver解析后返回具体View； DispatcherServlet对View进行渲染视图（即将模型数据填充至视图中） DispatcherServlet响应用户。 ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/617a1910/4920248a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eSpringMVC \n    \u003cdiv id=\"springmvc\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#springmvc\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eSpring Web MVC，Spring七大功能之一，就是这里springMVC框架，它主要是一种采用MVC设计模式的表现层框架\u003c/p\u003e","title":"1、SpringMVC","type":"posts"},{"content":" 什么是SpringSecurity # Spring 是非常流行和成功的 Java 应用开发框架，Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架，提供了一套 Web 应用安全性的完整解决方案。\n一般来说，Web 应用的安全性包括**用户认证（Authentication）和用户授权（Authorization）**两个部分，这两点也是 Spring Security 重要核心功能。\n用户认证 验证某个用户是否为系统中的合法主体，也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录。 用户授权 用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中，不同用户所具有的权限是不同的。比如对一个文件来说，有的用户只能进行读取，而有的用户可以进行修改。一般来说，系统会为不同的用户分配不同的角色，而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。 SpringSecurity 特点 # 和 Spring 无缝整合。 全面的权限控制。 专门为 Web 开发而设计。 旧版本不能脱离 Web 环境使用。 新版本对整个框架进行了分层抽取，分成了核心模块和 Web 模块。单独引入核心模块就可以脱离 Web 环境。 重量级。 和Shiro的选择 # 相对于 Shiro，在 SSM 中整合 Spring Security 都是比较麻烦的操作，所以，Spring Security 虽然功能比 Shiro 强大，但是使用反而没有 Shiro 多（Shiro 虽然功能没有Spring Security 多，但是对于大部分项目而言，Shiro 也够用了）。\n自从有了 Spring Boot 之后，Spring Boot 对于 Spring Security 提供了自动化配置方案，可以使用更少的配置来使用 Spring Security。\n因此，一般来说，常见的安全管理技术栈的组合是这样的：\nSSM + Shiro Spring Boot/Spring Cloud + Spring Security 搭建环境 # 1、SpringBoot项目中添加依赖\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-security\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、编写配置类，配置类可以不用写，由SpringBoot自动配置\n@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() //表单登录 .and() .authorizeRequests() // 认证配置 .anyRequest() // 任何请求 .authenticated(); // 都需要身份验证 } } 3、启动项目进行验证\n这是SpringSecurity框架提供的默认登录密码，默认用户名为user\n4、访问默认登陆页面（这个页面框架自带），localhost:port/login\n权限管理相关概念 # 主体 英文单词：principal 使用系统的用户或设备或从其他系统远程登录的用户等等。简单说就是谁使用系统谁就是主体。 认证 英文单词：authentication 权限管理系统确认一个主体的身份，允许主体进入系统。简单说就是“主体”证明自己是谁。 笼统的认为就是以前所做的登录操作。 授权 英文单词：authorization 将操作系统的“权力”、“授予”、“主体”，这样主体就具备了操作系统中特定功能的能力。 所以简单来说，授权就是给用户分配权限。 底层原理 # 过滤器 # SpringSecurity 本质是一个过滤器链\nWebAsyncManagerIntegrationFilter SecurityContextPersistenceFilter HeaderWriterFilter CsrfFilter LogoutFilter UsernamePasswordAuthenticationFilter 认证过滤器，对/login 的 POST 请求做拦截，校验表单中用户名，密码 DefaultLoginPageGeneratingFilter DefaultLogoutPageGeneratingFilter RequestCacheAwareFilter SecurityContextHolderAwareRequestFilter AnonymousAuthenticationFilter SessionManagementFilter ExceptionTranslationFilter 异常过滤器，用于处理认证授权过程中抛出的异常 FilterSecurityInterceptor 方法级的权限过滤器，对Controller中的方法（接口）进行权限验证， 基本位于过滤链的最底部 两个重要接口 # UserDetailsService和UserDetails # 当什么也没有配置的时候，账号和密码是由 Spring Security 定义生成的。而在实际项目中，账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。\n如果需要自定义逻辑时，只需要实现 UserDetailsService 接口即可。\npublic class LoginUserDetailService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { // 此处就可以从数据库根据userName查询用户信息 // 如果没有查询到，抛出UsernameNotFoundException // throw new UsernameNotFoundException(\u0026#34;用户名不存在\u0026#34;); List roles = new ArrayList(); return new User(\u0026#34;lucy\u0026#34;,\u0026#34;123\u0026#34;,true,false,false,false,null); } } 注意：There is no PasswordEncoder mapped for the id \u0026quot;null\u0026quot;如果按照上面的明文存储的密码，会出现这个异常，原因是因为框架底层默认使用了密码加密器，所以如果需要明文存储，需要在密码前面加上字符串{noop}\nUserDetails接口，这个接口规定了返回的用户主体，他有如下的方法，我们可以使用框架提供的User类，也可以自己实现\n// 表示获取登录用户所有权限 Collection\u0026lt;? extends GrantedAuthority\u0026gt; getAuthorities(); // 表示获取密码 String getPassword(); // 表示获取用户名 String getUsername(); // 表示判断账户是否没有过期 boolean isAccountNonExpired(); // 表示判断账户是否没有被锁定 boolean isAccountNonLocked(); // 表示凭证（密码）是否没有过期 boolean isCredentialsNonExpired(); // 表示当前用户是否没有可用 boolean isEnabled(); PasswordEncoder # 这个接口用于对密码进行加密以及验证，他有如下方法\n// 表示把参数按照特定的解析规则进行解析（加密） String encode(CharSequence rawPassword); // 表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配，则返回 true；如果不匹配，则返回 false。第一个参数表示需要被解析的密码。第二个参数表示存储的密码。 boolean matches(CharSequence rawPassword, String encodedPassword); // 表示如果解析的密码能够再次进行解析且达到更安全的结果则返回 true，否则返回false。默认返回 false。 default boolean upgradeEncoding(String encodedPassword) {return false; } BCryptPasswordEncoder是 Spring Security 官方推荐的密码解析器（PasswordEncoder）的实现类，平时多使用这个解析器。\nBCryptPasswordEncoder 是对 bcrypt 强散列方法的具体实现。是基于 Hash 算法实现的单向加密。可以通过 strength 控制加密强度，默认10。\npublic static void main(String[] args) { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10); String encode = encoder.encode(\u0026#34;abcd@123\u0026#34;); System.out.println(encode); //$2a$10$khPyohbsXmBmszXWiTY2ruinU/gFK9PrF6EPWHID7brPVmgMaqC.S boolean matches = encoder.matches(\u0026#34;abcd@123\u0026#34;, \u0026#34;$2a$10$khPyohbsXmBmszXWiTY2ruinU/gFK9PrF6EPWHID7brPVmgMaqC.S\u0026#34;); System.out.println(matches); //true boolean b = encoder.upgradeEncoding(\u0026#34;$2a$10$khPyohbsXmBmszXWiTY2ruinU/gFK9PrF6EPWHID7brPVmgMaqC.S\u0026#34;); System.out.println(b); //false } 注意：默认使用的PasswordEncoder要求数据库存储的密码格式为{id}密文，他会根据id去判断密码的加密方式，但是我们一般不会采用这种方式，所以就需要替换PasswordEncoder，我们一般使用BCryptPasswordEncoder\n我们只需要将BCryptPasswordEncoder注入Spring容器，框架就会采用这种加密方式进行校验；也可以在Security的配置文件中进行配置\n@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder setPasswordEncoder(){ return new BCryptPasswordEncoder(10); } } 常见登录失败异常 # // 用户名或密码错误 org.springframework.security.authentication.BadCredentialsException; // 用户帐号已过期 org.springframework.security.authentication.AccountExpiredException; // 用户帐号已被锁定 org.springframework.security.authentication.LockedException; // 用户凭证（）已过期 org.springframework.security.authentication.CredentialsExpiredException // 用户已失效 org.springframework.security.authentication.DisabledException; SpringSecurity配置类 # 编写配置类，继承WebSecurityConfigurerAdapter就可以了，可以重写方法configure对于细节进行配置\n配置围绕HttpSecurity，进行链式编程\n@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { } } 配置用户名密码 # configure的另一个重载方法可以进行配置，但是一般用不上，用户名密码都是从数据库查询\n@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser(\u0026#34;root\u0026#34;).password(\u0026#34;1234\u0026#34;).roles(\u0026#34;sys\u0026#34;) .and() .withUser(\u0026#34;lucy\u0026#34;).password(\u0026#34;1234\u0026#34;).roles(\u0026#34;user\u0026#34;); } 配置拦截规则 # @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // 验证请求 .antMatchers(\u0026#34;/user\u0026#34;).hasAnyRole(\u0026#34;sys\u0026#34;,\u0026#34;admin\u0026#34;) // user路径的请求需要具有角色sys或admin其中一种 .antMatchers(HttpMethod.GET,\u0026#34;/to\u0026#34;).permitAll() // to路径下get请求所有人都可以访问 .anyRequest().authenticated(); //剩余的所有请求都需要验证登录 } 常见的验证规则\nanyRequest | 匹配所有请求路径 access | SpringEl表达式结果为true时可以访问 anonymous | 匿名可以访问 denyAll | 用户不能访问 fullyAuthenticated | 用户完全认证可以访问（非remember-me下自动登录） hasAnyAuthority | 如果有参数，参数表示权限，则其中任何一个权限可以访问 hasAnyRole | 如果有参数，参数表示角色，则其中任何一个角色可以访问 hasAuthority | 如果有参数，参数表示权限，则其权限可以访问 hasIpAddress | 如果有参数，参数表示IP地址，如果用户IP和参数匹配，则可以访问 hasRole | 如果有参数，参数表示角色，则其角色可以访问 permitAll | 用户可以任意访问 rememberMe | 允许通过remember-me登录的用户访问 authenticated | 用户登录后可访问 Authority权限和Role角色的区别：\n区别就是Role在验证的时候会自动给传入的字符串加上 ROLE_ 前缀，所以在数据库中的权限字符串需要加上 ROLE_ 前缀；而Authority不会这样。\n添加自定义过滤器 # 例如我们需要在内置UsernamePasswordAuthenticationFilter前面添加一个自定义的token校验过滤器，如果校验完成，后面就不需要再进行账号合法性校验\n@Override protected void configure(HttpSecurity http) throws Exception { // 配置在内置用户名密码认证过滤器之前 http.addFilterBefore(tokenAuthFilter, UsernamePasswordAuthenticationFilter.class); } 重要的内置对象 # SecurityContextHolder # 这里我们存储应用程序当前安全上下文的详细信息，其中包括当前使用应用程序的主体的详细信息。SecurityContextHolder使用ThreadLocal去存储这些详细信息，这意味着安全上下文始终可用于同一执行线程中的方法，即使安全上下文没有作为参数显式传递给这些方法。\n通过方法getContext()，我们可以获得一个SecurityContext对象\nSecurityContext # 存储认证授权的相关信息，实际上就是存储当前用户（主体）账号信息和相关权限。这个接口只有两个方法，Authentication对象的getter、setter。\n我们可以在过滤链的任意一环，使用context.setAuthentication();方法设置Authentication认证对象，在后续的过滤链中，都可以获取使用，查看用户是否认证、权限、角色等信息。\nAuthentication # 认证（主体）对象，主要作用有：\n作为后续验证的入参 获取当前验证通过的用户信息 三个属性：\nprincipal：用户身份，如果是用户/密码认证，这个属性就是UserDetails实例 credentials：通常就是密码，在大多数情况下，在用户验证通过后就会被清除，以防密码泄露。 authorities：用户权限 常用的实现类有UsernamePasswordAuthenticationToken\nAuthenticationManager # AuthenticationManager是用来实现身份认证的API接口，我们可以主动调用该对象中的authenticate对Authentication进行认证，最常用的子类是ProviderManager AuthenticationProvider是某种具体的认证实现，例如DaoAuthenticationProvider用来实现用户/密码认证，JwtAuthenticationProvider实现JWT Token认证 支持多种类型AuthenticationProvider会注入到ProviderManager中，ProviderManager会根据Authentication的类型调用相应类型的AuthenticationProvider 授权 # 一共有两种方式可以设置 基于配置：在SecurityConfig中进行配置，比较麻烦，不容易控制 基于注解：在Controller方法上添加注解来控制 基于注解 # 在SecurityConfig类上添加注解，开启注解配置\n@EnableGlobalMethodSecurity(prePostEnabled = true) 在需要权限控制的Controller方法添加注解即可\n@GetMapping(\u0026#34;/test\u0026#34;) @PreAuthorize(\u0026#34;hasAnyAuthority(\u0026#39;sys\u0026#39;,\u0026#39;admin\u0026#39;)\u0026#34;) public String test(){ return \u0026#34;test\u0026#34;; } 自定义失败处理 # 如果希望在认证失败或授权失败的时候，也可以对请求返回自定义响应，而不是只是在后台抛出异常的话，那么我们可以自定义统一处理机制，在框架内部有如下机制：\n认证过程失败：会出现AuthenticationException，使用AuthenticationEntryPoint进行处理\n授权过程失败：会抛出AccessDeniedException，使用AccessDeniedHandler进行处理\n所以，如果我们需要自定义处理机制的话，只需要实现AuthenticationEntryPoint和AccessDeniedHandler并配置给SecurityConfig就可以了\n@Component public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { ResultVO\u0026lt;Object\u0026gt; result = ResultVO.result(\u0026#34;认证失败或已在别处登录，请进行登录\u0026#34;, ResultCode.UNAUTHORIZED, null); String jsonString = JSON.toJSONString(result); httpServletResponse.setHeader(\u0026#34;Content-Type\u0026#34;,\u0026#34;application/json;charset=utf-8\u0026#34;); httpServletResponse.getWriter().write(jsonString); } } @Component public class AccessDeniedHandlerImpl implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { ResultVO\u0026lt;Object\u0026gt; result = ResultVO.result(\u0026#34;无权限进行此操作\u0026#34;, ResultCode.NOACCESS, null); String jsonString = JSON.toJSONString(result); httpServletResponse.setHeader(\u0026#34;Content-Type\u0026#34;,\u0026#34;application/json;charset=utf-8\u0026#34;); httpServletResponse.getWriter().write(jsonString); } } 配置到SecurityConfig\n@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationEntryPointImpl authenticationEntryPoint; @Autowired private AccessDeniedHandlerImpl accessDeniedHandler; @Override protected void configure(HttpSecurity http) throws Exception { // 配置认证和授权失败过滤器 http.exceptionHandling() .authenticationEntryPoint(authenticationEntryPoint) .accessDeniedHandler(accessDeniedHandler); } } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/82b168e2/6119e38f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是SpringSecurity \n    \u003cdiv id=\"什么是springsecurity\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%afspringsecurity\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eSpring 是非常流行和成功的 Java 应用开发框架，Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架，提供了一套 Web 应用安全性的完整解决方案。\u003c/p\u003e","title":"1、SpringSecurity","type":"posts"},{"content":" Vue # Vue读成view ，而不是V U E Vue是个前端框架，是尤雨溪（中国人）的个人项目 这个框架主要关注点： View和 Model如何快速绑定 Vue的特点 # 非常简单，Easy，好学 遵循一种新的WEB架构模式： MVVM 它可以开发单页面的应用程序（每个单独的页面，就是一个独立的应用程序） 渐进式（从核心技术开发，一层一层的添加内容）与兼容性（允许兼容其他框架或其他技术） 视图组件化 虚拟 DOM（Virtual DOM） MVVM架构 # 之前，我们学过MVC（Model - View - Controller），但是这套东西是后端框架提出来的，不适合前端 他们提出了自己的前端框架模式：MVVM（Model - View - ViewModel） Model依旧是数据，View依旧是视图 ViewModel视图模型绑定对象（监听数据的变化，并实时做到与View进行同步） 视图组件化 # 将网页拆分成多个组件，然后各自开发\n最后，再拼接各个组件，通过拼接组件的方式，完成页面的开发 （类似于：Spring拼接组件）\n虚拟DOM(Virtual DOM) # 以前，很不规范的人在使用Jquery时，存在大量的针对DOM节点进行频繁的操作，这就导致浏览器在解析节点时，显得非常的卡顿\n为了解决这个问题，所以前端提出了 Virtual DOM的说法，Virtual DOM实际上是在内存中，先定义一颗节点树，所有的操作先针对该节点树进行完成，操作完毕之后，再同步到真实DOM对象中，从而提升浏览器在解析节点时的性能浪费\nVue的使用 # 1、引入vue # 引入js文件 # 官网下载：https://cn.vuejs.org/\n1）引入本地的vue.js文件\n2）引入网络，CDN加速vue.js\n\u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; 2、声明Vue实例 # \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;script src=\u0026#34;../js/vue.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div id=\u0026#34;app\u0026#34;\u0026gt; \u0026lt;!-- 声明式渲染 --\u0026gt; {{t1}} \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;script\u0026gt; //创建vue对象，参数是一个配置对象 var v = new Vue({ //用于指定Vue实例服务哪个容器 el:\u0026#34;#app\u0026#34;, //声明的属性值 data:{ t1:\u0026#34;test1\u0026#34;, }, //声明的方法 methods:{ add(){ alert(\u0026#34;add....\u0026#34;) }, } }); //Vue实例的el，也可以是用对象v的原型对象方法$mount()，进行挂载 //v.$mount(\u0026#34;#app\u0026#34;) \u0026lt;/script\u0026gt; \u0026lt;/html\u0026gt; 注意：一个Vue实例只可以对应一个容器，也就是如果el赋值了一个类选择器，也只会有一个生效；同样多个Vue实例也不可以对应同一个实例。\n数据代理：通过对象v可以调用属性t1，实际上，对象v的属性是由Object.defineProperty()方法完成的对_data属性（Vue将data存入了自身的_data属性）的代理，例如：\nlet myVue = { data:{ name: \u0026#34;lucy\u0026#34;, address: \u0026#34;zh\u0026#34; } } Object.defineProperty(myVue,\u0026#34;name\u0026#34;,{ get(){ return myVue.data.name }, set(value){ myVue.data.name = value } }) myVue.name = \u0026#34;tom\u0026#34; //此时修改的myVue的name属性就是调用set()方法修改的 console.log(myVue.name) //此时读取的myVue的name属性就是调用get()方法读取的 // tom 模板语法 # 字符串拼接 # \u0026lt;a :href=\u0026#34;\u0026#39;detailPage.html?workorderId=\u0026#39; + li.workorderId\u0026#34;\u0026gt;详情\u0026lt;/a\u0026gt; 声明式渲染（mustache语法） # mustache语法中，不仅仅可以写变量，也可以写简单的表达式\n格式：{{ message }}\n标签使用v-pre属性的话，那么mustache语法不会被解析\n标签可以使用v-cloak属性的话，只有在vue解析该标签的话，这个标签才会进行展示并且删除该属性(需要和css进行搭配，[v-cloak]{display:none})\nv-once # 如果标签添加这个属性，那么该标签只会展示首次的数据，不会随变量改变而发生界面改变\nv-html\u0026amp;v-text # v-html可以给当前标签添加文本，并解析html标签元素\nv-text可以给当前标签添加文本，但是不解析标签元素\nv-on # v-on可以给节点添加事件\n\u0026lt;!--方法没有形参的情况下()可以省略--\u0026gt; \u0026lt;button v-on:click=\u0026#34;add\u0026#34;\u0026gt;botton\u0026lt;/button\u0026gt; \u0026lt;button v-on:click=\u0026#34;add(1)\u0026#34;\u0026gt;botton\u0026lt;/button\u0026gt; \u0026lt;!-- 简写 --\u0026gt; \u0026lt;button @click=\u0026#34;add\u0026#34;\u0026gt;botton\u0026lt;/button\u0026gt; \u0026lt;button @click=\u0026#34;add(1)\u0026#34;\u0026gt;botton\u0026lt;/button\u0026gt; 支持的事件 # click在元素上按下并释放任意鼠标按键。 dblclick在元素上双击鼠标按钮。 mousedown在元素上按下任意鼠标按钮。 mouseenter指针移到有事件监听的元素内。 mouseleave指针移出元素范围外（不冒泡）。 mousemove指针在元素内移动时持续触发。 mouseover指针移到有事件监听的元素或者它的子元素内。 mouseout指针移出元素，或者移到它的子元素上。 mouseup在元素上释放任意鼠标按键。 pointerlockchange鼠标被锁定或者解除锁定发生时。 pointerlockerror可能因为一些技术的原因鼠标锁定被禁止时。 select有文本被选中。 wheel 滚动鼠标滚轮时 传参 # \u0026lt;div id=\u0026#34;app\u0026#34;\u0026gt; \u0026lt;!-- 如果调用方法没有（）的话，那么默认方法形参为当次触发事件的event对象 --\u0026gt; \u0026lt;button @click=\u0026#34;cli\u0026#34;\u0026gt;click\u0026lt;/button\u0026gt; \u0026lt;!-- 如果需要event对象，也需要其他参数（多参数），那么使用$event --\u0026gt; \u0026lt;button @click=\u0026#34;cli($event,lucy)\u0026#34;\u0026gt;click\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; let v = new Vue({ el:\u0026#34;#app\u0026#34;, methods: { //方法定义了形参 cli:function(event){ console.log(event); } } }) \u0026lt;/script\u0026gt; 修饰符 # @click.stop=\u0026quot;\u0026quot;，阻止当前事件的冒泡\n@click.prevent=\u0026quot;\u0026quot;，阻止默认事件，例如点击提交按钮后form表单的默认提交事件\n@keyup.keyCode|keyAlias=\u0026quot;\u0026quot;，监听键盘的点击\n@click.once=\u0026quot;\u0026quot;，当前事件只会触发一次\n@click.self=\u0026quot;\u0026quot;，只有当前事件target是当前元素时才会触发\n@click.capture=\u0026quot;\u0026quot;，当前元素在捕获阶段处理事件\n@click.native=\u0026quot;\u0026quot;，用于给组件绑定原生的事件\nv-bind # v-bind命令的作用： 可以用来定义标签的属性，单向绑定，也就是变量如果发生变化，那么页面随之发生变化，但是如果页面变化，变量不会变化\n\u0026lt;input type=\u0026#34;text\u0026#34; v-bind:value=\u0026#34;t1\u0026#34;/\u0026gt; \u0026lt;!-- 简写 --\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; :value=\u0026#34;t1\u0026#34;/\u0026gt; 绑定class # \u0026lt;style\u0026gt; .redColor{ color:red; } \u0026lt;/style\u0026gt; \u0026lt;div id=\u0026#34;app\u0026#34;\u0026gt; \u0026lt;!-- 此处:class可以通过一个对象，来绑定多个class，{类名1：boolean，类名2：boolean} --\u0026gt; \u0026lt;h1 :class=\u0026#34;{redColor:isRed}\u0026#34;\u0026gt;hello world！\u0026lt;/h1\u0026gt; \u0026lt;button @click=\u0026#34;isRed = !isRed\u0026#34;\u0026gt;切换颜色\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script\u0026gt; let v = new Vue({ el:\u0026#34;#app\u0026#34;, data:{ isRed:true } }) \u0026lt;/script\u0026gt; :class也可以通过一个数组来动态绑定，:class=\u0026quot;['类名1'，'类名2']\u0026quot;\nv-model # v-model 主要可以做到：view 与model 之间，双向绑定，只可以作用在表单元素上，也就是model的数据变化，view那边可以跟着变化； view的数据变化，model的数据跟着也会发生变化，实际是v-bind和v-value的结合\n\u0026lt;input type=\u0026#34;text\u0026#34; v-model=\u0026#34;t1\u0026#34;/\u0026gt; v-if # v-if是条件渲染指令，它根据表达式的真假来删除和插入元素\n\u0026lt;!--此元素会从dom中删除--\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; v-if=\u0026#34;1 \u0026lt; 0\u0026#34;/\u0026gt; v-else\u0026amp;v-else-if # v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面，否则它将不会被识别。\n\u0026lt;div v-if=\u0026#34;type === \u0026#39;A\u0026#39;\u0026#34;\u0026gt; A \u0026lt;/div\u0026gt; \u0026lt;div v-else-if=\u0026#34;type === \u0026#39;B\u0026#39;\u0026#34;\u0026gt; B \u0026lt;/div\u0026gt; \u0026lt;div v-else\u0026gt; Not A/B/C \u0026lt;/div\u0026gt; v-show # v-show可以做到DOM节点，进行条件显示，频繁切换建议使用v-show\n和v-if的区别 # v-if是通过新增或删除DOM节点，来做到节点是否展示 v-show是通过给节点添加属性style=\u0026quot;display:none\u0026quot;来实现的 \u0026lt;!--会给此元素加style=\u0026#34;display:none\u0026#34;--\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; v-show=\u0026#34;1 \u0026lt; 0\u0026#34;/\u0026gt; v-for # 遍历循环添加元素\n遍历数组 # \u0026lt;!--goods:[{name:\u0026#34;apple\u0026#34;,price:18},{name:\u0026#34;banana\u0026#34;,price:22}]--\u0026gt; \u0026lt;!--g为遍历出来的每个对象，index为索引--\u0026gt; \u0026lt;tr v-for=\u0026#34;(g,index) in goods\u0026#34;\u0026gt; \u0026lt;td\u0026gt;{{g.name}}\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;{{g.price}}\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; 遍历对象 # \u0026lt;!--t1:{name:\u0026#34;apple\u0026#34;,price:18}--\u0026gt; \u0026lt;!--value为对象的属性值，key为对象属性名--\u0026gt; \u0026lt;div v-for=\u0026#34;(value,key) in t1\u0026#34;\u0026gt; {{value}}==={{key}} \u0026lt;/div\u0026gt; 自定义指令 # 是用Vue配置项directives进行声明\n\u0026lt;body\u0026gt; \u0026lt;div id=\u0026#34;app\u0026#34;\u0026gt; \u0026lt;div v-log=\u0026#34;123\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;script\u0026gt; Vue.config.productionTip = false; new Vue({ el:\u0026#34;#app\u0026#34;, directives: { log(element,binding){ console.log(\u0026#34;自定义指令\u0026#34;); console.log(element); console.log(binding); } } }); //自定义指令 //div dom对象 //{name: \u0026#39;log\u0026#39;, rawName: \u0026#39;v-log\u0026#39;, value: 123, expression: \u0026#39;123\u0026#39;, modifiers: {…}, …} \u0026lt;/script\u0026gt; 定义全局指令\nVue.directive(\u0026#34;log\u0026#34;,function(element,binding){ console.log(\u0026#34;自定义指令\u0026#34;); console.log(element); console.log(binding); }) Vue的生命周期 # 生命周期：就是指Vue对象从产生到服务到死亡（GC回收）的完整过程。\n在Vue的整个生命周期过程中，跟Servlet一样会执行很多的默认方法（init(),service(),destroy()） ，同样也定义了非常多的默认方法，在前端，他们把这些默认方法，称为：钩子函数\nbeforeCreate (创建前) 组件实例在创建之前，组件属性计算之前，此时无法获得data、methods中的数据 created（创建后） 组件实例在创建之后，属性绑定，此时DOM还没有被产生 (相当于JQuery中的$(function(){ }))，此时异步请求数据 beforeMount(挂载前) 编译模板、挂载之前 mounted(挂载后) 编译模板、挂载之后 （产生DOM节点） beforeUpdate(更新前) 组件更新之前 updated(更新后) 组件更新之后 beforeDestroy(销毁前) 组件销毁之前调用 组件销毁就是GC回收 destroyed(销毁后) 组件销毁之后调用 Vue获取DOM元素 # 除了vue的方法以外，还可以使用jquery或js的方法来获取\n\u0026lt;div id=\u0026#34;app\u0026#34;\u0026gt; \u0026lt;input ref=\u0026#34;name\u0026#34;\u0026gt; \u0026lt;button @click=\u0026#34;m1\u0026#34;\u0026gt;get\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script\u0026gt; let vm = new Vue({ el:\u0026#34;#app\u0026#34;, methods:{ m1(){ //使用this.$refs.DOM元素ref属性值，获取该DOM元素 console.log(this.$refs.name.value); } } }); \u0026lt;/script\u0026gt; 监听属性 # 使用watch属性，进行监听属性\n在vue形参外监听 # var vm = new Vue({ el: \u0026#39;#app\u0026#39;, data: { counter: 1 } }); //监听counter这个属性改变前后的值 vm.$watch(\u0026#39;counter\u0026#39;, function(after, before) { alert(\u0026#39;改变前为:\u0026#39; + before + \u0026#39; ，改变后为： \u0026#39; + after); }); //一个形参代表改变后的值 vm.$watch(\u0026#39;counter\u0026#39;, function(after) { alert(\u0026#39;改变后为：\u0026#39; + after); }); 在vue形参内监听 # var vue = new Vue({ el:\u0026#34;div\u0026#34;, data:{ test:\u0026#34;111\u0026#34;, }, watch:{ test:function (after, before){ alert(\u0026#39;改变前为:\u0026#39; + before + \u0026#39; ，改变后为： \u0026#39; + after); } } }); immediate # 立即监听，也就是组件创建完成后就进行一次监听\nvar vue = new Vue({ el:\u0026#34;div\u0026#34;, data:{ test:\u0026#34;111\u0026#34;, }, watch:{ //此时监听属性的值应该是一个对象，才可以添加配置项 test:{ immediate: true, handler(after, before){ alert(\u0026#39;改变前为:\u0026#39; + before + \u0026#39; ，改变后为： \u0026#39; + after); } } } }); deep # 深度监视，也就是可以监听多层结构的数据\nvar vue = new Vue({ el:\u0026#34;div\u0026#34;, data:{ test:{ a: 1, b: 2 }, }, watch:{ //此时监听属性的值应该是一个对象，才可以添加配置项 test:{ deep: true, handler(after, before){ alert(\u0026#39;改变前为:\u0026#39; + before + \u0026#39; ，改变后为： \u0026#39; + after); } } } }); 计算属性 # 虽然在模板中可以直接插入data中的数据，但是有些时候，我们需要对数据进行一些转化，然后才可以使用，或者需要将data中的多个数据结合起来使用，所以需要使用computed实例\n基本使用 # \u0026lt;div id=\u0026#34;app\u0026#34;\u0026gt; \u0026lt;!-- 使用不需要加小括号，因为是一个属性 --\u0026gt; \u0026lt;h1\u0026gt;{{fullName}}\u0026lt;/h1\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; let v = new Vue({ el:\u0026#34;#app\u0026#34;, data:{ firstName:\u0026#34;Kobe\u0026#34;, lastName:\u0026#34;Bryant\u0026#34; }, computed:{ //计算属性，结果可以看做为一个属性 fullName:function(){ return this.firstName + \u0026#34; \u0026#34; + this.lastName; } } }) \u0026lt;/script\u0026gt; 本质 # 计算属性的本质是一个数据代理对象，有getter和setter两个方法，我们直接使用属性名，实际是调用了get方法，我们一般也只会实现getter方法，这个属性也成为只读属性\ncomputed:{ fullName{ get:function(){ }, set:function(newValue){ } } } computed和methods的区别 # 1、使用methods的话，在每次调用，都会执行一次方法\n2、使用computed的话，在首次调用，会执行一次，但是后续调用该属性，如果函数里面的数据没有发生改变的话，那么vue会使用自带的缓存\n过滤器 # 用来对数据进行过滤，例如，价格保留两位小数，并加单位（元）：\n\u0026lt;div id=\u0026#34;app\u0026#34;\u0026gt; \u0026lt;!-- 显示格式为：\u0026#34;data | 过滤器\u0026#34; --\u0026gt; {{price | showPrice}} \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; let v = new Vue({ el:\u0026#34;#app\u0026#34;, data: { price:98 }, filters: { showPrice(price){ return price.toFixed(2) + \u0026#34;元\u0026#34;; } } }) \u0026lt;/script\u0026gt; 全局过滤器\nVue.filter(\u0026#34;showPrice\u0026#34;,function(value){ return price.toFixed(2) + \u0026#34;元\u0026#34;; }) 计算属性、过滤器的区别 # 计算属性\n1、计算属性适合用在单个属性的计算； 2、计算属性只能在单个vue实例中使用； 3、计算属性不能接收参数，只能使用data中定义的变量进行计算； 4、计算属性有缓存机制，可减少调用次数； 5、计算属性相当于定义一个变量\n过滤器\n1、过滤器适合多个同样计算方法的属性的计算； 2、过滤器可以定义为全局过滤器，在多个vue实例中使用； 3、过滤器可以接收多个参数进行计算； 4、过滤器没有缓存机制，每调用一次都会计算一次； 5、过滤器相当于定义一个特殊的方法\n$nextTick # 语法：$nextTick(function(){})\n作用：在下一次DOM更新结束再执行其回调函数\n用处：由于Vue并不是在数据更改后就立刻更新DOM，而是在当前的函数执行完后才更新，所以如果当前操作需要依托更新后的DOM，那么就需要使用这个方法\n","date":"2024-01-19","externalUrl":null,"permalink":"/posts/bafd68f1/b2321cb9/551800e7/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eVue \n    \u003cdiv id=\"vue\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#vue\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eVue读成view ，而不是V U E\u003c/li\u003e\n\u003cli\u003eVue是个前端框架，是尤雨溪（中国人）的个人项目\u003c/li\u003e\n\u003cli\u003e这个框架主要关注点： View和 Model如何快速绑定\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eVue的特点 \n    \u003cdiv id=\"vue的特点\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#vue%e7%9a%84%e7%89%b9%e7%82%b9\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e非常简单，Easy，好学\u003c/li\u003e\n\u003cli\u003e遵循一种新的WEB架构模式： MVVM\u003c/li\u003e\n\u003cli\u003e它可以开发单页面的应用程序（每个单独的页面，就是一个独立的应用程序）\u003c/li\u003e\n\u003cli\u003e渐进式（从核心技术开发，一层一层的添加内容）与兼容性（允许兼容其他框架或其他技术）\u003c/li\u003e\n\u003cli\u003e视图组件化\u003c/li\u003e\n\u003cli\u003e虚拟 DOM（Virtual DOM）\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003ch2 class=\"relative group\"\u003eMVVM架构 \n    \u003cdiv id=\"mvvm架构\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#mvvm%e6%9e%b6%e6%9e%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e之前，我们学过MVC（Model - View - Controller），但是这套东西是后端框架提出来的，不适合前端\u003c/li\u003e\n\u003cli\u003e他们提出了自己的前端框架模式：MVVM（Model - View - ViewModel）\u003c/li\u003e\n\u003cli\u003eModel依旧是数据，View依旧是视图\u003c/li\u003e\n\u003cli\u003eViewModel视图模型绑定对象（监听数据的变化，并实时做到与View进行同步）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"说明: clipboard.png\"\n    data-zoom-src=\"/posts/bafd68f1/b2321cb9/551800e7/image/202109181415140.gif\"\n    src=\"/posts/bafd68f1/b2321cb9/551800e7/image/202109181415140.gif\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"1、Vue","type":"posts"},{"content":" 分区模型 # C++程序在执行时，将内存大方向划分为4个区域\n代码区：存放函数体的二进制代码，由操作系统进行管理的 全局区：存放全局变量和静态变量以及常量 栈区：由编译器自动分配释放, 存放函数的参数值,局部变量等 堆区：由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收 内存四区意义：\n不同区域存放的数据，赋予不同的生命周期, 给我们更大的灵活编程\n程序运行前 # 在程序编译后，生成了exe可执行程序，未执行该程序前分为两个区域\n代码区：\n​\t存放 CPU 执行的机器指令\n​\t代码区是共享的，共享的目的是对于频繁被执行的程序，只需要在内存中有一份代码即可\n​\t代码区是只读的，使其只读的原因是防止程序意外地修改了它的指令\n全局区：\n​\t全局变量和静态变量（static）存放在此.\n​\t全局区还包含了常量区, 字符串常量和其他常量（不包括局部常量）也存放在此.\n​\t该区域的数据在程序结束后由操作系统释放\n//全局变量 int g_a = 10; int g_b = 10; //全局常量 const int c_g_a = 10; const int c_g_b = 10; int main() { //局部变量 int a = 10; int b = 10; //打印地址 cout \u0026lt;\u0026lt; \u0026#34;局部变量a地址为： \u0026#34; \u0026lt;\u0026lt; (int)\u0026amp;a \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;局部变量b地址为： \u0026#34; \u0026lt;\u0026lt; (int)\u0026amp;b \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;全局变量g_a地址为： \u0026#34; \u0026lt;\u0026lt; (int)\u0026amp;g_a \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;全局变量g_b地址为： \u0026#34; \u0026lt;\u0026lt; (int)\u0026amp;g_b \u0026lt;\u0026lt; endl; //静态变量 static int s_a = 10; static int s_b = 10; cout \u0026lt;\u0026lt; \u0026#34;静态变量s_a地址为： \u0026#34; \u0026lt;\u0026lt; (int)\u0026amp;s_a \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;静态变量s_b地址为： \u0026#34; \u0026lt;\u0026lt; (int)\u0026amp;s_b \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;字符串常量地址为： \u0026#34; \u0026lt;\u0026lt; (int)\u0026amp;\u0026#34;hello world\u0026#34; \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;字符串常量地址为： \u0026#34; \u0026lt;\u0026lt; (int)\u0026amp;\u0026#34;hello world1\u0026#34; \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;全局常量c_g_a地址为： \u0026#34; \u0026lt;\u0026lt; (int)\u0026amp;c_g_a \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;全局常量c_g_b地址为： \u0026#34; \u0026lt;\u0026lt; (int)\u0026amp;c_g_b \u0026lt;\u0026lt; endl; const int c_l_a = 10; const int c_l_b = 10; cout \u0026lt;\u0026lt; \u0026#34;局部常量c_l_a地址为： \u0026#34; \u0026lt;\u0026lt; (int)\u0026amp;c_l_a \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;局部常量c_l_b地址为： \u0026#34; \u0026lt;\u0026lt; (int)\u0026amp;c_l_b \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 程序运行后 # 栈区：\n​\t由编译器自动分配释放, 存放函数的参数值,局部变量等\n​\t注意事项：不要返回局部变量的地址，栈区开辟的数据由编译器自动释放\n#include\u0026lt;iostream\u0026gt; using namespace std; /* 局部变量保存在栈空间，栈空间的数据在函数执行完以后会自动释放 所以不要返回局部变量的地址 */ int * func() { int a = 10; return \u0026amp;a; } int main() { int * p = func(); cout \u0026lt;\u0026lt; *p \u0026lt;\u0026lt; endl; //10，是因为编译器做了保留 cout \u0026lt;\u0026lt; *p \u0026lt;\u0026lt; endl; //2031917448乱码 system(\u0026#34;pause\u0026#34;); return 0; } 堆区：\n​\t由程序员分配释放,若程序员不释放,程序结束时由操作系统回收\n​\t在C++中主要利用new在堆区开辟内存\nnew操作符 # C++中利用new操作符在堆区开辟数据\n堆区开辟的数据，由程序员手动开辟，手动释放，释放利用操作符 delete\n语法： new 数据类型\n利用new创建的数据，会返回该数据对应的类型的指针\n在堆空间开辟空间 # #include\u0026lt;iostream\u0026gt; using namespace std; /* 如果自己控制局部变量的销毁，那么使用new关键字 这样的话，即使函数已经执行完毕，也不会进行销毁 因为，new出来的数据是保存在堆空间 */ int * func() { int * p = new int(10); return p; } int main() { int * p = func(); cout \u0026lt;\u0026lt; *p \u0026lt;\u0026lt; endl; //10 cout \u0026lt;\u0026lt; *p \u0026lt;\u0026lt; endl; //10 system(\u0026#34;pause\u0026#34;); return 0; } 释放指定的空间 # #include\u0026lt;iostream\u0026gt; using namespace std; int * func() { int * p = new int(10); return p; } int main() { int * p = func(); cout \u0026lt;\u0026lt; *p \u0026lt;\u0026lt; endl; //10 delete(p); // 释放指针p指向的堆区空间 cout \u0026lt;\u0026lt; *p \u0026lt;\u0026lt; endl; //此时指针p指向的堆区空间已经释放，再次解引用会报错 system(\u0026#34;pause\u0026#34;); return 0; } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/d8245fe1/f50d4583/9f11f65a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e分区模型 \n    \u003cdiv id=\"分区模型\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%86%e5%8c%ba%e6%a8%a1%e5%9e%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eC++程序在执行时，将内存大方向划分为\u003cstrong\u003e4个区域\u003c/strong\u003e\u003c/p\u003e","title":"1、内存分区模型","type":"posts"},{"content":" 为什么使用数据库 # 持久化（persistent）：把数据保存在一个可掉电式的存储设备中以供以后使用，通常数据持久化就是就内存上的数据保存到磁盘上加以固化，而持久化通常使用各种关系数据库完成\n数据库与数据库管理系统 # 数据库相关概念 # DB：数据库（Database） 即存储数据的“仓库”，其本质是一个文件系统。它保存了一系列有组织的数据。 DBMS：数据库管理系统（Database Management System） 是一种操纵和管理数据库的大型软件，用于建立、使用和维护数据库，对数据库进行统一管理和控制。用户通过数据库管理系统访问数据库中表内的数据。 SQL：结构化查询语言（Structured Query Language） 专门用来与数据库通信的语言。 MySQL概述 # MySQL是一个开放源代码的关系型数据库管理系统 ，由瑞典MySQL AB（创始人Michael Widenius）公司1995年开发，迅速成为开源数据库的 No.1。 2008被 Sun 收购（10亿美金），2009年Sun被 Oracle 收购。 MariaDB 应运而生。（MySQL 的创造者担心 MySQL 有闭源的风险，因此创建了 MySQL 的分支项目 MariaDB） MySQL6.x 版本之后分为 社区版 和 商业版 。 MySQL是一种关联数据库管理系统，将数据保存在不同的表中，而不是将所有数据放在一个大仓库内，这样就增加了速度并提高了灵活性。 MySQL是开源的，所以你不需要支付额外的费用。 MySQL是可以定制的，采用了 GPL（GNU General Public License） 协议，你可以修改源码来开发自己的MySQL系统。 MySQL支持大型的数据库。可以处理拥有上千万条记录的大型数据库。 MySQL支持大型数据库，支持5000万条记录的数据仓库，32位系统表文件最大可支持 4GB ，64位系统支持最大的表文件为 8TB。 MySQL使用 标准的SQL数据语言 形式。 MySQL可以允许运行于多个系统上，并且支持多种语言。这些编程语言包括C、C++、Python、Java、Perl、PHP和Ruby等。 关于MySQL8.0 # MySQL从5.7版本直接跳跃发布了8.0版本 ，可见这是一个令人兴奋的里程碑版本。MySQL 8版本在功能上做了显著的改进与增强，开发者对MySQL的源代码进行了重构，最突出的一点是多MySQL Optimizer优化器进行了改进。不仅在速度上得到了改善，还为用户带来了更好的性能和更棒的体验。\nOracle和MySQL选择 # Oracle 更适合大型跨国企业的使用，因为他们对费用不敏感，但是对性能要求以及安全性有更高的要求。\nMySQL 由于其体积小、速度快、总体拥有成本低，可处理上千万条记录的大型数据库，尤其是开放源码这一特点，使得很多互联网公司、中小型网站选择了MySQL作为网站数据库。\n数据库结构 # RDBMS和NO-RDBMS # 关系型数据库RDBMS # 这种类型的数据库是 最古老 的数据库类型，关系型数据库模型是把复杂的数据结构归结为简单的二元关系 （即二维表格形式）\n关系型数据库以 行(row) 和 列(column) 的形式存储数据，以便于用户理解。这一系列的行和列被称为 表(table) ，一组表组成了一个库(database)。\n表与表之间的数据记录有关系(relationship)。现实世界中的各种实体以及实体之间的各种联系均用关系模型来表示。关系型数据库，就是建立在关系模型基础上的数据库。\n优点 复杂查询 可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。 事务支持 使得对于安全性能很高的数据访问要求得以实现。 SQL 就是关系型数据库的查询语言。\n非关系型数据库NO-RDBMS # 非关系型数据库，可看成传统关系型数据库的功能阉割版本，基于键值对存储数据，不需要经过SQL层的解析，性能非常高。同时，通过减少不常用的功能，进一步提高性能。\n非关系数据库的种类 # 相比于 SQL，NoSQL 泛指非关系型数据库，包括了榜单上的键值型数据库、文档型数据库、搜索引擎和列存储等，除此以外还包括图形数据库。也只有用 NoSQL 一词才能将这些技术囊括进来。\n键值型数据库 # 键值型数据库通过 Key-Value 键值的方式来存储数据，其中 Key 和 Value 可以是简单的对象，也可以是复杂的对象。Key 作为唯一的标识符，优点是查找速度快，在这方面明显优于关系型数据库，缺点是无法像关系型数据库一样使用条件过滤（比如 WHERE），如果你不知道去哪里找数据，就要遍历所有的键，这就会消耗大量的计算。\n键值型数据库典型的使用场景是作为内存缓存。 Redis是最流行的键值型数据库。\n文档型数据库 # 此类数据库可存放并获取文档，可以是XML、JSON等格式。在数据库中文档作为处理信息的基本单位，一个文档就相当于一条记录。文档数据库所存放的文档，就相当于键值数据库所存放的“值”。\nMongoDB是最流行的文档型数据库。此外，还有CouchDB等。\n搜索引擎数据库 # 虽然关系型数据库采用了索引提升检索效率，但是针对全文索引效率却较低。搜索引擎数据库是应用在搜索引擎领域的数据存储形式，由于搜索引擎会爬取大量的数据，并以特定的格式进行存储，这样在检索的时候才能保证性能最优。核心原理是“倒排索引”。\n典型产品：Solr、Elasticsearch、Splunk 等。\n列式数据库 # 列式数据库是相对于行式存储的数据库，Oracle、MySQL、SQL Server 等数据库都是采用的行式存储（Row-based），而列式数据库是将数据按照列存储到数据库中，这样做的好处是可以大量降低系统的I/O，适合于分布式文件系统，不足在于功能相对有限。\n典型产品：HBase等。\n图形数据库 # 图形数据库，利用了图这种数据结构存储了实体（对象）之间的关系。图形数据库最典型的例子就是社交网络中人与人的关系，数据模型主要是以节点和边（关系）来实现，特点在于能高效地解决复杂的关 系问题。\n图形数据库顾名思义，就是一种存储图形关系的数据库。它利用了图这种数据结构存储了实体（对象）之间的关系。关系型数据用于存明确关系的数据，但对于复杂关系的数据存储却有些力不从心。如社交网络中人物之间的关系，如果用关系型数据库则非常复杂，用图形数据库将非常简单。\n典型产品：Neo4J、InfoGrid等。\nSql语句 # MySQL的语法规范 # 不区分大小写,但建议关键字大写，表名、列名小写 每条命令最好用分号结尾 每条命令根据需要，可以进行缩进或换行 注释 单行注释：#注释文字 单行注释：-- 注释文字 多行注释：/* 注释文字 */ SQl语句分类 # DDL data definition language 数据定义语句 (create alter drop)\nDML data manipulation language 数据操作语句 (insert update delete)\nDQL data query language 数据查询语句(select)\nDCL data control lanugage 数据控制语句(grant revoke commit rollback)\nMySQL常用命令 # MySQL服务的登录和退出 # 登录：mysql 【-h 主机名 -P 端口号】 -u 用户名 -p密码\n退出：exit或ctrl+C\nMySQL服务的启动和停止 # 方式一：通过dos窗口\nnet start 服务名 net stop 服务名\n查看服务器的版本 # 方式一：登录到mysql服务端\nselect version(); 方式二：没有登录到mysql服务端\nmysql --version 或 mysql --V\n查看SQL执行情况 # explain\nmysqldump # mysqldump是mysql自带的逻辑备份工具，是mysql的客户端命令。备份原理是通过mysql协议连接到mysql服务器，将需要备份的数据查询出来，将查询出的数据转换成对应的insert等sql语句，使用时，再执行sql语句，即可将对应的数据还原。\n备份 # 命令结构：mysqldump [options] [database] \u0026gt; [filename]\n参数名 简写 含义 --host -h 服务器主机IP地址 --port -P 服务器mysql端口号 --user -u mysql用户名 --password -p mysql密码 --databases -B 指定备份数据库，包括create database语句 --all-databases -A 备份mysql服务器所有数据库，含create database语句 --compact 压缩模式，产生更少的输出 --comments -i 添加注释信息 --complete-insert -c 输出完成的插入语句 --no-data -d 只备份表结构，不备份数据 --no-create-info -t 只备份数据，不备份表结构 --flush-privileges 备份mysql或相关是需要使用 --quick -q 不缓存查询，直接输出，加快备份速度 --lock-tables -l 备份前，锁定所有数据库表 --no-create-db -n 禁止生成创建数据库语句 --force -f 当出现错误时仍然继续备份操作 --default-character-set 指定默认字符集 --add-locks 备份数据库时锁定数据库表 --verbose -v 备份时打印各阶段信息 # 备份指定数据库 mysqldump -u[username] -p[password] [database] \u0026gt; [filename] # 备份指定数据库的指定表,多个表使用空格分隔 mysqldump -u[username] -p[password] [database] [table1] [table2] \u0026gt; [filename] # 备份指定数据库,并且忽略指定表 mysqldump -u[username] -p[password] [database] --ignore-table=[database].[table1] --ignore-table=[database].[table2] \u0026gt; [filename] 恢复 # # 1、连接数据库 mysql -u用户名 -p密码 # 2、创建数据库 create database [database] # 3、切换到可用数据库 use [database] # 4、进行恢复 source [filename] 数据类型 # 整数类型 # int(4):配合zerofill进行使用，显示占4位宽度，不够会补零，需要是无符号\n小数类型 # DECIMAL（max(65),max(30)）:最大数字位数和最大小数位数\n如果精确运算，使用DECIMAL，没有精确运算的需求，建议使用float double\n字符串类型 # char和varchar的区别\n场景区别：char适合存放定长字符串，varchar存放长度可变的字符串 后面的数字：字符个数，char后面最多可以写到255，varchar一行数据最多占65535个字节（除去Bolb类的数据类型） 除去其他列所占的空间，剩余的空间和varchar后面写的字符个数有关系，编码不同，一个字符所占用的存储空间也不一样，所以也会影响varchar后字符个数 char是固定长度，char(5),如果存放了3个字符，也会按照5个字符占用存储空间 varchar(5)：可变长度，如果存储了3个字符，会按照3个字符占用存储空间。 速度区别：char优于varchar 空格的处理：char会消灭掉字符后自己插入的空格，varchar不会 二进制数据类型 # 日期和时间数据类型 # date和datetime的区别 # date类型可用于需要一个日期而不需要时间的部分 格式为YYYY-MM-DD 范围是'1000-01-01\u0026rsquo; 到'9999-12-31' datetime类型可用于需要同时包含日期和时间的信息的值 格式为YYYY-MM-DD HH:mm:ss 范围是'1000-01-0100:00:00\u0026rsquo; 到 \u0026lsquo;9999-12-3123:59:59\u0026rsquo; enum和set的区别 # enum只能从列出来的值中选择一个作为数据，set可以从列出来的值中选择多个值作为数据。\n三大范式 # 第一范式 # 每列保证原子性，且唯一 第二范式 # 首先满足第一范式 必须要有一个主键 如果主键是复合主键，除了主键以外的其他列必须完全依赖于主键列，不能只依赖于主键的一部分 第三范式 # 满足第二范式 除了主键以外的其他列必须直接依赖于主键，不能间接依赖于主键 ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/a6ece75f/8b824d9b/5d290da9/723965a1/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e为什么使用数据库 \n    \u003cdiv id=\"为什么使用数据库\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bd%bf%e7%94%a8%e6%95%b0%e6%8d%ae%e5%ba%93\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e持久化（persistent）\u003c/strong\u003e：把数据保存在一个可掉电式的存储设备中以供以后使用，通常数据持久化就是就内存上的数据保存到磁盘上加以固化，而持久化通常使用各种关系数据库完成\u003c/p\u003e","title":"1、数据库概述","type":"posts"},{"content":"枚举是 C 语言中的一种基本数据类型，它可以让数据更简洁，更易读。\n定义 # enum　枚举名　{枚举元素1,枚举元素2,……}; 例如我们需要定义星期的常量\n#define MON 1 #define TUE 2 #define WED 3 #define THU 4 #define FRI 5 #define SAT 6 #define SUN 7 使用枚举\nenum Day{ MON=1, TUE, WED, THU, FRI, SAT, SUN }; 注意：第一个枚举成员的默认值为整型的 0，后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1，第二个就为 2，以此类推。\n枚举变量定义 # 方式一：先定义枚举类，然后定义变量\nenum Day{ MON=1, TUE, WED, THU, FRI, SAT, SUN }; enum Day day; 方式二：定义枚举类同时定义变量\nenum Day{ MON=1, TUE, WED, THU, FRI, SAT, SUN } day; 方式三：直接定义变量，不需要写枚举类名\nenum{ MON=1, TUE, WED, THU, FRI, SAT, SUN } day; 获取枚举值 # 在C 语言中，枚举类型是被当做 int 或者 unsigned int 类型来处理的，所以按照 C 语言规范是没有办法遍历枚举类型的。\n#include\u0026lt;stdio.h\u0026gt; int main(){ enum Day{ MON=1, TUE, WED, THU, FRI, SAT, SUN } day; printf(\u0026#34;%d\\n\u0026#34;,MON); return 0; } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/f571508d/8c7e092d/54b87be9/","section":"文章","summary":"\u003cp\u003e枚举是 C 语言中的一种基本数据类型，它可以让数据更简洁，更易读。\u003c/p\u003e","title":"1、枚举","type":"posts"},{"content":" 计算机系统 # 计算机系统 = 硬件 + 软件\n硬件 内存、硬盘、cpu、键鼠等 软件 系统软件：管理整个计算机系统 操作系统、数据库管理软件（DBMS）、标准程序库、网络软件、服务程序，语言处理程序 应用软件：根据任务需要编写的各种程序 qq、微信 硬件发展历程 # 第一代：电子管时代（1946-1957） 1946 - 第一台电子数字计算机ENIAC - 逻辑元件是电子管 第二代：晶体管时代（1958-1964） 操作系统雏形诞生 面向过程语言FORTRAN出现 第三代：中小规模集成电路（1964-1971） 高级语言迅速发展 出现分时操作系统 第四代：大规模、超大规模集成电路（1972-至今） 出现微处理器 个人计算机出现（PC） 操作系统快速发展 摩尔定律 # intel创始人摩尔提出，集成电路上可容纳的晶体管数目，大约每隔18个月就会提升一倍，性能也随之提升一倍\n发展两极化 # 1、微型计算机向着更微型化、网络化、高性能、多用途方向发展\n2、巨型机向着更巨型化、超高速、并行处理、智能化方向发展\n计算机硬件的基本组成 # 1、早期冯诺依曼结构 # 存储程序：将指令以二进制代码的形式实现输入计算机的主存储器，然后按其在存储器中的首地址执行程序的第一条指令，以后就按该程序的规定顺序执行其他指令，直至程序执行结束\n计算机中，软件和硬件在逻辑上是等效的\n冯诺依曼计算机的特点：\n1、计算机由五大部件组成\n2、指令和数据以同等地位存于存储器、可按地址寻访\n3、指令和数据用二进制表示\n4、指令由操作码和地址码组成\n5、存储程序\n6、以运算器为中心\n2、现代计算机结构 # 现代计算机的特点**：以主存储器（内存）为中心**\n各种硬件 # 1、主存储器（内存） # 存储体：由一个个存储单元组成，存储单元地址从0开始，大小和MDR一样\nMAR（Memory Address Register）：存储地址寄存器\nMDR（Memory Data Register）：存储数据寄存器\n2、运算器（CPU） # ACC（Accumulator）：累加器，用于存放操作数，或运算结果\nMQ（Multiple-Quotient Register）：乘商寄存器，在乘除运算时，用于存放操作数或运算结果\nALU（Arithmetic and Logic Unit）：算数逻辑单元，通过内部复杂电路实现算数运算、逻辑运算，运算器的核心部件\nX：通用的操作数寄存器，用于存放操作数\n3、控制器 # CU（Contol Unit）：控制单元，分析指令，给出控制信号\nIR（Instruction Register）：指令寄存器，存放当前执行的指令\nPC（Program Counter）：程序计数器，存放下一条指令地址，有自动加一功能\n计算机的工作过程 # 有c语言程序如下：\nint a = 2,b = 3,c = 1,y = 0; void main(){ y = a * b + c; } 编译后装入主存：\n1、根据PC（程序计数器）中存放的下一条指令地址，从主存中获取指令（操作码和地址码）放到IR（指令寄存器）中\n2、然后IR将指令的前6个bit（操作码）发送到CU（控制单元），CU对指令进行分析\n3、000001）是一个取数指令，通过地址码到主存中取到相应的操作数放到ACC（累加器）中\n4、（000100）是一个乘法指令，通过地址码到内存中取到另一个乘数放到MQ（乘商寄存器）中，再将ACC中的值放到X（通用寄存器）中，然后CU控制ALU（算术逻辑单元）进行乘法运算，ALU将X和MQ中存储的两个数进行乘法运算并将结果放到ACC中（如果结果过大，则使用MQ辅助存储）\n计算机系统的层次结构 # ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/c343b13e/f6ce07bb/176e63a2/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e计算机系统 \n    \u003cdiv id=\"计算机系统\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%ae%a1%e7%ae%97%e6%9c%ba%e7%b3%bb%e7%bb%9f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e计算机系统 = 硬件 + 软件\u003c/p\u003e","title":"1、计算机系统概述","type":"posts"},{"content":" 版本控制 # 工程设计领域中使用版本控制管理工程蓝图的设计过程。在 IT 开发过程中也可以使用版本控制思想管理代码的版本迭代。\n版本控制工具应该具备的功能 # 协同修改：多人并行不悖的修改服务器端的同一个文件。 数据备份：不仅保存目录和文件的当前状态，还能够保存每一个提交过的历史状态。 版本管理： 在保存每一个版本的文件信息的时候要做到不保存重复数据，以节约存储空间，提高运行效率。这方面 SVN 采用的是增量式管理的方式，而 Git 采取了文件系统快照的方式。 权限控制：对团队中参与开发的人员进行权限控制。对团队外开发者贡献的代码进行审核——Git 独有。 历史记录： 查看修改人、修改时间、修改内容、日志信息。 将本地文件恢复到某一个历史状态。 分支管理：允许开发团队在工作过程中多条生产线同时推进任务，进一步提高效率。 Git # Git官网 # https://git-scm.com/\nGit 的优势 # 大部分操作在本地完成，不需要联网 完整性保证 尽可能添加数据而不是删除或修改数据 分支操作非常快捷流畅 与 Linux 命令全面兼容 Git结构 # Git和代码托管中心 # 代码托管中心的任务：维护远程库\n局域网环境下： GitLab 服务器\n外网环境下 ：1、GitHub 2、码云（Gitee）\n本地库和远程库 # 团队内部协作 # 跨团队协作 # Git的概念 # 工作区（本地） # 工作区就是你克隆项目到本地后，项目所在的文件夹目录\n暂存区（本地） # 用于存储工作区中添加上来的变更（新增、修改、删除）的文件的地方。操作时，使用git add .会将本地所有新增、变更、删除过的文件的情况存入暂存区中。\n本地仓库（本地） # 用于存储本地工作区和暂存区提交上来的变更（新增、修改、删除）过的文件的地方。操作时，使用git commit –m \u0026quot;本次操作描述\u0026quot; 可以将添加到暂存区的修改的文件提交到本地仓库中。\n远程仓库（服务器） # 简单来说，就是我们工作过程中，当某一个人的开发工作完毕时，需要将自己开发的功能合并到主项目中去，但因为功能是多人开发，如果不能妥善保管好主项目中存储的代码及文件的话，将会存在丢失等情况出现，所以不能将主项目放到某一个人的本地电脑上，这时就需要有一个地方存储主项目，这个地方就是我们搭建在服务器上的git远程仓库，也就是在功能开始开发前，每个人要下载项目到本地的地方。操作时，使用git push origin 分支名称，将本次仓库存储的当前分支的修改推送至远程仓库中的对应分支中。\n常用命令 # 仓库状态 # # 初始化本地仓库，初始化后当前位置会出现隐藏目录.git git init # 查看当前git仓库的状态 git status 仓库操作 # # 将文件添加到暂存区，将所有文件添加到暂存区用git add . git add [file] # 将暂存区文件退回到工作区，将所有文件退回可以用git rm -r --cached . git rm --cached [file] # 提交暂存区文件到本地仓库 git commit [file] git commit -m [description] [file] 日志 # # 查看git日志，--oneline每条日志显示一行 git log # 切换版本 git reset --hard [version_index] 开发者信息 # 设置开发者信息，与远程库登陆帐密没有关系\n# 设置仓库级别的，信息保存在.git/config中 git config user.name [username] git config user.email [email] # 设置全局级别的，信息保存在～/.gitconfig中 git config --global user.name [username] git config --global user.email [email] 分支 # # 查看所有分支，带*的为当前分支，-a查看本地和远程，-r查看远程分支，-v查看最后提交的分支 git branch # 创建分支 git branch [branch] # 切换分支 git checkout [branch] # 删除分支，-d替换为-D是强制删除 git branch -d [branch] # 将制定分支合并进入当前分支 git merge [branch] 远程仓库 # # 查看所有远程仓库，-v同时显示URL git remote # 添加远程仓库 git remote add [remotename] [url] # 删除远程仓库 git remote remove [remotename] # 重命名远程仓库 git remote rename [oldname] [newname] # 从远程仓库克隆代码 git clone [url] # 从远程仓库获取更改 git pull [remotename] [remotebranch] # 推送到远程仓库 # -f：强行推送覆盖远程 # -u：是--set-upstream的简写，简化以后push的命令不需要再次指定远程分支名 git push [remotename] [localbranch]:[remotebranch] # 默认本地分支和远程分支同名 git push [remotename] [brach] 代理 # # 使用Http代理 git config --global https.proxy http://127.0.0.1:7890 git config --global https.proxy https://127.0.0.1:7890 # 使用Socks5代理 git config --global http.proxy socks5://127.0.0.1:7890 git config --global https.proxy socks5://127.0.0.1:7890 # 只对 github.com 使用代理 git config --global http.https://github.com.proxy socks5://127.0.0.1:7890 git config --global https.https://github.com.proxy socks5://127.0.0.1:7890 git配置 # # 查看当前仓库配置 git config --list # 查看全局配置 git config --global --list # 删除当前仓库配置，--global全局 git config --unset [key] # 设置配置 git config [key] [value] # 设置多value配置 git config --add [key] [value] 常见问题 # 大小写不敏感 # Git对文件名的大小写不敏感，例如：\n创建一个文件readme.md，提交到代码仓库； 接着在本地文件系统中将其修改为Readme.md； 接着去提交，发现代码没有变化，输入git status 也不显示任何信息。 # 可以通过git mv对文件进行重命名 git mv readme.md Readme.md # 可以对当前仓库设置大小写敏感 git config core.ignorecase false # 也可以设置全局大小写敏感 git config --global core.ignorecase false ","date":"2024-01-16","externalUrl":null,"permalink":"/posts/f1b56a1d/712987f6/d0279ba0/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e版本控制 \n    \u003cdiv id=\"版本控制\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%89%88%e6%9c%ac%e6%8e%a7%e5%88%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e工程设计领域中使用版本控制管理工程蓝图的设计过程。在 IT 开发过程中也可以使用版本控制思想管理代码的版本迭代。\u003c/p\u003e","title":"1、Git","type":"posts"},{"content":" 批处理 # 批处理，顾名思义就是进行批量的处理，也称为批处理脚本，英译为BATCH，后缀名.bat就是取的前三个字母。它的构成没有固定格式，只要遵守每一行可视为一个命令，每个命令里可以含多条子命令，从第一行开始执行，直到最后一行结束，它运行的平台是 DOS就可以了。批处理有一个很鲜明的特点：使用方便、灵活，功能强大，自动化程度高。\n常用命令 # echo 和 @ # 回显命令\n@ #关闭单行回显 echo off #从下一行开始关闭回显 @echo off #从本行开始关闭回显。一般批处理第一行都是这个 echo on #从下一行开始打开回显 echo #显示当前是 echo off 状态还是 echo on 状态 echo. #输出一个”回车换行”，空白行 (同echo, echo; echo+ echo[ echo] echo/ echo) errorlevel # 每个命令运行结束，可以用这个命令行格式查看返回码\n默认值为0，一般命令执行出错会设 errorlevel 为1\necho %errorlevel% dir # 显示文件夹内容\ndir #显示当前目录中的文件和子目录 dir /a #显示当前目录中的文件和子目录，包括隐藏文件和系统文件 dir c: /a:d #显示 C 盘当前目录中的目录 dir c: /a:-d #显示 C 盘根目录中的文件 dir c: /b/p #/b只显示文件名，/p分页显示 dir *.exe /s #显示当前目录和子目录里所有的.exe文件 cd # 切换目录\ncd #进入根目录 cd #显示当前目录 cd /d d:sdk #可以同时更改盘符和目录 md # 创建目录\nmd d:abc #如果 d:a 不存在，将会自动创建中级目录 #如果命令扩展名被停用，则需要键入 mkdir abc rd # 删除目录\nrd abc #删除当前目录里的 abc 子目录，要求为空目录 rd /s/q d:temp #删除 d:temp 文件夹及其子文件夹和文件，/q安静模式 del # 删除文件\ndel d:test.txt #删除指定文件，不能是隐藏、系统、只读文件 del /q/a/f d:temp*.* #删除 d:temp 文件夹里面的所有文件，包括隐藏、只读、系统文件，不包括子目录 del /q/a/f/s d:temp*.* #删除 d:temp 及子文件夹里面的所有文件，包括隐藏、只读、系统文件，不包括子目录 ren # 重命名命令\nren d:temp tmp #支持对文件夹的重命名 cls # 清屏\ntype # 显示文件内容\ntype c:boot.ini #显示指定文件的内容，程序文件一般会显示乱码 type *.txt #显示当前目录里所有.txt文件的内容 copy # 拷贝文件\ncopy c:test.txt d:test.bak #复制 c:test.txt 文件到 d: ，并重命名为 test.bak copy con test.txt #从屏幕上等待输入，按 Ctrl+Z 结束输入，输入内容存为test.txt文件,con代表屏幕，prn代表打印机，nul代表空设备 copy 1.txt + 2.txt 3.txt #合并 1.txt 和 2.txt 的内容，保存为 3.txt 文件,如果不指定 3.txt ，则保存到 1.txt copy test.txt + #复制文件到自己，实际上是修改了文件日期 title # 设置cmd窗口的标题\ntitle 新标题 #可以看到cmd窗口的标题栏变了 Color # 修改字体背景颜色\nver # 显示系统版本\nlabel 和 vol # 设置卷标\nvol #显示卷标 label #显示卷标，同时提示输入新卷标 label c:system #设置C盘的卷标为 system pause # 暂停命令\nrem 和 :: # 注释命令，注释行不执行操作\ndate 和 time # 日期和时间\ndate #显示当前日期，并提示输入新日期，按\u0026#34;回车\u0026#34;略过输入 date/t #只显示当前日期，不提示输入新日期 time #显示当前时间，并提示输入新时间，按\u0026#34;回车\u0026#34;略过输入 time/t #只显示当前时间，不提示输入新时间 goto 和 : # 跳转命令\n:label #行首为:表示该行是标签行，标签行不执行操作 goto label #跳转到指定的标签那一行 find (外部命令) # 查找命令\nfind \u0026#34;abc\u0026#34; c:test.txt #在 c:test.txt 文件里查找含 abc 字符串的行,如果找不到，将设 errorlevel 返回码为1 find /i “abc” c:test.txt #查找含 abc 的行，忽略大小写 find /c \u0026#34;abc\u0026#34; c:test.txt #显示含 abc 的行的行数 more (外部命令) # 逐屏显示\nmore c:test.txt #逐屏显示 c:test.txt 的文件内容 tree # 显示目录结构\ntree d: #显示D盘的文件目录结构 \u0026amp; # 顺序执行多条命令，而不管命令是否执行成功\n\u0026amp;\u0026amp; # 顺序执行多条命令，当碰到执行出错的命令后将不执行后面的命令\nfind \u0026#34;ok\u0026#34; c:test.txt \u0026amp;\u0026amp; echo 成功 #如果找到了\u0026#34;ok\u0026#34;字样，就显示\u0026#34;成功\u0026#34;，找不到就不显示 || # 顺序执行多条命令，当碰到执行正确的命令后将不执行后面的命令\nfind \u0026#34;ok\u0026#34; c:test.txt || echo 不成功 如果找不到\u0026#34;ok\u0026#34;字样，就显示\u0026#34;不成功\u0026#34;，找到了就不显示 | # 管道命令\ndir *.* /s/a | find /c \u0026#34;.exe\u0026#34; 管道命令表示先执行 dir 命令，对其输出的结果执行后面的 find 命令 该命令行结果：输出当前文件夹及所有子文件夹里的.exe文件的个数 type c:test.txt|more 这个和 more c:test.txt 的效果是一样的 \u0026gt; 和 \u0026raquo; # 输出重定向命令\n\u0026gt; 清除文件中原有的内容后再写入\n\u0026gt;\u0026gt; 追加内容到文件末尾，而不会清除原有的内容\n主要将本来显示在屏幕上的内容输出到指定文件中 指定文件如果不存在，则自动生成该文件\ntype c:test.txt \u0026gt;prn #屏幕上不显示文件内容，转向输出到打印机 echo hello world\u0026gt;con #在屏幕上显示hello world，实际上所有输出都是默认 \u0026gt;con 的 copy c:test.txt f: \u0026gt;nul #拷贝文件，并且不显示\u0026#34;文件复制成功\u0026#34;的提示信息，但如果f盘不存在，还是会显示出错信息 copy c:test.txt f: \u0026gt;nul 2\u0026gt;nul #不显示”文件复制成功”的提示信息，并且f盘不存在的话，也不显示错误提示信息 echo ^^W ^\u0026gt; ^W\u0026gt;c:test.txt #生成的文件内容为 ^W \u0026gt; W #^ 和 \u0026gt; 是控制命令，要把它们输出到文件，必须在前面加个 ^ 符号 \u0026lt; # 从文件中获得输入信息，而不是从屏幕上\n一般用于 date time label 等需要等待输入的命令\n@echo off echo 2005-05-01\u0026gt;temp.txt date \u0026lt;temp.txt del temp.txt #这样就可以不等待输入直接修改当前日期 %0 %1 %2 %3 %4 %5 %6 %7 %8 %9 %* # 命令行传递给批处理的参数\n%0 #批处理文件本身 %1 #第一个参数 %9 #第九个参数 %* #从第一个参数开始的所有参数 #批参数(%n)的替代已被增强。您可以使用以下语法: %~1 #删除引号(\u0026#34; )， 扩充 %1 %~f1 #将 %1 扩充到一个完全合格的路径名 %~d1 #仅将 %1 扩充到一个驱动器号 %~p1 #仅将 %1 扩充到一个路径 %~n1 #仅将 %1 扩充到一个文件名 %~x1 #仅将 %1 扩充到一个文件扩展名 %~s1 #扩充的路径指含有短名 %~a1 #将 %1 扩充到文件属性 %~t1 #将 %1 扩充到文件的日期/时间 %~z1 #将 %1 扩充到文件的大小 %~$PATH : 1 #查找列在 PATH 环境变量的目录，并将 %1 #扩充到找到的第一个完全合格的名称。如果环境变量名未被定义，或者没有找到文件，此组合键会扩充到空字符串 #可以组合修定符来取得多重结果: %~dp1 #只将 %1 扩展到驱动器号和路径 %~nx1 #只将 %1 扩展到文件名和扩展名 %~dp$PATH:1 #在列在 PATH 环境变量中的目录里查找 %1，并扩展到找到的第一个文件的驱动器号和路径。 %~ftza1 #将 %1 扩展到类似 DIR 的输出行。 #可以参照 call/? 或 for/? 看出每个参数的含意 echo load \u0026#34;%%1\u0026#34; \u0026#34;%%2\u0026#34;\u0026gt;c:test.txt #生成的文件内容为 load \u0026#34;%1\u0026#34; \u0026#34;%2\u0026#34;m批处理文件里，用这个格式把命令行参数输出到文件 if # 判断命令\nif \u0026#34;%1\u0026#34;==\u0026#34;/a\u0026#34; echo #第一个参数是/a if /i \u0026#34;%1\u0026#34; equ \u0026#34;/a\u0026#34; echo #第一个参数是/a #/i 表示不区分大小写，equ 和 == 是一样的，其它运算符参见 if/? if exist c:test.bat echo #存在c:test.bat文件 #不存在c:windows文件夹 if not exist c:windows ( echo ) #存在c:test.bat if exist c:test.bat ( echo #不存在c:test.bat ) else ( echo ) setlocal 和 endlocal # 设置”命令扩展名”和”延缓环境变量扩充”\nSETLOCAL ENABLEEXTENSIONS #启用\u0026#34;命令扩展名\u0026#34; SETLOCAL DISABLEEXTENSIONS #停用\u0026#34;命令扩展名\u0026#34; SETLOCAL ENABLEDELAYEDEXPANSION #启用\u0026#34;延缓环境变量扩充\u0026#34; SETLOCAL DISABLEDELAYEDEXPANSION #停用\u0026#34;延缓环境变量扩充\u0026#34; ENDLOCAL #恢复到使用SETLOCAL语句以前的状态 #“命令扩展名”默认为启用 #“延缓环境变量扩充”默认为停用 #批处理结束系统会自动恢复默认值 #可以修改注册表以禁用\u0026#34;命令扩展名\u0026#34;，详见 cmd /? 。所以用到\u0026#34;命令扩展名\u0026#34;的程序，建议在开头和结尾加上 SETLOCAL ENABLEEXTENSIONS 和 ENDLOCAL 语句，以确保程序能在其它系统上正确运行 #\u0026#34;延缓环境变量扩充\u0026#34;主要用于 if 和 for 的符合语句，在 set 的说明里有其实用例程 set # 设置变量\n引用变量可在变量名前后加 % ，即 %变量名%\nset #显示目前所有可用的变量，包括系统变量和自定义的变量 echo %SystemDrive% #显示系统盘盘符。系统变量可以直接引用 set p #显示所有以p开头的变量，要是一个也没有就设errorlevel=1 set p=aa1bb1aa2bb2 #设置变量p，并赋值为 = 后面的字符串，即aa1bb1aa2bb2 echo %p% #显示变量p代表的字符串，即aa1bb1aa2bb2 echo %p:~6% #显示变量p中第6个字符以后的所有字符，即aa2bb2 echo %p:~6,3% #显示第6个字符以后的3个字符，即aa2 echo %p:~0,3% #显示前3个字符，即aa1 echo %p:~-2% #显示最后面的2个字符，即b2 echo %p:~0,-2% #显示除了最后2个字符以外的其它字符，即aa1bb1aa2b echo %p:aa=c% #用c替换变量p中所有的aa，即显示c1bb1c2bb2 echo %p:aa=% #将变量p中的所有aa字符串置换为空，即显示1bb12bb2 echo %p:*bb=c% #第一个bb及其之前的所有字符被替换为c，即显示c1aa2bb2 set p=%p:*bb=c% #设置变量p，赋值为 %p:*bb=c% ，即c1aa2bb2 set /a p=39 #设置p为数值型变量，值为39 set /a p=39/10 #支持运算符，有小数时用去尾法，39/10=3.9，去尾得3，p=3 set /a p=p/10 #用 /a 参数时，在 = 后面的变量可以不加%直接引用 set /a p=”1\u0026amp;0″ #”与”运算，要加引号。其它支持的运算符参见set/? set p= #取消p变量 set /p p=请输入 #屏幕上显示”请输入”，并会将输入的字符串赋值给变量p,注意这条可以用来取代 choice 命令 #注意变量在 if 和 for 的复合语句里是一次性全部替换的，如 @echo off set p=aaa if %p%==aaa ( echo %p% set p=bbb echo %p% ) 结果将显示 aaa aaa 因为在读取 if 语句时已经将所有 %p% 替换为aaa 这里的\u0026#34;替换\u0026#34;，在 /? 帮助里就是指\u0026#34;扩充\u0026#34;、\u0026#34;环境变量扩充\u0026#34; 可以启用”延缓环境变量扩充”，用 ! 来引用变量，即 !变量名! @echo off SETLOCAL ENABLEDELAYEDEXPANSION set p=aaa if %p%==aaa ( echo %p% set p=bbb echo !p! ) ENDLOCAL 结果将显示 aaa bbb 还有几个动态变量，运行 set 看不到 %CD% #代表当前目录的字符串 %DATE% #当前日期 %TIME% #当前时间 %RANDOM% #随机整数，介于0~32767 %ERRORLEVEL% #当前 ERRORLEVEL 值 %CMDEXTVERSION% #当前命令处理器扩展名版本号 %CMDCMDLINE% #调用命令处理器的原始命令行 可以用echo命令查看每个变量值，如 echo %time% 注意 %time% 精确到毫秒，在批处理需要延时处理时可以用到 start # 批处理中调用外部程序的命令，否则等外部程序完成后才继续执行剩下的指令\ncall # 批处理中调用另外一个批处理的命令，否则剩下的批处理指令将不会被执行 有时有的应用程序用start调用出错的，也可以call调用\nchoice (外部命令) # 选择命令\n让用户输入一个字符，从而选择运行不同的命令，返回码errorlevel为1234……\nwin98里是choice.com\nwin2000pro里没有，可以从win98里拷过来\nwin2003里是choice.exe\nchoice /N /C y /T 5 /D y\u0026gt;nul 延时5秒 for # 循环，也可以将一个命令的返回值赋值给一个变量\nfor /F %%i in (\u0026#39;命令\u0026#39;) do ( set 变量=%%i) echo %变量% ","date":"2023-12-25","externalUrl":null,"permalink":"/posts/26654e7b/4e28ccf1/4dcf54d7/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e批处理 \n    \u003cdiv id=\"批处理\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%89%b9%e5%a4%84%e7%90%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e批处理，顾名思义就是进行批量的处理，也称为批处理脚本，英译为BATCH，后缀名\u003ccode\u003e.bat\u003c/code\u003e就是取的前三个字母。它的构成没有固定格式，只要\u003cstrong\u003e遵守每一行可视为一个命令，每个命令里可以含多条子命令，从第一行开始执行，直到最后一行结束，它运行的平台是 DOS\u003c/strong\u003e就可以了。批处理有一个很鲜明的特点：使用方便、灵活，功能强大，自动化程度高。\u003c/p\u003e","title":"1、BAT脚本","type":"posts"},{"content":" Docker虚拟进程技术 # Docker是一种开源的虚拟进程技术，基于GO语言进行开发的\nDocker可以让开发者自己打包我们自己的应用或依赖包到一个轻量级的，可移植的容器（也就是一个虚拟的精简版的linux系统）中去。然后，再一发布，那么就可以在所有的Linux上进行运行，一次构建，到处使用\nDocker容器技术，一定是依托Linux操作系统存在\n镜像产生出来的程序，我们叫“容器”。容器的完成使用一种“沙箱”机制\n沙箱的概念 # 沙箱是一种虚拟进程技术，它的特点：沙箱之间相互独立，互不干扰。并且对现有的Linux系统不会产生任何的影响 搭建测试，生产环境 发布自己的应用程序 使用Docker的原因 # 保证开发，测试，上线环境统一 更快速的交付和部署 提供比虚拟机更为高效的虚拟技术 可以更轻松的迁移或者横向扩展 Docker和Vmware虚拟机的区别 # VM是一个运行在宿主机之上的完整的操作系统，VM运行时，需要消耗宿主机大量系统资源，例如：CPU，内存，硬盘等一系列的资源。Docker容器与VM不一样，它只包含了应用程序以及各种依赖库 正因为它占用系统资源非常少，所以它更加的轻量。它在启动时，可能只需要简单的一个命令就可以了。启动仅仅只需要几秒或几十秒钟就可以完成。对于宿主机来讲，承担VM可能5-10个就已经非常厉害了 ，但是Docker容器很轻松就承担几千个。而且网络配置相对而言也比较简单，主要以桥接方式为主 Docker的应用场景 # 开发，测试，生产环境上使用 Docker容器的3个概念 # 一、Docker镜像 # Docker镜像是由文件系统叠加而成（是一种文件的存储形式）。最底端是一个文件引导系统，即bootfs，这很像典型的Linux/Unix的引导文件系统。Docker用户几乎永远不会和引导系统有什么交互。实际上，当一个容器启动后，它将会被移动到内存中，而引导文件系统则会被卸载，以留出更多的内存供磁盘镜像使用。Docker容器启动是需要的一些文件，而这些文件就可以称为Docker镜像。\n镜像是构建Docker的基石。用户基于镜像来运行自己的容器。镜像也是Docker生命周期中的“构建”部分。镜像是基于联合文件系统的一种层式结构，由一系列指令一步一步构建出来。例如：添加一个文件；执行一个命令；打开一个窗口。也可以将镜像当作容器的“源代码”。镜像体积很小，非常“便携”，易于分享、存储和更新。\n二、Docker容器 # Docker可以帮助你构建和部署容器，你只需要把自己的应用程序或者服务打包放进容器即可。容器是基于镜像启动起来的，容器中可以运行一个或多个进程。我们可以认为，镜像是Docker生命周期中的构建或者打包阶段，而容器则是启动或者执行阶段。 容器基于镜像启动，一旦容器启动完成后，我们就可以登录到容器中安装自己需要的软件或者服务。\n所以Docker容器就是：\n一个镜像格式；\n一些列标准操作；\n一个执行环境。\nDocker借鉴了标准集装箱的概念。标准集装箱将货物运往世界各地，Docker将这个模型运用到自己的设计中，唯一不同的是：集装箱运输货物，而Docker运输软件。\n和集装箱一样，Docker在执行上述操作时，并不关心容器中到底装了什么，它不管是web服务器，还是数据库，或者是应用程序服务器什么的。所有的容器都按照相同的方式将内容“装载”进去。\nDocker也不关心你要把容器运到何方：我们可以在自己的笔记本中构建容器，上传到Registry，然后下载到一个物理的或者虚拟的服务器来测试，在把容器部署到具体的主机中。像标准集装箱一样，Docker容器方便替换，可以叠加，易于分发，并且尽量通用。\n使用Docker，我们可以快速的构建一个应用程序服务器、一个消息总线、一套实用工具、一个持续集成（CI）测试环境或者任意一种应用程序、服务或工具。我们可以在本地构建一个完整的测试环境，也可以为生产或开发快速复制一套复杂的应用程序栈。\n三、Docker仓库 # 专门用于放置镜像的地方，镜像可以从网络上下载，也可以自己去产生。最大的公开仓库是 Docker Hub(https://hub.docker.com/)。\nDocker的运行原理 # Docker引擎的安装 # 安装Docker，首先需要先确定你的操作系统一定是Linux 并且的Linux的内核版本一定要大于3.8以上，centos要求必须是64位 1、检查Linux版本和内核版本 # cat /etc/redhat-releas uname -r 2、更新yum # yum update 3、安装Docker的依赖插件 # yum install -y yum-utils device-mapper-persistent-data lvm2 4、配置国内Docker加速源 # yum -config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 5、检查yum中关于Docker的版本 # yum list docker-ce --showduplicates | sort -r 6、安装Docker # yum install docker 7、检查Docker版本 # docker -v 8、设置Docker开机启动 # systemctl enable docker 9、开启Docker引擎 # systemctl start|restart|stop docker 卸载Docker引擎 # systemctl stop docker yum remove docker-ce docker-ce-cli containerd.io rm -rf /var/lib/docker rm -rf /var/lib/containerd Docker使用之前的准备工作 # 镜像：可以来自于自己构建，也可以来自于网络\nDocker镜像原本来自国外，地址：https://hub.docker.com/\n从国外服务器获取镜像时，由于跨国际，可能非常慢，此时就需要配置国内镜像加速源\n配置国内Docker加速源 # 创建配置目录\nsudo mkdir -p /etc/docker 配置镜像\nsudo tee /etc/docker/daemon.json \u0026lt;\u0026lt;-\u0026#39;EOF\u0026#39; { \u0026#34;registry-mirrors\u0026#34;: [ \u0026#34;https://registry.docker-cn.com\u0026#34;, \u0026#34;https://docker.mirrors.ustc.edu.cn\u0026#34;, \u0026#34;http://hub-mirror.c.163.com\u0026#34; ] } EOF 重启Docker服务\nsudo systemctl daemon-reload sudo systemctl restart docker 到此，Docker容器使用之前的准备工作，就告一段落\n","date":"2023-11-22","externalUrl":null,"permalink":"/posts/61bf7bc9/5e6103fa/36ca2e73/fed5f606/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eDocker虚拟进程技术 \n    \u003cdiv id=\"docker虚拟进程技术\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#docker%e8%99%9a%e6%8b%9f%e8%bf%9b%e7%a8%8b%e6%8a%80%e6%9c%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"img\"\n    data-zoom-src=\"/posts/61bf7bc9/5e6103fa/36ca2e73/fed5f606/image/202109181357965.jpeg\"\n    src=\"/posts/61bf7bc9/5e6103fa/36ca2e73/fed5f606/image/202109181357965.jpeg\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"1、Docker","type":"posts"},{"content":" 优点 # 具有丰富的css样式和js插件，可以使我们做的页面更加丰富，效果绚丽多彩\n支持响应式布局开发\n使用 # 下载BootStrap 创建一个项目，将这三个官方的提供的文件夹拷贝到项目下 在我们创建的html页面中引入指定的一些js和css的第三方文件即可 或者可以引用\n\u0026lt;link href=\u0026#34;https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\u0026#34; rel=\u0026#34;stylesheet\u0026#34;\u0026gt; Bootstrap # 响应式布局 # 响应式布局，只需要做一份页面，这个页面根据不同分辨率，来自行修改内容布局BootStrap的响应式依赖于栅格系统\n栅格系统 # 响应式网格系统随着屏幕或视口（viewport）尺寸的增加，系统会自动分为最多12列。\n使用方法 # 行必须放置在 .container class 内，以便获得适当的对齐（alignment）和内边距（padding）。 使用行来创建列的水平组。 内容应该放置在列内，且唯有列可以是行的直接子元素。 预定义的网格类，比如 .row 和 .col-xs-4，可用于快速创建网格布局。LESS 混合类可用于更多语义布局。 列通过内边距（padding）来创建列内容之间的间隙。该内边距是通过 .rows 上的外边距（margin）取负，表示第一列和最后一列的行偏移。 网格系统是通过指定您想要横跨的十二个可用的列来创建的。例如，要创建三个相等的列，则使用三个 .col-xs-4。 样式 # 图片 # class=\u0026#34;img-rounde\u0026#34;--圆角图片 class=\u0026#34;img-thumbnail\u0026#34;--图片框 class=\u0026#34;img-circle\u0026#34;--圆形图片 按钮 # class=\u0026#34;btn-success\u0026#34;--成功，绿色白字 class=\u0026#34;btn-info\u0026#34;--提示，蓝色白字 class=\u0026#34;btn-warning\u0026#34;--警告，橘色白字 class=\u0026#34;btn-danger\u0026#34;--危险，红色白字 class=\u0026#34;btn-block\u0026#34;--拉伸到父容器宽度 字体图标 # 字体图标库：https://v3.bootcss.com/components/\n/*给按钮添加字体图标*/ \u0026lt;button\u0026gt; \u0026lt;span class=\u0026#34;字体图标\u0026#34;\u0026gt; \u0026lt;/span\u0026gt; \u0026lt;/button\u0026gt; 表格 # \u0026lt;tr\u0026gt;,\u0026lt;th\u0026gt;和 \u0026lt;td\u0026gt; 类 # ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/bafd68f1/d4aa59a0/d9773e62/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e优点 \n    \u003cdiv id=\"优点\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bc%98%e7%82%b9\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e具有丰富的css样式和js插件，可以使我们做的页面更加丰富，效果绚丽多彩\u003c/p\u003e","title":"1、Bootstrap","type":"posts"},{"content":" 全文检索 # MySQL存储数据时，采用表格的方式（行和列）的方式来进行存储 Redis存储数据时，采用K-V结构的方式来进行存储 当数据量非常庞大时（T，P级），提升查询效率 需求 # 说：Redis简单，知道Key就可以，但如果其他人问你说我不知道Key，我只是知道大概的值，如何提升效率？\n说：MySQL在某些常用列上，添加索引，使用索引来提升查询效率，问你：没有使用索引的列，如何快速查询？\n解决方案：\n如果有一种东西，它可以根据全部数据（不分行，列）的所有内容，都建立索引的话，此时，我再根据索引查询，快了吧？ 全文检索：将数据库的所有数据，都进行分析出关键词，然后针对关键词建立索引 查询时：就可以根据关键词快速的检索数据出来 场景 # 百度，谷歌等一系列的搜索引擎 搜索引擎爬我们的网页数据，将网页数据进行分析，分析出关键词，然后针对关键词进行建立索引 ES的概述 # ElaticSearch，简称为ES， ES是一个开源的高扩展的分布式全文检索引擎，它可以近乎实时的存储、检索数据；本身扩展性很好，可以扩展到上百台服务器；处理PB级别（大数据时代）的数据。ES也使用Java开发、并使用Lucene作为其核心来实现所有索引和搜索的功能，但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性，从而让全文搜索变得简单。\n据国际权威的数据库产品评测机构DB Engines的统计，在2016年1月，ElasticSearch已超过Solr等，成为排名第一的搜索引擎类应用\nES提供的功能 # 分布式的搜索引擎和数据分析引擎 搜索：百度，谷歌，搜狗，网站的站内检索（起点……），IT系统的检索 数据分析：百度热词排行榜，百度竞价，热点新闻，猜你喜欢…… 全文检索，结构化查询 全文检索：针对所有的数据，提供关键词的检索 结构化查询：也可以提供类似于MYSQL中的结构化查询 对海量数据进行存储以及实时性的处理 NOSQL库都有特点：内存处理，天生就适合做集群 ES的适合场景 # 维基百科，类似百度百科，全文检索，高亮，搜索推荐（权重，百度） The Guardian（国外新闻网站），类似搜狐新闻，用户行为日志（点击，浏览，收藏，评论）+社交网络数据（对某某新闻的相关看法），数据分析，给到每篇新闻文章的作者，让他知道他的文章的公众 反馈（好，坏，热门，垃圾，鄙视，崇拜） Stack Overﬂow（国外的程序异常讨论论坛），IT问题，程序的报错，提交上去，有人会跟你讨论和回答，全文检索，搜索相关问题和答案，程序报错了，就会将报错信息粘贴到里面去，搜索有没有对应的答案 GitHub（开源代码管理），搜索上千亿行代码 电商网站，检索商品 日志数据分析，logstash采集日志，ES进行复杂的数据分析，ELK技术， elasticsearch+logstash+kibana 商品价格监控网站，用户设定某商品的价格阈值，当低于该阈值的时候，发送通知消息给用户，比如 说订阅牙膏的监控，如果高露洁牙膏的家庭套装低于50块钱，就通知我，我就去买。 BI系统，商业智能，Business Intelligence。比如说有个大型商场集团，BI，分析一下某某区域最近3年的用户消费金额的趋势以及用户群体的组成构成，产出相关的数张报表，**区，最近3年，每年消费 金额呈现100%的增长，而且用户群体85%是高级白领，开一个新商场。ES执行数据分析和挖掘， Kibana进行数据可视化 国内：站内搜索（电商，招聘，门户，等等），IT系统搜索（OA，CRM，ERP，等等），数据分析（ES热门的一个使用场景） ES的特点 # 天生支持集群，可以实时处理以PB为单位的数据，中小型公司也可以用 ES不是什么新技术，它就是对Lucene的封装，将全文检索，数据分析，分布式全部整合到一起了 对于程序员来说，它是一种开箱即用的技术，而且你在用它时，不会对现有其他技术造成任何的干扰 针对NoSQL库，针对RDBMS的不足，提供了补充机制 ES的常见术语 # 索引：就是关系型数据库中的数据库\n类型：就是关系型数据库中的表\n文档：就是关系型数据库中表中的一行数据（Document），文档\n属性：一行数据的列（字段）\nMapping # 相当于MySQL的Schema ，是对表（index）结构的定义，所以，在使用表之前，先对需要的表结构进行定义\nMapping的数据类型 # 属性名字 说明 text 用于全文索引，该类型的字段将通过分词器进行分词，最终用于构建索引 keyword 关键字，不进行分词 long 有符号64-bit integer：-2^63 ~ 2^63 - 1 integer 有符号32-bit integer，-2^31 ~ 2^31 - 1 short 有符号16-bit integer，-32768 ~ 32767 byte 有符号8-bit integer，-128 ~ 127 double 64-bit IEEE 754 浮点数 float 32-bit IEEE 754 浮点数 half_float 16-bit IEEE 754 浮点数 boolean true,false date 日期类型 binary 该类型的字段把值当做经过 base64 编码的字符串，默认不存储，且不可搜索 类型自动识别 # ES 类型的自动识别是基于 JSON 的格式，如果输入的是 JSON 是字符串且格式为日期格式，ES 会自动设置成 Date 类型；当输入的字符串是数字的时候，ES 默认会当成字符串来处理，可以通过设置来转换成合适的类型；如果输入的是 Text 字段的时候，ES 会自动增加 keyword 子字段，还有一些自动识别如下图所示\n类型 规则 字符串 匹配到日期格式，设置成Date。 字符串为数字时，当成字符串处理，但我们设置转换为数字。 其他情况，类型就是Text，并且会增加keyword的子字段 布尔值 Boolean 浮点数 Float 整数 Long 对象 Object 数组 由第一个非空数值的类型决定 空值 忽略 指定表中字段的类型 # PUT /index_ { \u0026#34;mappings\u0026#34;: { \u0026#34;properties\u0026#34;: { \u0026#34;key\u0026#34;:{ \u0026#34;type\u0026#34;: \u0026#34;type_name\u0026#34; } } } } 相比MySQL的索引的优点（面试题） # 可以根据关键词，快速定位凡是包含过该关键词的文档 比MySQL灵活，MySQL使用like % 导致索引失效，而ES靠关键词 ES可以自己完成数据分析，得出关键词出现的频率 ES所有字段都可以参与索引，而MySQL只有该频率的字段才可以参与索引 ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/d1774b5b/12a75c76/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e全文检索 \n    \u003cdiv id=\"全文检索\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%85%a8%e6%96%87%e6%a3%80%e7%b4%a2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eMySQL存储数据时，采用表格的方式（行和列）的方式来进行存储\u003c/li\u003e\n\u003cli\u003eRedis存储数据时，采用K-V结构的方式来进行存储\u003c/li\u003e\n\u003cli\u003e当数据量非常庞大时（T，P级），提升查询效率\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003e需求 \n    \u003cdiv id=\"需求\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%9c%80%e6%b1%82\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e说：Redis简单，知道Key就可以，但如果其他人问你说我不知道Key，我只是知道大概的值，如何提升效率？\u003c/p\u003e","title":"1、ElasticSearch","type":"posts"},{"content":" 安装 # 1、fiddler（下载classic版本，免费）工具下载网址：http://www.telerik.com/download/fiddler。 2、运行 FiddlerSetup.exe一键完成安装。 3、安装成功后点击关闭按钮，所选路径下多出一个Fiddler文件夹 4、运行fiddler.exe。\n原理 # Fiddler 是以代理web服务器的形式工作的，它使用代理地址:127.0.0.1，端口:8888，能支持HTTP代理的任意程序的数据包都能被Fiddler嗅探到，Fiddler的运行机制其实就是本机上监听8888端口的HTTP代理。当Fiddler退出的时候它会自动注销，这样就不会影响别的 程序。不过如果Fiddler非正常退出，这时候因为Fiddler没有自动注销，会造成网页无法访问。解决的办法是重新启动下Fiddler。\n配置 # 1、配置端口和允许远程计算机连接（Tools-options-connections）\n2、配置https数据包抓取（Tools-options-https）\n会弹出配置https证书，全部选是完成配置\n界面功能 # 顶部菜单 # File： 文件 capture Traffic：是否启动抓包；勾选后左下角显示capturing。是开始抓取；不勾选是不抓取，无capturing。 new viewer：新建fiddler窗口。 save：将当前抓取的会话进行保存。 Edit：编辑 copy：对所选的信息进行copy，包含对session、URL、headers等信息进行copy。 Remove：删除，包含删除选择的session，未选择的session、删除所有的。 Select All：将回话区所有的会话全选。 Undelent：将上一次删除的会话进行复原。 Paste as sessions： 将以前的会话粘贴回来。 Mack：自定义不同状态的会话的显示的颜色。 Unlock for Editing：不允许进行编辑。 Find sessions：对会话进行快捷查找，相当于Ctrl+F。 Rules：规则 Hide Image Requests： 隐藏图片请求。 Hide connects： 隐藏connects请求。 Automatic Breakpoints： 修改请求或者响应的内容，做断点使用较多。 Customize Rules：调取脚本操作，多用于修改网络、其他自定义时使用。 User-Agents：模拟其他浏览器进行请求。 Performance： 一个性能测试工具，包含：模拟调制解调器速度、禁止缓存、显示TTL8。 Tools：工具 Options：一些设置项，包含：对抓取接口是http、HTTPS类型设置、获取证书、设置代理的端口号等信息。 Wininet options：对PC端进行设置代理。 Clear wininet cache：清除缓存。 Clear wininet cookies：清除cookies。 TextWizard： 编码工具，可以用来进行编码、解码、转码操作。 compare sessions：将两个sessions会话进行对比，正常使用需要安装插件。 Reset Script：将脚本进行还原。 view IE cache：视图缓存 New sessions Clipboard：新建会话剪切板 HOSTS：对host进行修改、配置。 View：视图 show toolbar：开启工具栏 Autoscroll session list：设置会话区在抓取时是否自动滑动。 其他的主要是对布局方式的设置，包括菜单栏是否显示、界面的显示方式。 Help：帮助 工具栏 # statistics：统计选择请求的性能\ninspectors：显示请求响应的参数（请求头、body等）\nautoresponder：进行请求修改、重定向\ncomposer：模拟接口请求，简单的接口测试\nfiddlerscript：显示脚本内容\nlog：操作日志\nfilters：设置过滤\ntimeline：显示多个请求瀑布流\ninspectors # 请求参数显示区 headers：主要显示选择的请求的信息，包含、请求头、请求体、body等信息。 textView：以文本的形式显示请求参数以及body值。 SyntaxView：以脚本的形式显示请求参数以及body值（需要安装Syntaxview插件）。 WebForms：以列表的形式显示请求参数以及body值。 HexView：以16进制形式显示请求参数以及body值。 Auth：显示header中Proxy-Authorization和Authorization值。 Cookies：以直观的界面显示header中的cookies的值。 Raw：将整个请求以纯文本的形式显示。 Josn：以josn串形式显示请求参数以及body值。 XML：以XML的形式显示请求参数以及body值。 响应结果显示区 Transformer：响应信息的压缩编码格式，对响应信息进行编码、解码、转码操作。 Headers：响应信息，包含响应状态、响应头、响应体。 textView：以文本的形式展示响应结果。 SyntaxView：以脚本的形式展示响应结果（需要安装插件）。 ImageView：当响应中包含图片时可以用此功能进行查看图片以及图片信息。 HexView：以16进制展示响应结果。 WebView：以列表形式展示响应结果。 Auth：展示响应结果中部分信息。 Caching：响应的缓存过期时间或者缓存 Cookies：展示响应中的cookies信息。 Raw：以纯文本形式展示响应头。 json：以JSON形式展示响应结果。 XML：以XML形式展示响应结果。 断点 # Fiddler中设置断点的两种方式\nbefore response：这个是打在request请求的时候，未到达服务器之前\nafter response：也就是服务器响应之后，在Fiddler将响应传回给客户端之前。\n全局断点 # Fiddler最强大的功能莫过于设置断点了，设置好断点后，你可以修改httpRequest 的任何信息包括host, cookie或者表单中的数据。\nrules -\u0026gt; automatic breakpoint -\u0026gt; before requests\n单个断点 # 例如要对http://example.com打断点，那么就在命令行输入\nbpu http://example.com\n取消断点在命令行输入bpu就可以了\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/b18f3aef/5b0557cd/2d28c8d8/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e安装 \n    \u003cdiv id=\"安装\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%89%e8%a3%85\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e1、fiddler（下载classic版本，免费）工具下载网址：http://www.telerik.com/download/\u003ca\n  href=\"https://so.csdn.net/so/search?q=fiddler\u0026amp;spm=1001.2101.3001.7020\"\n    target=\"_blank\"\n  \u003efiddler\u003c/a\u003e。\n2、运行 FiddlerSetup.exe一键完成安装。\n3、安装成功后点击关闭按钮，所选路径下多出一个Fiddler文件夹\n4、运行fiddler.exe。\u003c/p\u003e","title":"1、Fiddler","type":"posts"},{"content":" JavaFX # 界面结构 # 舞台(Stage) # 舞台代表了顶级容器或窗口。它包含JavaFX应用中的所有对象。 它是由Javafx.stage.Stage类所定义的。 我们可以通过传递其尺寸(即：高度和宽度)，来指定舞台的大小。 舞台被分为内容区域和装饰区域(即：标题栏和边框)。 场景(Scene) # 场景表示JavaFX应用程序的物理内容。它包含了所有单独的控件或组件。 它是由Javafx.scene.Scene类所定义的。 一个应用可以有多个场景，但在任何给定时间内，舞台上只能显示一个场景。 场景的大小可以通过将其尺寸(即：高度和宽度)、连同根节点一起，传递给其构造函数来指定。 场景图(Scene Graph) # 场景图是表示场景内容的树状数据结构(分层)。所有可视组件，包括：控件、布局等，都是场景图的一部分。 场景图组件必须被附加到待显示的场景中，并且必须被进一步附加到舞台中，才能使得整个场景可见。 节点(Nodes) # 节点是场景图的视觉与图形对象。 场景图的节点是由JavaFX.scene.Node类所定义的。 一个节点可以包括： 各种几何或图形对象：2D、3D。 各种UI控件：Button、CheckBox、ChoiceBox、以及TextArea等。 各种容器或布局板式：BorderPane、GridPane、以及FlowPane等。 各种媒体元素：Audio、Video、以及Image对象。 节点有以下类型： 根节点：是场景图中的第一个节点。 分支与父节点：诸如Group、Region、以及StackPane等，都带有各种子节点。 叶子节点：诸如Rectangle、Ellipse、Box、ImageView、以及MediaView等，都带有各种子节点。 HelloWorld # import javafx.application.Application; import javafx.stage.Stage; public class Hello extends Application{ public static void main(String[] args){ launch(args); } public void start(Stage stage){ stage.show(); } } 生命周期 # 生命周期由javafx.application.Application类控制，在fx中，所有的组件都运行在UI线程中。\ninit()：初始化方法；\nstart()：开始UI线程渲染，所有的组件都需要在这个线程中声明；\nstop()：应用结束时方法。\n常用API # 默认浏览器打开指定链接\nHostServices services = application.getHostServices(); services.showDocument(\u0026#34;www.baidu.com\u0026#34;); ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/d9073060/edd0d20f/b5c7c04d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJavaFX \n    \u003cdiv id=\"javafx\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#javafx\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e界面结构 \n    \u003cdiv id=\"界面结构\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%95%8c%e9%9d%a2%e7%bb%93%e6%9e%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20220812163940474\"\n    data-zoom-src=\"/posts/3ab7256e/d9073060/edd0d20f/b5c7c04d/image/image-20220812163940474.png\"\n    src=\"/posts/3ab7256e/d9073060/edd0d20f/b5c7c04d/image/image-20220812163940474.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"1、JavaFX概述","type":"posts"},{"content":" java基础知识图解 # 软件开发 # 软件开发 # 软件，即一系列按照特定顺序组织的计算机数据和指令的集合。有系统软件和应用软件之分。\n人机交互方式 # 图形化界面(Graphical User Interface GUI)：这种方式简单直观，使用者易于接受，容易上手操作。\n命令行方式(Command Line Interface CLI)：需要有一个控制台，输入特定的指令，让计算机完成一些操作。\n应用程序=算法+数据结构\njava语言 # java版本历史迭代 # SUN(Stanford University Network，斯坦福大学网络公司 ) 1995年推出的一门高级编程语言。\n1991年 Green项目，开发语言最初命名为Oak (橡树)\n1996年，发布JDK 1.0，约8.3万个网页应用Java技术来制作\n2004年，发布里程碑式版本：JDK 1.5，为突出此版本的重要性，更名为JDK 5.0\n2009年，Oracle公司收购SUN，交易价格74亿美元\n2014年，发布JDK 8.0，是继JDK 5.0以来变化最大的版本\nJava技术体系平台 # Java SE（Java Standard Edition）标准版，支持面向桌面级应用（如Windows下的应用程序）的Java平台，提供了完整的Java核心API，此版本以前称为J2SE\nJava EE（Java Enterprise Edition）企业版，是为开发企业环境下的应用程序提供了一套解决方案，该技术体系中包含的技术如Servlet、JSP等，主要针对了Web应用程序的开发，此版本以前称为J2EE。Java EE（Java Enterprise Edition）已经转移到了Eclipse Foundation，并且在2021年更名为Jakarta EE。\nJava ME（Java Micro Edition）小型版，是面向嵌入式设备和移动设备的Java平台。它是Java SE（Java Standard Edition）的一个子集，旨在提供适合于资源受限设备的Java运行环境和开发框架。Java ME在近年来逐渐减少了市场份额，并且在2020年被宣布停止进一步的发展。现代的移动设备和嵌入式设备通常采用更为先进和全功能的平台，如Android和iOS。因此，如果您考虑开发移动应用程序，可能更适合选择Android或iOS平台进行开发。\nJava Card，是一种专为智能卡（如信用卡、SIM卡）和其他嵌入式设备设计的Java平台。它提供了一种安全可靠的环境，用于开发和运行应用程序，同时保护敏感数据和确保应用程序的完整性。\nJava在各领域的应用 # 企业级应用：主要指复杂的大企业的软件系统、各种类型的网站。Java的安全机制以及它的跨平台的优势，使它在分布式系统领域开发中有广泛应用。应用领域包括金融、电信、交通、电子商务等。\nAndroid平台应用：Android应用程序使用Java语言编写。Android开发水平的高低很大程度上取决于Java语言核心能力是否扎实。\n大数据平台开发：各类框架有Hadoop，spark，storm，flink等，就这类技术生态圈来讲，还有各种中间件如flume，kafka，sqoop等等 ，这些框架以及工具大多数是用Java编写而成，但提供诸如Java，scala，Python，R等各种语言API供编程。\n移动领域应用：主要表现在消费和嵌入式领域，是指在各种小型设备上的应用，包括手机、PDA、机顶盒、汽车通信设备等。\nJava语言运行机制及运行过程 # Java语言的特点 # 跨平台性 # Java两种核心机制 # Java虚拟机 (Java Virtal Machine)\n垃圾收集机制 (Garbage Collection)\n核心机制—Java虚拟机 # JVM是一个虚拟的计算机，具有指令集并使用不同的存储区域。负责执行指 令，管理数据、内存、寄存器。\n对于不同的平台，有不同的虚拟机。\n只有某平台提供了对应的java虚拟机，java程序才可在此平台运行\nJava虚拟机机制屏蔽了底层运行平台的差别，实现了“一次编译，到处运行“\n核心机制—垃圾回收 # 不再使用的内存空间应回收—— 垃圾回收。\n1、在C/C++等语言中，由程序员负责回收无用内存。\n2、Java 语言消除了程序员回收无用内存空间的责任：它提供一种系统级线程跟踪存储空间的分配情况。并在JVM空闲时，检查并释放那些可被释放的存储空间。\n垃圾回收在Java程序运行过程中自动进行，程序员无法精确控制和干预。\nJava程序还会出现内存泄漏和内存溢出问题吗？Yes!\nJava语言的环境搭建 # 什么是JDK，JRE # 下载并安装JDK # 官方网址：www.oracle.com\n配置环境变量 # HelloWorld # 开发HelloWorld # 1.将编写的java代码保存在以.java结尾的源文件中\nclass HelloChina{ public static void main(String[] args){ //args：arguments参数；可以更改 System.out.println(\u0026#34;Hello,World!\u0026#34;); //输出语句System .out.println()先输出后换行 } } 2.通过编译工具javac.exe编译为字节码文件，格式为javac 源文件名.java\n3.通过java.exe运行字节码文件，格式为java 字节码文件名\n注意 # 1、在一个源文件中可以声明多个类（class），但是只能最多有一个类声明为public的（不包含内部类）。而且，要求声明为public的类的类名必须与源文件名相同。\n2、程序的入口是main()方法，格式是固定的。\n3、每一个执行语句都以分号;结束。\n4、编译以后会生成一个多个字节码文件，字节码文件名与源文件中声明的类名相同。\n注释（Comment） # 用于注解说明解释程序的文字就是注释。\n/* 1、java规定了三种注释： 单行注释 多行注释 文档注释（java特有） 2、 单行注释和多行注释的作用： a.对所写的程序进行解释说明，增强可读性。 b.可以调试所写的代码 3、特点： 单行注释和多行注释的内容不参与编译。（编译后生成的 字节码文件不包含注释信息。） 4、多行注释不可以嵌套使用。 */ class HelloJava { /* 多行注释： 如下的main方法是程序的入口！ main的格式是固定的！ */ public static void main(String[] args) { //单行注释：如下的语句表示输出到控制台 System.out.println(\u0026#34;Hello World!\u0026#34;); } } 文档注释 # 文档注释的作用：\n注释内容可以被JDK提供的工具 javadoc 所解析，生成一套以网页文件形式体现的该程序的说明文档。\n使用/**文档注释*/的格式\n使用javadoc.exe解析\ndos命令行解析方法：\njavadoc -d myHello -author -version HelloJava.java 其中的myHello为文件名，HelloJava.java为源文件名\nJava API文档 # API （Application Programming Interface,应用程序编程接口）是 Java 提供 的基本编程接口）（类库）。\n下载地址：http://www.oracle.com/technetwork/java/javase/downloads/index.html\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/f742d77f/230f6847/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003ejava基础知识图解 \n    \u003cdiv id=\"java基础知识图解\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#java%e5%9f%ba%e7%a1%80%e7%9f%a5%e8%af%86%e5%9b%be%e8%a7%a3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"说明: untitle.png\"\n    data-zoom-src=\"/posts/3ab7256e/80aacaea/f742d77f/230f6847/image/202109181111062.jpg\"\n    src=\"/posts/3ab7256e/80aacaea/f742d77f/230f6847/image/202109181111062.jpg\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"1、java语言概述","type":"posts"},{"content":" JNI和JNA # Java原生自带的JNI，它允许Java代码和其他语言（尤其C/C++）写的代码进行交互，只要遵守调用约定即可。首先看下JNI调用C/C++的过程，注意写程序时自下而上，调用时自上而下。\n可见步骤非常的多，很麻烦，使用JNI调用.dll/.so共享库都能体会到这个痛苦的过程。如果已有一个编译好的.dll/.so文件，如果使用JNI技术调用，我们首先需要使用C语言另外写一个.dll/.so共享库，使用SUN规定的数据结构替代C语言的数据结构，调用已有的 dll/so中公布的函数。\nJNA(Java Native Access)是一个开源的Java框架，是Sun公司推出的一种调用本地方法的技术，是建立在经典的JNI基础之上的一个框架。之所以说它是JNI的替 代者，是因为JNA大大简化了调用本地方法的过程，使用很方便，基本上不需要脱离Java环境就可以完成，那么JNA调用C/C++的过程大致如下：\n使用 # 依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;net.java.dev.jna\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jna\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.5.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; Golang部分 # 编写动态连接库，这里就用Golang来写了，用C/C++同理\npackage main import \u0026#34;C\u0026#34; import \u0026#34;fmt\u0026#34; func main() { } //export Hello func Hello() { fmt.Println(\u0026#34;From dll : golang\u0026#34;) } //export Add func Add(a, b int) int { return a + b } //export Msg func Msg() string { return \u0026#34;Hello,This is a msg!\u0026#34; } 编译为dll\ngo build -buildmode=c-shared -o test.dll main.go Java部分 # 编写接口，并引入动态链接库\npublic interface TestDll extends Library { // 此处要么使用绝对路径，要么就使用文件名并把dll放在jdk的bin目录下 TestDll Test_Dll = (TestDll) Native.loadLibrary(\u0026#34;E:\\\\go-project\\\\demo\\\\test\u0026#34;,TestDll.class); void Hello(); int Add(int a,int b); } 调用\npublic static void main(String[] args) { TestDll.Test_Dll.Hello(); System.out.println(TestDll.Test_Dll.Add(1,2)); } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/0f3856f3/d8ad73f9/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJNI和JNA \n    \u003cdiv id=\"jni和jna\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jni%e5%92%8cjna\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eJava原生自带的JNI，它允许Java代码和其他语言（尤其C/C++）写的代码进行交互，只要遵守调用约定即可。首先看下JNI调用C/C++的过程，注意写程序时自下而上，调用时自上而下。\u003c/p\u003e","title":"1、JNA","type":"posts"},{"content":" JVM # JVM是Java Virtual Machine（Java虚拟机）的缩写，JVM是一种用于计算设备的规范，它是一个虚构出来的计算机，是通过在实际的计算机上仿真模拟各种计算机功能来实现的\n引入Java语言虚拟机后，Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息，使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码（.class二进制字节码文件），就可以在多种平台上不加修改地运行\nJVM还是一个跨语言的平台，除了java语言，很多语言都可以在JVM上进行运行，只有遵守JVM规范，使用自己的编译器生成.class字节码文件\n也就是说，JVM只关心字节码文件，并不关心该字节码文件是由什么语言编译的\nJVM的位置 # JVM的整体结构 # HotSpot虚拟机是目前市面上高性能虚拟机的代表之一，采用解释器和即使编译器共存的架构\n类装载器子系统：主要负责将每个字节码文件加载到内存中，生成Class对象，涉及加载、链接等过程\n运行时数据区：内存中的主要结构，方法区和堆区是多线程共用的，其余的虚拟机栈、本地方法栈、程序计数器是每个线程都有一份\n执行引擎：主要包括解释器、JIT及时编译器（编译器的后端，编译.java的编译器成为前端编译器）、垃圾回收器\nJVM的架构模型 # Java编译器输入的指令流基本是基于栈的指令集架构；还有一种指令集架构就是基于寄存器的指令集架构\n基于栈的架构 设计和实现更简单，适用于资源受限的系统 避开了寄存器的分配难题，使用零地址指令方式分配 指令流中的指令大部分是零地址指令，其执行过程依赖操作栈，指令集更小，编译器更容易实现 不需要硬件支持，可移植性更好，更好实现跨平台 基于寄存器的架构 典型应用是x86的二进制指令集，比如传统的PC以及Android的Davlik虚拟机 指令集架构则完全依赖硬件，可移植性差 性能优秀和执行更高效 花费更少的指令去完成一项操作 大部分情况下，基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主 例如，同样执行2+3的操作：\n基于栈\niconst_2 //常量2入栈 istore_1 iconst_3 //常量3入栈 istore_2 iload_1 iload_2 iadd\t//常量2、3出栈，执行相加 istore_0 //结果5出栈 基于寄存器\nmov eax,2 //将eax寄存器的值设为1 add eax,3 //是eax寄存器的值加3 反编译java文件就可以看到，java是基于栈的内存指令\npublic class Demo {\tpublic static void main(String[] args) { int i = 2; int j = 3; int k = i + j; }\t} 使用javap -v Demo.class命令对生成的字节码文件进行反编译\nJVM的声明周期 # 虚拟机的启动 # Java虚拟机的启动是通过引导类加载器（bootstrap class loader）创建一个初始类（initial class）来完成的，这个类是由虚拟机的具体实现来指定的\n虚拟机的执行 # 一个执行中的Java虚拟机有一个清晰的任务：执行Java程序 程序开始执行时它才运行，程序结束时它就停止 执行一个所谓的Java程序的时候，真真正正在执行的是一个叫做Java虚拟机的进程 虚拟机的退出 # 在以下几种情况下退出 程序正常执行结束 程序在执行过程中遇到了异常或错误而异常终止 由于操作系统出现错误导致Java虚拟机进程终止 某线程调用Runtime类或System类的exit方法，或Runtime类的halt方法，并且Java安全管理器也允许这次exit或halt操作 常见的JVM # Sun Classic VM # java1.0时发布，java1.4被淘汰\n是世界上第一款商用的Java虚拟机\n现在默认使用的Hotspot虚拟机内置了Sun Classic VM\n虚拟机内部只提供了解释器，而现在主流的JVM都是解释器+JIT编译器（即时编译器）\nExact VM # java1.2提供此虚拟机\nExact Memory Management：准确式内存管理，虚拟机可以知道内存某个位置的数据具体是什么类型\n具有现代高性能虚拟机的雏形：热点特测、编译器和解释器混合工作，被Hotspot虚拟机替换\nHotSpot VM（默认） # 三大高性能虚拟机之一\njava1.3成为默认虚拟机\nHotSpot这个名字就是指它的热点代码探测技术\nJRockit # 三大高性能虚拟机之一\n专注于服务器端应用，不在乎应用的启动速度，所以不包含解释器，全部代码都使用即时编译器编译后执行\n是世界上最快的JVM\n由BEA开发，现在已经被Oracle收购，Oracle大致在JDK8完成和HotSpot整合\nJ9 # 三大高性能虚拟机之一\nIBM开发\n市场定位和HotSpot接近，服务器端、桌面应用、嵌入式等\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/ebb2f342/81e7e5df/bcc499bb/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJVM \n    \u003cdiv id=\"jvm\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jvm\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eJVM是Java Virtual Machine（Java虚拟机）的缩写，JVM是一种用于计算设备的规范，它是一个虚构出来的计算机，是通过在实际的计算机上仿真模拟各种计算机功能来实现的\u003c/p\u003e","title":"1、JVM和Java体系结构","type":"posts"},{"content":" Tomcat存在的问题 # 1、Tomcat：一种轻量级的WEB容器，它的适用场景中小型系统或者并发量不高的系统，它是apache平台与Sun公司一起合作开发出来的，专门支持Servlet的一种WEB容器\n2、Tomcat 单位时间1s范围内（默认150），一般最多可以支持到500的并发（QPS）,当超过500以后，性能将急速下降。当超过最大承受量以后，系统可能返回：Server is too Busy\n解决方案 # 使用集群 集群：当单台服务器性能较低时，我们去购买更多的服务器，集中起来组成一个群体，统一对外。相同的服务集群可以提高系统的整体响应性能，以及整体的可用性 可用性：任何时间范围内容，系统都是正常的。按理论来说，应该是100%的时间，但是业界中，能做到99%的都不多，做的最好的：阿里 ，腾讯 （99.99%） 当一台服务器的处理能力、存储空间不足时，不要企图去换更强大的服务器，对大型网站而言，不管多么强大的服务器，都满足不了网站持续增长的业务需求。这种情况下，更恰当的做法是增加一台服务器分担原有服务器的访问及存储压力。扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。其意思就是分摊到多个操作单元上进行执行。 Nginx概述 # Nginx跟apache一样，都是HTTP服务器，但是它也有它自己的特点\nNginx 是一种反向代理服务器。也是一个IMAP/POP3/SMTP/STOMP的服务器 Nginx提供负载均衡功能（跟apache一样），可以HTTP服务器对外提供服务 处理静态资源文件，甚至可以提供对静态资源文件的缓存 支持模块化的结构配置 Nginx默认10K/S，每秒默认支持10000的并发量 国内很多大厂都在使用Nginx，如：百度，京东，新浪，网易，腾讯，阿里……；中小厂，就更喜欢用\n反向代理与正向代理 # 反向代理（Reverse Proxy） 从外网到内网的访问就是：反向代理 Nginx接收用户的请求，然后再将用户的请求转发给内部网络服务器，并从内部服务器身上得到结果，然后再把结果再转发给客户端。 此时：Nginx对于Tomcat来讲，它就是一台对外的代理服务器 正向代理 从内网到外网的访问就是：正向代理。 Nginx的功能 # 处理静态资源，也可以自动索引文件 反向代理，让外部可以访问内部资源，并提供多种负载均衡算法和容错机制 提供基于IP和域名的虚拟主机服务 提供了模拟化的结构，同一个Proxy请求的多个子请求并发处理 IMAP/POP3/SMTP代理服务 支持FLV（Flash视频） 基于客户端IP地址和HTTP基本认证的访问控制 Nginx + Tomcat 集群架构 # 整体思想方向：前端部署nginx服务器，后端部署tomcat应用 用户访问nginx服务器，对于静态资源，nginx服务器直接返回到浏览器展示给用户，对动态资源的请求被nginx服务器转发（分配）到tomcat应用服务器，tomcat应用服务器将处理后得到的数据结构返回给nginx服务器，然后返回到浏览器展示给用户 负载均衡机制 # 负载均衡的好处 # 1、多个服务器作响应\n2、服务器不会宕机\n3、能够对主机减少压力\n负载均衡策略（面试题） # 1、轮询（默认）\n每个请求按时间顺序逐一分配到不同的后端服务器，如果后端服务器down掉，能自动剔除。\n2、指定权重\n指定轮询几率，weight和访问比率成正比，用于后端服务器性能不均的情况。\n3、IP绑定 ip_hash\n每个请求按访问ip的hash结果分配，这样每个访客固定访问一个后端服务器，可以解决session的问题。\n4、fair（第三方）\n按后端服务器的响应时间来分配请求，响应时间短的优先分配。\n5、url_hash（第三方）\n按访问url的hash结果来分配请求，使每个url定向到同一个后端服务器，后端服务器为缓存时比较有效。\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/2c72a928/3098f9c4/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eTomcat存在的问题 \n    \u003cdiv id=\"tomcat存在的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#tomcat%e5%ad%98%e5%9c%a8%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e1、Tomcat：一种轻量级的WEB容器，它的适用场景中小型系统或者并发量不高的系统，它是apache平台与Sun公司一起合作开发出来的，专门支持Servlet的一种WEB容器\u003c/p\u003e","title":"1、Nginx+Tomcat集群","type":"posts"},{"content":"PIL：Python Imaging Library，已经是Python平台事实上的图像处理标准库了。PIL功能非常强大，但API却非常简单易用。\n由于PIL仅支持到Python 2.7，加上年久失修，于是一群志愿者在PIL的基础上创建了兼容的版本，名字叫Pillow，支持最新Python 3.x，又加入了许多新特性，因此，我们可以直接安装使用Pillow。\n文档地址：https://pillow-cn.readthedocs.io/zh_CN/latest/\n安装 # pip install pillow 主要模块 # Image：模块提供了一个具有相同名称的类，该类用于表示 PIL 映像。该模块还提供了许多出厂函数，包括从文件加载图像和创建新映像的函数。\nImageChops：模块包含许多算术图像操作，称为通道操作（\u0026ldquo;chops\u0026rdquo;）。这些可用于各种目的，包括特殊效果，图像合成，算法绘画等。\nImageColor：模块包含从 CSS3 样式的颜色说明符到 RGB 元组的颜色表和转换器。\nImageDraw：模块为 Image对象提供简单的 2D 图形。可以使用此模块创建新图像、批注或修饰现有图像，以及动态生成图形以供 Web 使用。\nImageGrab：模块可用于将屏幕或剪贴板的内容复制到 PIL 图像内存中。\nImage # 文件的属性 # from PIL import Image im = Image.open(\u0026#39;./test.jpg\u0026#39;) #format属性定义了图像的格式，如果图像不是从文件打开的，那么该属性值为None print(\u0026#39;图片的格式为：%s\u0026#39; % im.format) #size属性是一个tuple，表示图像的宽和高（单位为像素） print(\u0026#39;图片的，宽：%s，高：%s\u0026#39; % im.size) #mode属性为表示图像的模式，常用的模式为：L为灰度图，RGB为真彩色，CMYK为pre-press图像 print(\u0026#39;图片的模式为：%s\u0026#39; % im.mode) \u0026#39;\u0026#39;\u0026#39; 图片的格式为：JPEG 图片的，宽：995，高：995 图片的模式为：RGB \u0026#39;\u0026#39;\u0026#39; 缩放及保存 # from PIL import Image im = Image.open(\u0026#39;./test.jpg\u0026#39;) #获取图片的尺寸 w,h = im.size #进行缩放，这个函数直接修改原图的大小 #也可以是用resize()，这个函数不会修改原图大小，会返回 im.thumbnail((w / 2,h / 2)) #保存使用jpeg的格式保存图片 im.save(\u0026#39;./ahalf.jpg\u0026#39;,\u0026#39;jpeg\u0026#39;) Image与二进制流 # 以二进制流保存图片 # from PIL import Image from io import BytesIO im = Image.open(\u0026#39;./test.jpg\u0026#39;) b = BytesIO() #保存为二进制流 im.save(b,\u0026#39;jpeg\u0026#39;) #将二进制流中的数据，写入一个jpg文件 with open(\u0026#39;./new.jpg\u0026#39;,\u0026#39;wb\u0026#39;) as f: f.write(b.getvalue()) b.close() 以二进制流打开图片 # from PIL import Image from io import BytesIO f = open(\u0026#39;./test.jpg\u0026#39;,\u0026#39;rb\u0026#39;) b = BytesIO(f.read()) im = Image.open(b) #使用系统默认图片浏览器打开图片 im.show() ImageFilter # 图片效果 # from PIL import Image,ImageFilter im = Image.open(\u0026#39;./test.jpg\u0026#39;) # 高斯模糊 im.filter(ImageFilter.GaussianBlur) # 普通模糊 im.filter(ImageFilter.BLUR) # 边缘增强 im.filter(ImageFilter.EDGE_ENHANCE) # 找到边缘 im.filter(ImageFilter.FIND_EDGES) # 浮雕 im.filter(ImageFilter.EMBOSS) # 轮廓 im.filter(ImageFilter.CONTOUR) # 锐化 im.filter(ImageFilter.SHARPEN) # 平滑 im.filter(ImageFilter.SMOOTH) # 细节 im.filter(ImageFilter.DETAIL) im.show() ImageGrab # 屏幕截图 # from PIL import ImageGrab #截取屏幕指定区域的图像,不带参数表示全屏幕截图 #参数为一个tuple,分别是：开始位置、结束位置、长、高 im = ImageGrab.grab((0,0,800,200)) im.show() ImageDraw # 生成验证码 # from PIL import Image,ImageDraw,ImageFilter,ImageFont import random import string from io import BytesIO #随机字母或数字 def ranChar(): charList = string.ascii_letters + string.digits return random.choice(charList) #生成随机rgb颜色1 def ranColor1(): return (random.randint(64,255),random.randint(64,255),random.randint(64,255)) #生成随机rgb颜色2 def ranColor2(): return (random.randint(32,127),random.randint(32,127),random.randint(32,127)) #生成验证码图片字节流和验证码字符串 def getVerImage(size): \u0026#34;根据宽高tuple，生成验证码图片字节流和验证码字符串\u0026#34; verStr = \u0026#39;\u0026#39; #生成一个自定义图片，模式RGB，图片底色255.255.255 im = Image.new(\u0026#39;RGB\u0026#39;,size,(255,255,255)) #生成画笔对象 draw = ImageDraw.Draw(im) #给图片上放干扰点 for x in range(size[0]): for y in range(size[1]): draw.point(xy=(x,y),fill=ranColor1()) # 创建Font对象,字体文件可以到网上下一个，或者到c:/windows/Fonts文件夹找一个 font = ImageFont.truetype(font=\u0026#39;./ARLRDBD.TTF\u0026#39;,size=36) # 输出文字: for t in range(4): ranc = ranChar() verStr += ranc draw.text((60 * t + 10, 10), ranc,font=font, fill=ranColor2()) #添加模糊效果 im.filter(ImageFilter.BLUR) with BytesIO() as bt: im.save(bt,\u0026#39;jpeg\u0026#39;) return bt.getvalue(),verStr #测试 bts,verStr = getVerImage((240,60)) print(verStr) with open(\u0026#39;./ver.jpg\u0026#39;,\u0026#39;wb\u0026#39;) as f: f.write(bts) ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/1b37041b/46b8de4c/5fb5443e/","section":"文章","summary":"\u003cp\u003ePIL：Python Imaging Library，已经是Python平台事实上的图像处理标准库了。PIL功能非常强大，但API却非常简单易用。\u003c/p\u003e","title":"1、Pillow","type":"posts"},{"content":" 官网或镜像下载 # 官网（下载很慢）：http://download.qt.io/\n国内镜像：\n中国科学技术大学：http://mirrors.ustc.edu.cn/qtproject/ 清华大学：https://mirrors.tuna.tsinghua.edu.cn/qt/ 北京理工大学：http://mirror.bit.edu.cn/qtproject/ 中国互联网络信息中心：https://mirrors.cnnic.cn/qt/ 下载页面目录说明 # 目录 说明 archive 各种 Qt 开发工具安装包，新旧都有（可以下载 Qt 开发环境和源代码）。 community_releases 社区定制的 Qt 库，Tizen 版 Qt 以及 Qt 附加源码包。 development_releases 开发版，有新的和旧的不稳定版本，在 Qt 开发过程中的非正式版本。 learning 有学习 Qt 的文档教程和示范视频。 ministro 迷你版，目前是针对Android的版本。 official_releases 正式发布版，是与开发版相对的稳定版 Qt 库和开发工具（可以下载Qt开发环境和源代码）。 online Qt 在线安装源。 snapshots 预览版，最新的开发测试中的 Qt 库和开发工具。 archive 和 official_releases 两个目录都有最新的 Qt 开发环境安装包\n1、进入archive目录 # 目录 说明 vsaddin 这是 Qt 针对 Visual Studio 集成的插件，本教程基本不使用 Visual Studio ，所以不需要插件。 qtcreator 这是 Qt 官方的集成开发工具，但是 qtcreator 本身是个空壳，它没有编译套件和 Qt 开发库。 除了老版本的 Qt 4 需要手动下载 qtcreator、编译套件、Qt 开发库进行搭配之外，一般用不到。对于我们教程压根不需要下载它，因为 Qt 5 有专门的大安装包，里面包含开发需要的东西，并且能自动配置好。 qt 这是 Qt 开发环境的下载目录，我们刚说的 Qt 5 的大安装包就在这里面。 online_installers 在线安装器，国内用户不建议使用，在线安装是龟速，还经常断线。我们教程采用的全部是离线的大安装包 2、进入qt目录 # 会出现版本，选择一个版本，不要是最新的就可以，然后再进入子目录\n3、选择自己的操作系统 # windows下安装 # 1、注册和登录 # 不用管，直接下一步\n2、安装路径和关联文件 # 3、选择安装组件 # 组件说明 # qt5.9下组件\n组件 说明 MinGW 5.3.0 32 bit 编译器模块。MinGW 是 Minimalist GNU for Windows 的缩写，MinGW 是 Windows 平台上使用的 GNU 工具集导入库的集合。是本教程使用 MinGW 编译，所以必须安装。 UWP UWP 是 Windows 10 中 Universal Windows Platform 的简称，有不同编译器类型的 UWP，属于 MSVC 编译器生成的 Qt 库。如果不是开发 UWP 应用程序，就不需要，直接忽略。 MSVC 针对 Windows 平台上的 MSVC 编译器的 Qt 组件，如 msvc2015 32-bit 和 msvc2015 64-bit 等。安装该组件需要计算机上已经安装相应版本的 Visual Studio。如果你不使用 MSVC 编译器进行开发，就不用安装。本教程使用 MinGW 编译组件，所以不用安装 MSVC *** 组件。 Android 这是针对安卓应用开发的 Qt 库，如果读者有安卓开发这方面需求可以自己选择安装，一般情况下用不到。 Sources Qt 的源代码包，除非你想阅读 Qt 的源码，否则不用安装。 Qt Qt 的附加模块，大部分建议安装，这些附加模块括号里的 TP 是指 Technology Preview ，技术预览模块的意思，还处在功能测试阶段，不是正式版模块；附加模块括号里的 Deprecated 是指抛弃的旧模块，兼容旧代码使用的，一般用不到。这些附加模块读者可以选择部分或都勾选了安装，占用空间不大。 部分组件说明：Qt Charts 是二维图表模块，用于绘制柱状图、饼图、曲线图等常用二维图表。Qt Data Visualization 是三维数据图表模块，用于数据的三维显示，如散点的三维空间分布、三维曲面等。Qt Scritp（Deprecated）是脚本模块，已被抛弃，不建议安装。 Tools下组件\n组件 说明 Qt Creator 4.3.0 这是集成开发环境，强制安装的，以后所有的项目和代码都在 Qt Creator 里面新建和编辑。 Qt Creator 4.3.0 CDB Debugger surpport 用于和 CDB 调试工具对接，默认安装，一般用于调试 VC 编译的 Qt 程序。 MinGW 5.3.0 这是开源的编译器套件，这本教程必须用到的，需要读者勾选安装。 Strawberry Perl 5.22.1.3 用于编译 Qt 源代码的 Perl 开发环境，不需要安装。如果读者以后用到，也可以另外手动安装，在搜索引擎搜索 Strawberry Perl 关键词，去 Strawberry Perl 官网下载最新的安装包是一样用的。 4、剩下全部下一步就可以了 # ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/d8245fe1/83556763/ee27615a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e官网或镜像下载 \n    \u003cdiv id=\"官网或镜像下载\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%98%e7%bd%91%e6%88%96%e9%95%9c%e5%83%8f%e4%b8%8b%e8%bd%bd\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e官网（下载很慢）：http://download.qt.io/\u003c/p\u003e","title":"1、QT安装","type":"posts"},{"content":" 消息队列 # MQ是消息队列服务器，产品：ActiveMQ，RabbitMQ，RocketMQ，Kafka……\n消息队列（Message Queue，简称MQ），从字面意思上看，本质是个队列，FIFO先入先出，只不过队列中存放的内容是message而已\nRabbitMQ是一个由erlang开发的基于AMQP（Advanced Message Queue Protocol）协议的开源实现。用于在分布式系统中存储转发消息，在易用性、扩展性、高可用性等方面都非常的优秀。是当前最主流的消息中间件之一\n应用场景 # 1、异步处理 # 当用户注册，给邮箱和手机都发送验证信息\n用户注册需要50ms、发邮箱需要50ms、发手机需要50ms，总共需要150ms\n用户注册后、将发邮箱和发手机都放到消息队列中，可以通过消息队列进行异步处理,总共可以节省50ms\n2、程序解耦 # 多应用间通过消息队列对同一消息进行处理，避免调用接口失败导致整个过程失败\n3、限流削峰 # 广泛应用于秒杀或抢购活动中，避免流量过大导致应用系统挂掉的情况\nRabbitMQ选型和对比 # 1.从社区活跃度 # 按照目前网络上的资料，RabbitMQ 、activeM 、ZeroMQ 三者中，综合来看，RabbitMQ 是首选。\n2.持久化消息比较 # ZeroMq 不支持，ActiveMq 和RabbitMq 都支持。持久化消息主要是指我们机器在不可抗力因素等情况下挂掉了，消息不会丢失的机制。\n3.综合技术实现 # 可靠性、灵活的路由、集群、事务、高可用的队列、消息排序、问题追踪、可视化管理工具、插件系统等等。\nRabbitMq / Kafka 最好，ActiveMq 次之，ZeroMq 最差。当然ZeroMq 也可以做到，不过自己必须手动写代码实现，代码量不小。尤其是可靠性中的：持久性、投递确认、发布者证实和高可用性。\n4.高并发 # 毋庸置疑，RabbitMQ 最高，原因是它的实现语言是天生具备高并发高可用的erlang 语言。\n5.比较关注的比较， RabbitMQ 和 Kafka # RabbitMq 比Kafka 成熟，在可用性上，稳定性上，可靠性上，RabbitMQ胜于 Kafka（理论上）。\n另外，Kafka 的定位主要在日志等方面， 因为Kafka 设计的初衷就是处理日志的，可以看做是一个日志（消息）系统一个重要组件，针对性很强，所以 如果业务方面还是建议选择 RabbitMq 。\n还有就是，Kafka 的性能（吞吐量、TPS ）比RabbitMq 要高出来很多。\n安装配置 # 1、Erlang的安装及配置 # 1.1、下载 # 下载网址：http://www.erlang.org/downloads\n选择自己的安装目录，全程下一步\n1.2、配置环境变量 # 配置path，为ERLANG_HOME\\bin\n1.3、查看配置是否成功 # cmd下输入erl\n2、RabbitMQ的安装及配置 # 2.1、下载RabbitMQ # 下载网址：http://www.rabbitmq.com/download.html\n2.2、安装RabbitMQ # 双击安装包，全程下一步\n2.3、安装RabbitMQ-Plugins # 进入RabbitMQ的sbin目录，打开cmd\n执行命令：rabbitmq-plugins enable rabbitmq_management\n2.4、配置RabbitMQ环境变量 # path环境变量中添加：%RABBITMQ_HOME%\\sbin;\n3、启动RabbitMQ服务 # cmd输入命令 开启服务 net start RabbitMQ 关闭服务 net stop RabbitMQ 如果启动报错 # 管理员运行cmd\n1、执行命令：rabbitmq-service.bat remove\n2、在RabbitMq任意路径下，建立目录data\n3、执行命令，路径改为刚才创建的名录：set RABBITMQ_BASE=D:\\rabbitmq_server\\data\n4、执行命令：rabbitmq-service.bat install\n5、执行命令：rabbitmq-plugins enable rabbitmq_management\n6、在管理服务中启动服务就可以了、启动输入命令：services.msc\n启动成功 # 4、访问RabbitMQ登录页面 # http://localhost:15672\n初始的账号密码都是：guest\n登录监控平台 # Linux下安装 # 获取RabbitMQ的镜像 # docker pull daocloud.io/library/rabbitmq:3.7.26-management 启动RabbitMQ容器 # docker run -d --net=host --name some-rabbit --privileged=true daocloud.io/library/rabbitmq:3.7.26-management\n开启完毕之后，会暴露2大端口\n5672：数据访问端口 15672：监控平台端口 \u0026ndash;net=host：主动暴露端口 （也可以使用-p 一个一个的做映射） 如果没有关闭防火墙，请暴露端口\nfirewall -cmd --permanent --add-port=5672/tcp --permanent firewall -cmd --permanent --add-port=15672/tcp --permanent firewall -cmd --reload 应用中的问题 # 系统架构 # producer\u0026amp;Consumer p指的是消息生产者，c指消息的消费者 Queue：消息队列 提供了FIFO的处理机制，具有缓存消息的能力。rabbitmq中，队列消息可以设置为持久化，临时或者自动删除。 设置为持久化的队列，queue中的消息会在server本地硬盘存储一份，防止系统crash，数据丢失 设置为临时队列，queue中的数据在系统重启之后就会丢失 设置为自动删除的队列，当不存在用户连接到server，队列中的数据会被自动删除 Exchange：消息交换机 Exchange类似于数据通信网络中的交换机，提供消息路由策略。rabbitmq中，producer不是通过信道直接将消息发送给queue，而是先发送给Exchange。 一个Exchange可以和多个Queue进行绑定，producer在传递消息的时候，会传递一个ROUTINGKEY，Exchange会根据这个ROUTINGKEY按照特定的路由算法，将消息路由给指定的queue。 和Queue一样，Exchange也可设置为持久化，临时或者自动删除。 Binding：绑定 它的作用就是把exchange和queue按照路由规则绑定起来. Routing Key：路由关键字 exchange根据这个关键字进行消息投递。 Channel：消息通道 在客户端的每个连接里，可建立多个channel 信道是生产消费者与rabbit通信的渠道，生产者publish或者消费者消费一个队列都是需要通过信道来通信的。 信道是建立在TCP上面的虚拟链接，也就是rabbitMQ在一个TCP上面建立成百上千的信道来达到多个线程处理。 注意是一个TCP 被多个线程共享，每个线程对应一个信道，信道在rabbit都有唯一的ID，保证了信道的私有性，对应上唯一的线程使用。 ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/e2f9d0f6/81f45686/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e消息队列 \n    \u003cdiv id=\"消息队列\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%b6%88%e6%81%af%e9%98%9f%e5%88%97\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eMQ是消息队列服务器，产品：ActiveMQ，RabbitMQ，RocketMQ，Kafka……\u003c/p\u003e","title":"1、RabbitMQ","type":"posts"},{"content":" Shiro简介 # 公司项目中，常见的权限框架：shiro，spring security Apache Shiro是一个功能强大且灵活的开源安全框架，可以清晰地处理身份验证，授权，企业会话管理和加密 Apache Shiro的首要目标是易于使用和理解。安全有时可能非常复杂，甚至是痛苦的，但并非必须如此。框架应尽可能掩盖复杂性，并提供简洁直观的API，以简化开发人员确保其应用程序安全的工作 Shiro能帮系统做什么 # 做用户的身份认证，判断用户是否系统用户（重点） 给系统用户授权，用来帮助系统实现不同的用户展示不同的功能（重点） 针对密码等敏感信息，进行加密处理（明文变成密文）（重点） 提供了Session管理，但是它的Session不是HttpSession，是它自己自带的 做授权信息的缓存管理，降低对数据库的授权访问 提供测试支持，因为它也是一个轻量级框架，它也可以直接针对代码进行使用Junit单元测试 提供Remeber me的功能，可以做用户无需再次登录即可访问某些页面 Shiro提供的10大功能 # Authentication：身份认证/登录，验证用户是不是拥有相应的身份； Authorization：授权，即权限验证，验证某个已认证的用户是否拥有某个权限；即判断用户是否能做事情，常见的如：验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限 Session Management：会话管理，即用户登录后就是一次会话，在没有退出之前，它的所有信息都在会话中；会话可以是普通JavaSE环境的，也可以是如Web环境的 Cryptography：加密，保护数据的安全性，如密码加密存储到数据库，而不是明文存储； Web Support ：Web支持，可以非常容易的集成到Web环境； Caching 缓存，比如用户登录后，其用户信息、拥有的角色/权限不必每次去查，这样可以提高效率； Concurrency shiro：支持多线程应用的并发验证，即如在一个线程中开启另一个线程，能把权限自动传播过去； Testing：提供测试支持； Run As：允许一个用户假装为另一个用户（如果他们允许）的身份进行访问； Remember Me：记住我，这个是非常常见的功能，即一次登录后，下次再来的话不用登录了 Shiro框架的3个核心类 # Subject主体：需要登录系统的东西，都是主体。 代表了当前“用户”，这个用户不一定是一个具体的人，与当前应用交互的任何东西都是Subject，如网络爬虫，机器人等；即一个抽象概念；所有Subject都绑定到SecurityManager，与Subject的所有交互都会委托给SecurityManager，可以把Subject认为是一个门面；SecurityManager才是实际的执行者； SecurityManager安全管理器（实现类DefaultWebSecurityManager）：即所有与安全有关的操作都会与SecurityManager交互；且它管理着所有Subject；可以看出它是Shiro的核心，它负责与其他组件进行交互，相当于DispatcherServlet前端控制器； Realm域：一个用来做身份认证，以及授权的对象Shiro从Realm获取安全数据（如用户、角色、权限），就是SecurityManager要验证用户身份，那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法；也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作；可以把Realm看成DataSource，就是一个跟权限数据有关的数据源 Subject常用方法 # isAuthenticated()：判断当前的subject中包含的用户信息是否已经被认证（登录）\nlogin(token)：将当前的token（令牌）中存入的用户信息，交给realm的doGetAuthenticationInfo()方法进行验证\ngetSession()：获取shiro封装的Session对象\ngetPrincipal()：可以获取subject.login(token)后，在执行认证方法返回new SimpleAuthenticationInfo()的第一个形参对象，一般就是当前的登录用户对象\n使用 # 1、引入shiro依赖 # \u0026lt;!-- shiro的相关依赖 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.shiro\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;shiro-spring\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.4.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.shiro\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;shiro-ehcache\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.3.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.shiro\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;shiro-all\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.shiro\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;shiro-core\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.shiro\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;shiro-web\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 2、编写Realm组件，继承AuthorizingRealm，重写方法 # public class LoginAndAuthRealm extends AuthorizingRealm { //授权方法，需要使用\u0026lt;shiro\u0026gt;或@Shiro相同注解才能触发 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } //身份认证方法，需要在用户登录系统时触发 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { return null; } } 3、编写ShiroConfigure配置类 # @Configuration public class ShiroConfig{ //管理Subject主体对象，生命周期的组件 @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){ return new LifecycleBeanPostProcessor(); } //配置shiro框架中，用来完成身份认证，授权的Realm域对象 @Bean public LoginAndAuthRealm loginAndAuthRealm(){ return new LoginAndAuthRealm(); } //安全管理器，相当于SpringMVC框架的DispatherServlet @Bean public DefaultWebSecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 告诉安全管理器认证和授权的对象 // 如果有多个认证和授权对象，请使用：securityManager.setRealms(集合); securityManager.setRealm(loginAndAuthRealm()); return securityManager; } //Shiro过滤器，该过滤器的作用：完成对页面请求的过滤，并针对请求，配置对应的过滤规则\t@Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean() { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); // 配置安全管理器（所有的请求，需要经过安全管理器） factoryBean.setSecurityManager(securityManager()); // 配置登录路径、成功路径、无权限路径 factoryBean.setLoginUrl(\u0026#34;/\u0026#34;); factoryBean.setSuccessUrl(\u0026#34;/main\u0026#34;); factoryBean.setUnauthorizedUrl(\u0026#34;/unauthorized\u0026#34;); // 配置Shiro的过滤器链 Map\u0026lt;String, String\u0026gt; filters = new LinkedHashMap\u0026lt;\u0026gt;(); // anon：匿名，无需认证，直接操作 // 登录页面，匿名方式 filters.put(\u0026#34;/\u0026#34;, \u0026#34;anon\u0026#34;); // 登录请求，匿名方式 filters.put(\u0026#34;/sys/login\u0026#34;, \u0026#34;anon\u0026#34;); // 静态资源，匿名方式 filters.put(\u0026#34;/static/**\u0026#34;, \u0026#34;anon\u0026#34;); // authc：一定要认证 // 其他的，暂定为：需要认证 filters.put(\u0026#34;/**\u0026#34;, \u0026#34;authc\u0026#34;); // 将过滤规则，设置给Shiro工厂 factoryBean.setFilterChainDefinitionMap(filters); return factoryBean; }\t} 执行流程 # 认证流程 # ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/c3527798/d9a64321/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eShiro简介 \n    \u003cdiv id=\"shiro简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#shiro%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e公司项目中，常见的权限框架：shiro，spring security\u003c/li\u003e\n\u003cli\u003eApache Shiro是一个功能强大且灵活的开源安全框架，可以清晰地处理身份验证，授权，企业会话管理和加密\u003c/li\u003e\n\u003cli\u003eApache Shiro的首要目标是易于使用和理解。安全有时可能非常复杂，甚至是痛苦的，但并非必须如此。框架应尽可能掩盖复杂性，并提供简洁直观的API，以简化开发人员确保其应用程序安全的工作\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eShiro能帮系统做什么 \n    \u003cdiv id=\"shiro能帮系统做什么\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#shiro%e8%83%bd%e5%b8%ae%e7%b3%bb%e7%bb%9f%e5%81%9a%e4%bb%80%e4%b9%88\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e做用户的身份认证，判断用户是否系统用户（重点）\u003c/li\u003e\n\u003cli\u003e给系统用户授权，用来帮助系统实现不同的用户展示不同的功能（重点）\u003c/li\u003e\n\u003cli\u003e针对密码等敏感信息，进行加密处理（明文变成密文）（重点）\u003c/li\u003e\n\u003cli\u003e提供了Session管理，但是它的Session不是HttpSession，是它自己自带的\u003c/li\u003e\n\u003cli\u003e做授权信息的缓存管理，降低对数据库的授权访问\u003c/li\u003e\n\u003cli\u003e提供测试支持，因为它也是一个轻量级框架，它也可以直接针对代码进行使用Junit单元测试\u003c/li\u003e\n\u003cli\u003e提供Remeber me的功能，可以做用户无需再次登录即可访问某些页面\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003ch2 class=\"relative group\"\u003eShiro提供的10大功能 \n    \u003cdiv id=\"shiro提供的10大功能\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#shiro%e6%8f%90%e4%be%9b%e7%9a%8410%e5%a4%a7%e5%8a%9f%e8%83%bd\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"说明: clipboard.png\"\n    data-zoom-src=\"/posts/3ab7256e/98c7af00/c3527798/d9a64321/image/202109181345045.gif\"\n    src=\"/posts/3ab7256e/98c7af00/c3527798/d9a64321/image/202109181345045.gif\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"1、Shiro","type":"posts"},{"content":"javafx.stage.Stage，在一个javafx桌面应用程序里显示一个窗口。在JavaFX Stage内部，你可以插入一个JavaFX Scence，来显示窗口里要的内容。\n当JavaFX应用程序启动时，它会创建一个根Stage对象，并将其传递给JavaFX应用程序根类的start（Stage primaryStage）方法。此Stage对象表示JavaFX应用程序的主窗口。如果应用程序需要打开更多窗口，可以在应用程序生命周期的后期创建新的阶段对象。\nAPI # Stage primaryStage = new Stage(); //显示窗口 stage.show(); //添加窗口图标 primaryStage.getIcons().add(new Image(\u0026#34;/image/logo.png\u0026#34;)); //设置窗口标题 primaryStage.setTitle(\u0026#34;title\u0026#34;); //设置窗口最小化 primaryStage.setIconified(true); //设置窗口最大化 primaryStage.setMaximized(true); //关闭窗口 primaryStage.close(); //设置宽高 primaryStage.setHeight(500); primaryStage.setWidth(500); //设置不可调整大小 primaryStage.setResizable(false); //设置全屏 primaryStage.setFullScreen(true); //设置窗口不透明度，范围0-1 primaryStage.setOpacity(0.2); //设置窗口始终置顶（所有应用的最顶层） primaryStage.setAlwaysOnTop(true); 窗口风格 # primaryStage.initStyle(StageStyle.TRANSPARENT); DECORATED——白色背景，带有最小化/最大化/关闭等有操作系统平台装饰（ 默认设置）\nUNDECORATED——白色背景，没有操作系统平台装饰\nTRANSPARENT——透明背景，没有操作系统平台装饰\nUTILITY——白色背景，只有关闭操作系统平台装饰\nUNIFIED——有操作系统平台装饰，消除装饰和内容之间的边框，内容背景和边框背景一致\n窗口模态 # APPLICATION_MODAL：最高优先级，当前窗口未关闭，操作其他窗口会有提示音\nWINDOW_MODAL：基本不用\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/d9073060/d57b26b2/5236cf60/","section":"文章","summary":"\u003cp\u003e\u003ccode\u003ejavafx.stage.Stage\u003c/code\u003e，在一个javafx桌面应用程序里显示一个窗口。在JavaFX Stage内部，你可以插入一个JavaFX Scence，来显示窗口里要的内容。\u003c/p\u003e","title":"1、Stage","type":"posts"},{"content":" 为什么要有STL # 长久以来，软件界一直希望建立一种可重复利用的东西 C++的面向对象和泛型编程思想，目的就是复用性的提升 大多情况下，数据结构和算法都未能有一套标准,导致被迫从事大量重复工作 为了建立数据结构和算法的一套标准,诞生了STL STL的概念 # STL(Standard Template Library,标准模板库) STL 从广义上分为: 容器(container) 算法(algorithm) 迭代器(iterator) 容器和算法之间通过迭代器进行无缝连接 STL 几乎所有的代码都采用了模板类或者模板函数 STL六大组件 # STL大体分为六大组件，分别是:容器、算法、迭代器、仿函数、适配器（配接器）、空间配置器\n容器：各种数据结构，如vector、list、deque、set、map等，用来存放数据 算法：各种常用的算法，如sort、find、copy、for_each等 迭代器：扮演了容器与算法之间的胶合剂 仿函数：行为类似函数，可作为算法的某种策略 适配器：一种用来修饰容器或者仿函数或迭代器接口的东西 空间配置器：负责空间的配置与管理 STL中容器、算法、迭代器 # 容器：置物之所也 # STL容器就是将运用最广泛的一些数据结构实现出来\n常用的数据结构：数组, 链表,树, 栈, 队列, 集合, 映射表 等\n这些容器分为序列式容器和关联式容器两种：\n序列式容器：强调值的排序，序列式容器中的每个元素均有固定的位置。 关联式容器：二叉树结构，各元素之间没有\n严格的物理上的顺序关系\n算法：问题之解法也 # 有限的步骤，解决逻辑或数学上的问题，这一门学科我们叫做算法(Algorithms)\n算法分为：质变算法和非质变算法。\n质变算法：是指运算过程中会更改区间内的元素的内容。例如拷贝，替换，删除等等\n非质变算法：是指运算过程中不会更改区间内的元素内容，例如查找、计数、遍历、寻找极值等等\n迭代器：容器和算法之间粘合剂 # 提供一种方法，使之能够依序寻访某个容器所含的各个元素，而又无需暴露该容器的内部表示方式。\n每个容器都有自己专属的迭代器\n迭代器使用非常类似于指针，初学阶段我们可以先理解迭代器为指针\n迭代器种类 # 种类 功能 支持运算 输入迭代器 对数据的只读访问 只读，支持++、==、！= 输出迭代器 对数据的只写访问 只写，支持++ 前向迭代器 读写操作，并能向前推进迭代器 读写，支持++、==、！= 双向迭代器 读写操作，并能向前和向后操作 读写，支持++、\u0026ndash;， 随机访问迭代器 读写操作，可以以跳跃的方式访问任意数据，功能最强的迭代器 读写，支持++、\u0026ndash;、[n]、-n、\u0026lt;、\u0026lt;=、\u0026gt;、\u0026gt;= 常用的容器中迭代器种类为双向迭代器，和随机访问迭代器\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/d8245fe1/bf736d53/e6881d5c/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e为什么要有STL \n    \u003cdiv id=\"为什么要有stl\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%ba%e4%bb%80%e4%b9%88%e8%a6%81%e6%9c%89stl\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e长久以来，软件界一直希望建立一种可重复利用的东西\u003c/li\u003e\n\u003cli\u003eC++的面向对象和泛型编程思想，目的就是复用性的提升\u003c/li\u003e\n\u003cli\u003e大多情况下，数据结构和算法都未能有一套标准,导致被迫从事大量重复工作\u003c/li\u003e\n\u003cli\u003e为了建立数据结构和算法的一套标准,诞生了STL\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003ch2 class=\"relative group\"\u003eSTL的概念 \n    \u003cdiv id=\"stl的概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#stl%e7%9a%84%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003eSTL(Standard Template Library,标准模板库)\u003c/li\u003e\n\u003cli\u003eSTL 从广义上分为: 容器(container) 算法(algorithm) 迭代器(iterator)\u003c/li\u003e\n\u003cli\u003e容器和算法之间通过迭代器进行无缝连接\u003c/li\u003e\n\u003cli\u003eSTL 几乎所有的代码都采用了模板类或者模板函数\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003ch2 class=\"relative group\"\u003eSTL六大组件 \n    \u003cdiv id=\"stl六大组件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#stl%e5%85%ad%e5%a4%a7%e7%bb%84%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eSTL大体分为六大组件，分别是:容器、算法、迭代器、仿函数、适配器（配接器）、空间配置器\u003c/p\u003e","title":"1、STL基本概念","type":"posts"},{"content":" WebService简介 # Web Service技术， 能使得运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件， 就可相互交换数据或集成。依据Web Service规范实施的应用之间， 无论它们所使用的语言、平台或内部协议是什么，都可以相互交换数据。\n简单的说，WebService就是一种跨编程语言和跨操作系统平台的远程调用技术。所谓跨编程语言和跨操作平台，就是说服务端程序采用java编写，客户端程序则可以采用其他编程语言编写，反之亦然。跨操作系统平台则是指服务端程序和客户端程序可以在不同的操作系统上运行。 远程调用，就是一台计算机的应用可以调用其他计算机上的应用。例如：支付宝，支付宝并没有银行卡等数据，它只是去调用银行提供的接口来获得数据。还有天气预报等，也是气象局把自己的系统服务以webservice服务的形式暴露出来，让第三方网站和程序可以调用这些服务功能。\nWebService原理 # XML、SOAP和WSDL就是构成WebService平台的三大技术\nWSDL: 用来描述如何访问具体的服务。 SOAP: 基于HTTP协议，采用XML格式，用来传递信息的格式。 UDDI: 用户自己可以按UDDI标准搭建UDDI服务器，用来管理，分发，查询WebService 。其他用户可以自己注册发布WebService调用。 SOAP协议 # SOAP(Simple Object Access Protocol/简单文件传输协议)是一个轻量的、简单的、基于XML的协议。使用http发送的XML格式的数据，它可以跨平台，跨防火墙，SOAP不是webservice的专有协议。\nSOAP包括四个部分：\nSOAP封装(envelop)，封装定义了一个描述消息中的内容是什么，是谁发送的，谁应当接受并处理它以及如何处理它们的框架。 SOAP编码规则（encoding rules），用于表示应用程序需要使用的数据类型的实例。 SOAP RPC表示(RPC representation)，表示远程过程调用和应答的协定。 SOAP绑定（binding），使用底层协议交换信息。 SOAP的基本结构包含以下元素\nEnvelope（必须）标识文档为SOAP消息，是SOAP的根元素。 Header（可选）包含头部信息，如果SOAP消息有Header，则Header必须是Envelope的第一个子元素。 Body（必须）包含所有的调用和响应信息。 Fault（可选）提供错误消息 wsdl说明书 # Web Service描述语言WSDL（WebService Definition Language）就是用机器能阅读的方式提供的一个正式描述文档而基于XML（标准通用标记语言下的一个子集）的语言，用于描述Web Service及其函数、参数和返回值。因为是基于XML的，所以WSDL既是机器可阅读的，又是人可阅读的。\n抽象定义 # Types：定义Web服务使用的所有数据类型集合，可被元素的各消息部件所引用。 Messages：通信消息数据结构的抽象类型化定义。使用Types所定义的类型来定义整个消息的数据结构，包括函数参数或文档描述。 PortTypes：引用消息部分中消息定义来描述函数签名(操作名、输入参数、输出参数)。 具体定义 # Operation：对服务中操作的抽象描述。一个Operation描述了一个访问入口的请求/响应消息对。 Bindings：portType部分的操作在此绑定实现，包含了如何将抽象接口的元素转变为具体表示的细节。 Port：定义为协议/数据格式绑定与具体Web访问地址组合的单个服务访问点。 Services：确定每个绑定的端口地址。 portType、message和type描述了Web服务是什么，binding描述了如何使用Web服务，port和service描述了Web服务的位置。\nUDDI # UDDI(Universal Description, Discovery and Integration/通用描述、发现和集成)主要提供基于Web服务的注册和发现机制，为Web服务提供三个重要的技术支持：\n标准、透明、专门描述Web服务的机制。 调用Web服务的机制。 可以访问的Web服务注册中心。 但是使用webservice并不是必须使用UDDI，因为用户通过WSDL知道了web service的地址，可以直接通过WSDL调用webservice。\nJAVA WebService # Java 中共有三种WebService 规范，分别是JAXM\u0026amp;SAAJ、JAX-WS（JAX-RPC）、JAX-RS。\nJAX-WS JAX-WS（Java API For XML-WebService）。早期的基于SOAP 的JAVA 的Web 服务规范JAX-RPC（java API For XML-Remote Procedure Call）目前已经被JAX-WS 规范取代，JAX-WS 是JAX-RPC 的演进版本，但JAX-WS 并不完全向后兼容JAX-RPC，二者最大的区别就是RPC/encoded 样式的WSDL，JAX-WS 已经不提供这种支持。JAX-RPC 的API 从JAVA EE5 开始已经移除，如果你使用J2EE1.4，其API 位于javax.xml.rpc.包。JAX-WS（JSR 224）规范的API 位于javax.xml.ws.包，其中大部分都是注解，提供API 操作Web 服务（通常在客户端使用的较多，由于客户端可以借助SDK 生成，因此这个包中的API 我们较少会直接使用）。 JAXM\u0026amp;SAAJ JAXM（JAVA API For XML Message）主要定义了包含了发送和接收消息所需的API，相当于Web 服务的服务器端，其API 位于javax.messaging.*包，它是Java EE 的可选包，因此你需要单独下载。 SAAJ（SOAP With Attachment API For Java，JSR 67）是与JAXM 搭配使用的API，为构建SOAP 包和解析SOAP 包提供了重要的支持，支持附件传输，它在服务器端、客户端都需要使用。这里还要提到的是SAAJ 规范，其API 位于javax.xml.soap.*包。 JAXM\u0026amp;SAAJ 与JAX-WS 都是基于SOAP 的Web 服务，相比之下JAXM\u0026amp;SAAJ 暴漏了SOAP更多的底层细节，编码比较麻烦，而JAX-WS 更加抽象，隐藏了更多的细节，更加面向对象，实现起来你基本上不需要关心SOAP 的任何细节。那么如果你想控制SOAP 消息的更多细节，可以使用JAXM\u0026amp;SAAJ。 JAX-RS JAX-RS 是JAVA 针对REST(Representation State Transfer)风格制定的一套Web 服务规范，由于推出的较晚，该规范（JSR 311，目前JAX-RS 的版本为1.0）并未随JDK1.6 一起发行。 原生实现 # 服务端 # 1、编写服务接口 # @WebService( name = \u0026#34;testService\u0026#34;, //port name serviceName = \u0026#34;testService\u0026#34;, // 服务名称 targetNamespace = \u0026#34;http://top.ygnag.test/service\u0026#34; // namespace ) public interface TestService { @WebMethod(operationName = \u0026#34;testMethod\u0026#34;) @WebResult(name = \u0026#34;message\u0026#34;) String testMethod(@WebParam(name = \u0026#34;name\u0026#34;) String name); } 2、服务接口实现类 # @WebService( name = \u0026#34;testService\u0026#34;, //port name serviceName = \u0026#34;testService\u0026#34;, // 服务名称 targetNamespace = \u0026#34;http://top.ygnag.test/service\u0026#34; // namespace ) public class TestServiceImpl implements TestService{ @Override @WebMethod(operationName = \u0026#34;testMethod\u0026#34;) @WebResult(name = \u0026#34;message\u0026#34;) public String testMethod(@WebParam(name = \u0026#34;name\u0026#34;) String name) { return \u0026#34;accept success : \u0026#34; + name; } } 2、发布服务 # public class Server { public static void main(String[] args) { TestService testService = new TestServiceImpl(); Endpoint.publish(\u0026#34;http://localhost:8080/services/testService\u0026#34;,testService); } } 3、浏览器访问wsdl # 访问地址http://localhost:8080/services/testService?wsdl\n客户端 # 原生调用 # public class Client { public static void main(String[] args) throws Exception{ //创建WSDL文件的URL URL wsdlDocumentLocation = new URL(\u0026#34;http://127.0.0.1:8080/services/testService?wsdl\u0026#34;); //创建服务名称 //1.namespaceURI - 命名空间地址 //2.localPart - 服务名 QName serviceName=new QName(\u0026#34;http://top.ygnag.test/service\u0026#34;,\u0026#34;testService\u0026#34;); Service service=Service.create(wsdlDocumentLocation, serviceName); //获取服务实现类 TestService testService= service.getPort(TestService.class); //执行方法 String message = testService.testMethod(\u0026#34;webservice\u0026#34;); System.out.println(message); // accept success : webservice } } 使用谷歌浏览器Wizdler插件 # Wizdler，可以对webservice接口进行测试\n发送请求\n响应\nAxis2与CXF # 目前java开发WebService的框架主要包括Axis2和CXF，如果你需要多语言的支持，你应该选择Axis2。如果你需要把你的实现侧重java并希望和Spring集成，CXF就是更好的选择，特别是把你的WebService嵌入其他的程序中\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/578bc70a/d750bb3c/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eWebService简介 \n    \u003cdiv id=\"webservice简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#webservice%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eWeb Service技术， 能使得运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件， 就可相互交换数据或集成。依据Web Service规范实施的应用之间， 无论它们所使用的语言、平台或内部协议是什么，都可以相互交换数据。\u003c/p\u003e","title":"1、WebService","type":"posts"},{"content":" 原理 # 使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。\n代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。\n使用场景 # 静态代理 # 代码举例 # public interface Work { void doWork(); } //被代理类 public class NetWork implements Work{ @Override public void doWork() { System.out.println(\u0026#34;run NetWork\u0026#34;); } } //代理类 public class WorkProxy implements Work{ private Work work; public WorkProxy(Work work){ this.work = work; } @Override public void doWork() { System.out.println(\u0026#34;run WorkProxy\u0026#34;); work.doWork(); } } public static void main(String[] args) { NetWork netWork = new NetWork(); WorkProxy workProxy = new WorkProxy(netWork); workProxy.doWork(); //run WorkProxy //run do Work } 静态代理的缺点\n① 代理类和目标对象的类都是在编译期间确定下来，不利于程序的扩展。\n② 每一个代理类只能为一个接口服务，这样一来程序开发中必然产生过多的代理。\n动态代理 # 动态代理是指客户通过代理类来调用其它对象的方法，并且是在程序运行时根据需要动态创建目标类的代理对象。\n动态代理使用的场合 # 1、调试\n2、远程方法调用\n需要解决的问题 # 问题一：如何根据加载到内存中的被代理类，动态的创建一个代理类及其对象。\n通过Proxy.newProxyInstance()实现\n问题二：当通过代理类的对象调用方法a时，如何动态的去调用被代理类中的同名方法a。\n(通过InvocationHandler接口的实现类及其方法invoke())\n相较静态代理的优点 # 抽象角色中（接口）声明的所有方法都被转移到调用处理器一个集中的方法中处理，这样，我们可以更加灵活和统一的处理众多的方法。\n两种方式生成动态代理 # 方式一：java.lang.reflect.Proxy # Proxy.newProxyInstance(目标对象的类加载器，目标对象的接口，InvocationHandler调用代理对象时做的事情 )\n代码举例 # public interface Work { void doWork(); } public class NetWork implements Work{ @Override public void doWork() { System.out.println(\u0026#34;run NetWork\u0026#34;); } } public static void main(String[] args) { NetWork netWork = new NetWork(); Work proxy = proxy(netWork); proxy.doWork(); //run Proxy //run NetWork } //进行Proxy动态代理，返回代理对象 public static\u0026lt;T\u0026gt; T proxy(T obj){ Class clazz = obj.getClass(); //newProxyInstance方法，返回的就是代理对象 T proxtObj = (T)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new InvocationHandler() { //proxy代理对象 //method调用的方法 //args方法的参数 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(\u0026#34;run Proxy\u0026#34;); return method.invoke(obj, args); } }); return proxtObj; } 方式二：Cglib，子类代理 # JDK的动态代理机制只能代理实现了接口的类，而不能实现接口的类就不能实现JDK的动态代理，cglib是针对类来实现代理的，他的原理是对指定的目标类生成一个子类，并覆盖其中方法实现增强，但因为采用的是继承，所以不能对final修饰的类进行代理。\n使用方法 # 1、引入cglib的jar文件和asm的jar文件到工程\n2、引入功能包后，就可以在内存中动态的创建子类\n3、被代理类不可以使用final，否则报错\n4、被代理的方法，不可以使用final或static修饰\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;cglib\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;cglib\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.2.10\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 工厂类 # public static void main(String[] args) { NetWork netWork = new NetWork(); NetWork cglibProxy = cglib(netWork); cglibProxy.doWork(); //run Cglib Proxy //run NetWork } public static\u0026lt;T\u0026gt; T cglib(T obj){ //创建一个增强器 Enhancer enhancer = new Enhancer(); //设置父类，也就是被代理类 enhancer.setSuperclass(obj.getClass()); //设置方法的处理扩展 enhancer.setCallback(new MethodInterceptor() { //o代理对象本身 //method被代理对象的方法 //objects方法的参数 //methodProxy方法的代理 @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println(\u0026#34;run Cglib Proxy\u0026#34;); return method.invoke(obj,objects); } }); //获取一个代理对象 return (T) enhancer.create(); } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/6e08a29a/5d67f48c/cd17ae44/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e原理 \n    \u003cdiv id=\"原理\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8e%9f%e7%90%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。\u003c/p\u003e","title":"1、代理模式","type":"posts"},{"content":" 要解决的问题： # 所谓类的单例设计模式，就是采取一定的方法保证在整个的软件系统中，对某个类只能存在一个对象实例。\n具体代码的实现 # 饿汉式1 # class Bank{ //1.私化类的构造器 private Bank(){ } //2.内部创建类的对象 //4.要求此对象也必须声明为静态的 private static Bank instance = new Bank(); //3.提供公共的静态的方法，返回类的对象 public static Bank getInstance(){ return instance; } } 饿汉式2 # 使用了静态代码块\nclass Order{ //1.私化类的构造器 private Order(){ } //2.声明当前类对象，没初始化 //4.此对象也必须声明为static的 private static Order instance = null; static{ instance = new Order(); } //3.声明public、static的返回当前类对象的方法 public static Order getInstance(){ return instance; } } 懒汉式 # class Order{ //1.私化类的构造器 private Order(){ } //2.声明当前类对象，没初始化 //4.此对象也必须声明为static的 private static Order instance; //3.声明public、static的返回当前类对象的方法 public static Order getInstance(){ if(instance == null){ instance = new Order(); } return instance; } } 对比 # 饿汉式： 坏处：对象加载时间过长。 好处：饿汉式是线程安全的 懒汉式： 好处：延迟对象的创建。 坏处：线程不安全。\u0026mdash;\u0026gt;到多线程内容时，再修改 线程安全的懒汉 # //由于直接在方法上添加synchronized，开销比较大，所以使用双重检查锁 public class Singlet(){ private volatile static Singlet singlet; private Singlet(){}; public static Singlet getSinglet(){ if(singlet == null){ synchronized(Singlet.class){ if(singlet == null){ singlet = new Singlet(); } } } } } volatile关键字 # volatile可以保存内存可见，即便是跨线程，还可以禁止指令重排\n例如，此时程序会死循环，因为，主线程在运行到while循环的时候，i为0，而且这个i内存是不可见的，所以，即使另一个线程改变了i的值，循环还会继续\npublic class Test2 { private int i = 0; public static void main(String[] args) { Test2 test2 = new Test2(); Thread thread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } test2.add(); System.out.println(test2.i); } }); thread.start(); while(test2.i == 0){ } System.out.println(\u0026#34;end---\u0026#34;); } public void add(){ i = 100; } } 如果对i加上volatile关键字，那么内存可见，就不会出现死循环了\npublic class Test2 { private volatile int i = 0; public static void main(String[] args) { Test2 test2 = new Test2(); Thread thread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } test2.add(); System.out.println(test2.i); } }); thread.start(); while(test2.i == 0){ } System.out.println(\u0026#34;end---\u0026#34;); } public void add(){ i = 100; } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/6e08a29a/16c47591/d6eeb5cd/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e要解决的问题： \n    \u003cdiv id=\"要解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a6%81%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e所谓类的单例设计模式，就是采取一定的方法保证在整个的软件系统中，对某个类只能存在一个对象实例。\u003c/p\u003e","title":"1、单例模式","type":"posts"},{"content":" 准备工作 # 项目采用idea开发，所以不需要插件，使用spring-boot+javaFx，实现MVC结构，整体项目结构类似于spirngboot项目\n1、安装JavaFx Secen Builder，这个工具可视化布局，好用，官网：(https://www.oracle.com/java/technologies/javafxscenebuilder-1x-archive-downloads.html\n2、找一个项目的icon格式图标、png格式的图标\n3、下载exe4j，官网：https://exe4j.apponic.com/，后面打包要用\n项目构建 # 1、依赖 # idea生成新的JavaFx项目，然后右键项目，选择maven项目，pom.xml中添加依赖\n\u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.1.0.RELEASE\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;de.roskenet\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;springboot-javafx-support\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.1.6\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.projectlombok\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;lombok\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.18.12\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;build\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0.1.RELEASE\u0026lt;/version\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;repackage\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven.plugins\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-compiler-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;source\u0026gt;8\u0026lt;/source\u0026gt; \u0026lt;target\u0026gt;8\u0026lt;/target\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/build\u0026gt; 2、主界面fxml # fx:controller中为视图控制类全类名\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;?import java.lang.*?\u0026gt; \u0026lt;?import javafx.geometry.*?\u0026gt; \u0026lt;?import javafx.scene.control.*?\u0026gt; \u0026lt;?import javafx.scene.layout.*?\u0026gt; \u0026lt;AnchorPane prefHeight=\u0026#34;900.0\u0026#34; prefWidth=\u0026#34;1200.0\u0026#34; xmlns=\u0026#34;http://javafx.com/javafx/8\u0026#34; xmlns:fx=\u0026#34;http://javafx.com/fxml/1\u0026#34; fx:controller=\u0026#34;org.yhgh.memory.controller.MainController\u0026#34;\u0026gt; \u0026lt;/AnchorPane\u0026gt; 3、视图控制类 # 视图控制类，需要使用@FXMLView注解，并且继承AbstractFxmlView\n//这里的为该视图fxml的路径 @FXMLView(\u0026#34;/main.fxml\u0026#34;) public class MainController extends AbstractFxmlView { } 4、自定义开场动画 # 最简单的方法，更换图片，然后完事\npublic class SplashView extends SplashScreen { //开始界面图片，可以自定义 //@Override //public String getImagePath() { // return \u0026#34;/image/logo.png\u0026#34;; //} //是否显示开始界面 @Override public boolean visible() { return super.visible(); } //这个方法可以自定义界面 @Override public Parent getParent() { return super.getParent(); } } 4、使用托盘 # 使用系统托盘，点击关闭按钮就不会关闭程序了\n@Component public class SysTray { public static SystemTray getSysTray(Stage primaryStage) throws Exception { //点击关闭按钮，不退出fx程序 Platform.setImplicitExit(false); //构建系统托盘图标 BufferedImage image = ImageIO.read(Objects.requireNonNull(Main.class.getClassLoader().getResourceAsStream(\u0026#34;image/logo.png\u0026#34;))); TrayIcon trayIcon = new TrayIcon(image, \u0026#34;Memory\u0026#34;); trayIcon.setImageAutoSize(true); //设置弹出菜单 PopupMenu popup = new PopupMenu(); //菜单项(打开App) MenuItem showItem = new MenuItem(\u0026#34;OpenApp\u0026#34;); //菜单项(退出) MenuItem exitItem = new MenuItem(\u0026#34;Close\u0026#34;); popup.add(showItem); popup.add(exitItem); trayIcon.setPopupMenu(popup); //获取系统托盘 SystemTray tray = SystemTray.getSystemTray(); //添加托盘鼠标点击事件 trayIcon.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { //双击左键,判断是否打开应用 if (e.getButton() == MouseEvent.BUTTON1 \u0026amp;\u0026amp; e.getClickCount() == 2) { Platform.runLater(() -\u0026gt; { if (primaryStage.isIconified()) { primaryStage.setIconified(false); } if (!primaryStage.isShowing()) { primaryStage.show(); } primaryStage.toFront(); }); } } }); //添加托盘菜单事件 showItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Platform.runLater(() -\u0026gt; { if (primaryStage.isIconified()) { primaryStage.setIconified(false); } if (!primaryStage.isShowing()) { primaryStage.show(); } primaryStage.toFront(); }); } }); exitItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Platform.setImplicitExit(true); tray.remove(trayIcon); Platform.runLater(primaryStage::close); } }); //添加托盘图标 tray.add(trayIcon); return tray; } } 5、启动类 # 启动类需要使用@SpringBootApplication注解，然后继承AbstractJavaFxApplicationSupport\n@SpringBootApplication public class Main extends AbstractJavaFxApplicationSupport { // 这个方法是项目启动后的钩子函数，可以在里面写逻辑，例如系统托盘 @Override public void start(Stage stage) throws Exception { super.start(stage); //开启托盘 SysTray.getSysTray(stage); } public static void main(String[] args) { // 参数分别是Application的主类，主界面的UI类，闪屏对象，args // 不想要自定义闪屏的可以调用另一个不带闪屏对象的launch方法 launch(Main.class, MainController.class,new SplashView(), args); } } 6、配置文件 # javafx: title: demo appicons: /logo.png 7、运行 # 直接运行主程序就可以了\n8、打包 # 这里我使用的是exe4j打包\n准备环境 # a、先运行maven package，将项目打包为jar\nb、测试jar包是否运行正常\nc、在非中文路径中，创建一个文件夹，然后在这个文件夹中，创建lib文件夹，将打好的jar放里面；创建icon文件夹，将项目的图标放里面；将自己的jre复制一个到文件夹中\n使用exe4j打包 # a、打开安装好的exe4j，在右下角Change Listens，添加授权，name和company可以随便，key：L-g782dn2d-1f1yqxx1rv1sqd\nb、然后剩下的一路下一步，打包完，就可以打开目录里面的exe了\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/d9073060/8ed1dfd5/d18eabbd/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e准备工作 \n    \u003cdiv id=\"准备工作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%87%86%e5%a4%87%e5%b7%a5%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e项目采用idea开发，所以不需要插件，使用\u003ccode\u003espring-boot+javaFx\u003c/code\u003e，实现MVC结构，整体项目结构类似于spirngboot项目\u003c/p\u003e","title":"1、基于springboot项目构建","type":"posts"},{"content":" 微服务简介 # 倡导者：Martin Fowler，文章网址：http://martinfowler.com/articles/microservices.html\n简而言之，微服务体系结构风格是一种将单个应用程序开发为一组小服务的方法，每个小服务都在自己的进程中运行，并使用轻量级机制(通常是HTTP资源API)进行通信。这些服务是围绕业务功能构建的，可以通过完全自动化的部署机制独立部署。这些服务的集中管理是最低限度的，可以用不同的编程语言编写，并使用不同的数据存储技术。\nweb系统的架构演变 # 单体应用模式 -\u0026gt; 垂直应用架构模式 -\u0026gt; SOA（面向服务架构模式） -\u0026gt; 微服务架构\n1、单体应用模式 # 优点： 系统开发结构简单，迅速 部署，上线简单 成本低：一台机器安装：tomcat + mysql + redis + …… 缺点： 功能的耦合度高，扩展性差 不稳定，功能之间可能相互影响，一个地方可能导致整个项目都会崩溃 做集群，做分布式非常的不方便 可能存在资源浪费 2、垂直应用架构模式 # 特点：将上述的单体应用，按照业务功能进行分离，分离一个又一个的相互独立的子系统\n例如：百度拆分为：搜索，地图，贴吧，新闻，翻译，云盘……\n优点\n系统之间的耦合度低，相互之间互不干扰 系统分离关注，从开发到测试到上线，甚至到运维集群实现 资源可以得到更加合理的分配 缺点\n重复代码过多 3、面向服务架构模式（SOA） # 由于上述的垂直应用架构， 重复代码太多，一些架构师就想了一套办法将重复应用和单独应用进行分离，分离之后，抽离出一个层：服务层\n产品：阿里巴巴的Dubbo框架\n中小型公司，由于没有架构师，无法抽离服务层，如同扯淡\n优点：\n系统的扩展性非常好，系统之间的耦合度相对而言比较低 开发到测试到上线，都可以进行分离关注 资源可以合理的利用 缺点：\n只有业务非常复杂的系统，并且对SOA非常熟悉的团队才可以使用（大厂的产品） 如何抽象服务层是整体架构体系，最为复杂的地方 分布式事务控制，服务资源的成本，开发维护的成本都会急剧上升 为了中小厂也可以玩一玩分布式，所有微服务的概念就被提出来了 4、微服务架构模式 # 在垂直应用架构的基础上，演变而来的一种架构，区别：现在的微服务之间可以相互通讯\n而且在相互调用时，采用最基本的HTTP通讯协议\n每个微服务程序都是一个独立的个体，个体中完整的包含了：表现层，业务层，持久层\n优点：\n通过业务功能将系统进行拆分，拆分之后耦合度降低，特别满足：“高内聚，低耦合” 更加便于程序员采用分离关注的思想，完成对系统的开发，测试，上线 整个系统来讲，功能的扩展非常的强，满足：“开闭原则” 缺点：\n上线，部署，维护较为麻烦，运维的工作量将大大提升 如果微服务拆分“粒度”不合适，将大大提升系统的复杂度 单体应用服务和微服务的区别 # 单体 微服务 所有业务模块都集中在一个应用中 根据业务拆分出不同的微服务应用 修改任何一处代码，都要重新部署整个应用 修改某个微服务的代码，只需要重新部署该微服务应用即可 无法针对于高热业务，单独做集群部署 如果某个微服务的并发量特别高，可以针对于该微服务做集群 开发团队人员特别多，代码冲突也特别多 每个微服务都有独立的团队的维护，代码冲突也仅限于当前微服务的团队中 微服务的适用场景 # 微服务的拆分，拆分“粒度”可大可小，大可以以子系统为单位来进行拆分，小可以以某一个功能块的业务功能来拆分\n那什么样的系统，适合于进行微服务的拆分？能不能做成微服务，取决于四个要素：\n小：能不能拆，拆分之后微服务的体积是否比较小 独：能够独立的开发，测试，部署和运行 轻：使用轻量级的HTTP通信机制和架构 松：微服务之间是相对而言是比较松耦合的 微服务设计原则 # 单一职责原则 意思是每个微服务只需要实现自己的业务逻辑就可以了，比如订单微服务，它只需要处理订单的业务逻辑就可以了，其它的不必考虑 服务自治原则 意思是每个微服务从开发、测试、运维等都是独立的，包括存储的数据库也都是独立的，自己就有一套完整的流程，我们完全可以把它当成一个独立的项目来对待，不必依赖于其它模块 轻量级通信原则 首先是通信的语言非常的轻量，该通信方式需要是跨语言、跨平台的，之所以要跨平台、跨语言就是为了让每个微服务都有足够的独立性，可以不受技术的钳制 接口明确原则 由于微服务之间可能存在着调用关系，为了尽量避免以后由于某个微服务的接口变化而导致其它微服务都做调整，在设计之初就要考虑到所有情况，让接口尽量做的更通用，更灵活，从而尽量避免其它模块也做调整 微服务的优势和缺陷 # 优势 # 易于开发和维护 由于微服务单个模块就相当于一个项目，开发这个模块我们就只需关心这个模块的逻辑即可，代码量和逻辑复杂度都会降低，从而易于开发和维护 启动较快 这是相对单个微服务来讲的，相比于启动单体架构的整个项目，启动某个模块的服务速度明显是要快很多的 局部修改容易部署 在开发中发现了一个问题，如果是单体架构的话，我们就需要重新发布并启动整个项目，非常耗时间；但是微服务则不同，哪个模块出现了bug我们只需要解决那个模块的bug就可以了，解决完bug之后，我们只需要重启这个模块的服务即可，部署相对简单，不必重启整个项目从而大大节约时间 技术栈不受限 比如订单微服务和电影微服务原来都是用java写的，现在我们想把电影微服务改成node.Js技术，这是完全可以的，而且由于所关注的只是电影的逻辑而已，因此技术更换的成本也就会少很多 按需伸缩 上面说了单体架构在想扩展某个模块的性能时不得不考虑到其它模块的性能会不会受影响，对于我们微服务来讲，完全不是问题，电影模块通过什么方式来提升性能不必考虑其它模块的情况 缺陷 # 运维要求较高 对于单体架构来讲，我们只需要维护好这一个项目就可以了，但是对于微服务架构来讲，由于项目是由多个微服务构成的，每个模块出现问题都会造成整个项目运行出现异常，想要知道是哪个模块造成的问题往往是不容易的，因为我们无法一步一步通过debug的方式来跟踪，这就对运维人员提出了很高的要求 分布式的复杂性 对于单体架构来讲，我们可以不使用分布式，但是对于微服务架构来说，分布式几乎是必会用的技术，由于分布式本身的复杂性，导致微服务架构也变得复杂起来 接口调整成本高 比如，用户微服务是要被订单微服务和电影微服务所调用的，一旦用户微服务的接口发生大的变动，那么所有依赖它的微服务都要做相应的调整，由于微服务可能非常多，那么调整接口所造成的成本将会明显提高 重复劳动 对于单体架构来讲，如果某段业务被多个模块所共同使用，我们便可以抽象成一个工具类，被所有模块直接调用，但是微服务却无法这样做，因为这个微服务的工具类是不能被其它微服务所直接调用的，从而我们便不得不在每个微服务上都建这么一个工具类，从而导致代码的重复 ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/fd17f7dc/024f1da7/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e微服务简介 \n    \u003cdiv id=\"微服务简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%be%ae%e6%9c%8d%e5%8a%a1%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e倡导者：Martin Fowler，文章网址：http://martinfowler.com/articles/microservices.html\u003c/p\u003e","title":"1、微服务","type":"posts"},{"content":" 什么是数据结构 # 数据结构（Data Structure是一门和计算机硬件与软件都密切相关的学科，它的研究重点是在计算机的程序设计领域中探讨如何在计算机中组织和存储数据并进行高效率的运用，涉及的内容包含：数据的逻辑关系、数据的存储结构、排序算法（Algorithm）、查找（或搜索）等。\n数据结构与算法的理解 # 程序能否快速而高效地完成预定的任务，取决于是否选对了数据结构，而程序是否能清楚而正确地把问题解决，则取决于算法。\n所以大家认为：“Algorithms + Data Structures = Programs”（出自：Pascal之父Nicklaus Wirth）\n总结：算法是为了解决实际问题而设计的，数据结构是算法需要处理的问题载体。\n数据结构可视化网站 # https://www.cs.usfca.edu/~galles/visualization/Algorithms.html\n数据结构学习网站 # http://data.biancheng.net/view/61.html\nhttps://www.javatpoint.com/b-plus-tree\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/c343b13e/6eb2df4f/2cc3f3c2/f8c96125/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是数据结构 \n    \u003cdiv id=\"什么是数据结构\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e数据结构（Data Structure是一门和计算机硬件与软件都密切相关的学科，它的研究重点是在计算机的程序设计领域中探讨如何在计算机中组织和存储数据并进行高效率的运用，涉及的内容包含：数据的逻辑关系、数据的存储结构、排序算法（Algorithm）、查找（或搜索）等。\u003c/p\u003e","title":"1、数据结构","type":"posts"},{"content":" 枚举，顾名思义：就是一个一个列举出来\n当需要定义一组常量时，强烈建议使用枚举类\n枚举类的实现 # JDK1.5之前需要自定义枚举类\nJDK1.5新增的 enum 关键字用于定义枚举类\n若枚举只有一个对象, 则可以作为一种单例模式的实现方式。\n原理 # 1、实际上在使用关键字enum创建枚举类型并编译后，编译器会为我们生成一个相关的类，这个类继承了java.lang.Enum，也就是说通过关键字enum创建枚举类型在编译后事实上也是一个class类型而且该类继承自java.lang.Enum类。\n2、我们创建枚举项的时候，就是在创建所在枚举类的对象，比如：\npublic enum Color { RED,BLUE } Color是一个枚举类型，有一个枚举项为RED，相当于RED = new Color();\n3、枚举类的构造函数默认是private的，可以不写权限修饰符，编译器会自动加上，但是如果写只能写private\nEnum类常见方法 # int compareTo(E o)：比较此枚举于指定枚举对象的顺序 Enum[] values()：以数组形式返回枚举类型的所有枚举常量 boolean equals(Object other)：判断两个枚举类是否相同 String name()：返回枚举常量的名称 int ordinal()：返回枚举常量的序数，起始为0 String toString()：返回枚举常量的名称 static \u0026lt;T extends Enum\u0026lt;T\u0026gt;\u0026gt; T valueOf(Class\u0026lt;T\u0026gt; enumType,String name)：返回指定名称和枚举类型的枚举常量 static \u0026lt;T extends Enum\u0026lt;T\u0026gt;\u0026gt; T valueOf(String name)：返回指定名称的枚举常量 定义枚举类 # 使用普通类实现枚举 # //自定义枚举类 class Season{ //1.声明Season对象的属性:private final修饰 private final String seasonName; private final String seasonDesc; //2.私化类的构造器,并给对象属性赋值 private Season(String seasonName,String seasonDesc){ this.seasonName = seasonName; this.seasonDesc = seasonDesc; } //3.提供当前枚举类的多个对象：public static final的 public static final Season SPRING = new Season(\u0026#34;春天\u0026#34;,\u0026#34;春暖花开\u0026#34;); public static final Season SUMMER = new Season(\u0026#34;夏天\u0026#34;,\u0026#34;夏日炎炎\u0026#34;); public static final Season AUTUMN = new Season(\u0026#34;秋天\u0026#34;,\u0026#34;秋高气爽\u0026#34;); public static final Season WINTER = new Season(\u0026#34;冬天\u0026#34;,\u0026#34;冰天雪地\u0026#34;); //4.其他诉求：获取枚举类对象的属性 public String getSeasonName() { return seasonName; } public String getSeasonDesc() { return seasonDesc; } //4.其他诉求1：提供toString() @Override public String toString() { return \u0026#34;Season{\u0026#34; + \u0026#34;seasonName=\u0026#39;\u0026#34; + seasonName + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, seasonDesc=\u0026#39;\u0026#34; + seasonDesc + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#39;}\u0026#39;; } } 使用enum关键字枚举类 # //枚举类型中除了枚举项之外，也能出现构造方法、属性、成员方法，但是构造方法必须私有，并且所有除枚举项之外的其他成员必须写在枚举项之后，用分号隔开。 enum Season1 { //1.提供当前枚举类的对象，多个对象之间用\u0026#34;,\u0026#34;隔开，末尾对象\u0026#34;;\u0026#34;结束 SPRING(\u0026#34;春天\u0026#34;,\u0026#34;春暖花开\u0026#34;), SUMMER(\u0026#34;夏天\u0026#34;,\u0026#34;夏日炎炎\u0026#34;), AUTUMN(\u0026#34;秋天\u0026#34;,\u0026#34;秋高气爽\u0026#34;), WINTER(\u0026#34;冬天\u0026#34;,\u0026#34;冰天雪地\u0026#34;); //2.声明Season对象的属性:private final修饰 private String seasonName; private String seasonDesc; //3.私化类的构造器（构造器默认就是private）,并给对象属性赋值 Season1(String seasonName,String seasonDesc){ this.seasonName = seasonName; this.seasonDesc = seasonDesc; } //4.其他诉求：获取枚举类对象的属性 public String getSeasonName() { return seasonName; } public String getSeasonDesc() { return seasonDesc; } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/fbeea4f4/fc890640/","section":"文章","summary":"\u003cblockquote\u003e\n\u003cp\u003e枚举，顾名思义：就是一个一个列举出来\u003c/p\u003e\n\u003cp\u003e当需要定义一组常量时，强烈建议使用枚举类\u003c/p\u003e","title":"1、枚举类","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/1b37041b/339aff44/de00a60c/","section":"文章","summary":"","title":"1、爬虫","type":"posts"},{"content":" 什么是计算机网络 # 计算机网络是通信技术与计算机技术紧密结合的产物，是一种特殊的通信网络\n计算机网络是一个互联（互联互通）、**自治（无主从关系）**的计算机集合\n通信系统模型 # Internet # 全球最大的互联网络，网络之网络，ISP（Internet Service Provider）因特网服务提供商，是互联的计算设备的集合，组成：\n计算设备：主机（Host）、端设备（End System）\n通信链路：光纤、铜缆、无线电\n分组交换：路由器（Routers）、交换机（Switches）\n网络协议 # 硬件是计算机网络的基础，计算机网络的数据交换必须遵守实现约定好的协议\n网络协议（network protocol），简称协议，是未进行网络中的数据交换而建立的规定、标准和约定，协议规定了通信实体之间交换信息的格式、意义、顺序以及针对收到信息或发生的事件采取的动作\n协议规范了网络中所有信息的发送和接收过程，常见的协议：e.g.、TCP、IP、HTTP等\nInternet协议标准： RFC：Request for Comments，文档，查看权威协议描述 IETF：互联网工程任务组 协议的三要素 # 语法（Syntax）：数据和控制信息的结构和格式\n语义（Semantics）：需要发出何种控制信息，完成何种动作以及做出何种响应，差错控制\n时序（Timing）：事件顺序，速度匹配\n计算机网络结构 # 网络边缘： 主机 运行各种网络应用 网络应用 客户/服务器（client/server）应用模型：用户发送请求，接收服务器响应，如Web、FTP 对等（peer-peer，P2P）应用模型：无（或不仅依赖）专用服务器，通信在对等的实体之间直接进行，如BT、Skype、QQ 接入网络、物理介质： 有线或无线通信链路 带宽（bandwidth）（bps） 共享/独占 网络核心（核心网络）： 互联的路由器（或分组转发设备） 数据交换，主要解决每两个主机连接的，n平方链路问题，通过动态转接、同台分配传输资源 电路交换，在通信之前要在通信双方之间建立一条被双方独占的物理通路，用于数据的传输 报文交换，以报文为数据交换的单位，报文携带有目标地址、源地址等信息，在交换结点采用存储转发的传输方式 分组交换， 基于报文交换，将报文划分为更小的数据单位：报文分组（也称为段、包、分组），分组交换仍采用存储转发传输方式，但将一个长报文先分割为若干个较短的分组，然后把这些分组（携带源、目的地址和编号信息）逐个地发送出去，当一次数据传输的所有分组都到达接收方时,接收方再将所有分组重组为原来的数据 计算机网络性能 # 指标 # 速率 # 即数据率（data rate）或称数据传输速率或比特率（bit rate），单位时间（秒）传输信息（比特）量，单位：b/s（bps）、kb/s（10^3^b/s）、Mb/s（10^6^b/s）、Gb/s（10^9^b/s）\n带宽 # 带宽（bandwidth）原本是指信号具有的频带宽度，即最高频率和最低频率之差，单位（HZ）\n网络带宽是指数字信道所能传输的最高数据率，单位b/s（bps）\n延迟/时延 # 延迟=结点处理延迟（路由器检查数据、确定输出链路，通常不会超过毫秒级）+排队延迟（分组排队等待从链路传输，取决于路由器拥挤程度）+传输延迟（分组传输到链路，取决于分组长度bits和链路带宽bps）+传播延迟（在链路上传输，取决于物理链路长度和信号传播速度）】\n流量强度 # 流量强度（traffic intensity） = La/R R：链路带宽（bps） L：分组长度（bits） a：平局分组到达速率 La/R ~ 0：平均排队延迟很小\nLa/R -\u0026gt; 1：平均排队延迟很大\nLa/R \u0026gt; 1：超出服务能力，平均排队延迟无限大\n时延带宽积 # 时延带宽积 = 传播时延 * 带宽，又称为以比特为单位的链路长度\n丢包率 # 分组转发会发生丢包，是因为分组在路由器缓存中排队，如果分组到达速率超出输出链路容量，那么该分组会被丢弃（丢包loss）\n丢包率 = 丢包数 / 已发分组总数\n吞吐量/率 # 表示在发送端到接收端之间传送数据速率（b/s）\n计算机网络体系结构 # 计算机网络体系结构是从功能上描述计算机网络结构，而不是硬件，计算机网络中使用分层结构（network architecture网络体系结构）来描述网络，是计算机网络各层以及其协议的集合，是抽象的\n为什么使用分层结构 结构清晰，由利于识别复杂系统的部件及其关系 模块化分层有利于系统更新、维护 有利于标准化 OSI参考模型 # 开放系统互连（OSI）参考模型是由国际标准化组织（ISO）与1984年提出的分层网络体系结构模型，目的是支持异构网络系统的互联互通，一共是七层\n数据流向 # 上面四层也是端到端层\n传输中几乎每一层都会添加头尾信息，即数据封装，增加控制信息（PDU），主要包括：地址（Address）、差错检测编码（Error-detecting code），协议控制（Protocol control）\n各层功能 # 物理层 负责实现单个bit的传输，解决比特编码、数据率、比特同步、传输模式（单工、半双工、全双工）问题 接口特性：机械特性、电器特性、功能特性、规程特性 数据链路层 负责结点-结点（node-to-node）的数据传输，解决组帧、物理寻址、流量控制、差错控制、接入（访问）控制问题 网络层 负责源主机到目的主机数据分组（packet）交付，解决逻辑寻址、路由、分组转发问题 传输层 负责源-目的（端-端）（进程间）完整报文的传输，解决分段与重组、SAP寻址（端口号）、连接控制、流量控制、差错控制问题 会话层 负责对话控制（建立、维护），解决同步问题 表示层 负责两个系统之间交换信息的语法与语义，解决数据表示转化、加密解密、压缩解压缩问题 应用层 负责支持用户通过用户代理（如浏览器）或网络接口使用网络（服务），常见的应用层服务：文件传输（FTP）、电子邮件（SMTP）、Web（HTTP） TCP/IP参考模型 # 数据流向 # ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/c343b13e/45c36453/bee3a0f2/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是计算机网络 \n    \u003cdiv id=\"什么是计算机网络\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af%e8%ae%a1%e7%ae%97%e6%9c%ba%e7%bd%91%e7%bb%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e计算机网络是通信技术与计算机技术紧密结合的产物，是一种特殊的通信网络\u003c/p\u003e","title":"1、计算机网络概述","type":"posts"},{"content":"设计模式是在大量的实践中总结和理论化之后优的代码结构、编程风格、以及解决问题的思考方式。\n常用设计模式-23种经典的设计模式 GOF # 创建型模式，共5种：工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。\n结构型模式，共7种：适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式。\n行为型模式，共11种：策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。\nMVC设计模式\nModel（模型）- 模型代表一个存取数据的对象或JavaPOJO，他可以带有逻辑，在数据变化时更新控制器\nView（视图）- 视图代表模型包含的数据的可视化\nController（控制器）- 控制器作用于模型和视图上，他可以控制数据流向模型对象，并在数据变化时，更新视图，它使数据与模型分离开\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/6e08a29a/9beaf145/","section":"文章","summary":"\u003cp\u003e\u003cstrong\u003e设计模式\u003c/strong\u003e是在大量的实践中总结和理论化之后优的代码结构、编程风格、以及解决问题的思考方式。\u003c/p\u003e","title":"1、设计模式概述","type":"posts"},{"content":"viper 是一个配置解决方案，拥有丰富的特性：\n支持 JSON、TOML、YAML、HCL、envfile、Java properties 等多种格式的配置文件； 可以设置监听配置文件的修改，修改时自动加载新的配置； 从环境变量、命令行选项和io.Reader中读取配置； 从远程配置系统中读取和监听修改，如 etcd/Consul； 代码逻辑中显示设置键值。 安装 # go get -u github.com/spf13/viper 读取配置 # 使用默认实例读取配置文件\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/spf13/viper\u0026#34; ) func main() { viper.SetConfigName(\u0026#34;config\u0026#34;) // 设置配置文件名，不包括扩展名 viper.AddConfigPath(\u0026#34;/etc/appname/\u0026#34;) // 添加配置文件搜索路径 viper.AddConfigPath(\u0026#34;$HOME/.appname\u0026#34;) // 可以添加多个搜索路径 err := viper.ReadInConfig() // 读取配置文件 if err != nil { panic(fmt.Errorf(\u0026#34;Fatal error config file: %s \\n\u0026#34;, err)) } // 使用 viper.Get 或其他类型特定方法获取配置 fmt.Println(viper.GetString(\u0026#34;somekey\u0026#34;)) } 也可以构造新的实例加载配置文件\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/spf13/viper\u0026#34; ) func main() { config := viper.New() config.SetConfigName(\u0026#34;config\u0026#34;) // 设置配置文件名，不包括扩展名 config.AddConfigPath(\u0026#34;/etc/appname/\u0026#34;) // 添加配置文件搜索路径 config.AddConfigPath(\u0026#34;$HOME/.appname\u0026#34;) // 可以添加多个搜索路径 err := config.ReadInConfig() // 读取配置文件 if err != nil { panic(fmt.Errorf(\u0026#34;Fatal error config file: %s \\n\u0026#34;, err)) } // 使用 viper.Get 或其他类型特定方法获取配置 fmt.Println(config.GetString(\u0026#34;somekey\u0026#34;)) } 读取配置流 # 除了读取文件，Viper 还可以从任何实现了 io.Reader 接口的对象中读取配置。\nviper.SetConfigType(\u0026#34;yaml\u0026#34;) yamlExample := []byte(` name: myapp `) viper.ReadConfig(bytes.NewBuffer(yamlExample)) fmt.Println(viper.Get(\u0026#34;name\u0026#34;)) 设置参数默认值 # viper.SetDefault(\u0026#34;ContentDir\u0026#34;, \u0026#34;content\u0026#34;) viper.SetDefault(\u0026#34;LayoutDir\u0026#34;, \u0026#34;layouts\u0026#34;) 监听配置文件变化 # Viper 支持监听配置文件的变化，并在文件更改时触发回调函数。\nviper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { fmt.Println(\u0026#34;Config file changed:\u0026#34;, e.Name) }) 配置参数别名 # viper.RegisterAlias(\u0026#34;base\u0026#34;, \u0026#34;alias\u0026#34;) viper.Set(\u0026#34;base\u0026#34;, true) fmt.Println(viper.GetBool(\u0026#34;base\u0026#34;)) // 输出: true fmt.Println(viper.GetBool(\u0026#34;alias\u0026#34;)) // 输出: true 解序列化 # 可以选择将全部或特定值解序列化到一个结构体、映射等中。\n有两种方法可以实现这一点：\nUnmarshal(rawVal any) : error UnmarshalKey(key string, rawVal any) : error type config struct { Port int Name string PathMap string `mapstructure:\u0026#34;path_map\u0026#34;` } var C config err := viper.Unmarshal(\u0026amp;C) if err != nil { t.Fatalf(\u0026#34;unable to decode into struct, %v\u0026#34;, err) } 写入配置文件 # viper.WriteConfig() // 将当前配置写入“viper.AddConfigPath()”和“viper.SetConfigName”设置的预定义路径 viper.SafeWriteConfig() viper.WriteConfigAs(\u0026#34;/path/to/my/.config\u0026#34;) viper.SafeWriteConfigAs(\u0026#34;/path/to/my/.config\u0026#34;) // 因为该配置文件写入过，所以会报错 viper.SafeWriteConfigAs(\u0026#34;/path/to/my/.other_config\u0026#34;) 常见问题 # 配置属性含下划线无法读取 # 可以添加 Tag mapstructure 以读取带下划线的参数\ntype MySQL struct { DSN string `yaml:\u0026#34;dsn\u0026#34; mapstructure:\u0026#34;dsn\u0026#34;` MaxIdleConns int `yaml:\u0026#34;max_idle_conns\u0026#34; mapstructure:\u0026#34;max_idle_conns\u0026#34;` MaxOpenConns int `yaml:\u0026#34;max_open_conns\u0026#34; mapstructure:\u0026#34;max_open_conns\u0026#34;` ConnMaxLifetime int `yaml:\u0026#34;conn_max_lifetime\u0026#34; mapstructure:\u0026#34;conn_max_lifetime\u0026#34;` } ","date":"2025-07-23","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/29ede47b/edb361d4/","section":"文章","summary":"\u003cp\u003eviper 是一个配置解决方案，拥有丰富的特性：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e支持 \u003ccode\u003eJSON\u003c/code\u003e、\u003ccode\u003eTOML\u003c/code\u003e、\u003ccode\u003eYAML\u003c/code\u003e、\u003ccode\u003eHCL\u003c/code\u003e、\u003ccode\u003eenvfile\u003c/code\u003e、\u003ccode\u003eJava properties\u003c/code\u003e 等多种格式的配置文件；\u003c/li\u003e\n\u003cli\u003e可以设置监听配置文件的修改，修改时自动加载新的配置；\u003c/li\u003e\n\u003cli\u003e从环境变量、命令行选项和\u003ccode\u003eio.Reader\u003c/code\u003e中读取配置；\u003c/li\u003e\n\u003cli\u003e从远程配置系统中读取和监听修改，如 \u003ccode\u003eetcd/Consul\u003c/code\u003e；\u003c/li\u003e\n\u003cli\u003e代码逻辑中显示设置键值。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e安装 \n    \u003cdiv id=\"安装\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%89%e8%a3%85\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ego get -u github.com/spf13/viper\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e读取配置 \n    \u003cdiv id=\"读取配置\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%af%bb%e5%8f%96%e9%85%8d%e7%bd%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e使用默认实例读取配置文件\u003c/p\u003e","title":"2、viper","type":"posts"},{"content":" ORM概念介绍 # 虽然Go的原生SQL包database/sql提供了最大的灵活性和控制力，但在实际项目中，我们经常需要一种更便捷、更安全、更贴近业务逻辑的方式来操作数据库。这就是ORM（对象关系映射）技术的用武之地。\n什么是ORM # ORM（Object-Relational Mapping，对象关系映射）是一种编程技术，它建立了编程语言中的对象与关系型数据库中表的映射关系，使开发者能够使用面向对象的方式来操作数据库，而无需直接编写SQL语句。\n数据表映射为结构体 表中的字段映射为结构体的字段 表中的记录映射为结构体的实例 SQL操作映射为对象上的方法调用 ORM的优势 # 生产力提升：减少重复的CRUD（创建、读取、更新、删除）操作代码 面向对象的编程方式：使用对象和方法代替SQL语句 数据库抽象：减少与特定数据库系统的耦合 类型安全：编译时检查替代运行时SQL字符串拼接错误 安全性：减少SQL注入风险 自动处理关联关系：方便处理一对一、一对多、多对多等关系 内置迁移工具：简化数据库结构变更管理 ORM的劣势 # 性能开销：在简单查询上可能比原生SQL慢 学习曲线：需要学习ORM框架的特定API 复杂查询限制：有些复杂SQL查询可能难以用ORM表达 抽象泄漏：在某些情况下，隐藏的数据库细节可能会影响应用行为 过度使用可能导致非最优SQL：自动生成的SQL可能不如手写的优化 Go语言中的主要ORM库 # GORM：目前最流行的Go语言ORM库，功能全面且活跃维护 XORM：另一个成熟的ORM库，具有良好的性能 Ent：Facebook开发的实体框架，使用代码生成的方式 SQLBoiler：以生成代码为主的ORM，专注于类型安全 SQLx：介于原生SQL和ORM之间的库，提供便捷的查询构建器 GORM # GORM是Go语言中最受欢迎的ORM库，提供了友好的API，并支持各种数据库，包括MySQL、PostgreSQL、SQLite和SQL Server等。\n官网：https://gorm.io/\n安装 # **注意：**gorm 使用的驱动和database/sql包的驱动不一样\ngo get -u gorm.io/gorm go get -u gorm.io/driver/mysql # MySQL驱动 # 或其他数据库驱动 go get -u gorm.io/driver/postgres # PostgreSQL驱动 go get -u gorm.io/driver/sqlite # SQLite驱动 go get -u gorm.io/driver/sqlserver # SQL Server驱动 连接数据库 # GORM使用的数据库连接池由底层的*sql.DB实例管理，我们可以通过db.DB()方法获取这个实例并设置连接池参数。\npackage database import ( \u0026#34;gorm.io/driver/mysql\u0026#34; \u0026#34;gorm.io/gorm\u0026#34; \u0026#34;gorm.io/gorm/logger\u0026#34; \u0026#34;log\u0026#34; \u0026#34;time\u0026#34; ) type MySQLConfig struct { DSN string MaxIdleConns int MaxOpenConns int ConnMaxLifetime int } // InitDB 创建数据库连接 func InitDB(config *MySQLConfig) *gorm.DB { db, err := gorm.Open(mysql.Open(config.DSN), \u0026amp;gorm.Config{ Logger: logger.Default.LogMode(logger.Info), // 设置日志级别 }) if err != nil { log.Fatalf(\u0026#34;数据库连接失败，%v\u0026#34;, err) return nil } // 获取底层SQL连接以设置连接池参数 sqlDB, err := db.DB() if err != nil { log.Fatalf(\u0026#34;无法获取数据库连接: %v\u0026#34;, err) } // 设置连接池参数 sqlDB.SetMaxIdleConns(config.MaxIdleConns) // 设置空闲连接池中连接的最大数量 sqlDB.SetMaxOpenConns(config.MaxOpenConns) // 设置打开数据库连接的最大数量 sqlDB.SetConnMaxLifetime(time.Duration(config.ConnMaxLifetime) * time.Second) // 设置了连接可复用的最大时间 log.Println(\u0026#34;成功连接到数据库！\u0026#34;) return db } package main import \u0026#34;ygang.top/zhiyingkuaichuang/internal/database\u0026#34; func main() { db := database.InitDB(\u0026amp;database.MySQLConfig{ DSN: \u0026#34;root:123456@tcp(localhost:3306)/mcp_demo?charset=utf8\u0026amp;loc=Local\u0026#34;, ConnMaxLifetime: 60, MaxOpenConns: 10, MaxIdleConns: 5, }) } 定义模型 # 在GORM中，模型是与数据库表映射的结构体。我们通过定义Go结构体来创建数据库模型：\ntype User struct { ID uint `gorm:\u0026#34;primarykey\u0026#34;` CreatedAt time.Time `gorm:\u0026#34;autoCreateTime\u0026#34;` UpdatedAt time.Time `gorm:\u0026#34;autoUpdateTime\u0026#34;` DeletedAt gorm.DeletedAt `gorm:\u0026#34;index\u0026#34;` Name string `gorm:\u0026#34;size:255;not null\u0026#34;` Email string `gorm:\u0026#34;size:255;uniqueIndex;not null\u0026#34;` Age uint8 `gorm:\u0026#34;default:18\u0026#34;` Address string `gorm:\u0026#34;type:varchar(500)\u0026#34;` IsActive bool `gorm:\u0026#34;default:true\u0026#34;` } 约定 # 主键：GORM 使用一个名为ID 的字段作为每个模型的默认主键。 表名：默认情况下，GORM 将结构体名称SnakeCase转换为 snake_case 并为表名加上复数形式。 例如，一个 User 结构体在数据库中的表名变为 users 。 列名：GORM 自动将结构体字段名称SnakeCase转换为 snake_case 作为数据库中的列名。 时间戳字段：GORM使用字段 CreatedAt 和 UpdatedAt 来自动跟踪记录的创建和更新时间，后面的 autoCreateTime 和 autoUpdateTime必须要带上。 软删除：DeletedAt用于软删除（将记录标记为已删除，而实际上并未从数据库中删除）。 Struct Tag # 在声明模型时，标签是可选的，GORM支持以下标签：标签不区分大小写，但首选camelCase。如果使用多个标记，则应使用分号;分隔。对解析器具有特殊意义的字符可以用反斜杠\\转义，使其可以用作参数值。\n标签名 说明 column 指定 db 列名 type 列数据类型，推荐使用兼容性好的通用类型，例如：所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用，例如：not null、size, autoIncrement… 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时，它需要是完整的数据库数据类型，如：MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT serializer 指定将数据序列化或反序列化到数据库中的序列化器, 例如: serializer:json/gob/unixtime size 定义列数据类型的大小或长度，例如 size: 256 primaryKey 将列定义为主键 unique 将列定义为唯一键 default 定义列的默认值 precision 指定列的精度 scale 指定列大小 not null 指定列为 NOT NULL autoIncrement 指定列为自动增长 autoIncrementIncrement 自动步长，控制连续记录之间的间隔 embedded 嵌套字段 embeddedPrefix 嵌入字段的列名前缀 autoCreateTime 创建时追踪当前时间，对于 int 字段，它会追踪时间戳秒数，您可以使用 nano/milli 来追踪纳秒、毫秒时间戳，例如：autoCreateTime:nano autoUpdateTime 创建/更新时追踪当前时间，对于 int 字段，它会追踪时间戳秒数，您可以使用 nano/milli 来追踪纳秒、毫秒时间戳，例如：autoUpdateTime:milli index 根据参数创建索引，多个字段使用相同的名称则创建复合索引。 uniqueIndex 与 index 相同，但创建的是唯一索引 check 创建检查约束，例如 check:age \u0026gt; 13。 \u0026lt;- 设置字段写入的权限， \u0026lt;-:create 只创建、\u0026lt;-:update 只更新、\u0026lt;-:false 无写入权限、\u0026lt;- 创建和更新权限 -\u0026gt; 设置字段读的权限，-\u0026gt;:false 无读权限 - 忽略该字段，- 表示无读写，-:migration 表示无迁移权限，-:all 表示无读写迁移权限 comment 迁移时为字段添加注释 自定义表名 # 默认情况下，GORM会使用结构体名称的蛇形复数作为表名。例如，User结构体对应的表名是users。如果需要自定义表名，可以实现TableName方法：\n// TableName 指定用户模型的表名 func (User) TableName() string { return \u0026#34;my_users\u0026#34; } 或者全局设置表名：\ndb.Config.NamingStrategy = schema.NamingStrategy{ TablePrefix: \u0026#34;t_\u0026#34;, // 表名前缀 SingularTable: true, // 使用单数表名 } 自动迁移 # GORM提供了自动迁移功能，它会自动创建表、缺失的外键、约束、列和索引，并且会更改现有列的类型（如果大小、精度、是否为空等发生变化）。但它不会删除未使用的列，以保护数据。\n// 自动迁移 err := db.AutoMigrate(\u0026amp;User{}, \u0026amp;Product{}, \u0026amp;Order{}) if err != nil { log.Fatalf(\u0026#34;自动迁移失败: %v\u0026#34;, err) } 注意：自动迁移只应在开发环境中使用。在生产环境中，应该使用数据库迁移工具，如golang-migrate或Atlas。\n数据库操作 # 基本 CRUD 操作 # 新增 # // 创建一条记录 user := User{Name: \u0026#34;张三\u0026#34;, Email: \u0026#34;zhangsan@example.com\u0026#34;, Age: 25} result := db.Create(\u0026amp;user) if result.Error != nil { log.Fatalf(\u0026#34;创建用户失败: %v\u0026#34;, result.Error) } fmt.Printf(\u0026#34;创建用户成功，ID: %d, 受影响的行数: %d\\n\u0026#34;, user.ID, result.RowsAffected) // 批量创建 users := []User{ {Name: \u0026#34;李四\u0026#34;, Email: \u0026#34;lisi@example.com\u0026#34;, Age: 30}, {Name: \u0026#34;王五\u0026#34;, Email: \u0026#34;wangwu@example.com\u0026#34;, Age: 28}, } result = db.Create(\u0026amp;users) if result.Error != nil { log.Fatalf(\u0026#34;批量创建用户失败: %v\u0026#34;, result.Error) } fmt.Printf(\u0026#34;批量创建用户成功，受影响的行数: %d\\n\u0026#34;, result.RowsAffected) 查询 # // 查询单条记录 var firstUser User result := db.First(\u0026amp;firstUser) // 获取第一条记录（按主键排序） if result.Error != nil { log.Fatalf(\u0026#34;查询失败: %v\u0026#34;, result.Error) } fmt.Printf(\u0026#34;第一条用户记录: %+v\\n\u0026#34;, firstUser) // 根据主键查询 var user User result = db.First(\u0026amp;user, 10) // 查找ID为10的用户 // 或者 result = db.First(\u0026amp;user, \u0026#34;id = ?\u0026#34;, 10) // 查询多条记录 var users []User result = db.Find(\u0026amp;users) // 查询所有用户 fmt.Printf(\u0026#34;总共查询到 %d 个用户\\n\u0026#34;, len(users)) // 条件查询 var activeUsers []User result = db.Where(\u0026#34;age \u0026gt; ? AND is_active = ?\u0026#34;, 20, true).Find(\u0026amp;activeUsers) // 或者使用结构体 result = db.Where(\u0026amp;User{IsActive: true}).Where(\u0026#34;age \u0026gt; ?\u0026#34;, 20).Find(\u0026amp;activeUsers) // 或者使用map result = db.Where(map[string]interface{}{\u0026#34;is_active\u0026#34;: true}).Find(\u0026amp;activeUsers) // 排序 var orderedUsers []User db.Order(\u0026#34;age desc, name\u0026#34;).Limit(10).Find(\u0026amp;orderedUsers) // 分页 var pageUsers []User var totalCount int64 db.Model(\u0026amp;User{}).Count(\u0026amp;totalCount) db.Offset(10).Limit(10).Find(\u0026amp;pageUsers) // 第二页，每页10条 // 选择特定字段 var partialUsers []User db.Select(\u0026#34;name\u0026#34;, \u0026#34;email\u0026#34;).Find(\u0026amp;partialUsers) 更新 # // 保存所有字段 var user User db.First(\u0026amp;user, 1) user.Name = \u0026#34;新名字\u0026#34; user.Age = 32 db.Save(\u0026amp;user) // 更新所有字段 // 更新单个字段 db.Model(\u0026amp;user).Update(\u0026#34;Name\u0026#34;, \u0026#34;更新的名字\u0026#34;) // 更新多个字段 db.Model(\u0026amp;user).Updates(User{Name: \u0026#34;新名字\u0026#34;, Age: 35}) // 只会更新非零值字段 // 或者使用map更新任意值，包括零值 db.Model(\u0026amp;user).Updates(map[string]interface{}{\u0026#34;name\u0026#34;: \u0026#34;新名字\u0026#34;, \u0026#34;age\u0026#34;: 0, \u0026#34;is_active\u0026#34;: false}) // 批量更新 db.Model(\u0026amp;User{}).Where(\u0026#34;age \u0026gt; ?\u0026#34;, 30).Update(\u0026#34;is_active\u0026#34;, false) 删除 # // 删除记录 var user User db.First(\u0026amp;user, 1) // 删除 id 为 1 的记录 db.Delete(\u0026amp;user) // 软删除，会设置DeletedAt // 根据主键删除 db.Delete(\u0026amp;User{}, 10) // 或者 db.Delete(\u0026amp;User{}, []int{1, 2, 3}) // 批量删除 db.Where(\u0026#34;age \u0026lt; ?\u0026#34;, 18).Delete(\u0026amp;User{}) // 永久删除 db.Unscoped().Delete(\u0026amp;user) // 永久删除，不使用软删除 条件查询 # // 基本条件 db.Where(\u0026#34;name = ?\u0026#34;, \u0026#34;张三\u0026#34;).First(\u0026amp;user) // NOT条件 db.Not(\u0026#34;name = ?\u0026#34;, \u0026#34;张三\u0026#34;).Find(\u0026amp;users) // OR条件 db.Where(\u0026#34;name = ?\u0026#34;, \u0026#34;张三\u0026#34;).Or(\u0026#34;name = ?\u0026#34;, \u0026#34;李四\u0026#34;).Find(\u0026amp;users) // AND条件 db.Where(\u0026#34;name = ? AND age \u0026gt;= ?\u0026#34;, \u0026#34;张三\u0026#34;, 20).Find(\u0026amp;users) // IN条件 db.Where(\u0026#34;name IN ?\u0026#34;, []string{\u0026#34;张三\u0026#34;, \u0026#34;李四\u0026#34;, \u0026#34;王五\u0026#34;}).Find(\u0026amp;users) // LIKE条件 db.Where(\u0026#34;name LIKE ?\u0026#34;, \u0026#34;%张%\u0026#34;).Find(\u0026amp;users) // 时间范围 db.Where(\u0026#34;created_at BETWEEN ? AND ?\u0026#34;, lastWeek, today).Find(\u0026amp;users) // 原始SQL db.Raw(\u0026#34;SELECT * FROM users WHERE name = ?\u0026#34;, \u0026#34;张三\u0026#34;).Scan(\u0026amp;users) 事务 # // 手动事务 tx := db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() // 发生panic时回滚 } }() if err := tx.Create(\u0026amp;User{Name: \u0026#34;事务1\u0026#34;}).Error; err != nil { tx.Rollback() return } if err := tx.Create(\u0026amp;User{Name: \u0026#34;事务2\u0026#34;}).Error; err != nil { tx.Rollback() return } // 提交事务 if err := tx.Commit().Error; err != nil { log.Fatalf(\u0026#34;提交事务失败: %v\u0026#34;, err) } // 使用事务闭包 err := db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(\u0026amp;User{Name: \u0026#34;事务3\u0026#34;}).Error; err != nil { return err // 返回任何错误都会回滚事务 } if err := tx.Create(\u0026amp;User{Name: \u0026#34;事务4\u0026#34;}).Error; err != nil { return err } // 返回nil将提交事务 return nil }) 钩子（Hooks） # GORM允许在特定操作之前或之后运行代码，这些钩子方法可以用来设置值或处理错误：\nfunc (u *User) BeforeCreate(tx *gorm.DB) (err error) { // 创建记录前的钩子 u.UpdatedAt = time.Now() if u.Name == \u0026#34;\u0026#34; { err = errors.New(\u0026#34;用户名不能为空\u0026#34;) } return } func (u *User) AfterCreate(tx *gorm.DB) (err error) { // 创建记录后的钩子 fmt.Printf(\u0026#34;用户 %s 已创建\\n\u0026#34;, u.Name) return } GORM提供了以下钩子点：\n创建：BeforeSave, BeforeCreate, AfterCreate, AfterSave 更新：BeforeSave, BeforeUpdate, AfterUpdate, AfterSave 删除：BeforeDelete, AfterDelete 查询：AfterFind 关联（Associations） # GORM提供了多种关联关系的支持，包括：\n一对一（has one, belongs to） 一对多（has many） 多对多（many to many） 一对一关系 # 一对一关系意味着一个记录只与另一个表中的一条记录相关联。在GORM中，一对一关系可以通过has one或belongs to表示。\nHas One（拥有一个） # // 用户有一个信用卡 type User struct { ID uint Name string CreditCard CreditCard // has one 关系 } type CreditCard struct { ID uint Number string UserID uint // 外键 } // 查询用户的信用卡 var user User db.First(\u0026amp;user, 1) db.Model(\u0026amp;user).Association(\u0026#34;CreditCard\u0026#34;).Find(\u0026amp;creditCard) // 预加载关系 var userWithCard User db.Preload(\u0026#34;CreditCard\u0026#34;).First(\u0026amp;userWithCard, 1) Belongs To（属于） # belongs to关系表示一个模型属于另一个模型。例如，每个信用卡属于一个用户：\n// 信用卡属于一个用户 type CreditCard struct { ID uint Number string UserID uint // 外键 User User // belongs to 关系 } type User struct { ID uint Name string } // 查询信用卡所属的用户 var card CreditCard db.First(\u0026amp;card, 1) db.Model(\u0026amp;card).Association(\u0026#34;User\u0026#34;).Find(\u0026amp;user) // 预加载关系 var cardWithUser CreditCard db.Preload(\u0026#34;User\u0026#34;).First(\u0026amp;cardWithUser, 1) 一对多关系 # 一对多关系表示一个模型可以有多个其他模型的实例。例如，一个用户可以有多篇文章：\n// 用户有多篇文章 type User struct { ID uint Name string Articles []Article // has many 关系 } type Article struct { ID uint Title string Content string UserID uint // 外键 } // 查询用户的所有文章 var user User db.First(\u0026amp;user, 1) var articles []Article db.Model(\u0026amp;user).Association(\u0026#34;Articles\u0026#34;).Find(\u0026amp;articles) // 添加关联 newArticle := Article{Title: \u0026#34;新文章\u0026#34;, Content: \u0026#34;文章内容...\u0026#34;} db.Model(\u0026amp;user).Association(\u0026#34;Articles\u0026#34;).Append(\u0026amp;newArticle) // 替换关联 db.Model(\u0026amp;user).Association(\u0026#34;Articles\u0026#34;).Replace(\u0026amp;newArticles) // 删除关联 db.Model(\u0026amp;user).Association(\u0026#34;Articles\u0026#34;).Delete(\u0026amp;articlesToDelete) // 清空关联 db.Model(\u0026amp;user).Association(\u0026#34;Articles\u0026#34;).Clear() // 计数 count := db.Model(\u0026amp;user).Association(\u0026#34;Articles\u0026#34;).Count() // 预加载关系 var userWithArticles User db.Preload(\u0026#34;Articles\u0026#34;).First(\u0026amp;userWithArticles, 1) 多对多关系 # 多对多关系表示两个模型之间互相拥有多个实例。例如，一个用户可以有多个角色，一个角色也可以被多个用户拥有：\n// 用户和角色是多对多关系 type User struct { ID uint Name string Roles []Role `gorm:\u0026#34;many2many:user_roles;\u0026#34;` // 多对多关系 } type Role struct { ID uint Name string Users []User `gorm:\u0026#34;many2many:user_roles;\u0026#34;` // 多对多关系 } // 多对多关系会自动创建连接表 // user_roles表会包含user_id和role_id两个外键 // 查询用户的所有角色 var user User db.First(\u0026amp;user, 1) var roles []Role db.Model(\u0026amp;user).Association(\u0026#34;Roles\u0026#34;).Find(\u0026amp;roles) // 为用户添加角色 var role Role db.First(\u0026amp;role, \u0026#34;name = ?\u0026#34;, \u0026#34;管理员\u0026#34;) db.Model(\u0026amp;user).Association(\u0026#34;Roles\u0026#34;).Append(\u0026amp;role) // 预加载关系 var userWithRoles User db.Preload(\u0026#34;Roles\u0026#34;).First(\u0026amp;userWithRoles, 1) 关联预加载 # 预加载是避免N+1查询问题的重要技术。N+1问题指的是，当需要获取一个模型及其关联时，首先执行一次查询获取主模型，然后针对每个主模型实例再执行额外的查询来获取关联数据。\nGORM提供了Preload方法来一次性加载关联：\n// 预加载单个关联 var users []User db.Preload(\u0026#34;CreditCard\u0026#34;).Find(\u0026amp;users) // 预加载多个关联 db.Preload(\u0026#34;CreditCard\u0026#34;).Preload(\u0026#34;Articles\u0026#34;).Find(\u0026amp;users) // 预加载嵌套关联 db.Preload(\u0026#34;Articles.Comments\u0026#34;).Find(\u0026amp;users) // 预加载带条件的关联 db.Preload(\u0026#34;Articles\u0026#34;, \u0026#34;published = ?\u0026#34;, true).Find(\u0026amp;users) 延迟加载与急切加载 # 默认情况下，GORM使用延迟加载（lazy loading）模式，即只有在需要时才加载关联数据。但是这可能导致N+1查询问题。\n为了避免此问题，我们可以使用急切加载（eager loading）：\n// 急切加载 - 一次查询获取所有需要的数据 var users []User db.Preload(\u0026#34;CreditCard\u0026#34;).Preload(\u0026#34;Articles\u0026#34;).Find(\u0026amp;users) // 对比延迟加载 - 可能导致N+1查询 var users []User db.Find(\u0026amp;users) for _, user := range users { var card CreditCard db.Model(\u0026amp;user).Association(\u0026#34;CreditCard\u0026#34;).Find(\u0026amp;card) var articles []Article db.Model(\u0026amp;user).Association(\u0026#34;Articles\u0026#34;).Find(\u0026amp;articles) } 高级GORM功能 # 批量操作 # 对于需要处理大量数据的场景，GORM提供了批量操作功能：\n// 批量插入 var users = []User{ {Name: \u0026#34;用户1\u0026#34;, Age: 18}, {Name: \u0026#34;用户2\u0026#34;, Age: 20}, // 更多用户... } // 使用Create批量插入 db.Create(\u0026amp;users) // 使用CreateInBatches控制每批次插入的数量 db.CreateInBatches(\u0026amp;users, 100) // 每批次100条 // 批量更新 db.Model(\u0026amp;User{}).Where(\u0026#34;age \u0026lt; ?\u0026#34;, 20).Updates(map[string]interface{}{\u0026#34;is_adult\u0026#34;: false}) // 批量删除 db.Where(\u0026#34;created_at \u0026lt; ?\u0026#34;, lastMonth).Delete(\u0026amp;User{}) 自定义数据类型 # GORM允许我们定义自定义数据类型，以满足特定的业务需求：\ntype Status string const ( StatusPending Status = \u0026#34;pending\u0026#34; StatusProcessing Status = \u0026#34;processing\u0026#34; StatusCompleted Status = \u0026#34;completed\u0026#34; StatusFailed Status = \u0026#34;failed\u0026#34; ) // 继承Scanner和Valuer接口以便与数据库交互 func (s *Status) Scan(value interface{}) error { *s = Status(value.(string)) return nil } func (s Status) Value() (driver.Value, error) { return string(s), nil } type Order struct { ID uint UserID uint Amount float64 Status Status `gorm:\u0026#34;type:string;default:\u0026#39;pending\u0026#39;\u0026#34;` } 原生SQL与复杂查询 # 对于复杂查询，GORM允许我们使用原生SQL：\n// 原生SQL查询 var users []User db.Raw(\u0026#34;SELECT * FROM users WHERE age \u0026gt; ? AND is_active = ?\u0026#34;, 18, true).Scan(\u0026amp;users) // 执行原生SQL db.Exec(\u0026#34;UPDATE users SET is_active = ? WHERE last_login \u0026lt; ?\u0026#34;, false, lastMonth) // 使用子查询 subQuery := db.Model(\u0026amp;User{}).Select(\u0026#34;id\u0026#34;).Where(\u0026#34;age \u0026gt; ?\u0026#34;, 18) db.Where(\u0026#34;id IN (?)\u0026#34;, subQuery).Find(\u0026amp;users) // 使用命名参数 db.Where(\u0026#34;name = @name AND age = @age\u0026#34;, map[string]interface{}{\u0026#34;name\u0026#34;: \u0026#34;张三\u0026#34;, \u0026#34;age\u0026#34;: 18}).Find(\u0026amp;users) 软删除与硬删除 # GORM的模型如果包含gorm.DeletedAt字段，默认会启用软删除功能：\n// 软删除（默认行为） db.Delete(\u0026amp;user) // 查询时会自动排除软删除的记录 db.Find(\u0026amp;users) // 仅返回未删除的记录 // 包括已删除记录 db.Unscoped().Find(\u0026amp;users) // 返回所有记录，包括已删除的 // 永久删除（硬删除） db.Unscoped().Delete(\u0026amp;user) // 永久删除记录 GORM会话模式 # GORM v2引入了会话模式，可以在不同的操作中重用设置：\n// 创建一个新会话 session := db.Session(\u0026amp;gorm.Session{ PrepareStmt: true, // 预编译语句 Logger: logger.Default.LogMode(logger.Info), }) // 在会话中执行多个操作 var user User session.First(\u0026amp;user, 1) session.Model(\u0026amp;user).Update(\u0026#34;is_active\u0026#34;, true) // 创建一个只读会话 readSession := db.Session(\u0026amp;gorm.Session{ PrepareStmt: true, QueryFields: true, // 列出所有字段 DryRun: true, // 生成SQL但不执行 }) // 查看生成的SQL stmt := readSession.Find(\u0026amp;users).Statement fmt.Println(stmt.SQL.String()) 使用事务处理关联 # 在处理关联关系时，使用事务确保数据一致性非常重要：\nerr := db.Transaction(func(tx *gorm.DB) error { // 创建用户 user := User{Name: \u0026#34;新用户\u0026#34;} if err := tx.Create(\u0026amp;user).Error; err != nil { return err } // 为用户创建信用卡 card := CreditCard{Number: \u0026#34;1234-5678-9012-3456\u0026#34;, UserID: user.ID} if err := tx.Create(\u0026amp;card).Error; err != nil { return err } // 为用户创建文章 article := Article{Title: \u0026#34;我的第一篇文章\u0026#34;, UserID: user.ID} if err := tx.Create(\u0026amp;article).Error; err != nil { return err } return nil }) if err != nil { log.Fatalf(\u0026#34;事务失败: %v\u0026#34;, err) } 使用中间件和插件 # GORM允许我们通过实现接口来创建插件，扩展其功能：\ntype GormPlugin struct{} func (p *GormPlugin) Name() string { return \u0026#34;MyPlugin\u0026#34; } func (p *GormPlugin) Initialize(db *gorm.DB) error { // 注册回调 db.Callback().Create().Before(\u0026#34;gorm:create\u0026#34;).Register(\u0026#34;my_plugin:before_create\u0026#34;, func(db *gorm.DB) { // 在创建前执行逻辑 fmt.Println(\u0026#34;Before Create Hook!\u0026#34;) }) return nil } // 使用插件 db.Use(\u0026amp;GormPlugin{}) Gen # GEN 是一个基于 GORM 的安全 ORM 框架, 由字节跳动无恒实验室与 GORM 作者联合研发，主要功能说白了就是帮助生成数据表对应的模型文件和更安全方便地执行SQL。\n安装 # # 安装Gorm和mysql驱动 go get -u gorm.io/gorm go get -u gorm.io/driver/mysql # 安装Gen go get -u gorm.io/gen 生成 # package main import ( \u0026#34;gorm.io/driver/mysql\u0026#34; \u0026#34;gorm.io/gen\u0026#34; \u0026#34;gorm.io/gen/field\u0026#34; \u0026#34;gorm.io/gorm\u0026#34; \u0026#34;gorm.io/gorm/schema\u0026#34; ) // 更新会覆盖原有文件，所以通过 g.GenerateModel(\u0026#34;oss\u0026#34;, fieldOpts...) 指定需要更新的表，不要全部覆盖 const DBDSN = \u0026#34;root:123456@(127.0.0.1:3306)/test?charset=utf8mb4\u0026amp;parseTime=True\u0026amp;loc=Local\u0026#34; func main() { cfg := gen.Config{ OutPath: \u0026#34;./internal/dao\u0026#34;, // 生成查询方法的目录 ModelPkgPath: \u0026#34;./internal/model\u0026#34;, // 结构体生成目录 Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface, // 表字段可为 null 值时, 对应结体字段使用指针类型 FieldNullable: true, // 表字段默认值与模型结构体字段零值不一致的字段, 在插入数据时需要赋值该字段值为零值的, 结构体字段须是指针类型才能成功, 即`FieldCoverable:true`配置下生成的结构体字段. // 因为在插入时遇到字段为零值的会被GORM赋予默认值. 如字段`age`表默认值为10, 即使你显式设置为0最后也会被GORM设为10提交. // 如果该字段没有上面提到的插入时赋零值的特殊需要, 则字段为非指针类型使用起来会比较方便. FieldCoverable: false, // 模型结构体字段的数字类型的符号表示是否与表字段的一致, `false`指示都用有符号类型 FieldSignable: false, // 生成 gorm 标签的字段索引属性 FieldWithIndexTag: false, // 生成 gorm 标签的字段类型属性 FieldWithTypeTag: true, // generate with gorm column type tag } // 处理表名 cfg.WithTableNameStrategy(func(tableName string) (targetTableName string) { // 需要忽略的表 //if strings.EqualFold(tableName, \u0026#34;pay_credit\u0026#34;) || //\tstrings.EqualFold(tableName, \u0026#34;pay_log\u0026#34;) || //\tstrings.EqualFold(tableName, \u0026#34;space\u0026#34;) || //\tstrings.EqualFold(tableName, \u0026#34;space_account\u0026#34;) { //\treturn \u0026#34;\u0026#34; //} return tableName }) // 处理 model名 cfg.WithModelNameStrategy(func(tableName string) (targetTableName string) { s := tableName //if strings.HasPrefix(tableName, \u0026#34;conf_\u0026#34;) { //\ts = strings.TrimPrefix(tableName, \u0026#34;conf_\u0026#34;) //} ns := schema.NamingStrategy{SingularTable: true} return ns.SchemaName(s) return tableName }) // 处理文件名 cfg.WithFileNameStrategy(func(tableName string) (targetTableName string) { //if strings.HasPrefix(tableName, \u0026#34;conf_\u0026#34;) { //\treturn strings.TrimPrefix(tableName, \u0026#34;conf_\u0026#34;) //} return tableName }) g := gen.NewGenerator(cfg) gormdb, _ := gorm.Open(mysql.Open(DBDSN)) g.UseDB(gormdb) // reuse your gorm db // 自定义字段的数据类型 dataMap := map[string]func(detailType gorm.ColumnType) (dataType string){ \u0026#34;tinyint\u0026#34;: func(detailType gorm.ColumnType) (dataType string) { return \u0026#34;bool\u0026#34; }, \u0026#34;smallint\u0026#34;: func(detailType gorm.ColumnType) (dataType string) { return \u0026#34;int\u0026#34; }, \u0026#34;mediumint\u0026#34;: func(detailType gorm.ColumnType) (dataType string) { return \u0026#34;int\u0026#34; }, \u0026#34;bigint\u0026#34;: func(detailType gorm.ColumnType) (dataType string) { return \u0026#34;int\u0026#34; }, \u0026#34;int\u0026#34;: func(detailType gorm.ColumnType) (dataType string) { return \u0026#34;int\u0026#34; }, } // 要先于`ApplyBasic`执行 g.WithDataTypeMap(dataMap) // 自定义模型结体字段的标签 // 将特定字段名的 json 标签加上`string`属性,即 MarshalJSON 时该字段由数字类型转成字符串类型 //jsonField := gen.FieldJSONTagWithNS(func(columnName string) (tagContent string) { //\ttoStringField := `balance, ` //\tif strings.Contains(toStringField, columnName) { //\treturn columnName + \u0026#34;,string\u0026#34; //\t} //\treturn columnName //}) //delField := gen.FieldType(\u0026#34;deleted_at\u0026#34;, \u0026#34;time.Time\u0026#34;) // 不生成 默认的类型 //sizeField := gen.FieldType(\u0026#34;size\u0026#34;, \u0026#34;uint64\u0026#34;) // 不生成 默认的类型 // 将非默认字段名的字段定义为自动时间戳和软删除字段; // 自动时间戳默认字段名为:`updated_at`、`created_at, 表字段数据类型为: INT 或 DATETIME // 软删除默认字段名为:`deleted_at`, 表字段数据类型为: DATETIME autoUpdateTimeField := gen.FieldGORMTag(\u0026#34;update_time\u0026#34;, func(tag field.GormTag) field.GormTag { return tag.Set(\u0026#34;autoUpdateTime\u0026#34;) }) autoCreateTimeField := gen.FieldGORMTag(\u0026#34;create_time\u0026#34;, func(tag field.GormTag) field.GormTag { return tag.Set(\u0026#34;autoCreateTime\u0026#34;) }) //softDeleteField := gen.FieldType(\u0026#34;delete_time\u0026#34;, \u0026#34;soft_delete.DeletedAt\u0026#34;) // 模型自定义选项组 fieldOpts := []gen.ModelOpt{autoUpdateTimeField, autoCreateTimeField} // 创建模型的结构体,生成文件在 model 目录; 先创建的结果会被后面创建的覆盖 // 这里创建个别模型仅仅是为了拿到`*generate.QueryStructMeta`类型对象用于后面的模型关联操作中 // Address := g.GenerateModel(\u0026#34;address\u0026#34;) // 创建 全部模型文件 , 并覆盖前面创建的同名模型 allModel := g.GenerateAllTable(fieldOpts...) // 指定特定的表名 //models := []interface{}{ //\tg.GenerateModel(\u0026#34;workspace_subscribe_log\u0026#34;, fieldOpts...), //\tg.GenerateModel(\u0026#34;workspace_bill_log\u0026#34;, fieldOpts...), //\tg.GenerateModel(\u0026#34;workspace_version\u0026#34;, fieldOpts...), //\tg.GenerateModel(\u0026#34;storage_usage\u0026#34;, fieldOpts...), //\tg.GenerateModel(\u0026#34;inbox\u0026#34;, fieldOpts...), //} // 创建模型的方法,生成文件在 query 目录; 先创建结果不会被后创建的覆盖 g.ApplyBasic(allModel...) g.Execute() } 只生成 Model # 如果只想生成 Model\npackage main import ( \u0026#34;gorm.io/driver/mysql\u0026#34; \u0026#34;gorm.io/gen\u0026#34; \u0026#34;gorm.io/gen/field\u0026#34; \u0026#34;gorm.io/gorm\u0026#34; \u0026#34;gorm.io/gorm/schema\u0026#34; ) const DBDSN = \u0026#34;root:123456@(127.0.0.1:3306)/test?charset=utf8mb4\u0026amp;parseTime=True\u0026amp;loc=Local\u0026#34; func main() { cfg := gen.Config{ ModelPkgPath: \u0026#34;./internal/model\u0026#34;, // 结构体生成目录 // 表字段可为 null 值时, 对应结体字段使用指针类型 FieldNullable: false, // 表字段默认值与模型结构体字段零值不一致的字段, 在插入数据时需要赋值该字段值为零值的, 结构体字段须是指针类型才能成功, 即`FieldCoverable:true`配置下生成的结构体字段. // 因为在插入时遇到字段为零值的会被GORM赋予默认值. 如字段`age`表默认值为10, 即使你显式设置为0最后也会被GORM设为10提交. // 如果该字段没有上面提到的插入时赋零值的特殊需要, 则字段为非指针类型使用起来会比较方便. FieldCoverable: false, // 模型结构体字段的数字类型的符号表示是否与表字段的一致, `false`指示都用有符号类型 FieldSignable: false, // 生成 gorm 标签的字段索引属性 FieldWithIndexTag: false, // 生成 gorm 标签的字段类型属性 FieldWithTypeTag: true, // generate with gorm column type tag } // 处理表名 cfg.WithTableNameStrategy(func(tableName string) (targetTableName string) { // 需要忽略的表 //if strings.EqualFold(tableName, \u0026#34;pay_credit\u0026#34;) || //\tstrings.EqualFold(tableName, \u0026#34;pay_log\u0026#34;) || //\tstrings.EqualFold(tableName, \u0026#34;space\u0026#34;) || //\tstrings.EqualFold(tableName, \u0026#34;space_account\u0026#34;) { //\treturn \u0026#34;\u0026#34; //} return tableName }) // 处理 model名 cfg.WithModelNameStrategy(func(tableName string) (targetTableName string) { s := tableName //if strings.HasPrefix(tableName, \u0026#34;conf_\u0026#34;) { //\ts = strings.TrimPrefix(tableName, \u0026#34;conf_\u0026#34;) //} ns := schema.NamingStrategy{SingularTable: true} return ns.SchemaName(s) return tableName }) // 处理文件名 cfg.WithFileNameStrategy(func(tableName string) (targetTableName string) { //if strings.HasPrefix(tableName, \u0026#34;conf_\u0026#34;) { //\treturn strings.TrimPrefix(tableName, \u0026#34;conf_\u0026#34;) //} return tableName }) g := gen.NewGenerator(cfg) gormdb, _ := gorm.Open(mysql.Open(DBDSN)) g.UseDB(gormdb) // reuse your gorm db // 自定义字段的数据类型 dataMap := map[string]func(detailType gorm.ColumnType) (dataType string){ \u0026#34;tinyint\u0026#34;: func(detailType gorm.ColumnType) (dataType string) { return \u0026#34;bool\u0026#34; }, \u0026#34;smallint\u0026#34;: func(detailType gorm.ColumnType) (dataType string) { return \u0026#34;int\u0026#34; }, \u0026#34;mediumint\u0026#34;: func(detailType gorm.ColumnType) (dataType string) { return \u0026#34;int\u0026#34; }, \u0026#34;bigint\u0026#34;: func(detailType gorm.ColumnType) (dataType string) { return \u0026#34;int\u0026#34; }, \u0026#34;int\u0026#34;: func(detailType gorm.ColumnType) (dataType string) { return \u0026#34;int\u0026#34; }, // 这里将数据库中的`datetime`映射为`config.Time`，方便我们序列化和反序列化 \u0026#34;datetime\u0026#34;: func(detailType gorm.ColumnType) (dataType string) { return \u0026#34;config.Time\u0026#34; }, } // 要先于`ApplyBasic`执行 g.WithDataTypeMap(dataMap) // fields to their `datatypes.Null[T]` types gen.WithDataTypesNullType(true) // 将非默认字段名的字段定义为自动时间戳和软删除字段; autoUpdateTimeField := gen.FieldGORMTag(\u0026#34;update_time\u0026#34;, func(tag field.GormTag) field.GormTag { return tag.Set(\u0026#34;autoUpdateTime\u0026#34;) }) autoCreateTimeField := gen.FieldGORMTag(\u0026#34;create_time\u0026#34;, func(tag field.GormTag) field.GormTag { return tag.Set(\u0026#34;autoCreateTime\u0026#34;) }) // 模型自定义选项组 fieldOpts := []gen.ModelOpt{autoUpdateTimeField, autoCreateTimeField} // 创建 全部模型文件 , 并覆盖前面创建的同名模型 g.GenerateAllTable(fieldOpts...) g.Execute() } ","date":"2025-07-11","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/db9fe107/6b22f89c/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eORM概念介绍 \n    \u003cdiv id=\"orm概念介绍\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#orm%e6%a6%82%e5%bf%b5%e4%bb%8b%e7%bb%8d\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e虽然Go的原生SQL包\u003ccode\u003edatabase/sql\u003c/code\u003e提供了最大的灵活性和控制力，但在实际项目中，我们经常需要一种更便捷、更安全、更贴近业务逻辑的方式来操作数据库。这就是ORM（对象关系映射）技术的用武之地。\u003c/p\u003e","title":"2、gorm","type":"posts"},{"content":" settings.xml # Maven有一个settings.xml配置文件，它是用来设置 Maven 参数的配置文件，settings.xml 中包含类似本地仓储位置、修改远程仓储服务器、认证信息等配置。\nsettings.xml 文件一般存在于两个位置：\n全局配置： ${MAVEN_HOME}/conf/settings.xml 用户配置： ${user.home}/.m2/settings.xml 注意：用户配置优先于全局配置。${user.home} 和所有其他系统属性只能在 3.0+版本上使用。请注意 windows 和 Linux 使用变量的区别。 如果这些文件同时存在，在应用配置时，会合并它们的内容，如果有重复的配置，优先级高的配置会覆盖优先级低的。 settings.xml结构 # \u0026lt;settings ...\u0026gt; \u0026lt;localRepository/\u0026gt; \u0026lt;!--本地仓库路径--\u0026gt; \u0026lt;interactiveMode/\u0026gt; \u0026lt;!--是否需要和用户交互，默认true，一般无需修改--\u0026gt; \u0026lt;usePluginRegistry/\u0026gt; \u0026lt;!--是否通过pluginregistry.xml独立文件配置插件，默认false,一般直接配置到pom.xml--\u0026gt; \u0026lt;offline/\u0026gt; \u0026lt;!--是否离线模式，默认false，如果不想联网，可以开启--\u0026gt; \u0026lt;pluginGroups/\u0026gt; \u0026lt;!--配置如果插件groupid未提供时自动搜索，一般很少配置--\u0026gt; \u0026lt;servers/\u0026gt; \u0026lt;!--配置远程仓库服务器需要的认证信息，如用户名和密码--\u0026gt; \u0026lt;mirrors/\u0026gt; \u0026lt;!--为repositories中配置的仓库配置镜像--\u0026gt; \u0026lt;proxies/\u0026gt; \u0026lt;!--配置连接仓库的代理--\u0026gt; \u0026lt;profiles\u0026gt; \u0026lt;!--全局配置项目构建参数列表，一般通过它配置特定环境的定制化操作--\u0026gt; \u0026lt;activation/\u0026gt; \u0026lt;!--profile的扩展选项，指定某些条件下自动切换profile配置--\u0026gt; \u0026lt;properties/\u0026gt; \u0026lt;!--在配置文件中声明扩展配置项--\u0026gt; \u0026lt;repositories/\u0026gt; \u0026lt;!--配置远程仓库列表，用于多仓库配置--\u0026gt; \u0026lt;pluginRepositories/\u0026gt; \u0026lt;!--配置插件仓库列表--\u0026gt; \u0026lt;/profiles\u0026gt; \u0026lt;activeProfiles/\u0026gt; \u0026lt;!--手工激活profile，通过配置id选项完成激活--\u0026gt; \u0026lt;/settings\u0026gt; localRepository # 该值表示构建系统本地仓库的路径。其默认值：${user.home}/.m2/repository\ninteractiveMode # 表示 Maven 是否需要和用户交互以获得输入，默认true，一般无需修改。\n\u0026lt;interactiveMode\u0026gt;true\u0026lt;/interactiveMode\u0026gt; usePluginRegistry # 表示Maven 是否需要使用plugin-registry.xml文件来管理插件版本。\n如果需要让 Maven使用文件${user.home}/.m2/plugin-registry.xml 来管理插件版本，则设为 true，默认为 false。\n\u0026lt;usePluginRegistry\u0026gt;false\u0026lt;/usePluginRegistry\u0026gt; offline # 表示 Maven 是否需要在离线模式下运行。\n如果构建系统需要在离线模式下运行，则为 true，默认为 false。\n\u0026lt;offline\u0026gt;false\u0026lt;/offline\u0026gt; pluginGroups # 当插件的组织 id（groupId）没有显式提供时，提供搜寻插件组织 id（groupId）的列表。\n该元素包含一个 pluginGroup 元素列表，每个子元素包含了一个组织 Id（groupId）。 当我们使用某个插件，并且没有在命令行为其提供组织 Id（groupId）的时候，Maven 就会使用该列表。\n默认情况下该列表包含了org.apache.maven.plugins和org.codehaus.mojo\n\u0026lt;pluginGroups\u0026gt; \u0026lt;pluginGroup\u0026gt;org.apache.maven.plugins\u0026lt;/pluginGroup\u0026gt; \u0026lt;pluginGroup\u0026gt;org.codehaus.mojo\u0026lt;/pluginGroup\u0026gt; \u0026lt;/pluginGroups\u0026gt; servers # 仓库的下载和部署是在pom.xml文件中的repositories和distributionManagement元素中定义的。然而，一般类似用户名、密码（有些仓库访问是需要安全认证的）等信息不应该在pom.xml文件中配置，这些信息可以配置在settings.xml中。\n\u0026lt;!--配置服务端的一些设置。一些设置如安全证书不应该和pom.xml一起分发。 这种类型的信息应该存在于构建服务器上的settings.xml文件中。 --\u0026gt; \u0026lt;servers\u0026gt; \u0026lt;!--服务器元素包含配置服务器时需要的信息 --\u0026gt; \u0026lt;server\u0026gt; \u0026lt;!--这是server的id（注意不是用户登陆的id），该id与distributionManagement中repository元素的id相匹配。 --\u0026gt; \u0026lt;id\u0026gt;server001\u0026lt;/id\u0026gt; \u0026lt;!--鉴权用户名。鉴权用户名和鉴权密码表示服务器认证所需要的登录名和密码。 --\u0026gt; \u0026lt;username\u0026gt;my_login\u0026lt;/username\u0026gt; \u0026lt;!--鉴权密码 。鉴权用户名和鉴权密码表示服务器认证所需要的登录名和密码。密码加密功能已被添加到2.1.0 +。 详情请访问密码加密页面 --\u0026gt; \u0026lt;password\u0026gt;my_password\u0026lt;/password\u0026gt; \u0026lt;!--鉴权时使用的私钥位置。和前两个元素类似，私钥位置和私钥密码指定了一个私钥的路径 （默认是${user.home}/.ssh/id_dsa）以及如果需要的话，一个密语。 将来passphrase和password元素可能会被提取到外部， 但目前它们必须在settings.xml文件以纯文本的形式声明。 --\u0026gt; \u0026lt;privateKey\u0026gt;${usr.home}/.ssh/id_dsa\u0026lt;/privateKey\u0026gt; \u0026lt;!--鉴权时使用的私钥密码。 --\u0026gt; \u0026lt;passphrase\u0026gt;some_passphrase\u0026lt;/passphrase\u0026gt; \u0026lt;!--文件被创建时的权限。如果在部署的时候会创建一个仓库文件或者目录，这时候就可以使用权限（permission）。 这两个元素合法的值是一个三位数字，其对应了unix文件系统的权限，如664，或者775。 --\u0026gt; \u0026lt;filePermissions\u0026gt;664\u0026lt;/filePermissions\u0026gt; \u0026lt;!--目录被创建时的权限。 --\u0026gt; \u0026lt;directoryPermissions\u0026gt;775\u0026lt;/directoryPermissions\u0026gt; \u0026lt;/server\u0026gt; \u0026lt;/servers\u0026gt; mirrors # \u0026lt;mirrors\u0026gt; \u0026lt;!-- 给定仓库的下载镜像。 --\u0026gt; \u0026lt;mirror\u0026gt; \u0026lt;!-- 该镜像的唯一标识符。id用来区分不同的mirror元素。 --\u0026gt; \u0026lt;id\u0026gt;alimaven\u0026lt;/id\u0026gt; \u0026lt;!-- 镜像名称 --\u0026gt; \u0026lt;name\u0026gt;aliyun maven\u0026lt;/name\u0026gt; \u0026lt;!-- 该镜像的URL。构建系统会优先考虑使用该URL，而非使用默认的服务器URL。 --\u0026gt; \u0026lt;url\u0026gt;https://maven.aliyun.com/repository/central/\u0026lt;/url\u0026gt; \u0026lt;!-- 镜像的服务器的id。例如，如果我们要设置了一个Maven中央仓库 （http://repo.maven.apache.org/maven2/）的镜像，就需要将该元素设置成central。 这必须和默认中央仓库的id central完全一致。 --\u0026gt; \u0026lt;mirrorOf\u0026gt;central\u0026lt;/mirrorOf\u0026gt; \u0026lt;/mirror\u0026gt; \u0026lt;/mirrors\u0026gt; 为repositories标签中定义的仓库配置的下载镜像列表\n\u0026lt;mirrors\u0026gt; \u0026lt;mirror\u0026gt; \u0026lt;id\u0026gt;ALiYunMirror\u0026lt;/id\u0026gt; \u0026lt;mirrorOf\u0026gt;aliyun,google\u0026lt;/mirrorOf\u0026gt; \u0026lt;name\u0026gt;Nexus aliyun\u0026lt;/name\u0026gt; \u0026lt;url\u0026gt;https://maven.aliyun.com/repository/public\u0026lt;/url\u0026gt; \u0026lt;/mirror\u0026gt; \u0026lt;/mirrors\u0026gt; \u0026lt;repositories\u0026gt; \u0026lt;repository\u0026gt; \u0026lt;id\u0026gt;aliyun\u0026lt;/id\u0026gt; \u0026lt;url\u0026gt;https://maven.aliyun.com/repository/central\u0026lt;/url\u0026gt; \u0026lt;/repository\u0026gt; \u0026lt;repository\u0026gt; \u0026lt;id\u0026gt;google\u0026lt;/id\u0026gt; \u0026lt;url\u0026gt;https://maven.aliyun.com/repository/google\u0026lt;/url\u0026gt; \u0026lt;/repository\u0026gt; \u0026lt;/repositories\u0026gt; \u0026lt;mirrorOf\u0026gt;aliyun,google\u0026lt;/mirrorOf\u0026gt;：如果mirror的mirrorOf的值和repository的ID完全一致，则这个mirror和repository匹配。如上所示，当我们需要从aliyun或者google这两个仓库下载jar时，maven会直接从ALiYunMirror这个mirror下载。\n\u0026lt;mirrorOf\u0026gt;*\u0026lt;/mirrorOf\u0026gt;：可以匹配所有repository。如下所示，当需要从aliyun或者google下载jar包时下载的地址都会重定向到ALiYunMirror配置的地址上。\n\u0026lt;mirrorOf\u0026gt;external:*\u0026lt;/mirrorOf\u0026gt;：匹配所有远程仓库，使用localhost的除外，使用file://协议的除外。也就是说，匹配所有不在本机上的远程仓库。\n\u0026lt;mirrorOf\u0026gt;*,!repo1\u0026lt;/miiroOf\u0026gt;：匹配所有远程仓库，repo1除外，使用感叹号将仓库从匹配中排除。\nproxies # 用来配置连接仓库的代理\n\u0026lt;proxies\u0026gt; \u0026lt;!--代理元素包含配置代理时需要的信息 --\u0026gt; \u0026lt;proxy\u0026gt; \u0026lt;!--代理的唯一定义符，用来区分不同的代理元素。 --\u0026gt; \u0026lt;id\u0026gt;myproxy\u0026lt;/id\u0026gt; \u0026lt;!--该代理是否是激活的那个。true则激活代理。当我们声明了一组代理，而某个时候只需要激活一个代理的时候， 该元素就可以派上用处。 --\u0026gt; \u0026lt;active\u0026gt;true\u0026lt;/active\u0026gt; \u0026lt;!--代理的协议。 协议://主机名:端口，分隔成离散的元素以方便配置。 --\u0026gt; \u0026lt;protocol\u0026gt;http\u0026lt;/protocol\u0026gt; \u0026lt;!--代理的主机名。协议://主机名:端口，分隔成离散的元素以方便配置。 --\u0026gt; \u0026lt;host\u0026gt;proxy.somewhere.com\u0026lt;/host\u0026gt; \u0026lt;!--代理的端口。协议://主机名:端口，分隔成离散的元素以方便配置。 --\u0026gt; \u0026lt;port\u0026gt;8080\u0026lt;/port\u0026gt; \u0026lt;!--代理的用户名，用户名和密码表示代理服务器认证的登录名和密码。 --\u0026gt; \u0026lt;username\u0026gt;proxyuser\u0026lt;/username\u0026gt; \u0026lt;!--代理的密码，用户名和密码表示代理服务器认证的登录名和密码。 --\u0026gt; \u0026lt;password\u0026gt;somepassword\u0026lt;/password\u0026gt; \u0026lt;!--不该被代理的主机名列表。该列表的分隔符由代理服务器指定；例子中使用了竖线分隔符， 使用逗号分隔也很常见。 --\u0026gt; \u0026lt;nonProxyHosts\u0026gt;*.google.com|ibiblio.org\u0026lt;/nonProxyHosts\u0026gt; \u0026lt;/proxy\u0026gt; \u0026lt;/proxies\u0026gt; profiles # 全局配置项目构建参数列表，一般通过它配置特定环境的定制化操作\nsettings.xml 中的 profile 元素是pom.xml中 profile 元素的裁剪版本。\n这里的 profile 元素只包含id、activation、repositories、pluginRepositories 和 properties五个子元素，是因为这里只关心构建系统这个整体（这正是settings.xml文件的角色定位），而非单独的项目对象模型设置。如果一个settings.xml中的 profile 被激活，它的值会覆盖任何其它定义在pom.xml中带有相同 id 的 profile。\n\u0026lt;profiles\u0026gt; \u0026lt;profile\u0026gt; \u0026lt;id\u0026gt;test\u0026lt;/id\u0026gt; \u0026lt;!-- profile的唯一标识 --\u0026gt; \u0026lt;activation /\u0026gt; \u0026lt;!-- 自动触发profile的条件逻辑 --\u0026gt; \u0026lt;properties /\u0026gt; \u0026lt;!-- 扩展属性列表 --\u0026gt; \u0026lt;repositories /\u0026gt; \u0026lt;!-- 远程仓库列表 --\u0026gt; \u0026lt;pluginRepositories /\u0026gt; \u0026lt;!-- 插件仓库列表 --\u0026gt; \u0026lt;/profile\u0026gt; \u0026lt;/profiles\u0026gt; 1、activation # 如 pom.xml 中的 profile 一样，profile 的作用在于它能够在某些特定的环境中自动使用某些特定的值；这些环境通过 activation 元素指定。\nactivation 元素并不是激活 profile 的唯一方式。settings.xml 文件中的 activeProfile 元素可以包含 profile 的 id。profile 也可以通过在命令行，使用 -P 标记和逗号分隔的列表来显式的激活（如-P test）。\n\u0026lt;activation\u0026gt; \u0026lt;!-- profile默认是否激活 --\u0026gt; \u0026lt;activeByDefault\u0026gt;false\u0026lt;/activeByDefault\u0026gt; \u0026lt;!--当匹配的jdk被检测到，profile被激活。例如，1.4激活JDK1.4，1.4.0_2， 而 !1.4 激活所有版本不是以1.4开头的JDK。 --\u0026gt; \u0026lt;jdk\u0026gt;1.5\u0026lt;/jdk\u0026gt; \u0026lt;!--当匹配的操作系统属性被检测到，profile被激活。os元素可以定义一些操作系统相关的属性。 --\u0026gt; \u0026lt;os\u0026gt; \u0026lt;name\u0026gt;Windows XP\u0026lt;/name\u0026gt; \u0026lt;!--激活profile的操作系统的名字 --\u0026gt; \u0026lt;family\u0026gt;Windows\u0026lt;/family\u0026gt; \u0026lt;!--激活profile的操作系统所属家族(如windows、linux) --\u0026gt; \u0026lt;arch\u0026gt;x86\u0026lt;/arch\u0026gt; \u0026lt;!--激活profile的操作系统体系结构 --\u0026gt; \u0026lt;version\u0026gt;5.1.2600\u0026lt;/version\u0026gt; \u0026lt;!--激活profile的操作系统版本 --\u0026gt; \u0026lt;/os\u0026gt; \u0026lt;!--如果Maven检测到某一个属性（其值可以在POM中通过${name}引用），Profile就会被激活。 如果值字段是空的，那么存在属性名称字段就会激活profile，否则按区分大小写方式匹配属性值字段 --\u0026gt; \u0026lt;property\u0026gt; \u0026lt;name\u0026gt;mavenVersion\u0026lt;/name\u0026gt; \u0026lt;!--激活profile的属性的名称 --\u0026gt; \u0026lt;value\u0026gt;2.0.3\u0026lt;/value\u0026gt; \u0026lt;!--激活profile的属性的值 --\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;!--提供一个文件名，通过检测该文件的存在或不存在来激活profile。 missing检查文件是否存在，如果不存在则激活profile。 exists则会检查文件是否存在，如果存在则激活profile。 --\u0026gt; \u0026lt;file\u0026gt; \u0026lt;exists\u0026gt;${basedir}/file2.properties\u0026lt;/exists\u0026gt; \u0026lt;!--如果指定的文件存在，则激活profile。 --\u0026gt; \u0026lt;missing\u0026gt;${basedir}/file1.properties\u0026lt;/missing\u0026gt; \u0026lt;!--如果指定的文件不存在，则激活profile。 --\u0026gt; \u0026lt;/file\u0026gt; \u0026lt;/activation\u0026gt; 在Maven工程的 pom.xml 所在目录下执行 mvn help:active-profiles 命令可以查看中央仓储的 profile是否在工程中生效。\n2、properties # 对应profile的扩展属性列表\n\u0026lt;properties\u0026gt; \u0026lt;user.install\u0026gt;${user.home}/our-project\u0026lt;/user.install\u0026gt; \u0026lt;/properties\u0026gt; 3、repository # 远程仓库列表，它是Maven用来填充构建系统本地仓库所使用的一组远程仓库。\n\u0026lt;repositories\u0026gt; \u0026lt;!--包含需要连接到远程仓库的信息 --\u0026gt; \u0026lt;repository\u0026gt; \u0026lt;!--远程仓库唯一标识 --\u0026gt; \u0026lt;id\u0026gt;codehausSnapshots\u0026lt;/id\u0026gt; \u0026lt;!--远程仓库名称 --\u0026gt; \u0026lt;name\u0026gt;Codehaus Snapshots\u0026lt;/name\u0026gt; \u0026lt;!--如何处理远程仓库里发布版本的下载 --\u0026gt; \u0026lt;releases\u0026gt; \u0026lt;!--true或者false表示该仓库是否为下载某种类型构件（发布版，快照版）开启。 --\u0026gt; \u0026lt;enabled\u0026gt;false\u0026lt;/enabled\u0026gt; \u0026lt;!--该元素指定更新发生的频率。Maven会比较本地POM和远程POM的时间戳。这里的选项是：always（一直），daily（默认，每日），interval：X（这里X是以分钟为单位的时间间隔），或者never（从不）。 --\u0026gt; \u0026lt;updatePolicy\u0026gt;always\u0026lt;/updatePolicy\u0026gt; \u0026lt;!--当Maven验证构件校验文件失败时该怎么做-ignore（忽略），fail（失败），或者warn（警告）。 --\u0026gt; \u0026lt;checksumPolicy\u0026gt;warn\u0026lt;/checksumPolicy\u0026gt; \u0026lt;/releases\u0026gt; \u0026lt;!--如何处理远程仓库里不稳定版本的下载。子元素同releases子元素 --\u0026gt; \u0026lt;snapshots\u0026gt; \u0026lt;enabled /\u0026gt; \u0026lt;updatePolicy /\u0026gt; \u0026lt;checksumPolicy /\u0026gt; \u0026lt;/snapshots\u0026gt; \u0026lt;!--远程仓库URL --\u0026gt; \u0026lt;url\u0026gt;http://snapshots.maven.codehaus.org/maven2\u0026lt;/url\u0026gt; \u0026lt;!--用于定位和排序构件的仓库布局类型-可以是default（默认）或者legacy（遗留）。Maven 2为其仓库提供了一个默认的布局；然而，Maven 1.x有一种不同的布局。我们可以使用该元素指定布局是default（默认）还是legacy（遗留）。 --\u0026gt; \u0026lt;layout\u0026gt;default\u0026lt;/layout\u0026gt; \u0026lt;/repository\u0026gt; \u0026lt;/repositories\u0026gt; 4、pluginRepositories # 发现插件的远程仓库列表。\n和 repository 类似，只是 repository 是管理 jar 包依赖的仓库，pluginRepositories 则是管理插件的仓库。maven 插件是一种特殊类型的构件。由于这个原因，插件仓库独立于其它仓库。pluginRepositories 元素的结构和 repositories 元素的结构类似。每个 pluginRepository 元素指定一个 Maven 可以用来寻找新插件的远程地址。\n\u0026lt;pluginRepositories\u0026gt; \u0026lt;!-- 包含需要连接到远程插件仓库的信息.参见profiles/profile/repositories/repository元素的说明 --\u0026gt; \u0026lt;pluginRepository\u0026gt; \u0026lt;releases\u0026gt; \u0026lt;enabled /\u0026gt; \u0026lt;updatePolicy /\u0026gt; \u0026lt;checksumPolicy /\u0026gt; \u0026lt;/releases\u0026gt; \u0026lt;snapshots\u0026gt; \u0026lt;enabled /\u0026gt; \u0026lt;updatePolicy /\u0026gt; \u0026lt;checksumPolicy /\u0026gt; \u0026lt;/snapshots\u0026gt; \u0026lt;id /\u0026gt; \u0026lt;name /\u0026gt; \u0026lt;url /\u0026gt; \u0026lt;layout /\u0026gt; \u0026lt;/pluginRepository\u0026gt; \u0026lt;/pluginRepositories\u0026gt; activeProfiles # 手动激活 profiles 的列表，按照profile被应用的顺序定义activeProfile。\n该元素包含了一组 activeProfile 元素，每个 activeProfile 都含有一个 profile id。任何在 activeProfile 中定义的 profile id，不论环境设置如何，其对应的 profile 都会被激活。如果没有匹配的 profile，则什么都不会发生。\n\u0026lt;activeProfiles\u0026gt; \u0026lt;!-- 要激活的profile id --\u0026gt; \u0026lt;activeProfile\u0026gt;test\u0026lt;/activeProfile\u0026gt; \u0026lt;/activeProfiles\u0026gt; ","date":"2025-07-03","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/4ee304f3/0c8f30bd/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003esettings.xml \n    \u003cdiv id=\"settingsxml\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#settingsxml\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eMaven\u003c/code\u003e有一个\u003ccode\u003esettings.xml\u003c/code\u003e配置文件，它是用来设置 \u003ccode\u003eMaven \u003c/code\u003e参数的配置文件，\u003ccode\u003esettings.xml \u003c/code\u003e中包含类似本地仓储位置、修改远程仓储服务器、认证信息等配置。\u003c/p\u003e","title":"2、settings.xml","type":"posts"},{"content":" Buffer简介 # JavaScript 语言自身只有字符串数据类型，没有二进制数据类型。\nNode.js 中的 Buffer 类是用于处理二进制数据的核心工具，提供了对二进制数据的高效操作。\nBuffer 类在处理文件操作、网络通信、图像处理等场景中特别有用。\n二进制数据：Buffer 对象是一个包含原始二进制数据的固定大小的数组。每个元素占用一个字节（8位），因此 Buffer 适合处理二进制数据，如文件内容、网络数据包等。 不可变性：虽然 Buffer 对象的内容可以在创建后修改，但其长度是固定的，不能动态改变。 Buffer 使用 # 创建 Buffer # Buffer.alloc(size[, fill[, encoding]])： 创建了一个长度为 size 字节的 Buffer，相当于申请了 size 字节的内存空间，每个字节的值为 0。 Buffer.allocUnsafe(size)： 建了一个长度为 size 字节的 Buffer，但 Buffer 中可能存在旧的数据，可能会影响执行结果，所以叫 unsafe。 Buffer.allocUnsafeSlow(size)：用于分配给定大小 size 的新 Buffer 实例，但不对其进行初始化。 Buffer.from(array)： 返回一个被 array 的值初始化的新的 Buffer 实例（传入的 array 的元素只能是数字，不然就会自动被 0 覆盖） Buffer.from(arrayBuffer[, byteOffset[, length]])： 返回一个新建的与给定的 ArrayBuffer 共享同一内存的 Buffer。 Buffer.from(buffer)： 复制传入的 Buffer 实例的数据，并返回一个新的 Buffer 实例 Buffer.from(string[, encoding])： 通过字符串创建 Buffer，可以指定编码，默认为 UTF-8。 Node.js 中创建 Buffer 的方式主要如下几种：\nBuffer.alloc # //创建了一个长度为 10 字节的 Buffer，相当于申请了 10 字节的内存空间，每个字节的值为 0 let buf_1 = Buffer.alloc(10); // 结果为 \u0026lt;Buffer 00 00 00 00 00 00 00 00 00 00\u0026gt; Buffer.allocUnsafe # //创建了一个长度为 10 字节的 Buffer，buffer 中可能存在旧的数据, 可能会影响执行结果，所以叫unsafe let buf_2 = Buffer.allocUnsafe(10); Buffer.from # //通过字符串创建 Buffer，默认为 utf-8 let buf_3 = Buffer.from(\u0026#39;hello\u0026#39;); //通过字符串创建 Buffer，按照 ascii 编码 var buf = Buffer.from(\u0026#39;hello\u0026#39;,\u0026#39;ascii\u0026#39;) //通过数组创建 Buffer let buf_4 = Buffer.from([105, 108, 111, 118, 101, 121, 111, 117]); Buffer 与字符编码 # Buffer 实例一般用于表示编码字符的序列，比如 UTF-8 、 UCS2 、 Base64 、或十六进制编码的数据。 通过使用显式的字符编码，就可以在 Buffer 实例与普通的 JavaScript 字符串之间进行相互转换。\nconst buf = Buffer.from(\u0026#39;hello\u0026#39;, \u0026#39;ascii\u0026#39;); console.log(buf.toString(\u0026#39;hex\u0026#39;)); console.log(buf.toString(\u0026#39;base64\u0026#39;)); Node.js 目前支持的字符编码包括：\nascii：仅支持 7 位 ASCII 数据。如果设置去掉高位的话，这种编码是非常快的。 utf8：多字节编码的 Unicode 字符。许多网页和其他文档格式都使用 UTF-8 。 utf16le：2 或 4 个字节，小字节序编码的 Unicode 字符。支持代理对（U+10000 至 U+10FFFF）。 ucs2：utf16le 的别名。 base64：Base64 编码。 latin1：一种把 Buffer 编码成一字节编码的字符串的方式。 binary：latin1 的别名。 hex：将每个字节编码为两个十六进制字符。 写入缓冲区 # 写入 Node 缓冲区的语法如下所示：\nbuf.write(string[, offset[, length]][, encoding]) string：写入缓冲区的字符串。 offset：缓冲区开始写入的索引值，默认为 0 。 length：写入的字节数，默认为 buffer.length encoding：使用的编码。默认为utf8。 根据 encoding 的字符编码写入 string 到 buf 中的 offset 位置。 length 参数是写入的字节数。 如果 buf 没有足够的空间保存整个字符串，则只会写入 string 的一部分。 只部分解码的字符不会被写入。\n从缓冲区读取数据 # 将 Buffer 转换为字符串 # 读取 Node 缓冲区数据的语法如下所示：\nbuf.toString([encoding[, start[, end]]]) encoding：使用的编码。默认为utf8。 start：指定开始读取的索引位置，默认为 0。 end：结束位置，默认为缓冲区的末尾。 将 Buffer 转换为 JSON 对象 # buf.toJSON() 修改数据 # var buffer1 = Buffer.from(\u0026#39;hello\u0026#39;); // 剪切缓冲区 buffer1[0] = 97 console.log(buffer1.toString()) // aello 缓冲区合并 # Buffer.concat(list[, totalLength]) list：用于合并的 Buffer 对象数组列表。 totalLength：指定合并后Buffer对象的总长度。 var buffer1 = Buffer.from((\u0026#39;hello\u0026#39;)); var buffer2 = Buffer.from((\u0026#39;world\u0026#39;)); var buffer3 = Buffer.concat([buffer1,buffer2]); console.log(\u0026#34;buffer3 内容: \u0026#34; + buffer3.toString()); 缓冲区比较 # 该方法在 Node.js V0.12.2 版本引入\nbuf.compare(otherBuffer); otherBuffer：与 buf 对象比较的另外一个 Buffer 对象。 var buffer1 = Buffer.from(\u0026#39;ABC\u0026#39;); var buffer2 = Buffer.from(\u0026#39;ABCD\u0026#39;); var result = buffer1.compare(buffer2); if(result \u0026lt; 0) { console.log(buffer1 + \u0026#34; 在 \u0026#34; + buffer2 + \u0026#34;之前\u0026#34;); }else if(result == 0){ console.log(buffer1 + \u0026#34; 与 \u0026#34; + buffer2 + \u0026#34;相同\u0026#34;); }else { console.log(buffer1 + \u0026#34; 在 \u0026#34; + buffer2 + \u0026#34;之后\u0026#34;); } 拷贝缓冲区 # buf.copy(targetBuffer[, targetStart[, sourceStart[, sourceEnd]]]) targetBuffer：要拷贝的 Buffer 对象。 targetStart：数字, 可选, 默认: 0 sourceStart：数字, 可选, 默认: 0 sourceEnd：数字, 可选, 默认: buffer.length var buf1 = Buffer.from(\u0026#39;abcdefghijkl\u0026#39;); var buf2 = Buffer.from(\u0026#39;1234\u0026#39;); //将 buf2 插入到 buf1 指定位置上 buf2.copy(buf1, 2); console.log(buf1.toString()); // ab1234ghijkl 缓冲区裁剪 # buf.slice([start[, end]]) start：数字, 可选, 默认: 0 end：数字, 可选, 默认: buffer.length 返回一个新的缓冲区，它和旧缓冲区指向同一块内存，但是从索引 start 到 end 的位置剪切。\nvar buffer1 = Buffer.from(\u0026#39;hello\u0026#39;); // 剪切缓冲区 var buffer2 = buffer1.slice(0,2); console.log(\u0026#34;buffer2 content: \u0026#34; + buffer2.toString()); // he 缓冲区长度 # buf.length; ","date":"2025-05-27","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/65df2b3b/33d8f2d8/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eBuffer简介 \n    \u003cdiv id=\"buffer简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#buffer%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eJavaScript 语言自身只有字符串数据类型，没有二进制数据类型。\u003c/p\u003e","title":"2、Buffer","type":"posts"},{"content":"在开发命令行工具的时候，会遇到一些耗时的操作，比如下载一个大文件，这时候如果能给用户一个进度提示，会显得比较友好，因为用户知道自己还要等多久可以，就可以下载好这个文件。\n在Go语言中，可以使用第三方库schollz/progressbar来在终端上实现进度条以处理数据。这个库可以帮助您更容易地在终端中显示进度信息。\n下载 # go get -u github.com/schollz/progressbar/v3 简单使用 # 只需要通过Default函数生成一个bar，然后通过它的Add方法增加进度即可。留意这里的100代表进度的最大值。\npackage main import ( \u0026#34;github.com/schollz/progressbar/v3\u0026#34; \u0026#34;time\u0026#34; ) func main() { bar := progressbar.Default(100) for i := 0; i \u0026lt; 100; i++ { bar.Add(1) time.Sleep(40 * time.Millisecond) } } 监听 IO 操作 # 下载 Golang Mac 安装包并显示进度，代码如下所示：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/schollz/progressbar/v3\u0026#34; \u0026#34;io\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; ) func main() { golangPkg := \u0026#34;go1.16.4.darwin-amd64.pkg\u0026#34; url := fmt.Sprintf(\u0026#34;https://golang.google.cn/dl/%s\u0026#34;, golangPkg) request, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { log.Fatalln(err) } client := http.DefaultClient resp, err := client.Do(request) if err != nil { log.Fatalln(err) } file, err := os.OpenFile(golangPkg, os.O_CREATE|os.O_WRONLY, os.ModePerm) if err != nil { log.Fatalln(err) } pb := progressbar.DefaultBytes(resp.ContentLength, \u0026#34;正在下载\u0026#34;) io.Copy(io.MultiWriter(file, pb), resp.Body) } 以上代码的进度条使用到的就是基于字节大小计算的进度，用到了 progressbar.DefaultBytes 函数。\n并且因为ProcessBar实现了io.Writer接口，所以可以被io.MultiWriter使用，这样在使用io.Copy下载文件的时候，就可以根据已经下载的字节、总的字节数，计算出进度。\n在上面的示例中，我们是通过resp.ContentLength来获取文件的大小的，但是有时候，我们无法获取要下载文件的大小，这时候就可以使用-1代表，那么progressbar就会显示一个未知长度的进度条。\n","date":"2025-05-22","externalUrl":null,"permalink":"/posts/2d3150b2/a56b86e6/d27b85e5/","section":"文章","summary":"\u003cp\u003e在开发命令行工具的时候，会遇到一些耗时的操作，比如下载一个大文件，这时候如果能给用户一个进度提示，会显得比较友好，因为用户知道自己还要等多久可以，就可以下载好这个文件。\u003c/p\u003e","title":"2、processbar","type":"posts"},{"content":" Gin路由系统 # Gin的路由系统基于httprouter，经过优化后的性能是原生Go HTTP路由的40倍，是Echo等其他框架的2倍以上。Gin路由系统的主要特点包括：\n基于Trie树（前缀树/字典树）实现的高效路由匹配 支持RESTful API设计 灵活的路由分组机制 支持路径参数和通配符 路由注册方式 # Gin提供了多种路由注册方式，对应HTTP的各种方法：\n// GET请求路由 router.GET(\u0026#34;/path\u0026#34;, HandlerFunc) // POST请求路由 router.POST(\u0026#34;/path\u0026#34;, HandlerFunc) // PUT请求路由 router.PUT(\u0026#34;/path\u0026#34;, HandlerFunc) // DELETE请求路由 router.DELETE(\u0026#34;/path\u0026#34;, HandlerFunc) // 任意HTTP方法 router.Any(\u0026#34;/path\u0026#34;, HandlerFunc) // 自定义HTTP方法 router.Handle(\u0026#34;OPTIONS\u0026#34;, \u0026#34;/path\u0026#34;, HandlerFunc) 路径参数 # Gin支持在路由路径中定义参数，使用:前缀标识：\nrouter.GET(\u0026#34;/user/:id\u0026#34;, func(c *gin.Context) { id := c.Param(\u0026#34;id\u0026#34;) c.JSON(200, gin.H{\u0026#34;id\u0026#34;: id}) }) 通配符路由 # Gin支持通配符路由，使用*来匹配任意内容：\n// 匹配 /user/john/send // 匹配 /user/john/123/send router.GET(\u0026#34;/user/:name/*action\u0026#34;, func(c *gin.Context) { name := c.Param(\u0026#34;name\u0026#34;) action := c.Param(\u0026#34;action\u0026#34;) c.String(200, \u0026#34;name: %s, action: %s\u0026#34;, name, action) }) 路由分组 # 路由分组是将相关的路由按照功能、资源类型或访问权限等因素组织在一起的一种方式。Gin的路由分组功能允许开发者：\n为一组相关的路由定义共同的URL前缀 为一组路由应用相同的中间件 将路由组织成层次结构，提高代码的可读性和可维护性 路由分组在底层实现上，每个分组都是一个RouterGroup实例，它包含了前缀信息、中间件列表以及对父分组和引擎的引用，使Gin能够构建出高效的路由树。\n路由分组的基本语法 # 在Gin中创建路由分组的基本语法如下：\n// 创建一个路由分组 group := router.Group(\u0026#34;/prefix\u0026#34;) // 在分组上定义路由 group.GET(\u0026#34;/path\u0026#34;, handlerFunc) group.POST(\u0026#34;/another\u0026#34;, anotherHandlerFunc) 也可以使用大括号创建更清晰的分组结构：\nuserGroup := router.Group(\u0026#34;/users\u0026#34;) { userGroup.GET(\u0026#34;\u0026#34;, getAllUsers) userGroup.GET(\u0026#34;/:id\u0026#34;, getUserByID) userGroup.POST(\u0026#34;\u0026#34;, createUser) userGroup.PUT(\u0026#34;/:id\u0026#34;, updateUser) userGroup.DELETE(\u0026#34;/:id\u0026#34;, deleteUser) } 中间件继承 # 子分组会继承父分组的中间件，同时也可以添加自己的中间件：\n// 主分组及其中间件 api := router.Group(\u0026#34;/api\u0026#34;) api.Use(loggerMiddleware()) // 子分组继承父分组中间件，并添加额外中间件 authorized := api.Group(\u0026#34;/auth\u0026#34;) authorized.Use(authMiddleware()) // 更深层嵌套，继承所有上层中间件，并添加自己的中间件 admin := authorized.Group(\u0026#34;/admin\u0026#34;) admin.Use(adminMiddleware()) 在这个例子中，对/api/auth/admin路径的访问将会依次通过loggerMiddleware、authMiddleware和adminMiddleware。\n实用技巧 # 高效管理大型项目中的路由 # 路由模块化 # 在大型项目中，应该将路由定义从主文件中分离出来，实现模块化。\n1、编写各模块对应的业务代码，并暴露出一个方法用来注册路由，例如 user 相关业务：\nroutes/user.go\npackage routes import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;net/http\u0026#34; ) // SetupUserRoutes 注册\tuser 相关路由 func SetupUserRoutes(engine *gin.Engine) { group := engine.Group(\u0026#34;/user\u0026#34;) group.GET(\u0026#34;/list\u0026#34;, func(context *gin.Context) { context.JSON(http.StatusOK, []gin.H{ {\u0026#34;id\u0026#34;: 1, \u0026#34;name\u0026#34;: \u0026#34;lucy\u0026#34;}, {\u0026#34;id\u0026#34;: 2, \u0026#34;name\u0026#34;: \u0026#34;tom\u0026#34;}, }) }) group.POST(\u0026#34;/update\u0026#34;, func(context *gin.Context) { fmt.Println(\u0026#34;TODO user/update\u0026#34;) context.JSON(http.StatusOK, gin.H{ \u0026#34;status\u0026#34;: \u0026#34;OK\u0026#34;, }) }) } 2、编写一个统一管理路由的方法，用于统一注册路由\nroutes/routes.go\npackage routes import \u0026#34;github.com/gin-gonic/gin\u0026#34; // SetupRoutes 注册路由 func SetupRoutes(engine *gin.Engine) { SetupUserRoutes(engine) } 3、在主文件中注册路由\nmain.go\npackage main import ( \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;ygang.top/gin-demo/routes\u0026#34; ) func main() { engine := gin.Default() // 注册路由 routes.SetupRoutes(engine) engine.Run() } 使用结构体统一管理路由配置 # // 路由配置结构体 type RouteInfo struct { Method string Path string Handler gin.HandlerFunc Middlewares []gin.HandlerFunc } // 注册单个路由 func registerRoute(rg *gin.RouterGroup, route RouteInfo) { handlers := append(route.Middlewares, route.Handler) rg.Handle(route.Method, route.Path, handlers...) } // 注册一组路由 func registerRoutes(rg *gin.RouterGroup, routes []RouteInfo) { for _, route := range routes { registerRoute(rg, route) } } // 使用示例 func setupUserRoutes(rg *gin.RouterGroup) { users := rg.Group(\u0026#34;/users\u0026#34;) userRoutes := []RouteInfo{ { Method: \u0026#34;GET\u0026#34;, Path: \u0026#34;\u0026#34;, Handler: userController.GetAllUsers, }, { Method: \u0026#34;GET\u0026#34;, Path: \u0026#34;/:id\u0026#34;, Handler: userController.GetUser, }, { Method: \u0026#34;POST\u0026#34;, Path: \u0026#34;\u0026#34;, Handler: userController.CreateUser, Middlewares: []gin.HandlerFunc{validateUserMiddleware()}, }, } registerRoutes(users, userRoutes) } 中间件作用域控制 # 中间件可能会带来性能开销，因此应该只在必要的路由上应用特定中间件。全局中间件应限制在日志、恢复等必要功能上。\n中间件应该应用在尽可能小的作用域内，避免过度使用全局中间件：\n// 不推荐：全局应用验证中间件 router.Use(ValidationMiddleware()) // 推荐：仅在需要的路由组上应用验证中间件 postGroup := apiGroup.Group(\u0026#34;/posts\u0026#34;) postGroup.Use(ValidationMiddleware()) ","date":"2025-05-20","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/1bd46fa5/e7149fc0/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eGin路由系统 \n    \u003cdiv id=\"gin路由系统\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#gin%e8%b7%af%e7%94%b1%e7%b3%bb%e7%bb%9f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eGin的路由系统基于httprouter，经过优化后的性能是原生Go HTTP路由的40倍，是Echo等其他框架的2倍以上。Gin路由系统的主要特点包括：\u003c/p\u003e","title":"2、路由","type":"posts"},{"content":"fmt包含有格式化I/O函数，类似于C语言的printf和scanf。格式字符串的规则来源于C，但更简单一些\n标准输出 # Print # func Print(a ...interface{}) (n int, err error) Print采用默认格式将其参数格式化并写入标准输出。如果两个相邻的参数都不是字符串，会在它们的输出之间添加空格，返回写入的字节数和遇到的任何错误。 Println # func Println(a ...interface{}) (n int, err error) Println采用默认格式将其参数格式化并写入标准输出。总是会在相邻参数的输出之间添加空格并在输出结束后添加换行符，返回写入的字节数和遇到的任何错误。 Printf # func Printf(format string, a ...interface{}) (n int, err error) Printf根据format参数生成格式化的字符串并写入标准输出，返回写入的字节数和遇到的任何错误。 Fprint # func Fprint(w io.Writer, a ...interface{}) (n int, err error) func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) func Fprintln(w io.Writer, a ...interface{}) (n int, err error) Fprint系列函数会将内容输出到一个io.Writer接口类型的变量w中，我们通常用这个函数往文件中写入内容。 Sprint # func Sprint(a ...interface{}) string func Sprintf(format string, a ...interface{}) string func Sprintln(a ...interface{}) string Sprint系列函数会把传入的数据生成并返回一个字符串。 Errorf # func Errorf(format string, a ...interface{}) error Errorf函数根据format参数生成格式化字符串并返回一个包含该字符串的错误。 标准输入 # Scan # func Scan(a ...interface{}) (n int, err error) 功能：读取由空格分隔的输入数据，并将数据存储到提供的参数中。它不需要格式化字符串，而是依次将输入的值赋给传入的变量。 输入终止条件：输入以空格或换行符为分隔符，读取的值必须与传入的参数数量匹配。 package main import ( \u0026#34;fmt\u0026#34; ) func main() { var a string var b string //此处必须使用接收参数的变量指针 fmt.Scan(\u0026amp;a, \u0026amp;b) fmt.Print(a, b) } Scanf # func Scanf（format string, a ...interface{}）(n int, err error) 功能：根据格式化字符串 format 从标准输入读取数据，并将数据存储到提供的参数中。 输入终止条件：Scanf 读取输入时，会忽略空白字符，并且根据格式化字符串解析输入内容。输入可以包含换行符，但解析会按照格式字符串中的要求进行。 Scanln # func Scanln(a ...interface{}) (n int, err error) 功能：与 fmt.Scan 类似，但会在读取完所有指定的参数后检查是否有多余的输入。它要求读取的最后一个输入参数必须位于行的末尾，否则会返回一个错误。 输入终止条件：读取到换行符结束，并要求读取的参数数量与传入的变量数量匹配。 package main import \u0026#34;fmt\u0026#34; func main() { fmt.Println(\u0026#34;Please Input:\u0026#34;) var data string fmt.Scanln(\u0026amp;data) fmt.Printf(\u0026#34;User Input: %s\\n\u0026#34;, data) } IO输入输出 # 这些函数是针对io.Writer、io.Reader进行输入和输出\n输出 # func Fprint(w io.Writer, a ...interface{}) (n int, err error) func Fprintln(w io.Writer, a ...interface{}) (n int, err error) func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) 输入 # func Fscan(r io.Reader, a ...interface{}) (n int, err error) func Fscanln(r io.Reader, a ...interface{}) (n int, err error) func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error) string输入输出 # 这些函数是针对string进行输入和输出\n输出 # func Sprint(a ...interface{}) string func Sprintln(a ...interface{}) string func Sprintf(format string, a ...interface{}) string 输入 # func Sscan(str string, a ...interface{}) (n int, err error) func Sscanln(str string, a ...interface{}) (n int, err error) func Sscanf(str string, format string, a ...interface{}) (n int, err error) 格式化占位符 # 通用 # 占位符 说明 例子 结果 %v 相应值的默认格式 Printf(\u0026quot;%v\u0026quot;,person) {lucy 18} %+v 类似%v，但输出结构体时会添加字段名 Printf(\u0026quot;%+v\u0026quot;,person) {name:lucy age:18} %#v 相应值的Go语法表示 Printf(\u0026quot;#v\u0026quot;,person) main.Person{name:\u0026quot;lucy\u0026quot;, age:18} %T 相应值的类型的Go语法表示 Printf(\u0026quot;%T\u0026quot;,person) main.Person %% 字面上的百分号，并非值的占位符 Printf(\u0026quot;%%\u0026quot;) % 布尔 # 占位符 说明 例子 结果 %t 单词true或false Printf(\u0026quot;%t\u0026quot;,true) true 整数 # 占位符 说明 例子 结果 %b 二进制表示 Printf(\u0026quot;%b\u0026quot;,5) 101 %c 该值对应的unicode码值 Printf(\u0026quot;%c\u0026quot;,0x4E2d) 中 %d 十进制表示 Printf(\u0026quot;%d\u0026quot;,0x12) 18 %02d 两位整数 Printf(\u0026quot;%02d\u0026quot;,8) 08 %o 八进制表示 Printf(\u0026quot;%o\u0026quot;,10) 12 %q 单引号围绕的字符字面值，由Go语法安全的转译 Printf(\u0026quot;%q\u0026quot;,0x4E2d) 中 %x 十六进制表示，字母形式为小写a-f Printf(\u0026quot;%x\u0026quot;,13) d %X 十六进制表示，字母形式为大写A-F Printf(\u0026quot;%X\u0026quot;,13) D %U 表示为Unicode格式：U+1234，等价于U+%04X Printf(\u0026quot;%U\u0026quot;,0x4E2d) U+4E2D 浮点数和复数 # 占位符 说明 例子 结果 %e 科学计数法，如-1234.456e+78 Printf(\u0026quot;%e\u0026quot;,10.20) 1.020000e+01 %E 科学计数法，如-1234.456E+78 Printf(\u0026quot;%E\u0026quot;,10.20) 1.020000E+01 %f 有小数部分但无指数部分 Printf(\u0026quot;%.2f\u0026quot;, 3.1415926) 3.14 %9f 宽度9，默认精度 Printf(\u0026quot;%9f\u0026quot;, 88.88) 88.880000 %.2f 默认宽度，精度2 Printf(\u0026quot;%.2f\u0026quot;, 88.8888) 88.88 %g 根据实际情况采用%e或%f格式（以获得更简洁、准确的输出） Printf(\u0026quot;%g\u0026quot;,10.20) 10.2 %G 根据实际情况采用%E或%F格式（以获得更简洁、准确的输出） Printf(\u0026quot;%G\u0026quot;,10.20) 10.2+2i 字符串和字节数组 # 占位符 说明 例子 结果 %s 输出字符串表示(string类型或[]byte) Printf(\u0026quot;%s\u0026quot;,[]byte(\u0026quot;Go语言\u0026quot;)) Go语言 %q 双引号围绕的字符串，由Go语法安全的转译 Printf(\u0026quot;%q\u0026quot;,\u0026quot;Go语言\u0026quot;) \u0026quot;Go语言\u0026quot; %x 十六进制，小写字母，每字节两个字符 Printf(\u0026quot;%x\u0026quot;,\u0026quot;golang\u0026quot;) 676f6c616e67 %X 十六进制，大写字母，每字节两个字符 Printf(\u0026quot;%X\u0026quot;,\u0026quot;golang\u0026quot;) 676F6C616E67 指针 # 占位符 说明 例子 结果 %p 十六进制表示，前缀 0x Printf(\u0026quot;%p\u0026quot;,\u0026amp;person) 0xc0420341c0 %#p 十六进制表示，不带前缀 0x Printf(\u0026quot;%#p\u0026quot;,\u0026amp;person) c0420341c0 ","date":"2025-05-19","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/12d682f6/026050f0/","section":"文章","summary":"\u003cp\u003efmt包含有格式化I/O函数，类似于C语言的printf和scanf。格式字符串的规则来源于C，但更简单一些\u003c/p\u003e","title":"2、fmt","type":"posts"},{"content":"Go语言的net/http包是构建HTTP服务的核心，它提供了简洁而强大的API，使我们能够快速构建高性能的HTTP服务器。\nHTTP 服务器基础 # 创建最小HTTP服务器 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { // 注册处理函数 http.HandleFunc(\u0026#34;/\u0026#34;, func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, \u0026#34;你好，Go HTTP服务器！\u0026#34;) }) // 启动服务器 log.Println(\u0026#34;服务器启动在 :8080...\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } 运行这段代码，然后在浏览器中访问http://localhost:8080，你会看到\u0026quot;你好，Go HTTP服务器！\u0026ldquo;的消息。\nhttp.ListenAndServe函数是启动HTTP服务器的主要方式，它接收两个参数：\n监听的地址（如\u0026quot;:8080\u0026quot;表示所有网络接口的8080端口） 请求处理器（传递nil表示使用默认处理器） 处理器函数与处理器接口 # Go的HTTP服务器架构围绕两个核心概念构建：\n处理器函数(HandlerFunc)：一个签名为func(http.ResponseWriter, *http.Request)的函数 处理器(Handler)：任何实现了ServeHTTP(http.ResponseWriter, *http.Request)方法的类型 处理器对象的优势在于可以保持状态（如上例中的counter）和封装行为，而处理器函数则更简洁直观。\n两种方式的示例：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) // 处理器函数 func helloHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, \u0026#34;你好，世界！\u0026#34;) } // 处理器类型 type CounterHandler struct { counter int } // 实现ServeHTTP方法 func (h *CounterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.counter++ fmt.Fprintf(w, \u0026#34;你是第 %d 位访问者！\u0026#34;, h.counter) } func main() { // 注册处理器函数 http.HandleFunc(\u0026#34;/hello\u0026#34;, helloHandler) // 注册处理器 counter := \u0026amp;CounterHandler{} http.Handle(\u0026#34;/counter\u0026#34;, counter) log.Println(\u0026#34;服务器启动在 :8080...\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } 请求处理详解 # 当HTTP请求到达服务器时，http.Request结构包含了所有请求信息。\nfunc requestInfoHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, \u0026#34;===== 请求信息 =====\u0026#34;) fmt.Fprintf(w, \u0026#34;方法: %s\\n\u0026#34;, r.Method) fmt.Fprintf(w, \u0026#34;URL路径: %s\\n\u0026#34;, r.URL.Path) fmt.Fprintf(w, \u0026#34;协议版本: %s\\n\u0026#34;, r.Proto) // 查询参数 fmt.Fprintln(w, \u0026#34;\\n----- 查询参数 -----\u0026#34;) for key, values := range r.URL.Query() { for _, value := range values { fmt.Fprintf(w, \u0026#34;%s: %s\\n\u0026#34;, key, value) } } // 请求头 fmt.Fprintln(w, \u0026#34;\\n----- 请求头 -----\u0026#34;) for key, values := range r.Header { for _, value := range values { fmt.Fprintf(w, \u0026#34;%s: %s\\n\u0026#34;, key, value) } } // 远程地址 fmt.Fprintf(w, \u0026#34;\\n远程地址: %s\\n\u0026#34;, r.RemoteAddr) } 请求体读取 # 对于POST、PUT等包含请求体的请求，可以通过以下方式读取：\nfunc bodyHandler(w http.ResponseWriter, r *http.Request) { // 限制请求体大小，防止恶意请求 r.Body = http.MaxBytesReader(w, r.Body, 1024*1024) // 限制为1MB // 读取请求体 body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, \u0026#34;读取请求体失败\u0026#34;, http.StatusBadRequest) return } defer r.Body.Close() fmt.Fprintf(w, \u0026#34;收到请求体:\\n%s\u0026#34;, body) } 解析表单数据 # 对于表单提交，Go提供了便捷的解析方法：\nfunc formHandler(w http.ResponseWriter, r *http.Request) { // 解析表单数据（包括URL查询参数和表单字段） if err := r.ParseForm(); err != nil { http.Error(w, \u0026#34;解析表单失败\u0026#34;, http.StatusBadRequest) return } // 访问表单数据 fmt.Fprintln(w, \u0026#34;表单数据:\u0026#34;) for key, values := range r.Form { fmt.Fprintf(w, \u0026#34;%s: %s\\n\u0026#34;, key, strings.Join(values, \u0026#34;, \u0026#34;)) } // 仅访问表单字段（不包括URL查询参数） fmt.Fprintln(w, \u0026#34;\\n仅表单字段:\u0026#34;) for key, values := range r.PostForm { fmt.Fprintf(w, \u0026#34;%s: %s\\n\u0026#34;, key, strings.Join(values, \u0026#34;, \u0026#34;)) } } 解析多部分表单（multipart/form-data） # func uploadHandler(w http.ResponseWriter, r *http.Request) { // 设置文件上传最大尺寸为10MB r.ParseMultipartForm(10 \u0026lt;\u0026lt; 20) // 获取上传的文件 file, handler, err := r.FormFile(\u0026#34;file\u0026#34;) if err != nil { http.Error(w, \u0026#34;获取上传文件失败\u0026#34;, http.StatusBadRequest) return } defer file.Close() fmt.Fprintf(w, \u0026#34;上传的文件信息:\\n\u0026#34;) fmt.Fprintf(w, \u0026#34;文件名: %s\\n\u0026#34;, handler.Filename) fmt.Fprintf(w, \u0026#34;文件大小: %d字节\\n\u0026#34;, handler.Size) fmt.Fprintf(w, \u0026#34;MIME类型: %s\\n\u0026#34;, handler.Header.Get(\u0026#34;Content-Type\u0026#34;)) // 获取其他表单字段 if description := r.FormValue(\u0026#34;description\u0026#34;); description != \u0026#34;\u0026#34; { fmt.Fprintf(w, \u0026#34;文件描述: %s\\n\u0026#34;, description) } // 保存文件（在实际应用中，应检查文件类型和进行安全验证） tempFile, err := os.CreateTemp(\u0026#34;\u0026#34;, handler.Filename) if err != nil { http.Error(w, \u0026#34;创建临时文件失败\u0026#34;, http.StatusInternalServerError) return } defer tempFile.Close() _, err = io.Copy(tempFile, file) if err != nil { http.Error(w, \u0026#34;保存文件失败\u0026#34;, http.StatusInternalServerError) return } fmt.Fprintf(w, \u0026#34;文件已保存为: %s\\n\u0026#34;, tempFile.Name()) } 响应写入 # HTTP响应通过http.ResponseWriter接口构建。以下是常见的响应操作，有以下需要注意：\n必须在调用WriteHeader或写入响应体前设置响应头 如果不显式调用WriteHeader，第一次写入响应体时会隐式发送状态码200 func responseHandler(w http.ResponseWriter, r *http.Request) { // 设置状态码 w.WriteHeader(http.StatusOK) // 200 OK // 设置响应头 w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;text/html; charset=utf-8\u0026#34;) w.Header().Set(\u0026#34;X-Custom-Header\u0026#34;, \u0026#34;自定义值\u0026#34;) // 写入响应体 fmt.Fprintln(w, \u0026#34;\u0026lt;h1\u0026gt;响应示例\u0026lt;/h1\u0026gt;\u0026#34;) fmt.Fprintln(w, \u0026#34;\u0026lt;p\u0026gt;这是一个HTTP响应示例。\u0026lt;/p\u0026gt;\u0026#34;) } 不同类型的响应 # // 返回JSON响应 func jsonResponse(w http.ResponseWriter, r *http.Request) { // 创建响应数据 data := struct { Message string `json:\u0026#34;message\u0026#34;` Time string `json:\u0026#34;time\u0026#34;` Status int `json:\u0026#34;status\u0026#34;` }{ Message: \u0026#34;操作成功\u0026#34;, Time: time.Now().Format(time.RFC3339), Status: 1, } // 转换为JSON jsonData, err := json.Marshal(data) if err != nil { http.Error(w, \u0026#34;JSON编码失败\u0026#34;, http.StatusInternalServerError) return } // 设置头部和状态码 w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json; charset=utf-8\u0026#34;) w.WriteHeader(http.StatusOK) // 写入响应 w.Write(jsonData) } // 返回文件下载 func fileDownloadHandler(w http.ResponseWriter, r *http.Request) { file, err := os.Open(\u0026#34;example.pdf\u0026#34;) if err != nil { http.Error(w, \u0026#34;文件不存在\u0026#34;, http.StatusNotFound) return } defer file.Close() // 获取文件信息 fileInfo, err := file.Stat() if err != nil { http.Error(w, \u0026#34;无法获取文件信息\u0026#34;, http.StatusInternalServerError) return } // 设置响应头 w.Header().Set(\u0026#34;Content-Disposition\u0026#34;, fmt.Sprintf(\u0026#34;attachment; filename=%s\u0026#34;, fileInfo.Name())) w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/pdf\u0026#34;) w.Header().Set(\u0026#34;Content-Length\u0026#34;, fmt.Sprintf(\u0026#34;%d\u0026#34;, fileInfo.Size())) // 复制文件内容到响应 _, err = io.Copy(w, file) if err != nil { http.Error(w, \u0026#34;文件传输失败\u0026#34;, http.StatusInternalServerError) return } } // 重定向响应 func redirectHandler(w http.ResponseWriter, r *http.Request) { target := \u0026#34;/new-location\u0026#34; if r.URL.Query().Get(\u0026#34;type\u0026#34;) == \u0026#34;temporary\u0026#34; { // 临时重定向 (HTTP 302) http.Redirect(w, r, target, http.StatusFound) } else { // 永久重定向 (HTTP 301) http.Redirect(w, r, target, http.StatusMovedPermanently) } } 服务器生命周期 # 理解HTTP服务器的启动、运行和关闭过程对于构建可靠的应用至关重要：\npackage main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; \u0026#34;os/signal\u0026#34; \u0026#34;syscall\u0026#34; \u0026#34;time\u0026#34; ) func main() { // 创建路由 mux := http.NewServeMux() mux.HandleFunc(\u0026#34;/\u0026#34;, func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, \u0026#34;服务器运行中...\u0026#34;) }) // 配置服务器 server := \u0026amp;http.Server{ Addr: \u0026#34;:8080\u0026#34;, Handler: mux, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, } // 在goroutine中启动服务器 go func() { log.Println(\u0026#34;服务器启动在 :8080...\u0026#34;) if err := server.ListenAndServe(); err != nil \u0026amp;\u0026amp; err != http.ErrServerClosed { log.Fatalf(\u0026#34;服务器启动失败: %v\u0026#34;, err) } }() // 等待中断信号以优雅地关闭服务器 quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // 捕获CTRL+C \u0026lt;-quit log.Println(\u0026#34;关闭服务器中...\u0026#34;) // 创建一个5秒超时的上下文 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 优雅关闭：等待现有连接处理完毕 if err := server.Shutdown(ctx); err != nil { log.Fatalf(\u0026#34;服务器强制关闭: %v\u0026#34;, err) } log.Println(\u0026#34;服务器已优雅关闭\u0026#34;) } 请求声明周期 # HTTP请求在Go应用中的生命周期：\n监听连接：服务器在指定端口监听传入的HTTP连接 接收请求：接收客户端发送的HTTP请求 路由匹配：根据请求的URL和HTTP方法确定处理函数 中间件处理：请求按顺序通过一系列中间件 处理器处理：最终处理函数执行业务逻辑 生成响应：构建并返回HTTP响应 关闭连接：处理连接关闭（除非使用HTTP/1.1 Keep-Alive或HTTP/2） 易混淆的概念 # 在 Go 语言的 net/http 包中，http.Handle()、http.HandleFunc()、http.HandlerFunc() 和 http.Handler 是几个容易混淆的概念\nhttp.Handle() # 用于将一个实现了 http.Handler 接口的对象注册到指定的 URL 路径前缀上，当有匹配该路径前缀的 HTTP 请求到来时，会调用该对象的 ServeHTTP 方法进行处理。\npackage main import ( \u0026#34;net/http\u0026#34; ) // 自定义一个实现 http.Handler 接口的类型 type myHandler struct{} func (m myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte(\u0026#34;Handled by http.Handle()\u0026#34;)) } func main() { http.Handle(\u0026#34;/test\u0026#34;, myHandler{}) http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) } http.HandleFunc() # 用于将一个处理函数注册到指定的 URL 路径前缀上，当有匹配该路径前缀的 HTTP 请求到来时，会调用该处理函数进行处理。它是 http.Handle() 的便捷封装。\npackage main import ( \u0026#34;net/http\u0026#34; ) func myHandlerFunc(w http.ResponseWriter, r *http.Request) { w.Write([]byte(\u0026#34;Handled by http.HandleFunc()\u0026#34;)) } func main() { http.HandleFunc(\u0026#34;/test\u0026#34;, myHandlerFunc) http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) } http.HandlerFunc # 它是一个函数类型，实现了Handler接口。可用于将一个普通的处理函数（函数签名为 func(http.ResponseWriter, *http.Request)）转换为实现了 http.Handler 接口的对象。\ntype HandlerFunc func(http.ResponseWriter, *http.Request) func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { f(w, r) } http.Handler # 它是一个接口，定义了一个 ServeHTTP 方法，任何实现了该方法的类型都被认为实现了 http.Handler 接口。在 net/http 包中，很多地方都需要使用实现了该接口的对象来处理 HTTP 请求。\ntype Handler interface { ServeHTTP(ResponseWriter, *Request) } 路由与多路复用器 # 路由是Web服务器的核心功能，它决定了如何处理不同的请求路径。\nHandler接口 # type Handler interface { ServeHTTP(ResponseWriter, *Request) } 任何实现了ServeHTTP方法的类型都可以作为HTTP路由器，负责在ServeHTTP方法中分发请求。\ntype UserHandler struct { userService *UserService } func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { pathParts := strings.Split(r.URL.Path, \u0026#34;/\u0026#34;) if len(pathParts) \u0026lt; 3 { http.Error(w, \u0026#34;Invalid path\u0026#34;, http.StatusBadRequest) return } action := pathParts[2] switch action { case \u0026#34;list\u0026#34;: h.listUsers(w, r) case \u0026#34;create\u0026#34;: h.createUser(w, r) default: // 假设是用户ID h.getUser(w, r, action) } } func (h *UserHandler) listUsers(w http.ResponseWriter, r *http.Request) { // 实现列出用户的逻辑 } func (h *UserHandler) createUser(w http.ResponseWriter, r *http.Request) { // 实现创建用户的逻辑 } func (h *UserHandler) getUser(w http.ResponseWriter, r *http.Request, id string) { // 实现获取特定用户的逻辑 } func main() { userService := \u0026amp;UserService{} // 假设的用户服务 userHandler := \u0026amp;UserHandler{userService: userService} // 注册处理器 http.Handle(\u0026#34;/users/\u0026#34;, userHandler) http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) } 多路复用器 # 其实 Go 中的默认的HTTP请求多路复用器ServerMux同样也是实现了 Handle 接口。\n使用默认多路复用器 # Go的net/http包提供了一个全局默认的多路复用器(DefaultServeMux)，可以通过http.Handle和http.HandleFunc注册路由：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { // 注册不同路径的处理器函数 http.HandleFunc(\u0026#34;/\u0026#34;, homeHandler) http.HandleFunc(\u0026#34;/about\u0026#34;, aboutHandler) http.HandleFunc(\u0026#34;/api/data\u0026#34;, apiHandler) // 启动服务器 log.Println(\u0026#34;服务器启动在 :8080...\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) // nil参数表示使用默认多路复用器 } func homeHandler(w http.ResponseWriter, r *http.Request) { // 确保精确匹配根路径 if r.URL.Path != \u0026#34;/\u0026#34; { http.NotFound(w, r) return } fmt.Fprintln(w, \u0026#34;主页\u0026#34;) } func aboutHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, \u0026#34;关于我们\u0026#34;) } func apiHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) fmt.Fprintln(w, `{\u0026#34;message\u0026#34;: \u0026#34;这是API数据\u0026#34;}`) } 自定义多路复用器 # 在实际应用中，通常会创建自定义的ServeMux实例以获得更多控制：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { // 创建自定义多路复用器 mux := http.NewServeMux() // 注册路由 mux.HandleFunc(\u0026#34;/\u0026#34;, homeHandler) mux.HandleFunc(\u0026#34;/users/\u0026#34;, usersHandler) mux.HandleFunc(\u0026#34;/api/\u0026#34;, apiPrefixHandler) // 启动服务器，传入自定义多路复用器 log.Println(\u0026#34;服务器启动在 :8080...\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, mux)) } func homeHandler(w http.ResponseWriter, r *http.Request) { // 确保精确匹配根路径 if r.URL.Path != \u0026#34;/\u0026#34; { http.NotFound(w, r) return } fmt.Fprintln(w, \u0026#34;主页\u0026#34;) } func usersHandler(w http.ResponseWriter, r *http.Request) { // 提取用户ID（如果存在） path := r.URL.Path if path == \u0026#34;/users/\u0026#34; { fmt.Fprintln(w, \u0026#34;用户列表\u0026#34;) return } // 简单的路径解析示例 // 实际项目中可能会使用更复杂的路由库 userId := path[len(\u0026#34;/users/\u0026#34;):] fmt.Fprintf(w, \u0026#34;查看用户: %s\u0026#34;, userId) } func apiPrefixHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) fmt.Fprintf(w, `{\u0026#34;path\u0026#34;: \u0026#34;%s\u0026#34;, \u0026#34;method\u0026#34;: \u0026#34;%s\u0026#34;}`, r.URL.Path, r.Method) } ServeMux路由匹配规则 # ServeMux的路由匹配有两种模式：\n精确匹配：不以/结尾的路径，如/about 前缀匹配：以/结尾的路径，如/users/ // 精确匹配示例 mux.HandleFunc(\u0026#34;/products\u0026#34;, productsHandler) // 只匹配 /products，不匹配 /products/1 // 前缀匹配示例 mux.HandleFunc(\u0026#34;/products/\u0026#34;, productDetailsHandler) // 匹配 /products/ 和所有其子路径，如 /products/1 // 特殊情况：根路径 / 是所有路径的前缀 mux.HandleFunc(\u0026#34;/\u0026#34;, rootHandler) // 匹配所有未被其他处理器匹配的路径 当路径有多个匹配项时，ServeMux会选择最具体的匹配：\nfunc main() { mux := http.NewServeMux() // 根据匹配规则，会使用最具体的匹配 mux.HandleFunc(\u0026#34;/\u0026#34;, func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, \u0026#34;根路径处理器\u0026#34;) }) mux.HandleFunc(\u0026#34;/api/\u0026#34;, func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, \u0026#34;API前缀处理器\u0026#34;) }) mux.HandleFunc(\u0026#34;/api/users\u0026#34;, func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, \u0026#34;API用户精确处理器\u0026#34;) }) // 访问 /api/users 会匹配到第三个处理器 // 访问 /api/data 会匹配到第二个处理器 // 访问 /other 会匹配到第一个处理器 log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, mux)) } 简单路由器的实现 # 实现一个简单但功能更强大的路由器，支持HTTP方法匹配和路径参数：\npackage main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;regexp\u0026#34; ) // Route 表示单个路由 type Route struct { Method string Pattern *regexp.Regexp Handler http.HandlerFunc ParamNames []string } // Router 是我们的自定义路由器 type Router struct { routes []*Route } // NewRouter 创建新的路由器 func NewRouter() *Router { return \u0026amp;Router{routes: []*Route{}} } // parsePattern 解析路由模式，提取参数名 func parsePattern(pattern string) (*regexp.Regexp, []string) { // 匹配 {paramName} 格式的参数 paramRegex := regexp.MustCompile(`\\{([^/]+)\\}`) matches := paramRegex.FindAllStringSubmatch(pattern, -1) paramNames := make([]string, len(matches)) for i, match := range matches { paramNames[i] = match[1] } // 将 {paramName} 替换为正则捕获组 regexPattern := paramRegex.ReplaceAllString(pattern, `([^/]+)`) // 确保完全匹配 regexPattern = \u0026#34;^\u0026#34; + regexPattern + \u0026#34;$\u0026#34; return regexp.MustCompile(regexPattern), paramNames } // Handle 注册带方法的处理函数 func (r *Router) Handle(method, pattern string, handler http.HandlerFunc) { regex, paramNames := parsePattern(pattern) route := \u0026amp;Route{ Method: method, Pattern: regex, Handler: handler, ParamNames: paramNames, } r.routes = append(r.routes, route) } // GET 注册GET方法处理函数 func (r *Router) GET(pattern string, handler http.HandlerFunc) { r.Handle(http.MethodGet, pattern, handler) } // POST 注册POST方法处理函数 func (r *Router) POST(pattern string, handler http.HandlerFunc) { r.Handle(http.MethodPost, pattern, handler) } // PUT 注册PUT方法处理函数 func (r *Router) PUT(pattern string, handler http.HandlerFunc) { r.Handle(http.MethodPut, pattern, handler) } // DELETE 注册DELETE方法处理函数 func (r *Router) DELETE(pattern string, handler http.HandlerFunc) { r.Handle(http.MethodDelete, pattern, handler) } // extractParams 提取URL参数 func (r *Route) extractParams(path string) map[string]string { matches := r.Pattern.FindStringSubmatch(path) params := make(map[string]string) // 跳过第一个匹配（整个字符串） for i, name := range r.ParamNames { params[name] = matches[i+1] } return params } // ServeHTTP 实现http.Handler接口 func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { path := req.URL.Path method := req.Method for _, route := range r.routes { // 检查方法和路径是否匹配 if route.Method == method \u0026amp;\u0026amp; route.Pattern.MatchString(path) { // 提取参数 params := route.extractParams(path) // 将参数存储在请求上下文中 ctx := req.Context() for k, v := range params { ctx = context.WithValue(ctx, k, v) } // 使用更新的上下文调用处理函数 route.Handler(w, req.WithContext(ctx)) return } } // 找不到匹配的路由 http.NotFound(w, req) } // 使用示例 func main() { router := NewRouter() router.GET(\u0026#34;/hello\u0026#34;, func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, \u0026#34;Hello, World!\u0026#34;) }) router.GET(\u0026#34;/users/{id}\u0026#34;, func(w http.ResponseWriter, r *http.Request) { // 从上下文获取参数 userID := r.Context().Value(\u0026#34;id\u0026#34;).(string) fmt.Fprintf(w, \u0026#34;User ID: %s\u0026#34;, userID) }) router.POST(\u0026#34;/users\u0026#34;, func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, \u0026#34;Create User\u0026#34;) }) fmt.Println(\u0026#34;Server starting on port 8080...\u0026#34;) http.ListenAndServe(\u0026#34;:8080\u0026#34;, router) } ServeMux的限制和第三方路由 # 标准ServeMux有一些限制：\n不支持路径参数（如/users/:id） 不支持正则表达式匹配 不支持HTTP方法限制（如只匹配GET请求） 不支持中间件 在复杂项目中，常用的第三方路由库包括：\ngorilla/mux：https://github.com/gorilla/mux httprouter：https://github.com/julienschmidt/httprouter chi：https://github.com/go-chi/chi 以下是使用gorilla/mux的简单示例：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;github.com/gorilla/mux\u0026#34; ) func main() { // 创建一个gorilla/mux路由器 router := mux.NewRouter() // 使用路径参数 router.HandleFunc(\u0026#34;/users/{id:[0-9]+}\u0026#34;, func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) userID := vars[\u0026#34;id\u0026#34;] fmt.Fprintf(w, \u0026#34;用户ID: %s\u0026#34;, userID) }) // 限制HTTP方法 router.HandleFunc(\u0026#34;/api/products\u0026#34;, getProductsHandler).Methods(\u0026#34;GET\u0026#34;) router.HandleFunc(\u0026#34;/api/products\u0026#34;, createProductHandler).Methods(\u0026#34;POST\u0026#34;) // 匹配查询参数 router.HandleFunc(\u0026#34;/search\u0026#34;, searchHandler). Queries(\u0026#34;q\u0026#34;, \u0026#34;{query}\u0026#34;). Queries(\u0026#34;page\u0026#34;, \u0026#34;{page:[0-9]+}\u0026#34;) // 使用子路由 apiRouter := router.PathPrefix(\u0026#34;/api\u0026#34;).Subrouter() apiRouter.HandleFunc(\u0026#34;/users\u0026#34;, apiUsersHandler) // 启动服务器 log.Println(\u0026#34;服务器启动在 :8080...\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, router)) } func getProductsHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, \u0026#34;获取所有产品\u0026#34;) } func createProductHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, \u0026#34;创建新产品\u0026#34;) } func searchHandler(w http.ResponseWriter, r *http.Request) { query := r.URL.Query().Get(\u0026#34;q\u0026#34;) page := r.URL.Query().Get(\u0026#34;page\u0026#34;) fmt.Fprintf(w, \u0026#34;搜索: %s, 页码: %s\u0026#34;, query, page) } func apiUsersHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, \u0026#34;API用户列表\u0026#34;) } 中间件实现与使用 # 中间件基本概念 # 中间件本质上是一个函数，它接收一个处理器并返回一个新的处理器，在执行原始处理器前后添加额外的逻辑：\n// 中间件函数签名 type Middleware func(http.Handler) http.Handler 实现简单的中间件 # 日志中间件 # func LoggerMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { startTime := time.Now() // 创建自定义的ResponseWriter来捕获状态码 rw := NewResponseWriter(w) // 调用下一个处理函数 next(rw, r) // 计算请求处理时间 duration := time.Since(startTime) // 记录请求信息 log.Printf( \u0026#34;%s %s %d %s\u0026#34;, r.Method, r.URL.Path, rw.StatusCode, duration, ) } } // ResponseWriter的自定义包装器 type ResponseWriter struct { http.ResponseWriter StatusCode int } func NewResponseWriter(w http.ResponseWriter) *ResponseWriter { return \u0026amp;ResponseWriter{w, http.StatusOK} } func (rw *ResponseWriter) WriteHeader(code int) { rw.StatusCode = code rw.ResponseWriter.WriteHeader(code) } 认证中间件 # func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // 从请求获取认证令牌 token := r.Header.Get(\u0026#34;Authorization\u0026#34;) if token == \u0026#34;\u0026#34; { http.Error(w, \u0026#34;Unauthorized\u0026#34;, http.StatusUnauthorized) return } // 验证令牌（简化示例） if !isValidToken(token) { http.Error(w, \u0026#34;Invalid token\u0026#34;, http.StatusUnauthorized) return } // 提取用户信息并添加到请求上下文 userID, err := getUserIDFromToken(token) if err != nil { http.Error(w, \u0026#34;Invalid token\u0026#34;, http.StatusUnauthorized) return } ctx := context.WithValue(r.Context(), \u0026#34;userID\u0026#34;, userID) // 继续处理请求，使用更新的上下文 next(w, r.WithContext(ctx)) } } func isValidToken(token string) bool { // 实现令牌验证逻辑 return true } func getUserIDFromToken(token string) (string, error) { // 从令牌中提取用户ID return \u0026#34;user123\u0026#34;, nil } CORS中间件 # func CORSMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // 设置CORS头 w.Header().Set(\u0026#34;Access-Control-Allow-Origin\u0026#34;, \u0026#34;*\u0026#34;) // 生产环境中应该限制来源 w.Header().Set(\u0026#34;Access-Control-Allow-Methods\u0026#34;, \u0026#34;GET, POST, PUT, DELETE, OPTIONS\u0026#34;) w.Header().Set(\u0026#34;Access-Control-Allow-Headers\u0026#34;, \u0026#34;Content-Type, Authorization\u0026#34;) // 处理预检请求 if r.Method == http.MethodOptions { w.WriteHeader(http.StatusOK) return } // 继续处理请求 next(w, r) } } 中间件链 # 多个中间件可以组合成一个链，按顺序执行：\npackage main import ( \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { // 创建处理器 handler := http.HandlerFunc(finalHandler) // 应用中间件（从内到外） handler = authMiddleware(handler) handler = loggingMiddleware(handler) handler = recoveryMiddleware(handler) // 启动服务器 log.Println(\u0026#34;服务器启动在 :8080...\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, handler)) } func finalHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte(\u0026#34;Hello, 这是最终处理器！\u0026#34;)) } 使用第三方中间件库 # 在实际项目中，通常会使用更成熟的中间件库，如gorilla/handlers：\npackage main import ( \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; \u0026#34;github.com/gorilla/handlers\u0026#34; \u0026#34;github.com/gorilla/mux\u0026#34; ) func main() { r := mux.NewRouter() r.HandleFunc(\u0026#34;/\u0026#34;, homeHandler) r.HandleFunc(\u0026#34;/api\u0026#34;, apiHandler) // 应用gorilla/handlers中间件 loggedRouter := handlers.LoggingHandler(os.Stdout, r) corsRouter := handlers.CORS( handlers.AllowedOrigins([]string{\u0026#34;*\u0026#34;}), handlers.AllowedMethods([]string{\u0026#34;GET\u0026#34;, \u0026#34;POST\u0026#34;, \u0026#34;PUT\u0026#34;, \u0026#34;DELETE\u0026#34;, \u0026#34;OPTIONS\u0026#34;}), handlers.AllowedHeaders([]string{\u0026#34;X-Requested-With\u0026#34;, \u0026#34;Content-Type\u0026#34;, \u0026#34;Authorization\u0026#34;}), )(loggedRouter) log.Println(\u0026#34;服务器启动在 :8080...\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, corsRouter)) } func homeHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte(\u0026#34;欢迎访问首页\u0026#34;)) } func apiHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) w.Write([]byte(`{\u0026#34;message\u0026#34;: \u0026#34;API响应\u0026#34;}`)) } 静态文件服务 # 提供静态文件（如HTML、CSS、JavaScript、图片等）是Web服务器的基本功能。\n使用http.FileServer # Go的http.FileServer提供了简单的静态文件服务功能：\npackage main import ( \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { // 创建文件服务器处理器 fs := http.FileServer(http.Dir(\u0026#34;./static\u0026#34;)) // 注册到根路径 http.Handle(\u0026#34;/\u0026#34;, fs) log.Println(\u0026#34;静态文件服务器启动在 :8080...\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } 提供特定目录下的文件 # http.StripPrefix函数很重要，它从请求URL中删除指定的前缀，使文件服务器能够正确找到文件。\n通常，我们希望将静态文件映射到特定URL路径：\npackage main import ( \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { // 创建多路复用器 mux := http.NewServeMux() // 注册API处理器 mux.HandleFunc(\u0026#34;/api/\u0026#34;, apiHandler) // 提供/static/目录下的文件，映射到/assets/ URL路径 fileServer := http.FileServer(http.Dir(\u0026#34;./static\u0026#34;)) mux.Handle(\u0026#34;/assets/\u0026#34;, http.StripPrefix(\u0026#34;/assets/\u0026#34;, fileServer)) // 提供网站图标 mux.HandleFunc(\u0026#34;/favicon.ico\u0026#34;, func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, \u0026#34;./static/favicon.ico\u0026#34;) }) // 主页处理器 mux.HandleFunc(\u0026#34;/\u0026#34;, homeHandler) log.Println(\u0026#34;服务器启动在 :8080...\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, mux)) } func apiHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) w.Write([]byte(`{\u0026#34;status\u0026#34;: \u0026#34;API运行中\u0026#34;}`)) } func homeHandler(w http.ResponseWriter, r *http.Request) { // 确保只处理根路径 if r.URL.Path != \u0026#34;/\u0026#34; { http.NotFound(w, r) return } http.ServeFile(w, r, \u0026#34;./static/index.html\u0026#34;) } 单文件服务 # 对于需要单独提供的文件，可以使用http.ServeFile：\nfunc downloadHandler(w http.ResponseWriter, r *http.Request) { // 获取文件名参数 filename := r.URL.Query().Get(\u0026#34;file\u0026#34;) if filename == \u0026#34;\u0026#34; { http.Error(w, \u0026#34;缺少文件名参数\u0026#34;, http.StatusBadRequest) return } // 安全检查：防止目录遍历攻击 if strings.Contains(filename, \u0026#34;..\u0026#34;) { http.Error(w, \u0026#34;无效的文件路径\u0026#34;, http.StatusBadRequest) return } // 构建文件路径 filepath := path.Join(\u0026#34;./downloads\u0026#34;, filename) // 检查文件是否存在 if _, err := os.Stat(filepath); os.IsNotExist(err) { http.NotFound(w, r) return } // 设置Content-Disposition头，使浏览器下载而不是显示文件 w.Header().Set(\u0026#34;Content-Disposition\u0026#34;, fmt.Sprintf(\u0026#34;attachment; filename=%s\u0026#34;, filename)) // 提供文件 http.ServeFile(w, r, filepath) } 静态文件服务的安全考虑 # 提供静态文件时需要注意几个安全问题：\n防止目录遍历攻击：验证文件路径，不允许包含..等路径操作符 限制可访问的文件类型：可以实现自定义的http.FileSystem接口来过滤文件 设置适当的缓存控制：通过HTTP头控制浏览器缓存行为 // 自定义文件系统，限制可访问的文件类型 type RestrictedFileSystem struct { fs http.FileSystem } func (rfs RestrictedFileSystem) Open(name string) (http.File, error) { f, err := rfs.fs.Open(name) if err != nil { return nil, err } // 获取文件信息 stat, err := f.Stat() if err != nil { f.Close() return nil, err } // 如果是目录，检查是否存在index.html if stat.IsDir() { index := path.Join(name, \u0026#34;index.html\u0026#34;) if _, err := rfs.fs.Open(index); err != nil { f.Close() return nil, os.ErrNotExist // 不允许列出目录内容 } } // 检查文件扩展名 if !stat.IsDir() { ext := strings.ToLower(path.Ext(name)) allowedExts := map[string]bool{ \u0026#34;.html\u0026#34;: true, \u0026#34;.css\u0026#34;: true, \u0026#34;.js\u0026#34;: true, \u0026#34;.jpg\u0026#34;: true, \u0026#34;.jpeg\u0026#34;: true, \u0026#34;.png\u0026#34;: true, \u0026#34;.gif\u0026#34;: true, \u0026#34;.svg\u0026#34;: true, \u0026#34;.ico\u0026#34;: true, } if !allowedExts[ext] { f.Close() return nil, os.ErrNotExist // 不允许访问不在白名单中的文件类型 } } return f, nil } // 使用自定义文件系统 func main() { dir := \u0026#34;./static\u0026#34; restrictedFS := RestrictedFileSystem{http.Dir(dir)} fileServer := http.FileServer(restrictedFS) // 添加缓存控制中间件 handler := addCacheControlMiddleware(fileServer) http.Handle(\u0026#34;/static/\u0026#34;, http.StripPrefix(\u0026#34;/static/\u0026#34;, handler)) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } // 缓存控制中间件 func addCacheControlMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 为静态资源设置缓存控制 ext := strings.ToLower(path.Ext(r.URL.Path)) switch ext { case \u0026#34;.css\u0026#34;, \u0026#34;.js\u0026#34;, \u0026#34;.jpg\u0026#34;, \u0026#34;.jpeg\u0026#34;, \u0026#34;.png\u0026#34;, \u0026#34;.gif\u0026#34;, \u0026#34;.ico\u0026#34;: // 这些资源可以缓存较长时间 w.Header().Set(\u0026#34;Cache-Control\u0026#34;, \u0026#34;public, max-age=86400\u0026#34;) // 24小时 default: // HTML等其他资源缓存较短时间 w.Header().Set(\u0026#34;Cache-Control\u0026#34;, \u0026#34;public, max-age=3600\u0026#34;) // 1小时 } next.ServeHTTP(w, r) }) } 使用嵌入式文件系统 # Go 1.16引入了嵌入式文件功能，允许将静态资源直接嵌入到可执行文件中：\npackage main import ( \u0026#34;embed\u0026#34; \u0026#34;io/fs\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;log\u0026#34; ) // 嵌入静态文件 //go:embed static/* var staticFiles embed.FS func main() { // 创建子文件系统，去除\u0026#34;static\u0026#34;前缀 staticFS, err := fs.Sub(staticFiles, \u0026#34;static\u0026#34;) if err != nil { log.Fatal(err) } // 创建文件服务器 http.Handle(\u0026#34;/static/\u0026#34;, http.StripPrefix(\u0026#34;/static/\u0026#34;, http.FileServer(http.FS(staticFS)))) // ... 其他路由和服务器启动代码 } ","date":"2025-05-19","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/8b430461/ff293ea0/","section":"文章","summary":"\u003cp\u003eGo语言的\u003ccode\u003enet/http\u003c/code\u003e包是构建HTTP服务的核心，它提供了简洁而强大的API，使我们能够快速构建高性能的HTTP服务器。\u003c/p\u003e","title":"2、http服务器","type":"posts"},{"content":" Go的开发工具链 # Go语言不仅仅是一门编程语言，更是一套完整的开发生态系统。其核心是一组命令行工具，统一通过go命令调用，这套工具链遵循以下设计理念：\n简单易用：所有工具都遵循一致的命令模式 开箱即用：安装Go后即可获得全部开发工具 标准统一：强制执行统一的代码格式和项目结构 自给自足：无需第三方构建工具即可完成完整开发流程 工具类别 核心命令 主要用途 构建相关 go build、 go install、 go run 编译、安装和运行Go程序 代码质量 go fmt、 go vet、go lint 格式化代码、检查常见错误 测试相关 go test、 go cover 运行测试、查看测试覆盖率 依赖管理 go mod、 go get 管理项目依赖和模块 文档工具 go doc、 godoc 查看和生成代码文档 编译与构建工具 # go build # go build是最常用的Go命令之一，用于编译Go源代码。\ngo build会忽略目录下以_或者.开头的go文件。\n# 编译当前目录下的Go程序 go build # 编译指定文件 go build hello.go # 编译指定包 go build github.com/user/project 常用选项 # # 指定输出文件名 go build -o app.exe main.go # 跨平台编译(Windows上编译Linux可执行文件) SET GOOS=linux SET GOARCH=amd64 go build -o app-linux main.go # 禁用优化和内联，用于调试 go build -gcflags=\u0026#34;-N -l\u0026#34; main.go # 使用-ldflags可以在编译时修改变量值，常用于版本号注入 go build -ldflags=\u0026#34;-X \u0026#39;main.Version=1.0.0\u0026#39;\u0026#34; main.go # 开启所有优化 go build -gcflags=\u0026#34;-m\u0026#34; main.go # 内联优化 # 开启编译器动态分析 go build -gcflags=\u0026#34;-m=2\u0026#34; main.go # 更详细的内联信息 # 发布构建(禁用调试信息、启用优化) go build -ldflags=\u0026#34;-s -w\u0026#34; main.go # -s: 去除符号表 # -w: 去除DWARF调试信息 Go支持通过构建标签（build tags）条件编译代码：\n//go:build linux || darwin // +build linux darwin package main // 此代码仅在Linux或MacOS上编译 使用标签构建\ngo build -tags=debug,mysql main.go 编译不同平台的可执行文件 # 执行go build命令前，修改环境变量可以设置编译平台\nWindows下使用set GOARCH=[arch]和set GOOS=[os]进行设置\nLinux和Mac下使用export GOARCH=[arch]和export GOOS=[os]进行设置\n常见平台参数如下\n平台 GOARCH GOOS Windows x86_64 amd64 windows Linux x86_64 amd64 linux MacOS intel amd64 darwin MacOS Apple Silicon arm64 darwin go install # go install不仅编译程序，还会将生成的可执行文件复制到$GOPATH的bin目录下，便于全局访问。\n# 安装当前目录的程序 go install # 安装特定包 go install github.com/user/tool@latest 从Go 1.16开始，可以直接从GitHub安装命令行工具：\n# 安装最新版本 go install github.com/go-delve/delve/cmd/dlv@latest # 安装特定版本 go install github.com/golang/mock/mockgen@v1.6.0 go run # 通常使用go run快速测试代码，它会编译并立即运行程序，但不产生可执行文件：\n# 运行单个文件 go run hello.go # 运行目录下所有文件 go run . # 运行特定包 go run github.com/user/app # 带命令行参数运行 go run main.go -config=dev.json go run 命令只能接受一个命令源码文件以及若干个库源码文件（必须同属于 main 包）作为文件参数，且不能接受测试源码文件。它在执行时会检查源码文件的类型。如果参数中有多个或者没有命令源码文件，那么 go run 命令就只会打印错误提示信息并退出，而不会继续执行。\n编译错误undefined解决 # 在 Go 语言中，go run main.go 和 go run *.go 都是用于运行 Go 程序的命令，但它们的执行方式和适用场景有所不同：\ngo run main.go\n行为：仅编译并运行 main.go 文件。\n特点：\n如果 main.go 依赖其他文件（例如同包下的其他 .go 文件），这些文件不会被自动编译，除非它们被 main.go 直接导入（通过 import 语句）。 如果依赖的文件未被显式导入，可能会导致编译错误（如 undefined 错误）。 适用场景：\n单文件程序（如简单的 main.go 独立文件）。 明确只需要运行 main.go 且其他文件通过 import 正确引用时。 go run *.go\n行为：编译并运行当前目录下所有 .go 文件（匹配通配符 *.go）。 特点： 会将所有 .go 文件作为一个整体编译（类似于 go build），因此能正确处理多文件依赖。 如果目录中存在多个 main 包（即多个文件包含 package main 和 func main()），会报错，因为一个程序只能有一个入口。 适用场景： 多文件项目（例如同一个包 package main 分布在多个文件中）。 需要确保所有相关文件都被编译时。 注意：因为 Windows 的命令行解释器（如 cmd.exe 或旧版 PowerShell）对通配符（*）的处理方式与 Unix/Linux 系统不同，所以在Windows中，应该使用go run ./或者go run .。\n代码质量工具 # go fmt # Go强制使用统一的代码格式，这是其社区一致性的关键要素。go fmt自动格式化代码，使其符合官方标准：\n# 格式化当前包 go fmt # 格式化特定文件 go fmt file.go # 递归格式化所有子包 go fmt ./... 实际上，当你执行go fmt时，其调用的是gofmt -l -w：\n# 手动使用gofmt可以提供更多选项 gofmt -s -w file.go # -s进行额外简化，-w直接写入文件 将go fmt集成到你的编辑器或IDE中，在保存时自动格式化代码。\ngo vet # go vet检查代码中常见的错误和可疑的构造，可以发现编译器无法检测到的问题：\n# 分析当前包 go vet # 分析特定包 go vet github.com/user/package # 分析所有子包 go vet ./... go vet可以检测的问题包括：\n无法到达的代码（unreachable code） 格式化字符串与参数不匹配 无效的结构体标签（struct tags） 未使用的结果（如错误） 可疑的赋值 可疑的方法签名 golint # 静态分析工具，检查代码\n# 安装 go install golang.org/x/lint/golint@latest # 使用 golint ./... 除了官方工具，还有更高级的静态分析工具staticcheck\n# 安装 go install honnef.co/go/tools/cmd/staticcheck@latest # 使用 staticcheck ./... go fix # 当Go的API发生变化时，go fix可以自动更新代码以适应新版本：\ngo fix ./... 其他工具 # go doc # Go强调代码即文档的概念，go doc命令可以查看任何包或函数的文档：\n# 查看包的文档 go doc fmt # 查看特定函数的文档 go doc fmt.Println # 查看完整文档（包括内部实现） go doc -all fmt godoc # 生成HTML文档网站：\n# 安装godoc go install golang.org/x/tools/cmd/godoc@latest # 启动本地文档服务器 godoc -http=:6060 go generate # go generate执行源文件中特定注释里的命令，用于自动生成代码：\n// 在源文件中添加生成注释 //go:generate mockgen -source=repository.go -destination=mock_repository.go 然后运行：\ngo generate ./... 常见用途：\n生成模拟对象 将资源文件嵌入二进制 生成协议缓冲区代码 生成静态资源绑定 go tool pprof # Go提供了强大的性能分析工具：\n# 运行CPU分析 go test -cpuprofile=cpu.prof -bench=. # 分析CPU使用情况 go tool pprof cpu.prof # 内存分析 go test -memprofile=mem.prof -bench=. # 生成可视化报告 go tool pprof -http=:8080 cpu.prof 在pprof交互式控制台中，可以使用以下命令：\ntop：显示最消耗资源的函数 list functionName：查看特定函数的代码和性能数据 web：在浏览器中查看图形化调用图 go env # 环境变量管理\n# 查看所有环境变量 go env # 查看特定环境变量 go env GOPATH # 设置环境变量 go env -w GOPROXY=https://goproxy.cn,direct # 取消设置 go env -w GOPROXY= Go编译过程 # 编译基础知识 # 抽象语法树 # 在计算机科学中，抽象语法树（Abstract Syntax Tree，AST），或简称语法树（Syntax tree），是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构，树上的每个节点都表示源代码中的一种结构。\n之所以说语法是抽象的，是因为这里的语法并不会表示出真实语法中出现的每个细节。比如，嵌套括号被隐含在树的结构中，并没有以节点的形式呈现。而类似于 if else 这样的条件判断语句，可以使用带有两个分支的节点来表示。\n以算术表达式1+3*(4-1)+2为例，可以解析出的抽象语法树如下图所示：\n抽象语法树可以应用在很多领域，比如浏览器，智能编辑器，编译器。\n静态单赋值 # 在编译器设计中，静态单赋值形式（static single assignment form，通常简写为 SSA form 或是 SSA）是中介码（IR，intermediate representation）的属性，它要求每个变量只分配一次，并且变量需要在使用之前定义。在实践中我们通常会用添加下标的方式实现每个变量只能被赋值一次的特性，这里以下面的代码举一个简单的例子：\nx := 1 x := 2 y := x 从上面的描述所知，第一行赋值行为是不需要的，因为 x 在第二行被二度赋值并在第三行被使用，在 SSA 下，将会变成下列的形式：\nx1 := 1 x2 := 2 y1 := x2 从使用 SSA 的中间代码我们就可以非常清晰地看出变量 y1 的值和 x1 是完全没有任何关系的，所以在机器码生成时其实就可以省略第一步，这样就能减少需要执行的指令来优化这一段代码。\n在中间代码中使用 SSA 的特性能够为整个程序实现以下的优化：\n常数传播（constant propagation） 值域传播（value range propagation） 稀疏有条件的常数传播（sparse conditional constant propagation） 消除无用的程式码（dead code elimination） 全域数值编号（global value numbering） 消除部分的冗余（partial redundancy elimination） 强度折减（strength reduction） 寄存器分配（register allocation） 因为 SSA 的主要作用就是代码的优化，所以是编译器后端（主要负责目标代码的优化和生成）的一部分。\n指令集架构 # 指令集架构（Instruction Set Architecture，简称 ISA），又称指令集或指令集体系，是计算机体系结构中与程序设计有关的部分，包含了基本数据类型，指令集，寄存器，寻址模式，存储体系，中断，异常处理以及外部 I/O。指令集架构包含一系列的 opcode 即操作码（机器语言），以及由特定处理器执行的基本命令。\n指令集架构常见种类如下：\n复杂指令集运算（Complex Instruction Set Computing，简称 CISC）； 精简指令集运算（Reduced Instruction Set Computing，简称 RISC）； 显式并行指令集运算（Explicitly Parallel Instruction Computing，简称 EPIC）； 超长指令字指令集运算（VLIW）。 不同的处理器（CPU）使用了大不相同的机器语言，所以我们的程序想要在不同的机器上运行，就需要将源代码根据架构编译成不同的机器语言。\ngo编译原理 # Go语言编译器的源代码在 cmd/compile 目录中，目录下的文件共同构成了Go语言的编译器，编译器的前端一般承担着词法分析、语法分析、类型检查和中间代码生成几部分工作，而编译器后端主要负责目标代码的生成和优化，也就是将中间代码翻译成目标机器能够运行的机器码。\nGo的编译器在逻辑上可以被分成四个阶段：词法与语法分析、类型检查和 AST 转换、通用 SSA 生成和最后的机器代码生成。\n词法与语法分析 # 所有的编译过程其实都是从解析代码的源文件开始的，词法分析的作用就是解析源代码文件，它将文件中的字符串序列转换成 Token 序列，方便后面的处理和解析，我们一般会把执行词法分析的程序称为词法解析器（lexer）。\n而语法分析的输入就是词法分析器输出的 Token 序列，这些序列会按照顺序被语法分析器进行解析，语法的解析过程就是将词法分析生成的 Token 按照语言定义好的文法（Grammar）自下而上或者自上而下的进行规约，每一个 Go 的源代码文件最终会被归纳成一个 SourceFile 结构：\nSourceFile = PackageClause \u0026#34;;\u0026#34; { ImportDecl \u0026#34;;\u0026#34; } { TopLevelDecl \u0026#34;;\u0026#34; } 标准的 Golang 语法解析器使用的就是 LALR(1) 的文法，语法解析的结果其实就是抽象语法树（AST），每一个 AST 都对应着一个单独的Go语言文件，这个抽象语法树中包括当前文件属于的包名、定义的常量、结构体和函数等。\n如果在语法解析的过程中发生了任何语法错误，都会被语法解析器发现并将消息打印到标准输出上，整个编译过程也会随着错误的出现而被中止。\n类型检查 # 当拿到一组文件的抽象语法树 AST 之后，Go语言的编译器会对语法树中定义和使用的类型进行检查，类型检查分别会按照顺序对不同类型的节点进行验证，按照以下的顺序进行处理：\n常量、类型和函数名及类型； 变量的赋值和初始化； 函数和闭包的主体； 哈希键值对的类型； 导入函数体； 外部的声明； 通过对每一棵抽象节点树的遍历，我们在每一个节点上都会对当前子树的类型进行验证保证当前节点上不会出现类型错误的问题，所有的类型错误和不匹配都会在这一个阶段被发现和暴露出来。\n类型检查的阶段不止会对树状结构的节点进行验证，同时也会对一些内建的函数进行展开和改写，例如 make 关键字在这个阶段会根据子树的结构被替换成 makeslice 或者 makechan 等函数。\n其实类型检查不止对类型进行了验证工作，还对 AST 进行了改写以及处理Go语言内置的关键字，所以，这一过程在整个编译流程中是非常重要的，没有这个步骤很多关键字其实就没有办法工作。\n中间代码生成 # 当我们将源文件转换成了抽象语法树，对整个语法树的语法进行解析并进行类型检查之后，就可以认为当前文件中的代码基本上不存在无法编译或者语法错误的问题了，Go语言的编译器就会将输入的 AST 转换成中间代码。\nGo语言编译器的中间代码使用了 SSA(Static Single Assignment Form) 的特性，如果我们在中间代码生成的过程中使用这种特性，就能够比较容易的分析出代码中的无用变量和片段并对代码进行优化。\n在类型检查之后，就会通过一个名为 compileFunctions 的函数开始对整个Go语言项目中的全部函数进行编译，这些函数会在一个编译队列中等待几个后端工作协程的消费，这些 Goroutine 会将所有函数对应的 AST 转换成使用 SSA 特性的中间代码。\n机器码生成 # Go语言源代码的 cmd/compile/internal 目录中包含了非常多机器码生成相关的包，不同类型的 CPU 分别使用了不同的包进行生成 amd64、arm、arm64、mips、mips64、ppc64、s390x、x86 和 wasm，也就是说Go语言能够在几乎全部常见的 CPU 指令集类型上运行。\n编译器入口 # Go语言的编译器入口是 src/cmd/compile/internal/gc 包中的 main.go 文件，这个 600 多行的 Main 函数就是Go语言编译器的主程序，这个函数会先获取命令行传入的参数并更新编译的选项和配置，随后就会开始运行 parseFiles 函数对输入的所有文件进行词法与语法分析得到文件对应的抽象语法树：\nfunc Main(archInit func(*Arch)) { // ... lines := parseFiles(flag.Args()) } 接下来就会分九个阶段对抽象语法树进行更新和编译，整个过程会经历类型检查、SSA 中间代码生成以及机器码生成三个部分：\n检查常量、类型和函数的类型； 处理变量的赋值； 对函数的主体进行类型检查； 决定如何捕获变量； 检查内联函数的类型； 进行逃逸分析； 将闭包的主体转换成引用的捕获变量； 编译顶层函数； 检查外部依赖的声明； 重新回到词法和语法分析后的具体流程，在这里编译器会对生成语法树中的节点执行类型检查，除了常量、类型和函数这些顶层声明之外，它还会对变量的赋值语句、函数主体等结构进行检查：\nfor i := 0; i \u0026lt; len(xtop); i++ { n := xtop[i] if op := n.Op; op != ODCL \u0026amp;\u0026amp; op != OAS \u0026amp;\u0026amp; op != OAS2 \u0026amp;\u0026amp; (op != ODCLTYPE || !n.Left.Name.Param.Alias) { xtop[i] = typecheck(n, ctxStmt) } } for i := 0; i \u0026lt; len(xtop); i++ { n := xtop[i] if op := n.Op; op == ODCL || op == OAS || op == OAS2 || op == ODCLTYPE \u0026amp;\u0026amp; n.Left.Name.Param.Alias { xtop[i] = typecheck(n, ctxStmt) } } for i := 0; i \u0026lt; len(xtop); i++ { n := xtop[i] if op := n.Op; op == ODCLFUNC || op == OCLOSURE { typecheckslice(Curfn.Nbody.Slice(), ctxStmt) } } checkMapKeys() for _, n := range xtop { if n.Op == ODCLFUNC \u0026amp;\u0026amp; n.Func.Closure != nil { capturevars(n) } } escapes(xtop) for _, n := range xtop { if n.Op == ODCLFUNC \u0026amp;\u0026amp; n.Func.Closure != nil { transformclosure(n) } } 类型检查会对传入节点的子节点进行遍历，这个过程会对 make 等关键字进行展开和重写，类型检查结束之后并没有输出新的数据结构，只是改变了语法树中的一些节点，同时这个过程的结束也意味着源代码中已经不存在语法错误和类型错误，中间代码和机器码也都可以正常的生成了。\ninitssaconfig() peekitabs() for i := 0; i \u0026lt; len(xtop); i++ { n := xtop[i] if n.Op == ODCLFUNC { funccompile(n) } } compileFunctions() for i, n := range externdcl { if n.Op == ONAME { externdcl[i] = typecheck(externdcl[i], ctxExpr) } } checkMapKeys() 在主程序运行的最后，会将顶层的函数编译成中间代码并根据目标的 CPU 架构生成机器码，不过这里其实也可能会再次对外部依赖进行类型检查以验证正确性。\n源码文件分类 # 命令源码文件 # 声明自己属于 main 代码包、包含无参数声明和结果声明的 main 函数。\n命令源码文件被安装以后，GOPATH 如果只有一个工作区，那么相应的可执行文件会被存放当前工作区的 bin 文件夹下；如果有多个工作区，就会安装到 GOBIN 指向的目录下。\n命令源码文件是 Go 程序的入口。\n同一个代码包中最好也不要放多个命令源码文件。多个命令源码文件虽然可以分开单独 go run 运行起来，但是无法通过 go build 和 go install。\n库源码文件 # 库源码文件就是不具备命令源码文件上述两个特征的源码文件。存在于某个代码包中的普通的源码文件。\n库源码文件被安装后，相应的归档文件（.a 文件）会被存放到当前工作区的 pkg 的平台相关目录下。\n测试源码文件 # 名称以 _test.go 为后缀的代码文件，并且必须包含 Test 或者 Benchmark 名称前缀的函数。\n编译指令 # 在Go语言中，编译器指令是通过特殊格式的注释实现的，通常以 //go: 开头。这些指令用于指导编译器在编译过程中执行特定操作或优化。以下是Go中常用的编译器指令及其用途：\n//go:noinline # 强制编译器不要将函数内联到调用处。\n内联（Inlining）是编译器优化的一种，将小函数直接嵌入调用处以减少函数调用开销。 使用 //go:noinline 可以阻止这种优化，常用于调试（如保留函数调用栈）或性能分析（防止内联干扰基准测试）。 //go:noinline func DebugLog(msg string) { fmt.Println(\u0026#34;[DEBUG]\u0026#34;, msg) } 过度使用可能影响性能，仅在明确需要时使用。 //go:nosplit # 跳过函数的栈溢出检查（Stack Split Check）。\nGo 的每个 Goroutine 初始栈较小（2KB），运行时通过栈溢出检查动态扩展栈。 //go:nosplit 会禁用这一检查，常用于极低层代码（如运行时或调度器），确保函数在固定栈大小下运行。 //go:nosplit func atomicAdd(ptr *int32, delta int32) { // 汇编实现，无需栈操作 } 错误使用可能导致 栈溢出崩溃（如函数需要更多栈空间但未检查）。 通常与手写汇编或运行时内部函数配合使用。 //go:noescape # 向编译器保证函数的参数不会逃逸到堆（Heap）上。\n逃逸分析（Escape Analysis）是编译器确定变量能否在栈上分配的关键步骤。 //go:noescape 声明函数参数不会逃逸到堆，帮助编译器优化内存分配（如避免堆分配）。 //go:noescape func HashBytes(b []byte) uint64 仅适用于 函数签名中不包含未检查的指针（如 func(p *T) 可能不适用）。 若声明不实（实际发生逃逸），可能导致未定义行为。 //go:norace # 禁用当前函数的竞态检测（Race Detector）。\nGo 的竞态检测器（-race）会在运行时检测数据竞争。 对性能极度敏感且确定无竞争的代码，可用此指令跳过检测。 //go:norace func AtomicIncrement(ptr *int64) { atomic.AddInt64(ptr, 1) } 必须 100% 确定函数无数据竞争，否则可能掩盖潜在问题。 //go:linkname # 链接到其他包中的未导出（unexported）函数或变量。\n通过 //go:linkname 可以访问其他包内部的私有符号（如 runtime 包中的函数）。 需要配合 import _ \u0026quot;unsafe\u0026quot;。 import _ \u0026#34;unsafe\u0026#34; //go:linkname timeNow time.now func timeNow() (sec int64, nsec int32, mono int64) 破坏封装性，可能导致版本升级后的兼容性问题。 通常用于实现高级功能（如自定义调度或性能监控）。 //go:embed # Go 1.16+新增，将外部文件或目录嵌入到二进制中。\n通过 embed 包和指令，将静态文件（如 HTML、配置文件）编译到程序中。 支持嵌入单个文件、目录或模式匹配。 import \u0026#34;embed\u0026#34; //go:embed templates/*.html static/images/* var content embed.FS 路径相对于当前 Go 文件所在目录。 嵌入大文件可能增加二进制体积。 //go:build # 条件编译（替代旧的 // +build 语法）。\n根据操作系统、架构或自定义编译标签（Tag）选择是否编译代码块。 使用布尔表达式组合条件（如 linux \u0026amp;\u0026amp; amd64）。 //go:build darwin || (linux \u0026amp;\u0026amp; amd64) package main 旧语法 // +build 仍可用，但 //go:build 更清晰且支持复杂逻辑。 //go:uintptrescapes # 允许 uintptr 类型的参数逃逸到堆。\nuintptr 通常不参与逃逸分析，但在某些系统调用中需要传递指针的整数形式。 该指令强制编译器允许 uintptr 逃逸，避免被错误回收。 //go:uintptrescapes func SyscallWrite(fd uintptr, p []byte) (n int, err error) 封装操作系统调用（如 syscall 包）。 //go:nowritebarrierrec # 检测并禁止函数递归触发写屏障（Write Barrier）。\n写屏障是垃圾回收（GC）的关键机制，用于跟踪指针修改。 在 GC 的某些阶段（如标记阶段），禁止触发写屏障，否则可能导致死锁。 //go:nowritebarrierrec func gcMarkWorker() { // 标记阶段代码 } 主要用于 Go 运行时内部的 GC 相关代码。 //go:systemstack # 强制函数在系统栈（System Stack）而非用户 Goroutine 栈上运行。\nGo 的 Goroutine 栈初始较小且可扩展，但某些操作（如栈扩容自身）必须在固定大小的系统栈上执行。 该指令确保函数运行在系统栈，避免触发栈扩展逻辑。 //go:systemstack func runtimeCallback() { // 处理底层任务（如调度或 GC） } 运行时（Runtime）中的关键路径，如调度器或 GC。 Makefile # Makefile 是一个强大且灵活的构建工具，具备自动化构建、处理依赖关系、任务管理和跨平台支持等优点。通过编写和使用 Makefile，开发者可以简化项目的构建过程，提高开发效率，并实现自动化的构建和发布流程。\n在许多开源项目和工具中，Makefile 被广泛选择作为构建工具。它的灵活性主要体现在其具有 target（目标）的概念，相比于仅使用 Shell 脚本，Makefile 可以更好地组织和管理构建过程。\n此外，Makefile 还能够与其他工具和语言进行集成，例如与 C/C++ 编译器、Go 工具链等配合使用。通过定义适当的规则和命令，可以实现与其他构建工具的无缝集成，进一步提高构建过程的灵活性和效率。\nMakefile最初是用来解决C语言的编译问题的，所以和C的关系特别密切，但并不是说Makefile只能用来解决C的编译问题，也可以用来作为其他语言的编译工具，例如Java、Go等\nMakefile 是由 GNU Make 工具解析执行的配置文件。要调用 Makefile，需要在命令行中使用make命令，并指定要执行的目标或规则。\nMakefile可以简单的认为是一个工程文件的编译规则，描述了整个工程的自动编译和链接的规则。\n语法格式 # # 这是注释 target: prerequisites [tab]commands target（目标）：通常是要生成的文件的名称，可以是可执行文件或OBJ文件， 也可以是一个执行的动作名称，诸如clean ，目标的命名采用 \u0026ldquo;蛇型\u0026rdquo;（snake_case）更为常见和推荐。这是因为在 Linux 和 Unix 系统中，文件名通常使用小写字母和下划线，而不是 \u0026ldquo;驼峰命名法\u0026rdquo; 或 \u0026ldquo;中横线命名法\u0026rdquo;。 prerequisites（依赖）：是用来产生目标的材料(比如源文件)，一个目标经常有几个依赖。 commands（命令）：是生成目标时执行的动作，一个规则可以含有几个命令，每个命令占一行。 **注意：**每个命令行前面必须是一个 Tab 字符。\nmake命令 # 在命令行中使用 make 命令调用 Makefile，并指定要执行的目标。如果未指定目标，默认会执行 Makefile 中的第一个目标。\nmake [target] -f \u0026lt;filename\u0026gt;：指定要使用的 Makefile 文件名，例如 make -f mymakefile。\n-C \u0026lt;directory\u0026gt;：指定 Makefile 的工作目录，例如 make -C src。\n例子1\nall: @echo \u0026#34;Hello all\u0026#34; test: @echo \u0026#34;Hello test\u0026#34; # 执行结果如下 $ make Hello all $ make all Hello all $ make test Hello test 调用 make 命令时，我们得告诉它我们的目标是什么，即要它干什么。当没有指明具体的目标是什么时，那么 make 以 Makefile 文件中定义的第一个目标作为这次运行的目标。这第一个目标也称之为默认目标（和是不是all没有关系）。\n命令前加了一个@， 这一符号告诉 make，在运行时不要将这一行命令显示出来。\n例子 # 例子2\nall: test @echo \u0026#34;Hello all\u0026#34; test: @echo \u0026#34;Hello test\u0026#34; # 执行结果如下 $ make Hello test Hello all 会发现当运行 make 时，test 目标也被构建了。这里需要引入 Makefile 中依赖关系的概念，all 目标后面的 test 是告诉 make，all 目标依赖 test 目标，这一依赖目标在 Makefile 中又被称之为依赖。出现这种目标依赖关系时，make工具会按从左到右的先后顺序先构建规则中所依赖的每一个目标。如果希望构建 all 目标，那么make 会在构建它之前得先构建 test 目标，这就是为什么我们称之为依赖的原因。\n在实际使用中，通常一个编译过程需要生成多个目标文件（中间文件），如下\nall:main.o foo.o gcc -o simple main.o foo.o main.o: gcc -o main.o -c main.c foo.o: gcc -o foo.o -c foo.c clean: rm simple main.o foo.o # 执行结果如下 $make gcc -c main.c -o main.o gcc -c foo.c -o foo.o gcc -o simple main.o foo.o $make gcc -o simple main.o foo.o 上面的这个就是一个常见的C语言编译的过程，如果要生成可执行文件simple，那么需要main.o和foo.o，所以我们需要提前编译这两个文件。同时我们还增加了一个目标clean，用于清理编译产生的所有文件。\n第二次编译并没有构建目标文件的动作，但有构建simple可执行程序的动作，我们需要了解 make 是如何决定哪些目标（这里是文件）是需要重新编译的。为什么 make会知道我们并没有改变 main.c 和 foo.c 呢？通过文件的时间戳，也就是foo.c文件的时间戳小于foo.o。当 make 在运行一个规则时，我们前面已经提到了目标和依赖之间的关系，make 在检查一个规则时，采用的方法是：如果依赖目标中相关的文件的时间戳大于依赖文件的时间戳，依赖的文件比目标更新，则知道有变化，那么需要运行规则当中的命令重新构建目标。\n那为什么会执行一次gcc -o simple main.o foo.o呢？因为all文件在我们的编译过程中并不生成，即 make 在第二次编译时找不到，所以又重新编译了一遍。如果我们把all改成simple，那么就不会重新编译了。\n伪目标 # 我们现在有一个目标\nclean: rm simple main.o foo.o 如果此时在工作目录中存在一个clean文件，那么我们在执行make clean的时候，make会提示'clean' is up to date.。这是因为 make 将 clean 当作文件，且在当前目录找到了这个文件，加上 clean 目标没有任何依赖，当我们要求 make 为我们构建 clean 目标时，它就会认为 clean 是最新的。\n采用.PHONY关键字声明一个目标后，make 并不会将其当作一个文件来处理，而只是当作一个概念上的目标。对于假目标，我们可以想像的是由于并不与文件关联，所以每一次 make 这个假目标时，其所在的规则中的命令都会被执行。\n.PHONY:clean clean: rm simple main.o foo.o 变量 # 系统变量 # $*：不包括扩展名的目标文件名称；\n$+：所以的依赖文件，以空格分隔；\n$\u0026lt;：表示规则中的第一个条件；\n$?：所有时间戳比目标文件晚的依赖文件，以空格分隔；\n$@：目标文件的完整名称；\n$^：所有不重复的依赖文件，以空格分隔；\n$%：如果目标是归档成员，则该变量表示目标的归档成员名称；\n定义变量 # 在 Makefile 中，变量的定义需要使用:=进行赋值操作，变量通过$()进行访问\n# 定义变量 GREETING := \u0026#34;Hello, World!\u0026#34; # 输出变量 variable: @echo \u0026#34;$(GREETING)\u0026#34; # 执行make，输出Hello, World! 在 Makefile 中，?= 是一个预定义的变量赋值方式，被称为 “延迟求值”（Lazy Evaluation）。\n具体来说，这个符号用于设置一个变量的默认值，只有当该变量没有被显式设置时才会使用默认值。如果变量已经被设置了，那么 ?= 将不会起作用，而是保留原来的值。\n# 定义变量 GREETING := \u0026#34;Hello!\u0026#34; GREETING ?= \u0026#34;Hello, World!\u0026#34; # 输出变量 variable: @echo \u0026#34;$(GREETING)\u0026#34; # 执行make，输出Hello! 数组 # 访问数组时，第一个元素可以使用$(firstword $(arr))，也可以使用$(word 1,$(arr))来访问，最后一个元素可以使用$(lastword $(arr))，也可以使用$(word index,$(arr))来访问。\n# 定义数组 names := java go python # 访问数组中元素 show: @echo \u0026#34;Index1 : $(firstword $(names))\u0026#34; @echo \u0026#34;Index2 : $(word 2,$(names))\u0026#34; @echo \u0026#34;Index4 : $(lastword $(names))\u0026#34; 遍历数组\n# 定义数组 names := java go python # 访问数组中元素 show: @for name in $(names);do\\ echo \u0026#34;$$name\u0026#34;;\\ done ","date":"2025-05-19","externalUrl":null,"permalink":"/posts/2d3150b2/02a0817f/9c507962/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eGo的开发工具链 \n    \u003cdiv id=\"go的开发工具链\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#go%e7%9a%84%e5%bc%80%e5%8f%91%e5%b7%a5%e5%85%b7%e9%93%be\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eGo语言不仅仅是一门编程语言，更是一套完整的开发生态系统。其核心是一组命令行工具，统一通过go命令调用，这套工具链遵循以下设计理念：\u003c/p\u003e","title":"2、编译和开发工具链","type":"posts"},{"content":"工厂模式（Factory）提供了一种创建对象的接口，允许子类决定实例化的对象类型。在Go中，可以通过函数和接口实现工厂模式：\n定义 # package payment import \u0026#34;fmt\u0026#34; // PaymentMethod接口定义了支付方法的行为 type PaymentMethod interface { Pay(amount float64) bool GetName() string } // CreditCard实现PaymentMethod接口 type CreditCard struct { cardNumber string cvv string expiry string } func (c CreditCard) Pay(amount float64) bool { fmt.Printf(\u0026#34;使用信用卡支付%.2f元\\n\u0026#34;, amount) return true } func (c CreditCard) GetName() string { return \u0026#34;信用卡\u0026#34; } // Alipay实现PaymentMethod接口 type Alipay struct { accountID string } func (a Alipay) Pay(amount float64) bool { fmt.Printf(\u0026#34;使用支付宝支付%.2f元\\n\u0026#34;, amount) return true } func (a Alipay) GetName() string { return \u0026#34;支付宝\u0026#34; } // WechatPay实现PaymentMethod接口 type WechatPay struct { accountID string } func (w WechatPay) Pay(amount float64) bool { fmt.Printf(\u0026#34;使用微信支付%.2f元\\n\u0026#34;, amount) return true } func (w WechatPay) GetName() string { return \u0026#34;微信支付\u0026#34; } // 支付方式类型 type PaymentType string const ( CreditCardType PaymentType = \u0026#34;credit_card\u0026#34; AlipayType PaymentType = \u0026#34;alipay\u0026#34; WechatPayType PaymentType = \u0026#34;wechat_pay\u0026#34; ) // CreatePaymentMethod是一个工厂函数，根据类型创建支付方法 func CreatePaymentMethod(payType PaymentType) (PaymentMethod, error) { switch payType { case CreditCardType: return CreditCard{ cardNumber: \u0026#34;1234-5678-9012-3456\u0026#34;, cvv: \u0026#34;123\u0026#34;, expiry: \u0026#34;12/25\u0026#34;, }, nil case AlipayType: return Alipay{ accountID: \u0026#34;alipay@example.com\u0026#34;, }, nil case WechatPayType: return WechatPay{ accountID: \u0026#34;wxid_12345\u0026#34;, }, nil default: return nil, fmt.Errorf(\u0026#34;不支持的支付方式: %s\u0026#34;, payType) } } 使用 # // main.go package main import ( \u0026#34;fmt\u0026#34; \u0026#34;myapp/payment\u0026#34; ) func ProcessPayment(amount float64, payType payment.PaymentType) { method, err := payment.CreatePaymentMethod(payType) if err != nil { fmt.Println(\u0026#34;错误:\u0026#34;, err) return } fmt.Printf(\u0026#34;使用%s处理支付...\\n\u0026#34;, method.GetName()) success := method.Pay(amount) if success { fmt.Println(\u0026#34;支付成功！\u0026#34;) } else { fmt.Println(\u0026#34;支付失败！\u0026#34;) } } func main() { ProcessPayment(199.99, payment.CreditCardType) ProcessPayment(299.99, payment.AlipayType) ProcessPayment(399.99, payment.WechatPayType) // 测试错误情况 ProcessPayment(499.99, \u0026#34;unknown\u0026#34;) } ","date":"2025-05-14","externalUrl":null,"permalink":"/posts/2d3150b2/6742e0c3/44fa6cdd/","section":"文章","summary":"\u003cp\u003e工厂模式（Factory）提供了一种创建对象的接口，允许子类决定实例化的对象类型。在Go中，可以通过函数和接口实现工厂模式：\u003c/p\u003e","title":"2、工厂模式","type":"posts"},{"content":" 接口 # Go 语言的接口设计是非侵入式的，接口编写者无须知道接口被哪些类型实现。而接口实现者只需知道实现的是什么样子的接口，但无须指明实现哪一个接口。编译器知道最终编译时使用哪个类型实现哪个接口，或者接口应该由谁来实现。\nGo 的接口有以下特性：\n隐式实现：无需显式声明类型与接口的关系，只需实现方法。 方法签名匹配：方法名、参数列表、返回类型必须完全一致。 接收者类型无关：方法的接收者可以是值类型或指针类型，但需与方法声明一致。 鸭子类型：Go 的接口遵循“鸭子类型”（Duck Typing）——如果它走起来像鸭子，叫起来像鸭子，那么它就是鸭子。 接口的零值是nil，此时动态类型和动态值都是nil。 定义接口 # 接口定义规则如下：\n接口类型名：Go语言的接口在命名时，一般会在单词后面添加 er，如有写操作的接口叫 Writer，有字符串功能的接口叫 Stringer，有关闭功能的接口叫 Closer 等 方法名：当方法名首字母是大写时，且这个接口类型名首字母也是大写时，这个方法可以被接口所在的包（package）之外的代码访问 参数列表、返回值列表：参数列表和返回值列表中的参数变量名可以被忽略 Go语言的每个接口中的方法数量不会很多。Go语言希望通过一个接口精准描述它自己的功能，而通过多个接口的嵌入和组合的方式将简单的接口扩展为复杂的接口。 在Go中，接口是一种类型，它指定了一个方法集：\n// 定义一个接口 type Reader interface { Read(p []byte) (n int, err error) } // 另一个包含多个方法的接口 type Shape interface { Area() float64 Perimeter() float64 } 实现接口 # 实现接口的条件如下：\n接口的方法与实现接口的类型方法格式一致\n在类型中添加与接口签名一致的方法就可以实现该方法。签名包括方法的名称、参数列表、返回参数列表。\n接口中所有方法均被实现\n当一个接口中有多个方法时，只有这些方法都被实现了，接口才能被正确编译并使用。\nGo接口的特别之处在于它们是隐式实现的，即如果一个类型实现了接口中定义的所有方法，它就自动满足了该接口，无需显式声明：\n// Circle类型 type Circle struct { Radius float64 } // 实现Shape接口的Area方法 func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius } // 实现Shape接口的Perimeter方法 func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius } // Rectangle类型 type Rectangle struct { Width, Height float64 } // 实现Shape接口的方法 func (r Rectangle) Area() float64 { return r.Width * r.Height } func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) } // 使用接口作为参数 func PrintShapeInfo(s Shape) { fmt.Printf(\u0026#34;面积: %.2f, 周长: %.2f\\n\u0026#34;, s.Area(), s.Perimeter()) } func main() { c := Circle{Radius: 5} r := Rectangle{Width: 3, Height: 4} // 都可以作为Shape传递给函数 PrintShapeInfo(c) PrintShapeInfo(r) } 其他类型实现 # **注意：**在 Go 中，不是只有 struct 类型可以实现接口！其他的所有类型，只要实现了接口的所有方法，就自动实现了该接口。\npackage main type MyInterface interface { MyTestFunc(int) int } // MyFunc 类型实现了 MyInterface 接口 type MyFunc func(string) string func (m MyFunc) MyTestFunc(a int) int { return a } // MyInt 类型实现了 MyInterface 接口 type MyInt int func (i MyInt) MyTestFunc(a int) int { return a } 接口嵌套 # 接口可以嵌套其他接口，形成更大的接口：\ntype Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } // ReadWriter接口嵌套了Reader和Writer type ReadWriter interface { Reader Writer } 实现ReadWriter接口的类型必须同时实现Read和Write方法。\n一个类型可以实现多个接口 # 一个类型可以同时实现多个接口，而接口间彼此独立，不知道对方的实现。\n网络上的两个程序通过一个双向的通信连接实现数据的交换，连接的一端称为一个 Socket。Socket 能够同时读取和写入数据，这个特性与文件类似。因此，开发中把文件和 Socket 都具备的读写特性抽象为独立的读写器概念。\nSocket 和文件一样，在使用完毕后，也需要对资源进行释放。\n把 Socket 能够写入数据和需要关闭的特性使用接口来描述：\ntype Socket struct { } func (s *Socket) Write(p []byte) (n int, err error) { return 0, nil } func (s *Socket) Close() error { return nil } Socket 结构的 Write() 方法实现了 io.Writer 接口：\ntype Writer interface { Write(p []byte) (n int, err error) } 同时，Socket 结构也实现了 io.Closer 接口：\ntype Closer interface { Close() error } 使用 Socket 实现的 Writer 接口的代码，无须了解 Writer 接口的实现者是否具备 Closer 接口的特性。同样，使用 Closer 接口的代码也并不知道 Socket 已经实现了 Writer 接口，如下图所示：\n多个类型可以实现相同的接口 # 例如，现在需要有一个接口，这个接口可以实现服务的开启和日志功能，而日志功能是多个服务通用的，只有开启功能需要根据服务的不同来定义，每个服务都有这两个功能，那么就可以这么去做\npackage main import \u0026#34;fmt\u0026#34; //Service 服务接口 type Service interface { start() log() } // Logger 通用的日志处理器 type Logger struct{} func (log *Logger) log() { fmt.Println(\u0026#34;通用日志服务\u0026#34;) } // GameService 游戏服务 type GameService struct { //内嵌日志处理器 Logger } //实现游戏服务的启动功能 func (gs *GameService) start() { fmt.Printf(\u0026#34;开启游戏服务\u0026#34;) } func main() { var service Service = new(GameService) service.log() service.start() } 此时，如果再新增服务的话，只需要内嵌日志处理器然后再实现服务特有的启动功能，就可以了\n接口和nil # nil 在 Go语言中只能被赋值给指针和接口。接口在底层的实现有两个部分：type 和 data。\n在源码中，显式地将 nil 赋值给接口时，接口的 type 和 data 都将为 nil。此时，接口与 nil 值判断是相等的。\n但如果将一个带有类型的 nil 赋值给接口时，只有 data 为 nil，而 type 为 nil，此时，接口与 nil 判断将不相等。\n空接口 # interface{} any //在1.18后，可以使用any来表示空接口，源码中是对空接口做了别名 type any = interface{} 空接口是接口类型的特殊形式，空接口没有任何方法，因此任何类型都无须实现空接口。从实现的角度看，任何值都满足这个接口的需求。因此空接口类型可以保存任何值，也可以从空接口中取出原值。\n空接口的内部实现保存了对象的类型和指针。使用空接口保存一个数据的过程会比直接用数据对应类型的变量保存稍慢。因此在开发中，应在需要的地方使用空接口，而不是在所有地方使用空接口。\n空接口比较 # Go 语言中的空接口在保存不同的值后，可以和其他变量一样使用==进行比较操作。\n类型不同的空接口间的比较结果不相同 不能比较空接口中的动态值 类 型 说 明 map 不可比较，如果比较，程序会报错 切片（slice） 不可比较，如果比较，程序会报错 通道（channel） 可比较，必须由同一个 make 生成，也就是同一个通道才会是true，否则为 false 数组 可比较，编译期知道两个数组是否一致 结构体 可比较，可以逐个比较结构体的值 函数 可比较 类型断言 # 类型断言（Type Assertion）是一个使用在接口值上的操作，用于检查接口类型变量所持有的值是否实现了期望的接口或者具体的类型，也可以作为接口和具体类型直接转换的方法，语法格式如下：\nvalue, ok := x.(T) x 表示一个接口的类型变量，T 表示一个具体的类型（也可为接口类型）。\n该断言表达式会返回 x 的值（也就是 value）和一个布尔值（也就是 ok），可根据该布尔值判断 x 是否为 T 类型\n如果 T 是具体某个类型，类型断言会检查 x 的动态类型是否等于具体类型 T。如果检查成功，类型断言返回的结果是 x 的动态值，其类型是 T。 如果 T 是接口类型，类型断言会检查 x 的动态类型是否满足 T。如果检查成功，x 的动态值不会被提取，返回值是一个类型为 T 的接口值。 无论 T 是什么类型，如果 x 是 nil 接口值，类型断言都会失败。 注意：需要注意如果不接收第二个参数也就是上面代码中的 ok，断言失败时会直接造成一个 panic，也就是说，如果断言成功，就可以只接一个值，就是value\npackage main import \u0026#34;fmt\u0026#34; func main() { var a interface{} = 10 var b interface{} = \u0026#34;hello\u0026#34; v1, o1 := a.(int) v2, o2 := b.(int) fmt.Println(v1, o1) //10 true fmt.Println(v2, o2) //0 false } 类型分支 # type-switch 流程控制的语法或许是Go语言中最古怪的语法。 它可以被看作是类型断言的增强版。它和 switch-case 流程控制代码块有些相似。\nswitch 接口变量.(type) { case 类型1: // 变量是类型1时的处理 case 类型2: // 变量是类型2时的处理 … default: // 变量不是所有case中列举的类型时的处理 } package main import \u0026#34;fmt\u0026#34; func main() { var a interface{} = \u0026#34;hello\u0026#34; switch a.(type) { case int: fmt.Printf(\u0026#34;a is int type,value = %s\u0026#34;, a) case string: fmt.Printf(\u0026#34;a is string type,value = %s\u0026#34;, a) case float64: fmt.Printf(\u0026#34;a is float64 type,value = %s\u0026#34;, a) } //a is string type,value = hello } 接口设计模式 # io包中的接口设计 # Go标准库中的io包是接口设计的典范，这种设计允许：\n灵活组合功能 按需实现接口 基于最小接口进行编程 // io包核心接口 type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type Closer interface { Close() error } // 组合接口 type ReadWriter interface { Reader Writer } type ReadCloser interface { Reader Closer } // 更多组合... 通过结构体组合可以轻松实现多个接口：\n// 实现Reader接口 type StringReader struct { data string pos int } func (r *StringReader) Read(p []byte) (n int, err error) { // 实现细节... return len(p), nil } // 实现Writer接口 type StringWriter struct { data string } func (w *StringWriter) Write(p []byte) (n int, err error) { // 实现细节... return len(p), nil } // 通过组合实现ReadWriter接口 type StringReadWriter struct { *StringReader *StringWriter } // 可以使用工厂函数创建组合结构 func NewStringReadWriter(data string) *StringReadWriter { return \u0026amp;StringReadWriter{ StringReader: \u0026amp;StringReader{data: data}, StringWriter: \u0026amp;StringWriter{}, } } 错误处理中的接口应用 # // 标准错误接口 type error interface { Error() string } // 自定义错误类型 type NetworkError struct { Code int Message string } func (e NetworkError) Error() string { return fmt.Sprintf(\u0026#34;网络错误 [%d]: %s\u0026#34;, e.Code, e.Message) } // 错误断言 func handleError(err error) { if netErr, ok := err.(NetworkError); ok { fmt.Printf(\u0026#34;处理网络错误，代码: %d\\n\u0026#34;, netErr.Code) } else { fmt.Println(\u0026#34;处理其他错误\u0026#34;) } } 插件系统设计 # // 插件接口 type Plugin interface { Name() string Initialize() error Execute(ctx context.Context) error Shutdown() error } // 插件管理器 type PluginManager struct { plugins map[string]Plugin } func NewPluginManager() *PluginManager { return \u0026amp;PluginManager{ plugins: make(map[string]Plugin), } } func (pm *PluginManager) Register(p Plugin) error { name := p.Name() if _, exists := pm.plugins[name]; exists { return fmt.Errorf(\u0026#34;插件 %s 已注册\u0026#34;, name) } if err := p.Initialize(); err != nil { return fmt.Errorf(\u0026#34;插件 %s 初始化失败: %v\u0026#34;, name, err) } pm.plugins[name] = p return nil } func (pm *PluginManager) ExecuteAll(ctx context.Context) error { for name, p := range pm.plugins { if err := p.Execute(ctx); err != nil { return fmt.Errorf(\u0026#34;插件 %s 执行失败: %v\u0026#34;, name, err) } } return nil } func (pm *PluginManager) ShutdownAll() { for name, p := range pm.plugins { if err := p.Shutdown(); err != nil { fmt.Printf(\u0026#34;插件 %s 关闭失败: %v\\n\u0026#34;, name, err) } } } // 示例插件 type LoggingPlugin struct{} func (p *LoggingPlugin) Name() string { return \u0026#34;logging\u0026#34; } func (p *LoggingPlugin) Initialize() error { fmt.Println(\u0026#34;日志插件初始化\u0026#34;) return nil } func (p *LoggingPlugin) Execute(ctx context.Context) error { fmt.Println(\u0026#34;日志插件执行\u0026#34;) return nil } func (p *LoggingPlugin) Shutdown() error { fmt.Println(\u0026#34;日志插件关闭\u0026#34;) return nil } ","date":"2025-05-14","externalUrl":null,"permalink":"/posts/2d3150b2/0e0cf2f4/b1cccbc6/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e接口 \n    \u003cdiv id=\"接口\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a5%e5%8f%a3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eGo 语言的接口设计是\u003cstrong\u003e非侵入式\u003c/strong\u003e的，接口编写者无须知道接口被哪些类型实现。而接口实现者只需知道实现的是什么样子的接口，但无须指明实现哪一个接口。编译器知道最终编译时使用哪个类型实现哪个接口，或者接口应该由谁来实现。\u003c/p\u003e","title":"2、接口","type":"posts"},{"content":" 运行时 # 运行时是一个为应用程序提供实用方法的库。 有 Go 和 JavaScript 运行时，目的是在可能的情况下尝试使它们保持一致。\nGo 运行时可通过导入 github.com/wailsapp/wails/v2/pkg/runtime 获取。 此包中的所有方法都将 context 作为第一个参数。此 context 应该从应用启动回调或前端 Dom 加载完成回调方法中获取。\nJavaScript 库可通过 window.runtime 提供给前端。 使用 开发 模式时会生成一个运行时包，该包为运行时提供 TypeScript 声明。 这应该位于您的前端目录的wailsjs目录中。\n隐藏 # 隐藏应用程序。\nGo: Hide(ctx context.Context) JS: Hide() Hide 在 Mac 上，这将以与标准 Mac 应用程序中的菜单项相同的方式隐藏应用程序。 这与隐藏窗口不同，但应用程序仍处于前台。 对于 Windows 和 Linux，这与 WindowHide 相同。\n显示 # 显示应用程序。\nGo: Show(ctx context.Context) JS: Show() 在 Mac 上，这会将应用程序带回前台。 对于 Windows 和 Linux，这目前与 WindowShow 相同。\n退出 # 退出应用程序。\nGo: Quit(ctx context.Context) JS: Quit() 环境 # 返回当前环境的详细信息。\nGo: Environment(ctx context.Context) EnvironmentInfo JS: Environment(): Promise\u0026lt;EnvironmentInfo\u0026gt; Go - EnvironmentInfo # type EnvironmentInfo struct { BuildType string Platform string Arch string } JS - EnvironmentInfo # interface EnvironmentInfo { buildType: string; platform: string; arch: string; } 浏览器打开 URL # 使用系统默认浏览器打开给定的 URL。\nGo: BrowserOpenURL(ctx context.Context, url string) JS: BrowserOpenURL(url string) Events 事件 # Wails 运行时提供了一个统一的事件系统，其中事件可以由 Go 或 JavaScript 发出或接收。\n可选地，数据可以与事件一起传递。 侦听器将接收本地数据类型中的数据。\nEventsOn 添加事件侦听器 # 此方法为给定的事件名称设置一个侦听器。\n当触发指定事件名为 eventName 类型的事件时，将触发回调。 与触发事件一起发送的任何其他数据都将传递给回调。 它返回一个函数来取消侦听器。\nGo: EventsOn(ctx context.Context, eventName string, callback func(optionalData ...interface{})) func() JS: EventsOn(eventName string, callback function(optionalData?: any)): () =\u0026gt; void EventsOff 移除事件侦听器 # 此方法取消注册给定事件名称的侦听器，可选地，可以通过 additionalEventNames 取消注册多个侦听器。\nGo: EventsOff(ctx context.Context, eventName string, additionalEventNames ...string) JS: EventsOff(eventName string, ...additionalEventNames) EventsOnce 添加只触发一次的事件侦听器 # 此方法为给定的事件名称设置一个侦听器，但只会触发一次。 它返回 一个函数来取消侦听器。\nGo: EventsOnce(ctx context.Context, eventName string, callback func(optionalData ...interface{})) func() JS: EventsOnce(eventName string, callback function(optionalData?: any)): () =\u0026gt; void EventsOnMultiple 添加指定触发次数的事件侦听器 # 此方法为给定的事件名称设置一个侦听器，但最多只能触发 counter 次。 它返回 一个函数来取消侦听器。\nGo: EventsOnMultiple(ctx context.Context, eventName string, callback func(optionalData ...interface{}), counter int) func() JS: EventsOnMultiple(eventName string, callback function(optionalData?: any), counter int): () =\u0026gt; void EventsEmit 触发指定事件 # 此方法触发指定的事件。 可选数据可以与事件一起传递。 这将触发任意事件侦听器。\nGo: EventsEmit(ctx context.Context, eventName string, optionalData ...interface{}) JS: EventsEmit(eventName: string, ...optionalData: any) Log 日志 # Wails 运行时提供了一种可以从 Go 或 JavaScript 调用日志记录的机制。 像大多数记录器一样，有许多日志级别：\nTrace（追踪） Debug（调试） Info（信息） Warning（警告） Error（错误） Fatal（致命） 记录器将输出当前或更高日志级别的任何日志消息。\n示例：Debug日志级别将输出除Trace消息之外的所有消息。\n原始消息 Print # LogPrint Print 日志 # 将给定的消息记录为原始消息。\nGo: LogPrint(ctx context.Context, message string) JS: LogPrint(message: string) LogPrintf 格式化 Print 日志 # 将给定的消息记录为原始消息。\nGo: LogPrintf(ctx context.Context, format string, args ...interface{})\nTrace # LogTrace Trace 日志 # 在 Trace 日志级别记录给定的消息。\nGo: LogTrace(ctx context.Context, message string) JS: LogTrace(message: string) LogTracef 格式化 Trace 日志 # 在 Trace 日志级别记录给定的消息。\nGo: LogTracef(ctx context.Context, format string, args ...interface{})\nDebug # LogDebug Debug 日志 # 在 Debug 日志级别记录给定的消息。\nGo: LogDebug(ctx context.Context, message string) JS: LogDebug(message: string) LogDebugf 格式化 Debug 日志 # 在 Debug 日志级别记录给定的消息。\nGo: LogDebugf(ctx context.Context, format string, args ...interface{})\nInfo # LogInfo Info 日志 # 在Info日志级别记录给定的消息。\nGo: LogInfo(ctx context.Context, message string) JS: LogInfo(message: string) LogInfof 格式化 Info 日志 # 在Info日志级别记录给定的消息。\nGo: LogInfof(ctx context.Context, format string, args ...interface{})\nWarning # LogWarning Warning 日志 # 在 Warning 日志级别记录给定的消息。\nGo: LogWarning(ctx context.Context, message string) JS: LogWarning(message: string) LogWarningf 格式化 Warning 日志 # 在 Warning 日志级别记录给定的消息。\nGo: LogWarningf(ctx context.Context, format string, args ...interface{})\nError # LogError Error 日志 # 在 Error 日志级别记录给定的消息。\nGo: LogError(ctx context.Context, message string) JS: LogError(message: string) LogErrorf 格式化 Error 日志 # 在 Error 日志级别记录给定的消息。\nGo: LogErrorf(ctx context.Context, format string, args ...interface{})\nFatal # LogFatal Fatal 日志 # 在 Fatal 日志级别记录给定的消息。\nGo: LogFatal(ctx context.Context, message string) JS: LogFatal(message: string) LogFatalf 格式化 Fatal 日志 # 在 Fatal 日志级别记录给定的消息。\nGo: LogFatalf(ctx context.Context, format string, args ...interface{})\n设置日志级别 # 设置日志级别。 在 JavaScript 中，该数字与以下日志级别有关：\n值 日志等级 1 Trace（追踪） 2 Debug（调试） 3 Info（信息） 4 Warning（警告） 5 Error（错误） Go: LogSetLogLevel(ctx context.Context, level logger.LogLevel) JS: LogSetLogLevel(level: number) 使用自定义日志 # 可以通过使用应用程序参数选项日志提供自定义记录器来使用它。\n唯一的要求是记录器实现在 github.com/wailsapp/wails/v2/pkg/logger 里 logger.Logger 定义的接口：\ntype Logger interface { Print(message string) Trace(message string) Debug(message string) Info(message string) Warning(message string) Error(message string) Fatal(message string) } Window 窗口 # 相关对象定义 # Position（位置） # interface Position { x: number; y: number; } Size（尺寸） # interface Size { w: number; h: number; } WindowSetTitle 窗口标题 # 设置窗口标题栏中的文本。\nGo: WindowSetTitle(ctx context.Context, title string) JS: WindowSetTitle(title: string) WindowFullscreen 窗口全屏 # 使窗口全屏。\nGo: WindowFullscreen(ctx context.Context) JS: WindowFullscreen() WindowUnfullscreen 窗口取消全屏 # 恢复全屏之前的先前窗口尺寸和位置。\nGo: WindowUnfullscreen(ctx context.Context) JS: WindowUnfullscreen() WindowIsFullscreen 窗口是否全屏 # 如果窗口是全屏的，则返回 true。\nGo: WindowIsFullscreen(ctx context.Context) bool JS: WindowIsFullscreen() Promise\u0026lt;boolean\u0026gt; WindowCenter 窗口居中 # 使窗口在当前窗口所在的监视器上居中。\nGo: WindowCenter(ctx context.Context) JS: WindowCenter() WindowExecJS 窗口执行JS代码 # 在窗口中执行任意 JS 代码。\n此方法在浏览器中异步运行代码并立即返回。 如果脚本导致任何错误，它们将只在浏览器控制台中可用。\nGo: WindowExecJS(ctx context.Context, js string)\nWindowReload 窗口重新加载 # 执行“重新加载”（重新加载当前页面）。\nGo: WindowReload(ctx context.Context) JS: WindowReload() WindowReloadApp 重新加载应用程序前端。 # 重新加载应用程序前端。\nGo: WindowReloadApp(ctx context.Context) JS: WindowReloadApp() WindowSetSystemDefaultTheme 窗口设置系统默认主题 # 仅限 Windows。\nGo: WindowSetSystemDefaultTheme(ctx context.Context) JS: WindowSetSystemDefaultTheme() 将窗口主题设置为系统默认值（暗/亮）。\nWindowSetLightTheme 窗口设置浅色主题 # 仅限 Windows。\nGo: WindowSetLightTheme(ctx context.Context) JS: WindowSetLightTheme() 将窗口主题设置为浅色。\nWindowSetDarkTheme 窗口设置深色主题 # 仅限 Windows。\nGo: WindowSetDarkTheme(ctx context.Context) JS: WindowSetDarkTheme() 将窗口主题设置为深色。\nWindowShow 显示窗口 # 显示窗口，如果它当前是隐藏的。\nGo: WindowShow(ctx context.Context) JS: WindowShow() WindowShow 隐藏窗口 # 如果当前可见，则隐藏窗口。\nGo: WindowHide(ctx context.Context) JS: WindowHide() WindowIsNormal 窗口是否为正常 # 如果窗口未最小化、最大化或全屏，则返回 true。\nGo: WindowIsNormal(ctx context.Context) bool JS: WindowIsNormal() Promise\u0026lt;boolean\u0026gt; WindowSetSize 设置窗口尺寸 # 设置窗口的宽度和高度。\nGo: WindowSetSize(ctx context.Context, width int, height int) JS: WindowSetSize(width: number, height: number) WindowSetSize 获取窗口尺寸 # 获取窗口的宽度和高度。\nGo: WindowGetSize(ctx context.Context) (width int, height int) JS: WindowGetSize(): Promise\u0026lt;Size\u0026gt; WindowSetMinSize 设置窗口最小尺寸 # 设置窗口最小尺寸。 如果窗口当前小于给定尺寸，将调整窗口大小。\n设置大小 0,0 将禁用此约束。\nGo: WindowSetMinSize(ctx context.Context, width int, height int) JS: WindowSetMinSize(width: number, height: number) WindowSetMaxSize 设置窗口最大尺寸 # 设置窗口最大尺寸。 如果窗口当前大于给定尺寸，将调整窗口大小。\n设置大小 0,0 将禁用此约束。\nGo: WindowSetMaxSize(ctx context.Context, width int, height int) JS: WindowSetMaxSize(width: number, height: number) WindowSetAlwaysOnTop 设置窗口置顶 # 设置窗口置顶或取消置顶。\nGo: WindowSetAlwaysOnTop(ctx context.Context, b bool) JS: WindowSetAlwaysOnTop(b: boolean) WindowSetPosition 设置窗口位置 # 设置相对于窗口当前所在监视器的窗口位置。\nGo: WindowSetPosition(ctx context.Context, x int, y int) JS: WindowSetPosition(x: number, y: number) WindowGetPosition 获取窗口位置 # 获取相对于窗口当前所在监视器的窗口位置。\nGo: WindowGetPosition(ctx context.Context) (x int, y int) JS: WindowGetPosition(): Promise\u0026lt;Position\u0026gt; WindowMaximise 窗口最大化 # 最大化窗口以填满屏幕。\nGo: WindowMaximise(ctx context.Context) JS: WindowMaximise() WindowUnmaximise 窗口取消最大化 # 将窗口恢复到最大化之前的尺寸和位置。\nGo: WindowUnmaximise(ctx context.Context) JS: WindowUnmaximise() WindowIsMaximised 窗口是否最大化 # 如果窗口最大化，则返回 true。\nGo: WindowIsMaximised(ctx context.Context) bool JS: WindowIsMaximised() Promise\u0026lt;boolean\u0026gt; WindowToggleMaximise 窗口最大化切换 # 在最大化和未最大化之间切换。\nGo: WindowToggleMaximise(ctx context.Context) JS: WindowToggleMaximise() WindowMinimise 窗口最小化。 # 最小化窗口。\nGo: WindowMinimise(ctx context.Context) JS: WindowMinimise() WindowUnminimise 窗口取消最小化 # 将窗口恢复到最小化之前的尺寸和位置。\nGo: WindowUnminimise(ctx context.Context) JS: WindowUnminimise() WindowIsMinimised 窗口是否最小化 # 如果窗口最小化，则返回 true。\nGo: WindowIsMinimised(ctx context.Context) bool JS: WindowIsMinimised() Promise\u0026lt;boolean\u0026gt; WindowSetBackgroundColour 窗口设置背景色 # 将窗口的背景颜色设置为给定的 RGBA 颜色定义。 这种颜色将显示所有透明像素。\nR、G、B 和 A 的有效值为 0-255。\n**注意：**在 Windows 上，仅支持 0 或 255 的 alpha 值。 任何非 0 的值都将被视为 255。\nGo: WindowSetBackgroundColour(ctx context.Context, R, G, B, A uint8) JS: WindowSetBackgroundColour(R, G, B, A) WindowPrint # 打开本地打印对话框。\nGo: WindowPrint(ctx context.Context) JS: WindowPrint() Dialog 对话框 # 运行时的这一部分提供对原生对话框的调用，例如文件选择器和消息框。\n**注意：**JS 运行时当前不支持对话框。\n相关结构体 # OpenDialogOptions # type OpenDialogOptions struct { DefaultDirectory string DefaultFilename string Title string Filters []FileFilter ShowHiddenFiles bool CanCreateDirectories bool ResolvesAliases bool TreatPackagesAsDirectories bool } 字段 描述 Win Mac Linux DefaultDirectory 对话框打开时显示的目录 ✅ ✅ ✅ DefaultFilename 默认文件名 ✅ ✅ ✅ Title 对话框的标题 ✅ ✅ ✅ Filters 文件过滤器列表 ✅ ✅ ✅ ShowHiddenFiles 显示系统隐藏的文件 ✅ ✅ CanCreateDirectories 允许用户创建目录 ✅ ResolvesAliases 如果为 true，则返回文件而不是别名 ✅ TreatPackagesAsDirectories 允许导航到包 ✅ MessageDialogOptions # type MessageDialogOptions struct { Type DialogType Title string Message string Buttons []string DefaultButton string CancelButton string } 字段 描述 Win Mac Lin Type 消息对话框的类型，例如问题、信息\u0026hellip; ✅ ✅ ✅ Title 对话框的标题 ✅ ✅ ✅ Message 向用户显示的消息 ✅ ✅ ✅ Buttons 按钮标题列表 ✅ DefaultButton 带有此文本的按钮应被视为默认按钮。 必定 return。 ✅ ✅ CancelButton 带有此文本的按钮应被视为取消。 必定 escape ✅ 不同系统按钮的区别 # Windows 具有标准对话框类型，其中的按钮不可自定义。 返回的值将是以下之一：\u0026ldquo;Ok\u0026rdquo;、\u0026ldquo;Cancel\u0026rdquo;、\u0026ldquo;Abort\u0026rdquo;、\u0026ldquo;Retry\u0026rdquo;、\u0026ldquo;Ignore\u0026rdquo;、\u0026ldquo;Yes\u0026rdquo;、\u0026ldquo;No\u0026rdquo;、\u0026ldquo;Try Again\u0026quot;或\u0026quot;Continue\u0026rdquo;。\n对于问题对话框，默认按钮是 “是”，取消按钮是 “否”。 可以通过将 默认按钮 值设置为 \u0026quot;否\u0026quot; 来改变这一点。\nresult, err := runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{ Type: runtime.QuestionDialog, Title: \u0026#34;Question\u0026#34;, Message: \u0026#34;Do you want to continue?\u0026#34;, DefaultButton: \u0026#34;No\u0026#34;, }) Linux 有标准的对话框类型，其中的按钮是不可定制的。 返回的值将是以下之一：“Ok”、“Cancel”、“Yes”、“No”。\nMac 上的消息对话框最多可以指定 4 个按钮。 如果没有 DefaultButton 或 CancelButton 给出，第一个按钮被认为是默认的并绑定到 return 键。\nselection, err := runtime.MessageDialog(b.ctx, runtime.MessageDialogOptions{ Title: \u0026#34;It\u0026#39;s your turn!\u0026#34;, Message: \u0026#34;Select a number\u0026#34;, Buttons: []string{\u0026#34;one\u0026#34;, \u0026#34;two\u0026#34;, \u0026#34;three\u0026#34;, \u0026#34;four\u0026#34;}, }) 如果我们指定 DefaultButton 为“two”，则第二个按钮显示为默认值。 当 return 被按下时，则返回数值“two”。\nselection, err := runtime.MessageDialog(b.ctx, runtime.MessageDialogOptions{ Title: \u0026#34;It\u0026#39;s your turn!\u0026#34;, Message: \u0026#34;Select a number\u0026#34;,\tButtons: []string{\u0026#34;one\u0026#34;, \u0026#34;two\u0026#34;, \u0026#34;three\u0026#34;, \u0026#34;four\u0026#34;}, DefaultButton: \u0026#34;two\u0026#34;, }) 对话框类型 Type # const ( InfoDialog DialogType = \u0026#34;info\u0026#34; WarningDialog DialogType = \u0026#34;warning\u0026#34; ErrorDialog DialogType = \u0026#34;error\u0026#34; QuestionDialog DialogType = \u0026#34;question\u0026#34; ) FileFilter 文件过滤 # type FileFilter struct { DisplayName string // Filter information EG: \u0026#34;Image Files (*.jpg, *.png)\u0026#34; Pattern string // semi-colon separated list of extensions, EG: \u0026#34;*.jpg;*.png\u0026#34; } Windows # Windows 允许您在对话框中使用多个文件过滤器。 每个 FileFilter 将在对话框中显示为一个单独的条目：\nLinux # Linux 允许您在对话框中使用多个文件过滤器。 每个 FileFilter 将在对话框中显示为一个单独的条目：\nMac # Mac 对话框只有一组模式来过滤文件的概念。 如果提供了多个 FileFilters，Wails 将使用所有定义的模式。\nselection, err := runtime.OpenFileDialog(b.ctx, runtime.OpenDialogOptions{ Title: \u0026#34;Select File\u0026#34;, Filters: []runtime.FileFilter{ { DisplayName: \u0026#34;Images (*.png;*.jpg)\u0026#34;, Pattern: \u0026#34;*.png;*.jpg\u0026#34;, }, { DisplayName: \u0026#34;Videos (*.mov;*.mp4)\u0026#34;, Pattern: \u0026#34;*.mov;*.mp4\u0026#34;, }, }, }) 这将导致使用 *.png,*.jpg,*.mov,*.mp4 作为过滤器打开文件对话框。\nOpenDirectoryDialog 打开选择目录对话框 # 打开一个对话框，提示用户选择目录。\nGo: OpenDirectoryDialog(ctx context.Context, dialogOptions OpenDialogOptions) (string, error)\n返回值: 所选目录（如果用户取消则为空白）或错误\nOpenFileDialog 打开选择文件对话框 # 打开一个对话框，提示用户选择文件。\nGo: OpenFileDialog(ctx context.Context, dialogOptions OpenDialogOptions) (string, error)\n返回值: 所选文件（如果用户取消则为空白）或错误\nOpenMultipleFilesDialog 打开选择多文件对话框 # 打开一个对话框，提示用户选择多个文件。\nGo: OpenMultipleFilesDialog(ctx context.Context, dialogOptions OpenDialogOptions) ([]string, error)\n返回值: 选定的文件（如果用户取消则为 nil）或错误\nSaveFileDialog 保存文件对话框 # 打开一个对话框，提示用户选择文件名以进行保存。\nGo: SaveFileDialog(ctx context.Context, dialogOptions SaveDialogOptions) (string, error)\n返回值: 所选文件（如果用户取消则为空白）或错误\nMessageDialog 消息对话框 # 使用消息对话框显示消息。\nGo: MessageDialog(ctx context.Context, dialogOptions MessageDialogOptions) (string, error)\n返回值: 所选按钮的文本或错误\nMenu 菜单 # 这些方法与应用程序菜单相关。\n**注意：**JS 运行时当前不支持菜单。\n相关结构体 # 菜单结构体 # type Menu struct { Items []*MenuItem } 提供了一个简单的辅助方法来构建菜单：\nfunc NewMenuFromItems(first *MenuItem, rest ...*MenuItem) *Menu 菜单项结构体 # // MenuItem represents a menu item contained in a menu type MenuItem struct { Label string Role Role Accelerator *keys.Accelerator Type Type Disabled bool Hidden bool Checked bool SubMenu *Menu Click Callback } 字段 类型 注解 Label string 菜单文字 Accelerator *keys.Accelerator 此菜单项的键绑定 Type Type 菜单项的类型 Disabled bool 禁用菜单项 Hidden bool 隐藏此菜单项 Checked bool 添加检查项目 (复选框和单选类型) SubMenu *Menu 设置子菜单 Click Callback 单击菜单时的回调函数 Role string 定义此菜单项的角色。 暂时只支持 Mac 快捷键 # 键盘快捷键定义了按键和菜单项之间的绑定。 Wails 将加速器定义为一个组合或键 + 修饰符。 它们在 \u0026quot;github.com/wailsapp/wails/v2/pkg/menu/keys\u0026quot; 包中提供。\n// Defines cmd+o on Mac and ctrl-o on Window/Linux myShortcut := keys.CmdOrCtrl(\u0026#34;o\u0026#34;) Wails 还支持使用与 Electron 相同的语法来解析快捷键。 这对于将快捷键存储在配置文件中很有用。\n// Defines cmd+o on Mac and ctrl-o on Window/Linux myShortcut, err := keys.Parse(\u0026#34;Ctrl+Option+A\u0026#34;) 快捷键修饰符 # 以下修饰符是可以与快捷键结合使用的键：\nconst ( // CmdOrCtrlKey represents Command on Mac and Control on other platforms CmdOrCtrlKey Modifier = \u0026#34;cmdorctrl\u0026#34; // OptionOrAltKey represents Option on Mac and Alt on other platforms OptionOrAltKey Modifier = \u0026#34;optionoralt\u0026#34; // ShiftKey represents the shift key on all systems ShiftKey Modifier = \u0026#34;shift\u0026#34; // ControlKey represents the control key on all systems ControlKey Modifier = \u0026#34;ctrl\u0026#34; ) 许多辅助方法可用于使用修饰符创建快捷键：\nfunc CmdOrCtrl(key string) *Accelerator func OptionOrAlt(key string) *Accelerator func Shift(key string) *Accelerator func Control(key string) *Accelerator 可以使用 keys.Combo(key string, modifier1 Modifier, modifier2 Modifier, rest ...Modifier) 用以下方式组合修饰符 ：\n// Defines \u0026#34;Ctrl+Option+A\u0026#34; on Mac and \u0026#34;Ctrl+Alt+A\u0026#34; on Window/Linux myShortcut := keys.Combo(\u0026#34;a\u0026#34;, ControlKey, OptionOrAltKey) 菜单类型Type # 每个菜单项必须有一个类型，有 5 种类型可用：\nconst ( TextType Type = \u0026#34;Text\u0026#34; SeparatorType Type = \u0026#34;Separator\u0026#34; SubmenuType Type = \u0026#34;Submenu\u0026#34; CheckboxType Type = \u0026#34;Checkbox\u0026#34; RadioType Type = \u0026#34;Radio\u0026#34; ) 为方便起见，提供了帮助方法来快速创建菜单项：\nfunc Text(label string, accelerator *keys.Accelerator, click Callback) *MenuItem func Separator() *MenuItem func Radio(label string, selected bool, accelerator *keys.Accelerator, click Callback) *MenuItem func Checkbox(label string, checked bool, accelerator *keys.Accelerator, click Callback) *MenuItem func SubMenu(label string, menu *Menu) *Menu 您还可以使用addXXX方法直接在菜单上创建指定类型的菜单项：\nfunc (m *Menu) AddText(label string, accelerator *keys.Accelerator, click Callback) *MenuItem func (m *Menu) AddSeparator() *MenuItem func (m *Menu) AddRadio(label string, selected bool, accelerator *keys.Accelerator, click Callback) *MenuItem func (m *Menu) AddCheckbox(label string, checked bool, accelerator *keys.Accelerator, click Callback) *MenuItem func (m *Menu) AddSubMenu(label string, menu *Menu) *MenuI 回调Callback # type Callback func(*CallbackData) type CallbackData struct { MenuItem *MenuItem } 该函数被赋予一个 CallbackData 结构，该结构指示哪个菜单项触发了回调。 这在使用可能共享回调的单选组时很有用。\n角色Role # **注意：**目前仅 Mac 支持角色。\n一个菜单项可能有一个角色，它本质上是一个预定义的菜单项。 我们目前支持以下角色：\n角色 描述 AppMenuRole 标准的 Mac 应用程序菜单。 可以使用 menu.AppMenu() 创建 EditMenuRole 标准的 Mac 编辑菜单。 可以使用 menu.EditMenu() 创建 MenuSetApplicationMenu 设置应用程序菜单 # 将应用程序菜单设置为给定的菜单。\nGo: MenuSetApplicationMenu(ctx context.Context, menu *menu.Menu)\nMenuUpdateApplicationMenu 更新应用程序菜单 # 获取传递给 MenuSetApplicationMenu 的菜单的任意更改更新应用程序菜单。\nGo: MenuUpdateApplicationMenu(ctx context.Context)\n创建菜单示例 # app := NewApp() // 创建菜单 AppMenu := menu.NewMenu() // 创建子菜单 FileMenu := AppMenu.AddSubmenu(\u0026#34;File\u0026#34;) // 创建类型为 Text 的菜单项 FileMenu.AddText(\u0026#34;\u0026amp;Open\u0026#34;, keys.CmdOrCtrl(\u0026#34;o\u0026#34;), openFile) // 创建分隔线 FileMenu.AddSeparator() // 创建类型为 Text 的菜单项 FileMenu.AddText(\u0026#34;Quit\u0026#34;, keys.CmdOrCtrl(\u0026#34;q\u0026#34;), func(_ *menu.CallbackData) { runtime.Quit(app.ctx) }) // 判断如果是 Mac，则创建默认的角色菜单 if runtime.GOOS == \u0026#34;darwin\u0026#34; { AppMenu.Append(menu.EditMenu()) // on macos platform, we should append EditMenu to enable Cmd+C,Cmd+V,Cmd+Z... shortcut } // 将菜单配置到应用 err := wails.Run(\u0026amp;options.App{ Title: \u0026#34;Menus Demo\u0026#34;, Width: 800, Height: 600, Menu: AppMenu, // reference the menu above Bind: []interface{}{ app, }, ) Clipboard 剪贴板 # 运行时的这一部分提供了对操作系统剪贴板的访问。\n当前实现仅处理文本。\nClipboardGetText 剪贴板获取文本 # 从剪切板读取当前存储的文本。\nGo: ClipboardGetText(ctx context.Context) (string, error) 返回: 一个字符串（如果剪贴板为空，将返回一个空字符串）或一个错误。 JS: ClipboardGetText(): Promise\u0026lt;string\u0026gt; 返回: 带有字符串结果的 Promise（如果剪贴板为空，将返回空字符串）。 ClipboardSetText 剪贴板设置文本 # 将文本写入剪切板。\nGo: ClipboardSetText(ctx context.Context, text string) error\n返回: 如果存在错误，则会出现错误。 JS: ClipboardSetText(text: string): Promise\u0026lt;boolean\u0026gt;\n返回值: 一个Promise，如果文本成功地设置在剪贴板上，结果为 true，否则为 false。 Screen 屏幕 # 这些方法提供有关当前连接屏幕的信息。\n相关对象 # Go - Screen # type Screen struct { IsCurrent bool IsPrimary bool Width int Height int } Ts - Screen # interface Screen { isCurrent: boolean; isPrimary: boolean; width : number height : number } ScreenGetAll # 返回当前连接屏幕的列表。\nGo: ScreenGetAll(ctx context.Context) []screen JS: ScreenGetAll() Drag And Drop拖放 # 运行时的这一部分处理将文件或文件夹拖放到窗口中的操作。\n要启用此功能，必须在 options.App 中将 EnableFileDrop 设置为 true。\nOnFileDrop 注册拖放监听 # 这个方法是注册文件或文件夹拖放到窗口时回调函数\nGo: OnFileDrop(ctx context.Context, callback func(x, y int, paths []string))\nJS: OnFileDrop(callback: (x: number, y: number, paths: string[]) =\u0026gt; void, useDropTarget: boolean) :void\n调用回调函数，其中包含释放拖动时窗口内的坐标和绝对文件路径。\nOnFileDropOff 取消拖放监听 # 此方法会移除所有已注册的拖放事件侦听器和处理程序。\nGo: OnFileDropOff(ctx context.Context) JS: OnFileDropOff(): void ","date":"2025-05-11","externalUrl":null,"permalink":"/posts/2d3150b2/9f158c96/f88084c8/6e9548a6/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e运行时 \n    \u003cdiv id=\"运行时\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%bf%90%e8%a1%8c%e6%97%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e运行时是一个为应用程序提供实用方法的库。 有 Go 和 JavaScript 运行时，目的是在可能的情况下尝试使它们保持一致。\u003c/p\u003e","title":"2、Runtime","type":"posts"},{"content":" Swing 概念 # 实际使用 Java 开发图形界面程序时 ，很少使用 AWT 组件，绝大部分时候都是用 Swing 组件开发的。\nSwing是由100%纯 Java实现的，不再依赖于本地平台的 GUI， 因此可以在所有平台上都保持相同的界面外观。\n独立于本地平台的Swing组件被称为轻量级组件；而依赖于本地平台的 AWT 组件被称为重量级组件。\n由于 Swing 的所有组件完全采用 Java 实现，不再调用本地平台的 GUI，所以导致 Swing 图形界面的显示速度要比 AWT 图形界面的显示速度慢一些，但相对于快速发展的硬件设施而言，这种微小的速度差别无妨大碍。\n使用Swing的优势：\nSwing 组件不再依赖于本地平台的 GUI，无须采用各种平台的 GUI 交集，因此 Swing 提供了大量图形界面组件，远远超出了 AWT 所提供的图形界面组件集。 Swing 组件不再依赖于本地平台 GUI ，因此不会产生与平台相关的 BUG 。 Swing 组件在各种平台上运行时可以保证具有相同的图形界面外观。 Swing 组件采用 MVC（Model-View-Controller）， 即（模型一视图一控制器）设计模式：\n模型（Model）：用于维护组件的各种状态；\n视图（View）：是组件的可视化表现；\n控制器（Controller）：用于控制对于各种事件、组件做出响应 。\n当模型发生改变时，它会通知所有依赖它的视图，视图会根据模型数据来更新自己。Swing使用UI代理来包装视图和控制器， 还有一个模型对象来维护该组件的状态。例如，按钮JButton有一个维护其状态信息的模型ButtonModel对象。 Swing组件的模型是自动设置的，因此一般都使用JButton，而无须关心ButtonModel对象。\nSwing组件继承体系图 # 大部分Swing 组件都是 JComponent抽象类的直接或间接子类（并不是全部的 Swing 组件），JComponent 类定义了所有子类组件的通用方法 ，JComponent 类是 AWT 里 java.awt.Container 类的子类 ，这也是 AWT 和 Swing 的联系之一。 绝大部分 Swing 组件类继承了Container类，所以Swing 组件都可作为容器使用（ JFrame继承了Frame 类）。\nSwing组件和AWT组件的关系 # 大部分情况下，只需要在AWT组件的名称前面加个J，就可以得到其对应的Swing组件名称，但有几个例外：\nJComboBox: 对应于 AWT 里的 Choice 组件，但比 Choice 组件功能更丰富 。​ JFileChooser: 对应于 AWT 里的 FileDialog 组件 。​ JScrollBar: 对应于 AWT 里的 Scrollbar 组件，注意两个组件类名中 b 字母的大小写差别。​ JCheckBox : 对应于 AWT 里的 Checkbox 组件， 注意两个组件类名中 b 字母的大小 写差别 。​ JCheckBoxMenultem: 对应于 AWT 里的 CheckboxMenuItem 组件，注意两个组件类名中 b 字母的大小写差别。 Swing组件分类 # 顶层容器：JFrame、JApplet、JDialog 和 JWindow 。​ 中间容器：JPanel 、 JScrollPane 、 JSplitPane 、 JToolBar 等 。​ 特殊容器：在用户界面上具有特殊作用的中间容器，如 JIntemalFrame 、 JRootPane 、 JLayeredPane和 JDestopPane 等 。​ 基本组件：实现人机交互的组件，如 JButton、 JComboBox 、 JList、 JMenu、 JSlider 等 。​ 不可编辑信息的显示组件：向用户显示不可编辑信息的组件，如JLabel 、 JProgressBar 和 JToolTip等。​ 可编辑信息的显示组件：向用户显示能被编辑的格式化信息的组件，如 JTable 、 JTextArea 和JTextField 等 。​ 特殊对话框组件：可以直接产生特殊对话框的组件，如 JColorChooser 和 JFileChooser 等。 AWT组件的Swing实现 # Swing 为除 Canvas 之外的所有 AWT 组件提供了相应的实现，Swing 组件比 AWT 组件的功能更加强大。相对于 AWT 组件， Swing 组件具有如下 4 个额外的功能：\n可以为 Swing 组件设置提示信息。使用 setToolTipText()方法，为组件设置对用户有帮助的提示信息 。 很多 Swing 组件如按钮、标签、菜单项等，除使用文字外，还可以使用图标修饰自己。为了允许在 Swing 组件中使用图标， Swing为Icon接口提供了 一个实现类: Imagelcon ，该实现类代表一个图像图标。 支持插拔式的外观风格。每个 JComponent 对象都有一个相应的 ComponentUI 对象，为它完成所有的绘画、事件处理、决定尺寸大小等工作。 ComponentUI 对象依赖当前使用的 PLAF ， 使用 UIManager.setLookAndFeel() 方法可以改变图形界面的外观风格 。 支持设置边框。Swing 组件可以设置一个或多个边框。 Swing 中提供了各式各样的边框供用户使用，也能建立组合边框或自己设计边框。 一种空白边框可以用于增大组件，同时协助布局管理器对容器中的组件进行合理的布局。 和 AWT 编码上的区别 # Swing菜单项指定快捷键时必须通过组件名.setAccelerator(keyStroke.getKeyStroke(\u0026quot;大写字母\u0026quot;,InputEvent.CTRL_MASK))方法来设置，其中KeyStroke代表一次击键动作，可以直接通过按键对应字母来指定该击键动作 。 更新JFrame的风格时，调用了 SwingUtilities.updateComponentTreeUI(f.getContentPane());这是因为如果直接更新 JFrame 本身 ，将会导致 JFrame 也被更新， JFrame 是一个特殊的容器 ， JFrame 依然部分依赖于本地平台的图形组件 。如果强制 JFrame 更新，则有可能导致该窗口失去标题栏和边框 。 给组件设置右键菜单，不需要使用监听器，只需要调用组件的setComponentPopupMenu()方法即可，更简单。 关闭JFrame窗口，也无需监听器，只需要调用setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)方法即可，更简单。 如果需要让某个组件支持滚动条，只需要把该组件放入到JScrollPane中，然后使用JScrollPane即可。 Look And Feel # 每个 Swing 组件都有一个对应的UI 类，例如 JButton组件就有一个对应的 ButtonUI 类来作为UI代理 。每个Swing组件的UI代理的类名总是将该 Swing组件类名的 J 去掉，然后在后面添加 UI 后缀 。 UI代理类通常是一个抽象基类 ，不同的 PLAF 会有不同的UI代理实现类 。 Swing 类库中包含了几套UI代理,分别放在不同的包下， 每套UI代理都几乎包含了所有 Swing组件的 ComponentUI实现，每套这样的实现都被称为一种PLAF 实现 。以 JButton 为例，其 UI 代理的继承层次下图：\n如果需要改变程序的外观风格， 则可以使用如下代码：\nJFrame jf = new JFrame(); try { //设置外观风格 UIManager.setLookAndFeel(\u0026#34;com.sun.java.swing.plaf.windows.WindowsLookAndFeel\u0026#34;); //刷新jf容器及其内部组件的外观 SwingUtilities.updateComponentTreeUI(jf); } catch (Exception e) { e.printStackTrace(); } 具体的使用如下：\npublic class LookAndFeelDemo { private JFrame jFrame = new JFrame(\u0026#34;LookAndFeelDemo\u0026#34;); private JTextArea jTextArea = new JTextArea(10,30); private JTextField jTextField = new JTextField(30); private ButtonGroup buttonGroup = new ButtonGroup(); private JCheckBox boy = new JCheckBox(\u0026#34;男\u0026#34;,true); private JCheckBox girl = new JCheckBox(\u0026#34;女\u0026#34;,false); private JComboBox\u0026lt;String\u0026gt; jComboBox = new JComboBox(); private JList\u0026lt;String\u0026gt; jList = new JList\u0026lt;\u0026gt;(); private JButton jButton = new JButton(\u0026#34;确定\u0026#34;); private void init(){ buttonGroup.add(boy); buttonGroup.add(girl); Box bottom = Box.createHorizontalBox(); bottom.add(boy); bottom.add(girl); jComboBox.addItem(\u0026#34;Metal 风格\u0026#34;); jComboBox.addItem(\u0026#34;Nimbus 风格\u0026#34;); jComboBox.addItem(\u0026#34;Windows 风格\u0026#34;); jComboBox.addItem(\u0026#34;Windows 经典风格\u0026#34;); jComboBox.addItem(\u0026#34;Motif 风格\u0026#34;); bottom.add(jComboBox); bottom.add(jButton); Box center = Box.createVerticalBox(); jTextArea.setBorder(BorderFactory.createLineBorder(Color.BLACK)); center.add(jTextArea); center.add(jTextField); jList.setListData(new String[]{\u0026#34;Windows\u0026#34;,\u0026#34;Linux\u0026#34;,\u0026#34;MacOS\u0026#34;}); jList.setBorder(BorderFactory.createLineBorder(Color.BLACK)); jFrame.add(center,BorderLayout.CENTER); jFrame.add(bottom,BorderLayout.SOUTH); jFrame.add(jList,BorderLayout.EAST); jButton.addActionListener(e -\u0026gt; { String actionCommand = jComboBox.getActionCommand(); changeLookAndFeel(actionCommand); }); jFrame.pack(); jFrame.setVisible(true); } private void changeLookAndFeel(String actionCommand){ try { switch (actionCommand){ case \u0026#34;Metal 风格\u0026#34;: UIManager.setLookAndFeel(\u0026#34;javax.swing.plaf.metal.MetalLookAndFeel\u0026#34;); break; case \u0026#34;Nimbus 风格\u0026#34;: UIManager.setLookAndFeel(\u0026#34;javax.swing.plaf.nimbus.NimbusLookAndFeel\u0026#34;); break; case \u0026#34;Windows 风格\u0026#34;: UIManager.setLookAndFeel(\u0026#34;com.sun.java.swing.plaf.windows.WindowsLookAndFeel\u0026#34;); break; case \u0026#34;Windows 经典风格\u0026#34;: UIManager.setLookAndFeel(\u0026#34;com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel\u0026#34;); break; case \u0026#34;Motif 风格\u0026#34;: UIManager.setLookAndFeel(\u0026#34;com.sun.java.swing.plaf.motif.MotifLookAndFeel\u0026#34;); break; } SwingUtilities.updateComponentTreeUI(jFrame.getContentPane()); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { new LookAndFeelDemo().init(); } } 图标 # ImageIcon # 在Java Swing中，图标（Icon）通常用于按钮、标签和其他组件上以增强用户界面。Java Swing提供了多种方式来使用图标，包括直接使用Icon接口、使用ImageIcon类，以及使用Swing的内置图标类。\npublic class IconDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;IconDemo\u0026#34;); // 从 classpath 下加载图标 ImageIcon imageIcon = new ImageIcon(IconDemo.class.getClassLoader().getResource(\u0026#34;user.png\u0026#34;)); JButton jButton = new JButton(\u0026#34;图标按钮\u0026#34;,imageIcon); jFrame.add(jButton); jFrame.pack(); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.setVisible(true); } } 内置图标类 # Swing提供了一些内置的图标类，如UIManager中的图标，这些图标通常用于标准的UI组件。\npublic class IconDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;IconDemo\u0026#34;); // 获取内置图标，例如 Metal 主题的文件夹图标 Icon icon = MetalIconFactory.getTreeFolderIcon(); JButton jButton = new JButton(\u0026#34;图标按钮\u0026#34;,icon); jFrame.add(jButton); jFrame.pack(); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.setVisible(true); } } 组件边框 # 很多情况下，我们常常喜欢给不同的组件设置边框，从而让界面的层次感更明显，swing中提供了Border对象来代表一个边框，下图是Border的继承体系图\n特殊的Border： TitledBorder：它的作用并不是直接为其他组件添加边框，而是为其他边框设置标题，创建该类的对象时，需要传入一个其他的Border对象 ComoundBorder：用来组合其他两个边框，创建该类的对象时，需要传入其他两个Border对象，一个作为内边框，一个作为外边框 给组件设置边框步骤： 使用BorderFactory或者XxxBorder创建Border的实例对象； 调用Swing组件的setBorder(Border b)方法为组件设置边框； public class BorderDemo { JFrame jf = new JFrame(\u0026#34;测试边框\u0026#34;); public void init(){ //设置Jframe为网格布局 jf.setLayout(new GridLayout(2,4)); //创建凸起的斜边框，分别设置四条边的颜色 Border bb = BorderFactory.createBevelBorder(BevelBorder.RAISED,Color.RED,Color.GREEN,Color.BLUE,Color.GRAY); jf.add(getPanelWithBorder(bb,\u0026#34;BevelBorder\u0026#34;)); //创建LineBorder Border lb = BorderFactory.createLineBorder(Color.ORANGE, 10); jf.add(getPanelWithBorder(lb,\u0026#34;LineBorder\u0026#34;)); //创建EmptyBorder，会在组件的四周留白 Border eb = BorderFactory.createEmptyBorder(20, 5, 10, 30); jf.add(getPanelWithBorder(eb,\u0026#34;EmptyBorder\u0026#34;)); //创建EtchedBorder， Border etb = BorderFactory.createEtchedBorder(EtchedBorder.RAISED, Color.RED, Color.GREEN); jf.add(getPanelWithBorder(etb,\u0026#34;EtchedBorder\u0026#34;)); //创建TitledBorder,为原有的Border添加标题 TitledBorder tb = new TitledBorder(lb,\u0026#34;测试标题\u0026#34;,TitledBorder.LEFT,TitledBorder.BOTTOM,new Font(\u0026#34;StSong\u0026#34;,Font.BOLD,18),Color.BLUE); jf.add(getPanelWithBorder(tb,\u0026#34;TitledBorder\u0026#34;)); //直接创建MatteBorder，它是EmptyBorder的子类，EmptyBorder是留白，而MatteBorder可以给留空的区域填充颜色 MatteBorder mb = new MatteBorder(20,5,10,30,Color.GREEN); jf.add(getPanelWithBorder(mb,\u0026#34;MatteBorder\u0026#34;)); //直接创创建CompoundBorder，将两个边框组合成新边框 CompoundBorder cb = new CompoundBorder(new LineBorder(Color.RED,8),tb); jf.add(getPanelWithBorder(cb,\u0026#34;CompoundBorder\u0026#34;)); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.pack(); jf.setVisible(true); } public JPanel getPanelWithBorder(Border border,String borderName){ JPanel jPanel = new JPanel(); jPanel.add(new JLabel(borderName)); //为panel设置边框 jPanel.setBorder(border); return jPanel; } public static void main(String[] args) { new BorderDemo().init(); } } JToolBar工具条 # Swing 提供了JToolBar类来创建工具条，并且可以往JToolBar中添加多个工具按钮。\n方法名称 方法功能 JToolBar(String name,int orientation) 创建一个名字为name，方向为orientation的工具条对象，其orientation的是取值可以是SwingConstants.HORIZONTAL或SwingConstants.VERTICAL JButton add(Action a) 通过Action对象为JToolBar工具条添加对应的工具按钮 addSeparator(Dimension size) 向工具条中添加指定大小的分隔符 setFloatable(boolean b) 设定工具条是否可以被拖动 setMargin(Insets m) 设置工具条与工具按钮的边距 setOrientation(int o) 设置工具条的方向 setRollover(boolean rollover) 设置此工具条的rollover状态 public class JToolBarDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;JToolBarDemo\u0026#34;); // 创建一个文本域 JTextArea jTextArea = new JTextArea(6,30); // 给文本域设置滚动条 JScrollPane jScrollPane = new JScrollPane(jTextArea); // 创建工具条 JToolBar jToolBar = new JToolBar(); // 创建两个按钮 Action pre = new AbstractAction(\u0026#34;上一页\u0026#34;) { @Override public void actionPerformed(ActionEvent e) { jTextArea.append(\u0026#34;上一页\\n\u0026#34;); } }; Action next = new AbstractAction(\u0026#34;下一页\u0026#34;) { @Override public void actionPerformed(ActionEvent e) { jTextArea.append(\u0026#34;下一页\\n\u0026#34;); } }; // 将按钮添加到工具条中 jToolBar.add(pre); jToolBar.add(next); jFrame.add(jToolBar, BorderLayout.NORTH); jFrame.add(jScrollPane, BorderLayout.CENTER); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.pack(); jFrame.setVisible(true); } } JColorChooser # JColorChooser 用于创建颜色选择器对话框，该类的用法非常简单，只需要调用它的静态方法就可以快速生成一个颜色选择对话框\n/* 参数： componet:指定当前对话框的父组件 title：当前对话框的名称 initialColor：指定默认选中的颜色 返回值： 返回用户选中的颜色 */ public static Color showDialog(Component component,String title,Color initialColor) public class JColorChooserDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;JColorChooserDemo\u0026#34;); JTextArea jTextArea = new JTextArea(6,30); JButton jButton = new JButton(\u0026#34;选择颜色\u0026#34;); jButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Color result = JColorChooser.showDialog(jFrame, \u0026#34;选择颜色\u0026#34;, jTextArea.getBackground()); if (result != null) { jTextArea.setBackground(result); } } }); jFrame.add(jTextArea); jFrame.add(jButton, BorderLayout.SOUTH); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.pack(); jFrame.setVisible(true); } } JFileChooser # JFileChooser 的功能与AWT中的 FileDialog 基本相似，也是用于生成\u0026quot;打开文件\u0026quot;、\u0026ldquo;保存文件\u0026quot;对话框。与 FileDialog 不同的是 ， JFileChooser 无须依赖于本地平台的 GUI，它由 100%纯 Java 实现，在所有平台 上具有完全相同的行为，并可以在所有平台上具有相同的外观风格。\nJFileChooser使用步骤如下：\n1、创建JFileChooser对象\nJFileChooser chooser = new JFileChooser(\u0026#34;D:\\\\a\u0026#34;);//指定默认打开的本地磁盘路径 2、调用JFileChooser的方法，进行初始化\n// 设定默认选中的文件 setSelectedFile(File file); // 设定默认选中的多个文件 setSelectedFiles(File[] selectedFiles); // 设置是否允许多选，默认是单选 setMultiSelectionEnabled(boolean b); // 设置可以选择内容，例如文件、文件夹等，默认只能选择文件 setFileSelectionMode(int mode)： 3、打开文件对话框\n// 打开文件加载对话框，并指定父组件 showOpenDialog(Component parent); // 打开文件保存对话框，并指定父组件 showSaveDialog(Component parent); 4、获取用户选择的结果\n// 获取用户选择的一个文件 File getSelectedFile(); // 获取用户选择的多个文件 File[] getSelectedFiles(); showOpenDialog()和showSaveDialog() 返回以下常量：\nJFileChooser.APPROVE_OPTION：用户点击确认按钮（如“打开”或“确定”），表示成功选择文件。 JFileChooser.CANCEL_OPTION：用户点击取消按钮或关闭对话框，表示放弃选择。 JFileChooser.ERROR_OPTION：对话框因异常关闭（如权限问题），表示操作失败。 public class IconDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;IconDemo\u0026#34;); JFileChooser jFileChooser = new JFileChooser(\u0026#34;.\u0026#34;); JButton b1 = new JButton(\u0026#34;选择文件\u0026#34;); JButton b2 = new JButton(\u0026#34;选择文件夹\u0026#34;); JButton b3 = new JButton(\u0026#34;选择图片\u0026#34;); JButton b4 = new JButton(\u0026#34;保存文件\u0026#34;); b1.addActionListener(e -\u0026gt; { jFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); if (jFileChooser.showOpenDialog(jFrame) == JFileChooser.APPROVE_OPTION) { System.out.println(\u0026#34;文件路径：\u0026#34; + jFileChooser.getSelectedFile().getAbsolutePath()); } }); b2.addActionListener(e -\u0026gt; { jFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); if (jFileChooser.showOpenDialog(jFrame) == JFileChooser.APPROVE_OPTION) { System.out.println(\u0026#34;文件夹路径：\u0026#34; + jFileChooser.getSelectedFile().getAbsolutePath()); } }); b3.addActionListener(e -\u0026gt; { jFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); jFileChooser.setFileFilter(new FileFilter() { @Override public boolean accept(File f) { return f.getName().endsWith(\u0026#34;.jpg\u0026#34;) || f.getName().endsWith(\u0026#34;.png\u0026#34;); } @Override public String getDescription() { return \u0026#34;图片文件\u0026#34;; } }); if (jFileChooser.showOpenDialog(jFrame) == JFileChooser.APPROVE_OPTION) { System.out.println(\u0026#34;文件路径：\u0026#34; + jFileChooser.getSelectedFile().getAbsolutePath()); } }); b4.addActionListener(e -\u0026gt; { jFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); if (jFileChooser.showSaveDialog(jFrame) == JFileChooser.APPROVE_OPTION) { System.out.println(\u0026#34;文件路径：\u0026#34; + jFileChooser.getSelectedFile().getAbsolutePath()); } }); Box verticalBox = Box.createVerticalBox(); verticalBox.add(b1); verticalBox.add(b2); verticalBox.add(b3); verticalBox.add(b4); jFrame.add(verticalBox); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.pack(); jFrame.setVisible(true); } } JOptionPane # 通过 JOptionPane 可以非常方便地创建一些简单的对话框， Swing 已经为这些对话框添加了相应的组件，无须程序员手动添加组件。 JOptionPane 提供了如下 4 个方法来创建对话框 。\n方法名称 方法功能 showMessageDialogshowInternalMessageDialog 消息对话框 ，告知用户某事己发生 ， 用户只能单击\u0026quot;确定\u0026quot;按钮 ， 类似于 JavaScript 的 alert 函数 。 showConfirmDialog\nshowInternalConfirmDialog 确认对话框，向用户确认某个问题，用户可以选择 yes 、 no、 cancel 等选项 。 类似于 JavaScript 的 comfirm 函数 。该方法返回用户单击了 哪个按钮 showInputDialog\nshowInternalInputDialog 输入对话框，提示要求输入某些信息，类似于 JavaScript的 prompt 函数。该方法返回用户输入的字符串 。 showOptionDialog\nshowInternalOptionDialog 自定义选项对话框 ，允许使用自 定义选项 ，可以取代showConfirmDialog 所产生的对话框，只是用起来更复杂 。 上述方法都有都有很多重载形式，选择其中一种最全的形式，参数解释如下：\nshowXxxDialog( Component parentComponent, Object message, String title, int optionType, int messageType, Icon icon, Object[] options, Object initialValue ) parentComponent：当前对话框的父组件 message：对话框上显示的信息，信息可以是字符串、组件、图片等 title：当前对话框的标题 optionType：当前对话框上显示的按钮类型（DEFAULT_OPTION、YES_NO_OPTION、YES_NO_CANCEL_OPTION、OK_CANCEL_OPTION） messageType：当前对话框的类型（ERROR_MESSAGE、INFORMATION_MESSAGE、WARNING_MESSAGE、QUESTION_MESSAGE、PLAIN_MESSAGE） icon：当前对话框左上角的图标 options：自定义下拉列表的选项 initialValue：自定义选项中的默认选中项 当用户与对话框交互结束后，不同类型对话框的返回值如下：\nshowMessageDialog：无返回值 。 showlnputDialog：返回用户输入或选择的字符串 。 showConfirmDialog：返回一个整数代表用户选择的选项 。 showOptionDialog：返回一个整数代表用户选择的选项，如果用户选择第一项，则返回 0; 如果选择第二项，则返回1……依此类推 。 对 showConfirmDialog 所产生的对话框，有如下几个返回值：\nYES_OPTION：用户单击了\u0026quot;是\u0026quot;按钮后返回 。 NO_OPTION：用户单击了\u0026quot;否\u0026quot;按钮后返回 。 CANCEL_OPTION：用户单击了\u0026quot;取消\u0026quot;按钮后返回 。 OK_OPTION：用户单击了\u0026quot;确定\u0026quot;按钮后返回 。 CLOSED_OPTION：用户单击了对话框右上角的\u0026quot;X\u0026quot;按钮后返回。 消息对话框 # public class MessageDialogDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;MessageDialogDemo\u0026#34;); JButton b1 = new JButton(\u0026#34;ERROR_MESSAGE\u0026#34;); JButton b2 = new JButton(\u0026#34;INFORMATION_MESSAGE\u0026#34;); JButton b3 = new JButton(\u0026#34;WARNING_MESSAGE\u0026#34;); JButton b4 = new JButton(\u0026#34;QUESTION_MESSAGE\u0026#34;); JButton b5 = new JButton(\u0026#34;PLAIN_MESSAGE\u0026#34;); ActionListener actionListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { switch (e.getActionCommand()) { case \u0026#34;ERROR_MESSAGE\u0026#34;: JOptionPane.showMessageDialog(jFrame, \u0026#34;这是一个错误信息\u0026#34;, \u0026#34;错误信息\u0026#34;, JOptionPane.ERROR_MESSAGE); break; case \u0026#34;INFORMATION_MESSAGE\u0026#34;: JOptionPane.showMessageDialog(jFrame, \u0026#34;这是一个信息提示\u0026#34;, \u0026#34;信息提示\u0026#34;, JOptionPane.INFORMATION_MESSAGE); break; case \u0026#34;WARNING_MESSAGE\u0026#34;: JOptionPane.showMessageDialog(jFrame, \u0026#34;这是一个警告信息\u0026#34;, \u0026#34;警告信息\u0026#34;, JOptionPane.WARNING_MESSAGE); break; case \u0026#34;QUESTION_MESSAGE\u0026#34;: JOptionPane.showMessageDialog(jFrame, \u0026#34;这是一个询问信息\u0026#34;, \u0026#34;询问信息\u0026#34;, JOptionPane.QUESTION_MESSAGE); break; case \u0026#34;PLAIN_MESSAGE\u0026#34;: JOptionPane.showMessageDialog(jFrame, \u0026#34;这是一个普通信息\u0026#34;, \u0026#34;普通信息\u0026#34;, JOptionPane.PLAIN_MESSAGE); break; } } }; b1.addActionListener(actionListener); b2.addActionListener(actionListener); b3.addActionListener(actionListener); b4.addActionListener(actionListener); b5.addActionListener(actionListener); Box verticalBox = Box.createVerticalBox(); verticalBox.add(b1); verticalBox.add(b2); verticalBox.add(b3); verticalBox.add(b4); verticalBox.add(b5); jFrame.add(verticalBox); jFrame.pack(); jFrame.setVisible(true); } } 确认对话框 # public class ConfirmDialogDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;ConfirmDialogDemo\u0026#34;); JButton jButton = new JButton(\u0026#34;确认对话框\u0026#34;); jButton.addActionListener(e -\u0026gt; { int result = JOptionPane.showConfirmDialog(jFrame, \u0026#34;确认对话框\u0026#34;, \u0026#34;确认对话框\u0026#34;, JOptionPane.YES_NO_OPTION); if (result == JOptionPane.YES_OPTION) { System.out.println(\u0026#34;点击了确认\u0026#34;); } else if (result == JOptionPane.NO_OPTION) { System.out.println(\u0026#34;点击了取消\u0026#34;); }else if (result == JOptionPane.CLOSED_OPTION) { System.out.println(\u0026#34;点击了关闭\u0026#34;); }else if (result == JOptionPane.CANCEL_OPTION) { System.out.println(\u0026#34;点击了取消\u0026#34;); }else if (result == JOptionPane.OK_OPTION) { System.out.println(\u0026#34;点击了确定\u0026#34;); } }); jFrame.add(jButton); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.pack(); jFrame.setVisible(true); } } 输入对话框 # public class InputDialogDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;InputDialogDemo\u0026#34;); JTextArea jTextArea = new JTextArea(6,30); JButton jButton = new JButton(\u0026#34;输入对话框\u0026#34;); jButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String result = JOptionPane.showInputDialog(jFrame, \u0026#34;请输入内容\u0026#34;, \u0026#34;输入对话框\u0026#34;, JOptionPane.INFORMATION_MESSAGE); if (result != null) { jTextArea.setText(result); } } }); jFrame.add(jTextArea); jFrame.add(jButton, BorderLayout.SOUTH); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.pack(); jFrame.setVisible(true); } } public class InputDialogDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;InputDialogDemo\u0026#34;); JTextArea jTextArea = new JTextArea(6,30); JButton jButton = new JButton(\u0026#34;输入对话框\u0026#34;); jButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Object o = JOptionPane.showInputDialog( jFrame, \u0026#34;输入对话框\u0026#34;, \u0026#34;输入框标题\u0026#34;, JOptionPane.INFORMATION_MESSAGE, null, new String[]{\u0026#34;选项1\u0026#34;, \u0026#34;选项2\u0026#34;, \u0026#34;选项3\u0026#34;}, \u0026#34;选项1\u0026#34;); if (o != null) { jTextArea.setText(o.toString()); } } }); jFrame.add(jTextArea); jFrame.add(jButton, BorderLayout.SOUTH); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.pack(); jFrame.setVisible(true); } } 选项对话框 # public class OptionDialogDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;OptionDialogDemo\u0026#34;); JButton jButton = new JButton(\u0026#34;选项对话框\u0026#34;); jButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int i = JOptionPane.showOptionDialog( jFrame, \u0026#34;选项对话框\u0026#34;, \u0026#34;选项框标题\u0026#34;, JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null, new String[]{\u0026#34;选项1\u0026#34;, \u0026#34;选项2\u0026#34;, \u0026#34;选项3\u0026#34;}, \u0026#34;选项1\u0026#34; ); if (i == 0){ System.out.println(\u0026#34;点击了选项1\u0026#34;); }else if (i == 1){ System.out.println(\u0026#34;点击了选项2\u0026#34;); }else if (i == 2){ System.out.println(\u0026#34;点击了选项3\u0026#34;); } } }); jFrame.add(jButton); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.pack(); jFrame.setVisible(true); } } Swing中的特殊容器 # Swing提供了一些具有特殊功能的容器 ， 这些特殊容器可以用于创建一些更复杂的用户界面。\nJSplitPane # JSplitPane 用于创建一个分割面板，它可以将一个组件(通常是一个容器)分割成两个部分，并提供一个分割条，用户可以拖动该分割条来调整两个部分的大小。\n使用步骤如下：\n1、创建JSplitPane对象\nJSplitPane(int newOrientation, Component newLeftComponent,Component newRightComponent); newOrientation：指定JSplitPane容器的分割方向 JSplitPane.VERTICAL_SPLIT：为纵向分割； JSplitPane.HORIZONTAL_SPLIT：为横向分割； newLeftComponent：左侧或者上侧的组件； newRightComponent：右侧或者下侧的组件； 2、设置是否开启连续布局的支持（可选）\n默认是关闭的，如果设置为true，则打开连续布局的支持，但由于连续布局支持需要不断的重绘组件，所以效率会低一些\nsetContinuousLayout(boolean newContinuousLayout); 3、设置是否支持一触即展的支持（可选）\n默认是关闭的，如果设置为true，则打开\u0026quot;一触即展\u0026quot;的支持\nsetOneTouchExpandable(boolean newValue); 4、其他设置\n//设置分隔条的位置为JSplitPane的某个百分比 setDividerLocation(double proportionalLocation); //通过像素值设置分隔条的位置 setDividerLocation(int location); //通过像素值设置分隔条的大小 setDividerSize(int newSize); //设置指定位置的组件 setLeftComponent(Component comp); setTopComponent(Component comp); setRightComponent(Component comp); setBottomComponent(Component comp) public class JSplitPaneDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;JSplitPaneDemo\u0026#34;); JTextArea t1 = new JTextArea(6,30); JTextArea t2 = new JTextArea(6,30); // 创建分割面板 JSplitPane jSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,t1,t2); // 设置分割面板的位置 jSplitPane.setDividerLocation(0.5); // 设置分割面板的连续布局 jSplitPane.setContinuousLayout(true); // 设置分割面板的展开方式 jSplitPane.setOneTouchExpandable(true); jFrame.add(jSplitPane); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.pack(); jFrame.setVisible(true); } } JTabbedPane # JTabbedPane可以很方便地在窗口上放置多个标签页，每个标签页相当于获得了一个与外部容器具有相同大小的组件摆放区域。\n使用步骤如下：\n1、创建JTabbedPane对象\nJTabbedPane(int tabPlacement, int tabLayoutPolicy); tabPlacement：指定标签标题的放置位置，可以选择 SwingConstants中的四个常量：TOP、LEFT、BOTTOM、RIGHT tabLaoutPolicy：指定当窗口不能容纳标签页标题时的布局策略，可以选择JTabbedPane.WRAP_TAB_LAYOUT和JTabbedPane.SCROLL_TAB_LAYOUT 2、通过JTabbedPane对象堆标签进行增删改查\n// 添加标签 addTab(String title, Icon icon, Component component, String tip); title：标签的名称 icon：标签的图标 component：标签对应的组件 tip：光标放到标签上的提示 // 插入标签页 insertTab(String title, Icon icon, Component component, String tip, int index); title：标签的名称 icon：标签的图标 component：标签对应的组件 tip：光标放到标签上的提示 index：在哪个索引处插入标签页 // 修改标签页对应的组件 setComponentAt(int index, Component component); index：修改哪个索引处的标签 component：标签对应的组件 // 删除标签 removeTabAt(int index); index：删除哪个索引处的标签 3、设置当前显示的标签页\nsetSelectedIndex(int index); 4、设置JTabbedPane的其他属性\n// 将指定位置的禁用图标设置为icon，该图标也可以是null表示不使用禁用图标。 setDisabledIconAt(int index, Icon disabledIcon); // 设置指定位置的标签页是否启用。 setEnabledAt(int index, boolean enabled); // 设置指定位置标签页的标题为 title，该title可以是null,这表明设置该标签页的标题为空。 setTitleAt(int index, String title); // 设置指定位置标签页的提示文本。 setToolTipTextAt(int index, String toolTipText); 5、为JTabbedPane设置监听器\naddChangeListener(ChangeListener l); public class JTabbedPaneDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;JTabbedPaneDemo\u0026#34;); JTabbedPane jTabbedPane = new JTabbedPane(SwingConstants.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); JButton add = new JButton(\u0026#34;添加Tab\u0026#34;); add.addActionListener(e -\u0026gt; { JTextArea jTextArea = new JTextArea(5,30); jTabbedPane.add(\u0026#34;Tab\u0026#34; + jTabbedPane.getTabCount(), jTextArea); }); JButton remove = new JButton(\u0026#34;删除Tab\u0026#34;); remove.addActionListener(e -\u0026gt; { if (jTabbedPane.getTabCount() \u0026gt; 0) { jTabbedPane.remove(jTabbedPane.getTabCount() - 1); } }); jFrame.add(jTabbedPane); Box horizontalBox = Box.createHorizontalBox(); horizontalBox.add(add); horizontalBox.add(remove); jFrame.add(horizontalBox, BorderLayout.SOUTH); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.setSize(400, 300); jFrame.setVisible(true); } } JLayeredPane # JLayeredPane是 一个代表有层次深度的容器，它允许组件在需要时互相重叠。当向JLayeredPane容器中添加组件时，需要为该组件指定一个深度索引 ， 其中层次索引较高的层里的组件位于其他层的组件之上。\nJLayeredPane 还将容器的层次深度分成几个默认层 ，程序只是将组件放入相应的层，从而可以更容易地确保组件的正确重叠，无须为组件指定具体的深度索引。JLayeredPane 提供了如下几个默认层：\nDEFAULT_LAYER：大多数组件位于标准层，这是最底层； PALETTE_LAYER ： 调色板层位于默认层之上 。该层对于浮动工具栏和调色板很有用，因此可以位于其他组件之上 。 MODAL_LAYER：该层用于显示模式对话框。它们将出现在容器中所有工具栏 、调色板或标准组件的上面 。 POPUP_LAYER ： 该层用于显示右键菜单，与对话框 、工具提示和普通组件关联的弹出式窗口将出现在对应的对话框、工具提示和普通组件之上。 DRAG_LAYER：该层用于放置拖放过程中的组件，拖放操作中的组件位于所有组件之上。 一旦拖放操作结束后，该组件将重新分配到其所属的正常层。 JLayeredPane 方法：\nmoveToBack(Component c)：把当前组件c移动到所在层的所有组件的最后一个位置； moveToFront(Component c)：把当前组件c移动到所在层的所有组件的第一个位置； setLayer(Component c, int layer)：更改组件c所处的层； 需要注意的是，往JLayeredPane中添加组件，如果要显示，则必须手动设置该组件在容器中显示的位置以及大小。\npublic class JLayeredPanelDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;JLayeredPanelDemo\u0026#34;); JLayeredPane jLayeredPane = new JLayeredPane(); jLayeredPane.add(createJPanel(Color.RED), JLayeredPane.DEFAULT_LAYER); jLayeredPane.add(createJPanel(Color.GREEN), JLayeredPane.MODAL_LAYER); jLayeredPane.add(createJPanel(Color.BLUE), JLayeredPane.PALETTE_LAYER); jLayeredPane.setPreferredSize(new Dimension(300, 300)); jFrame.add(jLayeredPane); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.pack(); jFrame.setVisible(true); } public static JPanel createJPanel(Color backgroundColor){ JPanel jPanel = new JPanel(); jPanel.setBackground(backgroundColor); jPanel.setSize(100, 100); return jPanel; } } JDesktopPane和JInternalFrame # JDesktopPane是JLayeredPane的子类，这种容器在开发中会更常用很多应用程序都需要启动多个内部窗口来显示信息（典型的比如IDEA、NotePad++），这些内部窗口都属于同一个外部窗口，当外部窗口最小化时，这些内部窗口都被隐藏起来。\n在 Windows 环境中，这种用户界面被称为多文档界面 （Multiple Document Interface, MDI）。\n使用 Swing 可以非常简单地创建出这种 MDI 界面，通常，内部窗口有自己的标题栏、标题、图标、三个窗口按钮，并允许拖动改变内部窗口的大小和位置，但内部窗口不能拖出外部窗口。\nJDesktopPane 需要和 JInternalFrame 结合使用，其中JDesktopPane 代表一个虚拟桌面 ，而JInternalFrame则用于创建内部窗口。使用 JDesktopPane 和 JInternalFrame 创建内部窗口按如下步骤进行即可:\n1、创建 一 个 JDesktopPane 对象，代表虚拟桌面\nJDesktopPane() 2、使用 JInternalFrame 创建一个内部窗口\nJInternalFrame(String title, boolean resizable, boolean closable, boolean maximizable, boolean iconifiable); title：内部窗口标题 resizable：是否可改变大小 closeble： 是否可关闭 maximizable： 是否可最大化 iconifiable：是否可最小化 3、一旦获得了内部窗口之后，该窗口的用法和普通窗口的用法基本相似， 一样可以指定该窗口的布局管理器， 一样可以向窗口内添加组件、改变窗口图标等。\n4、将该内部窗口以合适大小、在合适位置显示出来。与普通窗口类似的是， 该窗口默认大小是0x0像素，位于(0,0)位置（虚拟桌面的左上角处），并且默认处于隐藏状态，程序可以通过如下代码将内部窗口显示出来：\n// 设置内部窗口的大小以及在外部窗口中的位置 reshape(int x, int y, int width, int height); // 设置内部窗口可见 show(); 5、将内部窗口JInternalFrame添加到 JDesktopPane 容器中，再将 JDesktopPane 容器添加到其他容器中。\npublic class JInternalFrameDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;JInternalFrameDemo\u0026#34;); JButton jButton = new JButton(\u0026#34;创建内部窗口\u0026#34;); JDesktopPane jDesktopPane = new JDesktopPane(); jButton.addActionListener(e -\u0026gt; { JInternalFrame jInternalFrame = new JInternalFrame(\u0026#34;内部窗口\u0026#34;,true,true,true,true); jInternalFrame.setSize(300,200); jDesktopPane.add(jInternalFrame); jInternalFrame.setVisible(true); }); jFrame.add(jDesktopPane); jFrame.add(jButton, BorderLayout.SOUTH); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.setSize(300,300); jFrame.setVisible(true); } } 进度条 # JProgressBar # 使用JProgressBar创建进度条的步骤如下：\n1、创建JProgressBar对象\npublic JProgressBar(int orient, int min, int max); orint：方向 min：最小值 max：最大值 2、设置属性\n// 设置进度条是否有边框 setBorderPainted(boolean b); // 设置当前进度条是不是进度不确定的进度条，如果是，则将看到一个滑块在进度条中左右移动 setIndeterminate(boolean newValue); // 设置进度条是否显示当前完成的百分比 setStringPainted(boolean b); 3、获取和设置当前进度条的进度状态\n// 设置当前进度值 setValue(int n); // 获取进度条的完成百分比 double getPercentComplete(); // 返回进度字符串的当前值 String getStrin(); public class JProgressBarDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;JProgressBarDemo\u0026#34;); JProgressBar pb1 = new JProgressBar(); JProgressBar pb2 = new JProgressBar(); pb2.setBorderPainted(false); JProgressBar pb3 = new JProgressBar(); pb3.setBorderPainted(true); pb3.setStringPainted(true); JProgressBar pb4 = new JProgressBar(); pb4.setIndeterminate(true); Box verticalBox = Box.createVerticalBox(); verticalBox.add(pb1); verticalBox.add(pb2); verticalBox.add(pb3); verticalBox.add(pb4); jFrame.add(verticalBox); jFrame.pack(); jFrame.setVisible(true); // 创建一个线程模拟操作 MyThread myThread = new MyThread(); new Thread(myThread).start(); // 定时器去获取线程的属性值 new Timer(100, e -\u0026gt; { pb1.setValue(myThread.value); pb2.setValue(myThread.value); pb3.setValue(myThread.value); pb4.setValue(myThread.value); }).start(); } static class MyThread implements Runnable { public volatile int value = 0; @Override public void run() { while (value \u0026lt; 100){ try { Thread.sleep(100); value++; } catch (InterruptedException e) { e.printStackTrace(); } } } } } ProgressMonitor # ProgressMonitor的用法与JProgressBar的用法基本相似，只是ProgressMonitor可以直接创建一个进度对话框，它提供了下面的构造器完成对话框的创建：\npublic ProgressMonitor(Component parentComponent,Object message,String note, int min,int max); parentComponent：对话框的父组件 message：对话框的描述信息 note：对话框的提示信息 min：进度条的最小值 max：进度条的最大值 public class ProgressMonitorDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;ProgressMonitorDemo\u0026#34;); JButton jButton = new JButton(\u0026#34;开始\u0026#34;); jButton.addActionListener(e -\u0026gt; { ProgressMonitor progressMonitor = new ProgressMonitor(jFrame, \u0026#34;进度条\u0026#34;, \u0026#34;正在加载...\u0026#34;, 0, 100); MyTask myTask = new MyTask(); new Thread(myTask).start(); Timer timer = new Timer(100, a -\u0026gt; { progressMonitor.setProgress(myTask.progress); }); timer.start(); }); jFrame.add(jButton); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.setSize(300,300); jFrame.setVisible(true); } static class MyTask implements Runnable{ private volatile int progress = 0; @Override public void run() { while (progress \u0026lt; 100){ try { Thread.sleep(100); }catch (Exception e){ e.printStackTrace(); } progress++; } } } } JList、JComboBox实现列表框 # 无论从哪个角度来看， JList 和 JComboBox 都是极其相似的，它们都有一个列表框，只是 JComboBox的列表框需要 以下拉方式显示出来； JList 和 JComboBox 都可以通过调用 setRenderer方法来改变列表项的表现形式 。甚至维护这两个组件的 Model 都是相似的， JList 使用 ListModel, JComboBox 使用ComboBoxModel ，而 ComboBoxModel 是 ListModel 的子类 。\n使用JList或JComboBox实现简单列表框的步骤如下：\n1、创建JList或JComboBox对象\n// 创建JList对象，把listData数组中的每项内容转换成一个列表项展示 JList(final E[] listData); // 创建JList对象，把listData数组中的每项内容转换成一个列表项展示 JList(final Vector\u0026lt;? extends E\u0026gt; listData); JComboBox(E[] items); JComboBox(Vector\u0026lt;E\u0026gt; items); 2、设置JList或JComboBox的外观行为\n---------------------------JList---------------------------------------------- // 在已经选中列表项的基础上，增加选中从anchor到lead索引范围内的所有列表项 addSelectionInterval(int anchor, int lead); // 设置列表项的高度和宽度 setFixedCellHeight(int height)/setFixedCellWidth(int width); // 设置列表框的布局方向 setLayoutOrientation(int layoutOrientation); // 设置默认选中项 setSelectedIndex(int index); // 设置默认选中的多个列表项 setSelectedIndices(int[] indices); // 设置默认选中项，并滚动到该项显示 setSelectedValue(Object anObject,boolean shouldScroll); // 设置选中项的背景颜色 setSelectionBackground(Color selectionBackground); // 设置选中项的前景色 setSelectionForeground(Color selectionForeground); // 设置从anchor到lead范围内的所有列表项被选中 setSelectionInterval(int anchor, int lead); // 设置选中模式，默认没有限制，也可以设置为单选或者区域选中 setSelectionMode(int selectionMode); // 设置列表框的可是高度足以显示多少行列表项 setVisibleRowCount(int visibleRowCount); ---------------------------JComboBox---------------------------------------------- // 设置是否可以直接修改列表文本框的值，默认为不可以 setEditable(boolean aFlag); // 设置列表框的可是高度足以显示多少行列表项 setMaximumRowCount(int count); // 设置默认选中项 setSelectedIndex(int anIndex); // 根据列表项的值，设置默认选中项 setSelectedItem(Object anObject); 3、设置监听器，监听列表项的变化，JList通过addListSelectionListener完成，JComboBox通过addItemListener完成\nDefaultListModel # 前面只是介绍了如何创建 JList 、 JComboBox 对象， 当调用 JList 和 JComboBox构造方法时时传入数组或 Vector 作为参数，这些数组元素或集合元素将会作为列表项。当使用JList 或 JComboBox 时 常常还需要动态地增加、删除列表项，例如JCombox提供了下列方法完成增删操作：\n// 添加一个列表项 addItem(E item); // 向指定索引处插入一个列表项 insertItemAt(E item, int index); // 删除所有列表项 removeAllItems(); // 删除指定列表项 removeItem(Object anObject); // 删除指定索引处的列表项 removeItemAt(int anIndex); JList 并没有提供这些类似的方法。如果需要创建一个可以增加、删除列表项的 JList 对象，则应该在创建 JList 时显式使用 DefaultListModel作为构造参数 。因为 DefaultListModel 作为 JList 的 Model，它负责维护 JList 组件的所有列表数据，所以可以通过向 DefaultListModel 中添加、删除元素来实现向 JList 对象中增加 、删除列表项 。DefaultListModel 提供了如下几个方法来添加、删除元素：\n// 在该 ListModel 的指定位置处插入指定元素 。 add(int index, E element); // 将指定元素添加到该 ListModel 的末尾 。 addElement(E obj); // 在该 ListModel 的指定位置处插入指定元素 。 insertElementAt(E obj, int index); // 删除该 ListModel 中指定位置处的元素 Object remove(int index); // 删除该 ListModel 中的所有元素，并将其的大小设置为零 。 removeAllElements(); // 删除该 ListModel 中第一个与参数匹配的元素。 removeElement(E obj); // 删除该 ListModel 中指定索引处的元素 。 removeElementAt(int index); // 删除该 ListModel 中指定范围内的所有元素。 removeRange(int 企omIndex ， int toIndex); // 将该 ListModel 指定索引处的元素替换成指定元素。 set(int index, E element); // 将该 ListModel 指定索引处的元素替换成指定元素。 setElementAt(E obj, int index); ListCellRenderer改变列表外观 # 前面程序中的 JList 和 JComboBox 采用的都是简单的字符串列表项， 实际上 ， JList 和 JComboBox还可以支持图标列表项，如果在创建 JList 或 JComboBox 时传入图标数组，则创建的 JList 和 JComboBox的列表项就是图标 。\npublic interface ListCellRenderer\u0026lt;E\u0026gt; { Component getListCellRendererComponent( JList\u0026lt;? extends E\u0026gt; list,//列表组件 E value,//当前列表项的值额索引 int index,//当前列表项d boolean isSelected,//当前列表项是否被选中 boolean cellHasFocus);//当前列表项是否获取了焦点 } 通过JList的setCellRenderer(ListCellRenderer\u0026lt;? super E\u0026gt; cellRenderer)方法，把自定义的ListCellRenderer对象传递给JList，就可以按照自定义的规则绘制列表项组件了。\nJTree、TreeModel实现树 # Swing 使用 JTree 对象来代表一棵树，JTree 树中节点可以使用 TreePath 来标识，该对象封装了当前节点及其所有的父节点。\n当一个节点具有子节点时，该节点有两种状态：\n展开状态：当父节点处于展开状态时，其子节点是可见的； 折叠状态：当父节点处于折叠状态时，其子节点都是不可见的 。 JTree常用构造方法：\n// 使用指定 的数据模型创建 JTree 对象，它默认显示根节点。 JTree(TreeModel newModel); // 使用 root 作为根节点创建 JTree 对象，它默认显示根节点 。 JTree(TreeNode root); //使用root作为根节点创建JTree对象，它默认显示根节点。 asksAllowsChildren 参数控制怎样的节点才算叶子节点，如果该参数为 true ，则只有当程序使用 setAllowsChildren(false)显式设置某个节点不允许添加子节点时(以后也不会拥有子节点) ，该节点才会被 JTree 当成叶子节点:如果该参数为 false ，则只要某个节点当时没有子节点(不管以后是否拥有子节点) ，该节点都会被 JTree 当成叶子节点。 JTree(TreeNode root, boolean asksAllowsChildren); JTree外观设置方法 # // 设置结点之间没有连接线 tree.putClientProperty( \u0026#34;JTree.lineStyle\u0026#34;, \u0026#34;None\u0026#34;); // 设置结点之间只有水平分割线 tree.putClientProperty(\u0026#34;JTree.lineStyle\u0026#34; , \u0026#34;Horizontal\u0026#34;); // 设置根结点有\u0026#34;展开、折叠\u0026#34;图标 tree.setShowsRootHandles(true); // 隐藏根结点 tree.setRootVisible(false); DefaultMutableTreeNode其他成员方法 # // 按广度优先的顺序遍历以此结点为根的子树，并返回所有结点组成的枚举对象 。 Enumeration breadthFirstEnumeration/preorderEnumeration(); // 按深度优先的顺序遍历以此结点为根的子树，并返回所有结点组成的枚举对象 。 Enumeration depthFirstEnumeration/postorderEnumeration(); // 返回此结点的下一个兄弟结点 。 DefaultMutableTreeNode getNextSibling(); // 返回此结点的父结点。如果此结点没有父结点，则返回null 。 TreeNode getParent(); // 返回从根结点到达此结点的所有结点组成的数组。 TreeNode[] getPath(); // 返回此结点的上一个兄弟结点。 DefaultMutableTreeNode getPreviousSibling(); // 返回包含此结点的树的根结点。 TreeNode getRoot(); // 返回此结点和aNode最近的共同祖先。 TreeNode getSharedAncestor(DefaultMutableTreeNode aNode); // 返回此结点的兄弟结点数。 int getSiblingCount(); // 返回该结点是否是叶子结点 。 boolean isLeaf() // 判断anotherNode是否是当前结点的祖先结点(包括父结点) 。 boolean isNodeAncestor(TreeNode anotherNode); // 如果aNode是此结点的子结点，则返回true。 boolean isNodeChild(TreeNode aNode); // 如果 anotherNode 是此结点的后代，包括是此结点本身、此结点的子结点或此结点的子结点的后代，都将返回true 。 boolean isNodeDescendant(DefaultMutableTreeNode anotherNode); // 当aNode和当前结点位于同一棵树中时返回 true 。 boolean isNodeRelated(DefaultMutableTreeNode aNode); // 返回anotherNode是否是当前结点的兄弟结点 。 boolean isNodeSibling(TreeNode anotherNode); // 返回当前结点是否是根结点 。 boolean isRoot(); // 返回从指定祖先结点到当前结点的所有结点组成的枚举对象 。 Enumeration pathFromAncestorEnumeration(TreeNode ancestor); 在构建目录树时，可以先创建很多DefaultMutableTreeNode对象，并调用他们的add方法构建好子父级结构，最后根据根节点构建一个JTree即可。\npublic class JTreeDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;JTreeDemo\u0026#34;); DefaultMutableTreeNode root = new DefaultMutableTreeNode(\u0026#34;中国\u0026#34;); DefaultMutableTreeNode shanxi = new DefaultMutableTreeNode(\u0026#34;陕西\u0026#34;); DefaultMutableTreeNode henan = new DefaultMutableTreeNode(\u0026#34;河南\u0026#34;); DefaultMutableTreeNode xian = new DefaultMutableTreeNode(\u0026#34;西安\u0026#34;); DefaultMutableTreeNode weinan = new DefaultMutableTreeNode(\u0026#34;渭南\u0026#34;); DefaultMutableTreeNode zhengzhou = new DefaultMutableTreeNode(\u0026#34;郑州\u0026#34;); DefaultMutableTreeNode luoyang = new DefaultMutableTreeNode(\u0026#34;洛阳\u0026#34;); shanxi.add(xian); shanxi.add(weinan); henan.add(zhengzhou); henan.add(luoyang); root.add(shanxi); root.add(henan); JTree jTree = new JTree(root); jFrame.add(jTree); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.setSize(300,300); jFrame.setVisible(true); } } 拖动、编辑树结点 # JTree 生成的树默认是不可编辑的，不可以添加、删除结点，也不可以改变结点数据。\n如果想让某个 JTree 对象变成可编辑状态，则可以调用 JTree 的setEditable(boolean b)方法，传入 true 即可把这棵树变成可编辑的树（可以添加、删除结点，也可以改变结点数据）。\n编辑树结点的步骤如下：\n1、获取被选中节点，有两种方式如下：\n方式一：通过JTree对象的某些方法，例如 TreePath getSelectionPath()等，得到一个TreePath对象，包含了从根结点到当前结点路径上的所有结点；然后再调用TreePath对象的 Object getLastPathComponent()方法，得到当前选中结点；\n方式二：调用JTree对象的 Object getLastSelectedPathComponent() 方法获取当前被选中的结点；\n2、调用DefaultTreeModel数据模型有关增删改的一系列方法完成编辑，方法执行完后，会自动重绘JTree\npublic class JTreeDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;JTreeDemo\u0026#34;); DefaultMutableTreeNode root = new DefaultMutableTreeNode(\u0026#34;中国\u0026#34;); DefaultMutableTreeNode shanxi = new DefaultMutableTreeNode(\u0026#34;陕西\u0026#34;); DefaultMutableTreeNode henan = new DefaultMutableTreeNode(\u0026#34;河南\u0026#34;); DefaultMutableTreeNode xian = new DefaultMutableTreeNode(\u0026#34;西安\u0026#34;); DefaultMutableTreeNode weinan = new DefaultMutableTreeNode(\u0026#34;渭南\u0026#34;); DefaultMutableTreeNode zhengzhou = new DefaultMutableTreeNode(\u0026#34;郑州\u0026#34;); DefaultMutableTreeNode luoyang = new DefaultMutableTreeNode(\u0026#34;洛阳\u0026#34;); shanxi.add(xian); shanxi.add(weinan); henan.add(zhengzhou); henan.add(luoyang); root.add(shanxi); root.add(henan); JTree jTree = new JTree(root); // 打开树的编辑功能 jTree.setEditable(true); // 监听鼠标拖动，完成树的拖动 jTree.addMouseListener(new MouseAdapter() { private TreePath tp; // 鼠标按下事件 @Override public void mousePressed(MouseEvent e) { // 获取当前鼠标所在的节点 tp = jTree.getPathForLocation(e.getX(), e.getY()); } // 鼠标释放事件 @Override public void mouseReleased(MouseEvent e) { TreePath targetPath = jTree.getPathForLocation(e.getX(), e.getY()); if (tp != null \u0026amp;\u0026amp; targetPath != null) { // 禁止拖动到子节点，和自身节点 // isDescendant：判断targetPath是否为tp的子节点 if (targetPath.isDescendant(tp) || tp.equals(targetPath)){ System.out.println(\u0026#34;不能拖动到子节点或自身节点！\u0026#34;); return; } System.out.println(\u0026#34;拖动成功\u0026#34;); DefaultMutableTreeNode parent = (DefaultMutableTreeNode) targetPath.getLastPathComponent(); DefaultMutableTreeNode child = (DefaultMutableTreeNode) tp.getLastPathComponent(); // add方法内部，先将该结点从原父结点删除，然后再把该结点添加到新结点中 parent.add(child); tp = null; jTree.updateUI(); } } }); jFrame.add(jTree); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jFrame.setSize(300,300); jFrame.setVisible(true); } } JTable、TableModel实现表格 # 使用JTable创建简单表格步骤:\n创建一个一维数组，存储表格中每一列的标题 创建一个二维数组，存储表格中每一行数据，其中二维数组内部的每个一维数组，代表表格中的一行数据 根据第一步和第二步创建的一维数组和二维数组，创建JTable对象 把JTable添加到其他容器中显示 public class JTableDemo { public static void main(String[] args) { JFrame jFrame = new JFrame(\u0026#34;JTableDemo\u0026#34;); Object[] titles = {\u0026#34;编号\u0026#34;, \u0026#34;姓名\u0026#34;, \u0026#34;性别\u0026#34;, \u0026#34;年龄\u0026#34;}; Object[][] data = { {1, \u0026#34;张三\u0026#34;, \u0026#34;男\u0026#34;, 18}, {2, \u0026#34;李四\u0026#34;, \u0026#34;女\u0026#34;, 19}, {3, \u0026#34;王五\u0026#34;, \u0026#34;男\u0026#34;, 20}, {4, \u0026#34;赵六\u0026#34;, \u0026#34;女\u0026#34;, 21}, {5, \u0026#34;孙七\u0026#34;, \u0026#34;男\u0026#34;, 22}, {6, \u0026#34;周八\u0026#34;, \u0026#34;女\u0026#34;, 23}, {7, \u0026#34;吴九\u0026#34;, \u0026#34;男\u0026#34;, 24}, {8, \u0026#34;郑十\u0026#34;, \u0026#34;女\u0026#34;, 25}, }; JTable jTable = new JTable(data, titles); jFrame.add(new JScrollPane(jTable)); jFrame.pack(); jFrame.setVisible(true); } } 在上面实现的简单表格中，大概有如下几个功能：\n当表格高度不足以显示所有的数据行时，该表格会自动显示滚动条 。 把鼠标移动到两列之间的分界符时，鼠标形状会变成可调整大小的形状，表明用户可以自由调整表格列的大小 。 在表格列上按下鼠标并拖动时，可以将表格的整列拖动到其他位置 。 当单击某一个单元格时，系统会自动选中该单元格所在的行 。 当双击某一个单元格时，系统会自动进入该单元格的修改状态 。 JTable调整列 # JTable提供了一个setAutoResizeMode(int mode)方法用来调整表格的格式，该方法可以接收下面几个值：\nJTable.AUTO_RESIZE_OFF：关闭表格的自动调整功能。当调整某一列的宽度时，其他列的宽度不会发生变化； JTable.AUTO_RESIZE_NEXT_COLUMN：只调整下一列的宽度，其他列及表格的宽度不会发生改变； JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS：平均调整当前列后面所有列的宽度，当前列的前面所有列及表格的宽度都不会发生变化，这是默认的调整方式 JTable.AUTO_RESIZE_LAST_COLUMN：只调整最后一列的宽度，其他列及表格的宽度不会发生变化； JTable.AUTO_RESIZE_ALL_COLUMNS：平均调整表格中所有列的宽度，表格的宽度不会发生改变 如果需要精确控制每一列的宽度，则可通过 TableColumn 对象来实现 。 JTable 使用 TableColumn 来表示表格中的每一列， JTable 中表格列的所有属性，如最佳宽度、是否可调整宽度、最小和最大宽度等都保存在该 TableColumn 中 。 此外， TableColumn 还允许为该列指定特定的单元格绘制器和单元格编辑器(这些内容将在后面讲解) 。 TableColumn 具有如下方法 。\nsetMaxWidth(int maxWidth)：设置该列的最大宽度 。 如果指定的 maxWidth 小于该列的最小宽度， 则 maxWidth 被设置成最小宽度 。 setMinWidth(int minWidth)：设置该列的最小宽度 。 setPreferredWidth(int preferredWidth)：设置该列的最佳宽度 。 setResizable(boolean isResizable)：设置是否可以调整该列的宽度 。 sizeWidthToFit()：调整该列的宽度，以适合其标题单元格的宽度 。 JTable调整选择模式 # 选则行：JTable默认的选择方式就是选择行，也可以调用setRowSelectionAllowed(boolean rowSelectionAllowed)来修改； 选择列：调用 setColumnSelectionAllowed(boolean columnSelectionAllowed)方法，修改当前JTable的选择模式为列； 选择单元格：setCellSelectionEnabled(boolean cellSelectionEnabled) ，修改当前JTable的选择模式为单元格； JTable调整表格选择状态 # 与 JList 、JTree 类似的是， JTable 使用了 一个ListSelectionModel 表示该表格的选择状态，程序可以通过 ListSelectionModel 来控制 JTable 的选择模式 。 JTable 的选择模式有如下三种：\nListSelectionMode.MULTIPLE_INTERVAL_SELECTION：没有任何限制，可以选择表格中任何表格单元，这是默认的选择模式。通过Shift 和 Ctrl 辅助键的帮助可以选择多个表格单元 。 ListSelectionMode.SINGLE_INTERVAL_SELECTION：选择单个连续区域，该选项可以选择多个表格单元，但多个表格单元之间必须是连续的。通过 Shift 辅助键的帮助来选择连续区域。 ListSelectionMode.SINGLE_SELECTION：只能选择单个表格单元 。 public class AdjustingWidthDemo { JFrame jf = new JFrame(\u0026#34;调整表格宽度\u0026#34;); JMenuBar menuBar = new JMenuBar(); JMenu adjustModeMenu = new JMenu(\u0026#34;调整方式\u0026#34;); JMenu selectUnitMenu = new JMenu(\u0026#34;选择单元\u0026#34;); JMenu selectModeMenu = new JMenu(\u0026#34;选择方式\u0026#34;); //定义5个单选框按钮，用以控制表格的宽度调整方式 JRadioButtonMenuItem[] adjustModeItem = new JRadioButtonMenuItem[5]; //定义3个单选框按钮，用以控制表格的选择方式 JRadioButtonMenuItem[] selectModeItem = new JRadioButtonMenuItem[3]; //定义复选菜单项，控制选择单元 JCheckBoxMenuItem rowsItem = new JCheckBoxMenuItem(\u0026#34;选择行\u0026#34;); JCheckBoxMenuItem columnItem = new JCheckBoxMenuItem(\u0026#34;选择列\u0026#34;); JCheckBoxMenuItem cellItem = new JCheckBoxMenuItem(\u0026#34;选择单元格\u0026#34;); //定义按钮组，实现单选 ButtonGroup adjustBg = new ButtonGroup(); ButtonGroup selectBg = new ButtonGroup(); //定义一个int类型的数组，用于保存表格所有的宽度调整方式 int[] adjustModes = { JTable.AUTO_RESIZE_OFF, JTable.AUTO_RESIZE_NEXT_COLUMN, JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS, JTable.AUTO_RESIZE_LAST_COLUMN, JTable.AUTO_RESIZE_ALL_COLUMNS }; //定义一个int乐行数组，用于保存表格所有的选择方式 int[] selectModes = { ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, ListSelectionModel.SINGLE_INTERVAL_SELECTION, ListSelectionModel.SINGLE_SELECTION }; //声明JTable JTable table; //定义一个二维数组，作为表格行数据 Object[][] tableData = { new Object[]{\u0026#34;李清照\u0026#34;,29,\u0026#34;女\u0026#34;}, new Object[]{\u0026#34;苏格拉底\u0026#34;,56,\u0026#34;男\u0026#34;}, new Object[]{\u0026#34;李白\u0026#34;,35,\u0026#34;男\u0026#34;}, new Object[]{\u0026#34;弄玉\u0026#34;,18,\u0026#34;女\u0026#34;}, new Object[]{\u0026#34;虎头\u0026#34;,2,\u0026#34;男\u0026#34;}, }; //定义一个一维数组，作为列标题 Object[] columnTitle = {\u0026#34;姓名\u0026#34;,\u0026#34;年龄\u0026#34;,\u0026#34;性别\u0026#34;}; public void init(){ //创建JTable对象 table = new JTable(tableData,columnTitle); //-----------------为窗口安装设置表格调整方式的菜单-------------------- adjustModeItem[0] = new JRadioButtonMenuItem(\u0026#34;只调整表格\u0026#34;); adjustModeItem[1] = new JRadioButtonMenuItem(\u0026#34;只调整下一列\u0026#34;); adjustModeItem[2] = new JRadioButtonMenuItem(\u0026#34;平均调整余下列\u0026#34;); adjustModeItem[3] = new JRadioButtonMenuItem(\u0026#34;只调整最后一列\u0026#34;); adjustModeItem[4] = new JRadioButtonMenuItem(\u0026#34;平均调整所有列\u0026#34;); menuBar.add(adjustModeMenu); for (int i = 0; i \u0026lt; adjustModeItem.length; i++) { //默认选中第三个菜单项，即对应表格默认的宽度调整方式 if (i==2){ adjustModeItem[i].setSelected(true); } adjustBg.add(adjustModeItem[i]); adjustModeMenu.add(adjustModeItem[i]); //为菜单项设置事件监听器 int index = i; adjustModeItem[i].addActionListener(e -\u0026gt; { if (adjustModeItem[index].isSelected()){ table.setAutoResizeMode(adjustModes[index]); } }); } //---------------为窗口安装设置表格选择方式的菜单------------------- selectModeItem[0] = new JRadioButtonMenuItem(\u0026#34;无限制\u0026#34;); selectModeItem[1] = new JRadioButtonMenuItem(\u0026#34;单独的连续区\u0026#34;); selectModeItem[2] = new JRadioButtonMenuItem(\u0026#34;单选\u0026#34;); menuBar.add(selectModeMenu); for (int i = 0; i \u0026lt; selectModeItem.length; i++) { //默认选中第一个菜单项，即表格的默认选择方式 if (i==0){ selectModeItem[i].setSelected(true); } selectBg.add(selectModeItem[i]); selectModeMenu.add(selectModeItem[i]); int index = i; selectModeItem[i].addActionListener(e -\u0026gt; { if (selectModeItem[index].isSelected()){ table.getSelectionModel().setSelectionMode(selectModes[index]); } }); } //---------------为窗口添加选择单元菜单---------------------- menuBar.add(selectUnitMenu); rowsItem.setSelected(table.getRowSelectionAllowed()); columnItem.setSelected(table.getColumnSelectionAllowed()); cellItem.setSelected(table.getCellSelectionEnabled()); rowsItem.addActionListener(e -\u0026gt; { //清除表格的选中状态 table.clearSelection(); //如果该菜单项处于选中状态，设置表格的选择单元是行 table.setRowSelectionAllowed(rowsItem.isSelected()); //如果选择行、选择列同时被选中，其实质是选择单元格 table.setCellSelectionEnabled(table.getCellSelectionEnabled()); }); selectUnitMenu.add(rowsItem); columnItem.addActionListener(e -\u0026gt; { //清除表格的选中状态 table.clearSelection(); //如果该菜单项处于选中状态，设置表格的选择单元是列 table.setColumnSelectionAllowed(columnItem.isSelected()); ///如果选择行、选择列同时被选中，其实质是选择单元格 table.setCellSelectionEnabled(table.getCellSelectionEnabled()); }); selectUnitMenu.add(columnItem); cellItem.addActionListener(e -\u0026gt; { //清除表格的选中状态 table.clearSelection(); //如果该菜单项处于选中状态，设置表格的选择单元是单元格 table.setCellSelectionEnabled(cellItem.isSelected()); ///该选项的改变会同时影响选择行、选择列两个菜单 table.setRowSelectionAllowed(table.getRowSelectionAllowed()); table.setColumnSelectionAllowed(table.getColumnSelectionAllowed()); }); selectUnitMenu.add(cellItem); jf.setJMenuBar(menuBar); //分别获取表格的三个表格列，并设置三列的最小宽、最佳宽度和最大宽度 TableColumn nameColumn = table.getColumn(columnTitle[0]); nameColumn.setMinWidth(40); TableColumn ageColumn = table.getColumn(columnTitle[1]); ageColumn.setPreferredWidth(50); TableColumn genderColumn = table.getColumn(columnTitle[2]); genderColumn.setMaxWidth(50); jf.add(new JScrollPane(table)); jf.pack(); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.setVisible(true); } public static void main(String[] args) { new AdjustingWidthDemo().init(); } } TableModel和监听器 # 与 JList、 JTree 类似的是 ， JTable 采用了 TableModel 来保存表格中的所有状态数据 : 与 ListModel类似的是， TableModel 也不强制保存该表格显示的数据 。 虽然在前面程序中看到的是直接利用一个二维数组来创建 JTable 对象，但也可以通过 TableModel 对象来创建表格。\n使用TableModel步骤如下：\n1、自定义类，继承AbstractTableModel抽象类，重写下面几个方法：\n// 返回表格列的数量 int getColumnCount(); // 返回表格行的数量 int getRowCount(); // 返回rowIndex行，column列的单元格的值 Object getValueAt(int rowIndex, int columnIndex); // 返回columnIndex列的列名称 String getColumnName(int columnIndex); // 设置rowIndex行，columnIndex列单元格是否可编辑 boolean isCellEditable(int rowIndex, int columnIndex); // 设置rowIndex行，columnIndex列单元格的值 setValueAt(Object aValue, int rowIndex, int columnIndex); ","date":"2025-05-07","externalUrl":null,"permalink":"/posts/3ab7256e/4e1989d8/5f0a2f9a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eSwing 概念 \n    \u003cdiv id=\"swing-概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#swing-%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e实际使用 Java 开发图形界面程序时 ，很少使用 AWT 组件，绝大部分时候都是用 Swing 组件开发的。\u003c/p\u003e","title":"2、Swing","type":"posts"},{"content":" 什么是NPM # NPM是随同NodeJS一起安装的包管理工具，能解决NodeJS代码部署上的很多问题，常见的使用场景有以下几种：\n允许用户从NPM服务器下载别人编写的第三方包到本地使用。 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。 主要功能如下：\n包管理：NPM 可以帮助你安装并管理项目所需的各种第三方库（包）。例如，可以通过简单的命令来安装、更新、或删除依赖。 版本管理：NPM 支持版本控制，允许你锁定某个特定版本的依赖，或根据需求选择最新的版本。 包发布：NPM 允许开发者将自己的库发布到 NPM 仓库中，其他开发者可以通过 NPM 下载并使用这些库。 命令行工具：NPM 提供了强大的命令行工具，可以用于安装包、运行脚本、初始化项目等多种操作。 由于新版的nodejs已经集成了npm，所以之前npm也一并安装好了。同样可以通过输入npm -v来测试是否成功安装。命令如下，出现版本提示表示安装成功。\n升级npm # 如果安装的是旧版本的 npm，可以很容易得通过 npm 命令来升级，命令如下\nnpm install npm -g 安装模块 # 语法：npm install \u0026lt;Module Name\u0026gt;\n例如，在某个工程下面执行命令，安装express模块\nnpm install express 在js代码中，只需要通过 require()进行引入，就可以了\nvar express = require(\u0026#39;express\u0026#39;); 如果在安装过程中提示npm resource busy or locked\u0026hellip;..，那么就执行命令npm cache clean清除缓存后再重新安装即可。\n全局安装 # 语法：npm install \u0026lt;Module Name\u0026gt; -g\n将安装包放在 /usr/local 下或者你 node 的安装目录（.npmrc文件指定的目录）。 可以直接在命令行里使用。 通过命令npm root -g查看全局包安装路径。\n修改全局安装目录，修改C：\\\\Users\\%username%\\.npmrc（Windows），~/.npmrc（Linux/Mac）文件，prefix为全局模块安装路径，cache为缓存路径\nprefix=E:\\npm\\node_global cache=E:\\npm\\node_cache 也可以通过命令，修改两个路径\nnpm config set prefix \u0026#34;E:\\npm\\node_global\u0026#34; npm config set cache \u0026#34;E:\\npm\\node_cache\u0026#34; 注意：修改完后，需要把新的全局模块安装路径E:\\npm\\node_global配置到系统环境变量中。\n局部安装 # 语法：npm install \u0026lt;Module Name\u0026gt;\n将安装包放在 ./node_modules 下（运行 npm 命令时所在的目录），如果没有 node_modules 目录，会在当前执行 npm 命令的目录下生成 node_modules 目录。 可以通过 require() 来引入本地安装的包。 淘宝镜像cnpm # 由于国内直接使用 npm 的官方镜像是非常慢的，这里推荐使用淘宝 NPM 镜像\nnpm install -g cnpm --registry=https://registry.npmmirror.com 这样就可以使用 cnpm 命令来安装模块了：\ncnpm install [module_name] 或者配置npm的镜像\nnpm config set registry https://registry.npmmirror.com 常用命令 # npm install #安装模块，简写：npm i,-g为全局 npm uninstall #卸载模块，-g为全局 npm list #查看安装的所有模块，简写：npm ls,-g为全局 npm list \u0026lt;Module Name\u0026gt; #查看指定模块的版本 npm update \u0026lt;Module Name\u0026gt; #更新指定模块 npm search \u0026lt;Module Name\u0026gt; #搜索指定模块 npm help #查看帮助文档 npm help \u0026lt;command\u0026gt; #查看指定命令的帮助文档 npm cache clear #清空本地缓存，用来解决相同版本号代码不同无法更新的问题 创建模块 # 创建模块，package.json 文件是必不可少的。我们可以使用 NPM 生成 package.json 文件，生成的文件包含了基本的结果\n语法：npm init\n发布模块 # #在npm资源库注册一个账号 npm adduser #查看当前用户 npm who an i #发布npm包，每次发布，版本号必须进行修改 npm publish package.json # package.json 位于项目/模块的目录下，用于定义包的属性\n{ \u0026#34;name\u0026#34;: \u0026#34;express\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Fast, unopinionated, minimalist web framework\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;4.13.3\u0026#34;, \u0026#34;author\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;TJ Holowaychuk\u0026#34;, \u0026#34;email\u0026#34;: \u0026#34;tj@vision-media.ca\u0026#34; }, \u0026#34;license\u0026#34;: \u0026#34;MIT\u0026#34;, \u0026#34;repository\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;git\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;git+https://github.com/strongloop/express.git\u0026#34; }, \u0026#34;homepage\u0026#34;: \u0026#34;http://expressjs.com/\u0026#34;, \u0026#34;keywords\u0026#34;: [ ], \u0026#34;dependencies\u0026#34;: { }, \u0026#34;devDependencies\u0026#34;: { }, \u0026#34;scripts\u0026#34;: { } } name - 包名。 version - 包的版本号。 description - 包的描述。 homepage - 包的官网 url 。 author - 包的作者姓名。 license - 开源协议。 dependencies - 运行时依赖包列表。如果依赖包没有安装。 devDependencies - 该模块中所列举的插件属于开发环境的依赖。 repository - 包代码存放的地方的类型，可以是 git 或 svn，git 可在 Github 上。 main - main 字段指定了程序的主入口文件，require(\u0026lsquo;moduleName\u0026rsquo;) 就会加载这个文件。这个字段的默认值是模块根目录下面的 index.js。 keywords - 关键字。 scripts - 项目的相关脚本。 ","date":"2025-05-06","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/79502aa8/3d0d1cb7/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是NPM \n    \u003cdiv id=\"什么是npm\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%afnpm\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eNPM是随同NodeJS一起安装的包管理工具，能解决NodeJS代码部署上的很多问题，常见的使用场景有以下几种：\u003c/p\u003e","title":"2、NPM","type":"posts"},{"content":" Homebrew # Homebrew官网有一句话：Homebrew complements macOS。（ Homebrew 使 macOS 更完整。）Homebrew 是 macOS 的套件管理工具，是高效下载软件的一种方法，相当于 Linux 下的 yum、apt-get 神器，用于下载存在依赖关系的软件包。通俗地说，Homebrew 是类似于 Mac App Store 的一个软件商店。\n通过 Homebrew 下载的软件都来自于官网，绝对放心软件的安全性。而且它尽可能地利用系统自带的各种库，使得软件包的编译时间大大缩短，基本上不会造成冗余。\n安装 # 前提条件：需要装有xcode以及git，可以使用xcode-select -v、git -v来查看是否安装。\n如果没有安装的话，可以使用如下命令，安装xcode commandline tools，这个工具集已经包含了git\nxcode-select --install 官网安装 # 按照提示执行就可以，但是国内网络环境一般无法安装，推荐使用国内源安装\n国内源安装 # https://gitee.com/cunkai/HomebrewCN\n配置 # 对于arm芯片的Mac来说，Homebrew默认安装在/opt/homebrew中，可以使用brew -repo查看\n# brew env export PATH=/opt/homebrew/bin:$PATH 切换下载源 # 有的时候brew update 会卡住的情况，在国内的话可以切换为清华或中科大的镜像。\n中科大 # # 替换 Homebrew 源 cd \u0026#34;$(brew --repo)\u0026#34; git remote set-url origin https://mirrors.ustc.edu.cn/brew.git # 切换 Homebrew Core 源 cd \u0026#34;$(brew --repo)/Library/Taps/homebrew/homebrew-core\u0026#34; git remote set-url origin https://mirrors.ustc.edu.cn/homebrew-core.git # 切换 Homebrew Cask 源 cd \u0026#34;$(brew --repo)/Library/Taps/homebrew/homebrew-cask\u0026#34; git remote set-url origin https://mirrors.ustc.edu.cn/homebrew-cask.git 清华 # # 替换 Homebrew 源 cd \u0026#34;$(brew --repo)\u0026#34; git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git # 切换 Homebrew Core 源 cd \u0026#34;$(brew --repo)/Library/Taps/homebrew/homebrew-core\u0026#34; git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git # 切换 Homebrew Cask 源 cd \u0026#34;$(brew --repo)/Library/Taps/homebrew/homebrew-cask\u0026#34; git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask.git 环境变量配置 # export HOMEBREW_BOTTLE_DOMAIN=\u0026#34;https://mirrors.aliyun.com/homebrew/homebrew-bottles\u0026#34; export HOMEBREW_API_DOMAIN=\u0026#34;https://mirrors.aliyun.com/homebrew/homebrew-bottles/api\u0026#34; export PATH=/opt/homebrew/bin/:$PATH 常用命令 # # 查看brew版本 brew -v # 查看brew状态 brew doctor # 更新Homebrew brew update # 模糊搜索应用 brew search \u0026lt;key\u0026gt; # 安装应用，--cask安装cli应用 brew install \u0026lt;package\u0026gt; # 卸载应用，--force强制卸载 brew uninstall \u0026lt;package\u0026gt; # 查看已安装应用，--versions带版本 brew list # 所有已安装的 Formulae brew list --formulae # 所有已安装的 Casks brew list --cask # 重装应用 brew reinstall \u0026lt;package\u0026gt; # 显示安装的软件数量、文件数量以及占用空间 brew info # 查看指定应用信息 brew info \u0026lt;package\u0026gt; # 查看已安装应用的依赖树 brew deps --installed --tree # 查看已有仓库 brew tap # 添加仓库 brew tap \u0026lt;user/repo\u0026gt; # 移除仓库，需要先将使用该仓库安装的软件卸载掉 brew untap \u0026lt;user/repo\u0026gt; # 列出所有可更新的应用 brew outdated # 锁定不想更新的应用 brew pin \u0026lt;package\u0026gt; # 取消锁定指定包 brew unpin \u0026lt;package\u0026gt; # 更新所有应用 brew upgrade # 更新指定应用 brew upgrade \u0026lt;package\u0026gt; # 查看可清理的旧版本包 brew cleanup -n # 清理所有旧版本应用包 brew cleanup # 清理指定应用旧版本包 brew cleanup \u0026lt;package\u0026gt; 安装历史版本的应用 # 例如安装go的历史版本，可以先使用brew info go，查看应用的信息\nyanggang@MacBook homebrew % brew info go ==\u0026gt; go: stable 1.21.3 (bottled), HEAD Open source programming language to build simple/reliable/efficient software https://go.dev/ Not installed From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/g/go.rb License: BSD-3-Clause ==\u0026gt; Options --HEAD Install HEAD version ==\u0026gt; Analytics install: 103,752 (30 days), 280,885 (90 days), 623,922 (365 days) install-on-request: 78,358 (30 days), 208,193 (90 days), 463,520 (365 days) build-error: 229 (30 days) 访问第六行ruby脚本地址https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/g/go.rb，点击右上角的History查看历史提交记录\n选择自己需要的版本，将ruby脚本下载下来go.rb\n然后在下载目录执行命令：brew install ./go.rb\nFormulae和Casks # Homebrew 的安装命令有两个：\nbrew install \u0026lt;package\u0026gt; brew install --cask \u0026lt;package\u0026gt; 官方描述：Homebrew Cask扩展了 Homebrew，并为 Atom 和 Google Chrome 等 GUI macOS 应用程序的安装和管理带来了优雅、简单和快速。 为此，我们提供了友好的 CLI 工作流来管理作为二进制文件分发的 macOS 应用程序。\n**「Formulae」**一般是那些命令行工具、开发库、字体、插件等不含 GUI 界面的软件。 **「Cask」**是指那些含有 GUI 图形化界面的软件，如 Google Chrome、FireFox 、Atom 等。 软件包下载完成后，若是 CLI 命令，intel的CPU会自动软链接至 /usr/local/bin 目录。如果是基于ARM的macOS则会软链接至 /opt/homebrew/bin 目录。\nrmtree # brew uninstall xxx卸载软件只会卸载软件本身而不会同时卸载其依赖包，使用工具rmtree可以完全卸载包以及依赖\n安装 # brew tap beeftornado/rmtree brew install brew-rmtree 使用 # brew rmtree \u0026lt;package\u0026gt; ","date":"2025-04-28","externalUrl":null,"permalink":"/posts/26654e7b/5a80d994/b7d3750b/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eHomebrew \n    \u003cdiv id=\"homebrew\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#homebrew\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eHomebrew\u003ca\n  href=\"https://brew.sh/\"\n    target=\"_blank\"\n  \u003e官网\u003c/a\u003e有一句话：\u003cstrong\u003eHomebrew complements macOS\u003c/strong\u003e。（ Homebrew 使 macOS 更完整。）Homebrew 是 macOS 的套件管理工具，是高效下载软件的一种方法，相当于 Linux 下的 \u003ccode\u003eyum\u003c/code\u003e、\u003ccode\u003eapt-get\u003c/code\u003e 神器，用于下载存在依赖关系的软件包。通俗地说，Homebrew 是类似于 Mac App Store 的一个软件商店。\u003c/p\u003e","title":"2、Homebrew","type":"posts"},{"content":" 使用步骤 # 搭建项目 # 1、安装VueCli，搭建Vue项目 # 找个喜欢的目录，执行以下命令，创建vue项目：\n（这里把项目名称定为electron-vue-demo）\nvue create electron-vue-demo 会出现以下选项（如果熟悉此步骤可跳过本节内容）：\nVue CLI v3.8.4 ? Please pick a preset: (Use arrow keys) default (babel, eslint) \u0026gt; Manually select features 选择“Manually select features” (自定义安装)。\n? Check the features needed for your project: (Press \u0026lt;space\u0026gt; to select, \u0026lt;a\u0026gt; to t oggle all, \u0026lt;i\u0026gt; to invert selection) ❯◉ Babel ◯ TypeScript ◯ Progressive Web App (PWA) Support ◉ Router ◉ Vuex ◉ CSS Pre-processors ◉ Linter / Formatter ◯ Unit Testing ◯ E2E Testing 这里选择了常用的模块，请根据实际需求进行选择。\n? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) n 如果选择了router，这里会询问是否使用history模式。\nvue-router 默认使用hash模式（即通过url#hash来跳转页面），使用URL的hash来模拟一个完整的 URL，当URL改变时，页面不会重新加载。 如果使用history，URL就像正常的url，例如http://yoursite.com/user/id，比较好看。但是还需要后台配置支持。\n这里我们选择“n”。\n? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys) Sass/SCSS (with dart-sass) Sass/SCSS (with node-sass) Less ❯ Stylus 选择CSS预处理模块，这里我们使用Stylus。\n? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ESLint + Airbnb config ❯ ESLint + Standard config ESLint + Prettier 选择ESLint代码格式检查工具的配置，选择“ESLint + Standard config”，标准配置。\n? Pick additional lint features: (Press \u0026lt;space\u0026gt; to select, \u0026lt;a\u0026gt; to toggle all, \u0026lt;i \u0026gt; to invert selection) ❯◉ Lint on save ◯ Lint and fix on commit Line on save表示在保存代码的时候，进行格式检查。\nLint and fix on commit表示在git commit的时候自动纠正格式。\n这里只选择Lint on save\n? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files ❯ In package.json 这里问把 babel, postcss, eslint 这些配置文件放哪？\nIn dedicated config files 表示独立文件\nIn package.json 表示放在package.json里\n这里选择In package.json。\n? Save this as a preset for future projects? (y/N) N 是否为以后的项目保留这些设置？选择“N”。\n然后耐心等待项目安装完成。\n2、自动安装Electron # 由于源在国外，安装会特别慢，可以在项目根目录新建文件.npmrc，写入如下源\nelectron_mirror = https://registry.npmmirror.com/-/binary/electron/ 进入项目根目录，安装\nvue add electron-builder 安装过程选择electron版本，选最高的就行\n编译启动 # 项目根目录下执行命令\nnpm run electron:serve 启动会有点慢，需要在src/background.js文件中，注释掉如下代码\n// 去掉安装 VUEJS3_DEVTOOLS // if (isDevelopment \u0026amp;\u0026amp; !process.env.IS_TEST) { // // Install Vue Devtools // try { // await installExtension(VUEJS3_DEVTOOLS) // } catch (e) { // console.error(\u0026#39;Vue Devtools failed to install:\u0026#39;, e.toString()) // } // } 配置项目 # 1、配置vue # 在项目根目录下创建vue.config.js，粘贴以下代码：\nconst path = require(\u0026#39;path\u0026#39;) function resolve (dir) { return path.join(__dirname, dir); } module.exports = { publicPath: \u0026#39;./\u0026#39;, devServer: { // can be overwritten by process.env.HOST host: \u0026#39;0.0.0.0\u0026#39;, port: 8080 }, chainWebpack: config =\u0026gt; { config.resolve.alias .set(\u0026#39;@\u0026#39;, resolve(\u0026#39;src\u0026#39;)) .set(\u0026#39;src\u0026#39;, resolve(\u0026#39;src\u0026#39;)) .set(\u0026#39;common\u0026#39;, resolve(\u0026#39;src/common\u0026#39;)) .set(\u0026#39;components\u0026#39;, resolve(\u0026#39;src/components\u0026#39;)); } }; devServer 用于设置开发环境的服务，这里表示在本地8080端口启动web服务。\nchainWebpack我们给项目目录起了“别名(alias)”，在代码中，我们可以直接用“别名”访问资源，省去了每次输入完整相对路径的麻烦。\n2、配置主进程文件background.js # 取消跨域限制 # function createWindow () { // Create the browser window. win = new BrowserWindow({ width: 1200, height: 620, webPreferences: { //取消跨域限制 webSecurity: false, nodeIntegration: true } }) 设置APP窗口图标 # windows: app.ico 最小尺寸：256x256 macOS: app.png或app.icns 最小尺寸：512x512 将图标放在public文件夹下，添加图标，${__static}代表public文件夹\nfunction createWindow () { // Create the browser window. win = new BrowserWindow({ width: 1200, height: 620, webPreferences: { nodeIntegration: true }, icon: `${__static}/app.ico` }) } 修改窗口名称 # 修改public/index.html文件的tittle就可以了\n打包应用 # 根目录创建electron-build.yml，写入如下内容\n# 唯一的应用程序标识符，用于操作系统级别的识别 appId: top.ygang.dbee # 应用程序的名称，显示在用户界面上 productName: DBee # 版权声明，通常包含版权年份和名称 copyright: Copyright © 2024 # 定义构建资源目录，放置图标、证书等资源文件 directories: buildResources: assets # 构建过程中使用的资源文件夹路径 output: dist_electron # 指定构建输出的目录 # 启用 ASAR 打包，将应用文件打包成单个压缩文件以提高安全性 asar: true nsis: oneClick: false # 是否一键安装，如果为 false，则显示安装向导 allowElevation: true # 是否允许请求提升（以管理员身份运行） allowToChangeInstallationDirectory: true # 是否允许用户更改安装目录 createDesktopShortcut: true # 是否在桌面上创建快捷方式 createStartMenuShortcut: true # 是否在开始菜单中创建快捷方式 shortcutName: ${productName} # 快捷方式的名称 uninstallDisplayName: ${productName}-UnInstall # 卸载时显示的名称 installerIcon: public/favicon.ico # 安装程序图标的路径 uninstallerIcon: public/favicon.ico # 卸载程序图标的路径 installerHeaderIcon: public/favicon.ico # 安装向导头部的图标路径 runAfterFinish: true # 安装完成后是否运行应用 perMachine: true # 是否为所有用户安装（而非仅当前用户） artifactName: ${productName}-${version}-Setup.${ext} # 自定义输出文件的名称 win: # must be at least 256x256 icon: public/favicon.ico target: - target: nsis # 使用nsis打成安装包，或者portable打包成免安装版 arch: - ia32 - x64 legalTrademarks: www.ygang.top mac: # must be at least 512x512 icon: public/favicon.ico linux: icon: public/favicon.ico 切记！！！项目不要放在中文路径上，不然打包会报错\n执行命令进行打包\nnpm run electron:build 注意：在mac上使用electron-builder打包的时候需要依赖 Python2，如果没有配置环境变量则需要使用如下命令临时创建环境变量\nexport PYTHON_PATH=/Library/Frameworks/Python.framework/Versions/2.7/bin/python npm run build 添加License # 在上面配置中buildResources的目录下创建license.txt文件即可，如果displayLanguageSelector为true的话，创建license_en.txt和license_zh_CN.txt，使用字符集编码带有BOM的UTF-8兼容win、mac\n","date":"2025-04-28","externalUrl":null,"permalink":"/posts/bafd68f1/8e24967e/d42199bc/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e使用步骤 \n    \u003cdiv id=\"使用步骤\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bd%bf%e7%94%a8%e6%ad%a5%e9%aa%a4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e搭建项目 \n    \u003cdiv id=\"搭建项目\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%90%ad%e5%bb%ba%e9%a1%b9%e7%9b%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003e1、安装VueCli，搭建Vue项目 \n    \u003cdiv id=\"1安装vuecli搭建vue项目\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1%e5%ae%89%e8%a3%85vuecli%e6%90%ad%e5%bb%bavue%e9%a1%b9%e7%9b%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cp\u003e找个喜欢的目录，执行以下命令，创建vue项目：\u003c/p\u003e","title":"2、集成Vue","type":"posts"},{"content":" use strict # \u0026quot;use strict\u0026quot;指令在 JavaScript 1.8.5 (ECMAScript5) 中新增。\n\u0026quot;use strict\u0026quot; 的目的是指定代码在严格条件下执行。\n严格模式下你不能使用未声明的变量。\n\u0026#34;use strict\u0026#34;; x = 3.14;// 报错 (x 未定义) \u0026#34;use strict\u0026#34;; myFunction(); function myFunction() { y = 3.14; // 报错 (y 未定义) } x = 3.14; // 不报错，因为作用域中没有use strict myFunction(); function myFunction() { \u0026#34;use strict\u0026#34;; y = 3.14; // 报错 (y 未定义)，因为作用域中有use strict } 为什么使用严格模式:\n消除 Javascript 语法的一些不合理、不严谨之处，减少一些怪异行为。 消除代码运行的一些不安全之处，保证代码运行的安全。 提高编译器效率，增加运行速度。 为未来新版本的Javascript做好铺垫。 ","date":"2025-04-25","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/0d08345a/47e69f85/0ed486a3/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003euse strict \n    \u003cdiv id=\"use-strict\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#use-strict\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003e\u0026quot;use strict\u0026quot;\u003c/code\u003e指令在 JavaScript 1.8.5 (ECMAScript5) 中新增。\u003c/p\u003e","title":"2、strict","type":"posts"},{"content":" 变量 # 声明格式：var name = value;\nvar、let、const # ES6引入let、const之前，JavaScript 只有两种类型的作用域：全局作用域和函数作用域\nconst # const声明的变量不可以进行修改，而且声明的时候必须赋值，和java的final相似\nconst声明的变量对象，可以修改对象的属性\nlet # let声明的作用域为块级作用域，即只作用于{}，如果在函数外（全局作用域）和函数内（函数作用域）使用和var没有区别。\n区别在于，函数（块）外声明后，再在函数（块）内声明并赋值的区别\nvar a = 5 for(var a = 0;a \u0026lt;= 10;a++){} console.log(a) //11 let a = 5 //如果在循环中用 let 声明了变量 a，那么只有在循环内，变量 a 才是可见的。 for(let a = 0;a \u0026lt;= 10;a++){} console.log(a) //5 数据类型 # 可以使用函数typeof(obj)，查看一个变量或值的数据类型\n表达式 返回值 说明 typeof undefined \u0026ldquo;undefined\u0026rdquo; 未定义的值 typeof true \u0026ldquo;boolean\u0026rdquo; 布尔值 typeof 42 \u0026ldquo;number\u0026rdquo; 所有数字类型 typeof \u0026quot;text\u0026quot; \u0026ldquo;string\u0026rdquo; 字符串 typeof {a:1} \u0026ldquo;object\u0026rdquo; 对象、数组、null typeof function(){} \u0026ldquo;function\u0026rdquo; 函数 typeof Symbol() \u0026ldquo;symbol\u0026rdquo; ES6新增符号类型 typeof BigInt(10) \u0026ldquo;bigint\u0026rdquo; ES2020新增大整数类型 Number # JavaScript不区分整数和浮点数，统一用Number表示，以下都是合法的Number类型\nvar a = 10; var b = 20.2; var c = 12e2; var d = -30; console.log(a); console.log(b); console.log(c); console.log(d); // 10 // 20.2 // 1200 // -30 字符串 # 字符串是以单引号'或双引号\u0026quot;括起来的任意文本\nvar a = \u0026#39;abc\u0026#39;; var b = \u0026#34;def\u0026#34;; var c = ` 你好呀， 这是一个多行字符串 ` console.log(a); console.log(b); console.log(c); // abc // def // 你好呀， // 这是一个多行字符串 模板字符串 # ES6新增模板字符串，可以使用变量作为替换\n注意：使用${}作为模板，该字符串需要使用多行字符串的符号反引号\nvar name = \u0026#39;lucy\u0026#39;; var age = 18; var msg = `我叫${name}，今年${age}岁了` console.log(msg) // 我叫lucy，今年18岁了 常用api # var str = \u0026#39;abcdef\u0026#39; //获取字符串长度 len = str.length //获取指定索引的字符，字符串是不可变的！这个方法不可以用来赋值 b = str[1] //将字符串全大写并返回 upStr = str.toUpperCase() //将字符串全小写并返回 lowStr = str.toLowerCase() //返回指定字符串所出现的索引，未找到返回-1 i = str.indexOf(\u0026#39;bc\u0026#39;) //返回指定区间的字符串，左开右闭 s1 = str.substring(3) s2 = str.substring(3,5) 方法 描述 charAt() 返回指定索引位置的字符 charCodeAt() 返回指定索引位置字符的 Unicode 值 concat() 连接两个或多个字符串，返回连接后的字符串 fromCharCode() 将 Unicode 转换为字符串 indexOf() 返回字符串中检索指定字符第一次出现的位置 lastIndexOf() 返回字符串中检索指定字符最后一次出现的位置 localeCompare() 用本地特定的顺序来比较两个字符串 match() 找到一个或多个正则表达式的匹配 replace() 替换与正则表达式匹配的子串 search() 检索与正则表达式相匹配的值 slice() 提取字符串的片断，并在新的字符串中返回被提取的部分 split() 把字符串分割为子字符串数组 substr() 从起始索引号提取字符串中指定数目的字符 substring() 提取字符串中两个指定的索引号之间的字符 toLocaleLowerCase() 根据主机的语言环境把字符串转换为小写，只有几种语言（如土耳其语）具有地方特有的大小写映射 toLocaleUpperCase() 根据主机的语言环境把字符串转换为大写，只有几种语言（如土耳其语）具有地方特有的大小写映射 toLowerCase() 把字符串转换为小写 toString() 返回字符串对象值 toUpperCase() 把字符串转换为大写 trim() 移除字符串首尾空白 valueOf() 返回某个字符串对象的原始值 布尔 # var a = true; var b = false; console.log(a); console.log(b); // true // false 数组 # 数组是一组按顺序排列的集合，集合的每个值称为元素。JavaScript的数组可以包括任意数据类型。\n//建议使用这种声明方式 var arr1 = [1,2,\u0026#39;lucy\u0026#39;,3] var arr2 = new Array(1,2,\u0026#39;lucy\u0026#39;,3) console.log(arr1) console.log(arr2) // [ 1, 2, \u0026#39;lucy\u0026#39;, 3 ] // [ 1, 2, \u0026#39;lucy\u0026#39;, 3 ] 常用api # var a = [\u0026#39;lucy\u0026#39;,\u0026#39;lily\u0026#39;,\u0026#39;tom\u0026#39;] //获取数组的长度，这个长度是可以进行赋值的 //如果赋值，多出来的会用undefined表示，如果长度变短，会截取 a.length //获取指定索引的元素 a[1] //修改指定索引元素 a[1] = \u0026#39;james\u0026#39; //获取元素所在索引 a.indexOf(\u0026#39;tom\u0026#39;) //截取数组，返回截取后的数组，左开右闭 a.slice(1,2) //向数组末尾添加一个两个元素，返回新的长度，参数可变 a.push(\u0026#39;mo\u0026#39;,\u0026#39;monkey\u0026#39;) //删除最后一个元素，并返回 a.pop() //向数组开始添加一个元素，返回新的长度，参数可变 a.unshift(\u0026#39;timi\u0026#39;) //删除第一个元素，并返回 a.shift() //对数组进行排序，可传入函数自定义规则 a.sort() //反转数组 a.reverse() //从数组的第2位开始删除3个元素，再添加两个元素到这个位置，并返回删除的元素 a.splice(2,3,\u0026#39;lulu\u0026#39;,\u0026#39;gogo\u0026#39;) //从数组的第2位开始删除3个元素 a.splice(2,3) //连接两个数组,并返回新的数组 b = [\u0026#39;haha\u0026#39;,\u0026#39;yiyi\u0026#39;] a.concat(b) //将数组的所有元素使用指定分隔符连接后，返回字符串lucy-lili-tom a.join(\u0026#39;-\u0026#39;) //返回数组符合要求的元素组成的新数组 a.filter(name =\u0026gt; { return name.length \u0026gt; 3 }) 对象 # JavaScript的对象是一种无序的集合数据类型，它由若干键值对组成\n注意，最后一个键值对不需要在末尾加,，如果加了，有的浏览器（如低版本的IE）将报错。\n对象的key只可以是字符串\nvar lucy = { name : \u0026#39;lucy\u0026#39;, age : 18, sex : \u0026#39;女\u0026#39; }; console.log(lucy) console.log(lucy.age) // { name: \u0026#39;lucy\u0026#39;, age: 18, sex: \u0026#39;女\u0026#39; } // 18 如果key含有特殊字符，那么必须使用引号包起来，说明是一个字符串，而且在获取对象属性值的时候，不可以使用obj.value的方式，所以命名属性的时候一定要规范\nvar lucy = { name : \u0026#39;lucy\u0026#39;, age : 18, \u0026#39;sex-name\u0026#39; : \u0026#39;女\u0026#39; }; console.log(lucy) console.log(lucy[\u0026#39;sex-name\u0026#39;]) // { name: \u0026#39;lucy\u0026#39;, age: 18, \u0026#39;sex-name\u0026#39;: \u0026#39;女\u0026#39; } // 女 判断属性是否存在 # var lucy = { name : \u0026#39;lucy\u0026#39;, age : 18, \u0026#39;sex-name\u0026#39; : \u0026#39;女\u0026#39; }; console.log(\u0026#39;name\u0026#39; in lucy) //js的所有对象最终原型链指向object，toString是object的 console.log(\u0026#39;toString\u0026#39; in lucy) //hasOwnProperty是判断对象本身有没有这个属性 console.log(lucy.hasOwnProperty(\u0026#39;toString\u0026#39;)) // true // true // false this # 面向对象语言中 this 表示当前对象的一个引用。\n但在 JavaScript 中 this 不是固定不变的，它会随着执行环境的改变而改变。\n在方法中，this 表示该方法所属的对象。 如果单独使用，this 表示全局对象。 在函数中，this 表示全局对象。 在函数中，在严格模式下，this 是未定义的(undefined)。 在事件中，this 表示接收事件的元素。 类似 call() 和 apply() 方法可以将 this 引用到任何对象。 JSON # JSON 格式在语法上与创建 JavaScript 对象代码是相同的。\n函数 描述 JSON.parse() 用于将一个 JSON 字符串转换为 JavaScript 对象。 JSON.stringify() 用于将 JavaScript 值转换为 JSON 字符串。 类型转换 # JavaScript 变量可以转换为新变量或其他数据类型：\n通过使用 JavaScript 函数 通过 JavaScript 自身自动转换 数字转换为字符串 # String(x) // 将变量 x 转换为字符串并返回 String(123) // 将数字 123 转换为字符串并返回 String(100 + 23) // 将数字表达式转换为字符串并返回 Number 方法 toString() 也是有同样的效果。\nx.toString() (123).toString() (100 + 23).toString() 布尔值转换为字符串 # String(false) // 返回 \u0026#34;false\u0026#34; String(true) // 返回 \u0026#34;true\u0026#34; Boolean 方法 toString() 也有相同的效果。\nfalse.toString() // 返回 \u0026#34;false\u0026#34; true.toString() // 返回 \u0026#34;true\u0026#34; 字符串转换为数字 # Number(\u0026#34;3.14\u0026#34;) // 返回 3.14 Number(\u0026#34; \u0026#34;) // 返回 0 Number(\u0026#34;\u0026#34;) // 返回 0 Number(\u0026#34;99abc\u0026#34;) // 返回 NaN 布尔值转换为数字 # Number(false) // 返回 0 Number(true) // 返回 1 特殊值 # NaN和Infinity # //表示Not a Number，即无法计算出结果 var a = NaN; //表示无限大，超出Number能表示的最大值 var b = Infinity; NaN和任何数值都不相等，和自己也不相等，要判断是否是NaN，需要使用函数isNaN()\nnull和undefined # null表示一个空的值，它和0以及空字符串''不同，0是一个数值，''表示长度为0的字符串，而null表示空。\nJavaScript的设计者希望用null表示一个空的值，而undefined表示值未定义。事实证明，这并没有什么卵用，区分两者的意义不大。大多数情况下，我们都应该用null。undefined仅仅在判断函数参数是否传递的情况下有用。\nnull 和 undefined 的值相等，但类型不等\ntypeof undefined // undefined typeof null // object null === undefined // false null == undefined // true 运算符 # 算术运算符 # y = 5\n运算符 描述 例子 x 运算结果 y 运算结果 + 加法 x=y+2 7 5 - 减法 x=y-2 3 5 * 乘法 x=y*2 10 5 / 除法 x=y/2 2.5 5 % 取模（余数） x=y%2 1 5 ++ 自增，前后和java的逻辑一样 x=++y 6 6 \u0026ndash; 自减，前后和java的逻辑一样 x=\u0026ndash;y 4 4 赋值运算符 # x = 10,y = 5\n运算符 例子 等同于 运算结果 = x=y x=5 += x+=y x=x+y x=15 -= x-=y x=x-y x=5 *= x*=y x=x*y x=50 /= x/=y x=x/y x=2 %= x%=y x=x%y x=0 比较运算符 # 运算符 描述 比较 返回值 == 等于 x==8 false === 绝对等于（值和类型均相等） x===\u0026ldquo;5\u0026rdquo; false != 不等于 x!=8 true !== 不绝对等于（值和类型有都不相等） x!==\u0026ldquo;5\u0026rdquo; true \u0026gt; 大于 x\u0026gt;8 false \u0026lt; 小于 x\u0026lt;8 true \u0026gt;= 大于或等于 x\u0026gt;=8 false \u0026lt;= 小于或等于 x\u0026lt;=8 true 逻辑运算符 # x = 6,y = 3\n运算符 描述 例子 \u0026amp;\u0026amp; and (x \u0026lt; 10 \u0026amp;\u0026amp; y \u0026gt; 1) 为 true || or `(x==5 ! not !(x==y) 为 true ","date":"2025-04-25","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/0d08345a/1403736b/4ad5db13/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e变量 \n    \u003cdiv id=\"变量\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8f%98%e9%87%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e声明格式：\u003ccode\u003evar name = value;\u003c/code\u003e\u003c/p\u003e","title":"2、变量和运算符","type":"posts"},{"content":" pipenv # pipenv 是 Python 项目的依赖管理器，与 Node.js 的 npm、Ruby 的 bundler 类似，在协作项目中优势明显，能简化依赖管理。\npipenv是Kenneth Reitz在2017年1月发布的Python依赖管理工具，现在由PyPA维护。你可以把它看做是pip和virtualenv的组合体，而它基于的Pipfile则用来替代旧的依赖记录方式（requirements.txt），pipenv 在易用性上要简单很多，同时增加了 lock 文件，能更好的锁定版本。如果没有特殊要求可以 pipenv 直接使用 lock 的版本，开发又可以小步迭代，实现依赖的稳步升级。\n自动关联项目相关的 virtualenv，能够快速的加载 virtualenv。 提供的pipenv替代pip并自带一个依赖清单Pipfile，和依赖锁定Pipfile.lock。 Pipfile除了依赖清单还支持固定pypi源地址，固定python版本。 Pipfile还支持dev依赖清单。pipenv install的包会强制使用Pipfile中的源。 使用pipenv graph命令可以看到依赖树。 可以直接切换python2和python3。 安装 # pip install pipenv 输入命令pipenv --version来查看是否安装成功，如果安装成功则会显示当前模块的版本编号\n更改pipenv的虚拟环境存放目录 # 但是默认情况下Pipenv会统一管理所有的虚拟环境，默认路径如下\nWindows默认路径： C:\\Users\\Administrator.virtualenvs\\ Linux或MacOS默认路径：~/.local/share/virtualenvs/ 设置方式如下：\n设置环境变量统一存放：创建 WORKON_HOME 环境变量，其值设置为存放的具体路径且路径必须已经创建好，如其值为D:\\virtualenvs\\，保存成功后新创建虚拟环境即可，即后续所有创建的虚拟环境都存在此路径下面。 设置环境变量存放在各自的项目中：创建 PIPENV_VENV_IN_PROJECT 环境变量，将其值设置为1，则项目的虚拟环境，会在项目的根目录自动生成.venv 存放。 常用命令 # pipenv shell：进入虚拟环境（检测当前项目对应的虚拟环境是否存在，不存在则自动创建项目所需要的环境） pipenv install：虚拟环境中安装包（检测当前项目对应的虚拟环境是否存在，不存在则自动创建项目所需要的环境） pipenv install 包名：安装虚拟环境包到默认环境 pipenv install 包名 --dev：安装虚拟环境包到开发环境 pipenv uninstall 包名：卸载虚拟环境包，会将该包与依赖包全部卸载 pipenv --where：列出本地工程的路径 pipenv --venv：列出虚拟环境路径 pipenv --py：列出虚拟环境的python可执行文件 pipenv lock：生成 Pipfile.lock 文件 exit：退出虚拟环境 pipenv --rm：删除虚拟环境 Pipfile文件 # [[source]] url = \u0026#34;https://pypi.tuna.tsinghua.edu.cn/simple\u0026#34; verify_ssl = true name = \u0026#34;pypi\u0026#34; [packages] requests = \u0026#34;*\u0026#34; pyyaml = \u0026#34;*\u0026#34; Django = \u0026#34;*\u0026#34; [dev-packages] pytest = \u0026#34;*\u0026#34; [requires] python_version = \u0026#34;3.7\u0026#34; [scripts] django = \u0026#34;python manage.py runserver 0.0.0.0:8080\u0026#34; source ：用来设置仓库地址，即指定镜像源下载虚拟环境所需要的包 packages ：用来指定项目依赖的包，可以用于生产环境和生成requirements文件 dev-packages ：用来指定开发环境需要的包，这类包只用于开发过程，不用与生产环境。 requires ：指定目标Python版本 scripts ：添加自定义的脚本命令，并通过 pipenv run + 名称 的方式在虚拟环境中执行对应的命令 。 pipenv run django ：相当于 执行命令 pipenv run python manage.py runserver 0.0.0.0:8080 Pipfile 文件可以复制到其他项目内，通过执行pipenv install命令， 根据这个 Pipfile 文件生成虚拟环境和依赖包的安装。\nPipfile.lock文件 # 通过hash算法将包的名称和版本，及依赖关系生成哈希值，保证包的完整性，除修改镜像源，非必要情况不对该文件进行修改。\n","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/b730e80e/622343f4/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003epipenv \n    \u003cdiv id=\"pipenv\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#pipenv\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003epipenv 是 Python 项目的依赖管理器，与 Node.js 的 npm、Ruby 的 bundler 类似，\u003cstrong\u003e在协作项目中优势明显，能简化依赖管理\u003c/strong\u003e。\u003c/p\u003e","title":"2、pipenv","type":"posts"},{"content":"我们配置好 QtDesigner 后，就可以在 IDE 中创建.ui文件了\n使用 UI 文件 # 动态加载UI文件 # from PySide6.QtWidgets import QApplication from PySide6.QtUiTools import QUiLoader application = QApplication([]) ui_loader = QUiLoader() ui = ui_loader.load(\u0026#39;say_hello.ui\u0026#39;) ui.show() application.exec() 转化UI文件为Python代码 # 还有一种使用UI文件的方式：先把UI文件直接转化为包含界面定义的Python代码文件，然后在你的程序中使用定义界面的类\n执行如下格式的命令转化\npyside6-uic say_hello.ui \u0026gt; say_hello_ui.py 代码文件中这样使用定义界面的类\nfrom PySide6.QtWidgets import QApplication,QWidget from say_hello_ui import Ui_Form application = QApplication([]) # 实例化自定义 ui 类 ui = Ui_Form() # 由于我们的 ui 窗体选择的是QWidget，所以这里实例化一个QWidget window = QWidget() # 初始化界面 ui.setupUi(window) window.show() application.exec() 按照规范编码的形式，我们应该这么写\nfrom PySide6.QtWidgets import QApplication,QWidget from say_hello_ui import Ui_Form class MyWindow(QWidget,Ui_Form): def __init__(self): super().__init__() self.setupUi(self) application = QApplication([]) my_window = MyWindow() my_window.show() application.exec() 两种方式的区别 # 通常采用动态加载比较方便，因为改动界面后，不需要转化，直接运行，特别方便。\n但是，如果你的程序里面有非qt designer提供的控件， 这时候，需要在代码里面加上一些额外的声明，而且可能还会有奇怪的问题。往往就要采用转化Python代码的方法。\n布局 Layout # QHBoxLayout 水平布局：把控件从左到右水平横着摆放 QVBoxLayout 垂直布局：把控件从上到下竖着摆放 QGridLayout 表格布局：把多个控件格子状摆放 QFormLayout 表单布局：表单就像一个只有两列的表格，非常适合填写注册表单这种类型的界面 ","date":"2025-04-23","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/901ba1e3/d87f02df/","section":"文章","summary":"\u003cp\u003e我们配置好 QtDesigner 后，就可以在 IDE 中创建\u003ccode\u003e.ui\u003c/code\u003e文件了\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20250421104557013\"\n    data-zoom-src=\"/posts/1b37041b/cf2af7cb/901ba1e3/d87f02df/image/image-20250421104557013.png\"\n    src=\"/posts/1b37041b/cf2af7cb/901ba1e3/d87f02df/image/image-20250421104557013.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20250421104616776\"\n    data-zoom-src=\"/posts/1b37041b/cf2af7cb/901ba1e3/d87f02df/image/image-20250421104616776.png\"\n    src=\"/posts/1b37041b/cf2af7cb/901ba1e3/d87f02df/image/image-20250421104616776.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e使用 UI 文件 \n    \u003cdiv id=\"使用-ui-文件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bd%bf%e7%94%a8-ui-%e6%96%87%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e动态加载UI文件 \n    \u003cdiv id=\"动态加载ui文件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8a%a8%e6%80%81%e5%8a%a0%e8%bd%bdui%e6%96%87%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003ePySide6.QtWidgets\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eQApplication\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003ePySide6.QtUiTools\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eQUiLoader\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapplication\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eQApplication\u003c/span\u003e\u003cspan class=\"p\"\u003e([])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eui_loader\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eQUiLoader\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eui\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eui_loader\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eload\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;say_hello.ui\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eui\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eshow\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eapplication\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eexec\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e转化UI文件为Python代码 \n    \u003cdiv id=\"转化ui文件为python代码\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%bd%ac%e5%8c%96ui%e6%96%87%e4%bb%b6%e4%b8%bapython%e4%bb%a3%e7%a0%81\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e还有一种使用UI文件的方式：先把UI文件直接转化为包含界面定义的Python代码文件，然后在你的程序中使用定义界面的类\u003c/p\u003e","title":"2、QtDesigner","type":"posts"},{"content":" 变量 # 变量命名规范需要遵守标识符命名规范\n变量的定义 # name = 10 python和java变量的差别 # python和java变量存储结构的区别 # Java中的基本类型变量，存放的是值 Java中的引用类型变量，一般存放在栈内存中，而值存放在堆内存中（使用new关键字）或者常量池中 Python中的变量存放的是值的地址 Python中的变量（任何变量）类似于Java中的引用类型（String、数组、ArrayList、类等） 常量 # Python 本身并没有提供像 C 或 Java 中那样的 const 关键字来定义常量。\n在 Python 中，所有的变量都是可以重新赋值的。因此，Python 并没有内建的常量类型。不过，Python 通过 命名约定 来模拟常量的概念，通常使用全大写字母来表示常量，表示这些变量不应该被修改。\nPI = 3.14159 MAX_CONNECTIONS = 100 数据类型 # 类型 描述 说明 数字（Number） 支持整数（int）、浮点数、（float）、复数（complex）、布尔（bool） 整数（int），如：10、-10 浮点数（float） 小数 如：13.14、-13.14 复数（complex） 以j结尾表示复数 如：4+3j， 布尔（bool） True本质上是一个数字记作1，False记作0 字符串（str） 描述文本的一种数据类型 字符串（string）由任意数量的字符组成 列表（list） 有序的可变序列 Python中使用最频繁的数据类型，可有序记录一堆数据 元组（tuple） 有序的不可变序列 可有序记录一堆不可变的Python数据集合 集合（set） 无序不重复集合 可无序记录一堆不重复的Python数据集合 字典（dict） 无序Key-Value集合 可无序记录一堆Key-Value型的Python数据集合 1、数字类型 # int（整数） # 整形，正负都可以，可以处理任意大小的整数\n对于很大的数，例如10000000000，很难数清楚0的个数。Python允许在数字中间以_分隔，因此，写成10_000_000_000和10000000000是完全一样的\na = 10 b = -20 print(a) float（浮点数） # 浮点类型，也就是小数类型\na = 1.22 b = -2.33 print(a) complex（复数） # 复数（Complex），是 Python 的内置类型，直接书写即可。换句话说，Python 语言本身就支持复数，而不依赖于标准库或者第三方库\n复数由实部（real）和虚部（imag）构成，在 Python 中，复数的虚部以j或者J作为后缀\nc1 = 12 + 0.2j print(\u0026#34;c1Value: \u0026#34;, c1) 2、bool（布尔） # 布尔类型，和java一样，但是首字母需要大写，python3的bool是int类型\na = True b = False print(a) 3、str（字符串） # python的String类型，也就是用单引号或双引号框起来的字符串\n# 双引号 a = \u0026#34;abcdefg\u0026#34; # 单引号 b = \u0026#39;1234567\u0026#39; # 多行字符串 c = \u0026#34;\u0026#34;\u0026#34;你好呀 我是字符串多行的写法 使用三个双引号表示\u0026#34;\u0026#34;\u0026#34; print(c) #根据索引截取字符串,格式a[i:j] #i、j默认为0，表示截取字符串第i位到第j位不包括第j位 print(a[:2]) #ab print(a[-3:-1]) #ef print(a[2:]) #cdefg #重复输出字符串 print(a * 2) #abcdefgabcdefg #判断字符串a中是否存在给定字符串de print(\u0026#39;de\u0026#39; in a) #True #判断字符串a中是否不存在给定字符串de print(\u0026#39;de\u0026#39; not in a) #False #原始字符串，即忽略所有转义字符，按照实际字面量输出,r/R print(r\u0026#39;\\n\u0026#39;) #\\n #将对象转化为供解释器读取的形式，即字符串化 print(repr(str)) 模板字符串 # 方式一：占位符 # 格式符号 转化 %s 将内容转换成字符串，放入占位位置 %d 将内容转换成整数，放入占位位置 %f 将内容转换成浮点型，放入占位位置 name = \u0026#34;lucy\u0026#34; age = 18 msg = \u0026#34;我是%s，今年%s岁\u0026#34; % (name,age) print(msg) 我们可以使用辅助符号\u0026quot;m.n\u0026quot;来控制数据的宽度和精度\nm，控制宽度，要求是数字（很少使用）,设置的宽度小于数字自身，不生效\n.n，控制小数点精度，要求是数字，会进行小数的四舍五入\n示例：\n%5d：表示将整数的宽度控制在5位，如数字11，被设置为5d，就会变成：[空格][空格][空格]11，用三个空格补足宽度。\n%5.2f：表示将宽度控制为5，将小数点精度设置为2，小数点和小数部分也算入宽度计算。如，对11.345设置了%7.2f 后，结果是：[空格][空格]11.35。2个空格补足宽度，小数部分限制2位精度后，四舍五入为 .35\n%.2f：表示不限制宽度，只设置小数点精度为2，如11.345设置%.2f后，结果是11.35\n方式二：format方法 # name = \u0026#34;lucy\u0026#34; age = 18 msg = \u0026#34;我是{}，今年{}岁\u0026#34;.format(name,age) print(msg) 方式三：f-string # f其实就等价于format，只要你用过format，就会用这个。\n这个是python3.6新增的特性。\nname = \u0026#34;lucy\u0026#34; age = 18 msg = f\u0026#34;我是{name}，今年{age}岁\u0026#34; print(msg) 花括号双写可以转义\ntimes1 = 1000 times2 = 2000 print(f\u0026#39;文章中 {{ 符号 出现了 {times1} 次\u0026#39;) print(f\u0026#39;文章中 }} 符号 出现了 {times2} 次\u0026#39;) 常用方法 # capitalize() # 将字符串的第一个字符转换为大写 center(width, fillchar) # 返回一个指定的宽度 width 居中的字符串，fillchar 为填充的字符，默认为空格。 count(str, beg= 0,end=len(string)) # 返回 str 在 string 里面出现的次数，如果 beg 或者 end 指定则返回指定范围内 str 出现的次数 bytes.decode(encoding=\u0026#34;utf-8\u0026#34;, errors=\u0026#34;strict\u0026#34;) # Python3 中没有 decode 方法，但我们可以使用 bytes 对象的 decode() 方法来解码给定的 bytes 对象，这个 bytes 对象可以由 str.encode() 来编码返回。 encode(encoding=\u0026#39;UTF-8\u0026#39;,errors=\u0026#39;strict\u0026#39;) # 以 encoding 指定的编码格式编码字符串，如果出错默认报一个ValueError 的异常，除非 errors 指定的是\u0026#39;ignore\u0026#39;或者\u0026#39;replace\u0026#39; endswith(\u0026#39;obj\u0026#39;,beg=0, end=len(string)) # 检查字符串是否以 obj 结束，如果beg 或者 end 指定则检查指定的范围内是否以 obj 结束，如果是，返回 True,否则返回 False. expandtabs(tabsize=8) # 把字符串 string 中的tab符号转为空格，tab 符号默认的空格数是 8 。 find(str, beg=0, end=len(string)) # 检测str是否包含在字符串中，如果指定范围 beg 和 end ，则检查是否包含在指定范围内，如果包含返回开始的索引值，否则返回-1 index(str, beg=0, end=len(string)) # 跟find()方法一样，只不过如果str不在字符串中会报一个异常。 isalnum() # 如果字符串至少有一个字符并且所有字符都是字母或数字则返 回 True，否则返回 False isalpha() # 如果字符串至少有一个字符并且所有字符都是字母或中文字则返回 True, 否则返回 False isdigit() # 如果字符串只包含数字则返回 True 否则返回 False islower() # 如果字符串中包含至少一个区分大小写的字符，并且所有这些(区分大小写的)字符都是小写，则返回 True，否则返回 False isnumeric() # 如果字符串中只包含数字字符，则返回 True，否则返回 False isspace() # 如果字符串中只包含空白，则返回 True，否则返回 False. istitle() # 如果字符串是标题化的(见 title())则返回 True，否则返回 False isupper() # 如果字符串中包含至少一个区分大小写的字符，并且所有这些(区分大小写的)字符都是大写，则返回 True，否则返回 False join(seq) # 以指定字符串作为分隔符，将 seq 中所有的元素(的字符串表示)合并为一个新的字符串 len(string) # 返回字符串长度 ljust(width[, fillchar]) # 返回一个原字符串左对齐,并使用 fillchar 填充至长度 width 的新字符串，fillchar 默认为空格。 lower() # 转换字符串中所有大写字符为小写. lstrip() # 截掉字符串左边的空格或指定字符。 maketrans() # 创建字符映射的转换表，对于接受两个参数的最简单的调用方式，第一个参数是字符串，表示需要转换的字符，第二个参数也是字符串表示转换的目标。 max(str) # 返回字符串 str 中最大的字母。 min(str) # 返回字符串 str 中最小的字母。 replace(old, new [, max]) # 将字符串中的 old替换成new后返回,如果 max 指定，则替换不超过 max 次。 rfind(str, beg=0,end=len(string)) # 类似于 find()函数，不过是从右边开始查找. rindex(str, beg=0, end=len(string)) # 类似于 index()，不过是从右边开始. rjust(width,[, fillchar]) # 返回一个原字符串右对齐,并使用fillchar(默认空格）填充至长度 width 的新字符串 rstrip() # 删除字符串末尾的空格或指定字符。 split(str=\u0026#34;\u0026#34;, num=string.count(str)) # 以 str 为分隔符截取字符串，如果 num 有指定值，则仅截取 num+1 个子字符串 splitlines([keepends]) # 按照行(\u0026#39;\\r\u0026#39;, \u0026#39;\\r\\n\u0026#39;, \\n\u0026#39;)分隔，返回一个包含各行作为元素的列表，如果参数 keepends 为 False，不包含换行符，如果为 True，则保留换行符。 startswith(substr, beg=0,end=len(string)) # 检查字符串是否是以指定子字符串 substr 开头，是则返回 True，否则返回 False。如果beg 和 end 指定值，则在指定范围内检查。 strip([chars]) # 在字符串上执行 lstrip()和 rstrip() swapcase() # 将字符串中大写转换为小写，小写转换为大写 title() # 返回\u0026#34;标题化\u0026#34;的字符串,就是说所有单词都是以大写开始，其余字母均为小写(见 istitle()) translate(table, deletechars=\u0026#34;\u0026#34;) # 根据 table 给出的表(包含 256 个字符)转换 string 的字符, 要过滤掉的字符放到 deletechars 参数中 upper() # 转换字符串中的小写字母为大写 zfill (width) # 返回长度为 width 的字符串，原字符串右对齐，前面填充0 isdecimal() # 检查字符串是否只包含十进制字符，如果是返回 true，否则返回 false。 string模块 # import string # 一些均为string的属性 ascii_letters #获取所有ascii码中字母字符的字符串（包含大写和小写） ascii_uppercase #获取所有ascii码中的大写英文字母 ascii_lowercase #获取所有ascii码中的小写英文字母 digits #获取所有的10进制数字字符 octdigits #获取所有的8进制数字字符 hexdigits #获取所有16进制的数字字符 printable #获取所有可以打印的字符 whitespace #获取所有空白字符 punctuation #获取所有的标点符号 4、空值 # 空值是Python里一个特殊的值，用None表示。None不能理解为0，因为0是有意义的，而None是一个特殊的空值\n5、容器类型 # 除了List（列表）、Tuple（元组）、Dictionary（字典）、Set（集合）之外，String（字符串）也属于容器类型\n1、list（列表） # 列表，相当于java的ArrayList，可以嵌套，可以进行增删（可变），有序，自动扩容，使用[]表示\n可以存储不同的数据类型\n# 声明带有字面量的列表 names = [\u0026#39;tom\u0026#39;,\u0026#39;lucy\u0026#39;,\u0026#39;lily\u0026#39;,\u0026#39;jatlin\u0026#39;] # 声明带有变量的列表 names = [n1,n2,n3,n4] # 声明空列表 names = [] names = list() 相关方法 # #查看列表的元素个数 len(names) #访问指定索引的元素 names[0] #获取最后一个元素 names[-1] #添加元素到列表最后 names.append(\u0026#39;james\u0026#39;) #插入元素到指定索引位置 names.insert(1,\u0026#39;mary\u0026#39;) #删除末尾的元素,并得到这个元素 names.pop() #删除指定索引的元素，并得到这个元素 names.pop(1) #从前往后，删除第一个匹配的元素 names.remove(\u0026#39;lucy\u0026#39;) #替换指定索引的元素 names[1] = \u0026#39;jack\u0026#39; #切片第二个到第四个,不包括第四个 names[1:4] #判断是否包含某个元素 \u0026#39;tom\u0026#39; in names #查看元素出现的次数 names.count(\u0026#39;tom\u0026#39;) #查找元素 names.index(\u0026#39;tom\u0026#39;) 2、tuple（元组） # 元组，和列表非常相似，但是不可以进行增删改（不可变），有序，只可以进行查询，使用()表示\na = (\u0026#39;lucy\u0026#39;, \u0026#39;tom\u0026#39;, \u0026#39;james\u0026#39;) print(a) #只有一个元素也会加逗号，消除歧义 b = (1,) print(b) 3、dict（字典） # 字典，类似于java的HashMap，key-value形式，写法和json类似，无序（python3有序），可变，使用{}表示\n一个字典中key是唯一的，如果出现多个key，那么会保存最后一次该key的value\nstudents = {\u0026#39;tom\u0026#39;:18,\u0026#39;lucy\u0026#39;:22} #新增元素 students[\u0026#39;james\u0026#39;] = 25 #根据key获取value值,如果key不存在会报错 print(students[\u0026#39;lucy\u0026#39;]) #判断key是否存在,返回boolean类型 isExist = \u0026#39;lucy\u0026#39; in students print(isExist) #根据key获取value值，key不存在会返回None，或者自己指定的值 age = students.get(\u0026#39;lily\u0026#39;,99) print(age) #根据key删除元素，并获取该key对应的value print(students.pop(\u0026#39;tom\u0026#39;)) #获取键值对的个数 print(len(students)) #获取所有的 key print(students.keys()) # 清空字典 students.clear() 遍历字典 # a = {\u0026#39;name\u0026#39;:\u0026#39;lucy\u0026#39;,\u0026#39;age\u0026#39;:18,\u0026#39;grade\u0026#39;:9} # 通过遍历key，也可以写成 for key in a.keys for key in a: print(key,a[key]) # 遍历values for value in a.values(): print(value) # 遍历字典项，每一项是一个元组 for k,v in a.items(): print(k,v) 4、Set（集合） # 集合，无序（python3有序），不可重复，基本功能是进行成员关系测试和删除重复元素\n可以使用大括号{ } 或者set()函数创建集合。\n注意：创建一个空集合必须用set()而不是{ }，因为{ }是用来创建一个空字典\n#创建方式一：通过一个列表来创建集合 names = set([\u0026#39;tom\u0026#39;,\u0026#39;lucy\u0026#39;]) #创建方式二：直接创建 names = {\u0026#39;lucy\u0026#39;,\u0026#39;mary\u0026#39;} #创建空集合 names = set() 相关方法 # #集合的修改，因为集合是无序的，所以集合不支持下标索引访问 #添加一个元素，如果添加已存在的元素，不会有效果 names.add(\u0026#39;james\u0026#39;) #删除一个元素 names.remove(\u0026#39;tom\u0026#39;) #从集合中随机抽取一个元素，同时集合本身被修改，元素被移除 names.pop() #清空集合 names.clear() #set可以看成数学意义上的无序和无重复元素的集合，因此，两个set可以做数学意义上的交集、并集等操作 #交集 print(names1 \u0026amp; names2) #并集 print(names1 | names2) 容器通用功能总览 # 功能 描述 通用for循环 遍历容器（字典是遍历key） max() 容器内最大元素 min() 容器内最小元素 len() 容器元素个数 list() 转换为列表 tuple() 转换为元组 str() 转换为字符串 set() 转换为集合 sorted(序列, [reverse=True]) 排序，reverse=True表示降序得到一个排好序的List（列表） python和java数据类型的对比 # 查看变量中数据的类型 # 注意：Python中变量没有具体的数据类型，数据类型取决与变量指向的数据的类型\na = {\u0026#34;lucy\u0026#34;: 12, \u0026#34;tom\u0026#34;: 18} typeOfa = type(a) print(typeOfA) # \u0026lt;class \u0026#39;dict\u0026#39;\u0026gt; 类型转换 # 基本类型的转换 # int(a)：将a转为int\nfloat(a)：将a转为float\nstr(a)：将a转为String\nbool(a)：将a转为boolean，对于int，0为False，其他都是True，对于字符串，以及集合类型，只要有内容就是True，没有内容就是False\n容器类型的转换 # list(容器)：将给定容器转换为列表\nstr(容器)：将给定容器转换为字符串\ntuple(容器)：将给定容器转换为元组\nset(容器)：将给定容器转换为集合\n运算符 # 算数运算符 # 运算符 描述 实例 + 加 两个对象相加 a + b 输出结果 30 - 减 得到负数或是一个数减去另一个数 a - b 输出结果 -10 * 乘 两个数相乘或是返回一个被重复若干次的字符串 a * b 输出结果 200 / 除 b / a 输出结果 2（Python中的这种除法会带有余数） // 取整除 返回商的整数部分 9//2 输出结果 4 , 9.0//2.0 输出结果 4.0 % 取余 返回除法的余数 b % a 输出结果 0 ** 指数 a**b 为10的20次方， 输出结果 100000000000000000000 赋值运算符 # 基本赋值运算符 # 运算符 描述 实例 = 赋值运算符 把 = 号右边的结果 赋给 左边的变量，如 num = 1 + 2 * 3，结果num的值为7 =，将等号右边的值，赋值给左边的变量，和java一样，只是某些格式是java没有的\n# 可以连续赋值 a = b = 12; print(a) # 多个变量同时分别赋值 a,b,c = 11,12,13 print(a) 复合赋值运算符 # 类似与java的+=、*=\n运算符 描述 实例 += 加法赋值运算符 c += a 等效于 c = c + a -= 减法赋值运算符 c -= a 等效于 c = c - a *= 乘法赋值运算符 c *= a 等效于 c = c * a /= 除法赋值运算符 c /= a 等效于 c = c / a %= 取模赋值运算符 c %= a 等效于 c = c % a **= 幂赋值运算符 c **= a 等效于 c = c ** a //= 取整除赋值运算符 c //= a 等效于 c = c // a 比较运算符 # 和java一样，返回的都是boolean类型的数据\n运算符 描述 示例 == 判断内容是否相等，满足为True，不满足为False 如a=3,b=3，则(a == b) 为 True != 判断内容是否不相等，满足为True，不满足为False 如a=1,b=3，则(a != b) 为 True \u0026gt; 判断运算符左侧内容是否大于右侧满足为True，不满足为False 如a=7,b=3，则(a \u0026gt; b) 为 True \u0026lt; 判断运算符左侧内容是否小于右侧满足为True，不满足为False 如a=3,b=7，则(a \u0026lt; b) 为 True \u0026gt;= 判断运算符左侧内容是否大于等于右侧满足为True，不满足为False 如a=3,b=3，则(a \u0026gt;= b) 为 True \u0026lt;= 判断运算符左侧内容是否小于等于右侧满足为True，不满足为False 如a=3,b=3，则(a \u0026lt;= b) 为 True 逻辑运算符 # 相比于java，没有|、\u0026amp;，在python中and、or都是短路的\n进制 # 进制前面一般会有一个符号，这个符号表明了到底是多少进制。比如：\n二进制前面的符号是0b；\n八进制前面的符号是0o；\n十六进制前面的符号是0x；\n十进制前面一般没有符号。\n二进制 # a = 12 a_bin = bin(a) # 十进制转二进制 print(a_bin) # 0b1100 a = int(0b1100) # 二进制转十进制 print(a) # 12 八进制 # a = 12 a_oct = oct(a) # 十进制转八进制 print(a_oct) # 0o14 a = int(0o14) # 八进制转十进制 print(a) # 12 十六进制 # a = 12 a_hex = hex(a) # 十进制转十六进制 print(a_hex) # 0xc a = int(0xc) # 十六进制转十进制 print(a) # 12 转换为字符串 # a = 12 print(format(a, \u0026#39;b\u0026#39;)) # 1100 二进制 print(format(a, \u0026#39;o\u0026#39;)) # 14 八进制 print(format(a, \u0026#39;x\u0026#39;)) # c 十六进制 print(format(a, \u0026#39;d\u0026#39;)) # 12 十进制 字符集编码 # 字符集和编码的理解 # 字符集（Character Set） # 定义：字符的集合，定义了文本中可以使用的所有字符（如字母、数字、符号、汉字等）。 作用：规定哪些字符是合法的。 例子 ASCII：包含 128 个字符（如 A-Z, a-z, 0-9, 符号）。 Unicode：包含 143,859 个字符（覆盖几乎所有语言的字符）。 编码（Encoding） # 定义：将字符集中的每个字符映射到二进制数字的规则。 作用：规定字符如何存储为二进制。 例子 ASCII 编码：用 7 位二进制表示 ASCII 字符（如 A → 01000001）。 UTF-8 编码：用 1~4 字节表示 Unicode 字符（如 你 → 11100100 10111101 10100000）。 理解 # 最开始的计算机使用ASCII字符集，但是ASCII只包含了字母数字符号。\n后面出现了Unicode 字符集，整理了世界上大部分的字符。\n字符集，就是由码点和字符一一对应的。\n码点：从 0 开始编号，每个字符都分配一个唯一的码点，完整的十六进制格式是 U+[XX]XXXX，具体可表示的范围为 U+0000 ~ U+10FFFF （所需要的空间最大为 3 个字节的空间），例如 U+0011 。这个范围可以容纳超过 100 万个字符，足够容纳目前全世界已创造的字符。\nUnicode 本身只定义了字符与码点的映射关系，相当于定义了一套标准，而这套标准真正在计算机中落地时，则有多种编码格式。常见的有 3 种编码格式：UTF-8、UTF-16 和 UTF-32。UTF是英文 Unicode Transformation Format 的缩写，意思是 Unicode 字符转换为某种格式。\n别看编码格式五花八门，本质上只是出于空间和时间的权衡，对同一套字符标准使用不同的编码算法而已。\n例如，字符 A 的 Unicode 码点和编码如下：\n图像（字符）：A 码点：U+0041 UTF-8 编码：0X41 UTF-16 编码：0X0041 UTF-32 编码：0X00000041 也就是说，当你根据 UTF-8、UTF-16 和 UTF-32 的编码规则进行解码后，你将得到的结果都是一样的：0x41。\nPython中的编码处理 # 字符串和字节序列 # 字节序列 # 在 Python 中，以b开头的字节串来表示一个字节序列，即bytes\n**注意：**这种以字符形式直接表示字节的方式只适用于 ASCII 编码的字符，即数字、字母、符号\nsb = b\u0026#39;Hello,World\u0026#39; print(sb[0]) # 72 编码 # 对于包含中文的字符，可以使用encode()方法按照指定的字符集编码规则进行编码，不传参数默认是utf-8\ns = \u0026#39;Hello,世界\u0026#39; print(s.encode(\u0026#39;utf-8\u0026#39;)) # b\u0026#39;Hello,\\xe4\\xb8\\x96\\xe7\\x95\\x8c\u0026#39; 解码 # 对于字节序列，可以使用decode()方法按照指定的字符集编码规则进行解码，不传参数默认是utf-8\ns = \u0026#39;Hello,世界\u0026#39; sb = s.encode(\u0026#39;utf-8\u0026#39;) print(sb) # b\u0026#39;Hello,\\xe4\\xb8\\x96\\xe7\\x95\\x8c\u0026#39; s = sb.decode(\u0026#39;utf-8\u0026#39;) print(s) # Hello,世界 Unicode码点 # ord()：获取单个字符的Unicode码点\nchr()：或通过Unicode码点转化为字符。\n注意：这两个方法操作的是 Unicode 字符集的码点，而上面的encode()和decode()方法是按照编码规则进行编解码。\ncp_1 = ord(\u0026#39;你\u0026#39;) cp_2 = ord(\u0026#39;好\u0026#39;) print(cp_1) # 20320 print(cp_2) # 22909 c_1 = chr(cp_1) c_2 = chr(cp_2) print(c_1) # 你 print(c_2) # 好 ","date":"2025-04-23","externalUrl":null,"permalink":"/posts/1b37041b/aae761a6/573dcefc/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e变量 \n    \u003cdiv id=\"变量\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8f%98%e9%87%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e变量命名规范需要遵守标识符命名规范\u003c/p\u003e","title":"2、变量、数据类型、运算符","type":"posts"},{"content":"标签就是输出显示信息可动态也可静态\nimport tkinter as tk root = tk.Tk() lable = tk.Label(root,text=\u0026#39;Hello\u0026#39;) lable.pack() root.mainloop() 属性 # 选项 含义 activebackground 1. 设置当 Label 处于活动状态（通过 state 选项设置状态）的背景色； 2. 默认值由系统指定 activeforeground 1. 设置当 Label 处于活动状态（通过 state 选项设置状态）的前景色；2. 默认值由系统指定 anchor 1. 控制文本（或图像）在 Label 中显示的位置； 2. \u0026ldquo;n\u0026rdquo;, \u0026ldquo;ne\u0026rdquo;, \u0026ldquo;e\u0026rdquo;, \u0026ldquo;se\u0026rdquo;, \u0026ldquo;s\u0026rdquo;, \u0026ldquo;sw\u0026rdquo;, \u0026ldquo;w\u0026rdquo;, \u0026ldquo;nw\u0026rdquo;, 或者 \u0026ldquo;center\u0026rdquo; 来定位（ewsn 代表东西南北，上北下南左西右东）； 3. 默认值是 \u0026ldquo;center\u0026rdquo; background 1. 设置背景颜色 ；2. 默认值由系统指定 bg 跟 background 一样 bitmap 1. 指定显示到 Label 上的位图 ；2. 如果指定了 image 选项，则该选项被忽略 borderwidth 1. 指定 Label 的边框宽度； 2. 默认值由系统指定，通常是 1 或 2 像素 bd 跟 borderwidth 一样 compound 1. 控制 Label 中文本和图像的混合模式 ；2. 默认情况下，如果有指定位图或图片，则不显示文本 ；3.如果该选项设置为 \u0026ldquo;center\u0026rdquo;，文本显示在图像上（文本重叠图像）；4.如果该选项设置为 \u0026ldquo;bottom\u0026rdquo;，\u0026ldquo;left\u0026rdquo;，\u0026ldquo;right\u0026rdquo; 或 \u0026ldquo;top\u0026rdquo;，那么图像显示在文本的旁边（如 \u0026ldquo;bottom\u0026rdquo;，则图像在文本的下方）；5.默认值是 NONE cursor 1. 指定当鼠标在 Label 上飘过的时候的鼠标样式 ；2. 默认值由系统指定 disabledforeground 1. 指定当 Label 不可用的时候前景色的颜色 ；2. 默认值由系统指定 font 1. 指定 Label 中文本的字体(注：如果同时设置字体和大小，应该用元组包起来，如（\u0026ldquo;楷体\u0026rdquo;, 20）； 2. 一个 Label 只能设置一种字体 3. 默认值由系统指定 foreground 1. 设置 Label 的文本和位图的颜色 ；2. 默认值由系统指定 fg 跟 foreground 一样 height 1. 设置 Label 的高度 ；2. 如果 Label 显示的是文本，那么单位是文本单元 ；3. 如果 Label 显示的是图像，那么单位是像素（或屏幕单元）；4.如果设置为 0 或者干脆不设置，那么会自动根据 Label 的内容计算出高度 highlightbackground 1. 指定当 Label 没有获得焦点的时候高亮边框的颜色 ；2. 默认值由系统指定，通常是标准背景颜色 highlightcolor 1. 指定当 Label 获得焦点的时候高亮边框的颜色 ；2. 默认值由系统指定 highlightthickness 1. 指定高亮边框的宽度 ；2. 默认值是 0（不带高亮边框） image 1. 指定 Label 显示的图片； 2. 该值应该是 PhotoImage，BitmapImage，或者能兼容的对象； 3. 该选项优先于 text 和 bitmap 选项 justify 1. 定义如何对齐多行文本； 2. 使用 \u0026ldquo;left\u0026rdquo;，\u0026ldquo;right\u0026rdquo; 或 \u0026ldquo;center\u0026rdquo; ；3. 注意，文本的位置取决于 anchor 选项 4. 默认值是 \u0026ldquo;center\u0026rdquo; padx 1. 指定 Label 水平方向上的额外间距（内容和边框间）； 2. 单位是像素 pady 1. 指定 Label 垂直方向上的额外间距（内容和边框间）； 2. 单位是像素 relief 1. 指定边框样式 ；2. 默认值是 \u0026ldquo;flat\u0026rdquo; ；3. 另外你还可以设置 \u0026ldquo;groove\u0026rdquo;, \u0026ldquo;raised\u0026rdquo;, \u0026ldquo;ridge\u0026rdquo;, \u0026ldquo;solid\u0026rdquo; 或者 \u0026ldquo;sunken\u0026rdquo; state 1. 指定 Label 的状态 ；2. 这个标签控制 Label 如何显示 ；3. 默认值是 \u0026ldquo;normal； 4. 另外你还可以设置 \u0026ldquo;active\u0026rdquo; 或 \u0026ldquo;disabled\u0026rdquo; takefocus 1. 如果是 True，该 Label 接受输入焦点 ；2. 默认值是 False text 1. 指定 Label 显示的文本； 2. 文本可以包含换行符 ；3. 如果设置了 bitmap 或 image 选项，该选项则被忽略 textvariable 1. Label 显示 Tkinter 变量（通常是一个 StringVar 变量）的内容 ；2. 如果变量被修改，Label 的文本会自动更新 underline 1. 跟 text 选项一起使用，用于指定哪一个字符画下划线（例如用于表示键盘快捷键） ； 2. 默认值是 -1 3. 例如设置为 1，则说明在 Button 的第 2 个字符处画下划线 width 1. 设置 Label 的宽度 ；2. 如果 Label 显示的是文本，那么单位是文本单元 ；3. 如果 Label 显示的是图像，那么单位是像素（或屏幕单元） ；4. 如果设置为 0 或者干脆不设置，那么会自动根据 Label 的内容计算出宽度 wraplength 1. 决定 Label 的文本应该被分成多少行 ；2. 该选项指定每行的长度，单位是屏幕单元 ；3. 默认值是 0 ","date":"2025-04-15","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/a879fe06/9cc3fbfc/","section":"文章","summary":"\u003cp\u003e标签就是输出显示信息可动态也可静态\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003etkinter\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nn\"\u003etk\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eTk\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elable\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLabel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003etext\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;Hello\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elable\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epack\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emainloop\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20250414111837228\"\n    data-zoom-src=\"/posts/1b37041b/cf2af7cb/a879fe06/9cc3fbfc/image/image-20250414111837228.png\"\n    src=\"/posts/1b37041b/cf2af7cb/a879fe06/9cc3fbfc/image/image-20250414111837228.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e属性 \n    \u003cdiv id=\"属性\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b1%9e%e6%80%a7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e\u003cstrong\u003e选项\u003c/strong\u003e\u003c/th\u003e\n          \u003cth\u003e\u003cstrong\u003e含义\u003c/strong\u003e\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eactivebackground\u003c/td\u003e\n          \u003ctd\u003e1. 设置当 Label 处于活动状态（通过 state 选项设置状态）的背景色； 2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eactiveforeground\u003c/td\u003e\n          \u003ctd\u003e1. 设置当 Label 处于活动状态（通过 state 选项设置状态）的前景色；2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eanchor\u003c/td\u003e\n          \u003ctd\u003e1. 控制文本（或图像）在 Label 中显示的位置； 2. \u0026ldquo;n\u0026rdquo;, \u0026ldquo;ne\u0026rdquo;, \u0026ldquo;e\u0026rdquo;, \u0026ldquo;se\u0026rdquo;, \u0026ldquo;s\u0026rdquo;, \u0026ldquo;sw\u0026rdquo;, \u0026ldquo;w\u0026rdquo;, \u0026ldquo;nw\u0026rdquo;, 或者 \u0026ldquo;center\u0026rdquo; 来定位（ewsn 代表东西南北，上北下南左西右东）； 3. 默认值是 \u0026ldquo;center\u0026rdquo;\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ebackground\u003c/td\u003e\n          \u003ctd\u003e1. 设置背景颜色 ；2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ebg\u003c/td\u003e\n          \u003ctd\u003e跟 background 一样\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ebitmap\u003c/td\u003e\n          \u003ctd\u003e1. 指定显示到 Label 上的位图 ；2. 如果指定了 image 选项，则该选项被忽略\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eborderwidth\u003c/td\u003e\n          \u003ctd\u003e1. 指定 Label 的边框宽度； 2. 默认值由系统指定，通常是 1 或 2 像素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ebd\u003c/td\u003e\n          \u003ctd\u003e跟 borderwidth 一样\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ecompound\u003c/td\u003e\n          \u003ctd\u003e1. 控制 Label 中文本和图像的混合模式 ；2. 默认情况下，如果有指定位图或图片，则不显示文本 ；3.如果该选项设置为 \u0026ldquo;center\u0026rdquo;，文本显示在图像上（文本重叠图像）；4.如果该选项设置为 \u0026ldquo;bottom\u0026rdquo;，\u0026ldquo;left\u0026rdquo;，\u0026ldquo;right\u0026rdquo; 或 \u0026ldquo;top\u0026rdquo;，那么图像显示在文本的旁边（如 \u0026ldquo;bottom\u0026rdquo;，则图像在文本的下方）；5.默认值是 NONE\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ecursor\u003c/td\u003e\n          \u003ctd\u003e1. 指定当鼠标在 Label 上飘过的时候的鼠标样式 ；2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003edisabledforeground\u003c/td\u003e\n          \u003ctd\u003e1. 指定当 Label 不可用的时候前景色的颜色 ；2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003efont\u003c/td\u003e\n          \u003ctd\u003e1. 指定 Label 中文本的字体(注：如果同时设置字体和大小，应该用元组包起来，如（\u0026ldquo;楷体\u0026rdquo;, 20）； 2. 一个 Label 只能设置一种字体 3. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eforeground\u003c/td\u003e\n          \u003ctd\u003e1. 设置 Label 的文本和位图的颜色 ；2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003efg\u003c/td\u003e\n          \u003ctd\u003e跟 foreground 一样\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eheight\u003c/td\u003e\n          \u003ctd\u003e1. 设置 Label 的高度 ；2. 如果 Label 显示的是文本，那么单位是文本单元 ；3. 如果 Label 显示的是图像，那么单位是像素（或屏幕单元）；4.如果设置为 0 或者干脆不设置，那么会自动根据 Label 的内容计算出高度\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehighlightbackground\u003c/td\u003e\n          \u003ctd\u003e1. 指定当 Label 没有获得焦点的时候高亮边框的颜色 ；2. 默认值由系统指定，通常是标准背景颜色\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehighlightcolor\u003c/td\u003e\n          \u003ctd\u003e1. 指定当 Label 获得焦点的时候高亮边框的颜色 ；2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehighlightthickness\u003c/td\u003e\n          \u003ctd\u003e1. 指定高亮边框的宽度 ；2. 默认值是 0（不带高亮边框）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eimage\u003c/td\u003e\n          \u003ctd\u003e1. 指定 Label 显示的图片； 2. 该值应该是 PhotoImage，BitmapImage，或者能兼容的对象； 3. 该选项优先于 text 和 bitmap 选项\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ejustify\u003c/td\u003e\n          \u003ctd\u003e1. 定义如何对齐多行文本； 2. 使用 \u0026ldquo;left\u0026rdquo;，\u0026ldquo;right\u0026rdquo; 或 \u0026ldquo;center\u0026rdquo; ；3. 注意，文本的位置取决于 anchor 选项 4. 默认值是 \u0026ldquo;center\u0026rdquo;\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003epadx\u003c/td\u003e\n          \u003ctd\u003e1. 指定 Label 水平方向上的额外间距（内容和边框间）； 2. 单位是像素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003epady\u003c/td\u003e\n          \u003ctd\u003e1. 指定 Label 垂直方向上的额外间距（内容和边框间）； 2. 单位是像素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003erelief\u003c/td\u003e\n          \u003ctd\u003e1. 指定边框样式 ；2. 默认值是 \u0026ldquo;flat\u0026rdquo; ；3. 另外你还可以设置 \u0026ldquo;groove\u0026rdquo;, \u0026ldquo;raised\u0026rdquo;, \u0026ldquo;ridge\u0026rdquo;, \u0026ldquo;solid\u0026rdquo; 或者 \u0026ldquo;sunken\u0026rdquo;\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003estate\u003c/td\u003e\n          \u003ctd\u003e1. 指定 Label 的状态 ；2. 这个标签控制 Label 如何显示 ；3. 默认值是 \u0026ldquo;normal； 4. 另外你还可以设置 \u0026ldquo;active\u0026rdquo; 或 \u0026ldquo;disabled\u0026rdquo;\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003etakefocus\u003c/td\u003e\n          \u003ctd\u003e1. 如果是 True，该 Label 接受输入焦点 ；2. 默认值是 False\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003etext\u003c/td\u003e\n          \u003ctd\u003e1. 指定 Label 显示的文本； 2. 文本可以包含换行符 ；3. 如果设置了 bitmap 或 image 选项，该选项则被忽略\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003etextvariable\u003c/td\u003e\n          \u003ctd\u003e1. Label 显示 Tkinter 变量（通常是一个 StringVar 变量）的内容 ；2. 如果变量被修改，Label 的文本会自动更新\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eunderline\u003c/td\u003e\n          \u003ctd\u003e1. 跟 text 选项一起使用，用于指定哪一个字符画下划线（例如用于表示键盘快捷键） ； 2. 默认值是 -1 3. 例如设置为 1，则说明在 Button 的第 2 个字符处画下划线\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ewidth\u003c/td\u003e\n          \u003ctd\u003e1. 设置 Label 的宽度 ；2. 如果 Label 显示的是文本，那么单位是文本单元 ；3. 如果 Label 显示的是图像，那么单位是像素（或屏幕单元） ；4. 如果设置为 0 或者干脆不设置，那么会自动根据 Label 的内容计算出宽度\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ewraplength\u003c/td\u003e\n          \u003ctd\u003e1. 决定 Label 的文本应该被分成多少行 ；2. 该选项指定每行的长度，单位是屏幕单元 ；3. 默认值是 0\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e","title":"2、标签Label","type":"posts"},{"content":" 十大建模操作 # 以下的建模操作均在编辑模式（按Tab键进入）下进行\n挤出 # 快捷键：E\n向内挤出 # 快捷键：I\n倒角 # 快捷键：Ctrl + B\n循环切割 # 快捷键：Ctrl + R\n合并 # 快捷键：M\n断开 # 快捷键：V\n填充 # 快捷键：F\n切刀 # 快捷键：K\n切透：C\n桥接 # 快捷键：Ctrl + E\n**注意：**桥接必须发生在同物体中，如果不是同物体，可以在物体模式下，选中两个物体，然后用Ctrl + J进行合并\n分离 # 快捷键：P\n点、边、面命令组 # 可以呼出命令组菜单\n点命令组：Ctrl + V\n边命令组：Ctrl + E\n面命令组：Ctrl + F\n常用命令 # 点 # 顶点倒角：Ctrl + Shift + B\n链接顶点：J\n断开顶点：V\n焊接、合并：M\n边 # 倒角：Ctrl + B\n环切：Ctrl + R\n边线折痕：Shift + E，一般用于添加表面细分修改器后，某些边不需要光滑圆润，就可以使用边线折痕进行调整\n标记锐边：边菜单中标记锐边，一般用于自动平滑着色后，如果不需要光滑圆润，就可以使用标记锐边进行调整\n面 # 挤出：E、Alt + E\n内插面：I\n外插面：O\n填充：F\n应用修改 # 当一个模型被旋转、缩放后，会改变物体的属性（按N可以查看），后续的操作将会按照物体的属性进行，导致不一致。\n可以使用Ctrl + A应用物体的属性改变后，再进行其他操作，例如倒角等。\n例如在使用简易形变修改器旋转物体出现变形扭曲时，查看物体属性的各项数值是否正常（正常情况下旋转角度应该都是0度），若不正常则需要将数值设为0之后，使用Ctrl+A来将调整应用到物体上，之后就能够正常使用简易形变修改器进行旋转了。\n法线 # 可以通过打开右上角（叠加）中的（面朝向），看到模型所有面的朝向。\n​\t此时朝向外面的面为蓝色，朝向内的面为红色，例如我们把一个 Box 打开，可以发现Box 外面是蓝色，Box 里面是红色\n法向相关快捷键 # 编辑模式下，选中面\n法向菜单：Alt + N\n重新计算外侧：Shift + N\n轴心点与局部坐标系 # 轴心点一般作为整个模型的中心\n我们可以使用Control + .来编辑轴心点，通过G，即可进行移动轴心点，同时通过R可以旋转局部坐标系\n其他操作 # 视图与所选面对齐：Shift + 7\n移动点按照走线：双击G\n关联：Ctrl + L，这个一般就是先选择需要关联的模型，最后选择被关联的模型，然后按Ctrl + L可以理解为将被关联模型的一些数据（比如物体数据、材质、动画等）复制给需要关联的模型\n","date":"2025-04-12","externalUrl":null,"permalink":"/posts/69064821/f1464f3a/248fbf0f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e十大建模操作 \n    \u003cdiv id=\"十大建模操作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8d%81%e5%a4%a7%e5%bb%ba%e6%a8%a1%e6%93%8d%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e以下的建模操作均在编辑模式（按Tab键进入）下进行\u003c/p\u003e","title":"2、建模操作","type":"posts"},{"content":" 类 # 声明 # python2需要在类名后括号内写明父类，python3如果不写后面的括号则默认继承object\npass是占位语句，用来保证函数（方法）或类定义的完整性，表示无内容，空的意思\n#class 类名(父类，没有就是object): class Student(object): \u0026#34;\u0026#34;\u0026#34; 类注释 Attributes: attr1: 属性1 attr2: 属性2 attr3: 属性3 \u0026#34;\u0026#34;\u0026#34; pass 实例化 # class Student(object): pass #实例变量名 = 类名() stu = Student() print(stu) \u0026gt;\u0026gt;\u0026gt;\u0026gt; #打印可以看到，实例属于Student \u0026lt;__main__.Student object at 0x000001538F0097F0\u0026gt; 属性 # 由于类可以起到模板的作用，因此，可以在创建实例的时候，把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法（构造方法），在创建实例的时候，就把属性绑上去\n__init__方法的第一个参数永远是self，表示创建的实例本身，因此，在__init__方法内部，就可以把各种属性绑定到self，因为self就指向创建的实例本身\nclass Student(object): #构造方法 def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex stu = Student(\u0026#39;lucy\u0026#39;,18,\u0026#39;女\u0026#39;) print(stu.name) print(stu.age) print(stu.sex) 私有属性 # 要让内部属性不被外部访问，可以把属性的名称前加上两个下划线__，在Python中，实例的变量名如果以__开头，就变成了一个私有变量（private），只有内部可以访问，外部不能访问\n不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name，所以，仍然可以通过stu._Student__name来访问__name变量，但是不要这么做\nclass Student(object): #构造方法 def __init__(self,name,age,sex): self.__name = name self.__age = age self.__sex = sex stu = Student(\u0026#39;lucy\u0026#39;,18,\u0026#39;女\u0026#39;) 方法 # 要定义一个方法，除了第一个参数必须是self外，其他和普通函数一样\nclass Student(object): #默认值私有属性 __sex = \u0026#39;男\u0026#39; #构造方法 def __init__(self,name,age,sex): self.__name = name self.__age = age self.__sex = sex def showMsg(self): \u0026#34;展示学生信息\u0026#34; print(\u0026#34;姓名：\u0026#34;,self.__name,\u0026#34;年龄：\u0026#34;,self.__age,\u0026#34;性别：\u0026#34;,self.__sex) stu = Student(\u0026#39;lucy\u0026#39;,18,\u0026#39;女\u0026#39;) stu.showMsg() 私有方法 # 私有方法和私有属性一样，在方法名前加两个下划线__，方法就变成了私有的\n私有方法同样可以通过stu._Student__sayHello()进行访问，但是不要这么做\nclass Student(object): #构造方法 def __init__(self,name,age,sex): self.__name = name self.__age = age self.__sex = sex def showMsg(self): \u0026#34;展示学生信息\u0026#34; print(\u0026#34;姓名：\u0026#34;,self.__name,\u0026#34;年龄：\u0026#34;,self.__age,\u0026#34;性别：\u0026#34;,self.__sex) def __sayHello(self): \u0026#34;私有方法\u0026#34; print(\u0026#39;Hello\u0026#39;) stu = Student(\u0026#39;lucy\u0026#39;,18,\u0026#39;女\u0026#39;) stu.showMsg() 类的内置方法 # 除了前面我们使用的__init__外，Python还提供了很多的类的内置方法，这些内置方法我们称之为：魔术方法\n__str__ # 我们可以通过__str__方法，控制类转换为字符串的行为。\nclass Student: def __init__(self,name,age): self.name = name self.age = age def __str__(self): return f\u0026#39;我叫{self.name}，今年{self.age}岁\u0026#39; stu = Student(\u0026#34;Lucy\u0026#34;,18) print(stu) # 我叫Lucy，今年18岁 __lt__ # 小于符号比较方法\n直接对2个对象进行比较是不可以的，但是在类中实现__lt__方法，即可同时完成：小于符号 和 大于符号 2种比较\n比较大于符号的魔术方法是：__gt__，不过，实现了__lt__，__gt__就没必要实现了\nclass Student: def __init__(self,name,age): self.name = name self.age = age def __lt__(self,other): return self.age \u0026lt; other.age stu1 = Student(\u0026#34;Lucy\u0026#34;,18) stu2 = Student(\u0026#34;Tom\u0026#34;,20) print(stu1 \u0026lt; stu2) # True __le__ # 小于等于的比较符号。同上。\n\u0026gt;=符号实现的魔术方法是：__ge__。不过，实现了__le__，__ge__就没必要实现了\n__eq__ # 等于的比较符号。不实现__eq__方法，对象之间可以比较，但是是比较内存地址，也即是：不同对象==比较一定是False结果。\n静态 # 静态属性 # 在类中直接定义的属性，就是静态属性，这个属性属于类，被该类所有实例共有\n需要注意：\n当我们根据使用实例对象修改静态属性时，该实例对象的类属性会改变，但只会作用于自身（修改的实例对象），不会影响其他实例的属性值。\n当我们通过类直接修改静态属性时，静态属性会发生改变，并且生效作用于其他的实例对象，其他的实例对象访问结果会变成类修改静态属性后的结果，而实例对象修改过后的静态属性却没有受到影响，它的静态属性的值是它（实例对象）修改过后的值。\nclass MyClass: class_attr = \u0026#34;I am a class attribute\u0026#34; def __init__(self, ins_attr): self.ins_attr = ins_attr if __name__ == \u0026#39;__main__\u0026#39;: obj1 = MyClass(\u0026#34;I am an instance attribute of obj1\u0026#34;) obj2 = MyClass(\u0026#34;I am an instance attribute of obj2\u0026#34;) print(obj1.class_attr) # 输出 \u0026#34;I am a class attribute\u0026#34; print(obj2.class_attr) # 输出 \u0026#34;I am a class attribute\u0026#34; print(obj1.ins_attr) # 输出 \u0026#34;I am an instance attribute of obj1\u0026#34; print(obj2.ins_attr) # 输出 \u0026#34;I am an instance attribute of obj2\u0026#34; obj1.class_attr = \u0026#34;I am a new update class attribute of obj1\u0026#34; print(obj1.class_attr) # 输出 \u0026#34;I am a new update class attribute of obj1\u0026#34; print(obj2.class_attr) # 输出 \u0026#34;I am a class attribute\u0026#34; MyClass.class_attr = \u0026#34;I am a new MyClass attribute\u0026#34; print(obj1.class_attr) # 输出 \u0026#34;I am a new update class attribute of obj1\u0026#34; print(obj2.class_attr) # 输出 \u0026#34;I am a new MyClass attribute\u0026#34; print(MyClass.class_attr) # 输出 \u0026#34;I am a new MyClass attribute\u0026#34; 静态方法 # python中，可以使用装饰器@classmethod或@staticmethod，\n@staticmethod不需要表示自身对象的self和自身类的cls参数，就跟使用函数一样。 @classmethod也不需要self参数，但第一个参数需要是表示自身类的cls参数。 如果在@staticmethod中要调用到这个类的一些属性方法，只能直接类名.属性名或类名.方法名。\n而@classmethod因为持有cls参数，可以来调用类的属性，类的方法，实例化对象等，避免硬编码。\nclass Student: @staticmethod def good(): print(\u0026#39;good\u0026#39;) @classmethod def hello(cls): cls.good() print(\u0026#39;hello\u0026#39;) @staticmethod def bye(): Student.good() print(\u0026#39;bye\u0026#39;) 继承和多态 # 在OOP程序设计中，当我们定义一个class的时候，可以从某个现有的class继承，新的class称为子类（Subclass），而被继承的class称为基类、父类或超类（Base class、Super class）\n可以使用isinstance(obj,class)来查看对象obj是否属于类class\nclass Animal(object): pass #狗、猫类继承于动物类 class Dog(Animal): pass class Cat(Animal): pass #实例化 d = Dog() c = Cat() print(isinstance(d,Animal)) print(isinstance(c,Animal)) 属性的继承 # 子类会继承父类中已经声明的属性，并且可以使用默认值\nclass Animal(object): name = \u0026#39;动物\u0026#39; #狗类继承于动物类 class Dog(Animal): pass #实例化 d = Dog() #调用父类声明的属性 print(d.name) 方法的继承 # 子类会继承父类的所有方法，包括构造方法__init__()\nclass Animal(object): def __init__(self,name): self.__name = name def run(self): \u0026#34;动物都有的行为\u0026#34; print(\u0026#39;%s在跑\u0026#39; % self.__name) #狗、猫类继承于动物类 class Dog(Animal): pass class Cat(Animal): pass #实例化，继承父类的构造方法 d = Dog(\u0026#34;狗\u0026#34;) c = Cat(\u0026#34;猫\u0026#34;) #继承父类的方法 d.run() c.run() 方法重写 # 如果你的父类方法的功能不能满足你的需求，你可以在子类重写你父类的方法\nclass Animal(object): def run(self): print(\u0026#34;动物在跑\u0026#34;) class Dog(Animal): #重写父类方法run() def run(self): print(\u0026#34;狗子在跑\u0026#34;) dog = Dog() dog.run() super()函数 # super() 函数是用于调用父类(超类)的一个方法，也就是使用子类对象，调用父类已被重写的方法\nsuper() 是用来解决多重继承问题的，直接用类名调用父类方法在使用单继承的时候没问题，但是如果使用多继承，会涉及到查找顺序（MRO）、重复调用（钻石继承）等种种问题。\nMRO就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表。\nclass Animal(object): def run(self): print(\u0026#34;动物在跑\u0026#34;) class Dog(Animal): # 重写父类方法run() def run(self): # 调用父类方法run() super().run() print(\u0026#34;狗子在跑\u0026#34;) d = Dog() d.run() # 动物在跑 # 狗子在跑 # 调用实例的父类方法 super(Dog,d).run() # 动物在跑 多继承 # python支持多继承，即可以继承于多个父类\n需要注意圆括号中父类的顺序，若是父类中有相同的方法名，而在子类使用时未指定，python从左至右搜索即方法在子类中未找到时，从左到右查找父类中是否包含方法\nclass Animal(object): def run(self): \u0026#34;动物都有的行为\u0026#34; print(\u0026#39;动物可以跑\u0026#39;) class Pet(object): def play(self): \u0026#34;宠物都有的行为\u0026#34; print(\u0026#39;宠物可以和主人玩\u0026#39;) class Cat(Animal,Pet): pass cat = Cat() cat.play() cat.run() 多态 # python不支持多态也用不到多态，多态的概念是应用于Java和C#这一类强类型语言中，而Python崇尚鸭子类型（Duck Typing）\n鸭子类型：是一种动态类型的风格。一个对象有效的语义，不是由继承自特定的类或实现特定的接口，而是由当前方法和属性的集合决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试，“鸭子测试”可以这样表述：“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子，那么这只鸟就可以被称为鸭子。”\n也就是说，我们可以编写一个函数，它接受一个类型为鸭的对象，并调用它的走和叫方法。在使用鸭子类型的语言中，这样的一个函数可以接受一个任意类型的对象，并调用它的走和叫方法。如果这些需要被调用的方法不存在，那么将引发一个运行时错误\nclass Duck(object): def go(self): print(\u0026#39;鸭子走\u0026#39;) def run(self): print(\u0026#39;鸭子跑\u0026#39;) class Dog(object): def go(self): print(\u0026#39;狗走\u0026#39;) def run(self): print(\u0026#39;狗跑\u0026#39;) #python的多态，不在于是哪个类，而是只要该对象有这两个方法就可以 def handler(duck): duck.go() duck.run() duck = Duck() dog = Dog() handler(duck) handler(dog) 获取对象信息 # type(obj) # type()可以获取对象的类型，返回一个Class类型的变量，可以使用==判断两个对象是否同一类型\nclass Person(object): def __init__(self,name,age): self.__name = name self.__age = age def showMsg(self): print(\u0026#39;姓名\u0026#39;,self.__name,\u0026#39;年龄\u0026#39;,self.__age) person = Person(\u0026#39;lucy\u0026#39;,18) print(type(person)) # \u0026lt;class \u0026#39;__main__.Person\u0026#39;\u0026gt; isinstance(obj,class) # isinstance(obj,class)来查看对象obj是否属于类class\nclass Person(object): def __init__(self,name,age): self.__name = name self.__age = age def showMsg(self): print(\u0026#39;姓名\u0026#39;,self.__name,\u0026#39;年龄\u0026#39;,self.__age) person = Person(\u0026#39;lucy\u0026#39;,18) print(isinstance(person,Person)) # Ture dir(obj) # 如果要获得一个对象的所有属性和方法，可以使用dir()函数，它返回一个包含字符串的list\nclass Person(object): def __init__(self,name,age): self.__name = name self.__age = age def showMsg(self): print(\u0026#39;姓名\u0026#39;,self.__name,\u0026#39;年龄\u0026#39;,self.__age) person = Person(\u0026#39;lucy\u0026#39;,18) print(dir(person)) \u0026#39;\u0026#39;\u0026#39; [\u0026#39;_Person__age\u0026#39;, \u0026#39;_Person__name\u0026#39;, \u0026#39;__class__\u0026#39;, \u0026#39;__delattr__\u0026#39;, \u0026#39;__dict__\u0026#39;, \u0026#39;__dir__\u0026#39;, \u0026#39;__doc__\u0026#39;, \u0026#39;__eq__\u0026#39;, \u0026#39;__format__\u0026#39;, \u0026#39;__ge__\u0026#39;, \u0026#39;__getattribute__\u0026#39;, \u0026#39;__gt__\u0026#39;, \u0026#39;__hash__\u0026#39;, \u0026#39;__init__\u0026#39;, \u0026#39;__init_subclass__\u0026#39;, \u0026#39;__le__\u0026#39;, \u0026#39;__lt__\u0026#39;, \u0026#39;__module__\u0026#39;, \u0026#39;__ne__\u0026#39;, \u0026#39;__new__\u0026#39;, \u0026#39;__reduce__\u0026#39;, \u0026#39;__reduce_ex__\u0026#39;, \u0026#39;__repr__\u0026#39;, \u0026#39;__setattr__\u0026#39;, \u0026#39;__sizeof__\u0026#39;, \u0026#39;__str__\u0026#39;, \u0026#39;__subclasshook__\u0026#39;, \u0026#39;__weakref__\u0026#39;, \u0026#39;showMsg\u0026#39;] \u0026#39;\u0026#39;\u0026#39; 仅仅把属性和方法列出来是不够的，配合getattr()、setattr()以及hasattr()，我们可以直接操作一个对象的状态，类似java的反射机制\ngetattr(obj,attr) # 获取对象某个属性的值\nclass Person(object): def __init__(self,name,age): self.__name = name self.__age = age def showMsg(self): print(\u0026#39;姓名\u0026#39;,self.__name,\u0026#39;年龄\u0026#39;,self.__age) person = Person(\u0026#39;lucy\u0026#39;,18) #获取person对象_Person__name属性的值 print(getattr(person,\u0026#39;_Person__name\u0026#39;)) #获取person对象_Person__name属性的值，如果不存在，返回404 print(getattr(person,\u0026#39;_Person__name\u0026#39;，404)) setattr(obj,attr,value) # 设置对象某个属性的值\nclass Person(object): def __init__(self,name,age): self.__name = name self.__age = age def showMsg(self): print(\u0026#39;姓名\u0026#39;,self.__name,\u0026#39;年龄\u0026#39;,self.__age) person = Person(\u0026#39;lucy\u0026#39;,18) #设置person对象，_Person__name属性值为tom setattr(person,\u0026#39;_Person__name\u0026#39;,\u0026#39;tom\u0026#39;) person.showMsg() hasattr(obj,attr) # 判断对象是否含有某个属性\nclass Person(object): def __init__(self,name,age): self.__name = name self.__age = age def showMsg(self): print(\u0026#39;姓名\u0026#39;,self.__name,\u0026#39;年龄\u0026#39;,self.__age) person = Person(\u0026#39;lucy\u0026#39;,18) #判断对象person中是否含有属性_Person__name print(hasattr(person,\u0026#39;_Person__name\u0026#39;)) 此外，这三个函数也可以作用于对象的方法\nclass Person(object): def __init__(self,name,age): self.__name = name self.__age = age def showMsg(self): print(\u0026#39;姓名\u0026#39;,self.__name,\u0026#39;年龄\u0026#39;,self.__age) person = Person(\u0026#39;lucy\u0026#39;,18) #判断对象person中是否含有方法showMsg print(hasattr(person,\u0026#39;showMsg\u0026#39;)) #获取对象person的方法showMsg show = getattr(person,\u0026#39;showMsg\u0026#39;) #调用方法 show() # 姓名 lucy 年龄 18 类型注解 # Python在3.5版本的时候引入了类型注解，以方便静态类型检查工具，IDE等第三方工具。\n类型注解：在代码中涉及数据交互的地方，提供数据类型的注解（显式的说明）。\n支持：\n变量的类型注解 函数（方法）形参列表和返回值的类型注解 # 变量的类型注解 a: int = 10 b: float = 2.5 c: str = \u0026#39;hello\u0026#39; d: bool = True e: dict[str,int] = {\u0026#39;a\u0026#39;:1,\u0026#39;b\u0026#39;:2} f: list[int] = [1,2,3,4] g: set[int] = {1,2,3,4,5} h: tuple[int] = (1,2,3,4,5) # 函数的类型注解 def test(a: int,b: str) -\u0026gt; str: pass Union类型 # 使用Union[类型, ......, 类型]可以定义联合类型注解，可以理解为，里面的类型都是可以的\nfrom typing import Union a: list[Union[int,str]] = [1,2,\u0026#39;3\u0026#39;,\u0026#39;4\u0026#39;] ","date":"2025-04-10","externalUrl":null,"permalink":"/posts/1b37041b/a64b122d/30ac5c13/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e类 \n    \u003cdiv id=\"类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e声明 \n    \u003cdiv id=\"声明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%a3%b0%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003epython2需要在类名后括号内写明父类，python3如果不写后面的括号则默认继承object\u003c/p\u003e","title":"2、面向对象","type":"posts"},{"content":" 数组 # 数组是相同类型数据的有序集合，数组可以代表任何相同类型的一组内容，其中存放的每一个数据称为数组的一个元素。\n创建数组 # 在Kotlin中有两种创建方式：\n官方预设工具函数，如arrayOf()、arrayOfNulls()以及emptyArray() 使用类Array构造函数创建。 Array 类 # // Array 类的定义 public class Array\u0026lt;T\u0026gt; { //构造函数，包括数组大小、元素初始化函数 public inline constructor(size: Int, init: (Int) -\u0026gt; T) //重载[]运算符 public operator fun get(index: Int): T public operator fun set(index: Int, value: T): Unit //当前数组大小（可以看到是val类型的，一旦确定不可修改） public val size: Int //迭代运算重载（后面讲解） public operator fun iterator(): Iterator\u0026lt;T\u0026gt; } 构造函数创建\n// [0,2,4] var arr: Array\u0026lt;Int\u0026gt; = Array(3) { i -\u0026gt; i * 2 } // [0, 0, 0, 0, 0] var arr: Array\u0026lt;Int\u0026gt; = Array(5){0} arrayOf # var arr1: Array\u0026lt;Int\u0026gt; = arrayOf(1,2,3,4,5) var arr2: Array\u0026lt;String\u0026gt; = arrayOf(\u0026#34;Tom\u0026#34;,\u0026#34;Lucy\u0026#34;) typeArrayOf # Kotlin 也提供了指定类型数组快速创建方法\n这些包含基本类型的数组往往在编译时可以得到优化（比如JVM平台会直接编译为基本类型数组，如int[]、double[]等，可以免去装箱拆箱开销）Kotlin提供了预设的原生类型数组\n原生类型数组 相当于Java BooleanArray boolean[] ByteArray byte[] CharArray char[] DoubleArray double[] FloatArray float[] IntArray int[] LongArray long[] ShortArray short[] var arr1 = intArrayOf(1,2,3) var arr2 = doubleArrayOf(1.0,2.0,3.0) var arr3 = charArrayOf(\u0026#39;a\u0026#39;,\u0026#39;b\u0026#39;,\u0026#39;c\u0026#39;) 这些原生类型数组也有一些额外的扩展\nval array: IntArray = intArrayOf(7, 3, 9, 1, 6) println(array.sum()) //快速求和操作，获得数组中所有元素之和 println(array.average()) //求整个数组的平均数 println(array.min()) // 获取最小值 println(array.max()) // 获取最大值 操作元素 # 访问 # fun main() { var arr: Array\u0026lt;Int\u0026gt; = arrayOf(1,2,3,4,5) println(arr[0]) println(arr[1]) } 设值 # fun main() { var arr: Array\u0026lt;Int\u0026gt; = arrayOf(1,2,3,4,5) arr[0] = 10 println(arr[0]) } 遍历 # 通过数组元素遍历\nfun main() { var arr: Array\u0026lt;Int\u0026gt; = arrayOf(1,2,3,4,5) for (i in arr){ println(i) } } 通过数组索引遍历\nfun main() { var arr: Array\u0026lt;Int\u0026gt; = arrayOf(1,2,3,4,5) for(i in 0..arr.size - 1){ println(arr[i]) } } 上面遍历的时候需要对arr.size - 1来获取有效的索引（因为 size = 5，但是 arr 最后一个元素是 arr[4]）\n其实Kotlin 的数组提供了一个属性indices来返回的是数组的有效索引范围\nfun main() { var arr: Array\u0026lt;Int\u0026gt; = arrayOf(1,2,3,4,5) for(i in arr.indices){ println(arr[i]) } } 如果想同时遍历数组的元素以及索引，那么 Kotlin 的数组提供了一个方法withIndex，它会生成一系列IndexedValue对象\nfun main() { var arr: Array\u0026lt;Int\u0026gt; = arrayOf(1,2,3,4,5) for ((index,value) in arr.withIndex()){ println(\u0026#34;$index $value\u0026#34;) } } 如果需要使用Lambda表达式快速处理里面的每一个元素，也可以使用forEach高阶函数\nfun main() { var arr: Array\u0026lt;Int\u0026gt; = arrayOf(1,2,3,4,5) // 只遍历元素 arr.forEach { println(it) } // 遍历元素和索引 arr.forEachIndexed { index, value -\u0026gt; println(\u0026#34;$index $value\u0026#34;) } } 如果只是想打印数组里面的内容，快速查看，我们可以使用如下方式\nfun main() { var arr: Array\u0026lt;Int\u0026gt; = arrayOf(1,2,3,4,5) println(arr.joinToString()) // 1, 2, 3, 4, 5 println(arr.joinToString(\u0026#34; - \u0026#34;)) // 1 - 2 - 3 - 4 - 5 println(arr.joinToString(\u0026#34; - \u0026#34;,\u0026#34;\u0026lt;\u0026#34; , \u0026#34;\u0026gt;\u0026#34;)) // \u0026lt;1 - 2 - 3 - 4 - 5\u0026gt; println(arr.joinToString(limit = 3, truncated = \u0026#34;...\u0026#34;)) // 1, 2, 3... println(arr.joinToString { \u0026#34;value: $it\u0026#34; }) // value: 1, value: 2, value: 3, value: 4, value: 5 } 数组比较 # fun main() { var arr1 = arrayOf(1,2,3,4,5) var arr2 = arrayOf(1,2,3,4,5) var arr3 = arrayOf(1,3,2,4,5) println(arr1 == arr2) // false println(arr1.contentEquals(arr2)) // true println(arr1.contentEquals(arr3)) // false } 拷贝 # 全部拷贝\nfun main() { var arr1 = arrayOf(1,2,3,4,5) var arr2 = arr1.copyOf() println(arr1 == arr2) // false 不是同一个对象 println(arr1.contentEquals(arr2)) // true } 指定拷贝的长度\n在拷贝时指定要拷贝的长度，如果小于数组长度则只保留前面一部分内容，如果大于则在末尾填充null，因此返回的类型是Int?可空\nfun main() { var arr1 = arrayOf(1,2,3,4,5) var arr2: Array\u0026lt;Int?\u0026gt; = arr1.copyOf(10) println(arr2.joinToString()) // 1, 2, 3, 4, 5, null, null, null, null, null } 使用copyOfRange拷贝指定下标范围上的元素\nfun main() { var arr1 = arrayOf(1,2,3,4,5) var arr2 = arr1.copyOfRange(1,3) // 从 index = 1 开始拷贝，到 index = 3 的前一个元素 println(arr2.joinToString()) // 2, 3 } 也有一个和copyOfRange相似的方法sliceArray分割数组，参数可以传入Range\nfun main() { var arr1 = arrayOf(1,2,3,4,5) var arr2 = arr1.sliceArray(1..3) // 按照传入的 Range 进行分割 println(arr2.joinToString()) // 2, 3, 4 } 拼接数组 # 两个数组也可以直接拼接到一起，形成一个长度为10的新数组，按顺序拼接\nfun main() { var arr1 = arrayOf(1,2,3,4,5) var arr2 = arrayOf(6,7,8,9,10) var arr3 = arr1 + arr2 println(arr3.joinToString()) // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } 查找元素 # fun main() { var arr1 = arrayOf(1,2,3,4,5) println(arr1.contains(3)) // 判断数组中是否包含3这个元素 println(3 in arr1) // 判断数组中是否包含3这个元素 println(arr1.indexOf(3)) // 获取3这个元素的索引 println(arr1.binarySearch(3)) // 二分查找，返回索引，找不到返回负数，但需要数组有序 } val array = arrayOf(1, 2, 3, 4, 5) println(array.any()) //判断数组是否为空数组（容量为0） println(array.first()) //快速获取首个元素 println(array.last()) //快速获取最后一个元素 翻转数组 # 翻转原数组\nfun main() { var arr1 = arrayOf(1,2,3,4,5) arr1.reverse() println(arr1.joinToString()) // 5, 4, 3, 2, 1 } 仅翻转指定下标\nfun main() { var arr1 = arrayOf(1,2,3,4,5) arr1.reverse(1,3) println(arr1.joinToString()) // 1, 3, 2, 4, 5 } 翻转并生成新数组\nfun main() { var arr1 = arrayOf(1,2,3,4,5) var arr2 = arr1.reversedArray() println(arr1.joinToString()) // 1, 2, 3, 4, 5 println(arr2.joinToString()) // 5, 4, 3, 2, 1 } 打乱数组 # 如果我们想要直接将数组中元素打乱，也有一个快速洗牌的函数将所有元素顺序重新随机分配\nfun main() { var arr1 = arrayOf(1,2,3,4,5) arr1.shuffle() println(arr1.joinToString()) // 3, 5, 4, 2, 1 arr1.shuffle() println(arr1.joinToString()) // 1, 2, 4, 5, 3 } 数组排序 # fun main() { var arr1 = arrayOf(1,2,3,4,5) arr1.shuffle() // 打乱 println(arr1.joinToString()) // 2, 4, 5, 3, 1 arr1.sort() // 排序 println(arr1.joinToString()) // 1, 2, 3, 4, 5 } 倒序\nfun main() { var arr1 = arrayOf(1,2,3,4,5) arr1.shuffle() // 打乱 println(arr1.joinToString()) // 2, 4, 5, 3, 1 arr1.sortDescending() // 倒序排序 println(arr1.joinToString()) // 5, 4, 3, 2, 1 } 填充数组 # fun main() { var arr = arrayOfNulls\u0026lt;Int\u0026gt;(10) arr.fill(1) // 全部填充 1 println(arr.contentToString()) // [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] } 集合 # 在Kotlin中，默认提供了以下类型的集合：\nList： 有序的集合，通过索引访问元素，可以包含重复元素，比如电话号码：它就是一组数字，并且顺序很重要，而且数字可以重复。 Set： 不包含重复元素的集合，它更像是数学中的集合，一般情况下不维护元素顺序，例如，彩票上的数字：都是独一无二的，并且它们的顺序不重要。 Map： 是一组键值对，其中每一个键不可重复存在，每个键都映射到恰好一个值（值可以重复存在）这跟数学上的映射关系很像。 所有集合类都是继承自Collection接口（Map除外）\nKotlin 的集合设计和 Java 不同的另一项重要特质是，它把访问集合数据的接口和修改集合数据的接口分开了。\n这种区别在于最基础的使用集合的接口之中：\n使用 kotlin.collections.Collection 接口，可以遍历结合中的元素、获取集合的大小、判断集合中是否包含某个元素，以及执行其他从该结合中读取数据的操作。但这个接口没有任何添加或移除元素的方法； 使用 kotlin.collections.MutableCollection 接口，可以修改集合中的数据。它继承了普通 kotlin.collections.Collection 接口，还提供了方法来添加和移除元素、清空集合等； 除了集合之外，Kotlin 中的 Map 类（它没有继承 Collection 或是 Iterable）也被表示成了两种不同的版本：Map 和 MutableMap。\nList # fun main() { var list: MutableList\u0026lt;Int\u0026gt; = mutableListOf(1,2,3) list.add(4) // 使用add函数添加一个新的元素到列表末尾 list[0] = 10 // 修改某个位置上的元素 println(list) // [10, 2, 3, 4] println(list[2]) // 获取某个位置上的元素 } 插入元素 # fun main() { val list = mutableListOf(1, 2, 3, 4) list.add(2, 666) //将666插入到第三个元素的位置上去 println(list) // [1, 2, 666, 3, 4] } 删除元素 # fun main() { val list = mutableListOf(\u0026#34;AAA\u0026#34;, \u0026#34;BBB\u0026#34;, \u0026#34;CCC\u0026#34;, \u0026#34;DDD\u0026#34;) list.removeAt(2) //使用removeAt可以删除指定位置上的元素 println(list) // [AAA, BBB, DDD] list.remove(\u0026#34;DDD\u0026#34;) //使用remove可以删除指定元素 println(list) // [AAA, BBB] } 只读列表 # 使用listOf生成的列表是只读的\nfun main() { var list = listOf(1,2,3) list[0] = 10 // No set method providing array access } Set # Set集合非常特殊，虽然它也可以保存一组数据，但是它不允许存在重复元素\nfun main() { var set = mutableSetOf(\u0026#34;AAA\u0026#34;,\u0026#34;BBB\u0026#34;,\u0026#34;BBB\u0026#34;,\u0026#34;CCC\u0026#34;) println(set) // [AAA, BBB, CCC] } 操作方法和 List 相同\nMap # Map是一个非常特殊的集合类型，它存储了一些的键值对\n在 Kotlin 中，提供了一个非常方便的中缀函数来定义键值对\npublic infix fun \u0026lt;A, B\u0026gt; A.to(that: B): Pair\u0026lt;A, B\u0026gt; = Pair(this, that) 也就是说，创建一个键值对是这样的，其中 100 是 key，lucy 是 value\nvar kv: Pair\u0026lt;Int, String\u0026gt; = 100 to \u0026#34;lucy\u0026#34; 创建一个 map 其实就是创建多个键值对\nfun main() { var map = mutableMapOf( \u0026#34;key1\u0026#34; to \u0026#34;value1\u0026#34;, \u0026#34;key2\u0026#34; to \u0026#34;value2\u0026#34; ) println(map) // {key1=value1, key2=value2} } 访问元素 # fun main() { var map = mutableMapOf( \u0026#34;key1\u0026#34; to \u0026#34;value1\u0026#34;, \u0026#34;key2\u0026#34; to \u0026#34;value2\u0026#34; ) map[\u0026#34;key3\u0026#34;] = \u0026#34;value3\u0026#34; // 添加键值对 map.put(\u0026#34;key4\u0026#34;, \u0026#34;value4\u0026#34;) // 添加键值对 map += \u0026#34;key5\u0026#34; to \u0026#34;value5\u0026#34; // 添加键值对 println(map[\u0026#34;key1\u0026#34;]) // value1 println(map.contains(\u0026#34;key1\u0026#34;)) // true println(\u0026#34;key1\u0026#34; in map) // 同上 true println(map.containsValue(\u0026#34;value1\u0026#34;)) // true } 注意： Map中的键值对存储，只能通过Key去访问Value，而不能通过Value来反向查找Key，映射关系是单向的。\n获取 keys 或 values # fun main() { var map = mutableMapOf( \u0026#34;key1\u0026#34; to \u0026#34;value1\u0026#34;, \u0026#34;key2\u0026#34; to \u0026#34;value2\u0026#34; ) var keys: MutableSet\u0026lt;String\u0026gt; = map.keys var values: MutableCollection\u0026lt;String\u0026gt; = map.values println(keys) // [key1, key2] println(values) // [value1, value2] } 遍历 # fun main() { var map = mutableMapOf( \u0026#34;key1\u0026#34; to \u0026#34;value1\u0026#34;, \u0026#34;key2\u0026#34; to \u0026#34;value2\u0026#34; ) map.forEach {(key,value) -\u0026gt; println(\u0026#34;$key - $value\u0026#34;)} for ((key,value) in map){ println(\u0026#34;$key - $value\u0026#34;) } for (entry in map){ println(\u0026#34;${entry.key} - ${entry.value}\u0026#34;) } } 不同的Map # 有了Map之后，我们在处理一些映射关系的时候就很方便了。跟Set一样，官方也提供了多种多样的集合\nval map1 = mapOf(1 to \u0026#34;AAA\u0026#34;) //只读Map val map2 = hashMapOf(1 to \u0026#34;AAA\u0026#34;) //不保存Key顺序的Map val map3 = linkedMapOf(1 to \u0026#34;AAA\u0026#34;) //保存Key顺序的Map，跟mutableMapOf一样 val map4 = sortedMapOf(1 to \u0026#34;AAA\u0026#34;) //根据排序规则自动对Key排序的Map val map5 = emptyMap\u0026lt;Int, String\u0026gt;() //空Map val hashMap = HashMap\u0026lt;Int, String\u0026gt;() //采用构造函数创建的HashMap，不保存Key顺序的Map，同map2 val linkedHashSet = LinkedHashMap\u0026lt;Int, String\u0026gt;() //采用构造函数创建的LinkedHashMap，保存Key顺序的Map，同map3 迭代器 # 集合类型的顶层接口都是一个叫做Collection的接口，继承自Iterable接口\npublic interface Iterator\u0026lt;out T\u0026gt; { //获取下一个待遍历元素 public operator fun next(): T //如果还有元素没有遍历，那么返回true否则返回false，而这个函数也是运算符重载函数正好对应着 for in 操作 public operator fun hasNext(): Boolean } 获取迭代器对象 # val list = listOf(\u0026#34;AAA\u0026#34;, \u0026#34;BBB\u0026#34;, \u0026#34;CCC\u0026#34;) val iterator: Iterator\u0026lt;String\u0026gt; = list.iterator() //通过iterator函数得到迭代器对象 迭代 # val list = listOf(\u0026#34;AAA\u0026#34;, \u0026#34;BBB\u0026#34;, \u0026#34;CCC\u0026#34;) val iterator: Iterator\u0026lt;String\u0026gt; = list.iterator() while (iterator.hasNext()) { //使用while不断判断是否存在下一个 println(iterator.next()) //每次循环都取出一个 } 集合和数组 # 数组转集合 # 数组跟集合的联动，有些时候我们可能拿到的是一个数组对象，但是我们希望将其转换为集合类进行操作，我们可以使用数组提供的集合快速转换函数来进行转换\nval array = arrayOf(\u0026#34;AAA\u0026#34;, \u0026#34;BBB\u0026#34;, \u0026#34;CCC\u0026#34;) val list: List\u0026lt;String\u0026gt; = array.toList() val list: MutableList\u0026lt;String\u0026gt; = array.toMutableList() val set: Set\u0026lt;String\u0026gt; = array.toSet() val set: MutableSet\u0026lt;String\u0026gt; = array.toMutableSet() 集合转数组 # var list = listOf(1,2,3,4,5) var arr = list.toIntArray() 压缩操作 # 它可以将当前集合元素和另一个集合中具有相同索引的元素组合起来，生成一个装满Pair的列表\nval list1 = listOf(1, 2, 3) val list2 = listOf(\u0026#34;AAA\u0026#34;, \u0026#34;BBB\u0026#34;, \u0026#34;CCC\u0026#34;) val pairs: List\u0026lt;Pair\u0026lt;Int, String\u0026gt;\u0026gt; = list1.zip(list2) println(pairs) // [(1, AAA), (2, BBB), (3, CCC)] 利用压缩操作我们可以快速将两个List集合揉成一个Map集合\nval map = mutableMapOf\u0026lt;Int, String\u0026gt;() map.putAll(list1.zip(list2)) println(map) //结果 {1=AAA, 2=BBB, 3=CCC} ","date":"2025-03-24","externalUrl":null,"permalink":"/posts/b3f5e6ce/1e30d624/3ddb5304/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e数组 \n    \u003cdiv id=\"数组\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%95%b0%e7%bb%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e数组是相同类型数据的有序集合，数组可以代表任何相同类型的一组内容，其中存放的每一个数据称为数组的一个元素。\u003c/p\u003e","title":"2、数组和集合","type":"posts"},{"content":" 变量和常量 # 变量 # 可变变量定义：var\nvar \u0026lt;标识符\u0026gt; : \u0026lt;类型\u0026gt; = \u0026lt;初始化值\u0026gt; fun main(args: Array\u0026lt;String\u0026gt;) { var name = \u0026#34;John\u0026#34; println(name) name = \u0026#34;Tom\u0026#34; println(name) } 常量 # 不可变常量定义：val，只能赋值一次的变量(类似Java中final修饰的变量)\nval \u0026lt;标识符\u0026gt; : \u0026lt;类型\u0026gt; = \u0026lt;初始化值\u0026gt; fun main(args: Array\u0026lt;String\u0026gt;) { val name = \u0026#34;John\u0026#34; println(name) } 注意：常量与变量都可以没有初始化值，但是在引用前必须初始化\n编译器支持自动类型判断，即声明时可以不指定类型，由编译器判断。\nfun main(args: Array\u0026lt;String\u0026gt;) { var name: String // 如果不在声明时初始化则必须提供变量类型 var age = 10 // 系统自动推断变量类型为Int name = \u0026#34;Jane\u0026#34; println(name) println(age) } 基本数据类型 # Kotlin 的基本数值类型包括 Byte、Short、Int、Long、Float、Double 等。\n不同于 Java 的是，字符不属于数值类型，是一个独立的数据类型。\n数字类型 # 整数类型 # 类型 大小（比特数） 最小值 最大值 Byte 8 -128 127 Short 16 -32768 32767 Int 32 -2,147,483,648 (-2^31) 2,147,483,647 (2^31 - 1) Long 64 -9,223,372,036,854,775,808 (-2^63) 9,223,372,036,854,775,807 (2^63 - 1) 当初始化一个没有显式指定类型的变量时，编译器会自动推断为自 Int 起足以表示该值的最小类型。 如果不超过 Int 的表示范围，那么类型是 Int。 如果超过了，那么类型是 Long。 如需显式指定 Long 值，请给该值追加后缀 L。 显式指定类型会触发编译器检测该值是否超出指定类型的表示范围。\nval one = 1 // Int val threeBillion = 3000000000 // Long val oneLong = 1L // Long val oneByte: Byte = 1 无符号整数 # Type Size (bits) Min value Max value UByte 8 0 255 UShort 16 0 65,535 UInt 32 0 4,294,967,295 (2^32 - 1) ULong 64 0 18,446,744,073,709,551,615 (2^64 - 1) 为使无符号整型更易于使用，Kotlin 提供了用后缀标记u整型字面值来表示指定无符号类型\nval b: UByte = 1u // UByte，已提供预期类型 val s: UShort = 1u // UShort，已提供预期类型 val l: ULong = 1u // ULong，已提供预期类型 val a1 = 42u // UInt：未提供预期类型，常量适于 UInt val a2 = 0xFFFF_FFFF_FFFFu // ULong：未提供预期类型，常量不适于 UInt val a = 1UL // ULong，即使未提供预期类型并且常量适于 UInt 浮点数类型 # 类型 大小（比特数） 有效数字比特数 指数比特数 十进制位数 Float 32 24 8 6-7 Double 64 53 11 15-16 可以使用带小数部分的数字初始化 Double 与 Float 变量。 小数部分与整数部分之间用句点（.）分隔 对于以小数初始化的变量，编译器会推断为 Double 类型\nval pi = 3.14 // Double // val one: Double = 1 // 错误：类型不匹配 val oneDouble = 1.0 // Double 如需将一个值显式指定为 Float 类型，请添加 f 或 F 后缀。 如果这样的值包含多于 6～7 位十进制数，那么会将其舍入：\nval e = 2.7182818284 // Double val eFloat = 2.7182818284f // Float，实际值为 2.7182817 数字字面常量 # 数值常量字面值有以下几种:\n十进制: 123 Long 类型用大写 L 标记: 123L 十六进制: 0x0F 二进制: 0b00001011 Kotlin 不支持八进制。\n比较数字 # 数值比较\nfun main(args: Array\u0026lt;String\u0026gt;) { var a: Int = 10 var b: Double = 2.5 println(a \u0026lt; b) println(a \u0026lt;= b) println(a \u0026gt; b) println(a \u0026gt;= b) println(a.toDouble() == b) } Kotlin 中 == 运算符用于结构相等性比较，即值的比较，而 === 运算符用于引用相等性比较，即对象是否是同一个实例。在比较基本数据类型时，通常使用 == 运算符。\n默认情况下在 JVM 平台数字存储的是原生类型 int、 double 等。\n但是当创建可空数字引用如 Int? 或者使用泛型时，数字会装箱为 Java 类 Integer、 Double 等。\n由于 JVM 对 -128 到 127 的整数（Integer）应用了内存优化，因此，a 的所有可空引用实际上都是同一对象。但是没有对 b 应用内存优化，所以它们是不同对象。\nfun main(args: Array\u0026lt;String\u0026gt;) { val a: Int = 100 val boxedA: Int? = a val anotherBoxedA: Int? = a val b: Int = 10000 val boxedB: Int? = b val anotherBoxedB: Int? = b println(boxedA === anotherBoxedA) // true println(boxedB === anotherBoxedB) // false } 显式数字类型转换 # 所有数字类型都支持转换为其他类型：\ntoByte(): Byte toShort(): Short toInt(): Int toLong(): Long toFloat(): Float toDouble(): Double 很多情况都不需要显式类型转换，因为类型会从上下文推断出来， 而算术运算会有重载做适当转换，例如：\nval l = 1L + 3 // Long + Int =\u0026gt; Long 数字运算 # Kotlin支持数字运算的标准集：+、 -、 *、 /、 %。它们已定义为相应的类成员：\nfun main() { println(1 + 2) println(2_500_000_000L - 1L) println(3.14 * 2.71) println(10.0 / 3) } 其中除法、取余的逻辑和 Java 一样\n同样，Kotlin 也有和 Java 一样的++以及--操作，放在变量前与变量后的逻辑也是一样的。\n位运算 # Kotlin提供了一组整数的位运算操作，可以直接在二进制层面上与数字表示的位进行操作，不过只适用于Int和Long类型的数据：\nshl(bits)– 有符号左移 shr(bits)– 有符号右移 ushr(bits)– 无符号右移 and(bits)– 按位与 or(bits)– 按位或 xor(bits)– 按位异或 inv()– 取反 字符类型 # Char：16 位的 Unicode 字符。 fun main(args: Array\u0026lt;String\u0026gt;) { var c : Char = \u0026#39;a\u0026#39; println(c) } 特殊字符可以以转义反斜杠 \\ 开始。 支持这几个转义序列：\n\\t——制表符 \\b——退格符 \\n——换行（LF） \\r——回车（CR） \\'——单引号 \\\u0026quot;——双引号 \\\\——反斜杠 \\$——美元符 编码其他字符要用 Unicode 转义序列语法：'\\uFF00' 如果字符变量的值是数字，那么可以使用 digitToInt()函数将其显式转换为 Int 数字。\n字符串类型 # String： 一系列字符的序列。 遍历字符 # 和 Java 一样，String 是不可变的。方括号[]语法可以很方便的获取字符串中的某个字符，也可以通过 for 循环来遍历：\nfun main(args: Array\u0026lt;String\u0026gt;) { var str = \u0026#34;Hello World\u0026#34; // 使用 for 遍历 for (s in str){ println(s) } // 使用方括号取字符 println(str[0]) } 多行字符串 # Kotlin 支持三个引号 \u0026quot;\u0026quot;\u0026quot; 扩起来的字符串\nfun main(args: Array\u0026lt;String\u0026gt;) { var str = \u0026#34;\u0026#34;\u0026#34; 你好 Kotlin \u0026#34;\u0026#34;\u0026#34; println(str) // 输出有一些前置空格 } 可以通过trimIndent()方法来删除多余的空白。\nfun main(args: Array\u0026lt;String\u0026gt;) { var str = \u0026#34;\u0026#34;\u0026#34; 你好 Kotlin \u0026#34;\u0026#34;\u0026#34;.trimIndent() println(str) } 字符串模板 # 字符串可以包含模板表达式 ，即一些小段代码，会求值并把结果合并到字符串中。 模板表达式以美元符$开头\n直接引用变量或常量\nfun main(args: Array\u0026lt;String\u0026gt;) { var age = 10 var msg = \u0026#34;my age is $age\u0026#34; println(msg) } 或者用花括号扩起来的任意表达式\nfun main(args: Array\u0026lt;String\u0026gt;) { var str = \u0026#34;Hello\u0026#34; println(\u0026#34;the str is $str,length is ${str.length}\u0026#34;) } 字符串拼接 # 拼接使用+，字符串除了和字符串拼接之外，也可以和其他类型进行拼接，但是我们需要注意字符串拼接的顺序，只能由字符串拼接其他类型，如果是其他类型拼接字符串，可能会出现问题。\nfun main(args: Array\u0026lt;String\u0026gt;) { var a = \u0026#34;Hello\u0026#34; var b = 123; println(a + b) println(b + a) // 这样是错误的 println(\u0026#34;$b$a\u0026#34;) } 布尔类型 # Boolean：有两个值：true 和 false。 布尔值的内置运算有：\n||——析取（逻辑或） \u0026amp;\u0026amp;——合取（逻辑与） !——否定（逻辑非） fun main(args: Array\u0026lt;String\u0026gt;) { var b = true println(b) } 数组 # 数组类型放在后面另起一章\n变量作用域 # 前面的变量都是声明在函数中的，此时也就是局部变量。可以将变量的作用域进行提升，将其直接变成一个顶级定义。此时，这个变量可以被所有的函数使用。\nvar name = \u0026#34;Tom\u0026#34; fun main() { println(\u0026#34;Hello, $name\u0026#34;) } 变量的作用域被提升到顶层，它可以具有更多的一些特性\ngetter和setter # 我们在使用这种全局变量时，对于变量的获取和设定，本质上都是通过其getter和setter函数来完成的，只不过默认情况下不需要我们去编写\ngetter：用于获取这个变量的值，默认情况下直接返回当前这个变量的值 setter：用于修改这个变量的值，默认情况下直接对这个变量的值进行修改 var \u0026lt;propertyName\u0026gt;[: \u0026lt;PropertyType\u0026gt;] [= \u0026lt;property_initializer\u0026gt;] [\u0026lt;getter\u0026gt;] [\u0026lt;setter\u0026gt;] var name = \u0026#34;Tom\u0026#34; fun main() { println(name) // 编译后，相当于 println(getName()) } 重写getter和setter # 在默认情况下，变量的获取就是直接返回，设置就是直接修改，不过有些时候我们可能希望修改这些变量获取或修改时执行的操作，我们可以手动编写\n注意：重写getter和setter 时候，使用变量field代表当前变量\n注意，对于val类型的变量，没有set函数，因为不可变\nvar name = \u0026#34;Tom\u0026#34; get() = \u0026#34;$field$field\u0026#34; fun main() { println(name) //TomTom } var name = \u0026#34;Tom\u0026#34; get() { println(\u0026#34;get $field\u0026#34;) return field } set(value) { println(\u0026#34;set $value\u0026#34;) field = value } fun main() { name = \u0026#34;Jerry\u0026#34; // set Jerry println(name) // get Jerry // Jerry } // 对于常量，有的时候也会这么写 val name get() = \u0026#34;Tom\u0026#34; // 但是不常用，直接声明就可以了 val name = \u0026#34;Tom\u0026#34; 类型别名 # 如果有一个在代码库中多次用到的函数类型或者带有类型参数的类型，那么最好为它定义一个类型别名，使用typealias定义\ntypealias MouseClickHandler = (Any, MouseEvent) -\u0026gt; Unit typealias PersonIndex = Map\u0026lt;String, Person\u0026gt; 空值和空类型 # 所有的变量除了引用一个具体的值之外，还有一种特殊的值可以使用，那就是null，它代表空值，也就是不引用任何对象。\n在其他语言中，比如Java中null是一个非常常见的值，因为在某些情况下，引用类型的变量默认值就是null，这就经常会导致程序中出现一些空指针导致的异常，在Kotlin中，对空值处理是非常严格的，正常情况下，我们的变量是不能直接赋值为null的，否则会报错，无法编译通过。\n这是因为所有的类型默认都是非空类型，非空类型的变量是不允许被赋值为null的，这直接在编译阶段就避免了其他语言中经常存在的空指针问题。\n那么，如果我们希望某个变量在初始情况下使用null而不去引用某一个具体对象，该怎么做呢，此时我们需要将变量的类型修改为可空类型，只需在类型名称的后面添加一个?即可。\nfun main() { var a: Int = null // 错误，不能赋值null给Int类型变量 var b: Int? = null // 正确，可以赋值null给Int?类型变量 } 不过在有些情况下，我们可能已经非常清楚，这里的str一定不为null，即使它是一个可空类型变量，我们可以像这样做，来告诉编译器，我们这里一定是安全的，只管执行就好\nfun main() { var str: String? = null //使用非空断言操作符!!.来明确不会出现null问题 println(str!!.length) } 虽然通过?我们可以将一个变量初始化为 null，但是也带来了安全问题，如果在引用这个变量的时候仍未 null 呢，这个时候就需要对变量进行判断，看看其是否为null然后才能去做一些正常情况下该做的事情\nfun main() { var str: String? = null //这里直接通过if语句判断str变量是否为null，如果不是才执行 if (str != null) { println(str.length) //现在就可以编译通过了 } } Kotlin为我们提供了一种更安全的空类型操作，要安全地访问可能包含null值的对象的属性，请使用安全调用运算符?.，如果对象的属性为null则安全调用运算符返回null\nfun main() { var str: String? = null println(str?.length) } 这里的调用结果存在两种情况：\n如果str为null，那么这里得到的结果就是null，并且不会正常执行后面的操作 如果str不为null，那就正常返回这里本应该得到的结果 不过在有些时候，可能我们希望如果变量为null，在使用安全调用运算符时，返回一个我们自定义的结果，我们可以使用Elvis运算符，这里我们使用了Elvis运算符来判断左侧是否为null，如果左侧为null，那么这里直接得到右侧的自定义值\nfun main() { var str: String? = null println(str?.length ?: 0) } 类型检测与类型转换 # is 与 !is 操作符 # 使用 is 操作符或其否定形式 !is 在运行时检测对象是否符合给定类型：\nif (obj is String) { print(obj.length) } if (obj !is String) { // 与 !(obj is String) 相同 print(\u0026#34;Not a String\u0026#34;) } else { print(obj.length) } 智能转换 # 大多数场景都不需要在 Kotlin 中使用显式转换操作符，因为编译器跟踪不可变值的is检测以及显式转换，并在必要时自动插入（安全的）转换：\nfun demo(x: Any) { if (x is String) { print(x.length) // x 自动转换为字符串 } } 不安全的转换操作符 # 通常，如果转换是不可能的，转换操作符会抛出一个异常。因此，称为不安全的。 Kotlin 中的不安全转换使用中缀操作符 as。\nval x: String = y as String 请注意，null 不能转换为 String 因该类型不是可空的。 如果 y 为空，上面的代码会抛出一个异常。 为了让这样的代码用于可空值，请在类型转换的右侧使用可空类型：\nval x: String? = y as String? 安全的（可空）转换操作符 # 为了避免异常，可以使用安全转换操作符 as?，它可以在失败时返回 null：\nval x: String? = y as? String 请注意，尽管事实上 as? 的右边是一个非空类型的 String，但是其转换的结果是可空的。\n","date":"2025-03-21","externalUrl":null,"permalink":"/posts/b3f5e6ce/129ca5af/97789457/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e变量和常量 \n    \u003cdiv id=\"变量和常量\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8f%98%e9%87%8f%e5%92%8c%e5%b8%b8%e9%87%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e变量 \n    \u003cdiv id=\"变量\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8f%98%e9%87%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e可变变量定义：\u003ccode\u003evar\u003c/code\u003e\u003c/p\u003e","title":"2、变量和数据类型","type":"posts"},{"content":"uni-app项目中，一个页面就是一个符合Vue SFC规范的 vue 文件。\n在 uni-app js 引擎版中，后缀名是.vue文件或.nvue文件。 这些页面均全平台支持，差异在于当 uni-app 发行到App平台时，.vue文件会使用webview进行渲染，.nvue会使用原生进行渲染。一个页面可以同时存在vue和nvue，在pages.json的路由注册中不包含页面文件名后缀，同一个页面可以对应2个文件名。重名时优先级如下： 在非app平台，先使用vue，忽略nvue 在app平台，使用nvue，忽略vue 在 uni-app x 中，后缀名是.uvue文件 uni-app x 中没有js引擎和webview，不支持和vue页面并存。 uni-app x 在app-android上，每个页面都是一个全屏activity，不支持透明。 页面管理 # 新建页面 # uni-app中的页面，默认保存在工程根目录下的pages目录下。\n每次新建页面，均需在pages.json中配置pages列表；未在pages.json -\u0026gt; pages 中注册的页面，uni-app会在编译阶段进行忽略。\n通过HBuilderX开发 uni-app 项目时，在 uni-app 项目上右键“新建页面”，HBuilderX会自动在pages.json中完成页面注册，开发更方便。\n新建页面时，可以选择是否创建同名目录。创建目录的意义在于：\n如果你的页面较复杂，需要拆分多个附属的js、css、组件等文件，则使用目录归纳比较合适。 如果只有一个页面文件，大可不必多放一层目录。 删除页面 # 删除页面时，需做两件工作：\n删除.vue文件、.nvue、.uvue文件 删除pages.json -\u0026gt; pages列表项中的配置 （如使用HBuilderX删除页面，会在状态栏提醒删除pages.json对应内容，点击后会打开pages.json并定位到相关配置项） 页面改名 # 操作和删除页面同理，依次修改文件和 pages.json。\npages.json # pages.json 文件用来对 uni-app 进行全局配置，决定页面文件的路径、窗口样式、原生的导航栏、底部的原生tabbar 等。\n导航栏高度为 44px (不含状态栏)，tabBar 高度为 50px (不含安全区)。\n它类似微信小程序中app.json的页面管理部分。注意定位权限申请等原属于app.json的内容，在uni-app中是在manifest中配置。\n{ \u0026#34;pages\u0026#34;: [{ \u0026#34;path\u0026#34;: \u0026#34;pages/component/index\u0026#34;, \u0026#34;style\u0026#34;: { \u0026#34;navigationBarTitleText\u0026#34;: \u0026#34;组件\u0026#34; } }, { \u0026#34;path\u0026#34;: \u0026#34;pages/API/index\u0026#34;, \u0026#34;style\u0026#34;: { \u0026#34;navigationBarTitleText\u0026#34;: \u0026#34;接口\u0026#34; } }, { \u0026#34;path\u0026#34;: \u0026#34;pages/component/view/index\u0026#34;, \u0026#34;style\u0026#34;: { \u0026#34;navigationBarTitleText\u0026#34;: \u0026#34;view\u0026#34; } }], \u0026#34;condition\u0026#34;: { //模式配置，仅开发期间生效 \u0026#34;current\u0026#34;: 0, //当前激活的模式（list 的索引项） \u0026#34;list\u0026#34;: [{ \u0026#34;name\u0026#34;: \u0026#34;test\u0026#34;, //模式名称 \u0026#34;path\u0026#34;: \u0026#34;pages/component/view/index\u0026#34; //启动页面，必选 }] }, \u0026#34;globalStyle\u0026#34;: { \u0026#34;navigationBarTextStyle\u0026#34;: \u0026#34;black\u0026#34;, \u0026#34;navigationBarTitleText\u0026#34;: \u0026#34;演示\u0026#34;, \u0026#34;navigationBarBackgroundColor\u0026#34;: \u0026#34;#F8F8F8\u0026#34;, \u0026#34;backgroundColor\u0026#34;: \u0026#34;#F8F8F8\u0026#34;, \u0026#34;usingComponents\u0026#34;:{ \u0026#34;collapse-tree-item\u0026#34;:\u0026#34;/components/collapse-tree-item\u0026#34; }, \u0026#34;renderingMode\u0026#34;: \u0026#34;seperated\u0026#34;, // 仅微信小程序，webrtc 无法正常时尝试强制关闭同层渲染 \u0026#34;pageOrientation\u0026#34;: \u0026#34;portrait\u0026#34;, //横屏配置，全局屏幕旋转设置(仅 APP/微信/QQ小程序)，支持 auto / portrait / landscape \u0026#34;rpxCalcMaxDeviceWidth\u0026#34;: 960, \u0026#34;rpxCalcBaseDeviceWidth\u0026#34;: 375, \u0026#34;rpxCalcIncludeWidth\u0026#34;: 750 }, \u0026#34;tabBar\u0026#34;: { \u0026#34;color\u0026#34;: \u0026#34;#7A7E83\u0026#34;, \u0026#34;selectedColor\u0026#34;: \u0026#34;#3cc51f\u0026#34;, \u0026#34;borderStyle\u0026#34;: \u0026#34;black\u0026#34;, \u0026#34;backgroundColor\u0026#34;: \u0026#34;#ffffff\u0026#34;, \u0026#34;height\u0026#34;: \u0026#34;50px\u0026#34;, \u0026#34;fontSize\u0026#34;: \u0026#34;10px\u0026#34;, \u0026#34;iconWidth\u0026#34;: \u0026#34;24px\u0026#34;, \u0026#34;spacing\u0026#34;: \u0026#34;3px\u0026#34;, \u0026#34;iconfontSrc\u0026#34;:\u0026#34;static/iconfont.ttf\u0026#34;, // app tabbar 字体.ttf文件路径 app 3.4.4+ \u0026#34;list\u0026#34;: [{ \u0026#34;pagePath\u0026#34;: \u0026#34;pages/component/index\u0026#34;, \u0026#34;iconPath\u0026#34;: \u0026#34;static/image/icon_component.png\u0026#34;, \u0026#34;selectedIconPath\u0026#34;: \u0026#34;static/image/icon_component_HL.png\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;组件\u0026#34;, \u0026#34;iconfont\u0026#34;: { // 优先级高于 iconPath，该属性依赖 tabbar 根节点的 iconfontSrc \u0026#34;text\u0026#34;: \u0026#34;\\ue102\u0026#34;, \u0026#34;selectedText\u0026#34;: \u0026#34;\\ue103\u0026#34;, \u0026#34;fontSize\u0026#34;: \u0026#34;17px\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#000000\u0026#34;, \u0026#34;selectedColor\u0026#34;: \u0026#34;#0000ff\u0026#34; } }, { \u0026#34;pagePath\u0026#34;: \u0026#34;pages/API/index\u0026#34;, \u0026#34;iconPath\u0026#34;: \u0026#34;static/image/icon_API.png\u0026#34;, \u0026#34;selectedIconPath\u0026#34;: \u0026#34;static/image/icon_API_HL.png\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;接口\u0026#34; }], \u0026#34;midButton\u0026#34;: { \u0026#34;width\u0026#34;: \u0026#34;80px\u0026#34;, \u0026#34;height\u0026#34;: \u0026#34;50px\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;文字\u0026#34;, \u0026#34;iconPath\u0026#34;: \u0026#34;static/image/midButton_iconPath.png\u0026#34;, \u0026#34;iconWidth\u0026#34;: \u0026#34;24px\u0026#34;, \u0026#34;backgroundImage\u0026#34;: \u0026#34;static/image/midButton_backgroundImage.png\u0026#34; } }, \u0026#34;easycom\u0026#34;: { \u0026#34;autoscan\u0026#34;: true, //是否自动扫描组件 \u0026#34;custom\u0026#34;: {//自定义扫描规则 \u0026#34;^uni-(.*)\u0026#34;: \u0026#34;@/components/uni-$1.vue\u0026#34; } }, \u0026#34;topWindow\u0026#34;: { \u0026#34;path\u0026#34;: \u0026#34;responsive/top-window.vue\u0026#34;, \u0026#34;style\u0026#34;: { \u0026#34;height\u0026#34;: \u0026#34;44px\u0026#34; } }, \u0026#34;leftWindow\u0026#34;: { \u0026#34;path\u0026#34;: \u0026#34;responsive/left-window.vue\u0026#34;, \u0026#34;style\u0026#34;: { \u0026#34;width\u0026#34;: \u0026#34;300px\u0026#34; } }, \u0026#34;rightWindow\u0026#34;: { \u0026#34;path\u0026#34;: \u0026#34;responsive/right-window.vue\u0026#34;, \u0026#34;style\u0026#34;: { \u0026#34;width\u0026#34;: \u0026#34;300px\u0026#34; }, \u0026#34;matchMedia\u0026#34;: { \u0026#34;minWidth\u0026#34;: 768 } } } easycom # HBuilderX 2.5.5起支持easycom组件模式。\n传统vue组件，需要安装、引用、注册，三个步骤后才能使用组件。easycom将其精简为一步。\n只要组件路径符合规范（具体见下），就可以不用引用、注册，直接在页面中使用。如下：\n\u0026lt;template\u0026gt; \u0026lt;view class=\u0026#34;container\u0026#34;\u0026gt; \u0026lt;comp-a\u0026gt;\u0026lt;/comp-a\u0026gt; \u0026lt;uni-list\u0026gt; \u0026lt;/uni-list\u0026gt; \u0026lt;/view\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; // 这里不用import引入，也不需要在components内注册uni-list组件。template里就可以直接用 export default { data() { return {} } } \u0026lt;/script\u0026gt; 路径规范指：\n安装在项目根目录的components目录下，并符合components/组件名称/组件名称.vue 安装在uni_modules下，路径为uni_modules/插件ID/components/组件名称/组件名称.vue ┌─components │ └─comp-a │ └─comp-a.vue 符合easycom规范的组件 └─uni_modules [uni_module](/plugin/uni_modules.md)中符合easycom规范的组件 └─uni_modules └─uni-list └─components └─uni-list └─ uni-list.vue 不管components目录下安装了多少组件，easycom打包会自动剔除没有使用的组件。\neasycom是自动开启的，不需要手动开启，有需求时可以在pages.json的easycom节点进行个性化设置，如关闭自动扫描，或自定义扫描匹配组件的策略。设置参数如下：\n属性 类型 默认值 描述 autoscan Boolean true 是否开启自动扫描，开启后将会自动扫描符合components/组件名称/组件名称.vue目录结构的组件 custom Object - 以正则方式自定义组件匹配规则。如果autoscan不能满足需求，可以使用custom自定义匹配规则 如果你的组件，不符合easycom前述的路径规范。可以在pages.json的easycom节点中自行定义路径规范。\n如果需要匹配node_modules内的vue文件，需要使用packageName/path/to/vue-file-$1.vue形式的匹配规则，其中packageName为安装的包名，/path/to/vue-file-$1.vue为vue文件在包内的路径。\n\u0026#34;easycom\u0026#34;: { \u0026#34;autoscan\u0026#34;: true, \u0026#34;custom\u0026#34;: { \u0026#34;^uni-(.*)\u0026#34;: \u0026#34;@/components/uni-$1.vue\u0026#34;, // 匹配components目录内的vue文件 \u0026#34;^vue-file-(.*)\u0026#34;: \u0026#34;packageName/path/to/vue-file-$1.vue\u0026#34; // 匹配node_modules内的vue文件 } } easycom方式引入的组件无需在页面内import，也不需要在components内声明，即可在任意页面使用。 easycom方式引入组件不是全局引入，而是局部引入。例如在H5端只有加载相应页面才会加载使用的组件。 在组件名完全一致的情况下，easycom引入的优先级低于手动引入（区分连字符形式与驼峰形式）。 考虑到编译速度，直接在pages.json内修改easycom不会触发重新编译，需要改动页面内容触发。 easycom只处理vue组件，不处理小程序专用组件（如微信的wxml格式组件）。不处理后缀为.nvue的组件。因为nvue页面引入的组件也是.vue组件。可以参考uni ui，使用vue后缀，同时兼容nvue页面。 nvue页面里引用.vue后缀的组件，会按照nvue方式使用原生渲染，其中不支持的css会被忽略掉。这种情况同样支持easycom。 页面内容构成 # uni-app 页面基于 vue 规范。一个页面内，有3个根节点标签：\n模板组件区 \u0026lt;template\u0026gt; 脚本区 \u0026lt;script\u0026gt; 样式区 \u0026lt;style\u0026gt; \u0026lt;template\u0026gt; \u0026lt;view class=\u0026#34;content\u0026#34;\u0026gt; \u0026lt;button @click=\u0026#34;buttonClick\u0026#34;\u0026gt;{{title}}\u0026lt;/button\u0026gt; \u0026lt;/view\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { data() { return { title: \u0026#34;Hello world\u0026#34;, // 定义绑定在页面上的data数据 } }, onLoad() { // 页面启动的生命周期，这里编写页面加载时的逻辑 }, methods: { buttonClick: function () { console.log(\u0026#34;按钮被点了\u0026#34;) }, } } \u0026lt;/script\u0026gt; \u0026lt;style\u0026gt; .content { width: 750rpx; background-color: white; } \u0026lt;/style\u0026gt; template模板区 # template中文名为模板，它类似html的标签。但有2个区别：\nhtml中 script 和 style 是 html 的二级节点。但在 vue 文件中，template、script、style 这3个是平级关系。 html 中写的是 web 标签，但 vue 的 template 中写的全都是 vue 组件，每个组件支持属性、事件、 vue 指令，还可以绑定 vue 的 data 数据。 在vue2中，template 的二级节点只能有一个节点，一般是在一个根 view 下继续写页面组件（如上示例代码）。\n但在vue3中，template可以有多个二级节点，省去一个层级。\n可以在 manifest 中切换使用 Vue2 还是 Vue3。\nscript 脚本区 # script中编写脚本，可以通过lang属性指定脚本语言。\n在vue和nvue中，默认是js，可以指定ts。 在uvue中，仅支持uts，不管script的lang属性写成什么，都按uts编译。 页面生命周期 # uni-app 页面除支持 Vue 组件生命周期外还支持下方页面生命周期函数。\n函数名 说明 平台差异说明 最低版本 onInit 监听页面初始化，其参数同 onLoad 参数，为上个页面传递的数据，参数类型为 Object（用于页面传参），触发时机早于 onLoad 百度小程序 3.1.0+ onLoad 监听页面加载，该钩子被调用时，响应式数据、计算属性、方法、侦听器、props、slots 已设置完成，其参数为上个页面传递的数据，参数类型为 Object（用于页面传参）。 onShow 监听页面显示，页面每次出现在屏幕上都触发，包括从下级页面点返回露出当前页面 onReady 监听页面初次渲染完成，此时组件已挂载完成，DOM 树($el)已可用，注意如果渲染速度快，会在页面进入动画完成前触发 onHide 监听页面隐藏 onUnload 监听页面卸载 onResize 监听窗口尺寸变化 App、微信小程序、快手小程序 onPullDownRefresh 监听用户下拉动作，一般用于下拉刷新 onReachBottom 页面滚动到底部的事件（不是scroll-view滚到底），常用于下拉下一页数据。具体见下方注意事项 onTabItemTap 点击 tab 时触发，参数为Object，具体见下方注意事项 微信小程序、QQ小程序、支付宝小程序、百度小程序、H5、App、快手小程序、京东小程序 onShareAppMessage 用户点击右上角分享 微信小程序、QQ小程序、支付宝小程序、抖音小程序、飞书小程序、快手小程序、京东小程序 onPageScroll 监听页面滚动，参数为Object nvue不支持 onNavigationBarButtonTap 监听原生标题栏按钮点击事件，参数为Object App、H5 onBackPress 监听页面返回，返回 event = {from:backbutton、 navigateBack} ，backbutton 表示来源是左上角返回按钮或 android 返回键；navigateBack表示来源是 uni.navigateBack； app、H5、支付宝小程序 onNavigationBarSearchInputChanged 监听原生标题栏搜索输入框输入内容变化事件 App、H5 1.6.0 onNavigationBarSearchInputConfirmed 监听原生标题栏搜索输入框搜索事件，用户点击软键盘上的“搜索”按钮时触发。 App、H5 1.6.0 onNavigationBarSearchInputClicked 监听原生标题栏搜索输入框点击事件（pages.json 中的 searchInput 配置 disabled 为 true 时才会触发） App、H5 1.6.0 onShareTimeline 监听用户点击右上角转发到朋友圈 微信小程序 2.8.1+ onAddToFavorites 监听用户点击右上角收藏 微信小程序、QQ小程序 2.8.1+ 页面调用接口 # getApp() # getApp() 函数用于获取当前应用实例，一般用于获取globalData。也可通过应用实例调用 App.vue methods 中定义的方法。\nconst app = getApp() console.log(app.globalData) app.doSomething() // 调用 App.vue methods 中的 doSomething 方法 getCurrentPages() # getCurrentPages() 函数用于获取当前页面栈的实例，以数组形式按栈的顺序给出，数组中的元素为页面实例，第一个元素为首页，最后一个元素为当前页面。\ngetCurrentPages() 仅用于展示页面栈的情况，请勿修改页面栈，以免造成页面状态错误。页面关闭时，对应页面实例会在页面栈中删除。\n页面通讯 # uni.$emit(eventName,OBJECT) # 触发全局的自定义事件。附加参数都会传给监听器回调。\nuni.$emit(\u0026#39;update\u0026#39;,{msg:\u0026#39;页面更新\u0026#39;}) 属性 类型 描述 eventName String 事件名 OBJECT Object 触发事件携带的附加参数 uni.$on(eventName,callback) # 监听全局的自定义事件。事件可以由 uni.$emit 触发，回调函数会接收所有传入事件触发函数的额外参数。\nuni.$on(\u0026#39;update\u0026#39;,function(data){ console.log(\u0026#39;监听到事件来自 update ，携带参数 msg 为：\u0026#39; + data.msg); }) 属性 类型 描述 eventName String 事件名 callback Function 事件的回调函数 uni.$once(eventName,callback) # 监听全局的自定义事件。事件可以由 uni.$emit 触发，但是只触发一次，在第一次触发之后移除监听器。\nuni.$once(\u0026#39;update\u0026#39;,function(data){ console.log(\u0026#39;监听到事件来自 update ，携带参数 msg 为：\u0026#39; + data.msg); }) 属性 类型 描述 eventName String 事件名 callback Function 事件的回调函数 uni.$off(eventName, callback) # 移除全局自定义事件监听器。\n属性 类型 描述 eventName String 事件名 callback Function 事件的回调函数 路由 # uni-app页面路由为框架统一管理，开发者需要在pages.json里配置每个路由页面的路径及页面样式。类似小程序在 app.json 中配置页面路由一样。所以 uni-app 的路由用法与 Vue Router 不同，如仍希望采用 Vue Router 方式管理路由，可在插件市场搜索Vue-Router。\n路由跳转 # uni-app 有两种页面路由跳转方式：使用navigator组件跳转、调用API跳转。\n页面返回时会自动关闭 loading 及 toast, modal 及 actionSheet 不会自动关闭。\n页面关闭时，只是销毁了页面实例，未完成的网络请求、计时器等副作用需开发者自行处理。\n页面栈 # 框架以栈的形式管理当前所有页面， 当发生路由切换的时候，页面栈的表现如下：\n路由方式 页面栈表现 触发时机 初始化 新页面入栈 uni-app 打开的第一个页面 打开新页面 新页面入栈 调用 API uni.navigateTo 、使用组件 页面重定向 当前页面出栈，新页面入栈 调用 API uni.redirectTo 、使用组件 页面返回 页面不断出栈，直到目标返回页 调用 API uni.navigateBack 、使用组件 、用户按左上角返回按钮、安卓用户点击物理back按键 Tab 切换 页面全部出栈，只留下新的 Tab 页面 调用 API uni.switchTab 、使用组件 、用户切换 Tab 重加载 页面全部出栈，只留下新的页面 调用 API uni.reLaunch 、使用组件 nvue 开发与 vue 开发的常见区别 # 基于原生引擎的渲染，虽然还是前端技术栈，但和 web 开发肯定是有区别的。\nnvue 页面控制显隐只可以使用v-if不可以使用v-show nvue 页面只能使用flex布局，不支持其他布局方式。页面开发前，首先想清楚这个页面的纵向内容有什么，哪些是要滚动的，然后每个纵向内容的横轴排布有什么，按 flex 布局设计好界面。 nvue 页面的布局排列方向默认为竖排（column），如需改变布局方向，可以在 manifest.json -\u0026gt; app-plus -\u0026gt; nvue -\u0026gt; flex-direction 节点下修改，仅在 uni-app 模式下生效。 nvue 页面编译为 H5、小程序时，会做一件 css 默认值对齐的工作。因为 weex 渲染引擎只支持 flex，并且默认 flex 方向是垂直。而 H5 和小程序端，使用 web 渲染，默认不是 flex，并且设置display:flex后，它的 flex 方向默认是水平而不是垂直的。所以 nvue 编译为 H5、小程序时，会自动把页面默认布局设为 flex、方向为垂直。当然开发者手动设置后会覆盖默认设置。 文字内容，必须、只能在\u0026lt;text\u0026gt;组件下。不能在\u0026lt;div\u0026gt;、\u0026lt;view\u0026gt;的text区域里直接写文字。否则即使渲染了，也无法绑定 js 里的变量。 只有text标签可以设置字体大小，字体颜色。 布局不能使用百分比、没有媒体查询。 nvue 切换横竖屏时可能导致样式出现问题，建议有 nvue 的页面锁定手机方向。 支持的 css 有限，不过并不影响布局出你需要的界面，flex还是非常强大的。 不支持背景图。但可以使用image组件和层级来实现类似 web 中的背景效果。因为原生开发本身也没有 web 这种背景图概念 css 选择器支持的比较少，只能使用 class 选择器。 nvue 的各组件在安卓端默认是透明的，如果不设置background-color，可能会导致出现重影的问题。 class 进行绑定时只支持数组语法。 Android 端在一个页面内使用大量圆角边框会造成性能问题，尤其是多个角的样式还不一样的话更耗费性能。应避免这类使用。 nvue 页面没有bounce回弹效果，只有几个列表组件有bounce效果，包括 list、recycle-list、waterfall。 原生开发没有页面滚动的概念，页面内容高过屏幕高度并不会自动滚动，只有部分组件可滚动（list、waterfall、scroll-view/scroller），要滚得内容需要套在可滚动组件下。这不符合前端开发的习惯，所以在 nvue 编译为 uni-app 模式时，给页面外层自动套了一个 scroller，页面内容过高会自动滚动。（组件不会套，页面有recycle-list时也不会套）。后续会提供配置，可以设置不自动套。 在 App.vue 中定义的全局 js 变量不会在 nvue 页面生效。globalData和vuex是生效的。 App.vue 中定义的全局 css，对 nvue 和 vue 页面同时生效。如果全局 css 中有些 css 在 nvue 下不支持，编译时控制台会报警，建议把这些不支持的 css 包裹在条件编译里，APP-PLUS-NVUE 不能在 style 中引入字体文件。如果是本地字体，可以用plus.io的 API 转换路径。 目前不支持在 nvue 页面使用 typescript/ts。 nvue 页面关闭原生导航栏时，想要模拟状态栏。但是，仍然强烈建议在 nvue 页面使用原生导航栏。nvue 的渲染速度再快，也没有原生导航栏快。原生排版引擎解析json绘制原生导航栏耗时很少，而解析 nvue 的 js 绘制整个页面的耗时要大的多，尤其在新页面进入动画期间，对于复杂页面，没有原生导航栏会在动画期间产生整个屏幕的白屏或闪屏。 ","date":"2025-03-17","externalUrl":null,"permalink":"/posts/bafd68f1/f2b2596e/fca38bdf/","section":"文章","summary":"\u003cp\u003euni-app项目中，一个页面就是一个符合\u003ccode\u003eVue SFC规范\u003c/code\u003e的 vue 文件。\u003c/p\u003e","title":"2、页面","type":"posts"},{"content":" 继承树 # Collection接口 # Map接口 # Collection 接口 # Collection接口（继承于java.lang.Iterable）：单列集合，用来存储一个一个的对象 List接口：extends Collection，存储有序的、可重复的数据。“动态”数组\nArrayList、LinkedList、Vector Set接口：extends Collection，存储无序的、不可重复的数据。高中讲的“集合”\nHashSet、LinkedHashSet、TreeSet Collection 接口方法 # 增加：add(Object obj)，addAll(Collection coll) 获取有效元素个数：int size() 清空集合：void clear() 是否为空集合：boolean isEmpty() 是否包含指定元素：boolean contains(Object obj)，boolean containsAll(Collection c) 删除：boolean remove(Object obj)，boolean removeAll(Collection c) 取两个集合的交集（只保留集合c中有的元素）：boolean retainAll(Collection c) 集合是否相等：boolean equals(Object obj) 转数组：Object[] toArray() 获取迭代器：iterator() 遍历Collection的两种方式 # 使用迭代器Iterator\nforeach循环（或增强for循环）：内部仍然调用的是迭代器\n迭代器Iterator接口 # Iterable：接口，规定实现类或子接口必须要有提供迭代器的能力\nIterator：迭代器的类型，迭代器使用Iterable接口中iterator方法返回的，通过Iterator\u0026lt;T\u0026gt; iterator();方法获取\njava.util包下定义的迭代器接口：Iterator\nIterator对象称为迭代器(设计模式的一种)，主要用于遍历Collection集合中的元素。\nGOF给迭代器模式的定义为：提供一种方法访问一个容器(container)对象中各个元素，而又不需暴露该对象的内部细节。迭代器模式，就是为容器而生。\n不支持删除行为：Iterator迭代时，删除当前遍历到的元素可以使用迭代器对象.remove()，但是，切记！！！迭代器在迭代的时候不支持集合本身的修改行为add|remove，否则，会引发java.util.ConcurrentModificationException并发修改异常。\n遍历代码实现 # Iterator iterator = coll.iterator(); //hasNext():判断是否还下一个元素 while(iterator.hasNext()){ //next():指针下移并将下移以后指向的元素返回 System.out.println(iterator.next()); //remove():删除指针当前指向的元素 iterator.remove(); } 符合迭代器的设计模式 # 抽象集合接口（Iterable（抽象接口）） 集合的具体实现类实现Iterable，必须拥有给外界提供迭代器（Iterator）的能力 抽象迭代器接口（Iterator） 具体的迭代器实现类（实现Iterator，体现为不同集合中的内部类） List接口 # 存储的数据特点：存储有序的、可重复的数据。\nList接口常用方法 # 增：add(Object obj) 删：remove(int index) / remove(Object obj) 改：set(int index, Object ele) 查：get(int index) 插：add(int index, Object ele) 长度：size() 遍历： Iterator迭代器方式 增强for循环 普通的循环 实现类1：ArrayList # 底层：底层是一个Object类型的数组，初始的默认长度0（jdk1.6之前是10），在第一次add时，容量变为10，也可以指定长度，初始长度如果满了，底层进行自动扩容，扩容为原来的1.5倍oldCapacity + (oldCapacity \u0026gt;\u0026gt; 1)。如果对集合中的元素个数可以预估，那么建议预先指定一个合适的初始容量new ArrayList(20);\n优点：查找效率高，向末尾添加元素也可以\n缺点：增加 、删除牵扯到数组的扩容和移动，效率低；线程不安全\n实现类2：LinkedList # 底层：是一个链表（双向）结构，不是线性存储。\n优点：增加、删除效率高\n缺点：查找效率低；线程不安全\n实现类3：Vector # 底层：是一个Object类型的数组，初始的默认长度为10，扩容的时候扩容为原来的2倍，如果自己指定扩容的长度，那么就是旧容量加指定的，如果没有指定，就是旧容量的2倍。\n优点：线程安全，通过synchronized同步锁实现\n缺点：效率低\n// 初始容量1，每次扩容增加2 Vector v = new Vector(1,2); 存储的元素的要求 # 添加的对象，所在的类要重写equals()方法\n三个实现类的异同 # Set接口 # 存储的数据特点：无序的、不可重复的元素（如果hashCode返回值一样和equals为true，则认为是重复元素）\n具体说明 # 以HashSet为例说明：\n无序性：不等于随机性。存储的数据在底层数组中并非照数组索引的顺序添加，而是根据数据的哈希值决定的。\n不可重复性：保证添加的元素照equals()判断时，不能返回true，hashCode不可以一样，即：相同的元素只能添加一个。\nSet接口中没额外定义新的方法，使用的都是Collection中声明过的方法。\n实现类1：HashSet # 底层：HashSet的底层是一个HashMap，只是将HashMap中的值设置为一个常量，用所有的键组成了一个HashSet\n优点：可以存储null值\n缺点：线程不安全\n实现类2：LinkedHashSet # LinkedHashSet 是 HashSet 的子类\n底层：是一个LinkedHashMap，底层维护了一个数组 + 双向链表\n遍历其内部数据时，可以按照添加的顺序遍历（存储了插入顺序） 在添加数据的同时，每个数据还维护了两个引用，记录此数据前一个数据和后一个数据。（双向链表） 对于频繁的遍历操作，LinkedHashSet效率高于HashSet，但是由于维护链表，所以插入效率不如HashSet 可以存放null值 存储对象所在类的要求：\n向Set(主要指：HashSet、LinkedHashSet)中添加的数据，其所在的类一定要重写hashCode()和equals()\n实现类3：TreeSet # TreeSet 是 SortedSet（继承于Set）接口的实现类，TreeSet 可以确保集合元素处于排序状态。\n不可以放null对象\n存储对象所在类的要求：\n向TreeSet中添加的数据，要求是相同类的对象而且对象必须是可比较的。\n两种排序方式：自然排序（实现Comparable接口） 和 定制排序（Comparator，创建TreeSet的时候可以作为构造方法参数传入！）\nTreeSet\u0026lt;Integer\u0026gt; s2 = new TreeSet\u0026lt;Integer\u0026gt;(new Comparator\u0026lt;Integer\u0026gt;() { @Override public int compare(Integer o1, Integer o2) { // 升序排序 if (o1 \u0026gt; o2){ return 1; }else { return -1; } } }); Map接口 # Map：存储双列数据，存储key-value对的数据，类似于高中的函数：y = f(x) HashMap LinkedHashMap TreeMap Hashtable Properties:常用来处理配置文件。key和value都是String类型 Map接口常用方法 # 新增：Object put(Object key,Object value)，void putAll(Map m) 删除：Object remove(Object key) 清空：void clear() 获取value：Object get(Object key) 是否包含key：boolean containsKey(Object key) 是否包含value：boolean containsValue(Object value) 键值对个数：int size() 是否为空map：boolean isEmpty() 获取所有key：Set keySet() 获取所有value：Collection values() 获取所有键值对：Set entrySet() 存储结构的理解 # Map中的key : 无序的、不可重复的，使用Set存储所有的key，key所在的类要重写equals()和hashCode()（以HashMap为例) Map中的value : 无序的、可重复的，使用Collection存储所的value，value所在的类要重写equals()，一个键值对：key-value构成了一个Entry对象。 Map中的entry : 无序的、不可重复的，使用Set存储所的entry 实现类1：HashMap # 底层：所有的键构成了一个HashSet，整体是数组+链表/红黑树\n优点：效率高，可以存储null值、null键\n缺点：线程不安全\n判断两个 key 相等的标准是：两个 key 通过 equals() 方法返回 true， hashCode 值也相等。 判断两个 value相等的标准是：两个 value 通过 equals() 方法返回 true。 初始长度16，每次扩容为原来的2倍，扩容因子（即当前元素个数 / 当前容量 \u0026gt; 扩容因子时就会进行扩容）0.75\nHashMap的put过程 # 根据键的hash码（调用键的hashCode方法）进行哈希运算，得到一个整数哈希值 判断哈希表是否为空或者长度是否为0，如果是，要对数组进行初始化，如果否，进入3 根据1得到的哈希值计算数组索引（与运算），得到一个和数组存储位置匹配的索引i 判断i号位置是否为null，如果null，就将键和值封装为一个Entry类型的对象进行插入,如果不为null，进入5 判断key是否存在（使用equals进行判断），如果存在，覆盖原有的值，如果不存在，进入6 判断i号位置是否为一个树结构，如果是一个树结构，在树中进行插入，如果不是树结构，进入7 为链表结构，对链表进行遍历，判断key是否存在，存在就覆盖，不存在就在链表中插入新的节点 链表中插入新节点后，如果i号位置的元素个数大于等于8，i号位置的所有元素转换为树结构，如果不大于等于8，新节点正常插入结束 size++ 判断是否要进行扩容，如果需要扩容，就执行Resize()进行扩容 结束 实现类2：LinkedHashMap # 底层**：HashMap的子类**；在原的HashMap底层结构基础上，添加了一对指针，指向前一个和后一个元素。\n优点：保证在遍历map元素时，可以照添加的顺序实现遍历。对于频繁的遍历操作，此类执行效率高HashMap。可以存储null值、null键\n缺点：线程不安全\n实现类3：TreeMap # 底层**：红黑树**\n优点：保证照添加的key-value对进行排序，实现按照键排序遍历，两种排序方式：自然排序（实现Comparable接口） 和 定制排序（Comparator）\n缺点**：键不可以为null，值可以为null**\n实现类4：Hashtable # 底层**：数组+链表/红黑树**\n优点：线程安全（使用synchronized实现）\n缺点：效率低；不能存储null的key和value\n不可以放入null值、null键\n初始长度11，每次扩容为原来的2倍加1，扩容因子（即当前元素个数 / 当前容量 \u0026gt; 扩容因子时就会进行扩容）0.75\n实现类5：Properties # 底层**：Hashtable的子类，数组+链表/红黑树**\n优点：一般用来存储配置文件\n缺点：key和value都是String类型\n面试题 # 为什么HashMap的长度为什么要设计成2的n次方？\n为了方便将去余运算转换为位运算 为什么设计扩容因子\n为了减少一个桶里元素碰撞的概率，本质就是不要让一个桶中的元素个数太多 根据key怎么找到值的get(key)？\n根据key的哈希码先找到桶的位置，然后再在一个桶中用equals方法进行比对，找到对应的元素，获取其值。 为什么使用hash码相关的集合的时候，重写equals方法的时候建议也重写hashCode方法\n如果equals返回true.但是哈希码不一样，有可能会放到不同的桶中，不同的桶中就存在了键重复的元素了，有漏洞，最终目的是为了让equals返回true的两个对象能放到一个桶中，保证键不重复 HashMap和Hashtable的区别：\n继承的类不一样，HashMap继承自AbstractMap，Hashtable继承自Dictionary类 根据hash码计算存储位置的过程和算法是不同的（hashMap最后进行位运算，hashtable最后进行取余的运算）。 Hashtable不能放入null键、null值，但是hastMap可以放入null键、null值 Hashtable初始的默认长度是11，HashMap是16. Hashtable线程安全，效率低，HashMap线程不安全，效率高 扩容方式不同，HashMap扩容为原来的2倍，Hashtable扩容为原来的二倍加1（int newCapacity = (oldCapacity \u0026lt;\u0026lt; 1) + 1） Hashtable支持Enumeration遍历 ，HashMap不支持 HashMap在jdk8中相较于jdk7在底层实现方面的不同：\njdk7：底层初始创建一个长度为16的数组，jdk8：初始化没有指定HashMap数组大小，而是在添加第一个元素时，进行扩容操作\njdk8底层的数组是：Node[]，而非Entry[]（jdk7）\n首次调用put()方法时，底层创建长度为16的数组\njdk7底层结构只：数组+链表。jdk8中底层结构：数组+链表+红黑树。\n形成链表时，七上八下（jdk7：新的元素指向旧的元素。jdk8：旧的元素指向新的元素）\n当数组的某一个索引位置上的元素以链表形式存在的数据个数 \u0026gt; 8 且当前数组的长度 \u0026gt; 64时，此时此索引位置上的所有数据改为使用红黑树存储。\nCollections工具类 # 作用：操作Collection和Map的工具类\nCollections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作， 还提供了对集合对象设置不可变、对集合对象实现同步控制等方法\nCollections常用方法 # 集合的线程安全 # List的线程安全 # 如果使用ArrayList多个线程同时添加和读取，可能会出现并发修改异常ConcurrentModificationException\nVector # 使用synchronized关键字，对修改、添加、删除等方法进行修饰，实际开发中，我们很少使用，强同步性能低\nCollections # 可以使用Collections中的静态方法synchronizedXXX将XXX类型的集合转为线程安全版本\nCopyOnWriteArrayList（推荐） # List list = new CopyOnWriteArrayList(); 使用写时复制技术，也就是读的时候可以并发进行读，但是写，只允许一个线程写，复制一份和当前集合相同的集合，然后在写完以后，进行合并\n使用Lock可重入锁进行实现\nSet的线程安全 # Collections # 和List的使用方法一样\nCopyOnWriteArraySet # 和List的使用方法一样\nMap的线程安全 # HashTable # 使用synchronizd强同步，效率低，不推荐\nCollections # 和List的使用方法一样\nConcurrentHashMap # Map map = new ConcurrentHashMap() 队列Queue # 任何无限容量的队列ho栈都是有容量的，这个容量就是Integer.MAX_VALUE\nQueue常用方法 # 入队 void add(Object e)：将指定元素加入此队列的尾部，当超过容量，抛出异常 boolean offer(Object e)：将指定元素加入该队列的尾部，当超过容量，返回false 出队 Object poll()：获取队列头部的元素，并删除该元素，队列为空则返回null Object remove()：获取队列头部的元素，并删除该元素，队列为空时抛出异常NoSuchElementException 查看队首元素 Object element()：获取列队头部的元素，但是不删除该元素，队列为空抛出异常 Object peek()：获取队列头部的元素，但是不删除该元素，队列为空，则返回null 双端队列 # deque是double ended queue的缩写。双端队列顾名思义就是队列的两个端口都能进出。\nDeque的实现类中，LinkedList是最常用的。值得注意的是，LinkedList也实现了List接口。\n大多数Deque既可以是有容量限制也可以是无固定容量限制。\n头部 头部 尾部 尾部 失败响应 抛出异常 返回值 抛出异常 返回值 入队 addFirst(e) offerFirst(e) addLast(e) offerLast(e) 出队 removeFirst() pollFirst() removeLast() pollLast() 查看 getFirst() peekFirst() getLast() peekLast() 双端队列的三种用法：\n1、作为队列，先进先出\nQueue\u0026lt;String\u0026gt; queue = new LinkedList(); // 队尾入队 queue.offer(\u0026#34;El\u0026#34;); // 队首出队 String el = queue.poll(); Deque\u0026lt;String\u0026gt; deque = new LinkedList(); // 队尾入队 deque.offerLast(\u0026#34;El\u0026#34;); // 队首出队 String el = deque.pollFirst(); 2、作为堆栈，先进后出\n注意：Java的堆栈类Stack已经过时，官方推荐Deque代替\nDeque\u0026lt;String\u0026gt; deque = new LinkedList(); // 队尾入队 deque.offerLast(\u0026#34;el\u0026#34;); // 队尾出队 String el = deque.pollLast(); 3、作为双端队列，两端都可进出\nDeque\u0026lt;String\u0026gt; deque = new LinkedList(); // 队尾入队 deque.offerLast(\u0026#34;el\u0026#34;); // 队首入队 deque.offerFirst(\u0026#34;el\u0026#34;); // 队尾出队 String el1 = deque.pollLast(); // 队首出队 String el2 = deque.pollFirst(); 阻塞队列 # 阻塞队列是一个支持两个附加操作的队列，即在队列为满时，存储元素的线程会等待队列可用；当队列为空时，获取元素的线程会等待队列为非空。\nArrayBlockingQueue ：一个由数组结构组成的有界阻塞队列。 LinkedBlockingQueue ：一个由链表结构组成的有界阻塞队列。 PriorityBlockingQueue ：一个支持优先级排序的无界阻塞队列。 DelayQueue：一个使用优先级队列实现的无界阻塞队列。 SynchronousQueue：一个不存储元素的阻塞队列。 LinkedTransferQueue：一个由链表结构组成的无界阻塞队列。 LinkedBlockingDeque：一个由链表结构组成的双向阻塞队列。 阻塞队列所实现接口有BlockingQueue和BlockingDeque，增加方法如下：\nBlockingQueue # 方法描述 一直阻塞 超时退出 入队 put(e) offer(e,time,unit) 出队 take() poll(time,unit) BlockingDeque # 头部 头部 尾部 尾部 方法描述 一直阻塞 超时退出 一直阻塞 超时退出 入队 putFirst(e) offerFirst(e,time,unit) putLast(e) offerLast(e,time,unit) 出队 takeFirst() pollFirst(time,unit) takeLast() pollLast(time,unit) 非阻塞队列 # 非阻塞队列不能阻塞，个人理解为普通队列，在多线程中，当队列满或空时，只能使用wait()和notify()进行队列消息传送。\nAbstractQueue是非阻塞队列所实现的接口，常见的非阻塞实现类有：\nLinkedList 既实现了AbstractQueue接口也实现了Deque接口，也可作为双端队列使用。 PriorityQueue 维护了一个有序队列，默认队头是规则排序中最小的元素（从小到大）。 排序的规则是通过构造函数comparator来实现，因此，该队列不允许插入null值或不可比较的对象。 ConcurrentLinkedQueue 该类是基于链接点的线程安全队列，并发访问不需要同步。 此队列不允许使用null元素。 ","date":"2025-02-18","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/fbeea4f4/31f83035/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e继承树 \n    \u003cdiv id=\"继承树\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%bb%a7%e6%89%bf%e6%a0%91\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eCollection接口 \n    \u003cdiv id=\"collection接口\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#collection%e6%8e%a5%e5%8f%a3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"说明: untitle.png\"\n    data-zoom-src=\"/posts/3ab7256e/80aacaea/fbeea4f4/31f83035/image/202109181148098.jpg\"\n    src=\"/posts/3ab7256e/80aacaea/fbeea4f4/31f83035/image/202109181148098.jpg\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"2、集合","type":"posts"},{"content":" JSP的缺陷 # JSP就是一个Servlet ，生命周期：翻译，编译，加载，初始化，服务，销毁 Servlet一定要放置在Servlet容器中才可以运行，浏览器不能直接解析 JSP页面一般构成：HTML + CSS + JS + Java代码 + JSTL标记 由此的坏处： JSP必须要在Servlet容器解析成HTML，才可以返回到浏览器中（系统：不管是静态请求也好， 还是动态请求，都压到Tomcat服务器，这就可能导致Tomcat解决动态请求的能力有所降低） JSP必须要在Servlet容器解析成HTML，这就意味着百度，谷歌，以及其他的搜索引擎无法去收录 JSP从2015年之前，就开始走向灭亡，取而代之的是，HTML + 模板引擎 模板引擎 # 使用符号或者自定义标记 + 网页中不变的东西 = 模板引擎 模板引擎的作用： 将页面中动态的内容，使用标记或某些符号，进行静态的替换。从而做到：浏览器认为整个网页都是静态的内容（进而可以做到：浏览器缓存HTML，Nginx等服务器缓存HTML） 从而提升系统的整合响应性能 常见的模板引擎**：Freemarker，thymeleaf，velocity** Thymeleaf的特点 # Thymeleaf 模板引擎具有以下特点：\n动静结合：Thymeleaf 既可以直接使用浏览器打开，查看页面的静态效果，也可以通过 Web 应用程序进行访问，查看动态页面效果。 开箱即用：Thymeleaf 提供了 Spring 标准方言以及一个与 SpringMVC 完美集成的可选模块，可以快速的实现表单绑定、属性编辑器、国际化等功能。 多方言支持：它提供了 Thymeleaf 标准和 Spring 标准两种方言，可以直接套用模板实现 JSTL、 OGNL 表达式；必要时，开发人员也可以扩展和创建自定义的方言。 与 SpringBoot 完美整合：SpringBoot 为 Thymeleaf 提供了的默认配置，并且还为 Thymeleaf 设置了视图解析器，因此 Thymeleaf 可以与 Spring Boot 完美整合。 Thymeleaf的基本使用 # Thymeleaf通常适用于前后端不分离的页面\n1、创建SpringBoot应用\n2、导入依赖\n\u0026lt;!-- 导入thymeleaf的启动器依赖：完成对thymeleaf的全部支持，自动配置了对应视图解析器 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-thymeleaf\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 3、配置文件\n# 指定模板所在的目录 spring.thymeleaf.prefix=classpath:/templates/ # 检查模板路径是否存在 spring.thymeleaf.check-template-location=true # 后缀 spring.thymeleaf.suffix=.html # 默认HTML spring.thymeleaf.mode=HTML # 是否缓存，开发环境true、生产环境false spring.thymeleaf.cache=false # 解析字符集编码 spring.thymeleaf.encoding=UTF-8 spring: thymeleaf: encoding: UTF-8 prefix: classpath:/templates/ mode: HTML suffix: .html 4、html页面放在src/main/resources/templates下\nhtml模板文件需要引入命名空间http://www.thymeleaf.org\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34; xmlns:th=\u0026#34;http://www.thymeleaf.org\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1 th:text=\u0026#34;\u0026#34;\u0026gt;\u0026lt;/h1\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 5、js、css等静态资源放在src/main/resources/static下\n由于SpringBoot约定大于配置的特性，默认的静态文件目录为src/main/resources/static，也可以通过配置文件进行更换\n注意：引入静态资源的时候不需要加static这一层路径\nspring: resources: static-locations: classpath:/myStatic/ Thymeleaf语法规则 # 标准表达式 # Thymeleaf 模板引擎支持多种表达式：\n变量表达式：${...} 选择变量表达式：*{...} 链接表达式：@{...} 国际化表达式：#{...} 片段引用表达式：~{...} 变量表达式 # 使用${}包裹的表达式被称为变量表达式，该表达式具有以下功能：\n获取对象的属性和方法 ${person.name} 使用内置的基本对象 #ctx：上下文对象 #vars：上下文变量 #locale：上下文的语言环境 #request：HttpServletRequest 对象（仅在 Web 应用中可用） #response：HttpServletResponse 对象（仅在 Web 应用中可用） #session：HttpSession 对象（仅在 Web 应用中可用） ${#session.getAttribute('map')} ${session.map} #servletContext：ServletContext 对象（仅在 Web 应用中可用） 使用内置的工具对象 strings：字符串工具对象，常用方法有：equals、equalsIgnoreCase、length、trim、toUpperCase、toLowerCase、indexOf、substring、replace、startsWith、endsWith，contains 和 containsIgnoreCase 等 ${#strings.equals('ygang',name)} numbers：数字工具对象，常用的方法有：formatDecimal 等 bools：布尔工具对象，常用的方法有：isTrue 和 isFalse 等 arrays：数组工具对象，常用的方法有：toArray、length、isEmpty、contains 和 containsAll 等 lists/sets：List/Set 集合工具对象，常用的方法有：toList、size、isEmpty、contains、containsAll 和 sort 等 maps：Map 集合工具对象，常用的方法有：size、isEmpty、containsKey 和 containsValue 等 dates：日期工具对象，常用的方法有：format、year、month、hour 和 createNow 等 th:text=\u0026quot;${#dates.format(date, 'yyyy-MM-dd HH:mm:ss')}\u0026quot; 选择变量表达式 # 选择变量表达式与变量表达式功能基本一致，只是在变量表达式的基础上增加了与 th:object 的配合使用。当使用 th:object 存储一个对象后，我们可以在其后代标签中使用选择变量表达式*{...}获取该对象中的属性，其中，*即代表该对象。\n\u0026lt;div th:object=\u0026#34;${user}\u0026#34; \u0026gt; \u0026lt;p th:text=\u0026#34;*{name}\u0026#34;\u0026gt;name\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;!-- 等价于 --\u0026gt; \u0026lt;div\u0026gt; \u0026lt;p th:text=\u0026#34;${user.name}\u0026#34;\u0026gt;name\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; 链接表达式 # 不管是静态资源的引用，还是 form 表单的请求，凡是链接都可以用链接表达式 @{...}。\n链接表达式的形式结构如下：\n无参请求：@{/xxx} 有参请求：@{/xxx(k1=v1,k2=v2)} \u0026lt;link th:href=\u0026#34;@{/asserts/css/signin.css}\u0026#34; rel=\u0026#34;stylesheet\u0026#34;\u0026gt; 国际化表达式 # 消息表达式一般用于国际化的场景。\nth:text=\u0026#34;#{msg}\u0026#34; 片段引用表达式 # 片段引用表达式用于在模板页面中引用其他的模板片段，该表达式支持以下 2 中语法结构：\n推荐：~{templatename::fragmentname}\n支持：~{templatename::#id}\ntemplatename：模版名，Thymeleaf 会根据模版名解析完整路径：/resources/templates/templatename.html，要注意文件的路径。\nfragmentname：片段名，Thymeleaf 通过 th:fragment 声明定义代码块，即：th:fragment=\u0026quot;fragmentname\u0026quot;\nid：HTML 的 id 选择器，使用时要在前面加上 # 号，不支持 class 选择器。\n通常情况下，~{} 可以省略\nth属性 # th:id # 替换 HTML 的 id 属性\nth:text # 文本替换，转义特殊字符，也就是会显示html标签为文本\nth:utext # 文本替换，不转义特殊字符，也就是会将html标签解析\nth:object # 在父标签选择对象，子标签使用 *{…} 选择表达式选取属性值。 没有选择对象，那子标签使用选择表达式和 ${…} 变量表达式是一样的效果。 同时即使选择了对象，子标签仍然可以使用变量表达式。\nth:value # 替换 value 属性\nth:with # 局部变量赋值运算\n\u0026lt;div th:with=\u0026#34;isEvens = ${prodStat.count}%2 == 0\u0026#34; th:text=\u0026#34;${isEvens}\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; th:style # 设置样式\nth:onclick # 设置点击事件\nth:each # 遍历，支持 Iterable、Map、数组等。\n\u0026lt;table\u0026gt; \u0026lt;tr th:each=\u0026#34;m:${session.map}\u0026#34;\u0026gt; \u0026lt;td th:text=\u0026#34;${m.getKey()}\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td th:text=\u0026#34;${m.getValue()}\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/table\u0026gt; 对于遍历，也可以拿到遍历中的状态变量，如索引\n\u0026lt;table\u0026gt; \u0026lt;tr th:each=\u0026#34;m,status:${session.map}\u0026#34;\u0026gt; \u0026lt;td th:text=\u0026#34;${m.getKey()}\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td th:text=\u0026#34;${m.getValue()}\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/table\u0026gt; status有如下属性 index：当前迭代对象的迭代索引，从0开始，这是索引属性； count：当前迭代对象的迭代索引，从1开始，这个是统计属性； size：迭代变量元素的总量，这是被迭代对象的大小属性； current：当前迭代变量； even/odd：布尔值，当前循环是否是偶数/奇数（从0开始计算）； first：布尔值，当前循环是否是第一个； last：布尔值，当前循环是否是最后一个； th:if # 根据条件判断是否需要展示此标签\nth:unless # 和th:if判断相反，满足条件时不显示\nth:switch # 与 Java 的 switch case语句类似，通常与 th:case 配合使用，根据不同的条件展示不同的内容\n\u0026lt;div th:switch=\u0026#34;${name}\u0026#34;\u0026gt; \u0026lt;span th:case=\u0026#34;a\u0026#34;\u0026gt;ygang\u0026lt;/span\u0026gt; \u0026lt;span th:case=\u0026#34;b\u0026#34;\u0026gt;top\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; th:fragment # 模板布局，类似 JSP 的 tag，用来定义一段被引用或包含的模板片段\nth:insert # 将使用 th:fragment 属性指定的模板片段（包含标签）插入到当前标签中。\nth:replace # 将使用 th:fragment 属性指定的模板片段（包含标签）替换当前整个标签。\nth:include # 将使用 th:fragment 属性指定的模板包含的内容（不包含标签）插入到当前标签中。\nth:selected # 设置select 选择框选中\n\u0026lt;select\u0026gt; \u0026lt;option\u0026gt;---\u0026lt;/option\u0026gt; \u0026lt;option th:selected=\u0026#34;${name==\u0026#39;a\u0026#39;}\u0026#34;\u0026gt; ygang \u0026lt;/option\u0026gt; \u0026lt;option th:selected=\u0026#34;${name==\u0026#39;b\u0026#39;}\u0026#34;\u0026gt; top \u0026lt;/option\u0026gt; \u0026lt;/select\u0026gt; th:src # 替换 HTML 中的 src 属性\nth:inline # 内联属性； 该属性有 text、none、javascript 三种取值， 在 \u0026lt;script\u0026gt; 标签中使用时，js 代码中可以获取到后台传递页面的对象\n\u0026lt;script type=\u0026#34;text/javascript\u0026#34; th:inline=\u0026#34;javascript\u0026#34;\u0026gt; var name = [[${name}]]; alert(name) \u0026lt;/script\u0026gt; th:action # 替换表单提交地址\n内联表达式 # [[…]] # 相当于th:text\n\u0026lt;p\u0026gt;The message is : [[${htmlContent}]]\u0026lt;/p\u0026gt; [(…)] # 相当于th:utext\n\u0026lt;p\u0026gt;The message is : [(${htmlContent})]\u0026lt;/p\u0026gt; th:inline # 值 描述 none 禁止内联表达式，可以原样输出 [[]] 和 [()] 字符串 text 文本内联，可以使用 th:each 等高级语法 css 样式内联，如：\u0026lt;style th:inline=\u0026quot;css\u0026quot;\u0026gt; javascript 脚本内联，如：\u0026lt;style th:inline=\u0026quot;javascript\u0026quot;\u0026gt; 组件化开发 # 定义模板，在templates/commons.html中\n\u0026lt;div th:fragment=\u0026#34;fragment-name\u0026#34; id=\u0026#34;fragment-id\u0026#34;\u0026gt; \u0026lt;span\u0026gt;公共页面片段\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; 引入模板\n\u0026lt;!--片段名引入--\u0026gt; \u0026lt;div th:insert=\u0026#34;commons::fragment-name\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;!--id选择器引入--\u0026gt; \u0026lt;div th:insert=\u0026#34;commons::#fragment-id\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; 传参 # 定义模板，在templates/commons.html中\n\u0026lt;div th:fragment=\u0026#34;fragment-name(name,age)\u0026#34; id=\u0026#34;fragment-id\u0026#34;\u0026gt; \u0026lt;span\u0026gt;公共页面片段\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; 引入模板\n\u0026lt;!--片段名引入并传参--\u0026gt; \u0026lt;div th:insert=\u0026#34;commons::fragment-name(${name},18)\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;!--片段名替换并传参--\u0026gt; \u0026lt;div th:replace=\u0026#34;commons::fragment-name(${name},18)\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; ","date":"2025-01-03","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/4f5b7444/953602ef/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJSP的缺陷 \n    \u003cdiv id=\"jsp的缺陷\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jsp%e7%9a%84%e7%bc%ba%e9%99%b7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eJSP就是一个Servlet ，生命周期：翻译，编译，加载，初始化，服务，销毁\u003c/li\u003e\n\u003cli\u003eServlet一定要放置在Servlet容器中才可以运行，浏览器不能直接解析\u003c/li\u003e\n\u003cli\u003eJSP页面一般构成：HTML + CSS + JS + Java代码 + JSTL标记\u003c/li\u003e\n\u003cli\u003e由此的坏处：\n\u003cul\u003e\n\u003cli\u003eJSP必须要在Servlet容器解析成HTML，才可以返回到浏览器中（系统：不管是静态请求也好， 还是动态请求，都压到Tomcat服务器，这就可能导致Tomcat解决动态请求的能力有所降低）\u003c/li\u003e\n\u003cli\u003eJSP必须要在Servlet容器解析成HTML，这就意味着百度，谷歌，以及其他的搜索引擎无法去收录\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eJSP从2015年之前，就开始走向灭亡，取而代之的是，HTML + 模板引擎\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e模板引擎 \n    \u003cdiv id=\"模板引擎\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%a8%a1%e6%9d%bf%e5%bc%95%e6%93%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e使用符号或者自定义标记 + 网页中不变的东西 = 模板引擎\u003c/li\u003e\n\u003cli\u003e模板引擎的作用： 将页面中动态的内容，使用标记或某些符号，进行静态的替换。从而做到：浏览器认为整个网页都是静态的内容（进而可以做到：浏览器缓存HTML，Nginx等服务器缓存HTML） 从而提升系统的整合响应性能\u003c/li\u003e\n\u003cli\u003e常见的模板引擎**：Freemarker，thymeleaf，velocity**\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003eThymeleaf的特点 \n    \u003cdiv id=\"thymeleaf的特点\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#thymeleaf%e7%9a%84%e7%89%b9%e7%82%b9\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003eThymeleaf 模板引擎具有以下特点：\u003c/p\u003e","title":"2、Thymeleaf","type":"posts"},{"content":"jQuery 是一个 JavaScript 库。\njQuery 极大地简化了 JavaScript 编程。\nJQuery的版本说明 # 1.x：兼容ie678，使用最为广泛的，官方只做BUG维护，功能不再新增。因此一般项目来说，使用1.x版本就可以了，最终版本：1.12.4 (2016年5月20日)\n2.x：不兼容ie678，很少有人使用，官方只做BUG维护，功能不再新增。如果不考虑兼容低版本的浏览器可以使用2.x，最终版本：2.2.4 (2016年5月20日)\n3.x：不兼容ie678，只支持最新的浏览器。除非特殊要求，一般不会使用3.x版本的，很多老的jQuery插件不支持这个版本\n使用方法 # 语法 # $(selector).action(); 工厂函数$()：将DOM对象转化为JQuery对象\n选择器selector：获取需要操作的DOM元素\n方法action()：JQuery中提供的方法，包括绑定事件处理的方法\n入口函数 # //$(document).ready(function(){}); $(function() { ...... }) 是$(document).ready(function(){});的简写，相当于window.onload = function{}\n样式设置 # div.css(\u0026#34;border\u0026#34;, \u0026#34;1px solid red\u0026#34;); div.css(\u0026#34;属性名\u0026#34;, \u0026#34;属性值\u0026#34;); div.css({\u0026#34;key0\u0026#34;:\u0026#34;value0\u0026#34;,\u0026#34;key1\u0026#34;:\u0026#34;value1\u0026#34;,...}) 事件处理 # 对象.click(function() { ...... }) bind绑定 # $(selector).bind(\u0026#34;事件名\u0026#34;,处理函数); //两个事件一个处理 $(selector).bind(\u0026#34;事件名1 事件名1\u0026#34;,处理函数); //多个事件的绑定 $(selector).bind({ 事件名1:处理函数; 事件名2:处理函数; 事件名3:处理函数; }); 对象转换 # js对象转JQuery对象 # var JQuery对象 = $(js对象); JQuery对象转js对象 # //方式一 var js对象 = JQuery对象数组[索引]; //方式二 var js对象 = JQuery对象数组.get(索引); 选择器 # 基本选择器 # id选择器：$(\u0026quot;#id属性值\u0026quot;) 标签选择器：$(\u0026quot;标签名\u0026quot;) 类选择器：$(\u0026quot;.class属性名\u0026quot;) 通配符选择器：$(\u0026quot;\\*\u0026quot;) 组合选择器：$(\u0026quot;#aaa, .bbb\u0026quot;) 层级选择器 # 包含选择器：$(\u0026quot;a b\u0026quot;) \u0026ndash; a的所有子元素b 子元素选择器：$(\u0026quot;a \u0026gt; b\u0026quot;) \u0026ndash; a的直接子元素b 兄弟元素选择器：$(\u0026quot;a~b\u0026quot;) \u0026ndash; 和a为同级的元素b 相邻元素选择器：$(\u0026quot;a+b\u0026quot;) \u0026ndash; 和a相邻的元素b 属性选择器 # $(\u0026quot;标签名[属性名]\u0026quot;)或$(\u0026quot;标签名[属性名='属性值']\u0026quot;)\n基本过滤选择器 # 获取第一个该选择器元素对象：$(\u0026quot;选择器:first\u0026quot;) 获取最后一个该选择器元素对象：$(\u0026quot;选择器:last\u0026quot;) 获取该选择器指定索引位置的元素对象：$(\u0026quot;选择器:eq(索引)\u0026quot;) 获取该选择器索引大于i的元素对象：$(\u0026quot;选择器:gt(i)\u0026quot;) 获取该选择器索引小于i的元素对象：$(\u0026quot;选择器:lt(i)\u0026quot;) 获取该选择器不是该（选择器）的元素对象：$(\u0026quot;选择器:not(选择器)\u0026quot;) 获取偶数索引该选择器元素对象：$(\u0026quot;选择器:even\u0026quot;) 获取奇数索引该选择器元素对象：$(\u0026quot;选择器:odd\u0026quot;) 注意：even和odd匹配的索引以0开始，所以实际展示出来的分别是第奇数和第偶数个\n可见性过滤选择器 # 获取该选择器中可见元素对象：$(\u0026quot;选择器:visible\u0026quot;)\n获取该选择器中隐藏元素对象：$(\u0026quot;选择器:hidden\u0026quot;)\n表单过滤选择器 # 内容过滤选择器 # DOM操作 # 文本 # 方法无参的情况下则返回该属性值\n$(\u0026#34;选择器\u0026#34;).val(\u0026#34;value属性值\u0026#34;); $(\u0026#34;选择器\u0026#34;).html(\u0026#34;HTML属性值\u0026#34;); $(\u0026#34;选择器\u0026#34;).text(\u0026#34;TEXT属性值\u0026#34;); 属性 # 通用属性：\nattr();：设置或返回被选元素的属性值 var 获取属性值 = $(\u0026#34;选择器\u0026#34;).attr(\u0026#34;属性名\u0026#34;); //设置属性值 $(\u0026#34;选择器\u0026#34;).attr(\u0026#34;属性名\u0026#34;,\u0026#34;属性值\u0026#34;) $(\u0026#34;选择器\u0026#34;).attr({\u0026#34;属性名\u0026#34;:\u0026#34;属性值\u0026#34;,\u0026#34;属性名\u0026#34;:\u0026#34;属性值\u0026#34;,...}); removeAttr();：从每一个匹配的元素中删除一个属性\nprop();：获取在匹配的元素集中的第一个元素的属性值\nremoveProp();：用来删除由.prop()方法设置的属性集\n注意：prop只可以操作固有属性（内置属性）；attr可以操作自定义属性，也可以操作固有属性，但某些固有属性无法读取，例如checked属性\nclass属性：\naddClass(); removeClass(); each() # 为每个元素执行函数\n//逐个获取元素对象，获取到的是js对象 $(\u0026#34;选择器\u0026#34;).each(function(index,element){ 针对元素对象element进行操作; }); find() # //在所有的p元素后代中，查找span元素 var spans = $(\u0026#34;p\u0026#34;).find(\u0026#34;span\u0026#34;); eq() # //在所有的p元素中，选择第几个元素，返回值是一个jquery对象 var p = $(\u0026#34;p\u0026#34;).eq(索引值); 方法 描述 add() 把元素添加到匹配元素的集合中 addBack() 把之前的元素集添加到当前集合中 children() 返回被选元素的所有直接子元素 closest() 返回被选元素的第一个祖先元素 contents() 返回被选元素的所有直接子元素（包含文本和注释节点） each() 为每个匹配元素执行函数 end() 结束当前链中最近的一次筛选操作，并把匹配元素集合返回到前一次的状态 eq() 返回带有被选元素的指定索引号的元素 filter() 把匹配元素集合缩减为匹配选择器或匹配函数返回值的新元素 find() 返回被选元素的后代元素 first() 返回被选元素的第一个元素 has() 返回拥有一个或多个元素在其内的所有元素 is() 根据选择器/元素/jQuery 对象检查匹配元素集合，如果存在至少一个匹配元素，则返回 true last() 返回被选元素的最后一个元素 map() 把当前匹配集合中的每个元素传递给函数，产生包含返回值的新 jQuery 对象 next() 返回被选元素的后一个同级元素 nextAll() 返回被选元素之后的所有同级元素 nextUntil() 返回介于两个给定参数之间的每个元素之后的所有同级元素 not() 从匹配元素集合中移除元素 offsetParent() 返回第一个定位的父元素 parent() 返回被选元素的直接父元素 parents() 返回被选元素的所有祖先元素 parentsUntil() 返回介于两个给定参数之间的所有祖先元素 prev() 返回被选元素的前一个同级元素 prevAll() 返回被选元素之前的所有同级元素 prevUntil() 返回介于两个给定参数之间的每个元素之前的所有同级元素 siblings() 返回被选元素的所有同级元素 slice() 把匹配元素集合缩减为指定范围的子集 动画 # //速度可选参数：fast、slow、毫秒数 //显示、隐藏 $(selector).show(显示的速度,显示完成后完成的函数);//显示 $(selector).hide(隐藏的速度,隐藏完成后完成的函数);//隐藏 //飞入、飞出 $(selector).fadeIn(显示的速度,显示完成后完成的函数);//飞入 $(selector).fadeOut(隐藏的速度,隐藏完成后完成的函数);//飞出 //滑动 $(selector).slideDown(滑动的速度,滑动完成后完成的函数);//向下滑动 $(selector).slideUp(滑动的速度,滑动完成后完成的函数);//向上滑动 自定义动画 # $(selector).animate({params},speed,callback); /* 必需的 params 参数定义形成动画的 CSS 属性。key:value 可选的 speed 参数规定效果的时长。它可以取以下值：\u0026#34;slow\u0026#34;、\u0026#34;fast\u0026#34; 或毫秒。 可选的 callback 参数是动画完成后所执行的函数名称。 */ ","date":"2025-01-02","externalUrl":null,"permalink":"/posts/bafd68f1/d4aa59a0/205f0402/","section":"文章","summary":"\u003cp\u003ejQuery 是一个 JavaScript 库。\u003c/p\u003e\n\u003cp\u003ejQuery 极大地简化了 JavaScript 编程。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003eJQuery的版本说明 \n    \u003cdiv id=\"jquery的版本说明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jquery%e7%9a%84%e7%89%88%e6%9c%ac%e8%af%b4%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003e1.x\u003c/code\u003e：兼容ie678，使用最为广泛的，官方只做BUG维护，功能不再新增。因此一般项目来说，使用1.x版本就可以了，最终版本：1.12.4 (2016年5月20日)\u003c/p\u003e","title":"2、JQuery","type":"posts"},{"content":" 添加、删除、修改 # //GoodTypeDaoMapper接口 //1、增加 boolean addOne(GoodType goodType); //2、删除 boolean removeById(int tid); //3、修改 boolean updateNameById(String tname,int tid); \u0026lt;!--GoodTypeDaoMapper.xml映射文件--\u0026gt; \u0026lt;!--1、增加--\u0026gt; \u0026lt;insert id=\u0026#34;addOne\u0026#34;\u0026gt; insert into goodtype (tid,tname,state,remarks) values (#{tid},#{tname},#{state},#{remarks}) \u0026lt;/insert\u0026gt; \u0026lt;!--2、删除--\u0026gt; \u0026lt;delete id=\u0026#34;addOne\u0026#34;\u0026gt; delete from goodtype where tid=#{tid} \u0026lt;/delete\u0026gt; \u0026lt;!--3、修改--\u0026gt; \u0026lt;update id=\u0026#34;updateNameById\u0026#34;\u0026gt; update goodtype set tname=#{arg0} where tid=#{arg1} \u0026lt;/update\u0026gt; 添加数据并获取主键值 # 方式一 # \u0026lt;insert id=\u0026#34;add\u0026#34; useGenneratedKeys=\u0026#34;true\u0026#34; keyColumn=\u0026#34;tid\u0026#34; keyProperty=\u0026#34;tid\u0026#34;\u0026gt; insert into goodtype (tname,state,remarks) values (#{tname},#{state},#{remarks}) \u0026lt;/insert\u0026gt; 方式二 # \u0026lt;insert id=\u0026#34;add\u0026#34;\u0026gt; \u0026lt;selectKey keyColumn=\u0026#34;tid\u0026#34; keyProperty=\u0026#34;tid\u0026#34; resultType=\u0026#34;int\u0026#34; order=\u0026#34;AFTER\u0026#34;\u0026gt; select last_insert_id() \u0026lt;/selectKey\u0026gt; insert into goodtype (tname,state,remarks) values (#{tname},#{state},#{remarks}) \u0026lt;/insert\u0026gt; 查询 # 简单查询 # //GoodTypeDaoMapper接口 List\u0026lt;GoodType\u0026gt; findAll(); \u0026lt;!--UserDao.xml:--\u0026gt; \u0026lt;select id=\u0026#34;findAll\u0026#34; resultType=\u0026#34;entity.GoodType\u0026#34;\u0026gt; select * from goodtype \u0026lt;/select\u0026gt; 多表查询 # 一对一 # 根据商品id查询出商品Goods信息，以及对应的商品类型GoodType信息\nresultMap-result # 如果goods表中的字段名和goodtype中的某些字段名相同，那么在赋值时，会出现未赋值的情况，可以通过起别名的方式进行解决\n\u0026lt;!--type：查询数据存放的对象--\u0026gt; \u0026lt;resultMap id=\u0026#34;goodsMap1\u0026#34; type=\u0026#34;entity.Goods\u0026#34;\u0026gt; \u0026lt;!--result节点就是处理查询出的列和属性映射关系--\u0026gt; \u0026lt;!--id节点和result节点作用相同，但可以区分主键--\u0026gt; \u0026lt;!--对于多表联合查询，商品实体类Goods中，有商品类型属性GoodType--\u0026gt; \u0026lt;id column=\u0026#34;tid\u0026#34; property=\u0026#34;goodType.tid\u0026#34;/\u0026gt; \u0026lt;result column=\u0026#34;tname\u0026#34; property=\u0026#34;goodType.tname\u0026#34;/\u0026gt; \u0026lt;result column=\u0026#34;tstate\u0026#34; property=\u0026#34;goodType.state\u0026#34;/\u0026gt; \u0026lt;result column=\u0026#34;trmarks\u0026#34; property=\u0026#34;goodType.remarks\u0026#34;/\u0026gt; \u0026lt;/resultMap\u0026gt; \u0026lt;select id=\u0026#34;findById\u0026#34; resultMap=\u0026#34;goodsMap1\u0026#34;\u0026gt; select * from goods g inner join goodtype t on a.tid=t.tid where gid=#{gid} \u0026lt;/select\u0026gt; resultMap-association # \u0026lt;resultMap id=\u0026#34;goodsMap2\u0026#34; type=\u0026#34;entity.Goods\u0026#34;\u0026gt; \u0026lt;result column=\u0026#34;gid\u0026#34; property=\u0026#34;gid\u0026#34;/\u0026gt; \u0026lt;!--对于多表联合查询，商品实体类Goods中，有商品类型GoodType属性--\u0026gt; \u0026lt;!--javaType：关联表的实体类GoodType全类名，property：实体类Goods中的，商品类型GoodType属性名--\u0026gt; \u0026lt;association property=\u0026#34;goodType\u0026#34; javaType=\u0026#34;entity.GoodType\u0026#34;\u0026gt; \u0026lt;id column=\u0026#34;tid\u0026#34; property=\u0026#34;tid\u0026#34;/\u0026gt; \u0026lt;result column=\u0026#34;tname\u0026#34; property=\u0026#34;tname\u0026#34;/\u0026gt; \u0026lt;result column=\u0026#34;tstate\u0026#34; property=\u0026#34;state\u0026#34;/\u0026gt; \u0026lt;result column=\u0026#34;trmarks\u0026#34; property=\u0026#34;remarks\u0026#34;/\u0026gt; \u0026lt;/association\u0026gt; \u0026lt;/resultMap\u0026gt; \u0026lt;select id=\u0026#34;findById\u0026#34; resultMap=\u0026#34;goodsMap2\u0026#34;\u0026gt; select * from goods g inner join goodtype t on a.tid=t.tid where gid=#{gid} \u0026lt;/select\u0026gt; resultMap-association-select：分布查询（先查询商品表goods，再查询关联的goodtype表） # \u0026lt;!--select：调用商品类型对应接口中的findById方法，column：select调用的方法中的参数值--\u0026gt; \u0026lt;resultMap id=\u0026#34;goodsMap3\u0026#34; type=\u0026#34;entity.Goods\u0026#34;\u0026gt; \u0026lt;association property=\u0026#34;goodType\u0026#34; javaType=\u0026#34;entity.GoodType\u0026#34; select=\u0026#34;dao.GoodTypeDaoMapper.findById\u0026#34; column=\u0026#34;tid\u0026#34;\u0026gt; \u0026lt;/association\u0026gt; \u0026lt;/resultMap\u0026gt; \u0026lt;select id=\u0026#34;findById\u0026#34; resultMap=\u0026#34;goodsMap3\u0026#34;\u0026gt; select * from goods where gid=#{gid} \u0026lt;/select\u0026gt; 一对多 # 根据商品类型tid查询出所有该类型商品List\u0026lt;Goods\u0026gt;信息，以及对应的商品类型GoodType信息\nresultMap-collection # \u0026lt;resultMap id=\u0026#34;goodTypeMap1\u0026#34; type=\u0026#34;entity.GoodType\u0026#34;\u0026gt; \u0026lt;!--property：goodType对象中关联多个商品的属性（集合）名--\u0026gt; \u0026lt;!--javaType：关联多个商品的属性（集合）类型的全类名--\u0026gt; \u0026lt;!--ofType：关联多个商品的属性（集合）中的元素的类型--\u0026gt; \u0026lt;collection property=\u0026#34;goods\u0026#34; javaType=\u0026#34;java.util.List\u0026#34; ofType=\u0026#34;entity.Goods\u0026#34;\u0026gt; \u0026lt;!--column：列名--\u0026gt; \u0026lt;!--property：关联多个商品的属性（集合）中的元素的属性--\u0026gt; \u0026lt;id column=\u0026#34;gid\u0026#34; property=\u0026#34;gid\u0026#34;/\u0026gt; \u0026lt;result column=\u0026#34;dname\u0026#34; property=\u0026#34;dname\u0026#34;/\u0026gt; \u0026lt;result column=\u0026#34;tid\u0026#34; property=\u0026#34;tid\u0026#34;/\u0026gt; \u0026lt;/collection\u0026gt; \u0026lt;/resultMap\u0026gt; \u0026lt;select id=\u0026#34;findGoodsAsId\u0026#34; resultMap=\u0026#34;goodTypeMap1\u0026#34;\u0026gt; select * from goodtype t inner join goods g on g.tid=t.tid where t.tid=#{tid} \u0026lt;/select\u0026gt; resultMap-collection-select # 先进行goodtype表的查询，然后通过select属性调用GoodsDaoMapper中的findByTid()方法，进行分步查询\n\u0026lt;resultMap id=\u0026#34;goodTypeMap2\u0026#34; type=\u0026#34;entity.GoodType\u0026#34;\u0026gt; \u0026lt;id column=\u0026#34;tid\u0026#34; property=\u0026#34;tid\u0026#34;/\u0026gt; \u0026lt;collection property=\u0026#34;goods\u0026#34; javaType=\u0026#34;java.util.List\u0026#34; ofType=\u0026#34;entity.Goods\u0026#34; select=\u0026#34;dao.GoodsDaoMapper.findByTid\u0026#34; column=\u0026#34;tid\u0026#34;\u0026gt; \u0026lt;/collection\u0026gt; \u0026lt;/resultMap\u0026gt; \u0026lt;select id=\u0026#34;findGoodsAsId\u0026#34; resultMap=\u0026#34;goodTypeMap2\u0026#34;\u0026gt; select * from goodtype where tid=#{tid} \u0026lt;/select\u0026gt; collection和association和区别 # association：用于实现对一查询，其中没有ofType的属性\ncollection：用于实现对多的查询，其中含有ofType的属性，该属性用于表示集合中的元素属性。\n分页查询（插件） # 1、依赖\n\u0026lt;!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.github.pagehelper\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;pagehelper\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;5.3.3\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 2、在mybatis核心配置文件中加入插件（放置在setting之后，environments之前）\n\u0026lt;plugins\u0026gt; \u0026lt;plugin interceptor=\u0026#34;com.github.pagehelper.PageInterceptor\u0026#34;\u0026gt; \u0026lt;!-- helperDialect:属性进赋值，值不能改变 value：设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库--\u0026gt; \u0026lt;property name=\u0026#34;helperDialect\u0026#34; value=\u0026#34;mysql\u0026#34;/\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; 3、在java测试中，调用PageHelper中的静态方法startPage()，进行分页\n//第一个参数是第几页，第二个参数是每页几条 PageHelper.startPage(2,3); 4、调用查询方法，进行查询\nList\u0026lt;GoodType\u0026gt; all = goodTypeDaoMapper.findAll(); 5、将查询结果集合，包装为pageinfo，获取页相关信息\nPageInfo\u0026lt;GoodType\u0026gt; pageInfo = new PageInfo\u0026lt;\u0026gt;(all); pageInfo的属性 # //当前页 private int pageNum; //每页的数量 private int pageSize; //当前页的数量 private int size; //当前页面第一个元素在数据库中的行号 private int startRow; //当前页面最后一个元素在数据库中的行号 private int endRow; //总记录数 private long total; //总页数 private int pages; //结果集 private List\u0026lt;T\u0026gt; list; //第一页 private int firstPage; //前一页 private int prePage; //是否为第一页 private boolean isFirstPage; //是否为最后一页 private boolean isLastPage; //是否有前一页 private boolean hasPreviousPage; //是否有下一页 private boolean hasNextPage; //导航页码数 private int navigatePages; //所有导航页号 private int[] navigatepageNums; 动态SQL # 动态查询 # if # 语法：\u0026lt;if test=\u0026quot;条件\u0026quot;\u0026gt;执行语句\u0026lt;/if\u0026gt;\n查询商品表goods，进行分页，为确保数据安全，不可以传入null或小于0的数据\n\u0026lt;select id=\u0026#34;findByPage\u0026#34; resultType=\u0026#34;entity.Goods\u0026#34;\u0026gt; select * from goods \u0026lt;if test=\u0026#34;arg0!=null and arg1!=null and arg0\u0026gt;=0 and arg1\u0026gt;=0\u0026#34;\u0026gt; limit #{arg0},#{arg1} \u0026lt;/if\u0026gt; \u0026lt;/select\u0026gt; choose when otherwise # 语法：\n\u0026lt;choose\u0026gt; \u0026lt;when test=\u0026#34;情况1\u0026#34;\u0026gt; 情况1时，执行的语句 \u0026lt;/when\u0026gt; \u0026lt;otherwise\u0026gt; 当其他情况时，执行的语句 \u0026lt;/otherwise\u0026gt; \u0026lt;/choose\u0026gt; 根据方法传入的字符串，如果是“正常”，那么就查询goods表中所有state为1的数据；如果是“失效”，那么就查询goods表中所有state为0的数据；当其他情况下，那么就查询goods表中所有state为null的数据\nwhen或者otherwise需要有choose结合使用，不能独立使用\n\u0026lt;select id=\u0026#34;findByStr\u0026#34; resultType=\u0026#34;entity.Goods\u0026#34;\u0026gt; select * from goods where \u0026lt;choose\u0026gt; \u0026lt;when test=\u0026#34;str==\u0026#39;正常\u0026#39;\u0026#34;\u0026gt; state=1 \u0026lt;/when\u0026gt; \u0026lt;when test=\u0026#34;str==\u0026#39;失效\u0026#39;\u0026#34;\u0026gt; state=0 \u0026lt;/when\u0026gt; \u0026lt;otherwise\u0026gt; state=null \u0026lt;/otherwise\u0026gt; \u0026lt;/choose\u0026gt; \u0026lt;/select\u0026gt; where # 语法：\u0026lt;where\u0026gt;动态语句\u0026lt;/where\u0026gt;\n相当于，SQL中的关键字where，可以避免，where后写多个\u0026lt;if\u0026gt;标签，都不成立，导致SQL语句中出现以where结尾语法错误；也可以避免，如果在多个\u0026lt;if\u0026gt;标签内写where后，多个标签同时成立，导致SQL语句内多个where语法错误\ntrim # prefix：前缀，如果\u0026lt;trim\u0026gt;标签中的存在语句成立的话，那么会添加该前缀\nprefixOverrides：智能去除SQL语句的该前缀（多个前缀使用|分割），达到SQL语法正确\nsuffix：后缀，作用和prefix相似\nsuffixOverrides：后缀，作用和prefixOverrides相似\n\u0026lt;select id=\u0026#34;find1\u0026#34; resultType=\u0026#34;entity.Goods\u0026#34;\u0026gt; select * from goods \u0026lt;trim prefix=\u0026#34;where\u0026#34; prefixOverrides=\u0026#34;and\u0026#34;\u0026gt; \u0026lt;if test=\u0026#34;tid\u0026gt;10\u0026#34;\u0026gt; and tid\u0026gt;#{tid} \u0026lt;/if\u0026gt; \u0026lt;if test=\u0026#34;state==1 or state==0\u0026#34;\u0026gt; and state=#{state} \u0026lt;/if\u0026gt; \u0026lt;/trim\u0026gt; \u0026lt;/select\u0026gt; 动态修改 # set # 语法：\n\u0026lt;update id=\u0026#34;\u0026#34;\u0026gt; \u0026lt;set\u0026gt; \u0026lt;if test=\u0026#34;\u0026#34;\u0026gt; \u0026lt;/if\u0026gt; \u0026lt;/set\u0026gt; \u0026lt;/update\u0026gt; 相当于，SQL中的关键字set，可以动态的去除多余的逗号,，保证SQL语法的正确，也可以使用trim标签替换\n修改商品类型表goodtype，保证传入的GoodType对象中，如果：tname != null，state == 1orstate == 0，remarks != null，才会根据tid进行修改\n\u0026lt;update id=\u0026#34;update\u0026#34;\u0026gt; update goodtype \u0026lt;set\u0026gt; \u0026lt;if test=\u0026#34;tname ! =null\u0026#34;\u0026gt; tname=#{tname}, \u0026lt;/if\u0026gt; \u0026lt;if test=\u0026#34;state == 1 or state == 0\u0026#34;\u0026gt; state=#{state}, \u0026lt;/if\u0026gt; \u0026lt;if test=\u0026#34;remarks != null\u0026#34;\u0026gt; remarks=#{remarks} \u0026lt;/if\u0026gt; \u0026lt;/set\u0026gt; where tid=#{tid} \u0026lt;/update\u0026gt; 批量操作（foreach） # 批量增加 # collection：遍历哪一种集合或数组，如list、array、collection，全小写；对于多参数，也可以写该集合或数组@param定义的名称，或者arg0\nitem：遍历的每个元素起的名字\nseparator：每遍历一次后的分隔符\n\u0026lt;foreach collection=\u0026#34;list\u0026#34; item=\u0026#34;t\u0026#34; separator=\u0026#34;,\u0026#34;\u0026gt; (#{t.属性名1},#{t.属性名2},#{t.属性名3}) \u0026lt;/foreach\u0026gt; 使用List集合批量增加商品类型表goodtype的数据\n\u0026lt;insert id=\u0026#34;insertList\u0026#34;\u0026gt; insert into goodtype(tname,state,remarks) values \u0026lt;foreach collection=\u0026#34;list\u0026#34; item=\u0026#34;t\u0026#34; separator=\u0026#34;,\u0026#34;\u0026gt; (#{t.tname},#{t.state},#{t.remarks}) \u0026lt;/foreach\u0026gt; \u0026lt;/insert\u0026gt; 批量修改 # open：遍历开始前的符号\nclose：遍历结束后的符号\n修改商品类型表goodtype中，给定数组中的tid的数据，state值为给定值\n\u0026lt;update id=\u0026#34;updateStateByTids\u0026#34;\u0026gt; update goodtype set state=#{arg1} where tid in \u0026lt;foreach collection=\u0026#34;arg0\u0026#34; open=\u0026#34;(\u0026#34; close=\u0026#34;)\u0026#34; separator=\u0026#34;,\u0026#34; item=\u0026#34;tid\u0026#34;\u0026gt; #{tid} \u0026lt;/foreach\u0026gt; \u0026lt;/update\u0026gt; ","date":"2024-12-09","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/79c2bc57/96e2494a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e添加、删除、修改 \n    \u003cdiv id=\"添加删除修改\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%b7%bb%e5%8a%a0%e5%88%a0%e9%99%a4%e4%bf%ae%e6%94%b9\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//GoodTypeDaoMapper接口\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//1、增加\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eaddOne\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eGoodType\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003egoodType\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//2、删除\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eremoveById\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etid\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//3、修改\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eupdateNameById\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etname\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etid\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!--GoodTypeDaoMapper.xml映射文件--\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!--1、增加--\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;insert\u003c/span\u003e \u003cspan class=\"na\"\u003eid=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;addOne\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    insert into goodtype (tid,tname,state,remarks) values (#{tid},#{tname},#{state},#{remarks})\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/insert\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!--2、删除--\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;delete\u003c/span\u003e \u003cspan class=\"na\"\u003eid=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;addOne\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    delete from goodtype where tid=#{tid}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/delete\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!--3、修改--\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;update\u003c/span\u003e \u003cspan class=\"na\"\u003eid=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;updateNameById\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    update goodtype set tname=#{arg0} where tid=#{arg1}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/update\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e添加数据并获取主键值 \n    \u003cdiv id=\"添加数据并获取主键值\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%b7%bb%e5%8a%a0%e6%95%b0%e6%8d%ae%e5%b9%b6%e8%8e%b7%e5%8f%96%e4%b8%bb%e9%94%ae%e5%80%bc\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003e方式一 \n    \u003cdiv id=\"方式一\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e5%bc%8f%e4%b8%80\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;insert\u003c/span\u003e \u003cspan class=\"na\"\u003eid=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;add\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"na\"\u003euseGenneratedKeys=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;true\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"na\"\u003ekeyColumn=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;tid\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"na\"\u003ekeyProperty=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;tid\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    insert into goodtype (tname,state,remarks) values (#{tname},#{state},#{remarks})\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/insert\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e方式二 \n    \u003cdiv id=\"方式二\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e5%bc%8f%e4%ba%8c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;insert\u003c/span\u003e \u003cspan class=\"na\"\u003eid=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;add\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;selectKey\u003c/span\u003e \u003cspan class=\"na\"\u003ekeyColumn=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;tid\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"na\"\u003ekeyProperty=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;tid\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"na\"\u003eresultType=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;int\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"na\"\u003eorder=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;AFTER\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        select last_insert_id()\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;/selectKey\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    insert into goodtype (tname,state,remarks) values (#{tname},#{state},#{remarks})\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/insert\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e查询 \n    \u003cdiv id=\"查询\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9f%a5%e8%af%a2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e简单查询 \n    \u003cdiv id=\"简单查询\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%ae%80%e5%8d%95%e6%9f%a5%e8%af%a2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//GoodTypeDaoMapper接口\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eGoodType\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003efindAll\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!--UserDao.xml:--\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;select\u003c/span\u003e \u003cspan class=\"na\"\u003eid=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;findAll\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eresultType=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;entity.GoodType\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    select * from goodtype\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/select\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e多表查询 \n    \u003cdiv id=\"多表查询\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%a4%9a%e8%a1%a8%e6%9f%a5%e8%af%a2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003e一对一 \n    \u003cdiv id=\"一对一\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%80%e5%af%b9%e4%b8%80\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cp\u003e根据商品id查询出商品Goods信息，以及对应的商品类型GoodType信息\u003c/p\u003e","title":"2、对数据库的操作","type":"posts"},{"content":" 脚本组件 # 脚本是使用 Unity 开发的所有应用程序中必不可少的组成部分。大多数应用程序都需要脚本来响应玩家的输入并安排游戏过程中应发生的事件。除此之外，脚本可用于创建图形效果，控制对象的物理行为，甚至为游戏中的角色实现自定义的 AI 系统。\n我们可以在Project视图右键进行脚本创建，除了C#脚本，还有两类脚本；Testing用来做单元测试，Playables是TimeLine引入的新概念。默认C#脚本模板如下：\nusing System.Collections; using System.Collections.Generic; using UnityEngine; public class Move : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } } 注意：\n继承UnityEngine.MonoBehavior的脚本 创建脚本继承MonoBehavior，才能挂载在GameObject上。 继承了MonoBehavior的脚本不能new对象，不能写构造函数 不继承UnityEngine.MonoBehavior的脚本 不能挂载在GameObject上 需要new 一般是单例模式的类（用于管理模块）或者数据结构类（用于存储数据） 不用保留默认出现的函数 生命周期 # 只有继承了MonoBehavior的脚本才可挂载到游戏对象上，这样的脚本才有生命周期。脚本的生命周期与游戏对象的状态密切相关，但脚本的生命周期函数会按照固定顺序执行，即该脚本从创建到销毁的各个阶段，这就是我们说的Unity的生命周期。\n游戏对象的生命周期主要有这几个状态：创建、激活、禁用和销毁。游戏对象的状态直接影响挂载在其上的脚本的生命周期和函数调用。\n创建：游戏对象被创建时，它的所有组件（包括脚本）都会被初始化，但这时脚本的生命周期方法尚未被调用。 激活：当游戏对象被激活时，脚本的生命周期方法开始生效。如果游戏对象在场景中被激活或设置为启用状态，那么挂载在该对象上的所有脚本附加的脚本会按顺序调用OnEnable()、Start()等生命周期函数。 禁用：当游戏对象被禁用时，游戏对象及所有挂载的脚本会停止调用生命周期函数，直到它再次被启用。 销毁：当游戏对象被销毁时，它和所有附加的组件（包括脚本）都会被清理，相关的生命周期方法会被调用来处理清理工作。 主要有四个阶段：初始化阶段-\u0026gt;更新阶段-\u0026gt;渲染阶段-\u0026gt;销毁阶段，下面的讲述的顺序也是生命周期函数的执行顺序。\n初始化阶段 # Awake() # 在脚本实例被加载时调用，用于初始化变量或设置对象的初始状态。这是在游戏对象启用之前调用的。注意：在脚本整个生命周期内它仅被调用一次，且每个游戏物体上的Awke以随机的顺序被调用，Awake总是在Start之前被调用。\nusing UnityEngine; public class Test01 : MonoBehaviour { private void Awake() { //在控制台打印 Debug.Log(\u0026#34;Awake()函数被调用！\u0026#34;); } } OnEnable() # 当脚本或对象被激活时调用，用于处理对象激活时的逻辑。\nusing UnityEngine; public class Test01 : MonoBehaviour { private void OnEnable() { Debug.Log(\u0026#34;OnEnable()函数被调用！\u0026#34;); } } Start() # 在所有Awake()方法调用完成后并且所有游戏对象已启用时调用，用于脚本的初始设置和游戏逻辑初始化。在第一个Update发生之前调用一次。\nusing UnityEngine; public class Test01 : MonoBehaviour { private void Start() { Debug.Log(\u0026#34;Start()函数被调用!\u0026#34;); } } 更新阶段 # FixedUpdate() # 每固定时间间隔调用一次，通常是0.02s，用于处理物理计算。如处理力，给游戏对象加上刚体（Rigidbody）组件后，通过刚体给物体加一个作用力时，必须在FixedUpdate里的固定帧执行，而不是Update中的帧，两者帧长不同，每帧应用一个力到刚体上。如果没有刚体（Rigidbody）组件，对象将不会受到物理引擎的影响，也就无法参与物理模拟和力的应用，所以需要给游戏对象添加刚体，但没加刚体组件的物体可以通过碰撞检测（Collider）来触发事件。\nusing UnityEngine; public class FixedUpdateTest : MonoBehaviour { public new Rigidbody rigidbody; private void FixedUpdate() { rigidbody.AddForce(Vector3.up); } } Update() # 每帧调用一次，用于常规的游戏逻辑处理，如输入检测和对象移动。同理，新建一个三维物体，挂载脚本进行测试。\nusing UnityEngine; public class UpdateTest : MonoBehaviour { //Unity中脚本的成员访问权限不写，默认是private void Update() { transform.position += new Vector3(Time.deltaTime * 1.2f, 0,0); } } LateUpdate() # 在所有Update()方法之后调用，用于在所有对象更新后处理逻辑，如当物体在Update里移动时，跟随物体的相机可以在此处实现。\nusing UnityEngine; public class UpdateTest : MonoBehaviour { //Unity中脚本的成员访问权限不写，默认是private void Update() { transform.position += new Vector3(Time.deltaTime * 1.2f, 0,0); } private void LateUpdate() { Camera.main.transform.position = transform.position+ new Vector3(0, 0.7f, -1.5f); } } 渲染阶段 # OnGUI() # 主要用于绘制即时用户界面元素和调试信息，适用于创建和管理GUI界面，如绘制一个按钮。也可以用于自定义EditorWindow或Editor类的GUI绘制，在方法中实现自定义的界面布局和绘制逻辑，此时该方法会在Unity编辑器的GUI渲染过程中调用。\n销毁阶段 # OnApplicationQuit() # 当应用程序退出时调用，用于清理代码、保存数据或执行其他在退出时需要完成的操作。\nusing UnityEngine; public class Test01 : MonoBehaviour { private void OnApplicationQuit() { Debug.Log(\u0026#34;OnApplicationQuit()函数被调用!\u0026#34;); } } OnDisable() # 当脚本或游戏对象被禁用时调用，用于清理操作或停止处理。\nusing UnityEngine; public class Test01 : MonoBehaviour { private void OnDisable() { Debug.Log(\u0026#34;OnDisable()函数被调用!\u0026#34;); } } OnDestroy() # 当游戏对象被销毁时调用，用于释放资源或执行清理操作。\nusing UnityEngine; public class Test01 : MonoBehaviour { private void OnDestroy() { Debug.Log(\u0026#34;OnDestroy()函数被调用!\u0026#34;); } } 脚本执行顺序 # 对于同一个物体上面存在多个脚本组件的时候，如果需要限制脚本的执行顺序，可以采用如下方法\n点击脚本，在检查器中点击Execution Order\n点击+将需要管理执行顺序的脚本添加进来，然后修改数值，数值小的先执行，数值大的后执行，注意不要小于系统默认的脚本。\nInspector检查器窗口可编辑的变量 # Inspector显示的可编辑内容就是脚本的成员变量，私有和保护无法显示编辑，只有声明public的变量才可以在检查器中编辑。\n例如，脚本如下，其中的变量myColor为public的，所以就可以在编辑器中直接编辑\nusing System.Collections; using System.Collections.Generic; using UnityEngine; public class Move : MonoBehaviour { public string myColor; // Start is called before the first frame update void Start() { Debug.Log(\u0026#34;Start ...\u0026#34;); } // Update is called once per frame void Update() { } } 常用特性 # 添加特性原理： Unity通过反射得到类的信息，然后在Inspector窗口中显示字段信息 Unity内部通过反射获取字段的特征，当具有一些特殊特性时，便会做相应的处理 [HideInInspector]：如果变量上方添加此特性，即使是public的也无法在检查器中编辑。\n[SerializeField]：此特性可以将变量序列化，就是把一个对象保存到一个文件或数据库字段中\n其他的辅助特性如下：\n1、分组说明特性 Header 为成员分组 [Header(\u0026#34;基础属性\u0026#34;)] public int age; public bool sex; [Header(\u0026#34;战斗属性\u0026#34;)] public int atk; public int def; 2、悬停注释 Tooltip 为变量添加说明 [Tooltip(\u0026#34;闪避\u0026#34;)] public int miss; 3、间隔特性 Space 让两个字段之间出现间隔 [Space()] public int crit; 4、修饰数值的滑条范围 Range [Range(0,10)] public float luck; 5、多行显示字符串，默认3行 Multiline [Multiline()] public string tips; 6、滚动条显示字符串，默认三行 TextArea [TextArea()] public string myLife; 7、为变量添加快捷方法 ContextMenuItem 参数1：显示按钮名 参数2；方法名，不能有参数 [ContextMenuItem(\u0026#34;重置钱\u0026#34;,\u0026#34;Money\u0026#34;)] public int money; private void Money() { money = 10; } 8、为方法添加特性能够在Inspector中执行 [ContextMenu(\u0026#34;测试\u0026#34;)] private void TestFunc() { print(\u0026#34;测试方法\u0026#34;); money = 20; } 支持的类型 # 大部分类型都能显示编辑，但是字典Dictionary、自定义类型变量不能显示\npublic int[] array; public List\u0026lt;int\u0026gt; list; public E_TestEnum type; public GameObject gameObj; MonoBehaviour提供的API # 基本信息 # using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class Move : MonoBehaviour { // Start is called before the first frame update void Start() { // 获取脚本挂载的物体对象 GameObject g = this.gameObject; // 获取脚本挂载的物体名称 Debug.Log(gameObject.name); // 获取挂载物体的位置，也可以直接使用transform.position Debug.Log(gameObject.transform.position); // 获取挂载物体的欧拉角 Debug.Log(transform.eulerAngles); // 获取挂载物体的缩放大小 Debug.Log(transform.lossyScale); // 获取当前脚本是否激活 Debug.Log(enabled); } // Update is called once per frame void Update() { } } 如果需要获取其他物体的属性，只需要拿到对应的GameObject。\n获取组件 # 获取GameObject上挂载的其他组件\nusing System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class Move : MonoBehaviour { // Start is called before the first frame update void Start() { // 通过组件名称获取 MeshRenderer m1 = GetComponent(\u0026#34;MeshRenderer\u0026#34;) as MeshRenderer; // 通过组件类型获取 MeshRenderer m2 = GetComponent(typeof(MeshRenderer)) as MeshRenderer; // 通过泛型获取 MeshRenderer m3 = GetComponent\u0026lt;MeshRenderer\u0026gt;(); } // Update is called once per frame void Update() { } } 如果GameObject上存在多个同类型的组件，可以使用如下方式获取\nusing System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class Move : MonoBehaviour { // Start is called before the first frame update void Start() { // 获取多个组件，并放入数组 MeshRenderer[] arr = GetComponents\u0026lt;MeshRenderer\u0026gt;(); // 获取多个组件，并放入集合 List\u0026lt;MeshRenderer\u0026gt; list = new List\u0026lt;MeshRenderer\u0026gt;(); GetComponents\u0026lt;MeshRenderer\u0026gt;(list); } // Update is called once per frame void Update() { } } 获取子对象挂载的组件，默认也会找自己是否挂载该组件\nusing System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class Move : MonoBehaviour { // Start is called before the first frame update void Start() { // 获取单个组件 MeshRenderer m = GetComponentInChildren\u0026lt;MeshRenderer\u0026gt;(); // 获取多个组件，并放入数组 MeshRenderer[] arr = GetComponentsInChildren\u0026lt;MeshRenderer\u0026gt;(); // 获取多个组件，并放入集合 List\u0026lt;MeshRenderer\u0026gt; list = new List\u0026lt;MeshRenderer\u0026gt;(); GetComponentsInChildren\u0026lt;MeshRenderer\u0026gt;(true,list); } // Update is called once per frame void Update() { } } 获取父对象挂载的组件，默认也会找自己是否挂载该组件\nusing System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class Move : MonoBehaviour { // Start is called before the first frame update void Start() { // 获取单个组件 MeshRenderer m = GetComponentInParent\u0026lt;MeshRenderer\u0026gt;(); // 获取多个组件，并放入数组 MeshRenderer[] arr = GetComponentsInParent\u0026lt;MeshRenderer\u0026gt;(); // 获取多个组件，并放入集合 List\u0026lt;MeshRenderer\u0026gt; list = new List\u0026lt;MeshRenderer\u0026gt;(); GetComponentsInParent\u0026lt;MeshRenderer\u0026gt;(true,list); } // Update is called once per frame void Update() { } } 如果不确定组件是否存在，可以尝试获取组件\nif (TryGetComponent\u0026lt;MeshRenderer\u0026gt;(out mr)) { } 常用API # Debug # 在Unity中，想要控制台输出就不能使用Console.WriteLine了，Unity提供了Debug，包含了三个方法：Log、LogWarning、LogError\n初次之外，还提供了在场景编辑器中画线的方法：Debug.DrawLine()，例如\nDebug.DrawLine(new Vector3(0,0,0),new Vector3(5,5,5),Color.yellow); GameObject # 游戏物体属性 this.gameObject：获取当前脚本挂载的游戏物体 gameObject.name：获取游戏物体的名称 gameObject.tag：获取游戏物体的标签 gameObject.layer：获取游戏物体的图层 gameObject.activeInHierarchy：获取游戏物体是否真正激活，例如父物体未激活，那么子物体也不会激活 gameObject.activeSelf ：获取游戏物体自己是否是激活状态 gameObject.transform：获取游戏物体的transform组件 获取游戏物体，除了可以使用public的GameObject类型变量在编辑器内拖拽，也可以使用API获取 GameObject.Find()：通过游戏物体的名称获取游戏物体 GameObject.FindWithTag()：通过标签获取游戏物体 预制体，类型也是GameObject，也可以通过声明public的变量然后在编辑器内拖拽获取 GameObject g = Instantiate(prefab)：实例化预制体 GameObject g = Instantiate(prefab,transform)：实例化预制体，并将父物体设置为transform组件所属的游戏物体 GameObject g = Instantiate(prefab,Vector3.zero,Quaternion.identity)：实例化预制体，并将位置设置为世界坐标系原点，并且不旋转 销毁游戏物体 Destory(gameObject)：销毁游戏物体gameObject Transform # 脚本中直接通过transform即可获取挂载物体的Transform组件\n位置 # transform.position：获取游戏物体的位置，相对于世界坐标 transform.localPosition：获取游戏物体的位置，相对于父物体 旋转 # transform.rotation：获取游戏物体的旋转，相对于世界坐标，值类型为四元数Quaternion transform.localRotation：获取游戏物体的旋转，相对于父物体，值类型为四元数Quaternion transform.eulerAngles：获取游戏物体的旋转，相对于世界坐标，值类型为欧拉角的Vector3 transform.localEulerAngles：获取游戏物体的旋转，相对于父物体，值类型为欧拉角的Vector3 表示旋转的有欧拉角和四元数，其中欧拉角就是使用Vector3的xyz三个值表示在不同坐标轴旋转的角度（0 - 360）。\n四元数使用Quaternion来表示，有四个属性：x、y、z、w\nQuaternion.Euler()：将一个欧拉角的Vector3转换为四元数 quaternion.eulerAngles()：将一个四元数转换为欧拉角 Quaternion.identity：创建一个没有旋转的四元数，四属性均为0 Quaternion.LookRotaion()：将物体旋转看向一个物体 缩放 # transform.localScale：获取游戏物体的缩放 向量 # transform.forward：获取游戏物体前方（面对方向）的向量，为游戏物体+z方向 transform.right：获取游戏物体前方（面对方向）的向量，为游戏物体+x方向 transform.up：获取游戏物体前方（面对方向）的向量，为游戏物体+y方向 方法 # transform.LookAt()：让游戏物体时刻看向一个位置，也就是游戏物体的+z方向始终对着这个点 transform.Rotate(Vector3.up,1);：让游戏物体旋转，按照Vector3.up即y轴，旋转1度 transform.RotateAround(otherGameObject.transform.position,Vector3.up,3);：让游戏物体绕着某个点，或另一个游戏物体旋转 transform.Translate(Vector3.forward * 0.01f);：让游戏物体向前方移动0.01的距离 父子关系 # 在Unity中，游戏物体的父子级关系是有Transform组件控制的。\ntransform.parent：获取父物体的Transform组件 transform.parent.gameObject：获取父物体 transform.childCount：获取子物体的个数 transform.DetachChildren：解除与所有子物体的父子关系 获取子物体 transform.Find(\u0026quot;\u0026quot;)：通过子物体名称查找 transform.GetChild(0)：根据子物体索引获取 transform.setParent()：设置父物体 时间 # Unity封装了Time类\nTime.time：游戏开始到现在的时间，单位秒 Time.timeScale：时间缩放值，默认是1.0 Time.fixedDeltaTime：方法FixedUpdate()调用的固定时间间隔 Time.deltaTime：上一帧到这一帧所用的时间 Application类 # Application.dataPath：获取游戏数据目录，这个目录是只读的，而且游戏发布出去后，目录中内容会被加密 Application.persistentDataPath：持久化文件路径，可读写 Application.streamingAssetsPath：和dataPath类似，只读，但是不会被加密 Application.temporaryCachePath：临时文件目录 Application.runInBackground：控制游戏是否在后台运行 Application.OpenURL()：使用默认浏览器打开URL Application.Quit()：退出游戏 Scene、SceneManager # 新增场景可以在project窗口中右键进行创建，但是创建的场景默认是不会被游戏最终打包的，需要在（文件 - 生成设置）中将场景添加进来\nSceneManager # 场景管理类，使用需要导入命名空间using UnityEngine.SceneManagement;\nSceneManager.LoadScene()：同步加载场景 第一个参数为场景名称或索引 第二个参数为LoadSceneMode的枚举 Single：替换当前场景 Additive：添加场景和当前场景并存 AsyncOperation ao = SceneManager.LoadSceneAsync()：异步加载场景，参数与LoadScene相同 SceneManager.GetActiveScene()：获取当前场景Scene实例 SceneManager.sceneCount：当前已加载场景的数量 SceneManager.CreateScene(\u0026quot;newScene\u0026quot;)：创建一个名为newScene的新场景 SceneManager.UnloadSceneAsync(1)：卸载索引为1的场景 异步加载场景并获取进度 # using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; public class SceneTest : MonoBehaviour { AsyncOperation asyncOperation; // Start is called before the first frame update void Start() { // 开启一个携程异步加载场景 StartCoroutine(loadScene()); } // 异步加载场景 IEnumerator loadScene(){ asyncOperation = SceneManager.LoadSceneAsync(\u0026#34;MyScene\u0026#34;); yield return asyncOperation; } // Update is called once per frame void Update() { // 获取加载的进度（0 - 0.9） Debug.Log(asyncOperation.progress); } } Scene # 场景类\nscene.name：场景的名称 scene.buildIndex：场景的索引 scene.isLoaded：场景是否已经加载 scene.path：场景的路径 ","date":"2024-09-18","externalUrl":null,"permalink":"/posts/69064821/af96a2fa/134f5713/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e脚本组件 \n    \u003cdiv id=\"脚本组件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%84%9a%e6%9c%ac%e7%bb%84%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e脚本是使用 Unity 开发的所有应用程序中必不可少的组成部分。大多数应用程序都需要脚本来响应玩家的输入并安排游戏过程中应发生的事件。除此之外，脚本可用于创建图形效果，控制对象的物理行为，甚至为游戏中的角色实现自定义的 AI 系统。\u003c/p\u003e","title":"2、脚本组件","type":"posts"},{"content":" 变量 # 在 C# 中，变量是用于存储和表示数据的标识符，在声明变量时，您需要指定变量的类型，并且可以选择性地为其分配一个初始值。\n在 C# 中，每个变量都有一个特定的类型，类型决定了变量的内存大小和布局，范围内的值可以存储在内存中，可以对变量进行一系列操作。\n注意：变量必须赋值以后才可以使用！\nint a = 12; int b; b = 20; int c,d; c = 15; d = 30; Console.WriteLine(a); // 12 Console.WriteLine(b); // 20 Console.WriteLine(c); // 15 Console.WriteLine(d); // 30 作用域与可见性 # 变量作用域与Java相同。\n一个 访问修饰符 定义了一个类成员的范围和可见性。C# 支持的访问修饰符如下所示：\npublic：所有对象都可以访问； private：对象本身在对象内部可以访问； protected：只有该类对象及其子类对象可以访问 internal：同一个程序集的对象可以访问； protected internal：访问限于当前程序集或派生自包含类的类型。 如果没有指定访问修饰符，则使用类成员的默认访问修饰符，即为 private。\n数据类型 # 在 C# 中，变量分为以下几种类型：\n值类型（Value types） 引用类型（Reference types） 指针类型（Pointer types） 值类型（Value types） # 值类型变量可以直接分配给一个值。它们是从类 System.ValueType 中派生的。\n值类型直接包含数据。比如 int、char、float，它们分别存储数字、字符、浮点数。\n类型 描述 范围 默认值 bool 布尔值 True 或 False False byte 8 位无符号整数 0 到 255 0 char 16 位 Unicode 字符 U +0000 到 U +ffff \u0026lsquo;\\0\u0026rsquo; decimal 128 位精确的十进制值，28-29 有效位数 (-7.9 x 1028 到 7.9 x 1028) / 100 到 28 0.0M double 64 位双精度浮点型 (+/-)5.0 x 10-324 到 (+/-)1.7 x 10308 0.0D float 32 位单精度浮点型 -3.4 x 1038 到 + 3.4 x 1038 0.0F int 32 位有符号整数类型 -2,147,483,648 到 2,147,483,647 0 long 64 位有符号整数类型 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 0L sbyte 8 位有符号整数类型 -128 到 127 0 short 16 位有符号整数类型 -32,768 到 32,767 0 uint 32 位无符号整数类型 0 到 4,294,967,295 0 ulong 64 位无符号整数类型 0 到 18,446,744,073,709,551,615 0 ushort 16 位无符号整数类型 0 到 65,535 0 如需得到一个类型或一个变量在特定平台上的准确尺寸，可以使用 sizeof 方法。\nConsole.WriteLine(sizeof(int)); // 4 引用类型（Reference types） # 引用类型不包含存储在变量中的实际数据，但它们包含对变量的引用。\n换句话说，它们指的是一个内存位置。使用多个变量时，引用类型可以指向一个内存位置。如果内存位置的数据是由一个变量改变的，其他变量会自动反映这种值的变化。内置的 引用类型有：object、dynamic 和 string。\nobject # object是 C# 通用类型系统（Common Type System - CTS）中所有数据类型的终极基类。Object 是 System.Object 类的别名。所以对象（Object）类型可以被分配任何其他类型（值类型、引用类型、预定义类型或用户自定义类型）的值。但是，在分配值之前，需要先进行类型转换。\n当一个值类型转换为对象类型时，则被称为 装箱；另一方面，当一个对象类型转换为值类型时，则被称为 拆箱。\nobject a = 10; object b = \u0026#34;hello\u0026#34;; Console.WriteLine(a); // 10 Console.WriteLine(b); // hello dynamic # C# 4.0引入了动态类型 (dynamic)，它允许在运行时推断变量的类型。这在一些特殊情况下很有用，但通常最好使用静态类型以获得更好的性能和编译时类型检查。\ndynamic b = \u0026#34;hello\u0026#34;; Console.WriteLine(b); // hello String # 字符串（String）类型 允许您给变量分配任何字符串值。字符串（String）类型是 System.String 类的别名。它是从对象（Object）类型派生的。字符串（String）类型的值可以通过两种形式进行分配：引号和 @引号。\n字符串的前面可以加 @（称作\u0026quot;逐字字符串\u0026quot;）将转义字符（\\）当作普通字符对待。\nString a = \u0026#34;你好\\\\\u0026#34;; String b = @\u0026#34;你好\\\\\u0026#34;; Console.WriteLine(a); // 你好\\ Console.WriteLine(b); // 你好\\\\ 指针类型（Pointer types） # 指针类型变量存储另一种类型的内存地址。C# 中的指针与 C 或 C++ 中的指针有相同的功能。\n声明指针类型的语法：\ntype* identifier; 例如：\nchar* cptr; int* iptr; 数据类型转换 # 数据类型转换与Java相同，分为两种：隐式类型转换和显式类型转换\nbyte a = 10; int b = a; double c = 2.13; int d = (int)c; Convert类 # 序号 方法 \u0026amp; 描述 1 ToBoolean 如果可能的话，把类型转换为布尔型。 2 ToByte 把类型转换为字节类型。 3 ToChar 如果可能的话，把类型转换为单个 Unicode 字符类型。 4 ToDateTime 把类型（整数或字符串类型）转换为 日期-时间 结构。 5 ToDecimal 把浮点型或整数类型转换为十进制类型。 6 ToDouble 把类型转换为双精度浮点型。 7 ToInt16 把类型转换为 16 位整数类型。 8 ToInt32 把类型转换为 32 位整数类型。 9 ToInt64 把类型转换为 64 位整数类型。 10 ToSbyte 把类型转换为有符号字节类型。 11 ToSingle 把类型转换为小浮点数类型。 12 ToString 把类型转换为字符串类型。 13 ToType 把类型转换为指定类型。 14 ToUInt16 把类型转换为 16 位无符号整数类型。 15 ToUInt32 把类型转换为 32 位无符号整数类型。 16 ToUInt64 把类型转换为 64 位无符号整数类型。 这些方法都定义在 System.Convert 类中，使用时需要包含 System 命名空间。它们提供了一种安全的方式来执行类型转换，因为它们可以处理 null值，并且会抛出异常，如果转换不可能进行。\nstring str = \u0026#34;123\u0026#34;; int number = Convert.ToInt32(str); // 转换成功，number为123 Parse 方法与 TryParse 方法 # Parse\nstring str = \u0026#34;123.45\u0026#34;; double d = double.Parse(str); TryParse\nstring str = \u0026#34;123.45\u0026#34;; double d; bool success = double.TryParse(str, out d); if (success) { Console.WriteLine(\u0026#34;转换成功: \u0026#34; + d); } else { Console.WriteLine(\u0026#34;转换失败\u0026#34;); } 可空类型 # ? 单问号用于对 int、double、bool 等无法直接赋值为 null 的数据类型进行 null 的赋值，意思是这个数据类型是 Nullable 类型的。\nint? i = 3; // 等同于 Nullable\u0026lt;int\u0026gt; i = new Nullable\u0026lt;int\u0026gt;(3); int i; //默认值0 int? ii; //默认值null Null合并运算符 # Null 合并运算符用于定义可空类型和引用类型的默认值。\nusing System; namespace CalculatorApplication { class NullablesAtShow { static void Main(string[] args) { double? num1 = null; double? num2 = 3.14157; double num3; num3 = num1 ?? 5.34; // num1 如果为空值则返回 5.34 Console.WriteLine(\u0026#34;num3 的值： {0}\u0026#34;, num3); num3 = num2 ?? 5.34; Console.WriteLine(\u0026#34;num3 的值： {0}\u0026#34;, num3); Console.ReadLine(); } } } ","date":"2024-09-11","externalUrl":null,"permalink":"/posts/5ab2821f/e305ea76/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e变量 \n    \u003cdiv id=\"变量\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8f%98%e9%87%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在 C# 中，变量是用于存储和表示数据的标识符，在声明变量时，您需要指定变量的类型，并且可以选择性地为其分配一个初始值。\u003c/p\u003e","title":"2、变量与数据类型","type":"posts"},{"content":" TypeScript 是 JS 的超集，TS 提供了 JS 的所有功能，并且额外的增加了：类型系统。\n所有的 JS 代码都是 TS 代码。 JS 有类型（比如，number/string 等），但是 JS 不会检查变量的类型是否发生变化。而 TS 会检查。 TypeScript 类型系统的主要优势：可以显示标记出代码中的意外行为，从而降低了发生错误的可能性。\n类型注解 # let age: number = 18; 说明：代码中的 number 就是类型注解。 作用：为变量添加类型约束。比如，上述代码中，约定变量 age 的类型为 number（数值类型）。 解释：约定了什么类型，就只能给变量赋值该类型的值，否则，就会报错。 基础类型 # 可以将 TS 中的常用基础类型细分为两类：\nJS 已有类型 原始类型：number、string、boolean、null、undefined、symbol。 对象类型：object（包括，数组、对象、函数等对象）。 TS 新增类型 联合类型、自定义类型（类型别名）、接口、元组、字面量类型、枚举、void、any 等。 原始类型 # 原始类型：number、string、boolean、null、undefined、symbol。 特点：这些类型，完全按照 JS 中类型的名称来书写。 let age: number = 18; let msg: string = \u0026#34;Hello World\u0026#34;; let isSuccess: boolean = false; 数组类型 # let names: string[] = [\u0026#39;tom\u0026#39;,\u0026#39;lucy\u0026#39;,\u0026#39;james\u0026#39;] let ages: Array[number] = [18,20,35] let arr = new Array\u0026lt;string\u0026gt;(5) 推荐string[]的写法\n联合类型 # 数组中既有 number 类型，又有 string 类型；或者一个变量既可以赋string值，又可以赋number值。\n|（竖线）在 TS 中叫做联合类型（由两个或多个其他类型组成的类型，表示可以是这些类型中的任意一种）。\nlet msgs:(string | number)[] = [18,\u0026#39;tom\u0026#39;,25,30] console.log(msgs) let msg:(string | number) = 18 console.log(msg) msg = \u0026#39;hello\u0026#39; console.log(msg) 类型别名 # 使用 type 关键字来创建类型别名。 类型别名（比如，此处的 MyType1 和 MyType2），可以是任意合法的变量名称。 创建类型别名后，直接使用该类型别名作为变量的类型注解即可。 type MyType1 = number type MyType2 = number | string let msg1: MyType1 = 18 console.log(msg1) let msg2: MyType2 = 20 console.log(msg2) msg2 = \u0026#39;hello\u0026#39; console.log(msg2) 函数类型 # function myFunc(num1: number,num2: number): number { return num1 + num2; } console.log(myFunc(1,2)) lambda表达式形式\nlet myFunc = (num1: number,num2: number): number =\u0026gt; { return num1 + num2 } console.log(myFunc(1,2)) 无返回值，void可写可不写\nfunction myFunc(msg: string){ console.log(msg) } myFunc(\u0026#39;Hello World\u0026#39;) function myFunc(msg: string): void{ console.log(msg) } myFunc(\u0026#39;Hello World\u0026#39;) 可选参数，可选参数只能出现在参数列表的最后。\nfunction myFunc(msg: string,info1?: string,info2?: string){ console.log(msg,info1,info2) } myFunc(\u0026#39;Hello\u0026#39;) // Hello undefined undefined myFunc(\u0026#39;Hello\u0026#39;,\u0026#39;Tom\u0026#39;) // Hello Tom undefined myFunc(\u0026#39;Hello\u0026#39;,\u0026#39;Tom\u0026#39;,\u0026#34;Lucy\u0026#34;) // Hello Tom Lucy 对象类型 # 直接使用 {} 来描述对象结构。属性采用属性名: 类型的形式；方法采用方法名(): 返回值类型的形式。 如果方法有参数，就在方法名后面的小括号中指定参数类型（比如：greet(name: string): void）。 在一行代码中指定对象的多个属性类型时，使用 ;（分号）来分隔。 如果一行代码只指定一个属性类型（通过换行来分隔多个属性类型），可以去掉 ;（分号）。 方法的类型也可以使用箭头函数形式（比如：{ sayHi: () =\u0026gt; void }）。 // 声明对象类型 type Person = { name: string age: number info(): void } // 类型赋值 var tom: Person = { name: \u0026#39;tom\u0026#39;, age: 18, info: function() { console.log(\u0026#39;my name is\u0026#39;,this.name) } } tom.info() 可选属性的语法与函数可选参数的语法一致，都使用 ? （问号）来表示。\ntype Person = { name: string age?: number info(): void } var tom: Person = { name: \u0026#39;tom\u0026#39;, info: function() { console.log(\u0026#39;my name is\u0026#39;,this.name) } } tom.info() 接口 # 当一个对象类型被多次使用时，一般会使用 接口 （ interface ）来描述对象的类型，达到复用的目的。接口中的属性同样也可以声明为可选。\ninterface Person { name: string age?: number info(): void } var tom: Person = { name: \u0026#39;tom\u0026#39;, info: function() { console.log(\u0026#39;my name is\u0026#39;,this.name) } } tom.info() 使用 interface 关键字来声明接口。\n接口名称（比如，此处的 IPerson），可以是任意合法的变量名称。\n声明接口后，直接使用接口名称作为变量的类型。\n因为每一行只有一个属性类型，因此，属性类型后没有 ;（分号）。\ninterface（接口）和 type（类型别名）的对比：\n相同点： 都可以给对象指定类型。 不同点： 接口，只能为对象指定类型。 类型别名，不仅可以为对象指定类型，实际上可以为任意类型指定别名。 接口继承 # 如果两个接口之间有相同的属性或方法，可以将公共的属性或方法抽离出来，通过继承来实现复用 。\ninterface Animal{ color: string name: string } interface Dog extends Animal{ age: number } var wangcai: Dog = { name: \u0026#39;wangcai\u0026#39;, color: \u0026#39;red\u0026#39;, age: 18 } console.log(wangcai) 可变key数量的接口 # interface demo { [name: string]: string } 这个就类似于map\n元组 # 元组类型是另一种类型的数组，它确切地知道包含多少个元素，以及特定索引对应的类型。例如坐标\nvar point: [number,number] = [189,210] console.log(point) 类型推论 # 在 TS 中，某些没有明确指出类型的地方，TS 的 类型推论机制会帮助提供类型 。\n字面量类型 # 变量 msg1 的类型为：string。 因为它的值是一个string类型的任意变量 变量 msg2 的类型为：\u0026lsquo;hello\u0026rsquo;。 因为它的值是一个常量，不可以更改，所以类型是hello 我们可以利用这一特性，搭配联合类型一起使用来限制方法的值\nfunction move(to: \u0026#39;top\u0026#39;|\u0026#39;bottom\u0026#39;|\u0026#39;right\u0026#39;|\u0026#39;left\u0026#39;){ console.log(to) } move(\u0026#39;top\u0026#39;) 高级类型 # Class类 # class Person{ // 属性 name: string age = 18 high: number = 170 // 构造函数 constructor(name: string,age: number,high: number){ this.name = name this.age = age this.high = high } } var p = new Person(\u0026#39;tom\u0026#39;,18,180) console.log(p) 继承 # class Animal{ color: string constructor(color: string){ this.color = color } } class Dog extends Animal{ name: string constructor(color: string,name: string){ super(color) this.name = name; } } var d = new Dog(\u0026#39;red\u0026#39;,\u0026#39;wangcai\u0026#39;) console.log(d) 实现接口 # interface Animal{ run(): void } class Dog implements Animal{ run(): void{ console.log(\u0026#34;run run run\u0026#34;) } } var d = new Dog() d.run() 类成员可见性 # public（公有的）默认 protected（受保护的） private（私有的） readonly（只读，用来防止在构造函数之外对属性进行赋值） ","date":"2024-09-03","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/6d366d27/e8faee47/","section":"文章","summary":"\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eTypeScript 是 JS 的超集，TS 提供了 JS 的所有功能，并且额外的增加了：类型系统。\u003c/p\u003e","title":"2、数据类型","type":"posts"},{"content":" 场景资源 # 在 Cocos Creator 3.0 中，游戏场景（Scene）是游戏开发时组织游戏内容的中心，也是呈现给玩家所有游戏内容的载体。而场景文件本身也作为游戏资源存在，并保存了游戏的大部分信息，也是创作的基础。\n创建方式有两种：\n在 资源管理器 中右键点击想要放置场景文件的文件夹，然后选择 （创建 - Scene） 即可。为了使项目具备良好的文件夹目录结构，强烈建议使用该方法创建场景。 在顶部菜单栏中选择（文件 - 新建场景），即可在 场景编辑器 中直接创建一个新场景。但 资源管理器 中不会出现新场景文件，需要在保存场景时弹出的 保存场景 窗口中手动保存场景文件，保存完成后才会在 资源管理器 的根目录下出现 scene.scene 场景文件。 节点和组件 # Cocos Creator 3.0 的工作流程是以组件式开发为核心的，组件式架构也称作 实体 — 组件架构（Entity-Component System），简单来说，就是以组合而非继承的方式进行游戏中各种元素的构建。\n在 Cocos Creator 3.0 中，节点（Node） 是承载组件的实体，我们通过将具有各种功能的 组件（Component） 挂载到节点上，来让节点具有各式各样的表现和功能。接下来我们看看如何在场景中创建节点和添加组件。\n节点 # 节点是场景的基础组成单位。节点之间是树状的组织关系，每个节点可以有多个子节点\n节点包含一组基础属性（位移、旋转、缩放），节点之间通过一组相对变换关系组织在一起。 节点间的更新顺序是逐级更新的。子节点的更新依赖于父节点，子节点跟随父节点变换 节点上可以添加组件，将多个组件与节点关联在一起 创建节点 # 要最快速地获得一个具有特定功能的节点，可以通过 层级管理器 左上角的 创建节点 按钮，创建一个Sphere（球体）节点：\n之后我们就可以在 场景编辑器 和 层级管理器 中看到新添加的 Sphere 节点了。新节点命名默认为 Sphere，表示这是一个主要由 Sphere 组件负责提供功能的节点。您也可以尝试再次点击 创建节点 按钮，选择其他的节点类型，可以看到它们的命名和表现会有所不同。需要注意的是：创建 UI 节点时会自动创建一个 Canvas 节点作为 UI 节点的根节点。\n节点属性 # Position：位置 Rotation：旋转 Scale：缩放 Mobility：节点的可移动性。不同的可移动性会导致节点在光照上有不同的特性和表现 对于持有光源组件的节点 Static 静态光源：会烘焙直接光与间接光，烘焙完运行时不参与计算 Stationary 固定光源：只烘焙间接光，只在运行时计算直接光 Movable 可移动光源：不参与烘焙，只在运行时计算直接光 对于持有 MeshRenderer 的节点 Static \u0026amp; Stationary 静态物体：可使用光照贴图 Movable 动态物体：可使用光照探针 通常来说在拥有 MeshRenderer 组件的节点上可以添加灯光组件，但并不建议，可以考虑分开多个节点来实现这样的需求。 Layer：设定节点的可见性能力 组件 # 属性检查器 中以 Node 标题开始的部分就是节点的属性，节点属性包括了节点的位置、旋转、缩放等变换信息。\n接下来以 cc.MeshRenderer 标题开始的部分就是 Sphere 上挂载的 MeshRenderer 组件的属性。在 Creator 中，MeshRenderer 组件用于渲染静态的 3D 模型，其中的 Mesh 属性用于指定渲染所用的网格资源。因为我们刚刚创建的是 Sphere 节点，所以这里默认是 sphere.mesh。而 Materials 属性用于指定渲染所用的材质资源。\n坐标系 # 世界坐标系（World Coordinate） # 世界坐标系也叫做绝对坐标系，在 Cocos Creator 3.0 游戏开发中表示场景空间内的统一坐标体系，「世界」用来表示我们的游戏场景。\nCreator 3.0 的世界坐标系采用的是笛卡尔右手坐标系，默认 x 向右，y 向上，z 向外，同时使用 -z 轴为正前方朝向。\n本地坐标系（Local Coordinate） # 本地坐标系也叫相对坐标系，是和节点相关联的坐标系。每个节点都有独立的坐标系，当节点移动或改变方向时，和该节点关联的坐标系将随之移动或改变方向。\nCreator 3.0 的 节点（Node） 之间可以有父子关系的层级结构，我们通过修改节点的 Position 属性设定的节点位置是该节点相对于父节点的 本地坐标系，而非世界坐标系。最后在绘制整个场景时 Creator 会把这些节点的本地坐标映射成世界坐标系坐标。\n","date":"2024-08-29","externalUrl":null,"permalink":"/posts/69064821/92082869/b4d34f9d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e场景资源 \n    \u003cdiv id=\"场景资源\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%9c%ba%e6%99%af%e8%b5%84%e6%ba%90\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在 Cocos Creator 3.0 中，游戏场景（Scene）是游戏开发时组织游戏内容的中心，也是呈现给玩家所有游戏内容的载体。而场景文件本身也作为游戏资源存在，并保存了游戏的大部分信息，也是创作的基础。\u003c/p\u003e","title":"2、场景构建","type":"posts"},{"content":" MySQL的4大版本 # MySQL Community Server 社区版本，开源免费，自由下载，但不提供官方技术支持，适用于大多数普通用户。 MySQL Enterprise Edition 企业版本，需付费，不能在线下载，可以试用30天。提供了更多的功能和更完备的技术支持，更适合于对数据库的功能和可靠性要求较高的企业客户。 MySQL Cluster 集群版，开源免费。用于架设集群服务器，可将几个MySQL Server封装成一个Server。需要在社区版或企业版的基础上使用。 MySQL Cluster CGE 高级集群版，需付费。 此外，官方还提供了 **MySQL Workbench （GUITOOL）**一款专为MySQL设计的 图形界面管理工具 。MySQLWorkbench又分为两个版本，分别是 社区版 （MySQL Workbench OSS）、 商用版 （MySQLWorkbenchSE）。\nWindows安装 # 下载 # 官网： https://www.mysql.com\n1、打开官网，点击DOWNLOADS\n2、然后，点击 MySQL Community(GPL) Downloads\n3、点击 MySQL Community Server\n4、在General Availability(GA) Releases中选择适合的版本\nWindows平台下提供两种安装文件：MySQL二进制分发版（.msi安装文件）和免安装版（.zip压缩文件）。一般来讲，应当使用二进制分发版，因为该版本提供了图形化的安装向导过程，比其他的分发版使用起来要简单，不再需要其他工具启动就可以运行MySQL。 这里在Windows 系统下推荐下载 MSI安装程序 ；点击 Go to Download Page 进行下载即可 5、Windows下的MySQL8.0安装有两种安装程序\nmysql-installer-web-community-8.0.26.0.msi 下载程序大小：2.4M；安装时需要联网安装组件。 mysql-installer-community-8.0.26.0.msi 下载程序大小：450.7M；安装时离线安装即可。推荐。 如果安装MySQL5.7版本的话，选择 Archives ，接着选择MySQL5.7的相应版本即可。这里下载最近期的MySQL5.7.34版本。\n安装 # 步骤1：双击下载的mysql-installer-community-8.0.26.0.msi文件，打开安装向导。\n步骤2：打开“Choosing a Setup Type”（选择安装类型）窗口，在其中列出了5种安装类型，分别是Developer Default（默认安装类型）、Server only（仅作为服务器）、Client only（仅作为客户端）、Full（完全安装）、Custom（自定义安装）。这里选择“Custom（自定义安装）”类型按钮，单击“Next(下一步)”按钮。\n步骤3：打开“Select Products” （选择产品）窗口，可以定制需要安装的产品清单。例如，选择“MySQLServer 8.0.26-X64”后，单击“→”添加按钮，即可选择安装MySQL服务器，如图所示。采用通用的方法，可以添加其他你需要安装的产品。\n此时如果直接“Next”（下一步），则产品的安装路径是默认的。如果想要自定义安装目录，则可以选中对应的产品，然后在下面会出现“Advanced Options”（高级选项）的超链接。\n单击“Advanced Options”（高级选项）则会弹出安装目录的选择窗口，如图所示，此时你可以分别设置MySQL的服务程序安装目录和数据存储目录。如果不设置，默认分别在C盘的Program Files目录和ProgramData目录（这是一个隐藏目录）。如果自定义安装目录，请避免“中文”目录。另外，建议服务目录和数据目录分开存放。\n步骤4：在上一步选择好要安装的产品之后，单击“Next”（下一步）进入确认窗口，如图所示。单击“Execute”（执行）按钮开始安装。\n步骤5：安装完成后在“Status”（状态）列表下将显示“Complete”（安装完成），如图所示。\n安装失败问题 # 无法打开MySQL8.0软件安装包或者安装过程中失败，如何解决？\n在运行MySQL8.0软件安装包之前，用户需要确保系统中已经安装了.Net Framework相关软件，如果缺少此软件，将不能正常地安装MySQL8.0软件。\n解决方案：到这个地址 https://www.microsoft.com/en-us/download/details.aspx?id=42642 下载Microsoft .NET Framework 4.5并安装后，再去安装MySQL。\n另外，还要确保Windows Installer正常安装。windows上安装mysql8.0需要操作系统提前已安装好Microsoft Visual C++ 2015-2019，下载地址 https://support.microsoft.com/en-us/topic/the-latest-supported-visual-c-downloads-2647da03-1eea-4433-9aff-95f26a218cc0 。\n配置 # MySQL安装之后，需要对服务器进行配置。具体的配置步骤如下。\n步骤1：在上一个小节的最后一步，单击“Next”（下一步）按钮，就可以进入产品配置窗口。\n步骤2：单击“Next”（下一步）按钮，进入MySQL服务器类型配置窗口，如图所示。端口号一般选择默认端口号3306。\n其中，“Config Type”选项用于设置服务器的类型。单击该选项右侧的下三角按钮，即可查看3个选项，如图所示。\nDevelopment Machine（开发机器） ：该选项代表典型个人用桌面工作站。此时机器上需要运行多个应用程序，那么MySQL服务器将占用最少的系统资源。 Server Machine（服务器） ：该选项代表服务器，MySQL服务器可以同其他服务器应用程序一起运行，例如Web服务器等。MySQL服务器配置成适当比例的系统资源。 Dedicated Machine（专用服务器） ：该选项代表只运行MySQL服务的服务器。MySQL服务器配置成使用所有可用系统资源。 步骤3：单击“Next”（下一步）按钮，打开设置授权方式窗口。其中，上面的选项是MySQL8.0提供的新的授权方式，采用SHA256基础的密码加密方法；下面的选项是传统授权方法（保留5.x版本兼容性）。\n步骤4：单击“Next”（下一步）按钮，打开设置服务器root超级管理员的密码窗口，如图所示，需要输入两次同样的登录密码。也可以通过“Add User”添加其他用户，添加其他用户时，需要指定用户名、允许该用户名在哪台/哪些主机上登录，还可以指定用户角色等。\n步骤5：单击“Next”（下一步）按钮，打开设置服务器名称窗口，如图所示。该服务名会出现在Windows服务列表中，也可以在命令行窗口中使用该服务名进行启动和停止服务。这里将服务名设置为“MySQL80”。如果希望开机自启动服务，也可以勾选“Start the MySQL Server at System Startup”选项（推荐）。\n下面是选择以什么方式运行服务？可以选择“Standard System Account”(标准系统用户)或者“Custom User”(自定义用户)中的一个。这里推荐前者。\n步骤6：单击“Next”（下一步）按钮，打开确认设置服务器窗口，单击“Execute”（执行）按钮。\n步骤7：完成配置，如图所示。单击“Finish”（完成）按钮，即可完成服务器的配置。\n步骤8：如果还有其他产品需要配置，可以选择其他产品，然后继续配置。如果没有，直接选择“Next”（下一步)，直接完成整个安装和配置过程。\n步骤9：结束安装和配置。\nLinux离线安装 # #解压tar tar -xf mysql-8.0.28-1.el8.x86_64.rpm-bundle.tar #删除自带库 rpm -qa | grep mariadb rpm -e mariadb-libs-5.5.56-2.el7.x86_64 --nodeps #按顺序安装 common→libs→client→server #必须安装(注意顺序) rpm -ivh mysql-community-common-8.0.13-1.el7.x86_64.rpm rpm -ivh mysql-community-libs-8.0.13-1.el7.x86_64.rpm rpm -ivh mysql-community-client-8.0.13-1.el7.x86_64.rpm rpm -ivh mysql-community-server-8.0.13-1.el7.x86_64.rpm #非必要安装（注意顺序） rpm -ivh mysql-community-libs-compat-8.0.13-1.el7.x86_64.rpm rpm -ivh mysql-community-embedded-compat-8.0.13-1.el7.x86_64.rpm rpm -ivh mysql-community-devel-8.0.13-1.el7.x86_64.rpm rpm -ivh mysql-community-test-8.0.13-1.el7.x86_64.rpm #启动 systemctl start mysqld service mysqld status #查看默认密码 cat /var/log/mysqld.log | grep password #设置密码 ALTER USER \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;123456\u0026#39;; #如果出现密码安全性限制Your password does not satisfy the current policy requirements. set global validate_password_policy=LOW; set global validate_password_length=6; #mysql8 以上版本变量名称有所改变 set global validate_password.policy=LOW; set global validate_password.length=6; #给防火墙加白名单，放开3306端口或者关闭防火墙。 firewall-cmd --zone=public --add-port=3306/tcp --permanent #设置远程访问权限 update mysql.user set host=\u0026#39;%\u0026#39; where user=\u0026#39;root\u0026#39;; flush privileges; 迁移data目录 # #停止mysql(切记) systemctl stop mysqld #例：复制/var/lib/mysql目录下的所有内容到/home/mysql/data下 cp -fa /var/lib/mysql/* /home/mysql/data/ chown -R mysql:mysql /home/mysql/ #改名原来的目录备份，防止移动失败 cd /var/lib/ mv mysql mysql_bak #修改配置文件/etc/my.cnf，修改后内容 [mysqld] datadir=/home/mysql/data socket=/home/mysql/data/mysql.sock [mysql] socket=/home/mysql/data/mysql.sock #重启 systemctl start mysqld MySQL的环境变量 # 如果不配置MySQL环境变量，就不能在命令行直接输入MySQL登录命令。\n将MySQL安装目录下bin目录配置到path\n查看MySQL版本 # mysql -V mysql --version #在mysql命令行中 select version(); 命令行登录MySQL # mysql -h 主机名 -P 端口号 -u 用户名 -p密码 #-h如果不写默认为localhost #-P如果不写默认3306 #一般-p不写，在下一行输入密码 查看所有数据库 # show databases; 默认数据库 # information_schema：是 MySQL 系统自带的数据库，主要保存 MySQL 数据库服务器的系统信息，比如数据库的名称、数据表的名称、字段名称、存取权限、数据文件 所在的文件夹和系统使用的文件夹，等等\nperformance_schema：是 MySQL 系统自带的数据库，可以用来监控 MySQL 的各类性能指标。\nsys：数据库是 MySQL 系统自带的数据库，主要作用是以一种更容易被理解的方式展示 MySQL 数据库服务器的各类性能指标，帮助系统管理员和开发人员监控 MySQL 的技术性能。\nmysql：数据库保存了 MySQL 数据库服务器运行时需要的系统信息，比如数据文件夹、当前使用的字符集、约束检查信息，等等\n字符集编码 # 如果出现MySQL中文属性乱码，那么需要设置字符集编码\n查看字符集编码\nshow variables like \u0026#39;character_%\u0026#39;; show variables like \u0026#39;collation_%\u0026#39;; 修改mysql的数据目录下的my.ini配置文件\n[mysql] #大概在63行左右，在其下添加 default-character-set=utf8 #默认字符集 [mysqld] # 大概在76行左右，在其下添加 character-set-server=utf8 collation-server=utf8_general_ci 图形化界面 # MySQL Workbench # MySQL官方提供的图形化管理工具MySQL Workbench完全支持MySQL 5.0以上的版本。MySQL Workbench分为社区版和商业版，社区版完全免费，而商业版则是按年收费。\nMySQL Workbench 为数据库管理员、程序开发者和系统规划师提供可视化设计、模型建立、以及数据库管理功能。它包含了用于创建复杂的数据建模ER模型，正向和逆向数据库工程，也可以用于执行通常需要花费大量时间的、难以变更和管理的文档任务。\n下载地址： http://dev.mysql.com/downloads/workbench/ 。\nNavicat # Navicat MySQL是一个强大的MySQL数据库服务器管理和开发工具。它可以与任何3.21或以上版本的MySQL一起工作，支持触发器、存储过程、函数、事件、视图、管理用户等，对于新手来说易学易用。其精心设计的图形用户界面（GUI）可以让用户用一种安全简便的方式来快速方便地创建、组织、访问和共享信息。Navicat支持中文，有免费版本提供。\n下载地址： http://www.navicat.com/ 。\nSQLyog # SQLyog 是业界著名的 Webyog 公司出品的一款简洁高效、功能强大的图形化 MySQL 数据库管理工具。这款工具是使用C++语言开发的。该工具可以方便地创建数据库、表、视图和索引等，还可以方便地进行插入、更新和删除等操作，同时可以方便地进行数据库、数据表的备份和还原。该工具不仅可以通过SQL文件进行大量文件的导入和导出，还可以导入和导出XML、HTML和CSV等多种格式的数据。\n下载地址： http://www.webyog.com/。\ndbeaver # DBeaver是一个通用的数据库管理工具和 SQL 客户端，支持所有流行的数据库：MySQL、PostgreSQL、SQLite、Oracle、DB2、SQL Server、 Sybase、MS Access、Teradata、 Firebird、Apache Hive、Phoenix、Presto等。DBeaver比大多数的SQL管理工具要轻量，而且支持中文界面。DBeaver社区版作为一个免费开源的产品，和其他类似的软件相比，在功能和易用性上都毫不逊色。\n唯一需要注意是 DBeaver 是用Java编程语言开发的，所以需要拥有 JDK（Java Development ToolKit）环境。如果电脑上没有JDK，在选择安装DBeaver组件时，勾选“Include Java”即可。\n下载地址： https://dbeaver.io/download/\nMySQL目录结构 # MySQL的目录结构 说明 bin目录 所有MySQL的可执行文件。如：mysql.exe MySQLInstanceConfig.exe 数据库的配置向导，在安装时出现的内容 data目录 系统数据库所在的目录 my.ini文件 MySQL的主要配置文件 C:\\ProgramData\\MySQL\\MySQL Server 8.0\\data\\ 用户创建的数据库所在的目录，默认在这个目录 常见问题解决 # 图形化界面连接报错 # 有些图形界面工具，特别是旧版本的图形界面工具，在连接MySQL8时出现Authentication plugin 'caching_sha2_password' cannot be loaded错误。\n出现这个原因是MySQL8之前的版本中加密规则是mysql_native_password，而在MySQL8之后，加密规则是caching_sha2_password。解决问题方法有两种，第一种是升级图形界面工具版本，第二种是把MySQL8用户登录密码加密规则还原成mysql_native_password。\n#使用mysql数据库 USE mysql; #修改\u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39;用户的密码规则和密码 ALTER USER \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED WITH mysql_native_password BY \u0026#39;123456\u0026#39;; #刷新权限 FLUSH PRIVILEGES; root用户密码忘记，重置的操作 # 1: 通过任务管理器或者服务管理，关掉mysqld(服务进程)\n2: 通过命令行+特殊参数开启mysqld\nmysqld --defaults-file=\u0026#34;D:\\ProgramFiles\\mysql\\MySQLServer5.7Data\\my.ini\u0026#34; --skip-grant-tables 3: 此时，mysqld服务进程已经打开。并且不需要权限检查\n4: mysql -uroot 无密码登陆服务器。另启动一个客户端进行\n5: 修改权限表\nuse mysql; update user set authentication_string=password(\u0026#39;新密码\u0026#39;) where user=\u0026#39;root\u0026#39;; flush privileges; 6: 通过任务管理器，关掉mysqld服务进程。\n7: 再次通过服务管理，打开mysql服务。\n8: 即可用修改后的新密码登陆。\n命令行客户端中文乱码 # SHOW VARIABLES LIKE \u0026#39;character_set_%\u0026#39;; #设置为windows默认的gbk SET NAMES GBK; 修改已存在表或库的字符集编码 # 如果是在修改my.ini之前建的库和表，那么库和表的编码还是原来的Latin1，要么删了重建，要么使用alter语句修改编码。\n#查看表的建表信息 show create table student; #修改表的 alter table student charset utf8; #查看库的建库信息 show create database school; #修改库的字符集编码 alter database school charset utf8; 忽略大小写 # 1表示忽略大小写，0表示不忽略大小写。修改后需要重启MySQL服务。\n[mysqld] lower_case_table_names=1 注意：MYSQL8如果初始化后再添加该配置，8启动会报Different lower_case_table_names settings for server ('1') and data dictionary ('0').，需要彻底卸载mysql后，重新安装，在启动前，将该配置完成\ndatetime字段默认值错误 # mysql5.7后时间字段出现问题，MySQL5.7.27中datetime字段默认值设置为0000-00-00 00:00:00会出现异常Invalid default value for 'comment_date'\n[mysqld] #实际是去除NO_ZERO_IN_DATE,NO_ZERO_DATE sql_mode=ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION 设置数据库默认时区 # [mysqld] default-time-zone = \u0026#39;+08:00\u0026#39; 通过data目录恢复mysql # # 1、提取data目录中如下文件以及自己的数据库目录 auto.cnf ib_buffer_pool ib_logfile0 ib_logfile1 ibdata1 mysql.ibd # 2、覆盖到同版本数据库的datadir 卸载mysql # # 查找mysql的包 rpm -qa | grep mysql # 依次删除 rpm -e --nodeps # 删除目录 rm -rf /var/lib/mysql/ ","date":"2024-08-09","externalUrl":null,"permalink":"/posts/a6ece75f/8b824d9b/5d290da9/f089a209/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eMySQL的4大版本 \n    \u003cdiv id=\"mysql的4大版本\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#mysql%e7%9a%844%e5%a4%a7%e7%89%88%e6%9c%ac\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eMySQL Community Server 社区版本\u003c/strong\u003e，开源免费，自由下载，但不提供官方技术支持，适用于大多数普通用户。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eMySQL Enterprise Edition 企业版本\u003c/strong\u003e，需付费，不能在线下载，可以试用30天。提供了更多的功能和更完备的技术支持，更适合于对数据库的功能和可靠性要求较高的企业客户。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eMySQL Cluster 集群版\u003c/strong\u003e，开源免费。用于架设集群服务器，可将几个MySQL Server封装成一个Server。需要在社区版或企业版的基础上使用。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eMySQL Cluster CGE 高级集群版\u003c/strong\u003e，需付费。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e此外，官方还提供了 **MySQL Workbench （GUITOOL）**一款专为MySQL设计的 图形界面管理工具 。MySQLWorkbench又分为两个版本，分别是 社区版 （MySQL Workbench OSS）、 商用版 （MySQLWorkbenchSE）。\u003c/p\u003e","title":"2、MySQL安装","type":"posts"},{"content":" Docker相关命令 # 帮助启动类 # 启动、重启、停止docker # systemctl start|restart|stop docker 查看docker状态 # systemctl status docker 查看docker概要信息 # docker info 查看docker总体帮助文档 # docker --help 查看docker命令帮助文档 # docker [command] --help 镜像类 # 查看本地镜像 # 如果一个镜像仓库名和tag都是\u0026lt;none\u0026gt;，那么就称为虚悬镜像（dangling image）\ndocker images -a #列出本地所有镜像，含历史映像层 -q #只显示镜像id 查询镜像 # docker search [name] --limit [size] #最显示size个，默认25个 拉取镜像 # docker pull [name] docker pull [name]:[tag] 查看镜像、容器、数据卷所占空间 # docker system df 删除镜像 # docker rmi [name/id] [name/id] ... -f #强制删除，如果提示该镜像使用过无法删除，可以使用这个参数 ${docker images -qa} #删除全部镜像 导入导出镜像 # 可以将镜像保存为tar文件\ndocker save -o [tarpath] [imagename/id] 或 docker save [imagename/id]\u0026gt;[tarpath] docker load\u0026lt;[tarpath] docker tag [id] [name:tag] 容器类 # 新建并启动容器 # docker run [options] [name/id] [command] [arg...] --name=[name] #为容器指定一个名字con -d #后台运行并返回容器id，也就是启动守护式容器 -i #以交互式运行容器，通常与-t一起使用 -t #为容器重新分配一个伪输入终端，通常与-i一起使用，通常在整个run命令后加/bin/bash -P #随机端口映射 -p [hostport:containerport] #指定端口映射 -v [hostpath:containerpath] #将容器中的目录映射到宿主机目录，一定要配合--privileged=true使用 -e [key=value] #设置容器内的环境变量 --privileged=true #将容器的权限，设置为最高权限root 注意：docker容器运行必须要有一个前台进程，容器运行的命令如果不是一直挂起的命令（top、tail等）就会自动退出，一般可以使用-dit\n避坑：如果在启动时出现警告IPv4相关 # WARNING: IPv4 forwarding is disabled. Networking will not work.\n#进行编辑配置文件 vi /usr/lib/sysctl.d/00-system.conf #在其中添加一段 net.ipv4.ip_forward=1 #重启网络 service network restart 查看容器 # docker ps -a #查看所有运行的容器，包括已经退出的 -n [size] #查看最近size个容器 -l #查看最近创建的一个容器 -q #只显示容器编号 退出容器 # exit #退出容器，并且容器停止 ctrl+p+q #退出容器，容器不会停止 启动已停止容器 # docker start [name/id] 重启正在运行的容器 # docker restart [name/id] 停止正在运行的容器 # docker stop [name/id] docker kill [name/id] #强制停止 删除已停止的容器 # docker rm [name/id] -f #强制删除，可删除正在运行的 进入容器 # docker exec -it [name/id] /bin/bash docker attach [name/id] #区别： attach直接进入容器启动命令的终端，不会启动新的线程，exit会导致容器停止 exec在容器中打开新的终端，并且会启动新的线程，exit不会导致容器停止 查看容器日志 # docker logs -f [name/id] 查看容器内运行的进程 # docker top [name/id] 查看容器内部细节 # docker inspect [name/id] 从容器内拷贝文件到主机 # docker cp [containername/id]:[containerpath] [localpath] 从主机拷贝文件到容器 # docker cp [localpath] [containername/id]:[containerpath] 导入导出容器 # docker export [containername/id] \u0026gt; [name.tar] cat [name.tar] | docker import - name:tag 容器保存为镜像 # docker commit -a=[作者] -m=[备注] [容器ID] [仓库名称]:[TAG] #注意：挂载类似软连接(快捷方式)，挂载目录新增文件在容器中不会新增，容器只是引用了挂载目录的新增文件，本地容器打包为本地镜像前必须将宿主机的挂载目录新增文件拷贝到对应的容器目录中，否则挂载目录的新增文件无法被打包到本地镜像 跨平台使用 # 在 Docker 中，平台是指硬件和操作系统的组合。不同的硬件架构和操作系统版本具有不同的特性和限制。常见的硬件架构包括 x86、 x86_64、 ARM 和 ARM64 等，常见的操作系统包括 Linux、 Windows 和 macOS 等。\n拉取镜像：docker pull --platform=linux/amd64 [imagename] 构建镜像，此时dockerfile中的基础镜像可以选择自己需要的平台：FROM --platform=linux/amd64 [imagename] 运行镜像：docker run --platform=linux/amd64 [imagename] 支持的平台 # Docker 支持的平台非常广泛，包括但不限于以下几种：\nlinux/amd64：x86 架构的 Linux 系统，例如常见的 PC 和服务器。 linux/arm64：ARM 架构的 Linux 系统，例如 Raspberry Pi。 linux/ppc64le：IBM Power 架构的 Linux 系统。 linux/s390x：IBM Z 架构的 Linux 系统。 另外，还可以使用通配符来指定多个平台。例如，linux/* 表示所有的 Linux 平台，*/*表示所有的平台。\nDocker安装、启动常用镜像 # 一、Docker安装MySQL # 从官网上https://hub.docker.com/ 进行镜像的搜索（推荐） 或者使用docker search mysql进行命令查找 1、下载MySQL镜像 # 在Xshell中，执行如下命令，即可下载镜像\ndocker pull mysql:5.7 pull：是下拉的意思 mysql：是镜像的名称 5.7：是镜像的版本 校验镜像是否下载完成\ndocker images 2、Docker后台模式运行MySQL # Docker容器的运行命令是：docker run\ndocker run --name mysql01 -p 3306:3306 -v /root/mysql/data:/var/lib/mysql --privileged=true -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 属性：\n--name mysql01：给Docker容器 定义一个名称mysql01 -p 3306:3306：将宿主机的3306 映射到Docker容器内部的3306上 （第1个是外部的） -v /root/mysql/data:/var/lib/mysql：表示目录映射关系（前者\u0026quot;:前\u0026quot;是宿主机目录，后者是容器被映射到宿主机上的目录），可以使用多个－v做多个目录或文件映射。注意：最好做目录映射，在宿主机上做修改，然后共享到容器上。 --privileged=true：将Docker容器的权限，设置为最高权限 root -e MYSQL_ROOT_PASSWORD=123456：配置Docker内部的环境变量 -d：让容器在后端运行 mysql:5.7：镜像的名称 检验Docker容器是否启动成功\ndocker ps 避坑：docker中MySQL的默认字符集编码是latin1，插入中文会出现乱码show variables like 'character%'\n3、使用navicat链接mysql # 4、Docker前台模式运行MySQL # docker run --name mysql01 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=12345 -i -t -v /root/mysql/data:/var/lib/mysql --privileged=true mysql:5.7 /bin/bash -i：以前台运行模式进行启动docker容器 -t：为容器重新分配一个伪输入终端，通常与 -i 同时使用 /bin/bash：让容器启动完毕后，开启一个内部进程 这种模式有个特点是：退出容器内部，Docker容器将会自动关闭 前台模式和后台模式的区别 # 前台模式的特点：启动之后就立马进入到docker内部 后台模式的特点：启动之后，我们的程序依旧在物理上 推荐使用****：后台模式启动 5、生产环境使用MySQL # 启动命令，对日志、数据、配置进行数据卷备份\ndocker run -d -p 3306:3306 --privileged=true -v /docker/mysql/log:/var/log/mysql -v /docker/mysql/data:/var/lib/mysql -v /docker/mysql/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 --name mysql mysql:5.7 在宿主机/docker/mysql/conf路径下，添加配置文件my.conf解决中文乱码\n[client] default_character_set=utf8 [mysqld] collation_server=utf8_general_ci character_set_server=utf8 重启mysql，使配置文件生效\ndocker restart mysql 二、Docker安装Tomcat # 在https://hub.daocloud.io/网站上，输入tomcat进行镜像查找 1、下载镜像 # docker pull daocloud.io/library/tomcat:8.5.15-jre8 检验下载docker images 2、运行镜像 # docker run -d -p 8080:8080 --name mytomcat daocloud.io/library/tomcat:8.5.15-jre8 3、进行访问 # 4、挂载war文件 # 4.1、运行tomcat，并且-v声明docker数据卷 # docker run -d -p 8080:8080 --name mytomcat --privileged=true -v /root/tomcat/webapps:/usr/local/tomcat/webapps daocloud.io/library/tomcat:8.5.15-jre8 运行完成，可以在目录中看到创建的数据卷目录\n数据卷 作用 通过-v 宿主机目录:容器中目录，将容器里面的目录，映射到宿主机上，可以在宿主机对容器类的文件进行更改，同步到容器 好处 Docker容器是一种“沙箱”机制，会将容器的环境，容器产生的数据等，统一放置在Docker容器内部。有的时候，可能会出现问题：比如MySQL容器突然异常，无法重启，那么容器内部的数据，也就跟着丢失掉了，使用数据卷就可以解决 4.2、上传war文件 # 将war文件上传到数据卷目录中\n4.3、进行访问 # 如果访问不到，重启一次容器，就可以了\ndocker restart mytomcat ","date":"2024-04-03","externalUrl":null,"permalink":"/posts/61bf7bc9/5e6103fa/36ca2e73/ea4878a4/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eDocker相关命令 \n    \u003cdiv id=\"docker相关命令\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#docker%e7%9b%b8%e5%85%b3%e5%91%bd%e4%bb%a4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e帮助启动类 \n    \u003cdiv id=\"帮助启动类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b8%ae%e5%8a%a9%e5%90%af%e5%8a%a8%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003e启动、重启、停止docker \n    \u003cdiv id=\"启动重启停止docker\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%90%af%e5%8a%a8%e9%87%8d%e5%90%af%e5%81%9c%e6%ad%a2docker\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esystemctl start\u003cspan class=\"p\"\u003e|\u003c/span\u003erestart\u003cspan class=\"p\"\u003e|\u003c/span\u003estop docker\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e查看docker状态 \n    \u003cdiv id=\"查看docker状态\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9f%a5%e7%9c%8bdocker%e7%8a%b6%e6%80%81\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esystemctl status docker\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e查看docker概要信息 \n    \u003cdiv id=\"查看docker概要信息\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9f%a5%e7%9c%8bdocker%e6%a6%82%e8%a6%81%e4%bf%a1%e6%81%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker info\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e查看docker总体帮助文档 \n    \u003cdiv id=\"查看docker总体帮助文档\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9f%a5%e7%9c%8bdocker%e6%80%bb%e4%bd%93%e5%b8%ae%e5%8a%a9%e6%96%87%e6%a1%a3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker --help\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e查看docker命令帮助文档 \n    \u003cdiv id=\"查看docker命令帮助文档\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9f%a5%e7%9c%8bdocker%e5%91%bd%e4%bb%a4%e5%b8%ae%e5%8a%a9%e6%96%87%e6%a1%a3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edocker \u003cspan class=\"o\"\u003e[\u003c/span\u003ecommand\u003cspan class=\"o\"\u003e]\u003c/span\u003e --help\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e镜像类 \n    \u003cdiv id=\"镜像类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%95%9c%e5%83%8f%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003e查看本地镜像 \n    \u003cdiv id=\"查看本地镜像\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9f%a5%e7%9c%8b%e6%9c%ac%e5%9c%b0%e9%95%9c%e5%83%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cp\u003e如果一个镜像仓库名和tag都是\u003ccode\u003e\u0026lt;none\u0026gt;\u003c/code\u003e，那么就称为虚悬镜像（dangling image）\u003c/p\u003e","title":"2、Docker的使用","type":"posts"},{"content":" Nginx的使用 # Nginx启动 # //在Nginx的目录下使用dos命令 start nginx.exe #启动nginx nginx -s reload #nginx可以重新加载文件 nginx -t #查看配置文件是否有错 nginx -s stop #停止nginx Nginx整合Tomcat，并实现动静分离（单个Tomcat） # 修改nginx.conf文件 # listen：表示当前的代理服务器监听的端口，默认的是监听80端口。注意，如果我们配置了多个server，这个listen要配置不一样，不然就不能确定转到哪里去了。 server_name：表示监听到之后需要转到哪里去，这时我们直接转到本地，这时是直接到nginx文件夹内。 location：表示匹配的路径，这时配置了/表示所有请求都被匹配到这里 root：里面配置了root这时表示当匹配这个请求的路径时，将会在这个文件夹内寻找相应的文件，这里对我们之后的静态文件伺服很有用。 index：当没有指定主页时，默认会选择这个指定的文件，它可以有多个，并按顺序来加载，如果第一个不存在，则找第二个，依此类推。 server { listen 80; #为虚拟服务器的识别路径。因此不同的域名会通过请求头中的HOST字段，匹配到特定的server块，转发到对应的应用服务器中去。 server_name localhost:8080; # proxy_pass:它表示代理路径，相当于转发，而不像之前说的root必须指定一个文件夹 location / { root html; index index.html index.htm; proxy_pass http://localhost:8080; } #静态文件交给nginx处理 location ~ .*\\.(js|css|htm|html|gif|jpg|jpeg|png|bmp|swf|ioc|rar|zip|txt|flv|mid|doc|ppt|pdf|xls|mp3|wma)$ { root G:/work/2018/prj04/src/main/webapp; expires 30d; } # 动态请求由反向代理分配去哪儿，见upstream{} location ~ .*$ { index index; proxy_pass http://localhost:8080; } } 配置负载均衡（多个Tomcat） # 1：启动多个tomcat\n2：修改配置文件\n#服务器的集群 upstream 127.0.0.1 { #服务器集群名字 server 127.0.0.1:8082 weight=1;#服务器配置 weight是权重的意思，权重越大，分配的概率越大。 server 127.0.0.1:8081 weight=1; } 3：修改nginx.conf文件\nlocation / { root html; index index.html index.htm; proxy_pass http://127.0.0.1; proxy_redirect default; } nginx.conf配置Gateway集群文件实例 # upstream gateway { #服务器集群名字 server 127.0.0.1:9000 weight=1;#服务器配置 weight是权重的意思，权重越大，分配的概率越大。 } server { listen 80; server_name gateway; location / { root html; index index.html index.htm /index.html; proxy_pass http://localhost:9000; proxy_redirect default; } #静态文件交给nginx处理 location ~ .*\\.(htm|html|gif|jpg|jpeg|png|bmp|swf|ioc|rar|zip|txt|flv|mid|doc|ppt|pdf|xls|mp3|wma)$ { root G:/Nginx/nginx-1.16.10/nginx-1.16.0/html; expires 30d; } } ","date":"2024-04-03","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/2c72a928/3524dfbd/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eNginx的使用 \n    \u003cdiv id=\"nginx的使用\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#nginx%e7%9a%84%e4%bd%bf%e7%94%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eNginx启动 \n    \u003cdiv id=\"nginx启动\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#nginx%e5%90%af%e5%8a%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e//在Nginx的目录下使用dos命令\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003estart nginx.exe  \u003cspan class=\"c1\"\u003e#启动nginx\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enginx -s reload  \u003cspan class=\"c1\"\u003e#nginx可以重新加载文件\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enginx -t \u003cspan class=\"c1\"\u003e#查看配置文件是否有错\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enginx -s stop \u003cspan class=\"c1\"\u003e#停止nginx\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eNginx整合Tomcat，并实现动静分离（单个Tomcat） \n    \u003cdiv id=\"nginx整合tomcat并实现动静分离单个tomcat\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#nginx%e6%95%b4%e5%90%88tomcat%e5%b9%b6%e5%ae%9e%e7%8e%b0%e5%8a%a8%e9%9d%99%e5%88%86%e7%a6%bb%e5%8d%95%e4%b8%aatomcat\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003e修改nginx.conf文件 \n    \u003cdiv id=\"修改nginxconf文件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bf%ae%e6%94%b9nginxconf%e6%96%87%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003elisten：表示当前的代理服务器监听的端口，默认的是监听80端口。注意，如果我们配置了多个server，这个listen要配置不一样，不然就不能确定转到哪里去了。\u003c/li\u003e\n\u003cli\u003eserver_name：表示监听到之后需要转到哪里去，这时我们直接转到本地，这时是直接到nginx文件夹内。\u003c/li\u003e\n\u003cli\u003elocation：表示匹配的路径，这时配置了/表示所有请求都被匹配到这里\u003c/li\u003e\n\u003cli\u003eroot：里面配置了root这时表示当匹配这个请求的路径时，将会在这个文件夹内寻找相应的文件，这里对我们之后的静态文件伺服很有用。\u003c/li\u003e\n\u003cli\u003eindex：当没有指定主页时，默认会选择这个指定的文件，它可以有多个，并按顺序来加载，如果第一个不存在，则找第二个，依此类推。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eserver \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    listen 80\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e#为虚拟服务器的识别路径。因此不同的域名会通过请求头中的HOST字段，匹配到特定的server块，转发到对应的应用服务器中去。\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    server_name localhost:8080\u003cspan class=\"p\"\u003e;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# proxy_pass:它表示代理路径，相当于转发，而不像之前说的root必须指定一个文件夹\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    location / \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        root   html\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        index  index.html index.htm\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        proxy_pass http://localhost:8080\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e}\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e#静态文件交给nginx处理\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    location ~ .*\u003cspan class=\"se\"\u003e\\.\u003c/span\u003e\u003cspan class=\"o\"\u003e(\u003c/span\u003ejs\u003cspan class=\"p\"\u003e|\u003c/span\u003ecss\u003cspan class=\"p\"\u003e|\u003c/span\u003ehtm\u003cspan class=\"p\"\u003e|\u003c/span\u003ehtml\u003cspan class=\"p\"\u003e|\u003c/span\u003egif\u003cspan class=\"p\"\u003e|\u003c/span\u003ejpg\u003cspan class=\"p\"\u003e|\u003c/span\u003ejpeg\u003cspan class=\"p\"\u003e|\u003c/span\u003epng\u003cspan class=\"p\"\u003e|\u003c/span\u003ebmp\u003cspan class=\"p\"\u003e|\u003c/span\u003eswf\u003cspan class=\"p\"\u003e|\u003c/span\u003eioc\u003cspan class=\"p\"\u003e|\u003c/span\u003erar\u003cspan class=\"p\"\u003e|\u003c/span\u003ezip\u003cspan class=\"p\"\u003e|\u003c/span\u003etxt\u003cspan class=\"p\"\u003e|\u003c/span\u003eflv\u003cspan class=\"p\"\u003e|\u003c/span\u003emid\u003cspan class=\"p\"\u003e|\u003c/span\u003edoc\u003cspan class=\"p\"\u003e|\u003c/span\u003eppt\u003cspan class=\"p\"\u003e|\u003c/span\u003epdf\u003cspan class=\"p\"\u003e|\u003c/span\u003exls\u003cspan class=\"p\"\u003e|\u003c/span\u003emp3\u003cspan class=\"p\"\u003e|\u003c/span\u003ewma\u003cspan class=\"o\"\u003e)\u003c/span\u003e$\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        root  G:/work/2018/prj04/src/main/webapp\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        expires 30d\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# 动态请求由反向代理分配去哪儿，见upstream{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    location ~ .*$ \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        index index\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        proxy_pass http://localhost:8080\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e配置负载均衡（多个Tomcat） \n    \u003cdiv id=\"配置负载均衡多个tomcat\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%85%8d%e7%bd%ae%e8%b4%9f%e8%bd%bd%e5%9d%87%e8%a1%a1%e5%a4%9a%e4%b8%aatomcat\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e1：启动多个tomcat\u003c/p\u003e","title":"2、集群的实现，动静分离","type":"posts"},{"content":" 1、添加git # 2、初始化工作区 # 3、添加工作区内容到暂存区 # 右键项目\u0026hellip;\n4、提交项目到本地仓库 # 右键项目\u0026hellip;\n5、提交到远程仓库（gitee） # 需要跳过的文件 # # kdiff3 ignore *.orig # maven ignore .mvn/ target/ # eclipse ignore .settings/ .project .classpath # idea ignore .idea/ *.ipr *.iml *.iws # temp ignore *.log *.cache *.diff *.patch *.tmp # system ignore .DS_Store Thumbs.db # package ignore (optional) *.jar *.war *.zip *.tar *.tar.gz ","date":"2024-01-26","externalUrl":null,"permalink":"/posts/f1b56a1d/712987f6/f28ed828/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e1、添加git \n    \u003cdiv id=\"1添加git\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1%e6%b7%bb%e5%8a%a0git\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20210720120730115\"\n    data-zoom-src=\"/posts/f1b56a1d/712987f6/f28ed828/image/202109181438942.png\"\n    src=\"/posts/f1b56a1d/712987f6/f28ed828/image/202109181438942.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"2、Idea使用git","type":"posts"},{"content":" 全局配置 # Vue.config 是一个对象，包含 Vue 的全局配置。\nsilent # 取消 Vue 所有的日志与警告\n类型：boolean 默认值：false Vue.config.silent = true optionMergeStrategies # 自定义合并策略的选项，合并策略选项分别接收在父实例和子实例上定义的该选项的值作为第一个和第二个参数，Vue 实例上下文被作为第三个参数传入\n类型：{ [key: string]: Function } 默认值：{} Vue.config.optionMergeStrategies._my_option = function (parent, child, vm) { return child + 1 } const Profile = Vue.extend({ _my_option: 1 }) // Profile.options._my_option = 2 devtools # 配置是否允许 vue-devtools 检查代码。\n类型：boolean 默认值：true (生产版为 false) // 务必在加载 Vue 之后，立即同步设置以下内容 Vue.config.devtools = true errorHandler # 指定组件的渲染和观察期间未捕获错误的处理函数。这个处理函数被调用时，可获取错误信息和 Vue 实例\n类型：Function 默认值：undefined Vue.config.errorHandler = function (err, vm, info) { // handle error // `info` 是 Vue 特定的错误信息，比如错误所在的生命周期钩子 // 只在 2.2.0+ 可用 } warnHandler # 2.4.0 新增，为 Vue 的运行时警告赋予一个自定义处理函数。注意这只会在开发者环境下生效，在生产环境下它会被忽略\n类型：Function 默认值：undefined Vue.config.warnHandler = function (msg, vm, trace) { // `trace` 是组件的继承关系追踪 } ignoredElements # 须使 Vue 忽略在 Vue 之外的自定义元素 (e.g. 使用了 Web Components APIs)。否则，它会假设你忘记注册全局组件或者拼错了组件名称，从而抛出一个关于 Unknown custom element 的警告。\n类型：Array\u0026lt;string | RegExp\u0026gt; 默认值：[] Vue.config.ignoredElements = [ \u0026#39;my-custom-web-component\u0026#39;, \u0026#39;another-web-component\u0026#39;, // 用一个 `RegExp` 忽略所有“ion-”开头的元素 // 仅在 2.5+ 支持 /^ion-/ ] keyCodes # 给 v-on 自定义键位别名\n类型：{ [key: string]: number | Array\u0026lt;number\u0026gt; } 默认值：{} Vue.config.keyCodes = { v: 86, f1: 112, // camelCase 不可用 mediaPlayPause: 179, // 取而代之的是 kebab-case 且用双引号括起来 \u0026#34;media-play-pause\u0026#34;: 179, up: [38, 87] } performance # 2.2.0 新增，设置为 true 以在浏览器开发工具的性能/时间线面板中启用对组件初始化、编译、渲染和打补丁的性能追踪。只适用于开发模式和支持 performance.mark API 的浏览器上\n类型：boolean 默认值：false (自 2.2.3 起) productionTip # 2.2.0 新增，设置为 false 以阻止 vue 在启动时生成生产提示\n类型：boolean 默认值：true 全局API # Vue.set # Vue.set( target, propertyName/index, value )\n参数： {Object | Array} target {string | number} propertyName/index {any} value 返回值：设置的值。 用法：向响应式对象中添加一个 property，并确保这个新 property 同样是响应式的，且触发视图更新。它必须用于向响应式对象上添加新 property，因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi') 注意：对象不能是 Vue 实例，或者 Vue 实例的根数据对象。 Vue.extend # Vue.extend( options )\n参数：\n{Object} options 用法：\n使用基础 Vue 构造器，创建一个子类。参数是一个包含组件选项的对象。\ndata 选项是特例，需要注意 - 在 Vue.extend() 中它必须是函数\n// 创建构造器 var Profile = Vue.extend({ template: \u0026#39;\u0026lt;p\u0026gt;{{firstName}} {{lastName}} aka {{alias}}\u0026lt;/p\u0026gt;\u0026#39;, data: function () { return { firstName: \u0026#39;Walter\u0026#39;, lastName: \u0026#39;White\u0026#39;, alias: \u0026#39;Heisenberg\u0026#39; } }, //datak //data(){ //} }) // 创建 Profile 实例，并挂载到一个元素上。 new Profile().$mount(\u0026#39;#mount-point\u0026#39;) Vue.component # Vue.component( id, [definition] )\n参数：\n{string} id {Function | Object} [definition] 用法：\n注册或获取全局组件。注册还会自动使用给定的 id 设置组件的名称。\n// 注册组件，传入一个扩展过的构造器 Vue.component(\u0026#39;my-component\u0026#39;, Vue.extend({ /* ... */ })) // 注册组件，传入一个选项对象 (自动调用 Vue.extend) Vue.component(\u0026#39;my-component\u0026#39;, { /* ... */ }) // 获取注册的组件 (始终返回构造器) var MyComponent = Vue.component(\u0026#39;my-component\u0026#39;) ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/bafd68f1/b2321cb9/43393935/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e全局配置 \n    \u003cdiv id=\"全局配置\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%85%a8%e5%b1%80%e9%85%8d%e7%bd%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eVue.config\u003c/code\u003e 是一个对象，包含 Vue 的全局配置。\u003c/p\u003e","title":"2、API","type":"posts"},{"content":" IOC与DI # Spring的IOC（Inversion of Control，控制反转）和DI（Dependency Injection，依赖注入）是Spring框架的核心概念，用于实现松耦合和可维护的应用程序。\nIOC（控制反转）意味着将对象的创建、组装和管理的控制权从应用程序代码转移到框架（Spring）中。传统的编程方式中，对象之间的依赖关系通常通过对象自己创建和管理其他对象实例来实现。而在Spring中，对象的创建和组装由Spring容器来完成，通过IOC容器，对象的依赖关系得以解耦，使得应用程序更加灵活、可扩展和可维护。 DI（依赖注入）是IOC的一种实现方式。它通过在对象之间注入依赖关系，即通过构造函数、属性的Setter方法或接口的实现来向对象提供它所依赖的其他对象。这样，对象不再负责自己创建和管理依赖对象，而是通过IOC容器将依赖关系注入进来。 基本概念 # 容器（Container）：Spring的IOC容器是一个负责创建、装配和管理对象的运行环境。它是Spring框架的核心部分，可以通过XML配置文件、注解或Java代码进行配置。 Bean：在Spring中，对象被称为Bean。Bean是由Spring容器创建、装配和管理的对象实例。 配置元数据（Configuration Metadata）：Spring使用配置元数据来描述和定义Bean及其依赖关系。配置元数据可以使用XML文件、注解或Java代码进行定义。 装配（Assembly）：装配是指将Bean之间的依赖关系建立起来，使它们能够协同工作。Spring提供了多种装配方式，如构造函数注入、属性注入和接口注入。 注入（Injection）：注入是指将依赖对象实例注入到目标对象中。Spring通过DI实现注入，即通过构造函数、属性的Setter方法或接口的实现将依赖关系注入到对象中。 生命周期管理（Lifecycle Management）：Spring容器负责管理Bean的生命周期，包括实例化、初始化、使用和销毁。通过回调方法和生命周期接口，开发人员可以在Bean的生命周期中插入自定义逻辑。 底层原理 # 读取配置：Spring容器会读取配置文件（如XML配置文件）或注解来获取Bean定义和依赖关系的信息。 创建Bean实例：Spring容器根据Bean定义使用反射机制实例化Bean对象。 属性注入：Spring容器根据配置中的依赖关系，将所需的依赖对象注入到目标Bean中，可以通过构造函数注入、Setter方法注入或接口实现注入来实现依赖注入。 初始化和生命周期管理：Spring容器对Bean实例进行初始化，包括调用初始化方法、注册销毁回调等。Spring提供了多种方式来管理Bean的生命周期。 提供Bean：Spring容器将创建好的Bean对象提供给应用程序，应用程序可以通过容器来获取所需的Bean实例。 主要技术 # Spring在实现IOC时运用了一些常见的设计模式，如工厂模式、单例模式和依赖注入模式等。这些设计模式帮助Spring实现了对象的创建、组装和管理，提供了灵活、可扩展的编程模型。\n基于XML配置文件管理Bean # 创建对象 # \u0026lt;bean id=\u0026#34;userBean\u0026#34; class=\u0026#34;org.example.bean.UserBean\u0026#34;\u0026gt;\u0026lt;/bean\u0026gt; 在Spring配置文件中，使用bean标签，添加相应的属性，实现对象的创建\nid：对象的唯一表示，一般为类名并首字母小写 class：创建对象所在类的全类名 name：和id作用相同，只不过name属性可以添加特殊符号如/，id属性不可以 创建对象的时候，默认执行无参的构造\n注入属性 # 注入属性-value # 方式一：Setter方法注入 # 实际上就是Spring在创建对象后，通过调用相应的Setter完成属性的注入\n1、bean类中，创建属性，以及对应的Setter\npublic class Book { private String bookName; public void setBookName(String bookName) { this.bookName = bookName; } } 2、在applicationContext.xml配置文件中，先配置对象的创建，再配置属性\n\u0026lt;bean id=\u0026#34;book\u0026#34; class=\u0026#34;org.example.bean.Book\u0026#34;\u0026gt; \u0026lt;!-- 使用property标签，完成属性注入 name:表示需要注入的属性 value:表示注入属性的值，如果注入的是对象，那么使用ref --\u0026gt; \u0026lt;property name=\u0026#34;bookName\u0026#34; value=\u0026#34;西游记\u0026#34;\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 方式二：通过有参构造注入 # 1、创建类，并提供有参构造\npublic class Person { private String personName; public Person() { } public Person(String personName) { this.personName = personName; } } 2、在Spring配置文件中进行配置\n\u0026lt;bean id=\u0026#34;person\u0026#34; class=\u0026#34;org.example.bean.Person\u0026#34;\u0026gt; \u0026lt;!-- 使用constructor-arg标签，完成使用有参构造属性注入 name：需要注入的属性，可以换成index，按照在有参构造中的属性的索引注入 value：注入属性的值，如果注入的是对象，那么使用ref --\u0026gt; \u0026lt;constructor-arg name=\u0026#34;personName\u0026#34; value=\u0026#34;lucy\u0026#34;\u0026gt;\u0026lt;/constructor-arg\u0026gt; \u0026lt;/bean\u0026gt; 方式三：P命名空间注入 # 实际上是对于Setter注入的一种简化，底层使用的还是Setter\n1、配置文件添加P命名空间xmlns:p=\u0026quot;http://www.springframework.org/schema/p\u0026quot;\n\u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xmlns:p=\u0026#34;http://www.springframework.org/schema/p\u0026#34; xsi:schemaLocation=\u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\u0026#34;\u0026gt; \u0026lt;/beans\u0026gt; 2、在bean标签中，进行操作\n\u0026lt;bean id=\u0026#34;book\u0026#34; class=\u0026#34;org.example.bean.Book\u0026#34; p:bookName=\u0026#34;西游记\u0026#34;\u0026gt;\u0026lt;/bean\u0026gt; property标签 # 在Spring的XML配置文件中，\u0026lt;property\u0026gt;标签用于定义Bean的属性注入。通过\u0026lt;property\u0026gt;标签，可以将值或引用注入到Bean的属性中。\n设置空值 # \u0026lt;property name=\u0026#34;bookName\u0026#34;\u0026gt; \u0026lt;null/\u0026gt; \u0026lt;/property\u0026gt; 值中包含特殊符号 # 例如特殊符号\u0026lt;，可以使用如下两种方式注入值\n方式一，使用转义字符\n\u0026lt;property name=\u0026#34;bookName\u0026#34; value=\u0026#34;\u0026amp;lt;lucy\u0026amp;gt;\u0026#34;\u0026gt;\u0026lt;/property\u0026gt; 方式二，使用CDATA\n\u0026lt;property name=\u0026#34;bookName\u0026#34;\u0026gt; \u0026lt;value\u0026gt; \u0026lt;![CADATA[\u0026lt;lucy\u0026gt;]]\u0026gt; \u0026lt;/value\u0026gt; \u0026lt;/property\u0026gt; 注入属性-bean # 外部bean # 只需要将相关的对象，添加在配置文件中，由Spring创建，然后通过ref属性指定beanId，进行相应的属性（对象）注入，需要在调用类中，添加被调用类对象为属性，并且提供setter，例如，UserService中，需要调用UserDao\npublic class UserDao { public void add(){ System.out.println(\u0026#34;open in UserDao -- add\u0026#34;); } } public class UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void showAdd(){ userDao.add(); } } \u0026lt;bean id=\u0026#34;userDao\u0026#34; class=\u0026#34;org.example.dao.UserDao\u0026#34;\u0026gt;\u0026lt;/bean\u0026gt; \u0026lt;bean id=\u0026#34;userService\u0026#34; class=\u0026#34;org.example.service.UserService\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;userDao\u0026#34; ref=\u0026#34;userDao\u0026#34;\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 内部bean、级联赋值 # 例如有Student、School两个实体类，Student实体类中有School属性，那么可以通过内部bean的方式进行注入，也可以通过外部bean，内部bean方式如下\npublic class School { private String schoolName; public String getSchoolName() { return schoolName; } public void setSchoolName(String schoolName) { this.schoolName = schoolName; } } public class Student { private String studentName; private School school; public String getStudentName() { return studentName; } public void setStudentName(String studentName) { this.studentName = studentName; } public School getSchool() { return school; } public void setSchool(School school) { this.school = school; } } \u0026lt;bean id=\u0026#34;student\u0026#34; class=\u0026#34;org.example.bean.Student\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;studentName\u0026#34; value=\u0026#34;lucy\u0026#34;\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;school\u0026#34;\u0026gt; \u0026lt;bean id=\u0026#34;school\u0026#34; class=\u0026#34;org.example.bean.School\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;schoolName\u0026#34; value=\u0026#34;清华大学\u0026#34;\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; 自动注入 # 用于自动装配Bean的依赖项。它指定了如何解析Bean的依赖关系，常见值如下\nno（默认值）：不自动装配依赖项。需要手动通过\u0026lt;property\u0026gt;或\u0026lt;constructor-arg\u0026gt;标签显式地注入依赖项。 byName：按照属性名称自动装配依赖项。Spring会查找与属性名称相同的Bean，并将其注入到相应的属性中。 byType：按照属性类型自动装配依赖项。Spring会查找与属性类型兼容的Bean，并将其注入到相应的属性中。如果存在多个兼容的Bean，将抛出异常。 constructor：通过构造函数自动装配依赖项。Spring会根据构造函数的参数类型查找与之兼容的Bean，并自动将其注入到构造函数中。 default：会根据情况自动确定使用哪种自动装配方式。 public class Family { private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public String toString() { return \u0026#34;Family{\u0026#34; + \u0026#34;user=\u0026#34; + user + \u0026#39;}\u0026#39;; } } public class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return \u0026#34;User{\u0026#34; + \u0026#34;name=\u0026#39;\u0026#34; + name + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#39;}\u0026#39;; } } \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; standalone=\u0026#34;no\u0026#34;?\u0026gt; \u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\u0026#34; \u0026gt; \u0026lt;!-- 创建bean --\u0026gt; \u0026lt;bean id=\u0026#34;user\u0026#34; class=\u0026#34;top.ygang.springdemo.User\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;name\u0026#34; value=\u0026#34;grady\u0026#34;/\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;!-- 创建bean，并使用autowire注入属性 --\u0026gt; \u0026lt;bean id=\u0026#34;family\u0026#34; class=\u0026#34;top.ygang.springdemo.Family\u0026#34; autowire=\u0026#34;byType\u0026#34;/\u0026gt; \u0026lt;/beans\u0026gt; 作用域 # 使用bean标签的scope属性进行指定Bean的作用域，控制Bean的生命周期和可见性。\nsingleton：默认值，单例模式，表示在整个应用程序中只创建一个Bean实例。 prototype：每次请求时都创建一个新的Bean实例，每次在获得才会被创建，每次创建都是新的对象。 request：在每个HTTP请求中创建一个新的Bean实例（仅适用于Web应用）。 session：在每个HTTP会话中创建一个新的Bean实例（仅适用于Web应用）。 \u0026lt;bean scope=\u0026#34;singleton\u0026#34; ... /\u0026gt; 延迟初始化 # 使用bean标签的lazy-init属性指定Bean的延迟初始化。当设置为true时，Bean将在第一次使用时才被初始化，而不是在应用程序启动时立即初始化。\n\u0026lt;bean lazy-init=\u0026#34;true\u0026#34; ... /\u0026gt; depends-on # 使用bean标签的depends-on属性指定Bean依赖的其他Bean的名称。它确保在当前Bean实例化之前，指定的Bean已经被实例化。\n\u0026lt;bean depends-on=\u0026#34;userRepository\u0026#34; ... /\u0026gt; 基于注解的方式管理Bean # 注解 描述 @Component 通常用于普通Java类身上，表示这是一个需要被Spring容器进行管理的组件 @Controller 用于描述表现（控制）层controller的类，这个类需要被Spring容器进行管理，效果等同于@Component @Service 用于描述业务层service的类，这个类需要被Spring容器进行管理，效果等同于@Component @Repository 用于描述持久层mapper（dao）的类，这个类需要被Spring容器进行管理，效果等同于@Component @Configuration 声明配置类，效果等同于@Component @scope 声明该类的作用范围，例如@Scope(\u0026quot;prototype\u0026quot;) @Bean 在配置类的方法上添加注解，方法返回的对象将被注册为bean @Value 注入外部配置文件的值，例如@Value(\u0026quot;${property.name}\u0026quot;) @Qualifier 指定具体的依赖对象，和@Autowired一起使用，通过指定bean的名称或标识符来精确注入依赖对象，例如：@Autowired @Qualifier(\u0026quot;myBean\u0026quot;) @PostConstruct 在需要执行初始化操作的方法上添加注解，在构造函数执行之后执行初始化操作 @PreDestroy 在需要执行清理操作的方法上添加注解，在bean销毁之前执行清理操作 @Autowired 自动装配，由Spring框架提供，按照类型进行自动装配，找不到会抛出异常，可以设置属性required=false找不到也不会抛异常，更具有Spring的特性和功能，例如支持@Qualifier注解进行更精确的依赖注入，可以应用于类的构造方法、字段、Setter方法和任意方法上 @Resource 自动装配，由Java EE提供，先按照名称进行装配，找不到再按类型，可以应用于类的字段、Setter方法和任意方法上，但是不支持构造方法注入 context:component-scan # 以上注解要使用的前提条件，必须在XML配置文件中开启注解扫描，指定要扫描的基础包，可以使用逗号分隔多个包名\n\u0026lt;context:component-scan base-package=\u0026#34;top.ygang.controller,top.ygang.service\u0026#34; /\u0026gt; 标签指定了要扫描的基础包为top.ygang。Spring将在该包及其子包下扫描所有被注解标记的组件，并将其注册到容器中。\n过滤条件，可以使用\u0026lt;context:include-filter\u0026gt;和\u0026lt;context:exclude-filter\u0026gt;来设置扫描的过滤条件\n\u0026lt;context:component-scan base-package=\u0026#34;top.ygang\u0026#34;\u0026gt; \u0026lt;!-- 包含所有以org.springframework.stereotype.Service注解标记的类 --\u0026gt; \u0026lt;context:include-filter type=\u0026#34;annotation\u0026#34; expression=\u0026#34;org.springframework.stereotype.Service\u0026#34;/\u0026gt; \u0026lt;!-- 排除所有以Impl结尾的类 --\u0026gt; \u0026lt;context:exclude-filter type=\u0026#34;regex\u0026#34; expression=\u0026#34;.*Impl$\u0026#34;/\u0026gt; \u0026lt;/context:component-scan\u0026gt; context:annotation-config # \u0026lt;context:annotation-config /\u0026gt; 用于启用基于注解的配置和自动装配的支持，让Spring能够自动解析和处理@Autowired、@Resource等注解，从而实现依赖注入（2.5后的版本没必要加了）\n在Spring2.5版本之前，还用来开启声明周期注解的使用@PostConstruct和@PreDestroy，从Spring 2.5版本开始，这些生命周期注解的支持被默认启用，无需额外的配置。\n@Value # 注意：使用@Value注解时，通常需要将其应用于被Spring管理的组件（例如使用@Component、@Service、@Repository等注解标识的类）中，以便Spring能够扫描到该组件，并在实例化过程中处理@Value注解。\n用于将值注入到类的字段、方法参数或构造函数参数中。它可以用于注入简单类型的值，如字符串、数字，也可以用于注入复杂类型，如对象、集合等\n@Value(\u0026#34;grady\u0026#34;) private String name; // 使用Autowired注解可以在Spring容器创建当前类实例的时候自动调用，并且注入参数 @Autowired public void setName(@Value(\u0026#34;${name}\u0026#34;)String name) { this.name = name; } 也可以从配置文件中读取，Spring内置了解析.properties、.yaml\\yml的文件解析器，用来解析配置文件，需要在Spring配置文件中开启属性文件解析器，多个配置文件可以使用逗号,进行分隔，也可以使用通配符*来指定匹配，例如*.yml\n\u0026lt;context:property-placeholder location=\u0026#34;1.properties,2.yml\u0026#34; /\u0026gt; my.name=grady my: name: grady @Value(\u0026#34;${my.name}\u0026#34;) private String name; Bean的生命周期 # 在传统的Java应用中，bean的生命周期很简单。使用Java关键字new进行bean实例化，然后该bean就可以使用了。一旦该bean不再被使用，则由Java自动进行垃圾回收。\n在Spring框架中，每个Bean都有其生命周期，即它在容器中的创建、初始化和销毁过程中经历的一系列阶段。Spring框架中的Bean生命周期涉及多个阶段，包括实例化、依赖注入、初始化、使用和销毁。下面是Spring Bean的生命周期的详细解释：\n实例化： 当Spring容器启动时，根据配置文件或注解扫描等方式，创建Bean的实例。可以通过构造方法或工厂方法来实例化Bean。 依赖属性注入： 在实例化Bean后，Spring容器会检查Bean的依赖关系，并将所需的依赖注入到Bean中。依赖注入可以通过构造方法注入、Setter方法注入或字段注入来完成。 Aware接口回调： 如果Bean实现了Spring的Aware接口（如BeanNameAware、BeanFactoryAware、ApplicationContextAware等），Spring容器会调用相应的回调方法，使Bean能够获取与Spring容器相关的信息。 初始化前回调（BeanPostProcessor）： Spring容器会调用注册的BeanPostProcessor接口实现类的postProcessBeforeInitialization()方法，可以在Bean初始化之前对Bean进行一些定制和处理 初始化： 在属性注入完成后，Spring容器会调用Bean的初始化方法。Bean的初始化方法可以通过两种方式来定义： 使用@PostConstruct注解标记的方法。 实现InitializingBean接口，并实现其中的afterPropertiesSet()方法。 初始化后回调（BeanPostProcessor）： Spring容器会调用注册的BeanPostProcessor接口实现类的postProcessAfterInitialization()方法，可以在Bean初始化之后对Bean进行一些定制和处理 使用Bean： 初始化完成后，Bean可以被应用程序使用。在这个阶段，Bean会执行业务逻辑，提供所需的功能。 销毁： 在销毁阶段，Spring容器会调用Bean的销毁方法。Bean的销毁方法可以通过两种方式来定义： 使用@PreDestroy注解标记的方法。 实现DisposableBean接口，并实现其中的destroy()方法。 需要注意的是，Bean的销毁阶段只有在Spring容器正常关闭时才会触发，或者在Web应用程序中，当Servlet容器销毁时才会触发。 代码体现 # Spring配置文件\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; standalone=\u0026#34;no\u0026#34;?\u0026gt; \u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xmlns:context=\u0026#34;http://www.springframework.org/schema/context\u0026#34; xsi:schemaLocation=\u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd\u0026#34; \u0026gt; \u0026lt;!-- 开启注解扫描 --\u0026gt; \u0026lt;context:component-scan base-package=\u0026#34;top.ygang\u0026#34;/\u0026gt; \u0026lt;!-- 开启注解支持 --\u0026gt; \u0026lt;context:annotation-config/\u0026gt; \u0026lt;/beans\u0026gt; Bean\n@Component public class User implements ApplicationContextAware, InitializingBean, DisposableBean { private String name; public User(){ System.out.println(\u0026#34;Bean实例化：constructor\u0026#34;); } public String getName() { return name; } @Autowired public void setName(@Value(\u0026#34;grady\u0026#34;) String name) { System.out.println(\u0026#34;Bean依赖注入：@Autowired\u0026#34;); this.name = name; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println(\u0026#34;Aware调用：setApplicationContext\u0026#34;); } @Override public void afterPropertiesSet() throws Exception { System.out.println(\u0026#34;Bean初始化：afterPropertiesSet\u0026#34;); } @Override public void destroy() throws Exception { System.out.println(\u0026#34;Bean销毁：destroy\u0026#34;); } @Override public String toString() { return \u0026#34;User{\u0026#34; + \u0026#34;name=\u0026#39;\u0026#34; + name + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#39;}\u0026#39;; } } BeanPostProcessor\n@Configuration public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println(\u0026#34;初始化前回调：postProcessBeforeInitialization\u0026#34;); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println(\u0026#34;初始化后回调：postProcessAfterInitialization\u0026#34;); return bean; } } main方法\npublic static void main(String[] args) { System.out.println(\u0026#34;容器启动：ClassPathXmlApplicationContext\u0026#34;); ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext(\u0026#34;applicationContext.xml\u0026#34;); User bean = applicationContext.getBean(User.class); System.out.println(\u0026#34;使用Bean\u0026#34;); System.out.println(bean); System.out.println(\u0026#34;容器关闭：ConfigurableApplicationContext.close()\u0026#34;); applicationContext.close(); } 输出结果\n容器启动：ClassPathXmlApplicationContext Bean实例化：constructor Bean依赖注入：@Autowired Aware调用：setApplicationContext 初始化前回调：postProcessBeforeInitialization Bean初始化：afterPropertiesSet 初始化后回调：postProcessAfterInitialization 使用Bean User{name=\u0026#39;grady\u0026#39;} 容器关闭：ConfigurableApplicationContext.close() Bean销毁：destroy ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/c70ae83e/22675b6f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eIOC与DI \n    \u003cdiv id=\"ioc与di\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#ioc%e4%b8%8edi\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eSpring的\u003cstrong\u003eIOC\u003c/strong\u003e（Inversion of Control，控制反转）和\u003cstrong\u003eDI\u003c/strong\u003e（Dependency Injection，依赖注入）是Spring框架的核心概念，用于实现松耦合和可维护的应用程序。\u003c/p\u003e","title":"2、IOC","type":"posts"},{"content":" 关键字与保留字 # 关键字 # 定义：被Java语言赋予了特殊含义，用做专门用途的字符串（单词）\n特点：关键字中所有字母都为小写\n官方地址：https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html\n保留字 # Java保留字：现有Java版本尚未使用，但以后版本可能会作为关键字使用。自己命名标识符时要避免使用这些保留字。\ngoto 、const\n标识符(Identifier) # Java 对各种变量、方法和类等要素命名时使用的字符序列称为标识符。\n凡是自己可以起名字的地方都叫标识符。\n定义合法标识符规则 # 由26个英文字母大小写，0-9 ，_或$组成\n数字不可以开头。\n不可以使用关键字和保留字，但能包含关键字和保留字。\nJava中严格区分大小写，长度无限制。\n标识符不能包含空格。\nJava中的名称命名规范 # 包名：多单词组成时所有字母都小写：xxxyyyzzz\n类名、接口名：多单词组成时，所有单词的首字母大写：XxxYyyZzz\n变量名、方法名：多单词组成时，第一个单词首字母小写，第二个单词开始每个单词首字母大写：xxxYyyZzz\n常量名：所有字母都大写。多单词时每个单词用下划线连接：XXX_YYY_ZZZ\n注意1：在起名字时，为了提高阅读性，要尽量有意义，“见名知意”。\n注意2：java采用unicode字符集，因此标识符也可以使用汉字声明，但是不建议使用。\n变量 # 变量的概念 # 内存中的一个存储区域\n该区域的数据可以在同一类型范围内不断变化\n变量是程序中最基本的存储单元。包含变量类型、变量名和存储的值 。\n变量的作用 # 用于在内存中保存数据\n使用变量注意 # Java中每个变量必须先声明，后使用\n使用变量名来访问这块区域的数据\n变量的作用域：其定义所在的一对{}内\n变量只有在其作用域内才有效\n同一个作用域内，不能定义重名的变量\n声明变量 # 语法：\u0026lt;数据类型\u0026gt; \u0026lt;变量名称\u0026gt;\n例如：int var;\n变量的赋值 # 语法：\u0026lt;变量名称\u0026gt; = \u0026lt;值\u0026gt;\n例如：var = 10;\n声明和赋值变量 # 语法： \u0026lt;数据类型\u0026gt; \u0026lt;变量名\u0026gt; = \u0026lt;初始化值\u0026gt;\n例如：int var = 10;\n连续声明赋值 # 必须是同类型的变量\n例如：int a = 10,b = 20,c = 30;\nclass VariableTest { public static void main(String[] args){ //变量的定义 int myAge = 12; //变量的使用 System.out.println(myAge); //变量的声明 int myNumber; //变量的赋值 myNumber = 1997; //连续声明赋值 int a = 10,b = 20,c = 30; //变量的使用 System.out.println(myNumber); } } 输出 12 1997 变量的分类-按数据类型 # 对于每一种数据都定义了明确的具体数据类型（强类型语言），在内存中分配了不同大小的内存空间。\n整数类型：byte、short、int、long # Java各整数类型有固定的表数范围和字段长度，不受具体OS的影响，以保证java程序的可移植性。\njava的整型常量默认为 int 型，声明long型常量须后加l或L\njava程序中变量通常声明为int型，除非不足以表示较大的数，才使用long\nclass VariableTest1{ public static void main(String[] args){ byte b1 = 24; byte b2 = -128; //b2=128; 编译不通过，byte类型-128~127 System.out.println(b1); System.out.println(b2); short s1 = 128; int s2 = 1234; //声明：long型常量，必须以“l”或“L”结尾 long s3 = 12345678L; System.out.println(s1); System.out.println(s2); System.out.println(s3); } } 输出 24 -128 128 1234 12345678 浮点类型：float、double # 与整数类型类似，Java 浮点类型也有固定的表数范围和字段长度，不受具体操作系统的影响。\n浮点型常量有两种表示形式：\n十进制数形式：如：5.12 512.0f .512 (必须有小数点）\n科学计数法形式:如：5.12e2 512E2 100E-2 float：单精度，尾数可以精确到7位有效数字。很多情况下，精度很难满足需求。\ndouble：双精度，精度是float的两倍。通常采用此类型。\nclass VariableTest1{ public static void main(String[] args){ double s1 = 123.3; System.out.println(s1); //定义float类型值的末尾要以‘f’或‘F’结尾 float s2 = 12.3F; System.out.println(s2); } } 输出 123.3 12.3 字符类型：char # char 型数据用来表示通常意义上字符（2字节）\nJava中的所有字符都使用Unicode编码，故一个字符可以存储一个字母，一个汉字，或其他书面语的一个字符，也因此每个字符占用空间都是（2byte，16bit）\n字符型变量的三种表现形式：\n字符常量是用单引号''括起来的单个字符。例如：char c1 = 'a'; char c2= '中'; char c3 = '9';\nJava中还允许使用转义字符\\来将其后的字符转变为特殊字符型常量。例如：char c3 = ‘\\n’; \\n表示换行符。\n直接使用 Unicode 值来表示字符型常量：'\\uXXXX'。其中，XXXX代表一个十六进制整数。如：\\u000a 表示\\n。\nchar类型是可以进行运算的。因为它都对应有Unicode（整数）。\nclass VariableTest1{ public static void main(String[] args){ char c1 = \u0026#39;a\u0026#39;; System.out.println(c1); char c2 = \u0026#39;中\u0026#39;; System.out.println(c2); } } 输出a中 unicode、utf8、ascii的关系 # ASCII，只有英文、数字、符号 ASCII 只有127个字符，表示英文字母的大小写、数字和一些符号，但由于其他语言用ASCII 编码表示字节不够，例如：常用中文需要两个字节，且不能和ASCII冲突，中国定制了GB2312编码格式，相同的，其他国家的语言也有属于自己的编码格式 Unicode，表示多语言，但是每个字符都是两个字节 由于每个国家的语言都有属于自己的编码格式，在多语言编辑文本中会出现乱码，这样Unicode应运而生，Unicode就是将这些语言统一到一套编码格式中，通常两个字节表示一个字符，而ASCII是一个字节表示一个字符，这样如果你编译的文本是全英文的，用Unicode编码比ASCII编码需要多一倍的存储空间，在存储和传输上就十分不划算。 UTF-8，表示多语言，可以根据字符类型编码为1-6字节 为了解决上述问题，又出现了把Unicode编码转化为“可变长编码”UTF-8编码，UTF-8编码将Unicode字符按数字大小编码为1-6个字节，英文字母被编码成一个字节，常用汉字被编码成三个字节，如果你编译的文本是纯英文的，那么用UTF-8就会非常节省空间，并且ASCII码也是UTF-8的一部分。 转义字符 # 布尔类型：boolean # boolean类型数据只允许取值true和false，无null（1字节）\nboolean 类型用来判断逻辑条件，一般用于程序流程控制：\nif条件控制语句；\nwhile循环控制语句；\ndo-while循环控制语句；\nfor循环控制语句；\n变量的分类-按声明的位置的不同 # 在方法体外，类体内声明的变量称为成员变量。\n在方法体内部声明的变量称为局部变量。\n二者在初始化值方面的异同: # 同：都有生命周期\n异：局部变量除形参外，需显式初始化。\n基本数据类型转换 # 前提**：boolean类型不能与其它数据类型运算。**\n自动类型提升 # 容量小的类型自动转换为容量大的数据类型。数据类型按容 量大小排序为：（容量的大小表示数的范围的大小）\nbyte,short,char之间不会相互转换，他们三者在计算时首先转换为int类型。\nclass VariableTest2{ public static void main(String[] args) { byte b1 = 2; int i1 = 129; //byte i2 = b1 + i1; 解析不通过 int i2 = b1 + i1; //使用容量较大的类型int System.out.println(i2); } } 输出 131 强制类型转换 # 自动类型转换的逆过程，将容量大的数据类型转换为容量小的数据类型。使用时要加上强制转换符：()，但可能造成精度降低或溢出,格外要注意。\nclass VariableTest2{ public static void main(String[] args) { double d1= 12.3; int i1 = (int)d1; //使用强转符（），截断操作损失精度\tSystem.out.println(i1); } } 输出 12 字符串类型：String # String不是基本数据类型，属于引用数据类型，声明String变量值的时候，使用一对双引号\u0026quot;\u0026quot;\n一个字符串可以串接另一个字符串，也可以直接串接其他类型的数据。例如：\n字符串的定义是public final class String{}，所以+拼接字符串并不是在原来的字符串上进行拼接，而是在内存中生成了一个新的字符串，所以如果需要进行频繁的字符串拼接那么使用StringBuffer或StringBuilder更好。\nstr = str + \u0026#34;xyz\u0026#34;; intn = 100; str = str + n; String类型可以和八种基本数据类型进行运算，且只能是连接运算，运算结果仍然是String类型。\nclass VariableTest2{ public static void main(String[] args){ String numberStr = \u0026#34;学号：\u0026#34;; int number = 1997; String info = numberStr + number; System.out.println(numberStr + number); } } 输出学号：1997 String类型索引、长度 # String a = \u0026#34;12345\u0026#34;; //获取字符串的长度 int long = a.length(); //索引指定位置的字符 char b = a.charAt(0); 获取指定索引的数字，并且用来运算 # 可以使用字符减去字符0，获得编码的差\nString a = \u0026#34;12345\u0026#34;; char b = a.charAt(2); int num = b - \u0026#39;0\u0026#39;; 输出 3 比较字符串是否相等 # String a = \u0026#34;yes\u0026#34;; String b = \u0026#34;no\u0026#34;; boolean c = a.equals(b); System.out.println(c); 输出 false 进制 # 所有数字在计算机底层都以二进制形式存在。\n二进制(binary)：0-1 ，满2进1，以0b或0B开头。\n十进制(decimal)：0-9 ，满10进1。\n八进制(octal)：0-7 ，满8进1，以数字0开头表示。\n十六进制(hex)：0-9及A-F，满16进1，以0x或0X开头表示，此处的A-F不区分大小写，如：0x21AF + 1 = 0X21B0\n二进制转换十进制，以及负数的原码、反码、补码\n计算机底层都以补码的方式存储数据！\n位、字节、字符、编码 # 位（Bit）：数据存储的最小单位。每个二进制数字0或者1就是1个位\n字节（Byte）：8个位构成一个字节；即：1 byte(字节)= 8 bit(位)\n字符（char）：a、A、中、+、*、の均表示一个字符\n编码：规定每个“字符”分别用一个字节还是多个字节存储，用哪些字节来存储，这个规定就叫做“编码”。（其实际是对字符集中字符进行编码，即：每个字符用二进制在计算中表示存储）\nASCII 码中，一个英文字母（不分大小写）为一个字节，一个中文汉字为两个字节。 UTF-8 编码中，一个英文字为一个字节，一个中文为三个字节。 Unicode 编码中，中英文都是两个字节。 UTF-16 编码中，一个英文字母字符或一个汉字字符存储都需要 2 个字节（Unicode 扩展区的一些汉字存储需要 4 个字节）。 UTF-32 编码中，世界上任何字符的存储都需要 4 个字节。 运算符 # 运算符是一种特殊的符号，用以表示数据的运算、赋值和比较等。\n算术运算符 # 赋值运算符 # 符号：=\n当=两侧数据类型不一致时，可以使用自动类型转换或使用强制类型转换原则进行处理。\n支持连续赋值。\n比较运算符 # 逻辑运算符 # 变量的类型全部都是boolean类型的\n区分\u0026amp;和\u0026amp;\u0026amp;： # 相同点：\n1、\u0026amp;和\u0026amp;\u0026amp;运算的结果相同\n2、当符号左边是true时，都会运行符号右边的运算\n不同点：\n当符号左边是false时，\u0026amp;符号右边的运算继续执行，\u0026amp;\u0026amp;右边的运算不再执行。\n区别 |和||： # 相同点：\n1、|和||运算的结果相同\n2、当符号左边是false时，都会执行符号右边的运算\n不同点：\n当符号左边是true时，|符号右边的运算继续执行，||右边的运算不再执行。\n异或 # 异或^就是运算符两边值相同则为false，不同则为true\n位运算符 # 结论：\n1、位运算符操作的都是整形数据\n1、\u0026lt;\u0026lt;每向左移一位，相当于*2\n2、\u0026gt;\u0026gt;每向右移一位，相当于/2\n面试题：\n1、最高效的计算2 * 8？ 答：2 \u0026lt;\u0026lt; 3或8 \u0026lt;\u0026lt; 1\n2、int num1 = 10;int num2 = 20; 交换两个变量的值。\nclass AriTest { public static void main(String[] args) { int num1 = 10; int num2 = 20; int temp = num1; num1 = num2; num2 = temp; System.out.println(\u0026#34;num1 = \u0026#34; + num1 + \u0026#34;,num2 = \u0026#34; + num2); } } 输出 num1 = 20，num2 = 10 或者使用位运算符^，k = m ^ n,m= k ^ n = (m ^ n) ^ n\nclass AriTest { public static void main(String[] args) { int num1 = 10; int num2 = 20; inttemp = num1 ^ num2; num2 = temp ^ num2; num1 = temp ^ num1; System.out.println(\u0026#34;num1 = \u0026#34; + num1 + \u0026#34;,num2 = \u0026#34; + num2); } } 输出 num1 = 20，num2 = 10 三元运算符 # 格式: # 说明：\n1、条件表达式的结果为boolean类型\n2、根据条件表达式的结果为ture或false，决定执行表达式1，还是表达式2\n如果结果为true，则执行表达式1\n如果结果为false，则执行表达式2\n3、表达式1和表达式2类型要求是一致的\n4、三元运算符可以嵌套使用\n5、凡是可以使用三元运算符的地方，都可以改写为if\u0026hellip;else\u0026hellip;\n运算符的优先级 # ！ \u0026gt; \u0026amp; \u0026gt; | \u0026gt; \u0026amp;\u0026amp; \u0026gt; ||\n","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/f742d77f/447cf7f6/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e关键字与保留字 \n    \u003cdiv id=\"关键字与保留字\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%85%b3%e9%94%ae%e5%ad%97%e4%b8%8e%e4%bf%9d%e7%95%99%e5%ad%97\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e关键字 \n    \u003cdiv id=\"关键字\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%85%b3%e9%94%ae%e5%ad%97\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e定义：被Java语言赋予了特殊含义，用做专门用途的字符串（单词）\u003c/p\u003e","title":"2、java基础语法（上）：变量与运算符","type":"posts"},{"content":" 流程 # 原生认证流程 # 在前后端分离中，跳过了UsernamePasswordAuthenticationFilter，直接在接口中调用AuthenticationManager的authenticate方法进行校验\n需要解决的问题 # 1、Spring Security自带登录页面，而前后端分离项目不需要，我们只需要提供登录接口就可以了\n2、登录成功应该响应token而不是Session+cookie\n3、不再是传统ssm中的校验逻辑，而是需要我们自己写逻辑去校验\n使用步骤 # 1、添加依赖 # \u0026lt;!-- springboot依赖 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- spring-security依赖 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-security\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- mybatis-plus依赖 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.baomidou\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mybatis-plus-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${mybatis-plus.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- redis依赖 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-data-redis\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、建表，写crud # 创建用户表（user）以及用户表的CRUD，使用MyBatisPlus，我这里user的用户名字段是email\n3、配置用户名查询服务 # 实现UserDetail，对登录实体的方法进行实现\n@Data @AllArgsConstructor public class LoginUser implements UserDetails { private User user; @Override public Collection\u0026lt;? extends GrantedAuthority\u0026gt; getAuthorities() { return null; } @Override public String getPassword() { return this.user.getPassword(); } @Override public String getUsername() { return this.user.getEmail(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } } 实现UserDetailsService，编写查询逻辑\n@Service public class LoginUserDetailService implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { // 此处就可以从数据库根据userName查询用户信息 QueryWrapper\u0026lt;User\u0026gt; queryWrapper = new QueryWrapper\u0026lt;\u0026gt;(); queryWrapper.eq(\u0026#34;email\u0026#34;,userName); User user = userMapper.selectOne(queryWrapper); System.out.println(user); if (Objects.isNull(user)){ throw new UsernameNotFoundException(\u0026#34;用户名不存在\u0026#34;); } // 此处查询用户的权限 // 将查询到的用户封装进LoginUser return new LoginUser(user); } } 4、实现账号密码认证 # 在SecurityConfig中配置密码加密器和注入AuthenticationManager\n@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * 注入bcrypt对密码进行加密 * @return */ @Bean public PasswordEncoder setPasswordEncoder(){ return new BCryptPasswordEncoder(10); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } 实现登录接口\n@RestController @RequestMapping(\u0026#34;/user\u0026#34;) public class UserController { @Autowired private UserService userService; @PostMapping(\u0026#34;/login\u0026#34;) public ResultVO login(@RequestBody User user){ try { String token = userService.login(user); return ResultVO.success(\u0026#34;登录成功\u0026#34;,token); }catch (BadCredentialsException e){ return ResultVO.failed(\u0026#34;邮箱或密码错误，请重新输入！\u0026#34;); }catch (LockedException e){ return ResultVO.failed(\u0026#34;账号被锁定，请联系管理员进行处理！\u0026#34;); }catch (DisabledException e){ return ResultVO.failed(\u0026#34;用户已失效，请联系管理员进行处理！\u0026#34;); }catch (Exception e){ e.printStackTrace(); return ResultVO.failed(\u0026#34;登陆失败，服务器异常！\u0026#34;); } } } @Service public class UserService { @Autowired private AuthenticationManager authenticationManager; public String login(User user) { // AuthenticationManager进行用户认证 // 创建一个认证对象，第一个参数是用户名，第二个参数是密码 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getEmail(),user.getPassword()); //manager的认证方法返回值也是一个认证对象，如果认证失败会抛出异常 Authentication authenticate = authenticationManager.authenticate(authenticationToken); //认证成功的话，可以从返回的认证对象中获取LoginUser LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); //创建一个永久的jwt给用户，自己在redis中维护用户名对应jwtid，用于续签和登出 String jwtId = UUID.randomUUID().toString(); String token = JwtUtil.createToken(loginUser.getUsername(), -1, jwtId); //将token存入Redis RedisFactory.select(0).opsForValue().set(loginUser.getUsername(),jwtId); return token; } } 在SecurityConfig中配置登录路径不拦截\n@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() //这个非常重要，关闭使用session .csrf().disable() //关闭csrf .authorizeRequests() // 认证请求 .antMatchers(\u0026#34;/user/login\u0026#34;).permitAll() // 定义此路径所有人都可以访问 .anyRequest().authenticated(); // 任何请求都需要身份验证 } } 5、配置Token认证过滤器 # 编写Token认证过滤器，对已登录用户的token进行认证，注意：这里建议继承框架提供的OncePerRequestFilter，避免一个请求多次过滤\n@Component public class TokenAuthFilter extends OncePerRequestFilter { @Autowired private RoleMapper roleMapper; @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { // 从请求头中获取jwt String authToken = httpServletRequest.getHeader(\u0026#34;auth-token\u0026#34;); // 如果请求头没有token，直接放行，交给下面的过滤器处理 // 如果当前请求的接口需要认证或权限，下面的过滤器会拦截 if (Objects.isNull(authToken) || !StringUtils.hasText(authToken)){ filterChain.doFilter(httpServletRequest,httpServletResponse); return; } // 验证token，以及redis中是否存在该token if (JwtUtil.verifyToken(authToken)){ Map\u0026lt;String, String\u0026gt; payload = JwtUtil.getPayload(authToken); String id = payload.get(\u0026#34;id\u0026#34;); String sub = payload.get(\u0026#34;sub\u0026#34;); String s = RedisFactory.select(0).opsForValue().get(sub); // 验证jwt中的id和redis中的id是否一致 if (!Objects.isNull(s) \u0026amp;\u0026amp; s.equals(id)) { //完成续签,刷新token的有效时间 RedisFactory.select(0).expire(sub,UserService.JWTID_LIVE, TimeUnit.SECONDS); //获取用户权限信息 List\u0026lt;String\u0026gt; list = roleMapper.selectUserRolesByEmail(sub); List\u0026lt;SimpleGrantedAuthority\u0026gt; authorities = new ArrayList\u0026lt;\u0026gt;(); for (String authority : list){ authorities.add(new SimpleGrantedAuthority(authority)); } // 创建一个认证对象，第一个参数为用户信息，第三个参数为用户权限 UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(sub,null,authorities); // 传入认证对象到SecurityContextHolder中，证明该用户已经认证 SecurityContextHolder.getContext().setAuthentication(token); } } // 放行 filterChain.doFilter(httpServletRequest,httpServletResponse); } } 在SecurityConfig中配置Token过滤器\n@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private TokenAuthFilter tokenAuthFilter; @Override protected void configure(HttpSecurity http) throws Exception { http\t.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() //这个非常重要，关闭使用session .csrf().disable() //关闭csrf .authorizeRequests() // 认证请求 .antMatchers(\u0026#34;/user/login\u0026#34;).permitAll() // 定义此路径所有人都可以访问 .anyRequest().authenticated(); // 任何请求都需要身份验证 // 配置在内置用户名密码认证过滤器之前 http.addFilterBefore(tokenAuthFilter, UsernamePasswordAuthenticationFilter.class); } } 6、退出登录 # @RestController @RequestMapping(\u0026#34;/user\u0026#34;) public class UserController { @Autowired private UserService userService; /** * 退出登录 * @return */ @PostMapping(\u0026#34;/logout\u0026#34;) public ResultVO logout(){ try { userService.logout(); return ResultVO.success(\u0026#34;退出登录成功\u0026#34;); }catch (Exception e){ return ResultVO.failed(\u0026#34;退出登录失败，服务器异常\u0026#34;); } } } @Service public class UserService { /** * 退出登录 */ public void logout() { // 在token认证过滤器里面已经将用户信息存入了SecurityContextHolder // 从SecurityContextHolder取出用户信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // 我们在过滤器中存入的是邮箱，所以获取也是邮箱 String email = (String) authentication.getPrincipal(); RedisFactory.select(0).delete(email); } } 7、授权 # 思路就是，开启注解，然后在Token认证过滤器中，将用户权限查出来，放到UsernamePasswordAuthenticationToken的第三个参数里就可以了\n配置其他登录方式 # 这里以邮件验证码为例\n1、配置过滤器 # 可以仿照原生的UsernamePasswordAuthenticationFilter来写\n注意：如果是前后端分离项目，由于验证是自己在service中手动调用、跳过了过滤器，也就不需要这个过滤器\npublic class EmailCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter{ /** * 表示拦截/user/loginByEmail地址的post请求 */ public EmailCodeAuthenticationFilter() { super(new AntPathRequestMatcher(\u0026#34;/user/loginByEmail\u0026#34;,\u0026#34;POST\u0026#34;)); } @Override public void setAuthenticationManager(AuthenticationManager authenticationManager) { super.setAuthenticationManager(authenticationManager); } /** * 从请求中获取参数，封装为EmailCodeAuthenticationToken然后调用authenticate方法进行认证 * @param request * @param response * @return * @throws AuthenticationException * @throws IOException * @throws ServletException */ @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { //如果不是post请求，直接异常 if (!\u0026#34;POST\u0026#34;.equals(request.getMethod())) { throw new AuthenticationServiceException(\u0026#34;请求方式有误: \u0026#34; + request.getMethod()); } //如果请求的参数格式不是json，直接异常 if (!request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)){ throw new AuthenticationServiceException(\u0026#34;参数不是json：\u0026#34; + request.getMethod()); } //从请求体中获取参数 String email = \u0026#34;\u0026#34;; String code = \u0026#34;\u0026#34;; try { Map\u0026lt;String,String\u0026gt; map = JSONObject.parseObject(String.valueOf(request.getInputStream()),Map.class); email = map.get(\u0026#34;email\u0026#34;); code = map.get(\u0026#34;code\u0026#34;); } catch (IOException e) { throw new AuthenticationServiceException(\u0026#34;参数不对：\u0026#34; + request.getMethod()); } email = email.trim(); //封装为EmailCodeAuthenticationToken EmailCodeAuthenticationToken token = new EmailCodeAuthenticationToken(email,code); //进行验证 return this.getAuthenticationManager().authenticate(token); } } 2、编写认证对象 # 可以仿照原生的UsernamePasswordAuthenticationToken来写，这个类主要封装了验证链过程中需要的主体、是否验证通过等信息\npublic class EmailCodeAuthenticationToken extends AbstractAuthenticationToken { /** * email地址 */ private final Object email; /** * 验证码 */ private final Object code; public EmailCodeAuthenticationToken(Object email, Object code) { super(null); this.email = email; this.code = code; super.setAuthenticated(false); } public EmailCodeAuthenticationToken(Object email, Object code,Collection\u0026lt;? extends GrantedAuthority\u0026gt; authorities) { super(authorities); this.email = email; this.code = code; super.setAuthenticated(true); } @Override public Object getCredentials() { return this.code; } @Override public Object getPrincipal() { return this.email; } } 3、配置验证逻辑 # 可以仿照原生的DaoAuthenticationProvider来写\n这个类有两个方法\nauthenticate这个方法接收一个Authentication的参数，用来对传入的主体信息进行验证\nsupports这个方法，主要用于框架分析不同的Authentication实现类，使用哪个Provider进行验证\npublic class EmailCodeAuthenticationProvider implements AuthenticationProvider { private UserDetailsService userDetailsService; public EmailCodeAuthenticationProvider(UserDetailsService service){ this.userDetailsService = service; } /** * 验证码认证逻辑 * @param authentication * @return * @throws AuthenticationException */ @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { EmailCodeAuthenticationToken token = (EmailCodeAuthenticationToken) authentication; //从token中获取email和验证码code String email = (String) token.getPrincipal(); String code = (String) token.getCredentials(); //从redis中查询该用户的验证码 String s = RedisFactory.select(1).opsForValue().get(email); if (Objects.isNull(s) || !code.equals(s)){ throw new BadCredentialsException(\u0026#34;验证码错误\u0026#34;); } // 验证成功从redis删除验证码保证安全 RedisFactory.select(1).delete(email); // 验证成功就调用我们自己写的LoginUserDetailService来查询用户的相关信息 LoginUser loginUser = (LoginUser) userDetailsService.loadUserByUsername(email); // 将用户信息封装到EmailCodeAuthenticationToken中返回 EmailCodeAuthenticationToken result = new EmailCodeAuthenticationToken(loginUser,null, loginUser.getAuthorities()); return result; } /** * 判断是上面 authenticate 方法的 authentication 参数，是哪种类型 * 如果是EmailCodeAuthenticationToken就使用该provider进行验证 * @param aClass * @return */ @Override public boolean supports(Class\u0026lt;?\u0026gt; aClass) { return (EmailCodeAuthenticationToken.class.isAssignableFrom(aClass)); } } 4、配置到框架 # @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private LoginUserDetailService loginUserDetailService; /** * 配置自定义验证逻辑 * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(loginUserDetailService).passwordEncoder(setPasswordEncoder()); auth.authenticationProvider(emailCodeAuthenticationProvider()); } /** * 注入bcrypt对密码进行加密 * @return */ @Bean public PasswordEncoder setPasswordEncoder(){ return new BCryptPasswordEncoder(10); } /** * 注入邮件验证码登录认证provider * @return */ @Bean public AuthenticationProvider emailCodeAuthenticationProvider(){ EmailCodeAuthenticationProvider provider = new EmailCodeAuthenticationProvider(loginUserDetailService); return provider; } } 5、使用 # 使用的时候和上面账号密码验证的login方法一样，只不过传入authenticationManager.authenticate()方法的认证对象应该使用自定义的EmailCodeAuthenticationToken类型\n","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/82b168e2/7e63148f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e流程 \n    \u003cdiv id=\"流程\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%b5%81%e7%a8%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20220519163824885\"\n    data-zoom-src=\"/posts/3ab7256e/3f5635d6/82b168e2/7e63148f/image/image-20220519163824885.png\"\n    src=\"/posts/3ab7256e/3f5635d6/82b168e2/7e63148f/image/image-20220519163824885.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"2、前后端分离中使用","type":"posts"},{"content":" 引用和指针的区别 # (1)指针：指针是一个变量，只不过这个变量存储的是一个地址，指向内存的一个存储单元；而引用跟原来的变量实质上是同一个东西，只不过是原变量的一个别名而已。如：\nint a=1;int *p=\u0026amp;a;\nint a=1;int \u0026amp;b=a;\n上面定义了一个整形变量和一个指针变量p，该指针变量指向a的存储单元，即p的值是a存储单元的地址。\n而下面2句定义了一个整形变量a和这个整形a的引用b，事实上a和b是同一个东西，在内存占有同一个存储单元。\n(2)可以有const指针，但是没有const引用；\n(3)指针可以有多级，但是引用只能是一级（int **p；合法 而 int \u0026amp;\u0026amp;a是不合法的）\n(4)指针的值可以为空，但是引用的值不能为NULL，并且引用在定义的时候必须初始化；\n(5)指针的值在初始化后可以改变，即指向其它的存储单元，而引用在进行初始化后就不会再改变了。\n(6)\u0026ldquo;sizeof引用\u0026quot;得到的是所指向的变量(对象)的大小，而\u0026quot;sizeof指针\u0026quot;得到的是指针本身的大小；\n(7)指针和引用的自增(++)运算意义不一样；\n引用的基本使用 # **作用： **给变量起别名，也就是新建一个指向，指向原变量的内存空间\n语法： 数据类型 \u0026amp;别名 = 原名\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { int a = 10; //给变量a指向的内存空间起一个别名，新的引用 int \u0026amp; b = a; //int \u0026amp; c = 10; 这么使用不允许，引用必须指向内存存在的空间 cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; b \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 引用使用的注意事项 # 引用必须初始化 引用在初始化后，不可以改变 例子：\n引用必须初始化\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { int a = 10; //引用必须要初始化！！！ int \u0026amp; b; //此时会报错 system(\u0026#34;pause\u0026#34;); return 0; } 引用在初始化后，不可以改变\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { int a = 10; int c = 20; int \u0026amp; b = a; //此时并不是将b改为c的引用，而是将20赋值给b指向的内存，也就是a指向的内存 b = c; cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; // 20 cout \u0026lt;\u0026lt; b \u0026lt;\u0026lt; endl; // 20 system(\u0026#34;pause\u0026#34;); return 0; } 引用做函数参数 # 截至目前，一共有两种参数传递方式，1、值传递，2、地址传递\n现在，可以使用引用传递\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; //值传递 void change1(int a) { a = 20; } //地址传递 void change2(int * a) { *a = 20; } //引用传递 void change3(int \u0026amp; a) { a = 20; } int main() { int a = 10; change1(a); cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; //10，值传递，不会改变入参的源数据 int b = 10; change2(\u0026amp;b); cout \u0026lt;\u0026lt; b \u0026lt;\u0026lt; endl; //20，地址传递，会改变入参的源数据 int c = 10; change3(c); cout \u0026lt;\u0026lt; c \u0026lt;\u0026lt; endl; //20，引用传递，实际上，函数中的引用a，和c指向的是同一块内存中的数据 system(\u0026#34;pause\u0026#34;); return 0; } 引用做函数的返回值 # 作用：引用是可以作为函数的返回值存在的\n注意：不要返回局部变量引用\n用法：函数调用作为左值\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int\u0026amp; test() { static int a = 10; return a; } int main() { int \u0026amp; yy = test(); cout \u0026lt;\u0026lt; yy \u0026lt;\u0026lt; endl; // 10 test() = 100; cout \u0026lt;\u0026lt; yy \u0026lt;\u0026lt; endl; // 100 system(\u0026#34;pause\u0026#34;); return 0; } 引用的本质 # 引用的本质在c++内部实现是一个指针常量\n#include\u0026lt;iostream\u0026gt; using namespace std; //在使用引用的时候，编译器帮我们做了一些操作 int main() { int a = 10; //实际上就是：int * const y = \u0026amp;a; int \u0026amp; y = a; //实际上就是：*y = 20; y = 20; //实际上就是：cout \u0026lt;\u0026lt; *y \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; y \u0026lt;\u0026lt; endl; return 0; } 常量引用 # #include\u0026lt;iostream\u0026gt; using namespace std; //由于引用无法指向常量，所以可以添加const，编译器会帮我们进行一系列操作 int main() { //实际上是：const int temp = 10; int \u0026amp; a = temp; const int \u0026amp; a = 10; //a = 20; 因为添加了const，所以不允许修改 return 0; } #include\u0026lt;iostream\u0026gt; using namespace std; //如果担心函数内修改变量值，那么可以添加const void test(const int \u0026amp; a) { // a = 20; 防止函数内修改 cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; } int main() { int a = 10; test(a); return 0; } 引用传递和指针传递的区别 # 指针传递 # 指针参数传递本质上是值传递，它所传递的是一个地址值。值传递过程中，被调函数的形式参数作为被调函数的局部变量处理，会在栈中开辟内存空间以存放由主调函数传递进来的实参值，从而形成了实参的一个副本（替身）。值传递的特点是，被调函数对形式参数的任何操作都是作为局部变量进行的，不会影响主调函数的实参变量的值（形参指针变了，实参指针不会变）。\n引用传递 # 引用参数传递过程中，被调函数的形式参数也作为局部变量在栈中开辟了内存空间，但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参（本体）的任何操作都被处理成间接寻址，即通过栈中存放的地址访问主调函数中的实参变量（根据别名找到主调函数中的本体）。因此，被调函数对形参的任何操作都会影响主调函数中的实参变量。\n总结 # 引用传递和指针传递是不同的，虽然他们都是在被调函数栈空间上的一个局部变量，但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数，如果改变被调函数中的指针地址，它将应用不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量（地址），那就得使用指向指针的指针或者指针引用。\n从编译的角度来讲，程序在编译时分别将指针和引用添加到符号表上，符号表中记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值，而引用在符号表上对应的地址值为引用对象的地址值（与实参名字不同，地址相同）。符号表生成之后就不会再改，因此指针可以改变其指向的对象（指针变量中的值可以改），而引用对象则不能修改。\n","date":"2024-01-19","externalUrl":null,"permalink":"/posts/d8245fe1/f50d4583/6a3386c8/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e引用和指针的区别 \n    \u003cdiv id=\"引用和指针的区别\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%bc%95%e7%94%a8%e5%92%8c%e6%8c%87%e9%92%88%e7%9a%84%e5%8c%ba%e5%88%ab\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e(1)指针：指针是一个变量，只不过这个变量存储的是一个地址，指向内存的一个存储单元；而引用跟原来的变量实质上是同一个东西，只不过是原变量的一个别名而已。如：\u003c/p\u003e","title":"2、引用","type":"posts"},{"content":" 指针 # 计算机中所有的数据都必须放在内存中，不同类型的数据占用的字节数不一样，例如 int 占用 4 个字节，char 占用 1 个字节。为了正确地访问这些数据，必须为每个字节都编上号码，就像门牌号、身份证号一样，每个字节的编号是唯一的，根据编号可以准确地找到某个字节。\n我们将内存中字节的编号称为地址（Address）或指针（Pointer）。地址从 0 开始依次增加，对于 32 位环境，程序能够使用的内存为 4GB，最小的地址为 0，最大的地址为0XFFFFFFFF。\n所有指针所占内存空间，在32位操作系统下是4个字节，在64位操作系统下是8个字节\n指针变量及取址 # #include\u0026lt;stdio.h\u0026gt; int main(){ int i = 10; int *p = \u0026amp;i; printf(\u0026#34;int类型指针变量p的地址是：%#x\\n\u0026#34;,\u0026amp;p); printf(\u0026#34;int类型变量i的地址是：%#x\\n\u0026#34;,p); } int类型指针变量p的地址是：0x62fe10 int类型变量i的地址是：0x62fe1c p是一个指针变量，存储着变量i的地址，使用取址符\u0026amp;可以获取一个变量的地址，%#x表示以十六进制输出，并加前缀0x\n指针p的类型必须和变量i的类型一致\n指针变量p除了保存变量i的地址，自己也有地址信息\n操作指针 # 解引用 # 解引用就是引用指针指向的变量值，使用符号*\n#include\u0026lt;stdio.h\u0026gt; int main(){ int i = 10; int *p = \u0026amp;i; printf(\u0026#34;指针p指向的变量值为：%d\u0026#34;,*p); return 0; } 指针p指向的变量值为：10 指针运算 # 指针的每一次递增，它其实会指向下一个元素的存储单元。 指针的每一次递减，它都会指向前一个元素的存储单元。 指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度，比如 int 就是 4 个字节。 利用这一点，我们可以利用指针操作数组，因为数组在内存中是一段连续的空间，而且数组变量的指针可以认为是指向数组的首个元素的一个指针变量，而这个指针变量+1以后，指针就会指向下一个元素，例如是int类型的数组，那么指针就会向后移动一个int的长度（4字节）\n#include\u0026lt;stdio.h\u0026gt; int main(){ int arr[] = {1,2,3,4,5}; int *p = arr; int len = sizeof(arr) / sizeof(arr[0]); for(int i = 0;i \u0026lt; len;i++){ printf(\u0026#34;%d\\n\u0026#34;,*p); p++; } return 0; } 函数和指针 # 指针作为参数 # 值传递，不会改变入参变量的实际值，只是把值10传进了函数\n#include\u0026lt;stdio.h\u0026gt; void add(int a){ a += 1; } int main(){ int a = 10; add(a); printf(\u0026#34;%d\\n\u0026#34;,a);//10 return 0; } 指针传递，会改变入参变量的实际值，因为传入函数的是这个变量在内存中的地址\n#include\u0026lt;stdio.h\u0026gt; void add(int *a){ *a += 1; } int main(){ int a = 10; add(\u0026amp;a); printf(\u0026#34;%d\\n\u0026#34;,a);//11 return 0; } 指针作为返回值 # C语言允许函数的返回值是一个指针（地址），我们将这样的函数称为指针函数。\n例如，返回比较两个字符串，返回较长的字符串\n#include\u0026lt;stdio.h\u0026gt; #include\u0026lt;string.h\u0026gt; char *moreLong(char *str1,char *str2){ int len1 = strlen(str1); int len2 = strlen(str2); return len1 \u0026gt; len2? str1 : str2; } int main(){ char s1[] = \u0026#34;hello world\u0026#34;; char s2[] = \u0026#34;hello c\u0026#34;; char *s3 = moreLong(s1,s2); printf(\u0026#34;%s\u0026#34;,s3); return 0; } 函数指针 # 一个函数总是占用一段连续的内存区域，函数名在表达式中有时也会被转换为该函数所在内存区域的首地址，这和数组名非常类似。我们可以把函数的这个首地址（或称入口地址）赋予一个指针变量，使指针变量指向函数所在的内存区域，然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。\n函数指针的定义：returnType (*pointerName)(param list);\nreturnType 为函数返回值类型，pointerName 为指针名称，param list 为函数参数列表。参数列表中可以同时给出参数的类型和名称，也可以只给出参数的类型，省略参数的名称，这一点和函数原型非常类似。\n由于可以声明一个指向函数的指针，那么这个指针就可以作为参数，使这个函数成为回调函数\n#include\u0026lt;stdio.h\u0026gt; int max(int a,int b){ return a \u0026gt; b? a : b; } int main(){ //定义函数指针 int (*m)(int,int) = max; int a = 10; int b = 20; //通过函数指针调用函数 int maxInt = (*m)(a,b); printf(\u0026#34;%d\\n\u0026#34;,maxInt); return 0; } 指针数组 # 如果一个数组中的所有元素保存的都是指针，那么我们就称它为指针数组。\n指针数组的定义：dataType *arrayName[length];\n[ ]的优先级高于*，该定义形式应该理解为，括号里面说明arrayName是一个数组，包含了length个元素，括号外面说明每个元素的类型为dataType *。\n#include\u0026lt;stdio.h\u0026gt; int max(int a,int b){ return a \u0026gt; b? a : b; } int main(){ char s1[] = \u0026#34;hello\u0026#34;; char s2[] = \u0026#34;world\u0026#34;; char *strs[2]; strs[0] = s1; strs[1] = s2; printf(\u0026#34;%s\\n\u0026#34;,strs[0]); printf(\u0026#34;%s\\n\u0026#34;,strs[1]); return 0; } 空指针 # 在变量声明的时候，如果没有确切的地址可以赋值，为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针，NULL 指针是一个定义在标准库中的值为零的常量。\n#include \u0026lt;stdio.h\u0026gt; int main () { int *ptr = NULL; printf(\u0026#34;ptr 的地址是 %p\\n\u0026#34;, ptr ); return 0; } ptr 的地址是 0x0 在大多数的操作系统上，程序不允许访问地址为 0 的内存，因为该内存是操作系统保留的。然而，内存地址 0 有特别重要的意义，它表明该指针不指向一个可访问的内存位置。但按照惯例，如果指针包含空值（零值），则假定它不指向任何东西。\n野指针 # 在使用指针时，要避免野指针的出现： 野指针指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同，野指针无法通过简单地判断是否为 NULL避免，而只能通过养成良好的编程习惯来尽力减少。对野指针进行操作很容易造成程序错误。\n导致野指针的几种情况： 指针变量未初始化 任何指针变量刚被创建时不会自动成为NULL指针，它的缺省值是随机的，它会乱指一气。所以，指针变量在创建的同时应当被初始化，要么将指针设置为NULL，要么让它指向合法的内存。 指针释放后未置空 有时指针在free或delete后未赋值 NULL，便会使人以为是合法的。别看free和delete的名字（尤其是delete），它们只是把指针所指的内存给释放掉，但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL，防止产生“野指针”。 指针操作超越变量作用域 不要返回指向栈内存的指针或引用，因为栈内存在函数结束时会被释放。 常见指针定义 # 定 义 含 义 int *p; p 可以指向 int 类型的数据，也可以指向类似 int arr[n] 的数组。 int **p; p 为二级指针，指向 int * 类型的数据。 int *p[n]; p 为指针数组。[ ] 的优先级高于 *，所以应该理解为 int *(p[n]); int (*p)[n]; p 为二维数组指针。 int *p(); p 是一个函数，它的返回值类型为 int *。 int (*p)(); p 是一个函数指针，指向原型为 int func() 的函数。 内存管理 # C 语言为内存的分配和管理提供了几个函数。这些函数可以在 \u0026lt;stdlib.h\u0026gt; 头文件中找到。\n序号 函数和描述 1 void *calloc(int num, int size); 在内存中动态地分配 num 个长度为 size 的连续空间，并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间，并且每个字节的值都是0。 2 void free(void *address); 该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。 3 void *malloc(int num); 在堆区分配一块指定大小的内存空间，用来存放数据。这块内存空间在函数执行完成后不会被初始化，它们的值是未知的。 4 void *realloc(void *address, int newsize); 该函数重新分配内存，把内存扩展到 newsize。 注意：void * 类型表示未确定类型的指针。C、C++ 规定 void * 类型可以通过类型转换强制转换为任何其它类型的指针。\n","date":"2024-01-19","externalUrl":null,"permalink":"/posts/f571508d/8c7e092d/334a5f79/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e指针 \n    \u003cdiv id=\"指针\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8c%87%e9%92%88\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e计算机中所有的数据都必须放在内存中，不同类型的数据占用的字节数不一样，例如 int 占用 4 个字节，char 占用 1 个字节。为了正确地访问这些数据，必须为每个字节都编上号码，就像门牌号、身份证号一样，每个字节的编号是唯一的，根据编号可以准确地找到某个字节。\u003c/p\u003e","title":"2、指针","type":"posts"},{"content":" 编译和链接 # 平时所说的程序，是指双击后就可以直接运行的程序，这样的程序被称为可执行程序（Executable Program）。在 Windows 下，可执行程序的后缀有.exe和.com（其中.exe比较常见）；在类 UNIX 系统（Linux、Mac OS 等）下，可执行程序没有特定的后缀，系统根据文件的头部信息来判断是否是可执行程序。\n可执行程序的内部是一系列计算机指令和数据的集合，它们都是二进制形式的，CPU 可以直接识别，毫无障碍；但是对于程序员，它们非常晦涩，难以记忆和使用，例如\nputs(\u0026#34;VIP会员\u0026#34;); 对应的可执行文件二进制写法是\n编译（Compile) # C语言代码由固定的词汇按照固定的格式组织起来，简单直观，程序员容易识别和理解，但是对于CPU，C语言代码就是天书，根本不认识，CPU只认识几百个二进制形式的指令。这就需要一个工具，将C语言代码转换成CPU能够识别的二进制指令，这个工具是一个特殊的软件，叫做编译器（Compiler）。\n注意：通常编译后的代码不能直接执行，编译只是把C语言代码转换成机器码，但是还缺少启动代码（startup code）和库代码。\n编译器能够识别代码中的词汇、句子以及各种特定的格式，并将他们转换成计算机能够识别的二进制形式，这个过程称为编译（Compile）。\nC语言的编译器有很多种，不同的平台下有不同的编译器，例如：\nWindows 下常用的是微软开发的 Visual C++，它被集成在 Visual Studio 中，一般不单独使用； Linux 下常用的是 GUN 组织开发的 GCC，很多 Linux 发行版都自带 GCC； Mac 下常用的是 LLVM/Clang，它被集成在 Xcode 中（Xcode 以前集成的是 GCC，后来由于 GCC 的不配合才改为 LLVM/Clang，LLVM/Clang 的性能比 GCC 更加强大）。 链接（Link） # C语言代码经过编译以后，并没有生成最终的可执行文件（.exe 文件），而是生成了一种叫做目标文件（Object File）的中间文件（或者说临时文件）。目标文件也是二进制形式的，它和可执行文件的格式是一样的。对于 Visual C++目标文件的后缀是.obj；对于 GCC目标文件的后缀是.o。\n为什么不会直接生成可执行文件？因为编译只是将我们自己写的代码变成了二进制形式，它还需要和系统组件（比如标准库、动态链接库等）结合起来，这些组件都是程序运行所必须的。\n链接（Link）其实就是一个打包的过程，它将所有二进制形式的目标文件和系统组件组合成一个可执行文件。完成链接的过程也需要一个特殊的软件，叫做链接器（Linker）。\n在有些系统中，必须分别运行编译程序和链接程序，而在另一些系统中，编译器会自动启动链接器，用户只需给出编译命令即可。\n多个源文件中，编译器每次只能编译一个源文件，生成一个目标文件，这个时候，链接器除了将目标文件和系统组件组合起来，还需要将编译器生成的多个目标文件组合起来。\n编译是针对一个源文件的，有多少个源文件就需要编译多少次，就会生成多少个目标文件。\n编译器 # 以前，UNIX C编译器要调用语言定义的cc命令。但是，它没有跟上标准发展的脚步，已经退出了历史舞台。但是，UNIX 系统提供的C编译器通常来自一些其他源，然后以cc命令作为编译器的别名。\n桌面操作系统 # 对于当前主流桌面操作系统而言，可使用 Visual C++（MSVC、VC）、GCC以及 LLVM Clang 这三大编译器。\nMSVC # Visual C++（简称 MSVC）是由微软开发的，只能用于 Windows 操作系统，它不开源。用户可以使用 Visual Studio Community 版本来免费使用它，但是如果要把通过 Visual Studio Community 工具生成出来的应用进行商用，那么就得好好阅读一下微软的许可证和说明书了。而使用 GCC 与 Clang 编译器构建出来的应用一般没有任何限制，程序员可以将应用程序随意发布和进行商用。\nGCC # GNU 项目始于1987 年，是一个开发大量自由UNIX 软件的集合（GNU 的意思是GNU ’s Not UNIX，即GNU 不是UNIX ）。GNU 编译器集合（也被称为GCC ，其中包含GCC C编译器）是该项目的产品之一。GCC 在一个指导委员会的带领下，持续不断地开发，它的C编译器紧跟C标准的改动。GCC 有各种版本以适应不同的硬件平台和操作系统，包括UNIX 、Linux 和Windows 。用gcc命令便可调用GCC C编译器。许多使用gcc 的系统都用cc作为gcc的别名。\nLLVM Clang # LLVM 项目成为cc的另一个替代品。该项目是与编译器相关的开源软件集合，始于伊利诺伊大学2000 年的研究项目。它的Clang编译器处理C代码，可以通过clang调用。有多种版本供不同的平台使用，包括Linux。2012 年，Clang 成为FreeBSD 的默认C编译器。Clang 也对最新的C标准支持得很好。\n嵌入式系统 # 而在嵌入式系统方面，可用的C语言编译器就非常丰富了，比如：\n用于 Keil 公司 51 系列单片机的 Keil C51 编译器； 当前大红大紫的 Arduino 板搭载的开发套件，可用针对 AVR 微控制器的 AVR GCC 编译器； ARM 自己出的 ADS（ARM Development Suite）、RVDS（RealView Development Suite）和当前最新的 DS-5 Studio； DSP 设计商 TI（Texas Instruments）的 CCS（Code Composer Studio）； DSP 设计商 ADI（Analog Devices，Inc.）的 Visual DSP++ 编译器，等等。 通常，用于嵌入式系统开发的编译工具链都没有免费版本，而且一般需要通过国内代理进行购买。所以，这对于个人开发者或者嵌入式系统爱好者而言是一道不低的门槛。\n不过 Arduino 的开发套件是可免费下载使用的，并且用它做开发板连接调试也十分简单。Arduino 所采用的C编译器是基于 GCC 的。\n还有像树莓派（Raspberry Pi）这种迷你电脑可以直接使用 GCC 和 Clang 编译器。此外，还有像 nVidia 公司推出的 Jetson TK 系列开发板也可直接使用 GCC 和 Clang 编译器。树莓派与 Jetson TK 都默认安装了 Linux 操作系统。\n在嵌入式领域，一般比较低端的单片机，比如 8 位的 MCU 所对应的C编译器可能只支持 C90 标准，有些甚至连 C90 标准的很多特性都不支持。因为它们一方面内存小，ROM 的容量也小；另一方面，本身处理器机能就十分有限，有些甚至无法支持函数指针，因为处理器本身不包含通过寄存器做间接过程调用的指令。\n而像 32 位处理器或 DSP，一般都至少能支持 C99 标准，它们本身的性能也十分强大。而像 ARM 出的 RVDS 编译器甚至可用 GNU 语法扩展。\n","date":"2024-01-19","externalUrl":null,"permalink":"/posts/f571508d/fa8095d0/18f9f856/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e编译和链接 \n    \u003cdiv id=\"编译和链接\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%bc%96%e8%af%91%e5%92%8c%e9%93%be%e6%8e%a5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e平时所说的程序，是指双击后就可以直接运行的程序，这样的程序被称为可执行程序（Executable Program）。在 Windows 下，可执行程序的后缀有\u003ccode\u003e.exe\u003c/code\u003e和\u003ccode\u003e.com\u003c/code\u003e（其中\u003ccode\u003e.exe\u003c/code\u003e比较常见）；在类 UNIX 系统（Linux、Mac OS 等）下，可执行程序没有特定的后缀，系统根据文件的头部信息来判断是否是可执行程序。\u003c/p\u003e","title":"2、编译和链接","type":"posts"},{"content":" 简单使用 # 1、创建Web项目 # 创建Java Enterprise项目，选择Maven管理依赖\n注意：由于SpringMVC并没有集成Web容器，所以仍需要依靠Tomcat等Web容器启动\n1、pom.xml # \u0026lt;!-- war包打包 --\u0026gt; \u0026lt;packaging\u0026gt;war\u0026lt;/packaging\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;!-- springmvc --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-webmvc\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;5.2.9.RELEASE\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- Servlet API --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;javax.servlet\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;javax.servlet-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.0.1\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;provided\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;javax.servlet\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jstl\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- jsp --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;javax.servlet.jsp\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jsp-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;provided\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;!-- war包打包插件 --\u0026gt; \u0026lt;build\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven.plugins\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-war-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.3.0\u0026lt;/version\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/build\u0026gt; 2、web.xml # 在web/WEB-INF/目录或webapp/WEB-INF/目录下，修改web.xml文件，配置SpringMVC的前端（核心）控制器\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;web-app xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xmlns=\u0026#34;http://xmlns.jcp.org/xml/ns/javaee\u0026#34; xsi:schemaLocation=\u0026#34;http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd\u0026#34; id=\u0026#34;WebApp_ID\u0026#34; version=\u0026#34;3.1\u0026#34;\u0026gt; \u0026lt;!-- 项目访问路径 --\u0026gt; \u0026lt;display-name\u0026gt;myweb\u0026lt;/display-name\u0026gt; \u0026lt;!-- 配置前端控制器 --\u0026gt; \u0026lt;servlet\u0026gt; \u0026lt;servlet-name\u0026gt;dispatcherServlet\u0026lt;/servlet-name\u0026gt; \u0026lt;servlet-class\u0026gt;org.springframework.web.servlet.DispatcherServlet\u0026lt;/servlet-class\u0026gt; \u0026lt;!-- 开启一个专门用来支持WEB项目的WebApplicationContext容器 --\u0026gt; \u0026lt;init-param\u0026gt; \u0026lt;param-name\u0026gt;contextConfigLocation\u0026lt;/param-name\u0026gt; \u0026lt;!-- 此处为项目编译后spring配置文件路径 --\u0026gt; \u0026lt;param-value\u0026gt;classpath:applicationContext.xml\u0026lt;/param-value\u0026gt; \u0026lt;/init-param\u0026gt; \u0026lt;!-- 正数：项目在启动时就会创建DispatcherServlet；负数：会在第一次访问时创建 --\u0026gt; \u0026lt;load-on-startup\u0026gt;1\u0026lt;/load-on-startup\u0026gt; \u0026lt;/servlet\u0026gt; \u0026lt;servlet-mapping\u0026gt; \u0026lt;servlet-name\u0026gt;dispatcherServlet\u0026lt;/servlet-name\u0026gt; \u0026lt;!--此处使用*.do来标记动态请求，如果使用/的话，会导致静态请求404--\u0026gt; \u0026lt;url-pattern\u0026gt;/\u0026lt;/url-pattern\u0026gt; \u0026lt;/servlet-mapping\u0026gt; \u0026lt;/web-app\u0026gt; 3、applicationContext.xml # \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; standalone=\u0026#34;no\u0026#34;?\u0026gt; \u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:aop=\u0026#34;http://www.springframework.org/schema/aop\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xmlns:context=\u0026#34;http://www.springframework.org/schema/context\u0026#34; xmlns:mvc=\u0026#34;http://www.springframework.org/schema/mvc\u0026#34; xsi:schemaLocation=\u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd\u0026#34;\u0026gt; \u0026lt;!-- 配置自动扫描注解路径 --\u0026gt; \u0026lt;context:component-scan base-package=\u0026#34;top.ygang.*\u0026#34;\u0026gt;\u0026lt;/context:component-scan\u0026gt; \u0026lt;!-- 开启MVC注解支持 --\u0026gt; \u0026lt;mvc:annotation-driven\u0026gt;\u0026lt;/mvc:annotation-driven\u0026gt; \u0026lt;!-- 静态资源处理方式一：交给Tomcat默认的defaultServlet处理 \u0026lt;mvc:default-servlet-handler\u0026gt;\u0026lt;/mvc:default-servlet-handler\u0026gt; --\u0026gt; \u0026lt;!-- 静态资源处理方式二：将静态资源交给SpringMVC处理 mapping:指的是以static开头的url资源路径 location:指的是静态资源存放路径，多个路径可以使用逗号分隔 --\u0026gt; \u0026lt;mvc:resources mapping=\u0026#34;/static/**\u0026#34; location=\u0026#34;classpath:/static/\u0026#34;\u0026gt;\u0026lt;/mvc:resources\u0026gt; \u0026lt;!-- 视图解析器、设置controller转发、重定向路径默认前缀后缀，可选 --\u0026gt; \u0026lt;bean class=\u0026#34;org.springframework.web.servlet.view.InternalResourceViewResolver\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;prefix\u0026#34; value=\u0026#34;/\u0026#34;\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;suffix\u0026#34; value=\u0026#34;.jsp\u0026#34;\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;/beans\u0026gt; 4、编写entity、mapper、service、controller等 # 配置方式 # 在Spring2.5之前的版本，没有加入SpringMVC相关注解，所以自定义Controller需要实现rg.springframework.stereotype.Controller接口，并且实现handleRequest方法，然后在Spring配置文件applicationContext.xml中配置bean\nbean标签对应的name即为该Controller映射的路径\n注意：这种方法基本现在已经没人用了，都是基于注解进行开发的\npublic class TestController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { return new ModelAndView(\u0026#34;test\u0026#34;); } } \u0026lt;bean name=\u0026#34;/test\u0026#34; class=\u0026#34;top.ygang.springmvc_demo.controller.TestController\u0026#34;\u0026gt;\u0026lt;/bean\u0026gt; 注解方式（推荐） # Spring 2.5 版本新增了 Spring MVC 注解功能，用于替换传统的基于 XML 的 Spring MVC 配置。\n不是所有的业务都需要持久层、业务层、控制层，可以根据自己的实际需求搭配\n实体Entity\n一般作为数据库表实体出现，封装数据库表实体数据\npublic class TestEntity { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return \u0026#34;TestEntity{\u0026#34; + \u0026#34;name=\u0026#39;\u0026#34; + name + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, age=\u0026#34; + age + \u0026#39;}\u0026#39;; } } 持久层Dao\n一般作为与数据库交互层出现，主要是数据持久化相关代码逻辑\n@Repository public class TestDao { public TestEntity test(){ TestEntity testEntity = new TestEntity(); testEntity.setAge(18); testEntity.setName(\u0026#34;lucy\u0026#34;); return testEntity; } } 业务层Service\n一般作为实际业务层出现，主要是业务相关代码逻辑\n@Service public class TestService { @Autowired private TestDao testDao; public TestEntity test(){ return testDao.test(); } } info.jsp\n\u0026lt;%@ page contentType=\u0026#34;text/html;charset=UTF-8\u0026#34; language=\u0026#34;java\u0026#34; %\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; name: ${entity.name}\u0026lt;br/\u0026gt; age: ${entity.age} \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 控制层Controller\n控制层中一般不会编写业务逻辑，主要是接收、响应前端数据，以及异常响应的处理\n@Controller public class InfoController{ @Autowired private TestService testService; @GetMapping(\u0026#34;/info\u0026#34;) public ModelAndView info(){ ModelAndView modelAndView = new ModelAndView(\u0026#34;info\u0026#34;); modelAndView.addObject(\u0026#34;entity\u0026#34;,testService.test()); return modelAndView; } } 控制层方法的形参 # 控制层方法的形参，在满足条件的情况下，一般可以由SpringMVC框架自动注入（传参）\n域对象 # 可以直接获取域对象：HttpServletRequest，HttpServletResponse，HttpSession，方便控制请求和响应\n@PostMapping(\u0026#34;/login\u0026#34;) public ModelAndView login(HttpServletRequest request, HttpServletResponse response, HttpSession session) { String loginName = request.getParameter(\u0026#34;loginName\u0026#34;); String password = request.getParameter(\u0026#34;password\u0026#34;); System.out.println(loginName); System.out.println(password); ModelAndView mv = new ModelAndView(); mv.setViewName(\u0026#34;sysmag/main\u0026#34;); return mv; } Cookie # @RequestMapping(\u0026#34;/login\u0026#34;) public String login(@CookieValue(\u0026#34;JSESSIONID\u0026#34;) String sessionID) { System.out.println(sessionID); return \u0026#34;sysmag/main\u0026#34;; } 请求头 # @RequestMapping(\u0026#34;/login\u0026#34;) public String login(@RequestHeader(\u0026#34;User-Agent\u0026#34;) String userAgent) { System.out.println(userAgent); return \u0026#34;sysmag/main\u0026#34;; } RequestEntity # RequestEntity是封装请求报文的一种类型，需要在控制器方法的形参中设置该类型的形参，当前请求的请求报文就会赋值给该形参，可以通过getHeaders()获取请求头信息，通过getBody()获取请求体信息，泛型为请求体数据类型\n@PostMapping(\u0026#34;/test\u0026#34;) public String test(RequestEntity\u0026lt;String\u0026gt; entity){ HttpHeaders headers = entity.getHeaders(); String body = entity.getBody(); System.out.println(headers); System.out.println(body); return \u0026#34;info\u0026#34;; } 接收请求参数 # 除了通过域对象获取请求参数外，我们可以通过SpringMVC封装的参数解析器，接收解析常用类型的请求参数\n1、UrlParams # 参数拼接在url上，例如：http://localhost:8080/test?name=lucy\u0026amp;age=18\n// 可以分别接收每个属性 @GetMapping(\u0026#34;/test\u0026#34;) public String test(String name,int age){ System.out.println(name); System.out.println(age); return \u0026#34;info\u0026#34;; } 可以使用@RequestParam注解指定参数名称，以及是否必传，默认情况下如果少参数会抛出异常\n@GetMapping(\u0026#34;/test\u0026#34;) public String test(@RequestParam(value = \u0026#34;name\u0026#34;,required = false)String name, int age){ System.out.println(name); System.out.println(age); return \u0026#34;info\u0026#34;; } 还可以通过实体类接收\nclass User{ private String name; private int age; public User(){} public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return \u0026#34;User{\u0026#34; + \u0026#34;name=\u0026#39;\u0026#34; + name + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, age=\u0026#34; + age + \u0026#39;}\u0026#39;; } } @GetMapping(\u0026#34;/test\u0026#34;) public String test(User user){ System.out.println(user); return \u0026#34;info\u0026#34;; } 也可以通过Map进行接收，必须使用@RequestParam修饰\n@GetMapping(\u0026#34;/test\u0026#34;) public String test(@RequestParam Map\u0026lt;String,Object\u0026gt; map){ System.out.println(map); return \u0026#34;info\u0026#34;; } urlParams也可以传递数组或List，必须使用@RequestParam修饰\nhttp://localhost:8080/test?id=1\u0026amp;id=2\n@GetMapping(\u0026#34;/test\u0026#34;) public String test(@RequestParam int[] id){ System.out.println(Arrays.toString(id)); return \u0026#34;info\u0026#34;; } @GetMapping(\u0026#34;/test\u0026#34;) public String test(@RequestParam List\u0026lt;Integer\u0026gt; id){ System.out.println(id); return \u0026#34;info\u0026#34;; } 2、PathParams # rest风格参数，例如：http://localhost:8080/test/lucy/18\n@GetMapping(\u0026#34;/test/{name}/{age}\u0026#34;) public String test(@PathVariable(\u0026#34;name\u0026#34;) String name,@PathVariable(\u0026#34;age\u0026#34;) int age){ System.out.println(name); System.out.println(age); return \u0026#34;info\u0026#34;; } 3、BodyParams # 存在于请求体的参数，常见的由application/json、 multipart/form-data、application/x-www-form-urlencoded等\napplication/x-www-form-urlencoded # @PostMapping(\u0026#34;/test\u0026#34;) public String test(String name, int age){ System.out.println(name); System.out.println(age); return \u0026#34;info\u0026#34;; } 也可以使用实体接收\nclass User{ private String name; private int age; public User(){} public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return \u0026#34;User{\u0026#34; + \u0026#34;name=\u0026#39;\u0026#34; + name + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, age=\u0026#34; + age + \u0026#39;}\u0026#39;; } } @PostMapping(\u0026#34;/test\u0026#34;) public String test(User user){ System.out.println(user); return \u0026#34;info\u0026#34;; } 同样支持Map、Array、List\napplication/form-data # 一般作为前端文件上传到后端的方式，SpringMVC的FormHttpMessageConverter不支持读取该Content-Type，所以如果需要解析这种类型\n1、需要引入common-fileupload依赖\n\u0026lt;!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;commons-fileupload\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;commons-fileupload\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.4\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 2、Spring配置文件applicationContext.xml中配置解析器\n\u0026lt;!-- application/multipart-file解析器 --\u0026gt; \u0026lt;bean id=\u0026#34;multipartResolver\u0026#34; class=\u0026#34;org.springframework.web.multipart.commons.CommonsMultipartResolver\u0026#34;\u0026gt; \u0026lt;!-- 配置编码集 --\u0026gt; \u0026lt;property name=\u0026#34;defaultEncoding\u0026#34; value=\u0026#34;utf-8\u0026#34;/\u0026gt; \u0026lt;!-- 单次文件上传时的最大容量，单位：byte eg:100M=1024*1024*100，-1 表示不做限制--\u0026gt; \u0026lt;property name=\u0026#34;maxUploadSize\u0026#34; value=\u0026#34;104857600\u0026#34;/\u0026gt; \u0026lt;!-- 每次读取文件时，以1KB的方式来读取 --\u0026gt; \u0026lt;property name=\u0026#34;maxInMemorySize\u0026#34; value=\u0026#34;1024\u0026#34;/\u0026gt; \u0026lt;/bean\u0026gt; 3、控制层接收文件字段\n@PostMapping(\u0026#34;/test\u0026#34;) public String test(String name,MultipartFile file){ System.out.println(name); System.out.println(file.getOriginalFilename()); return \u0026#34;info\u0026#34;; } application/json # 默认的@RequestBody，只会接收Body中的值，不会进行解析，例如json则需要我们自己解析\n{ \u0026#34;name\u0026#34;: \u0026#34;lucy\u0026#34;, \u0026#34;age\u0026#34;: 18 } @PostMapping(\u0026#34;/test\u0026#34;) public String test(@RequestBody String json){ System.out.println(json); return \u0026#34;info\u0026#34;; } //{ // \u0026#34;name\u0026#34;: \u0026#34;lucy\u0026#34;, // \u0026#34;age\u0026#34;: 18 //} 我们可以定义json消息转换器，来自动解析application/json类型数据\n1、导入jackson依赖\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.fasterxml.jackson.core\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jackson-databind\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.9.6\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 2、使用@RequestBody接收即可\n@PostMapping(\u0026#34;/test\u0026#34;) public String test(@RequestBody Map\u0026lt;String,Object\u0026gt; map){ System.out.println(map); return \u0026#34;info\u0026#34;; } 或者使用实体接收\nclass User{ private String name; private int age; public User(){} public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return \u0026#34;User{\u0026#34; + \u0026#34;name=\u0026#39;\u0026#34; + name + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, age=\u0026#34; + age + \u0026#39;}\u0026#39;; } } @PostMapping(\u0026#34;/test\u0026#34;) public String test(@RequestBody User user){ System.out.println(user); return \u0026#34;info\u0026#34;; } 控制层方法返回类型 # 1、ModelAndView # ModelAndView 是Spring MVC框架中默认的返回类型，Model代表是模型数据，View代表的是需要跳转的页面（但是只是一个逻辑视图的名字），可以通过addObject进行传递数据，可以通过在构造器参数，或setViewName进行页面的跳转，return该对象即可\n@RequestMapping(\u0026#34;/login\u0026#34;) public ModelAndView login(String loginName){ System.out.println(loginName); ModelAndView mv = new ModelAndView(); mv.setViewName(\u0026#34;sysmag/main\u0026#34;); return mv; } 2、String # 默认是转发\nString返回的字符串，就是需要跳转的页面，如果返回值是null，Spring MVC框架将会自动将请求路径作为即将要跳转的页面，所以尽量不要返回 null\n@RequestMapping(\u0026#34;/login\u0026#34;) public String login(String loginName){ System.out.println(loginName); return \u0026#34;sysmag/main\u0026#34;; } 转发 # \u0026quot;forward:/sys/login02\u0026quot;\n注意：forward后一定要是完整路径，可以用来转发控制层的路径\n@RequestMapping(\u0026#34;/login\u0026#34;) public String login(String loginName){ System.out.println(loginName); return \u0026#34;forward:/sys/login02\u0026#34;; } 重定向 # \u0026quot;redirect:/sys/login02\u0026quot;\n@RequestMapping(\u0026#34;/login\u0026#34;) public String login(String loginName){ System.out.println(loginName); return \u0026#34;redirect:/sys/login02\u0026#34;; } 视图参数传递方法 # 1、ModelAndView # ModelAndView可以指定视图文件（.jsp）路径，也可以给视图进行传参\n@GetMapping(\u0026#34;/info\u0026#34;) public ModelAndView info(ModelAndView modelAndView){ User user = new User(); user.setId(3L); user.setUserName(\u0026#34;王五\u0026#34;); modelAndView.addObject(\u0026#34;user\u0026#34;,user); modelAndView.setViewName(\u0026#34;userView\u0026#34;); return modelAndView; } 2、ModelMap # ModelMap对象主要用于传递控制方法处理数据到结果页面，他的作用类似于request对象的setAttribute方法的作用，用来在一个请求过程中传递处理的数据\naddAttribute(String key,Object value);\nmodelmap本身不能设置页面跳转的url地址别名或者物理跳转地址，那么我们可以通过控制器方法的返回值来设置跳转url地址别名或者物理跳转地址。\n@RequestMapping(\u0026#34;/useModelMap\u0026#34;) public String useModelMap(ModelMap modelMap) { User user = new User(); user.setId(3L); user.setUserName(\u0026#34;王五\u0026#34;); modelMap.addAttribute(\u0026#34;user\u0026#34;, user); return \u0026#34;userView\u0026#34;; } 3、Model # ModelMap 主要用于传递控制器方法处理数据到页面。等同于ModelMap。\n@RequestMapping(\u0026#34;/userModel\u0026#34;) public String userModel(Model model) { User user = new User(); user.setId(2L); user.setUserName(\u0026#34;李四\u0026#34;);; model.addAttribute(\u0026#34;user\u0026#34;, user); return \u0026#34;userView\u0026#34;; } HttpMessageConverter # org.springframework.http.converter.HttpMessageConverter，报文消息转换器，主要用来控制层接收、响应消息时，对消息进行解析\npublic interface HttpMessageConverter\u0026lt;T\u0026gt; { // 检查clazz对象是否能转换为mediaType表示的数据类型，这个mediaType是前端页面请求时设定的ContentType格式！ boolean canRead(Class\u0026lt;?\u0026gt; clazz, @Nullable MediaType mediaType); // 如果canRead方法返回值为true则调用read方法将数据进行格式转换！ T read(Class\u0026lt;? extends T\u0026gt; clazz, HttpInputMessage inputMessage); // 检查clazz对象是否能转换为mediaType类型，此时的mediaType表示后端想要响应给前端的数据格式！ boolean canWrite(Class\u0026lt;?\u0026gt; clazz, @Nullable MediaType mediaType); // 如果canWrite返回值为true，则将数据进行格式的转换然后响应！ void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage); // 获取该转换器支持的mediaType，即ContentType List\u0026lt;MediaType\u0026gt; getSupportedMediaTypes(); } 默认情况下SpringMVC框架在配置文件中不进行注解驱动\u0026lt;mvc:annotation-driven\u0026gt;的支持情况下，会默认给应用使用4个实现类用于接受/响应数据的转换，在开启后会增加到7个左右（版本不同）。\n实现类 说明 StringHttpMessageConverter 负责读取、写出String格式的数据 MappingJackson2HttpMessageConverter 利用jackson的ObjectMapper读写Json数据，需要引入jackson依赖 StringHttpMessageConverter 将请求信息转为字符串 FormHttpMessageConverter 可以读写application/x-www-form-urlencoded的数据，但只能用来写multipart/form-data的数据， XmlAwareFormHttpMessageConverter 扩展与FormHttpMessageConverter，如果部分表单属性是XML数据，可用该转换器进行读取 ResourceHttpMessageConverter 读写org.springframework.core.io.Resource对象 BufferedImageHttpMessageConverter 读写BufferedImage对象 ByteArrayHttpMessageConverter 读写二进制数据 SourceHttpMessageConverter 读写java.xml.transform.Source类型的对象 MarshallingHttpMessageConverter 通过Spring的org.springframework,xml.Marshaller和Unmarshaller读写XML消息 Jaxb2RootElementHttpMessageConverter 通过JAXB2读写XML消息，将请求消息转换为标注的XmlRootElement和XmlType连接的类中 MappingJacksonHttpMessageConverter 利用Jackson开源包的ObjectMapper读写JSON数据 RssChannelHttpMessageConverter 读写RSS种子消息 AtomFeedHttpMessageConverter 和RssChannelHttpMessageConverter能够读写RSS种子消息 SpringMVC乱码过滤器 # web.xml文件中添加过滤器\n\u0026lt;filter\u0026gt; \u0026lt;filter-name\u0026gt;CharacterEncodingFilter\u0026lt;/filter-name\u0026gt; \u0026lt;filter-class\u0026gt;org.springframework.web.filter.CharacterEncodingFilter\u0026lt;/filter-class\u0026gt; \u0026lt;init-param\u0026gt; \u0026lt;param-name\u0026gt;encoding\u0026lt;/param-name\u0026gt; \u0026lt;param-value\u0026gt;UTF-8\u0026lt;/param-value\u0026gt; \u0026lt;/init-param\u0026gt; \u0026lt;init-param\u0026gt; \u0026lt;param-name\u0026gt;forceEncoding\u0026lt;/param-name\u0026gt; \u0026lt;param-value\u0026gt;true\u0026lt;/param-value\u0026gt; \u0026lt;/init-param\u0026gt; \u0026lt;/filter\u0026gt; \u0026lt;filter-mapping\u0026gt; \u0026lt;filter-name\u0026gt;CharacterEncodingFilter\u0026lt;/filter-name\u0026gt; \u0026lt;url-pattern\u0026gt;/*\u0026lt;/url-pattern\u0026gt; \u0026lt;/filter-mapping\u0026gt; ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/617a1910/40e82bed/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e简单使用 \n    \u003cdiv id=\"简单使用\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%ae%80%e5%8d%95%e4%bd%bf%e7%94%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e1、创建Web项目 \n    \u003cdiv id=\"1创建web项目\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1%e5%88%9b%e5%bb%baweb%e9%a1%b9%e7%9b%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e创建Java Enterprise项目，选择Maven管理依赖\u003c/p\u003e","title":"2、详细使用","type":"posts"},{"content":" VMware虚拟机安装 # 由于我们一般开发是在Windows上面，所以就需要按照虚拟机软件来运行其他的操作系统\n许可证 # ZF3R0-FHED2-M80TY-8QYGC-NPKYF Centos下载与安装 # 下载系统镜像文件 # https://www.centos.org/download/\nCentos不同版本的区别 # CentOS-7.0-x86_64-DVD-1503-01.iso : 标准安装版，一般下载这个就可以了（推荐） CentOS-7.0-x86_64-NetInstall-1503-01.iso : 网络安装镜像（从网络安装或者救援系统） CentOS-7.0-x86_64-Everything-1503-01.iso: 对完整版安装盘的软件进行补充，集成所有软件。（包含centos7的一套完整的软件包，可以用来安装系统或者填充本地镜像） CentOS-7.0-x86_64-GnomeLive-1503-01.iso: GNOME桌面版 CentOS-7.0-x86_64-KdeLive-1503-01.iso: KDE桌面版 CentOS-7.0-x86_64-livecd-1503-01.iso : 光盘上运行的系统，类拟于winpe CentOS-7.0-x86_64-minimal-1503-01.iso : 精简版，自带的软件最少 安装一个新的虚拟机 # 进入虚拟机安装系统 # 安装完成后配置 # ","date":"2023-11-14","externalUrl":null,"permalink":"/posts/26654e7b/1a145064/0d30346d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eVMware虚拟机安装 \n    \u003cdiv id=\"vmware虚拟机安装\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#vmware%e8%99%9a%e6%8b%9f%e6%9c%ba%e5%ae%89%e8%a3%85\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e由于我们一般开发是在Windows上面，所以就需要按照虚拟机软件来运行其他的操作系统\u003c/p\u003e","title":"2、下载与安装","type":"posts"},{"content":" B+树 # Ｂ＋树是Ｂ树的变种，有着比Ｂ树更高的查询效率。\n特点 # 一个k阶的B+树具有如下几个特征 有k个子树的中间节点包含有k-1个元素（每个节点最多k-1个元素），每个元素不保存数据，只用来索引，所有数据 都保存在叶子节点。 所有的叶子结点中包含了全部元素的信息，及指向含这些元素记录的指针，且叶子结点本身依关键字的大小，自小而大顺序链接。 所有的中间节点元素都同时存在于子节点，在子节点元素中是最大（或最小）元素。 4阶B+树 # 因为是4阶B+树，所以每个节点最多可以存储3个元素\n每一个父节点都会出现在子节点中，是子节点中最大或者最小的元素\nB+树查询 # 除了图中所说的，每个叶子节点都会带有指向下一个叶子节点的指针，形成一个有序链表\n在聚集索引(Clustered Index)中，叶子节点直接包含卫星数据，在非聚集索引(Non Clustered Index)中，叶子节点带有指向卫星数据的指针。\nB+树插入 # 在B+树中，所有记录节点都是按键值的大小顺序存放在同一层的叶节点中，各叶节点用指针连接。因此在进行插入删除操作时，要进行调整，维持其平衡性。\n插入过程\n1、如果是一个空树，那么创建一个叶子节点，然后将记录插入该叶子节点，插入操作结束\n2、如果只有叶子节点，那么会根据key值，找到叶子节点，然后再这个叶子节点中插入记录，如果叶子节点中记录的个数小于等于B+树的阶数-1，那么插入结束；如果大于，就会将这个叶子节点分裂为两个叶子节点，左边的叶子节点包含前阶数/2个记录，右边叶子节点包含剩余的记录，然后将第阶数/2+1个元素进位到父节点，这个父节点的左指针指向左子节点，右指针指向右子节点，插入结束。\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/c343b13e/6eb2df4f/2cc3f3c2/e8d64676/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eB+树 \n    \u003cdiv id=\"b树\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#b%e6%a0%91\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eＢ＋树是Ｂ树的变种，有着比Ｂ树更高的查询效率。\u003c/p\u003e","title":"2、b+树","type":"posts"},{"content":" 变量 # 作用：给一段指定内存空间起名字，方便操作这段内存空间\n声明语法：数据类型 变量名 = 初始值;\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { int a = 10; cout \u0026lt;\u0026lt; \u0026#34;声明了一个变量a，初始值是：\u0026#34; \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 和java不同的是，cpp中的变量在声明的时候必须赋默认值！！！\n常量 # 什么是常量：常量是不可更改的数据\ncpp中有两种方法可以声明常量：\n#define 常量名 常量值：宏常量 通常在文件的上方定义，表示一个常量 const 数据类型 常量名 = 常量值：const修饰的变量 通常在声明变量的前面加const，表示常量 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; //切记，宏常量声明后面不需要带分号; #define day 7 int main() { const int month = 12; cout \u0026lt;\u0026lt; \u0026#34;一星期有：\u0026#34; \u0026lt;\u0026lt; day \u0026lt;\u0026lt; \u0026#34;天\u0026#34; \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;一年有：\u0026#34; \u0026lt;\u0026lt; month \u0026lt;\u0026lt; \u0026#34;月\u0026#34; \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } cpp中的关键字 # 在使用标识符的时候，需要避开这些关键字\nasm do if return typedef auto double inline short typeid bool dynamic_cast int signed typename break else long sizeof union case enum mutable static unsigned catch explicit namespace static_cast using char export new struct virtual class extern operator switch void const false private template volatile const_cast float protected this wchar_t continue for public throw while default friend register true delete goto reinterpret_cast try 标识符命名规则 # C++规定给标识符（变量、常量）命名时，有一套自己的规则\n标识符不能是关键字 标识符只能由字母、数字、下划线组成 第一个字符必须为字母或下划线 标识符中字母区分大小写 建议：给标识符命名时，争取做到见名知意的效果，方便自己和他人的阅读\n作用域运算符 # 表明数据、方法的归属性问题\n#include \u0026lt;iostream\u0026gt; using namespace std; //全局变量 int a = 10; int main(){ //局部变量 int a = 20; cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; // 优先在局部变量中寻找，找不到再去找全局变量 cout \u0026lt;\u0026lt; ::a \u0026lt;\u0026lt; endl; // 使用全局变量 return 0; } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/d8245fe1/09e70634/cc9db325/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e变量 \n    \u003cdiv id=\"变量\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8f%98%e9%87%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e作用：给一段指定内存空间起名字，方便操作这段内存空间\u003c/p\u003e","title":"2、c++变量及标识符","type":"posts"},{"content":"字符串编码一直是令人非常头疼的问题，尤其是我们在处理一些不规范的第三方网页的时候。虽然Python提供了Unicode表示的str和bytes两种数据类型，并且可以通过encode()和decode()方法转换，但是，在不知道编码的情况下，对bytes做decode()不好做。\n对于未知编码的bytes，要把它转换成str，需要先“猜测”编码。猜测的方式是先收集各种编码的特征字符，根据特征字符判断，就能有很大概率“猜对”。\n当然，我们肯定不能从头自己写这个检测编码的功能，这样做费时费力。chardet这个第三方库正好就派上了用场。用它来检测编码，简单易用。\n安装 # pip install chardet 使用 # encoding：编码\nconfidence：可信度，也就是正确的概率\nlanguage：语言种类\nimport chardet result = chardet.detect(b\u0026#39;Hello World!\u0026#39;) print(result) \u0026#39;\u0026#39;\u0026#39; {\u0026#39;encoding\u0026#39;: \u0026#39;ascii\u0026#39;, \u0026#39;confidence\u0026#39;: 1.0, \u0026#39;language\u0026#39;: \u0026#39;\u0026#39;} \u0026#39;\u0026#39;\u0026#39; 例子 # import chardet b = \u0026#39;离离原上草，一岁一枯荣\u0026#39;.encode(\u0026#39;gbk\u0026#39;) result = chardet.detect(b) print(result) \u0026#39;\u0026#39;\u0026#39; {\u0026#39;encoding\u0026#39;: \u0026#39;GB2312\u0026#39;, \u0026#39;confidence\u0026#39;: 0.7407407407407407, \u0026#39;language\u0026#39;: \u0026#39;Chinese\u0026#39;} \u0026#39;\u0026#39;\u0026#39; ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/1b37041b/46b8de4c/6a904bf5/","section":"文章","summary":"\u003cp\u003e字符串编码一直是令人非常头疼的问题，尤其是我们在处理一些不规范的第三方网页的时候。虽然Python提供了Unicode表示的\u003ccode\u003estr\u003c/code\u003e和\u003ccode\u003ebytes\u003c/code\u003e两种数据类型，并且可以通过\u003ccode\u003eencode()\u003c/code\u003e和\u003ccode\u003edecode()\u003c/code\u003e方法转换，但是，在不知道编码的情况下，对\u003ccode\u003ebytes\u003c/code\u003e做\u003ccode\u003edecode()\u003c/code\u003e不好做。\u003c/p\u003e","title":"2、chardet","type":"posts"},{"content":"collections是Python内建的一个集合模块，提供了许多有用的集合类。\nnamedtuple # namedtuple是一个函数，它用来创建一个自定义的tuple对象，并且规定了tuple元素的个数，并可以用属性而不是索引来引用tuple的某个元素\n这样一来，我们用namedtuple可以很方便地定义一种数据类型，它具备tuple的不变性，又可以根据属性来引用，使用十分方便\nfrom collections import namedtuple # 用元组tuple定义一个坐标，在获取的时候还需要用下标获取，而且1和2无法分清哪个是x哪个是y a = (1,2) print(\u0026#39;a的x坐标：%s，y坐标：%s\u0026#39; % (a[0],a[1])) # 用namedtuple函数定义一个坐标类 Point = namedtuple(\u0026#39;Point\u0026#39;,[\u0026#39;x\u0026#39;,\u0026#39;y\u0026#39;]) p1 = Point(1,2) p2 = Point(2,3) print(\u0026#39;p1的x坐标：%s，y坐标：%s\u0026#39; % (p1.x,p1.y)) print(\u0026#39;p2的x坐标：%s，y坐标：%s\u0026#39; % (p2.x,p2.y)) \u0026#39;\u0026#39;\u0026#39; a的x坐标：1，y坐标：2 p1的x坐标：1，y坐标：2 p2的x坐标：2，y坐标：3 \u0026#39;\u0026#39;\u0026#39; 验证创建的Point是tuple的一种子类\nfrom collections import namedtuple from typing import Tuple Point = namedtuple(\u0026#39;Point\u0026#39;,[\u0026#39;x\u0026#39;,\u0026#39;y\u0026#39;]) p1 = Point(1,2) print(isinstance(p1,Point)) print(isinstance(p1,Tuple)) \u0026#39;\u0026#39;\u0026#39; True True \u0026#39;\u0026#39;\u0026#39; deque # 使用list存储数据时，按索引访问元素很快，但是插入和删除元素就很慢了，因为list是线性存储，数据量大的时候，插入和删除效率很低。\ndeque是为了高效实现插入和删除操作的双向列表，适合用于队列和栈\nfrom collections import deque dq = deque([\u0026#39;lucy\u0026#39;,\u0026#39;tom\u0026#39;,\u0026#39;james\u0026#39;,\u0026#39;lily\u0026#39;]) dq.append(\u0026#39;young\u0026#39;) dq.appendleft(\u0026#39;mary\u0026#39;) print(dq) print(type(dq)) print(isinstance(dq,list)) print(isinstance(dq,deque)) print(dq[1]) \u0026#39;\u0026#39;\u0026#39; deque([\u0026#39;mary\u0026#39;, \u0026#39;lucy\u0026#39;, \u0026#39;tom\u0026#39;, \u0026#39;james\u0026#39;, \u0026#39;lily\u0026#39;, \u0026#39;young\u0026#39;]) \u0026lt;class \u0026#39;collections.deque\u0026#39;\u0026gt; False True lucy \u0026#39;\u0026#39;\u0026#39; defaultdict # 使用dict时，如果使用['key']引用的Key不存在，就会抛出KeyError。如果希望key不存在时，返回一个默认值，就可以用defaultdict\nfrom collections import defaultdict dd = defaultdict(lambda : \u0026#39;N/A\u0026#39;,{\u0026#39;tom\u0026#39;:18,\u0026#39;lucy\u0026#39;:20}) print(dd[\u0026#39;lucy\u0026#39;]) print(dd[\u0026#39;james\u0026#39;]) \u0026#39;\u0026#39;\u0026#39; 20 N/A \u0026#39;\u0026#39;\u0026#39; Conter # Counter是一个简单的计数器，例如，统计字符出现的个数\nfrom collections import Counter msg = \u0026#39;sdaffsdadsasfssdda\u0026#39; counter = Counter() for c in msg: counter[c] += 1 print(counter) \u0026#39;\u0026#39;\u0026#39; Counter({\u0026#39;s\u0026#39;: 6, \u0026#39;d\u0026#39;: 5, \u0026#39;a\u0026#39;: 4, \u0026#39;f\u0026#39;: 3}) \u0026#39;\u0026#39;\u0026#39; ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/1b37041b/9766939c/e4908555/","section":"文章","summary":"\u003cp\u003ecollections是Python内建的一个集合模块，提供了许多有用的集合类。\u003c/p\u003e","title":"2、collections","type":"posts"},{"content":" 整合SpringBoot+CXF # 1、添加依赖 # \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;project xmlns=\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u0026gt; \u0026lt;modelVersion\u0026gt;4.0.0\u0026lt;/modelVersion\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;cxfdemo\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;parent\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-parent\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.2.0.RELEASE\u0026lt;/version\u0026gt; \u0026lt;/parent\u0026gt; \u0026lt;properties\u0026gt; \u0026lt;maven.compiler.source\u0026gt;8\u0026lt;/maven.compiler.source\u0026gt; \u0026lt;maven.compiler.target\u0026gt;8\u0026lt;/maven.compiler.target\u0026gt; \u0026lt;cxf.version\u0026gt;3.2.1\u0026lt;/cxf.version\u0026gt; \u0026lt;/properties\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;!-- webService--\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web-services\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- CXF webservice --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.cxf\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;cxf-spring-boot-starter-jaxws\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${cxf.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.cxf\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;cxf-rt-transports-http\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${cxf.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;/project\u0026gt; 2、编写服务接口和服务实现类 # @WebService( name = \u0026#34;testService\u0026#34;, targetNamespace = \u0026#34;http://top.ygang.test/service\u0026#34;, serviceName = \u0026#34;testService\u0026#34; ) public interface TestService { @WebMethod(operationName = \u0026#34;testMethod\u0026#34;) @WebResult(name = \u0026#34;message\u0026#34;) String testMethod(@WebParam(name = \u0026#34;name\u0026#34;) String name); } @Component @WebService( name = \u0026#34;testService\u0026#34;, targetNamespace = \u0026#34;http://top.ygang.test/service\u0026#34;, serviceName = \u0026#34;testService\u0026#34; ) public class TestServiceImpl implements TestService{ @Override @WebMethod(operationName = \u0026#34;testMethod\u0026#34;) @WebResult(name = \u0026#34;message\u0026#34;) public String testMethod(@WebParam(name = \u0026#34;name\u0026#34;)String name) { return \u0026#34;accept success : \u0026#34; + name; } } 3、编写配置类 # @Configuration public class WSConfig { @Autowired private TestService testService; /** * Apache CXF 核心架构是以BUS为核心，整合其他组件。 * Bus是CXF的主干, 为共享资源提供一个可配置的场所，作用类似于Spring的ApplicationContext，这些共享资源包括 * WSDl管理器、绑定工厂等。通过对BUS进行扩展，可以方便地容纳自己的资源，或者替换现有的资源。默认Bus实现基于Spring架构， * 通过依赖注入，在运行时将组件串联起来。BusFactory负责Bus的创建。默认的BusFactory是SpringBusFactory，对应于默认 * 的Bus实现。在构造过程中，SpringBusFactory会搜索META-INF/cxf（包含在 CXF 的jar中）下的所有bean配置文件。 * 根据这些配置文件构建一个ApplicationContext。开发者也可以提供自己的配置文件来定制Bus。 */ @Bean(name = Bus.DEFAULT_BUS_ID) public SpringBus springBus() { return new SpringBus(); } /** * 此方法作用是改变项目中服务名的前缀名，此处127.0.0.1或者localhost不能访问时，请使用ipconfig查看本机ip来访问 * 此方法被注释后, 即不改变前缀名(默认是services) * * 如果启动时出现错误：not loaded because DispatcherServlet Registration found non dispatcher servlet dispatcherServlet * 可能是springboot与cfx版本不兼容。 * 同时在spring boot2.0.6之后的版本与xcf集成，不需要在定义以下方法，直接在application.properties配置文件中添加： * cxf.path=/service（默认是services） */ // @Bean // public ServletRegistrationBean dispatcherServlet() { // return new ServletRegistrationBean(new CXFServlet(), \u0026#34;/services/*\u0026#34;); // } /** * 此方法的作用是发布服务 */ @Bean public Endpoint endpoint() { EndpointImpl endpoint = new EndpointImpl(springBus(), testService); endpoint.publish(\u0026#34;/\u0026#34;); return endpoint; } } 4、启动类 # @SpringBootApplication public class Start { public static void main(String[] args) { SpringApplication.run(Start.class,args); } } 5、访问浏览器查看wsdl # 访问地址：http://127.0.0.1:8080/services/testService?wsdl\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/578bc70a/66e2c3a9/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e整合SpringBoot+CXF \n    \u003cdiv id=\"整合springbootcxf\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%95%b4%e5%90%88springbootcxf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e1、添加依赖 \n    \u003cdiv id=\"1添加依赖\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1%e6%b7%bb%e5%8a%a0%e4%be%9d%e8%b5%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;project\u003c/span\u003e \u003cspan class=\"na\"\u003exmlns=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"na\"\u003exmlns:xsi=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"na\"\u003exsi:schemaLocation=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;modelVersion\u0026gt;\u003c/span\u003e4.0.0\u003cspan class=\"nt\"\u003e\u0026lt;/modelVersion\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003etop.ygang\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003ecxfdemo\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e1.0-SNAPSHOT\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;parent\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.springframework.boot\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003espring-boot-parent\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e2.2.0.RELEASE\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;/parent\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;properties\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;maven.compiler.source\u0026gt;\u003c/span\u003e8\u003cspan class=\"nt\"\u003e\u0026lt;/maven.compiler.source\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;maven.compiler.target\u0026gt;\u003c/span\u003e8\u003cspan class=\"nt\"\u003e\u0026lt;/maven.compiler.target\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;cxf.version\u0026gt;\u003c/span\u003e3.2.1\u003cspan class=\"nt\"\u003e\u0026lt;/cxf.version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;/properties\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;dependencies\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c\"\u003e\u0026lt;!-- webService--\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.springframework.boot\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003espring-boot-starter-web-services\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c\"\u003e\u0026lt;!-- CXF webservice --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.cxf\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003ecxf-spring-boot-starter-jaxws\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e${cxf.version}\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.cxf\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003ecxf-rt-transports-http\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e${cxf.version}\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;/dependencies\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/project\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e2、编写服务接口和服务实现类 \n    \u003cdiv id=\"2编写服务接口和服务实现类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#2%e7%bc%96%e5%86%99%e6%9c%8d%e5%8a%a1%e6%8e%a5%e5%8f%a3%e5%92%8c%e6%9c%8d%e5%8a%a1%e5%ae%9e%e7%8e%b0%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@WebService\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;testService\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003etargetNamespace\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;http://top.ygang.test/service\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eserviceName\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;testService\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003einterface\u003c/span\u003e \u003cspan class=\"nc\"\u003eTestService\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@WebMethod\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eoperationName\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;testMethod\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@WebResult\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;message\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003etestMethod\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nd\"\u003e@WebParam\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Component\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nd\"\u003e@WebService\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;testService\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003etargetNamespace\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;http://top.ygang.test/service\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eserviceName\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;testService\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eTestServiceImpl\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eimplements\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eTestService\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Override\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@WebMethod\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eoperationName\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;testMethod\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@WebResult\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;message\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003etestMethod\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nd\"\u003e@WebParam\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;accept success : \u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e3、编写配置类 \n    \u003cdiv id=\"3编写配置类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#3%e7%bc%96%e5%86%99%e9%85%8d%e7%bd%ae%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Configuration\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eWSConfig\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Autowired\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eTestService\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etestService\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * Apache CXF 核心架构是以BUS为核心，整合其他组件。\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * Bus是CXF的主干, 为共享资源提供一个可配置的场所，作用类似于Spring的ApplicationContext，这些共享资源包括\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * WSDl管理器、绑定工厂等。通过对BUS进行扩展，可以方便地容纳自己的资源，或者替换现有的资源。默认Bus实现基于Spring架构，\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 通过依赖注入，在运行时将组件串联起来。BusFactory负责Bus的创建。默认的BusFactory是SpringBusFactory，对应于默认\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 的Bus实现。在构造过程中，SpringBusFactory会搜索META-INF/cxf（包含在 CXF 的jar中）下的所有bean配置文件。\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 根据这些配置文件构建一个ApplicationContext。开发者也可以提供自己的配置文件来定制Bus。\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Bean\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eBus\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eDEFAULT_BUS_ID\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eSpringBus\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003espringBus\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eSpringBus\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 此方法作用是改变项目中服务名的前缀名，此处127.0.0.1或者localhost不能访问时，请使用ipconfig查看本机ip来访问\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 此方法被注释后, 即不改变前缀名(默认是services)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     *\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 如果启动时出现错误：not loaded because DispatcherServlet Registration found non dispatcher servlet dispatcherServlet\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 可能是springboot与cfx版本不兼容。\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 同时在spring boot2.0.6之后的版本与xcf集成，不需要在定义以下方法，直接在application.properties配置文件中添加：\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * cxf.path=/service（默认是services）\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//    @Bean\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//    public ServletRegistrationBean dispatcherServlet() {\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//        return new ServletRegistrationBean(new CXFServlet(), \u0026#34;/services/*\u0026#34;);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//    }\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 此方法的作用是发布服务\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Bean\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eEndpoint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eendpoint\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eEndpointImpl\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eendpoint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eEndpointImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003espringBus\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etestService\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eendpoint\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003epublish\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eendpoint\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e4、启动类 \n    \u003cdiv id=\"4启动类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#4%e5%90%af%e5%8a%a8%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@SpringBootApplication\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eStart\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSpringApplication\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003erun\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eStart\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclass\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e5、访问浏览器查看wsdl \n    \u003cdiv id=\"5访问浏览器查看wsdl\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#5%e8%ae%bf%e9%97%ae%e6%b5%8f%e8%a7%88%e5%99%a8%e6%9f%a5%e7%9c%8bwsdl\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e访问地址：\u003ccode\u003ehttp://127.0.0.1:8080/services/testService?wsdl\u003c/code\u003e\u003c/p\u003e","title":"2、CXF","type":"posts"},{"content":" ELK # ELK是ElasiticSearch+LogStash+Kibana的三合一版本\nELK的安装 # 1、使用Docker查询镜像 # docker search elk 2、下载镜像 # docker pull sebp/elk:700 3、查看镜像是否安装成功 # docker images 启动ELK # 1、添加配置文件 # es最大虚拟内存至少262144\necho \u0026#34;vm.max_map_count=262144\u0026#34; \u0026gt; /etc/sysctl.conf 2、启用配置文件 # sysctl -p 3、在Docker中启动ELK镜像 # docker run -dit --name elk -p 5601:5601 -p 9200:9200 -p 5044:5044 -v /root/elk/elk-data:/var/lib/elasticsearch -v /root/elk/elasticsearch/plugins:/opt/elasticsearch/plugins --privileged=true sebp/elk:700 -dit：后台启动、交互式运行、tty终端 --name elk：启动后容器名为elk -p 5601:5601：映射kibana访问端口 -p 9200:9200：映射ES访问端口 -p 5044:5044：映射logstash收集日志端口 -v /opt/elk-data:/var/lib/elasticsearch ：指定ES数据目录 --privileged=true：赋予容器最高的root权限 4、查看容器文件目录并进行kibana汉化 # docker exec -it elk /bin/bash /etc/logstash/：logstash 配置文件路径\n/etc/elasticsearch/：ES配置文件路径\n/var/log/ ：日志路径\n4.1、kibana汉化 # 进入容器后，执行命令\nvi /opt/kibana/config/kibana.yml 在最后一行添加\ni18n.locale: \u0026#34;zh-CN\u0026#34; 保存退出，重启容器\n5、将三个端口配置到防火墙白名单 # firewall-cmd --zone=public --add-port=5601/tcp --permanent firewall-cmd --zone=public --add-port=9200/tcp --permanent firewall-cmd --zone=public --add-port=5044/tcp --permanent #重启防火墙 firewall-cmd --reload 6、测试是否启动成功 # 6.2、浏览器访问宿主机ip:9200 # 6.3、浏览器访问宿主机ip:5601 # ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/d1774b5b/06202fc1/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eELK \n    \u003cdiv id=\"elk\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#elk\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eELK是ElasiticSearch+LogStash+Kibana的三合一版本\u003c/p\u003e","title":"2、ELK安装","type":"posts"},{"content":" HTTP协议 # 超文本传输协议（hypertext transport protocol），服务端和客户端发送数据，需要遵守的协议，传递的数据又称为报文\nHTTP协议的特点 # 1、HTTP协议是基于TCP/IP通信协议来传递数据（HTML文件，图片文件，查询结果等）\n2、HTTP协议的默认端口号是80\n3、HTTP协议是基于请求和响应的模式的协议，一次请求对应一次响应\n4、HTTP协议是一种无连接协议，两次请求之间没有任何关系，互相不干涉\n工作原理 # 1、HTTP协议工作于客户端-服务端架构上，浏览器作为HTTP客户端通过URL向HTTP服务器（Web服务器）发送所有请求。\n2、Web根据接收到的请求，向客户端发送响应信息\nHTTP注意事项 # 1、HTTP是无连接的：每次连接只处理一个请求\n2、HTTP是媒体独立的：任何类型的数据都可以通过HTTP发送，只要告诉服务器就可以\n3、HTTP是无状态的：对于事务处理没有记忆功能，意味着如果后续的处理需要前面的信息，就必须重新传输\nHTTP消息结构 # 请求消息结构 # 请求行（request line） # 格式：请求方法 URI 协议版本 GET /day27/servletDemo1?username=tom HTTP/1.1 请求方法 # HTTP1.0 定义了三种请求方法： GET, POST 和 HEAD 方法。\nHTTP1.1 新增了六种请求方法：OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。\n方法 描述 GET 请求指定的页面信息，并返回实体主体。 HEAD 类似于 GET 请求，只不过返回的响应中没有具体的内容，用于获取报头 POST 向指定资源提交数据进行处理请求（例如提交表单或者上传文件）。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。 PUT 从客户端向服务器传送的数据取代指定的文档的内容。 DELETE 请求服务器删除指定的数据。 CONNECT HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 OPTIONS 允许客户端查看服务器的性能。 TRACE 回显服务器收到的请求，主要用于测试或诊断。 PATCH 是对 PUT 方法的补充，用来对已知资源进行局部更新 。 请求头部（header） # 格式：请求消息头：请求消息值 回车符 换行符\nkey ：value\\r\\n key 说明 举例 Accept 设置接受的内容类型 Accept: text/plain Accept-Charset 设置接受的字符编码 Accept-Charset: utf-8 Accept-Encoding 设置接受的编码格式 Accept-Encoding: gzip, deflate Accept-Datetime 设置接受的版本时间 Accept-Datetime: Thu, 31 May 2007 20:35:00 GMT Accept-Language 设置接受的语言 Accept-Language: en-US Authorization 设置HTTP身份验证的凭证 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== Cache-Control 设置请求响应链上所有的缓存机制必须遵守的指令 Cache-Control: no-cache Connection 设置当前连接和hop-by-hop协议请求字段列表的控制选项 Connection: keep-alive\nConnection: Upgrade Content-Length 设置请求体的字节长度 Content-Length: 348 Content-MD5 设置基于MD5算法对请求体内容进行Base64二进制编码 Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ== Content-Type 设置请求体的MIME类型（适用POST和PUT请求） Content-Type: application/x-www-form-urlencoded Cookie 设置服务器使用Set-Cookie发送的http cookie Cookie: $Version=1; Skin=new; Date 设置消息发送的日期和时间 Date: Tue, 15 Nov 1994 08:12:31 GMT Expect 标识客户端需要的特殊浏览器行为 Expect: 100-continue Forwarded 披露客户端通过http代理连接web服务的源信息 Forwarded: for=192.0.2.60;proto=http;by=203.0.113.43Forwarded: for=192.0.2.43, for=198.51.100.17 From 设置发送请求的用户的email地址 From: user@example.com Host 设置服务器域名和TCP端口号，如果使用的是服务请求标准端口号，端口号可以省略 Host: en.wikipedia.org:8080Host: en.wikipedia.org If-Match 设置客户端的ETag,当时客户端ETag和服务器生成的ETag一致才执行，适用于更新自从上次更新之后没有改变的资源 If-Match: \u0026ldquo;737060cd8c284d8af7ad3082f209582d If-Modified-Since 设置更新时间，从更新时间到服务端接受请求这段时间内如果资源没有改变，允许服务端返回304 Not Modified If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT If-None-Match 设置客户端ETag，如果和服务端接受请求生成的ETage相同，允许服务端返回304 Not Modified If-None-Match: \u0026ldquo;737060cd8c284d8af7ad3082f209582d\u0026rdquo; If-Range 设置客户端ETag，如果和服务端接受请求生成的ETage相同，返回缺失的实体部分；否则返回整个新的实体 If-Range: \u0026ldquo;737060cd8c284d8af7ad3082f209582d\u0026rdquo; If-Unmodified-Since 设置更新时间，只有从更新时间到服务端接受请求这段时间内实体没有改变，服务端才会发送响应 If-Unmodified-Since: Sat, 29 Oct 1994 19:43:31 GMT Max-Forwards 限制代理或网关转发消息的次数 Max-Forwards: 10 Origin 标识跨域资源请求（请求服务端设置Access-Control-Allow-Origin响应字段） Origin: http://www.example-social-network.com Pragma 设置特殊实现字段，可能会对请求响应链有多种影响 Pragma: no-cache Proxy-Authorization 为连接代理授权认证信息 Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== Range 请求部分实体，设置请求实体的字节数范围，具体可以参见HTTP/1.1中的Byte serving Range: bytes=500-999 Referer 设置前一个页面的地址，并且前一个页面中的连接指向当前请求，意思就是如果当前请求是在A页面中发送的，那么referer就是A页面的url地址（轶事：这个单词正确的拼法应该是\u0026quot;referrer\u0026rdquo;,但是在很多规范中都拼成了\u0026quot;referer\u0026quot;，所以这个单词也就成为标准用法） Referer: http://en.wikipedia.org/wiki/Main_Page TE 设置用户代理期望接受的传输编码格式，和响应头中的Transfer-Encoding字段一样 TE: trailers, deflate Upgrade 请求服务端升级协议 Upgrade: HTTP/2.0, HTTPS/1.3, IRC/6.9, RTA/x11, websocket User-Agent 用户代理的字符串值 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/21.0 Via 通知服务器代理请求 Via: 1.0 fred, 1.1 example.com (Apache/1.1) Warning 实体可能会发生的问题的通用警告 Warning: 199 Miscellaneous warning 空行 # 它的用处只是用来区分请求头和请求体的\n请求数据（请求体） # get：由于请求参数是在请求行的URI的后面跟着，所以并不会存储在请求体中，一般我们也会说get请求的请求体是没有的，多个请求参数之间是使用\u0026amp;分隔、连续的。 post：请求参数是不在请求行中的uri后面的，是存储在请求体中的，所以post请求是有请求体的 响应消息结构 # 状态行 # 格式：协议/版本号 状态码\n状态码：描述此次请求和响应的状态\n消息报头（响应头） # 格式：key：value\\r\\n\nkey 说明 举例 Access-Control-Allow-Origin 指定哪些站点可以参与跨站资源共享 Access-Control-Allow-Origin: * Accept-Patch 指定服务器支持的补丁文档格式，适用于http的patch方法 Accept-Patch: text/example;charset=utf-8 Accept-Ranges 服务器通过byte serving支持的部分内容范围类型 Accept-Ranges: bytes Age 对象在代理缓存中暂存的秒数 Age: 12 Allow 设置特定资源的有效行为，适用方法不被允许的http 405错误 Allow: GET, HEAD Alt-Svc 服务器使用\u0026quot;Alt-Svc\u0026quot;（Alternative Servicesde的缩写）头标识资源可以通过不同的网络位置或者不同的网络协议获取 Alt-Svc: h2=\u0026ldquo;http2.example.com:443\u0026rdquo;; ma=7200 Cache-Control 告诉服务端到客户端所有的缓存机制是否可以缓存这个对象，单位是秒 Cache-Control: max-age=3600 Connection 设置当前连接和hop-by-hop协议请求字段列表的控制选项 Connection: close Content-Disposition 告诉客户端弹出一个文件下载框，并且可以指定下载文件名 Content-Disposition: attachment; filename=\u0026ldquo;fname.ext\u0026rdquo; Content-Encoding 设置数据使用的编码类型 Content-Encoding: gzip Content-Language 为封闭内容设置自然语言或者目标用户语言 Content-Language: en Content-Length 响应体的字节长度 Content-Length: 348 Content-Location 设置返回数据的另一个位置 Content-Location: /index.htm Content-MD5 设置基于MD5算法对响应体内容进行Base64二进制编码 Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ== Content-Range 标识响应体内容属于完整消息体中的那一部分 Content-Range: bytes 21010-47021/47022 Content-Type 设置响应体的MIME类型 Content-Type: text/html; charset=utf-8 Date 设置消息发送的日期和时间 Date: Tue, 15 Nov 1994 08:12:31 GMT ETag 特定版本资源的标识符，通常是消息摘要 ETag: \u0026ldquo;737060cd8c284d8af7ad3082f209582d\u0026rdquo; Expires 设置响应体的过期时间 Expires: Thu, 01 Dec 1994 16:00:00 GMT Last-Modified 设置请求对象最后一次的修改日期 Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT Link 设置与其他资源的类型关系 Link: \u0026lt;/feed\u0026gt;; rel=\u0026quot;alternate\u0026quot; Location 在重定向中或者创建新资源时使用 Location: http://www.w3.org/pub/WWW/People.html P3P 以P3P:CP=\u0026ldquo;your_compact_policy\u0026quot;的格式设置支持P3P(Platform for Privacy Preferences Project)策略，大部分浏览器没有完全支持P3P策略，许多站点设置假的策略内容欺骗支持P3P策略的浏览器以获取第三方cookie的授权 P3P: CP=\u0026ldquo;This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en\u0026amp;answer=151657 for more info.\u0026rdquo; Pragma 设置特殊实现字段，可能会对请求响应链有多种影响 Pragma: no-cache Proxy-Authenticate 设置访问代理的请求权限 Proxy-Authenticate: Basic Public-Key-Pins 设置站点的授权TLS证书 Public-Key-Pins: max-age=2592000; pin-sha256=\u0026ldquo;E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\u0026rdquo;; Refresh 重定向或者新资源创建时使用，在页面的头部有个扩展可以实现相似的功能，并且大部分浏览器都支持\u0026lt;meta http-equiv=\u0026quot;refresh\u0026quot; content=\u0026quot;5; url=http://example.com/\u0026quot;\u0026gt; Refresh: 5; url=http://www.w3.org/pub/WWW/People.html Retry-After 如果实体暂时不可用，可以设置这个值让客户端重试，可以使用时间段（单位是秒）或者HTTP时间 Example 1: Retry-After: 120Example 2: Retry-After: Fri, 07 Nov 2014 23:59:59 GMT Server 服务器名称 Server: Apache/2.4.1 (Unix) Set-Cookie 设置HTTP Cookie Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1 Status 设置HTTP响应状态 Status: 200 OK Strict-Transport-Security 一种HSTS策略通知HTTP客户端缓存HTTPS策略多长时间以及是否应用到子域 Strict-Transport-Security: max-age=16070400; includeSubDomains Trailer 标识给定的header字段将展示在后续的chunked编码的消息中 Trailer: Max-Forwards Transfer-Encoding 设置传输实体的编码格式，目前支持的格式： chunked, compress, deflate, gzip, identity Transfer-Encoding: chunked Upgrade 请求客户端升级协议 Upgrade: HTTP/2.0, HTTPS/1.3, IRC/6.9, RTA/x11, websocket Vary 通知下级代理如何匹配未来的请求头已让其决定缓存的响应是否可用而不是重新从源主机请求新的 Example 1: Vary: *Example 2: Vary: Accept-Language Via 通知客户端代理，通过其要发送什么响应 Via: 1.0 fred, 1.1 example.com (Apache/1.1) Warning 实体可能会发生的问题的通用警告 Warning: 199 Miscellaneous warning WWW-Authenticate 标识访问请求实体的身份验证方案 WWW-Authenticate: Basic X-Frame-Options 点击劫持保护\ndeny frame中不渲染sameorigin 如果源不匹配不渲染allow-from 允许指定位置访问allowall 不标准，允许任意位置访问 X-Frame-Options: deny 空行 # 为了区分响应头和响应体的\n响应正文（响应体） # 相应的内容\n常见的状态码 # 1XX：消息 这一类型的状态码，代表请求已被接受，需要继续处理 这类响应是临时响应，只包含状态行和某些可选的响应头信息，并以空行结束 服务器接收到了客户端的请求，但是还没有接收完成，等待了一会，服务器给客户端发送的状态码 2XX：成功 这一类型的状态码，代表请求已成功被服务器接收、理解、并接受。 200：代表没问题，成功 3XX：重定向 这类状态码代表需要客户端采取进一步的操作才能完成请求。 302：重定向 当我们访问服务器端的某一个资源的时候，如果响应状态吗为302，那我们就只到此资源是通过重定向返回给我们的 304：缓存 当我们第一次访问服务器的资源的时候，响应回来的状态是200，当我们再一次访问服务器的该资源的时候，那响应状态就是304；当我们清空缓存之后，再去访问服务器的资源的时候，响应状态码还是200，当我们再一次访问该资源，状态码又会变成304；当访问的资源被更新之后，那我们再去访问该资源的话，响应状态码会变成200 如何确定资源被更新：看资源文件最后修改时间 4XX：请求错误 这类的状态码代表了客户端看起来可能发生了错误，妨碍了服务器的处理 404：资源没找到 一般情况路径写错，或者资源名写错了 405：没有重写doGet()或者doPost() 5XX：服务器错误 这类状态码代表了服务器在处理请求的过程中有错误或者异常状态发生 500：出现了500，说明服务器有错误，而且产生这个问题的原因有很多种情况 URI、URL、URN # URI（Uniform Resource Identifier，统一资源标识符）是一个更通用的术语，它用于标识任何类型的资源。它可以是一个名字、地址或者两者的组合，以便在互联网上唯一地标识一个资源。URI可以用于识别和定位资源，但具体的定位方式可能因资源的类型而有所不同。 URN（Uniform Resource Name，统一资源名称）是URI的一个子集，它被设计用于标识资源的名称而不是其位置。URN的目的是提供一个持久的、独特的标识符，即使资源的位置发生变化也能保持不变。URN通常用于标识需要长期访问的资源，如文档、书籍、文章等。URN的格式一般为\u0026quot;urn:namespace:identifier\u0026rdquo;，其中namespace表示命名空间，identifier表示资源的唯一标识符。 URL（Uniform Resource Locator，统一资源定位器）是URI的另一个子集，它用于定位资源的具体位置。URL包含了访问资源所需的详细信息，如协议类型（如HTTP、FTP）、主机名、路径和查询参数等。URL通常用于标识可以通过特定协议访问的资源，如网页、图片、视频等。 ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/fb007bea/b6f47f2c/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eHTTP协议 \n    \u003cdiv id=\"http协议\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#http%e5%8d%8f%e8%ae%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e超文本传输协议（hypertext transport protocol），服务端和客户端发送数据，需要遵守的协议，传递的数据又称为\u003cstrong\u003e报文\u003c/strong\u003e\u003c/p\u003e","title":"2、Http协议","type":"posts"},{"content":" 准备工作 # 1、查看数据端口，默认5672 # 2、导入依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-amqp\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 3、编写连接工具类 # /** * 连接工具类 */ public class ConnectionUtil { /** * RabbitMQ的工具方法 */ public static Connection getConnection() throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); //主机ip factory.setHost(\u0026#34;localhost\u0026#34;); //数据端口，默认5672 factory.setPort(5672); //账号，默认guest factory.setUsername(\u0026#34;guest\u0026#34;); //密码，默认guest factory.setPassword(\u0026#34;guest\u0026#34;); return factory.newConnection(); } } RabbitMQ提供了2种通讯方式：Queue模式（点对点），发布/订阅模式 Queue模式 # 简单模式（1v1） # 队列模式：跳过交换机，消息的发布者与消费者直接通过队列进行消息的交互 P是我们的生产者，C是我们的消费者。中间的框是一个队列——RabbitMQ代表消费者保存的消息缓冲区 生产者 # public class MyTest { public static final String QUEUE_NAME = \u0026#34;testQueue\u0026#34;; /** * 消息的生产者 */ @Test public void producer(){ Connection connection = null; try { //一、获取连接对象 connection = ConnectionUtil.getConnection(); //二、创建一个消息传输通道 Channel channel = connection.createChannel(); /* * 三、定义一个队列 * 1、队列的名称 * 2、重启RabbitMQ时候，是否需要删除该队列(持久化) * 3、队列是否只能被该连接所独占 * 4、队列在没有连接使用的情况，是否需要删除 * 5、附加参数，一般设置为null */ channel.queueDeclare(QUEUE_NAME,false,false,false,null); String msg = \u0026#34;Hello RabbitMQ!!!\u0026#34;; for (int i = 0; i \u0026lt; 20; i++) { msg += i; //四、发送消息 channel.basicPublish(\u0026#34;\u0026#34;,QUEUE_NAME,null,msg.getBytes()); } System.out.println(\u0026#34;发送完毕\u0026#34;); } catch (Exception e) { e.printStackTrace(); } } } 执行后可以发现队列中，有二十个消息\n消费者 # public class MyConsumer { public static final String QUEUE_NAME = \u0026#34;testQueue\u0026#34;; public static void main(String[] args) { Connection connection = null; try{ //一、获取连接 connection = ConnectionUtil.getConnection(); //二、创建一个消息传输通道 Channel channel = connection.createChannel(); /* * 定义一个队列 * 1、队列的名称 * 2、重启RabbitMQ时候，是否需要删除该队列(持久化) * 3、队列是否只能被该连接所独占 * 4、队列在没有连接使用的情况，是否需要删除 * 5、附加参数，一般设置为null */ channel.queueDeclare(QUEUE_NAME,false,false,false,null); //三、声明一个消费者，定义规则 Consumer consumer = new DefaultConsumer(channel){ /** * 处理MQ交付过来的数据 * @param consumerTag * @param envelope * @param properties * @param body 传递过来的数据 * @throws IOException */ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, \u0026#34;UTF-8\u0026#34;)); // 手动提交 channel.basicAck(envelope.getDeliveryTag(), false); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }; /** * 四、进行消费 * 自动确认true * 手动确认false channel.basicAck(envelope.getDeliveryTag(),false); */ channel.basicConsume(QUEUE_NAME, false, consumer); }catch (Exception e){ e.printStackTrace(); } } } 工作模式（能者多劳work） # 上一个例子是一对一发送接收形式，而工作队列为一对多发送接收形式\n工作队列（即任务队列）背后的主要思想是避免立即执行资源密集型任务，并且必须等待它完成。相反，我们把任务安排在以后做\n我们将任务封装为消息并将其发送到队列。在后台运行的工作进程会弹出任务并最终执行任务。当你运行许多Consumer时，任务将在他们之间共享，如下图\n简单模式的多个消费者存在的问题 # 1、消息会被一次性拿走，如果在消费者消费的过程中，消费者的服务器发生了宕机，那么会造成消息的丢失\n2、多个消费者存在的情况，消息是被平分的，所以每个消费者拿到的消息是相同的，会导致效率高的消费者消费完以后无事可做\n解决方法 # 解决第一个问题，可以使用手动应答（简单模式代码已经实现）\n//所有消费者第二个参数，改为false channel.basicConsume(QUEUE_NAME, false, consumer); /* * 在重写的handleDelivery方法中，每处理完一个消息，进行手动应答 * 第一个参数为消息的下标 * 第二个参数为false：只确认当前消息；true：确认当前消息以及之前的消息 */ channel.basicAck(envelope.getDeliveryTag(),false); 解决第二个问题，可以使用QOS限流\n//参数限制了，当前消费者，每次可以拉去1个消息 channel.basicQos(1); 消费者代码示例 # public class Consumer1 { public static void main(String[] args) { Connection connection = null; try { //获取连接 connection = ConnectionUtil.getConnection(); //创建数据通道 Channel channel = connection.createChannel(); //队列声明 channel.queueDeclare(MyTest.QUEUE_NAME,false,false,false,null); //进行限流 channel.basicQos(1); //声明一个消费者，进行消费 Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(\u0026#34;消费者1：\u0026#34; + new String(body,\u0026#34;utf8\u0026#34;)); //进行手动提交 channel.basicAck(envelope.getDeliveryTag(), false); } }; //启动消费，并且关闭自动提交 channel.basicConsume(MyTest.QUEUE_NAME,false,consumer); }catch (Exception e){ e.printStackTrace(); } } } exchange模式（订阅模式） # direct交换机（路由模式） # 如下图，在这个设置中，我们可以看到与它绑定的两个队列的直接交换X。第一个队列绑定了绑定键橙色，第二个队列有两个绑定，一个绑定键为黑色，另一个为绿色。在这样的设置中，将发送到与路由键橙色的交换的消息将被路由到队列Q1。带有黑色或绿色的路由键的消息将会进入Q2，所有其他消息将被丢弃\n生产者 # public class Producer { //交换机名称，生产者只针对于交换机 public static final String EXCHANGE_NAME = \u0026#34;direc_exchange\u0026#34;; public static void main(String[] args) { Connection connection = null; try{ //获取连接对象 connection = ConnectionUtil.getConnection(); //获取消息通道 Channel channel = connection.createChannel(); /* * 给通道u交换机 * 第1个参数：交换机的名称 * 第2个参数：交换机的类型 direct */ channel.exchangeDeclare(EXCHANGE_NAME,\u0026#34;direct\u0026#34;); for (int i = 0; i \u0026lt; 100; i++) { //不同情况，生成不同的路由键 String routerKey = \u0026#34;\u0026#34;; if (i \u0026lt; 30){ routerKey = \u0026#34;orange\u0026#34;; }else if (i \u0026gt; 70){ routerKey = \u0026#34;black\u0026#34;; }else { routerKey = \u0026#34;green\u0026#34;; } //发送消息到交换机 String msg = \u0026#34;Hello direct!!!\u0026#34; + i; channel.basicPublish(EXCHANGE_NAME,routerKey,null,msg.getBytes()); } }catch (Exception e){ e.printStackTrace(); } } } 消费者1 # public class Consumer1 { public static final String QUEUE_NAME = \u0026#34;con1\u0026#34;; public static void main(String[] args) { Connection connection = null; try{ //获取连接对象 connection = ConnectionUtil.getConnection(); //获取消息通道 Channel channel = connection.createChannel(); //声明队列 channel.queueDeclare(QUEUE_NAME,false,false,false,null); //声明交换机 channel.exchangeDeclare(Producer.EXCHANGE_NAME,\u0026#34;direct\u0026#34;); //绑定队列和交换机之间的关系,可以声明多次，绑定多个关系 channel.queueBind(QUEUE_NAME,Producer.EXCHANGE_NAME,\u0026#34;orange\u0026#34;); //定义消费者 Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(\u0026#34;消费者1：\u0026#34; + new String(body,\u0026#34;utf8\u0026#34;)); //手动提交 channel.basicAck(envelope.getDeliveryTag(),false); } }; //进行消费 channel.basicConsume(QUEUE_NAME,false,consumer); }catch (Exception e){ e.printStackTrace(); } } } 消费者2 # public class Consumer2 { public static final String QUEUE_NAME = \u0026#34;con2\u0026#34;; public static void main(String[] args) { Connection connection = null; try { connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME,false,false,false,null); channel.exchangeDeclare(Producer.EXCHANGE_NAME,\u0026#34;direct\u0026#34;); channel.queueBind(QUEUE_NAME,Producer.EXCHANGE_NAME,\u0026#34;black\u0026#34;); channel.queueBind(QUEUE_NAME,Producer.EXCHANGE_NAME,\u0026#34;green\u0026#34;); Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(\u0026#34;消费者2：\u0026#34; + new String(body,\u0026#34;utf8\u0026#34;)); channel.basicAck(envelope.getDeliveryTag(),false); } }; channel.basicConsume(QUEUE_NAME,false,consumer); }catch (Exception e){ e.printStackTrace(); } } } fanout交换机（广播模式） # 广播模式：发布者将消息通过交换机，直接发布到所有的与该交换机进行绑定了的队列身上 广播模式是没有路由键的，队列与交换机直接绑定 此模式下，每一个队列都会收到同样的所有的消息 生产者 # public class Producer { public static final String EXCHANGE_NAME = \u0026#34;fanout_exchange\u0026#34;; public static void main(String[] args) { Connection connection = null; try{ //获取连接对象 connection = ConnectionUtil.getConnection(); //获取消息通道 Channel channel = connection.createChannel(); //声明交换机 channel.exchangeDeclare(EXCHANGE_NAME,\u0026#34;fanout\u0026#34;); for (int i = 0; i \u0026lt; 100; i++) { String msg = \u0026#34;Hello fanout!!!\u0026#34; + i; //发送消息,没有路由键 channel.basicPublish(EXCHANGE_NAME,\u0026#34;\u0026#34;,null,msg.getBytes()); } }catch (Exception e){ e.printStackTrace(); } } } 消费者1 # public class Consumer1 { public static final String QUEUE_NAME = \u0026#34;con1\u0026#34;; public static void main(String[] args) { Connection connection = null; try { //获取连接 connection = ConnectionUtil.getConnection(); //获取消息通道 Channel channel = connection.createChannel(); //声明队列 channel.queueDeclare(QUEUE_NAME,false,false,false,null); //声明交换机 channel.exchangeDeclare(Producer.EXCHANGE_NAME,\u0026#34;fanout\u0026#34;); //绑定交换机和队列的关系，没有路由键 channel.queueBind(QUEUE_NAME,Producer.EXCHANGE_NAME,\u0026#34;\u0026#34;); //定义消费者 Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(\u0026#34;消费者1：\u0026#34; + new String(body,\u0026#34;utf8\u0026#34;)); //手动提交 channel.basicAck(envelope.getDeliveryTag(),false); } }; //开启消费 channel.basicConsume(QUEUE_NAME,false,consumer); }catch (Exception e){ e.printStackTrace(); } } } 消费者2 # public class Consumer2 { public static final String QUEUE_NAME = \u0026#34;con2\u0026#34;; public static void main(String[] args) { Connection connection = null; try { connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME,false,false,false,null); channel.exchangeDeclare(Producer.EXCHANGE_NAME,\u0026#34;fanout\u0026#34;); channel.queueBind(QUEUE_NAME,Producer.EXCHANGE_NAME,\u0026#34;\u0026#34;); Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(\u0026#34;消费者2：\u0026#34; + new String(body,\u0026#34;utf8\u0026#34;)); channel.basicAck(envelope.getDeliveryTag(),false); } }; channel.basicConsume(QUEUE_NAME,false,consumer); }catch (Exception e){ e.printStackTrace(); } } } topic交换机（主题模式） # 可以理解为Routing的通配符模式，就是将路由模式中消费者的路由键替换为使用通配符的路由键\n如果两个消费者队列绑定的路由键都匹配的情况下，那么exchange会将这个消息同样发送到两个queue\n通配符 #：可以用来表示一个或多个单词 *：可以用来表示一个单词 生产者 # 模拟发送不同的消息，西安的订单、退货，北京的订单、退货，以不同的路由键发送\npublic class Producer { public static final String EXCHANGE_NAME = \u0026#34;topic_exchange\u0026#34;; public static void main(String[] args){ Connection connection = null; try{ connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME,\u0026#34;topic\u0026#34;); for (int i = 0; i \u0026lt; 10; i++) { String msg = \u0026#34;西安的订单\u0026#34; + i; channel.basicPublish(EXCHANGE_NAME,\u0026#34;order.xian\u0026#34;,null,msg.getBytes()); } for (int i = 0; i \u0026lt; 10; i++) { String msg = \u0026#34;北京的订单\u0026#34; + i; channel.basicPublish(EXCHANGE_NAME,\u0026#34;order.beijing\u0026#34;,null,msg.getBytes()); } for (int i = 0; i \u0026lt; 10; i++) { String msg = \u0026#34;西安的退货\u0026#34; + i; channel.basicPublish(EXCHANGE_NAME,\u0026#34;back.xian\u0026#34;,null,msg.getBytes()); } for (int i = 0; i \u0026lt; 10; i++) { String msg = \u0026#34;北京的退货\u0026#34; + i; channel.basicPublish(EXCHANGE_NAME,\u0026#34;back.beijing\u0026#34;,null,msg.getBytes()); } }catch (Exception e){ e.printStackTrace(); }finally { try { connection.close(); } catch (IOException ioException) { ioException.printStackTrace(); } } } } 消费者西安 # 只处理路由键为*.xian的订单以及退货\npublic class ConsumerXian { public static final String QUEUE_NAME = \u0026#34;xian\u0026#34;; public static void main(String[] args) { Connection connection = null; try { connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME,false,false,false,null); channel.exchangeDeclare(Producer.EXCHANGE_NAME,\u0026#34;topic\u0026#34;); channel.queueBind(QUEUE_NAME,Producer.EXCHANGE_NAME,\u0026#34;*.xian\u0026#34;); Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(\u0026#34;西安仓：\u0026#34; + new String(body,\u0026#34;utf8\u0026#34;)); channel.basicAck(envelope.getDeliveryTag(),false); } }; channel.basicConsume(QUEUE_NAME,false,consumer); }catch (Exception e){ e.printStackTrace(); } } } 消费者北京 # 只处理路由键为*.beijing的订单以及退货\npublic class ConsumerBeijing { public static final String QUEUE_NAME = \u0026#34;beijing\u0026#34;; public static void main(String[] args) { Connection connection = null; try { connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME,false,false,false,null); channel.exchangeDeclare(Producer.EXCHANGE_NAME,\u0026#34;topic\u0026#34;); channel.queueBind(QUEUE_NAME,Producer.EXCHANGE_NAME,\u0026#34;*.beijing\u0026#34;); Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(\u0026#34;北京仓：\u0026#34; + new String(body,\u0026#34;utf8\u0026#34;)); channel.basicAck(envelope.getDeliveryTag(),false); } }; channel.basicConsume(QUEUE_NAME,false,consumer); }catch (Exception e){ e.printStackTrace(); } } } 消费者总部 # 只处理路由键为back.#的所有的退货消息，exchange中back.#路由键的消息，除了按照后缀发送到西安、北京消费者queue以外，还会发送到总部的queue\npublic class ConsumerZongbu { public static final String QUEUE_NAME = \u0026#34;zongbu\u0026#34;; public static void main(String[] args) { Connection connection = null; try { connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME,false,false,false,null); channel.exchangeDeclare(Producer.EXCHANGE_NAME,\u0026#34;topic\u0026#34;); channel.queueBind(QUEUE_NAME,Producer.EXCHANGE_NAME,\u0026#34;back.#\u0026#34;); Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(\u0026#34;总部处理：\u0026#34; + new String(body,\u0026#34;utf8\u0026#34;)); channel.basicAck(envelope.getDeliveryTag(),false); } }; channel.basicConsume(QUEUE_NAME,false,consumer); }catch (Exception e){ e.printStackTrace(); } } } 配置 # 队列queue配置 # 在声明队列的时候，可以对队列进行配置\n/* * 可选参数 * x-max-length 队列最大允许的消息数 * x-expires 队列的自动删除时间 * x-message-ttl 消息存活时间 */ Map\u0026lt;String,String\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); map.put(\u0026#34;x-max-length\u0026#34;,35); //声明队列，在第五个参数，Map类型 channel.queueDeclare(QUEUE_NAME,false,false,false,map); 消息确认机制 # 消费者确认 # 就是改为手动应答，在每次处理完消息后，手动进行确认\n//所有消费者第二个参数，改为false channel.basicConsume(QUEUE_NAME, false, consumer); /* * 在重写的handleDelivery方法中，每处理完一个消息，进行手动应答 * 第一个参数为消息的下标 * 第二个参数为false：只确认当前消息；true：确认当前消息以及之前的消息 */ channel.basicAck(envelope.getDeliveryTag(),false); 生产者确认 # 保证消息已经发送到exchange或queue\n方式一：添加事务 # //开启事务 channel.txSelect(); //发送消息 channel.basicPublish(exchangeName,routingKey, MessageProperties, msg.getBytes()); //提交事务 channel.txCommit(); //回滚事务，如果多次提交消息的话，那么多次提交具有原子性，要么都提交，要么都不提交 channel.txRollback(); 方式二：confirm # 1、普通confirm模式 # publish一条消息后，等待服务器端confirm,如果服务端返回false或者超时时间内未返回，客户端进行消息重传。\n//开启发送方确认模式 channel.confirmSelect(); //发送消息 channel.basicPublish(exchangeName,routingKey, MessageProperties, msg.getBytes()); //服务器确认消息是否发送成功 if(channel.waitForConfirms()) { System.out.println(\u0026#34;发送成功\u0026#34;); }else { System.out.println(\u0026#34;发送失败\u0026#34;); } 2、批量Confirm模式 # 使用同步方式等所有的消息发送之后才会执行后面代码，只要有一个消息未被确认就会抛出IOException异常\n//开启发送方确认模式 channel.confirmSelect(); //发送消息 channel.basicPublish(exchangeName,routingKey, MessageProperties, msg.getBytes()); //直到所有信息都发布，只要有一个未确认就会IOException channel.waitForConfirmsOrDie(); 3、异步Confirm模式 # 异步模式的优点，就是执行效率高，不需要等待消息执行完，只需要监听消息即可\n//开启发送方确认模式 channel.confirmSelect(); //开启异步监听 channel.addConfirmListener(new ConfirmListener() { //消息未确认执行该方法 public void handleNack(long deliveryTag, boolean multiple) throws IOException { // TODO Auto-generated method stub System.out.println(\u0026#34;消息未确认\u0026#34;+deliveryTag); } //消息已确认执行该方法 public void handleAck(long deliveryTag, boolean multiple) throws IOException { // TODO Auto-generated method stub System.out.println(\u0026#34;消息已经确认\u0026#34;+deliveryTag+\u0026#34; \u0026#34;+multiple); } }); //发送消息 channel.basicPublish(exchangeName,routingKey, MessageProperties, msg.getBytes()); ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/e2f9d0f6/46c17ead/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e准备工作 \n    \u003cdiv id=\"准备工作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%87%86%e5%a4%87%e5%b7%a5%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e1、查看数据端口，默认5672 \n    \u003cdiv id=\"1查看数据端口默认5672\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1%e6%9f%a5%e7%9c%8b%e6%95%b0%e6%8d%ae%e7%ab%af%e5%8f%a3%e9%bb%98%e8%ae%a45672\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20210901121611491\"\n    data-zoom-src=\"/posts/3ab7256e/675117b0/e2f9d0f6/46c17ead/image/202109181356428.png\"\n    src=\"/posts/3ab7256e/675117b0/e2f9d0f6/46c17ead/image/202109181356428.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"2、Java操作RabbitMQ","type":"posts"},{"content":" 主从 # 该架构只能主机写入和读取，从机只能读取\n主：192.168.0.1；从：192.168.0.2\n保证两台服务器可以互相访问，且端口打开\n主机配置 # [mysqld] # 主服务器唯一ID，一般为ip第四位 server-id=1 # 启用二进制日志 log-bin=mysql-bin # 设置不要复制的数据库（可设置多个） binlog-ignore-db=mysql # binlog-ignore-db=information_schema # 设置需要复制的数据库,不配置代表所有，但切记配置不要复制的库，建议基础库都不排除掉 # binlog-do-db=test1 # 设置binlog格式 binlog_format=STATEMENT binlog 日志有三种格式\nSTATEMENT 会把sql写到日志中，并且带有时间，所以从的时间和主的时间必须一致 ROW 不记录写SQL，只记录每行的改变 MIXED 切换 STATEMENT 和 ROW 从机配置 # [mysqld] server-id=2 # 启用中继日志 relay-log=mysql-relay 重启主从数据库\n登录主机 # # 创建用户 CREATE USER \u0026#39;myslave\u0026#39;@\u0026#39;%\u0026#39; IDENTIFIED BY \u0026#39;123456\u0026#39;; # 授权 GRANT REPLICATION SLAVE ON *.* TO \u0026#39;myslave\u0026#39;@\u0026#39;%\u0026#39;; # 查看主机的状态 mysql\u0026gt; show master status; +---------------+-----------+--------------+------------------+-----------------------------------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +---------------+-----------+--------------+------------------+-----------------------------------------------+ | binlog.000001 | 587 | test1 | mysql | | +---------------+-----------+--------------+------------------+-----------------------------------------------+ File binlog 文件 Position 接入位置，372357328 之前的数据不会复制 Binlog_Do_DB 要复制的库 Binlog_Ignore_DB 不复制的库 登录从机 # # 停止复制 stop slave; # 重置复制信息 reset master; reset slave; 配置主机信息\n# 配置复制信息 CHANGE MASTER TO MASTER_HOST = \u0026#39;192.168.0.1\u0026#39;, MASTER_PORT = 3306, MASTER_USER = \u0026#39;myslave\u0026#39;, MASTER_PASSWORD = \u0026#39;123456\u0026#39;, MASTER_LOG_FILE=\u0026#39;binlog.000001\u0026#39;, MASTER_LOG_POS=587; # 开始复制 start slave; # 查看从机状态，出现双yes即为正常 show slave status\\G; 主主 # 该架构两个主机都可以读取\n主1：192.168.0.1；主2：192.168.0.2\n主1配置 # [mysqld] # 主服务器唯一ID，一般为ip第四位 server-id=1 # 启用二进制日志 log-bin=mysql-bin # 设置不要复制的数据库（可设置多个） binlog-ignore-db=mysql # binlog-ignore-db=information_schema # 设置需要复制的数据库,不配置代表所有，但切记配置不要复制的库，建议基础库都不排除掉 # binlog-do-db=test1 # 设置binlog格式 binlog_format=STATEMENT # 在作为从数据库的时候，有写入操作也要更新二进制日志文件 log-slave-updates # 表示自增长字段每次递增的量，指自己字段的起始值，其默认值是1，取值范围是1~65535(主要是区分M1，M2的自增) auto-increment-increment=2 # 表示自增长字段从哪个数开始，指字段一次递增多少，他的取值范围是1~65535 auto-increment-offset=1 主2配置 # [mysqld] # 主服务器唯一ID，一般为ip第四位 server-id=2 # 启用二进制日志 log-bin=mysql-bin # 设置不要复制的数据库（可设置多个） binlog-ignore-db=mysql # binlog-ignore-db=information_schema # 设置需要复制的数据库,不配置代表所有，但切记配置不要复制的库，建议基础库都不排除掉 # binlog-do-db=test1 # 设置binlog格式 binlog_format=STATEMENT # 在作为从数据库的时候，有写入操作也要更新二进制日志文件 log-slave-updates # 表示自增长字段每次递增的量，指自己字段的起始值，其默认值是1，取值范围是1~65535(主要是区分M1，M2的自增) auto-increment-increment=2 # 表示自增长字段从哪个数开始，指字段一次递增多少，他的取值范围是1~65535 auto-increment-offset=2 重启主从数据库\n双主都创建同步账号 # # 创建用户 CREATE USER \u0026#39;myslave\u0026#39;@\u0026#39;%\u0026#39; IDENTIFIED BY \u0026#39;123456\u0026#39;; # 授权 GRANT REPLICATION SLAVE ON *.* TO \u0026#39;myslave\u0026#39;@\u0026#39;%\u0026#39;; # 停止复制 stop slave; # 重置复制信息 reset master; reset slave; # 查看主机的状态、记录下来 mysql\u0026gt; show master status; +---------------+-----------+--------------+------------------+-----------------------------------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +---------------+-----------+--------------+------------------+-----------------------------------------------+ | binlog.000001 | 587 | test1 | mysql | | +---------------+-----------+--------------+------------------+-----------------------------------------------+ 登录主1 # # 配置复制信息 CHANGE MASTER TO MASTER_HOST = \u0026#39;192.168.0.2\u0026#39;, MASTER_PORT = 3306, MASTER_USER = \u0026#39;myslave\u0026#39;, MASTER_PASSWORD = \u0026#39;123456\u0026#39;, MASTER_LOG_FILE=\u0026#39;binlog.000001\u0026#39;, MASTER_LOG_POS=587; # 开始复制 start slave; # 查看从机状态，出现双yes即为正常 show slave status\\G; 登录主2 # # 配置复制信息 CHANGE MASTER TO MASTER_HOST = \u0026#39;192.168.0.1\u0026#39;, MASTER_PORT = 3306, MASTER_USER = \u0026#39;myslave\u0026#39;, MASTER_PASSWORD = \u0026#39;123456\u0026#39;, MASTER_LOG_FILE=\u0026#39;binlog.000001\u0026#39;, MASTER_LOG_POS=587; # 开始复制 start slave; # 查看从机状态，出现双yes即为正常 show slave status\\G; MyCat # MyCat 是目前最流行的基于 java 语言编写的数据库中间件，是一个实现了 MySQL 协议的服务器，前端用户可以把它看作是一个数据库代理，用 MySQL 客户端工具和命令行访问，而其后端可以用 MySQL 原生协议与多个 MySQL 服务器通信，也可以用 JDBC 协议与大多数主流数据库服务器通信，其核心功能是分库分表。配合数据库的主从模式还可实现读写分离。\n下载地址：https://github.com/MyCATApache/Mycat-Server/releases\n安装配置 # # 下载 wget http://dl.mycat.org.cn/1.6.7.6/20210730131311/Mycat-server-1.6.7.6-release-20210730131311-linux.tar.gz #解压 tar -xvf Mycat-server-1.6.7.6-release-20210730131311-linux.tar.gz schema.xml 定义逻辑库，表、分片节点等内容 rule.xml 定义分片规则 server.xml 定义用户以及系统相关变量，如端口等 由于mycat1.6仅支持mysql5，不支持mysql8, 首先需要替换bin目录下面的mysq驱动文件为对应版本的。\n将mysql驱动上传到mycat的lib目录下，替换之前的驱动\n修改配置文件schema.xml\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE mycat:schema SYSTEM \u0026#34;schema.dtd\u0026#34;\u0026gt; \u0026lt;mycat:schema xmlns:mycat=\u0026#34;http://io.mycat/\u0026#34;\u0026gt; \u0026lt;!-- 在server.xml中被指定的逻辑库 --\u0026gt; \u0026lt;schema name=\u0026#34;TESTDB\u0026#34; checkSQLschema=\u0026#34;true\u0026#34; sqlMaxLimit=\u0026#34;100\u0026#34; randomDataNode=\u0026#34;dn1\u0026#34; dataNode=\u0026#34;dn1\u0026#34;\u0026gt; \u0026lt;/schema\u0026gt; \u0026lt;!-- 对应schema中的 DataNode，database 是真正物理上的数据库 --\u0026gt; \u0026lt;dataNode name=\u0026#34;dn1\u0026#34; dataHost=\u0026#34;host1\u0026#34; database=\u0026#34;my_sql_test\u0026#34; /\u0026gt; \u0026lt;!-- name对应 dataNode 的 datatHost --\u0026gt; \u0026lt;dataHost name=\u0026#34;host1\u0026#34; maxCon=\u0026#34;1000\u0026#34; minCon=\u0026#34;10\u0026#34; balance=\u0026#34;3\u0026#34; writeType=\u0026#34;0\u0026#34; dbType=\u0026#34;mysql\u0026#34; dbDriver=\u0026#34;jdbc\u0026#34; switchType=\u0026#34;1\u0026#34; slaveThreshold=\u0026#34;100\u0026#34;\u0026gt; \u0026lt;!-- 心跳检测 --\u0026gt; \u0026lt;heartbeat\u0026gt;select user()\u0026lt;/heartbeat\u0026gt; \u0026lt;!-- 定义写主机 --\u0026gt; \u0026lt;writeHost host=\u0026#34;hostM1\u0026#34; url=\u0026#34;jdbc:mysql://192.168.0.1:3306\u0026#34; user=\u0026#34;myslave\u0026#34; password=\u0026#34;123456\u0026#34;\u0026gt; \u0026lt;redaHost host=\u0026#34;hostS1\u0026#34; url=\u0026#34;jdbc:mysql://192.168.0.2:3306\u0026#34; user=\u0026#34;myslave\u0026#34; password=\u0026#34;123456\u0026#34;\u0026gt;\u0026lt;/redaHost\u0026gt; \u0026lt;/writeHost\u0026gt; \u0026lt;/dataHost\u0026gt; \u0026lt;/mycat:schema\u0026gt; dataNode 的 database 属性，为MySQL 真实的数据库名称。\ndataHost 的 balance 属性，通过此属性配置读写分离的类型\nbalance=\u0026quot;0\u0026quot; 不开启读写分离机制，所有读操作都发送到当前可用的 writeHost balance=\u0026quot;1\u0026quot; 全部的 readHost 与 stand by writeHost 参与 select 语句的负载均衡，简单来说，当双主双从模式(M1-\u0026gt;S1,M2-\u0026gt;S2，并且M1与M2互为主备)，正常情况下，M2，S1，S2都参与 select 语句的负载均衡。 balance=\u0026quot;2\u0026quot; 所有读操作都随机的在 writeHost、readHost 上分发 balance=\u0026quot;3\u0026quot; 所有读请求随机的分发到 readHost 执行，writeHost 不负担读压力 dataHost 的 writeType 属性：\nwriteType=\u0026quot;0\u0026quot; 所有写操作发送到配置的第一个 writeHost，第一个挂了切到还生存的第二个 writeType=\u0026quot;1\u0026quot; 所有操作都随机的发送到配置的 writeHost，1.5以后废弃不推荐 dataHost 的 switchType 属性：\nswitchType=\u0026quot;1\u0026quot; 默认值，自动切换 switchType=\u0026quot;-1\u0026quot; 不自动切换 switchType=\u0026quot;2\u0026quot; 基于MySQL 主从同步的状态决定是否切换 修改配置文件server.xml\n\u0026lt;!-- 连接mycat的用户信息 --\u0026gt; \u0026lt;user name=\u0026#34;mycat\u0026#34; defaultAccount=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;!-- 密码 --\u0026gt; \u0026lt;property name=\u0026#34;password\u0026#34;\u0026gt;123456\u0026lt;/property\u0026gt; \u0026lt;!-- 逻辑库名称 --\u0026gt; \u0026lt;property name=\u0026#34;schemas\u0026#34;\u0026gt;TESTDB\u0026lt;/property\u0026gt; \u0026lt;!-- 默认逻辑库 --\u0026gt; \u0026lt;property name=\u0026#34;defaultSchema\u0026#34;\u0026gt;TESTDB\u0026lt;/property\u0026gt; \u0026lt;/user\u0026gt; 常用命令 # # 启动 ./mycat start # 停止 ./mycat stop # 前台运行 ./mycat console # 重启服务 ./mycat restart # 暂停 ./mycat pause # 查看启动状态 ./mycat status ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/a6ece75f/8b824d9b/6930a0a9/9289a1a4/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e主从 \n    \u003cdiv id=\"主从\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%bb%e4%bb%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e该架构只能主机写入和读取，从机只能读取\u003c/p\u003e","title":"2、MySQL主从","type":"posts"},{"content":" java.lang.Object # Object类是所有Java类的根父类 如果在类的声明中未使用extends关键字指明其父类，则默认父类为java.lang.Object类 Object类中的功能(方法)具有通用性。 Object类只声明了一个空参的构造器 registerNatives() # native方法，让程序主动将本地方法链接到调用方，当Java程序需要调用本地方法时就可以直接调用，而不需要虚拟机再去定位并链接。\ngetClass() # getClass()方法返回当前运行时的类Class，即当前调用getClass()方法的对象对应的Class（字节码）实例。\nclone() # clone()方法用于克隆当前对象，克隆分为浅克隆和深克隆。Object类中的clone()方法是浅克隆对象。\n浅克隆 # 就是复制值一份，然后赋值给克隆出来的对象，所以当浅克隆基本数据类型时，那么可以实现真正意义上的克隆，但是当浅克隆引用数据类型时，那么就会出现复制地址引用一份，然后赋值给克隆对象，这种克隆仅仅只是克隆复制了一份地址引用，本质上这两个对象操作的还是同一块内存，所以达不到真正意义上的克隆对象。\n实现深克隆 # 所以一般需要实现Cloneable接口，并重写clone()方法，当复制的属性是引用数据类型时，进行递归克隆（基本数据类型直接复制一份，引用数据类型递归调用clone()方法），重写完clone()方法才实现真正意义上的克隆。\nequals() # 是一个方法，而非运算符 只能适用于引用数据类型 Object类中equals()的定义： public boolean equals(Object obj) { return (this == obj); } 说明：Object类中定义的equals()和==的作用是相同的：比较两个对象的地址值是否相同，即两个引用是否指向同一个对象实体 像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后，比较的不是两个引用的地址是否相同，而是比较两个对象的实体内容是否相同。 通常情况下，我们自定义的类如果使用equals()的话，也通常是比较两个对象的实体内容是否相同。那么，我们就需要对Object类中的equals()进行重写. 重写的原则：比较两个对象的实体内容是否相同. 如果equals()方法比较两个对象返回为true，那么两个对象的hashCode一定相同；如果equals()方法比较两个对象返回为false，那么两个对象的hashCode不一定不同 换句话说，如果两个对象的hashCode不同，那么两个对象的equals()一定为false；如果两个对象的hashCode相同，那么两个对象的equals()不一定为true ==和equals()的区别 # ==，是一个比较运算符，既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值，对于引用类型就是比较内存地址 equals()的话，它是属于java.lang.Object类里面的方法，如果某个类的equals()没有重写，那么和使用==比较的结果一样；我们可以看到String等类的equals()方法是被重写过的，而且String类在日常开发中用的比较多，久而久之，形成了equals()是比较值的错误观点。 具体要看自定义类里有没有重写Object的equals()方法来判断。 通常情况下，重写equals()方法，会比较类中的相应属性是否都相等。 hashCode() # 什么是HashCode # 哈希码（HashCode），并不是完全唯一的，它是一种算法，让同一个类的对象按照自己不同的特征尽量的有不同的哈希码，但不表示不同的对象哈希码完全不同。\nHashCode的作用 # 如果在一个包含很多元素的集合中，查找一个元素，那么使用equals()方法挨个比较，那么会非常的慢。所以引入了哈希算法，哈希算法可以根据元素的某些特征（属性），计算出哈希值（散列值），相同哈希值的作为一组，也就是哈希桶，这个桶里面放着的都是哈希值相同元素。在查找元素的时候，可以很快的确定哈希值，从而找到哈希桶，并且在桶里面找到这个元素。\n在java中，例如Hashtable、HashSet、HashMap底层使用了数组+链表的基本存储结构，其中数组存储的就是HashCode并且这个HashCode指向一个链表（类似于哈希桶），链表里面存储的都是HashCode相同的元素\n当自定义类没重写hashCode()时，哈希值是根据对象在内存中的地址生成的，所以，hashCode()常会和equals()方法同时重写，确保相等的两个对象拥有相等的hashCode。\n重写hashCode()与equals() # public class Student { private String name; private int age; @Override public boolean equals(Object o) { // 首先判断两个实例地址是否相同，同地址肯定是同一元素 if (this == o) return true; // 判断元素是否为null，并且判断两个实例是否属于同一类 if (o == null || getClass() != o.getClass()) return false; // 依次比较每一个属性是否相等 Student student = (Student) o; return age == student.age \u0026amp;\u0026amp; name.equals(student.name); } @Override public int hashCode() { // 调用Objects工具类的方法，计算多个属性共同的HashCode return Objects.hash(name, age); } } 重写equals和hashCode方法 # public class User { public String id; public String name; public int age; @Override public boolean equals(Object obj) { if(this == obj){ return true;//地址相等 } if(obj == null){ return false;//非空性检查 } if(obj instanceof User){ User other = (User) obj; //需要比较的字段相等，则这两个对象相等 if(this.name.equals(other.name) \u0026amp;\u0026amp; this.age == other.age){ return true; } } return false; } @Override public int hashCode() { int result = 17; result = 31 * result + (name == null ? 0 : name.hashCode()); result = 31 * result + (age == null ? 0 : age.hashCode()); return result; } } toString() # 获取实例信息，当使用System.out.println()方法打印一个实例的时候，默认调用的就是toString()方法，toString()如果没有重写，默认实现如下\npublic String toString() { return getClass().getName() + \u0026#34;@\u0026#34; + Integer.toHexString(hashCode()); } notify() # 用于唤醒因线程同步进入阻塞状态的任意一个线程\nnotifyAll() # 用于唤醒因线程同步进入阻塞状态的所有线程\nwait(timeout) # native方法，可以让当前正在执行的线程进入阻塞状态，直到其他线程调用notify()或者notifyAll()方法来唤醒这个线程，或者当过了参数timeout毫秒之后，自动从阻塞状态进入就绪状态\nwait() # 实际上就是wait(0)，也就是阻塞后不会自动进入就绪状态，必须由其他线程调用notify()或者notifyAll()方法唤醒\npublic final void wait() throws InterruptedException { wait(0); } wait(timeout, nanos) # 比wait(timeout)更精细，支持0-999999的纳秒\nfinalize() # 当垃圾回收机制（Garbage Collection）确认了一个对象没有被引用时，那么垃圾回收机制就会回收这个对象，并在回收的前一刻这个对象调用finalize()方法，这类似C++中的析构函数\njava.util.Objects # Objects是jdk1.7添加的一个工具类，进行一些常用的检查操作。\nequals(a,b) # 可以不用考虑空指针异常，比较两个元素\npublic static boolean equals(Object a, Object b) { return (a == b) || (a != null \u0026amp;\u0026amp; a.equals(b)); } deepEquals(a,b) # 主要用于比较数组的所有元素是否相等\nisNull(a) # 判断元素是否为null\nnonNull(a) # 判断元素是否不为null\nrequireNonNull(obj) # 检查元素是否为null，如果为null，则会抛出空指针异常；否则返回元素\nrequireNonNull(obj,message) # 检查元素是否为null，如果为null，则会抛出空指针异常（自定义异常消息）；否则返回元素\nrequireNonNullElse(obj,defaultObj) # 检查obj是否为null，如果不为null则返回obj；如果为null，则返回defaultObj\nhash(Object \u0026hellip; val) # 为一系列的输入值生成哈希码，该方法的参数是可变参数。\ntoString(Object o) # 返回指定对象的字符串形式。如果参数为null，则返回字符串\u0026quot;null\u0026quot;。\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/409de243/5ff69f1b/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003ejava.lang.Object \n    \u003cdiv id=\"javalangobject\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#javalangobject\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eObject类是所有Java类的根父类\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003e如果在类的声明中未使用\u003ccode\u003eextends\u003c/code\u003e关键字指明其父类，则默认父类为\u003ccode\u003ejava.lang.Object\u003c/code\u003e类\u003c/li\u003e\n\u003cli\u003eObject类中的功能(方法)具有通用性。\u003c/li\u003e\n\u003cli\u003eObject类只声明了一个空参的构造器\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003eregisterNatives() \n    \u003cdiv id=\"registernatives\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#registernatives\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003enative\u003c/code\u003e方法，让程序主动将本地方法链接到调用方，当Java程序需要调用本地方法时就可以直接调用，而不需要虚拟机再去定位并链接。\u003c/p\u003e","title":"2、Object","type":"posts"},{"content":" javafx.application.Platform # 作为JavaFx的工具类\n//内部线程也是UI线程，作用是，线程空闲时候，更新UI，但是仍不可做频繁大量的UI更新 Platform.runLater(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); //即使关闭最后一个窗口，程序也会保持运行 Platform.setImplicitExit(false); //退出程序 Platform.exit(); //检测电脑是否支持一些特性 Platform.isSupported(ConditionalFeature.MEDIA); javafx.concurrent.Task # 使用Platform.runLater()方法，虽然可以实现更新UI，但是也仅限于简单的更新，如果出现大量频繁的更新，会造成UI卡死\nTask类可以实现对复杂任务进度的监听，并且配合监听器可以实现实时更新UI\npublic class MyTask extends Task\u0026lt;Integer\u0026gt; { // 通知更新进度 @Override protected void updateProgress(long workDone, long max) { super.updateProgress(workDone, max); } // 通知更新进度 @Override protected void updateProgress(double workDone, double max) { super.updateProgress(workDone, max); } // 通知更新消息 @Override protected void updateMessage(String message) { super.updateMessage(message); } // 通知更新标题 @Override protected void updateTitle(String title) { super.updateTitle(title); } // 参数value即为call方法的返回值，当call方法执行完成后调用 @Override protected void updateValue(Integer value) { super.updateValue(value); } // 以上方法在call方法中需要主动调用（updateValue除外） // 调用后，即可配合Task实现类实例的相关监听器进行监听 @Override protected Integer call() throws Exception { for (int i = 0; i \u0026lt; 10; i++) { Thread.sleep(1000); updateTitle(\u0026#34;第\u0026#34; + i + \u0026#34;次更新\u0026#34;); updateMessage(\u0026#34;第\u0026#34; + i + \u0026#34;次更新\u0026#34;); updateProgress(i,9); } return 1; } } 使用监听器\nMyTask myTask = new MyTask(); // 绑定Title监听 myTask.titleProperty().addListener(new ChangeListener\u0026lt;String\u0026gt;() { @Override public void changed(ObservableValue\u0026lt;? extends String\u0026gt; observable, String oldValue, String newValue) { // 对UI的修改操作 } }); // 绑定Message监听 myTask.messageProperty().addListener(new ChangeListener\u0026lt;String\u0026gt;() { @Override public void changed(ObservableValue\u0026lt;? extends String\u0026gt; observable, String oldValue, String newValue) { // 对UI的修改操作 } }); // 绑定Value（即call方法返回值）监听 myTask.valueProperty().addListener(new ChangeListener\u0026lt;Integer\u0026gt;() { @Override public void changed(ObservableValue\u0026lt;? extends Integer\u0026gt; observable, Integer oldValue, Integer newValue) { // 对UI的修改操作 } }); // 对Progress（进度）的监听 myTask.progressProperty().addListener(new ChangeListener\u0026lt;Number\u0026gt;() { @Override public void changed(ObservableValue\u0026lt;? extends Number\u0026gt; observable, Number oldValue, Number newValue) { // 对UI的修改操作 } }); // 启动任务 new Thread(myTask).start(); ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/d9073060/edd0d20f/07c9638f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003ejavafx.application.Platform \n    \u003cdiv id=\"javafxapplicationplatform\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#javafxapplicationplatform\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e作为JavaFx的工具类\u003c/p\u003e","title":"2、Platform、Task","type":"posts"},{"content":" 通用命令 # 连接数据库 redis-cli 使用默认IP地址和端口连接Redis数据库（IP地址：127.0.0.1，端口号6379） redis-cli -h 192.168.2.144 -p 6379 使用指定IP地址和端口连接Redis数据库 持久化 save 将数据同步保存到磁盘(存储在dump.rdb文件中) bgsave 将数据异步保存到磁盘 lastsave 返回上次成功将数据保存到磁盘的Unix时戳 shundown 将数据同步保存到磁盘，然后关闭服务 远程服务控制 info 提供服务器的信息和统计 monitor 实时转储收到的请求 slaveof 改变复制策略设置 config 在运行时配置Redis服务器 对value操作的命令 exists(key) 确认一个key是否存在 del(key) 删除一个key type(key) 返回值的类型 keys(pattern) 返回满足给定pattern的所有key randomkey 随机返回key空间的一个 keyrename(oldname, newname) 重命名key dbsize 返回当前数据库中key的数目 expire 设定一个key的活动时间（s） ttl 获得一个key的活动时间 select(index) 按索引查询（切换到指定的数据库），默认索引为0 move(key, dbindex) 移动当前数据库中的key到dbindex数据库 flushdb 删除当前选择数据库中的所有key flushall 删除所有数据库中的所有key 对String操作的命令 # set(key, value) 给数据库中名称为key的string赋予值value get(key) 返回数据库中名称为key的string的value getset(key, value) 给名称为key的string赋予上一次的value mget(key1, key2,…, key N) 返回库中多个string的value setnx(key, value) 添加string，名称为key，值为value setex(key, time, value) 向库中添加string，设定过期时间time mset(key N, value N) 批量设置多个string的值 msetnx(key N, value N) 如果所有名称为key i的string都不存在 incr(key) 名称为key的string增1操作 incrby(key, integer) 名称为key的string增加integer decr(key) 名称为key的string减1操作 decrby(key, integer) 名称为key的string减少integer append(key, value) 名称为key的string的值附加value substr(key, start, end) 返回名称为key的string的value的子串 对List操作的命令 # rpush(key, value) 在名称为key的list尾添加一个值为value的元素 lpush(key, value) 在名称为key的list头添加一个值为value的元素 llen(key) 返回名称为key的list的长度 lrange(key, start, end) 返回名称为key的list中start至end之间的元素 ltrim(key, start, end) 截取名称为key的list lindex(key, index) 返回名称为key的list中index位置的元素 lset(key, index, value) 给名称为key的list中index位置的元素赋值 lrem(key, count, value) 删除count个key的list中值为value的元素 lpop(key) 返回并删除名称为key的list中的首元素 rpop(key) 返回并删除名称为key的list中的尾元素 blpop(key1, key2,… key N, timeout) lpop命令的block版本 brpop(key1, key2,… key N, timeout) rpop的block版本 rpoplpush(srckey, dstkey) 返回并删除名称为srckey的list的尾元素，并将该元素添加到名称为dstkey的list的头部 对Set操作的命令 # sadd(key, member) 向名称为key的set中添加元素member srem(key, member) 删除名称为key的set中的元素member spop(key) 随机返回并删除名称为key的set中一个元素 smove(srckey, dstkey, member) 移到集合元素 scard(key) 返回名称为key的set的基数 sismember(key, member) member是否是名称为key的set的元素 sinter(key1, key2,…key N) 求交集 sinterstore(dstkey, (keys)) 求交集并将交集保存到dstkey的集合 sunion(key1, (keys)) 求并集 sunionstore(dstkey, (keys)) 求并集并将并集保存到dstkey的集合 sdiff(key1, (keys)) 求差集 sdiffstore(dstkey, (keys)) 求差集并将差集保存到dstkey的集合 smembers(key) 返回名称为key的set的所有元素 srandmember(key) 随机返回名称为key的set的一个元素 对Hash操作的命令 # hset(key, field, value) 向名称为key的hash中添加元素field hget(key, field) 返回名称为key的hash中field对应的value hmget(key, (fields)) 返回名称为key的hash中field i对应的value hmset(key, (fields)) 向名称为key的hash中添加元素field hincrby(key, field, integer) 将名称为key的hash中field的value增加integer hexists(key, field) 名称为key的hash中是否存在键为field的域 hdel(key, field) 删除名称为key的hash中键为field的域 hlen(key) 返回名称为key的hash中元素个数 hkeys(key) 返回名称为key的hash中所有键 hvals(key) 返回名称为key的hash中所有键对应的value hgetall(key) 返回名称为key的hash中所有的键（field）及其对应的value ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/caadfbf9/45d68b63/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e通用命令 \n    \u003cdiv id=\"通用命令\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%80%9a%e7%94%a8%e5%91%bd%e4%bb%a4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e\u003cstrong\u003e连接数据库\u003c/strong\u003e\u003c/th\u003e\n          \u003cth\u003e\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eredis-cli\u003c/td\u003e\n          \u003ctd\u003e使用默认IP地址和端口连接Redis数据库（IP地址：127.0.0.1，端口号6379）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eredis-cli -h 192.168.2.144 -p 6379\u003c/td\u003e\n          \u003ctd\u003e使用指定IP地址和端口连接Redis数据库\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e持久化\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003esave\u003c/td\u003e\n          \u003ctd\u003e将数据同步保存到磁盘(存储在dump.rdb文件中)\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ebgsave\u003c/td\u003e\n          \u003ctd\u003e将数据异步保存到磁盘\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003elastsave\u003c/td\u003e\n          \u003ctd\u003e返回上次成功将数据保存到磁盘的Unix时戳\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eshundown\u003c/td\u003e\n          \u003ctd\u003e将数据同步保存到磁盘，然后关闭服务\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e远程服务控制\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003einfo\u003c/td\u003e\n          \u003ctd\u003e提供服务器的信息和统计\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003emonitor\u003c/td\u003e\n          \u003ctd\u003e实时转储收到的请求\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eslaveof\u003c/td\u003e\n          \u003ctd\u003e改变复制策略设置\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003econfig\u003c/td\u003e\n          \u003ctd\u003e在运行时配置Redis服务器\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e对value操作的命令\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eexists(key)\u003c/td\u003e\n          \u003ctd\u003e确认一个key是否存在\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003edel(key)\u003c/td\u003e\n          \u003ctd\u003e删除一个key\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003etype(key)\u003c/td\u003e\n          \u003ctd\u003e返回值的类型\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ekeys(pattern)\u003c/td\u003e\n          \u003ctd\u003e返回满足给定pattern的所有key\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003erandomkey\u003c/td\u003e\n          \u003ctd\u003e随机返回key空间的一个\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ekeyrename(oldname, newname)\u003c/td\u003e\n          \u003ctd\u003e重命名key\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003edbsize\u003c/td\u003e\n          \u003ctd\u003e返回当前数据库中key的数目\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eexpire\u003c/td\u003e\n          \u003ctd\u003e设定一个key的活动时间（s）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ettl\u003c/td\u003e\n          \u003ctd\u003e获得一个key的活动时间\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eselect(index)\u003c/td\u003e\n          \u003ctd\u003e按索引查询（切换到指定的数据库），默认索引为0\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003emove(key, dbindex)\u003c/td\u003e\n          \u003ctd\u003e移动当前数据库中的key到dbindex数据库\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eflushdb\u003c/td\u003e\n          \u003ctd\u003e删除当前选择数据库中的所有key\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eflushall\u003c/td\u003e\n          \u003ctd\u003e删除所有数据库中的所有key\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2 class=\"relative group\"\u003e对String操作的命令 \n    \u003cdiv id=\"对string操作的命令\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%af%b9string%e6%93%8d%e4%bd%9c%e7%9a%84%e5%91%bd%e4%bb%a4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003eset(key, value)\u003c/th\u003e\n          \u003cth\u003e给数据库中名称为key的string赋予值value\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eget(key)\u003c/td\u003e\n          \u003ctd\u003e返回数据库中名称为key的string的value\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003egetset(key, value)\u003c/td\u003e\n          \u003ctd\u003e给名称为key的string赋予上一次的value\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003emget(key1, key2,…, key N)\u003c/td\u003e\n          \u003ctd\u003e返回库中多个string的value\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003esetnx(key, value)\u003c/td\u003e\n          \u003ctd\u003e添加string，名称为key，值为value\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003esetex(key, time, value)\u003c/td\u003e\n          \u003ctd\u003e向库中添加string，设定过期时间time\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003emset(key N, value N)\u003c/td\u003e\n          \u003ctd\u003e批量设置多个string的值\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003emsetnx(key N, value N)\u003c/td\u003e\n          \u003ctd\u003e如果所有名称为key i的string都不存在\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eincr(key)\u003c/td\u003e\n          \u003ctd\u003e名称为key的string增1操作\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eincrby(key, integer)\u003c/td\u003e\n          \u003ctd\u003e名称为key的string增加integer\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003edecr(key)\u003c/td\u003e\n          \u003ctd\u003e名称为key的string减1操作\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003edecrby(key, integer)\u003c/td\u003e\n          \u003ctd\u003e名称为key的string减少integer\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eappend(key, value)\u003c/td\u003e\n          \u003ctd\u003e名称为key的string的值附加value\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003esubstr(key, start, end)\u003c/td\u003e\n          \u003ctd\u003e返回名称为key的string的value的子串\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2 class=\"relative group\"\u003e对List操作的命令 \n    \u003cdiv id=\"对list操作的命令\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%af%b9list%e6%93%8d%e4%bd%9c%e7%9a%84%e5%91%bd%e4%bb%a4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003erpush(key, value)\u003c/th\u003e\n          \u003cth\u003e在名称为key的list尾添加一个值为value的元素\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003elpush(key, value)\u003c/td\u003e\n          \u003ctd\u003e在名称为key的list头添加一个值为value的元素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ellen(key)\u003c/td\u003e\n          \u003ctd\u003e返回名称为key的list的长度\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003elrange(key, start, end)\u003c/td\u003e\n          \u003ctd\u003e返回名称为key的list中start至end之间的元素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eltrim(key, start, end)\u003c/td\u003e\n          \u003ctd\u003e截取名称为key的list\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003elindex(key, index)\u003c/td\u003e\n          \u003ctd\u003e返回名称为key的list中index位置的元素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003elset(key, index, value)\u003c/td\u003e\n          \u003ctd\u003e给名称为key的list中index位置的元素赋值\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003elrem(key, count, value)\u003c/td\u003e\n          \u003ctd\u003e删除count个key的list中值为value的元素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003elpop(key)\u003c/td\u003e\n          \u003ctd\u003e返回并删除名称为key的list中的首元素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003erpop(key)\u003c/td\u003e\n          \u003ctd\u003e返回并删除名称为key的list中的尾元素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eblpop(key1, key2,… key N, timeout)\u003c/td\u003e\n          \u003ctd\u003elpop命令的block版本\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ebrpop(key1, key2,… key N, timeout)\u003c/td\u003e\n          \u003ctd\u003erpop的block版本\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003erpoplpush(srckey, dstkey)\u003c/td\u003e\n          \u003ctd\u003e返回并删除名称为srckey的list的尾元素，并将该元素添加到名称为dstkey的list的头部\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2 class=\"relative group\"\u003e对Set操作的命令 \n    \u003cdiv id=\"对set操作的命令\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%af%b9set%e6%93%8d%e4%bd%9c%e7%9a%84%e5%91%bd%e4%bb%a4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003esadd(key, member)\u003c/th\u003e\n          \u003cth\u003e向名称为key的set中添加元素member\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003esrem(key, member)\u003c/td\u003e\n          \u003ctd\u003e删除名称为key的set中的元素member\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003espop(key)\u003c/td\u003e\n          \u003ctd\u003e随机返回并删除名称为key的set中一个元素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003esmove(srckey, dstkey, member)\u003c/td\u003e\n          \u003ctd\u003e移到集合元素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003escard(key)\u003c/td\u003e\n          \u003ctd\u003e返回名称为key的set的基数\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003esismember(key, member)\u003c/td\u003e\n          \u003ctd\u003emember是否是名称为key的set的元素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003esinter(key1, key2,…key N)\u003c/td\u003e\n          \u003ctd\u003e求交集\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003esinterstore(dstkey, (keys))\u003c/td\u003e\n          \u003ctd\u003e求交集并将交集保存到dstkey的集合\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003esunion(key1, (keys))\u003c/td\u003e\n          \u003ctd\u003e求并集\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003esunionstore(dstkey, (keys))\u003c/td\u003e\n          \u003ctd\u003e求并集并将并集保存到dstkey的集合\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003esdiff(key1, (keys))\u003c/td\u003e\n          \u003ctd\u003e求差集\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003esdiffstore(dstkey, (keys))\u003c/td\u003e\n          \u003ctd\u003e求差集并将差集保存到dstkey的集合\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003esmembers(key)\u003c/td\u003e\n          \u003ctd\u003e返回名称为key的set的所有元素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003esrandmember(key)\u003c/td\u003e\n          \u003ctd\u003e随机返回名称为key的set的一个元素\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2 class=\"relative group\"\u003e对Hash操作的命令 \n    \u003cdiv id=\"对hash操作的命令\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%af%b9hash%e6%93%8d%e4%bd%9c%e7%9a%84%e5%91%bd%e4%bb%a4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ehset(key, field, value)\u003c/th\u003e\n          \u003cth\u003e向名称为key的hash中添加元素field\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehget(key, field)\u003c/td\u003e\n          \u003ctd\u003e返回名称为key的hash中field对应的value\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehmget(key, (fields))\u003c/td\u003e\n          \u003ctd\u003e返回名称为key的hash中field i对应的value\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehmset(key, (fields))\u003c/td\u003e\n          \u003ctd\u003e向名称为key的hash中添加元素field\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehincrby(key, field, integer)\u003c/td\u003e\n          \u003ctd\u003e将名称为key的hash中field的value增加integer\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehexists(key, field)\u003c/td\u003e\n          \u003ctd\u003e名称为key的hash中是否存在键为field的域\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehdel(key, field)\u003c/td\u003e\n          \u003ctd\u003e删除名称为key的hash中键为field的域\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehlen(key)\u003c/td\u003e\n          \u003ctd\u003e返回名称为key的hash中元素个数\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehkeys(key)\u003c/td\u003e\n          \u003ctd\u003e返回名称为key的hash中所有键\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehvals(key)\u003c/td\u003e\n          \u003ctd\u003e返回名称为key的hash中所有键对应的value\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehgetall(key)\u003c/td\u003e\n          \u003ctd\u003e返回名称为key的hash中所有的键（field）及其对应的value\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e","title":"2、Redis常用命令","type":"posts"},{"content":"搭建在窗口上的场景，各种组件、控件都必须放在场景上，Scene上必须有个根节点\nScene scene = new Scene(new Button(\u0026#34;btn\u0026#34;)); primaryStage.setScene(scene); //修改场景上鼠标的样式 scene.setCursor(Cursor.HAND); ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/d9073060/d57b26b2/11509fb7/","section":"文章","summary":"\u003cp\u003e搭建在窗口上的场景，各种组件、控件都必须放在场景上，Scene上必须有个根节点\u003c/p\u003e","title":"2、Scene","type":"posts"},{"content":" SpringCloud的概念 # Spring Cloud框架是Spring框架中专门用来针对微服务开发的框架，它的原生是NetFlix公司开发的一套微服务框架，但是后来被Spring收购了 Spring Cloud框架产品中，提供了很多微服务服务组件，如: Eureka注册中心，Zuul网关组件 ，Configuration配置中心 ， Feign服务间通信 ， Hystrix服务降级，Bus消息总线等 SpringCloud已经闭源了，SpringCloud的版本均为英国伦敦的地铁站站名，最早 的 Release 版本 Angel，第二个 Release 版本 Brixton（英国地名），然后是 Camden、 Dalston、Edgware、Finchley、Greenwich、Hoxton。 SpringCloud的组件 # Eureka注册中心 # 注册中心的作用：完成对微服务的服务注册与发现 遵循发布/订阅模式，作用：所有的微服务都需要向注册中心进行注册，微服务之间在相互调用时，就可以通过注册中心发现微服务的地址 Feign服务间通信 # 服务通信的作用：主要用于微服务之间完成使用HTTP协议进行相互调用 微服务之间可能存在相互调用，调用时就需要使用Open Feign接口调用技术 Hystrix服务降级 # 服务降级的作用：主要是处理微服务在调用过程中，由于出现的一些异常或延迟访问的问题，而导致服务异常的问题 微服务在调用时，可能存在调用失败，或长时间无法接收到响应的情况。SpringCloud提供了服务降级机制，服务降级，就是将某一个服务从同步调用，修正为类异步调用，有结果就返回结果，没有结果，调用方自己产生结果然后返回上层 Zuul网关组件 # 网关的作用：为所有的微服务提供一个统一的访问入口 每一个微服务都是一个WEB程序，WEB程序就有自己的访问地址，就不能称之为“一个项目”。网关的作用就是解决多个微服务有自己独立访问地址的问题，它可以为微服务体系提供一个统一的“访问入口” Configuration配置中心 # 配置中心的作用：管理所有微服务的配置信息 每个微服务都有自己的配置文件，提供一个公共在线的配置中心，让我们修改配置更加的方便 Bus消息总线 # 配合配置中心实现，在线的配置中心发生变化之后，需要使用消息总线推送至所有的微服务中 上述的技术中：注册中心，zuul网关，配置中心，Bus消息总结 这几个通常是项目经理或者技术人员已经配好的。我们需要重点的掌握的是：如何开发自己的微服务 ，微服务之间的调用，以及微服务之间的分布式事务处理 现在的分布式技术 # Dubbo+Zookeeper（RPC协议，主要用于大型分布式项目：当当）\nSpringCloud+Eureka（官方）(闭源)(Http协议,中小型开发)\nSpringCloud+Eureka+RestTemplate+Feign+Hystric+Zull+GateWay+配置中心+分布式事务+Bus总线 SpringCoud+Nacos(阿里)(Http协议,中小型开发)\nSpringCloud+Nacos+RestTemplate+Feign+Sentinel+GateWay+Nacos自带的配置中心+分布式事务+链路跟踪 古老的技术\nWebService(常见的，互联网向外界提供服务一般有2中姿势，1：json 2:webservice) Spring Cloud和Spring Boot之间的关系 # Spring Cloud是2014年，由Martin Fowler与James Lewis提出开发的。他们在开发的时候，为了提升开发效率，减少配置文件的编写，于是选择了Spring Boot作为微服务程序开发脚手架工具\n但是它们在开发时，有版本的要求，版本对应关系，可从官网查看\nhttps://spring.io/projects/spring-cloud https://start.spring.io/actuator/info Spring Cloud Spring Boot Angel版本 兼容Spring Boot 1.2.x Brixton版本 兼容Spring Boot 1.3.x，也兼容Spring Boot 1.4.x Camden版本 兼容Spring Boot 1.4.x，也兼容Spring Boot 1.5.x Dalston版本、Edgware版本 兼容Spring Boot 1.5.x，不兼容Spring Boot 2.0.x Finchley版本 兼容Spring Boot 2.0.x，不兼容Spring Boot 1.5.x Greenwich版本 兼容Spring Boot 2.1.x 实际开发过程中，可以对应的更加详细 Spring Boot Spring Cloud 1.5.2.RELEASE Dalston.RC1 1.5.9.RELEASE Edgware.RELEASE 2.0.2.RELEASE Finchley.BUILD-SNAPSHOT 2.0.3.RELEASE Finchley.RELEASE 2.1.0.RELEASE Greenwich.SR1 2.3.0.RELEASE Hoxton.SR4 微服务环境搭建 # 1、技术选型 # maven\nmysql\nmybatis\nspring cloud alibaba\n2、模块设计 # 工程名 描述 端口 star-cloud 父工程 - star-common 公共模块，放置实体类和工具类 - star-product 商品微服务 808x star-order 订单微服务 809x 3、创建父工程（Maven项目） # 作为父工程，是不需要编写源代码的，所以注解把父工程中的src目录删除掉，然后编辑父工程的pom.xml，做依赖版本控制\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;project xmlns=\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u0026gt; \u0026lt;modelVersion\u0026gt;4.0.0\u0026lt;/modelVersion\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;star-cloud\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;packaging\u0026gt;pom\u0026lt;/packaging\u0026gt; \u0026lt;!-- 依赖版本锁定 --\u0026gt; \u0026lt;properties\u0026gt; \u0026lt;java.version\u0026gt;1.8\u0026lt;/java.version\u0026gt; \u0026lt;project.build.sourceEncoding\u0026gt;UTF-8\u0026lt;/project.build.sourceEncoding\u0026gt; \u0026lt;project.reporting.outputEncoding\u0026gt;UTF-8\u0026lt;/project.reporting.outputEncoding\u0026gt; \u0026lt;spring-cloud.version\u0026gt;Greenwich.RELEASE\u0026lt;/spring-cloud.version\u0026gt; \u0026lt;spring-cloud-alibaba.version\u0026gt;2.1.1.RELEASE\u0026lt;/spring-cloud-alibaba.version\u0026gt; \u0026lt;spring-boot.version\u0026gt;2.1.3.RELEASE\u0026lt;/spring-boot.version\u0026gt; \u0026lt;lombok.version\u0026gt;1.18.16\u0026lt;/lombok.version\u0026gt; \u0026lt;mysql.version\u0026gt;5.1.38\u0026lt;/mysql.version\u0026gt; \u0026lt;mybatis.version\u0026gt;1.3.2\u0026lt;/mybatis.version\u0026gt; \u0026lt;/properties\u0026gt; \u0026lt;!-- 版本仲裁中心,管理子模块中使用的依赖版本 --\u0026gt; \u0026lt;dependencyManagement\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-dependencies\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${spring-boot.version}\u0026lt;/version\u0026gt; \u0026lt;type\u0026gt;pom\u0026lt;/type\u0026gt; \u0026lt;scope\u0026gt;import\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-dependencies\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${spring-cloud.version}\u0026lt;/version\u0026gt; \u0026lt;type\u0026gt;pom\u0026lt;/type\u0026gt; \u0026lt;scope\u0026gt;import\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-alibaba-dependencies\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${spring-cloud-alibaba.version}\u0026lt;/version\u0026gt; \u0026lt;type\u0026gt;pom\u0026lt;/type\u0026gt; \u0026lt;scope\u0026gt;import\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.projectlombok\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;lombok\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${lombok.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;mysql\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mysql-connector-java\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${mysql.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.mybatis.spring.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mybatis-spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${mybatis.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;/dependencyManagement\u0026gt; \u0026lt;!-- 安装Spring Boot Maven插件，可以简化Maven和Spring Boot之间的交互 --\u0026gt; \u0026lt;build\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/build\u0026gt; \u0026lt;/project\u0026gt; 4、创建公共模块star-common（Maven项目） # 1、在该模块pom.xml中，加入需要的依赖\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;project xmlns=\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u0026gt; \u0026lt;parent\u0026gt; \u0026lt;artifactId\u0026gt;star-cloud\u0026lt;/artifactId\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;/parent\u0026gt; \u0026lt;modelVersion\u0026gt;4.0.0\u0026lt;/modelVersion\u0026gt; \u0026lt;artifactId\u0026gt;star-common\u0026lt;/artifactId\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.projectlombok\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;lombok\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;mysql\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mysql-connector-java\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;/project\u0026gt; 2、编写实体类以及工具类\n5、创建微服务 # 5.1、创建商品微服务 # 引入依赖\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;project xmlns=\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u0026gt; \u0026lt;parent\u0026gt; \u0026lt;artifactId\u0026gt;star-cloud\u0026lt;/artifactId\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;/parent\u0026gt; \u0026lt;modelVersion\u0026gt;4.0.0\u0026lt;/modelVersion\u0026gt; \u0026lt;artifactId\u0026gt;star-product\u0026lt;/artifactId\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;star-common\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.mybatis.spring.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mybatis-spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;druid\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;build\u0026gt; \u0026lt;resources\u0026gt; \u0026lt;resource\u0026gt; \u0026lt;directory\u0026gt;src/main/java\u0026lt;/directory\u0026gt; \u0026lt;includes\u0026gt; \u0026lt;include\u0026gt;**/*.xml\u0026lt;/include\u0026gt; \u0026lt;/includes\u0026gt; \u0026lt;/resource\u0026gt; \u0026lt;resource\u0026gt; \u0026lt;directory\u0026gt;src/main/resources\u0026lt;/directory\u0026gt; \u0026lt;/resource\u0026gt; \u0026lt;/resources\u0026gt; \u0026lt;/build\u0026gt; \u0026lt;/project\u0026gt; 由于引入的公共模块已经引入了lombok和mysql依赖，所以不需要再引入\n编写代码\n5.2、创建订单微服务 # 和商品微服务一样的步骤\n6、订单微服务访问商品微服务 # 6.1、在需要的微服务spring容器中添加RestTemplate # @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } 6.2、使用RestTemplate实例方法进行访问商品微服务 # @PostMapping(\u0026#34;/insert/{pid}/{number}\u0026#34;) public ResultVO insert(@PathVariable Integer pid,@PathVariable Integer number){ try { ResultVO resultVO = restTemplate.getForObject(\u0026#34;http://localhost:8080/product/findByPid/\u0026#34; + pid, ResultVO.class); //此处返回的是一个LinkedHashMap LinkedHashMap map = (LinkedHashMap)resultVO.getE(); //使用hutool工具，将map中的值封装进对象 Product product = BeanUtil.fillBeanWithMap(map,new Product(),true); //将商品中的id和名称封装进订单对象 Orders orders = new Orders(); BeanUtil.copyProperties(product,orders,true); orders.setNumber(number); orders.setPrice(product.getPrice() * number); orders.setUsername(\u0026#34;lucy\u0026#34;); //插入数据库 ordersService.insert(orders); return ResultVO.success(\u0026#34;新增订单成功\u0026#34;); }catch (Exception e){ e.printStackTrace(); return ResultVO.fail(\u0026#34;新增订单异常\u0026#34;); } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/fd17f7dc/ea8d3623/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eSpringCloud的概念 \n    \u003cdiv id=\"springcloud的概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#springcloud%e7%9a%84%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eSpring Cloud框架是Spring框架中专门用来针对微服务开发的框架，它的原生是NetFlix公司开发的一套微服务框架，但是后来被Spring收购了\u003c/li\u003e\n\u003cli\u003eSpring Cloud框架产品中，提供了很多微服务服务组件，如: Eureka注册中心，Zuul网关组件 ，Configuration配置中心 ， Feign服务间通信 ， Hystrix服务降级，Bus消息总线等\u003c/li\u003e\n\u003cli\u003eSpringCloud已经闭源了，SpringCloud的版本均为英国伦敦的地铁站站名，最早 的 Release 版本 Angel，第二个 Release 版本 Brixton（英国地名），然后是 Camden、 Dalston、Edgware、Finchley、Greenwich、Hoxton。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eSpringCloud的组件 \n    \u003cdiv id=\"springcloud的组件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#springcloud%e7%9a%84%e7%bb%84%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"1597213783265.png\"\n    data-zoom-src=\"/posts/3ab7256e/3f5635d6/fd17f7dc/ea8d3623/image/202109181347783.png\"\n    src=\"/posts/3ab7256e/3f5635d6/fd17f7dc/ea8d3623/image/202109181347783.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"2、SpringCloud","type":"posts"},{"content":"urllib提供了一系列用于操作URL的功能。\nurllib包包含以下几个模块：\nurllib.request - 打开和读取 URL。 urllib.error - 包含 urllib.request 抛出的异常。 urllib.parse - 解析 URL。 urllib.robotparser - 解析 robots.txt 文件。 request # urllib.request 定义了一些打开 URL 的函数和类，包含授权验证、重定向、浏览器 cookies等。\nurllib.request 可以模拟浏览器的一个请求发起过程。\nurlopen就是默认的opener，如果需要设置cookie、代理等，就需要自己定义opener\n我们可以使用 urllib.request 的 urlopen 方法来打开一个 URL，返回一个HTTPResponse对象，语法格式如下：\nurllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, context=None) \u0026#39;\u0026#39;\u0026#39; url：url 地址。 data：发送到服务器的其他数据对象，默认为 None。 timeout：设置访问超时时间。 cafile 和 capath：cafile 为 CA 证书， capath 为 CA 证书的路径，使用 HTTPS 需要用到。 context：ssl.SSLContext类型，用来指定 SSL 设置 \u0026#39;\u0026#39;\u0026#39; Get # from urllib import request resp = request.urlopen(\u0026#39;http://www.baidu.com/\u0026#39;) #获取相应状态 print(\u0026#39;Statu:\u0026#39;,resp.status,resp.code,resp.reason) #获取相应头，返回结果是一个list head = resp.getheaders() for k,v in head: print(\u0026#39;%s : %s\u0026#39; % (k,v)) #获取html,返回的是一个bytes htmlBytes = resp.read() print(\u0026#39;Data:\u0026#39;,htmlBytes.decode(\u0026#39;utf8\u0026#39;)) \u0026#39;\u0026#39;\u0026#39; Statu: 200 200 OK Bdpagetype : 1 Bdqid : 0xa3f2605700088dec Cache-Control : private Content-Type : text/html;charset=utf-8 Date : Sat, 19 Feb 2022 04:05:09 GMT Expires : Sat, 19 Feb 2022 04:04:28 GMT P3p : CP=\u0026#34; OTI DSP COR IVA OUR IND COM \u0026#34; P3p : CP=\u0026#34; OTI DSP COR IVA OUR IND COM \u0026#34; Server : BWS/1.1 Set-Cookie : BAIDUID=2A0FEC55C3A41C151689C00F84061B38:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com Set-Cookie : BIDUPSID=2A0FEC55C3A41C151689C00F84061B38; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com Set-Cookie : PSTM=1645243509; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com Set-Cookie : BAIDUID=2A0FEC55C3A41C15DAA13C7EFAE72913:FG=1; max-age=31536000; expires=Sun, 19-Feb-23 04:05:09 GMT; domain=.baidu.com; path=/; version=1; comment=bd Set-Cookie : BDSVRTM=0; path=/ Set-Cookie : BD_HOME=1; path=/ Set-Cookie : H_PS_PSSID=35836_35106_31660_35768_35776_34584_35490_35871_35804_35324_26350; path=/; domain=.baidu.com Traceid : 1645243509051811482611813610699325214188 Vary : Accept-Encoding Vary : Accept-Encoding X-Frame-Options : sameorigin X-Ua-Compatible : IE=Edge,chrome=1 Connection : close Transfer-Encoding : chunked Data: \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt;... \u0026#39;\u0026#39;\u0026#39; Post # 如果要以POST发送一个请求，只需要把参数data以bytes形式传入。\n例如，在本地使用java写一个post接口\n@PostMapping(\u0026#34;/postTest\u0026#34;) @ResponseBody public String postTest(String name,int age){ return \u0026#34;收到post请求，name = \u0026#34; + name + \u0026#34;,age = \u0026#34; + age; } 使用python发送post请求\nfrom urllib import request,parse # dict类型的参数 requestData = { \u0026#39;name\u0026#39;:\u0026#39;lucy\u0026#39;, \u0026#39;age\u0026#39;:18 } # 通过parse.urlencode()将字典转换为字符串 requestDataBytes = parse.urlencode(requestData).encode(\u0026#39;utf8\u0026#39;) # data设置参数，发送请求 resp = request.urlopen(\u0026#39;http://localhost:8090/postTest\u0026#39;,data=requestDataBytes) print(\u0026#39;Status:\u0026#39;,resp.code,resp.reason) head = resp.getheaders() print(type(head)) for k,v in head: print(\u0026#39;%s : %s\u0026#39; % (k,v)) print(\u0026#39;Data:\u0026#39;,resp.read().decode(\u0026#39;utf8\u0026#39;)) \u0026#39;\u0026#39;\u0026#39; Status: 200 \u0026lt;class \u0026#39;list\u0026#39;\u0026gt; Content-Type : text/plain;charset=UTF-8 Content-Length : 39 Date : Sat, 19 Feb 2022 04:48:03 GMT Connection : close Data: 收到post请求，name = lucy,age = 18 \u0026#39;\u0026#39;\u0026#39; Request对象 # 如果我们要想模拟浏览器发送GET请求，就需要使用Request对象，通过往Request对象添加HTTP头User-Agent，我们就可以把请求伪装成浏览器。例如，模拟Win7使用Chrome请求廖雪峰首页（这个首页是有反爬的，不加请求头无法回503）：\nUser-Agent中文名为用户代理，是Http协议中的一部分，属于头域的组成部分，User Agent也简称UA。它是一个特殊字符串头，是一种向访问网站提供你所使用的浏览器类型及版本、操作系统及版本、浏览器内核、等信息的标识。通过这个标 识，用户所访问的网站可以显示不同的排版从而为用户提供更好的体验或者进行信息统计；例如用手机访问谷歌和电脑访问是不一样的，这些是谷歌根据访问者的 UA来判断的。UA可以进行伪装。\nfrom urllib import request #定义Request对象 req = request.Request(\u0026#39;http://www.liaoxuefeng.com/\u0026#39;) req.add_header(\u0026#39;User-Agent\u0026#39;, \u0026#39;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1\u0026#39;) resp = request.urlopen(req) #获取相应状态 print(\u0026#39;Statu:\u0026#39;,resp.status,resp.code,resp.reason) #获取相应头，返回结果是一个dict字典 head = resp.getheaders() for k,v in head: print(\u0026#39;%s : %s\u0026#39; % (k,v)) #获取html,返回的是一个bytes htmlBytes = resp.read() print(\u0026#39;Data:\u0026#39;,htmlBytes.decode(\u0026#39;utf8\u0026#39;)) User-Agent # User-Agent中文名为用户代理，是Http协议中的一部分，属于头域的组成部分，User Agent也简称UA。它是一个特殊字符串头，是一种向访问网站提供你所使用的浏览器类型及版本、操作系统及版本、浏览器内核、等信息的标识。通过这个标 识，用户所访问的网站可以显示不同的排版从而为用户提供更好的体验或者进行信息统计；例如用手机访问谷歌和电脑访问是不一样的，这些是谷歌根据访问者的 UA来判断的。UA可以进行伪装。\n常用的User-Agent可以在网上查到\nChrome-Win7: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1 Handler # Handler 的中文是处理者、处理器。 Handler 能处理请求（HTTP、HTTPS、FTP等）中的各种事情。它的具体实现是这个类 urllib.request.BaseHandler。它是所有的 Handler 的基类，其提供了最基本的Handler的方法，例如default_open()、protocol_request()等。\n继承 BaseHandler常见的类：\nProxyHandler：为请求设置代理 HTTPCookieProcessor：处理 HTTP 请求中的 Cookies HTTPDefaultErrorHandler：处理 HTTP 响应错误。 HTTPRedirectHandler：处理 HTTP 重定向。 HTTPPasswordMgr：用于管理密码，它维护了用户名密码的表。 HTTPBasicAuthHandler：用于登录认证，一般和 HTTPPasswordMgr 结合使用。 代理 # 有些网站做了浏览频率限制。如果我们请求该网站频率过高。该网站会被封 IP，禁止我们的访问。所以我们需要使用代理来突破这“枷锁”。\n通过一个Proxy去访问网站，我们需要利用ProxyHandler来处理\n这个网站可以查询一些免费的代理：https://www.kuaidaili.com/free/\nfrom urllib import request #定义代理服务器,需要传入一个字典 ph = request.ProxyHandler({ \u0026#39;http\u0026#39;:\u0026#39;103.37.141.69:80\u0026#39;, \u0026#39;https\u0026#39;:\u0026#39;103.37.141.69:80\u0026#39; }) #定义opener op = request.build_opener(ph) #定义Request对象，如果没有request要设置，url也可以直接写在op.open的参数里面 req = request.Request(\u0026#39;http://w\u0026#39;) #访问 resp = op.open(req) print(\u0026#39;Status:\u0026#39;,resp.code,resp.reason) head = resp.getheaders() print(type(head)) for k,v in head: print(\u0026#39;%s : %s\u0026#39; % (k,v)) print(\u0026#39;Data:\u0026#39;,resp.read().decode(\u0026#39;utf8\u0026#39;)) Cookie # 如果请求的页面每次需要身份验证，我们可以使用 Cookies 来自动登录，免去重复登录验证的操作。获取 Cookies 需要使用http.cookiejar.CookieJar() 实例化一个 Cookies 对象。再用 urllib.request.HTTPCookieProcessor 构建出 handler 对象。最后使用 opener 的 open() 函数即可。\n保存cookie # import http.cookiejar import urllib.request #需要请求的地址 url = \u0026#34;http://tieba.baidu.com/\u0026#34; #保存cookie的文件 fileName = \u0026#39;cookie.txt\u0026#39; #实例化一个Cookies对象 cookie = http.cookiejar.CookieJar() #构建handler对象 handler = urllib.request.HTTPCookieProcessor(cookie) opener = urllib.request.build_opener(handler) response = opener.open(url) #将cookie保存到文件中 f = open(fileName,\u0026#39;a\u0026#39;) for item in cookie: f.write(item.name+\u0026#34; = \u0026#34;+item.value+\u0026#39;\\n\u0026#39;) f.close() 携带cookie # 直接设置到请求头就可以了\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/1b37041b/339aff44/e34f906c/","section":"文章","summary":"\u003cp\u003eurllib提供了一系列用于操作URL的功能。\u003c/p\u003e\n\u003cp\u003eurllib包包含以下几个模块：\u003c/p\u003e","title":"2、urllib","type":"posts"},{"content":" Java实现定时器的方式 # 1、Timer：这是Java自带的java.util.Timer类，这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行，但不能在指定时间运行，一般使用比较少\n2、Spring Task：Spring3.0以后自带的Task，可以将它看成一个轻量级的Quartz，而且使用起来比Quartz简单许多\n3、ScheduledExecutorService：也是jdk自带的一个类，是基于线程池设计的定时任务类，每个调度任务都会分配到线程池中的一个线程去执行，也就是说，任务是并发执行的，互不影响\n4、Quartz：这是一个功能比较强大的调度器，可以让你的程序在指定的时间执行，也可以按照某一个频度执行，配置起来稍显复杂\n5、xxl-job：XXL-JOB是一个轻量级分布式任务调度平台，其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线，开箱即用。xxl三个字母是其开发者许雪里名字的缩写。\nSpringScheduled # 简单使用 # 1、添加依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、启动类添加注解，开启对定时任务的支持 # @EnableScheduling 3、新建定时任务 # @Component public class TimingService { private int count=0; //使用cron表达式，每天23点执行一次 @Scheduled(cron=\u0026#34;0 0 23 * *\u0026#34;) public void timingTask() throws Exception { System.out.println(\u0026#34;this is scheduler task runing \u0026#34;+(count++)); } } 动态编辑定时任务 # 1、添加执行定时任务的线程池配置类 # import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; /** * @描述 执行定时任务的线程池配置类 * @创建人 yhgh * @创建时间 2022/8/26 14:01 */ @Configuration public class SchedulingConfig { @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); // 定时任务执行线程池核心线程数 taskScheduler.setPoolSize(4); taskScheduler.setRemoveOnCancelPolicy(true); taskScheduler.setThreadNamePrefix(\u0026#34;TaskSchedulerThreadPool-\u0026#34;); return taskScheduler; } } 2、添加ScheduledFuture的包装类 # import java.util.concurrent.ScheduledFuture; /** * @描述 ScheduledFuture的包装类。ScheduledFuture是ScheduledExecutorService定时任务线程池的执行结果。 * @创建人 yhgh * @创建时间 2022/8/26 14:03 */ public final class ScheduledTask { public volatile ScheduledFuture\u0026lt;?\u0026gt; future; /** * 取消定时任务 */ public void cancel() { ScheduledFuture\u0026lt;?\u0026gt; future = this.future; if (future != null) { future.cancel(true); } } } 3、SpringContextUtils用于从Spring容器中操作Bean # import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * @描述 用于从Spring容器中操作Bean * @创建人 yhgh * @创建时间 2022/8/26 14:13 */ @Component public class SpringContextUtils implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringContextUtils.applicationContext = applicationContext; } public static Object getBean(String name) { return applicationContext.getBean(name); } public static \u0026lt;T\u0026gt; T getBean(Class\u0026lt;T\u0026gt; requiredType) { return applicationContext.getBean(requiredType); } public static \u0026lt;T\u0026gt; T getBean(String name, Class\u0026lt;T\u0026gt; requiredType) { return applicationContext.getBean(name, requiredType); } public static boolean containsBean(String name) { return applicationContext.containsBean(name); } public static boolean isSingleton(String name) { return applicationContext.isSingleton(name); } public static Class\u0026lt;? extends Object\u0026gt; getType(String name) { return applicationContext.getType(name); } } 4、添加Runnable接口实现类，被定时任务线程池调用，用来执行指定bean里面的方法。 # import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import java.lang.reflect.Method; import java.util.Objects; /** * @描述 Runnable接口实现类，被定时任务线程池调用，用来执行指定bean里面的方法。 * @创建人 yhgh * @创建时间 2022/8/26 14:04 */ public class SchedulingRunnable implements Runnable { private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class); private String beanName; private String methodName; private String params; public SchedulingRunnable(String beanName, String methodName) { this(beanName, methodName, null); } public SchedulingRunnable(String beanName, String methodName, String params) { this.beanName = beanName; this.methodName = methodName; this.params = params; } @Override public void run() { logger.info(\u0026#34;定时任务开始执行 - bean：{}，方法：{}，参数：{}\u0026#34;, beanName, methodName, params); long startTime = System.currentTimeMillis(); try { Object target = SpringContextUtils.getBean(beanName); Method method = null; if (StringUtils.hasText(params)) { method = target.getClass().getDeclaredMethod(methodName, String.class); } else { method = target.getClass().getDeclaredMethod(methodName); } ReflectionUtils.makeAccessible(method); if (StringUtils.hasText(params)) { method.invoke(target, params); } else { method.invoke(target); } } catch (Exception ex) { logger.error(String.format(\u0026#34;定时任务执行异常 - bean：%s，方法：%s，参数：%s \u0026#34;, beanName, methodName, params), ex); } long times = System.currentTimeMillis() - startTime; logger.info(\u0026#34;定时任务执行结束 - bean：{}，方法：{}，参数：{}，耗时：{} 毫秒\u0026#34;, beanName, methodName, params, times); } /** * 重写equals方法，使用beanName、methodName、params比较 * @param o * @return */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SchedulingRunnable that = (SchedulingRunnable) o; if (params == null) { return beanName.equals(that.beanName) \u0026amp;\u0026amp; methodName.equals(that.methodName) \u0026amp;\u0026amp; that.params == null; } return beanName.equals(that.beanName) \u0026amp;\u0026amp; methodName.equals(that.methodName) \u0026amp;\u0026amp; params.equals(that.params); } @Override public int hashCode() { if (params == null) { return Objects.hash(beanName, methodName); } return Objects.hash(beanName, methodName, params); } } 5、添加定时任务注册类，用来增加、删除定时任务。 # import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.config.CronTask; import org.springframework.stereotype.Component; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @描述 添加定时任务注册类，用来增加、删除定时任务。 * @创建人 yhgh * @创建时间 2022/8/26 14:18 */ @Component public class CronTaskRegistrar implements DisposableBean { private final Map\u0026lt;Runnable, ScheduledTask\u0026gt; scheduledTasks = new ConcurrentHashMap\u0026lt;\u0026gt;(16); @Autowired private TaskScheduler taskScheduler; public TaskScheduler getScheduler() { return this.taskScheduler; } /** * 添加任务 * @param task * @param cronExpression */ public void addCronTask(Runnable task, String cronExpression) { addCronTask(new CronTask(task, cronExpression)); } /** * 将定时任务添加进Map中 * @param cronTask */ private void addCronTask(CronTask cronTask) { if (cronTask != null) { Runnable task = cronTask.getRunnable(); if (this.scheduledTasks.containsKey(task)) { removeCronTask(task); } this.scheduledTasks.put(task, scheduleCronTask(cronTask)); } } /** * 删除任务 * @param task */ public void removeCronTask(Runnable task) { ScheduledTask scheduledTask = this.scheduledTasks.remove(task); if (scheduledTask != null) scheduledTask.cancel(); } /** * 对cronTask进行包装，并执行定时任务 * @param cronTask * @return */ private ScheduledTask scheduleCronTask(CronTask cronTask) { ScheduledTask scheduledTask = new ScheduledTask(); scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger()); return scheduledTask; } @Override public void destroy() { for (ScheduledTask task : this.scheduledTasks.values()) { task.cancel(); } this.scheduledTasks.clear(); } } 6、建表、编写Dao # CREATE TABLE `system_task` ( `task_id` varchar(70) NOT NULL COMMENT \u0026#39;任务id\u0026#39;, `bean_name` varchar(100) DEFAULT NULL COMMENT \u0026#39;bean名称\u0026#39;, `method_name` varchar(100) DEFAULT NULL COMMENT \u0026#39;方法名称\u0026#39;, `method_params` varchar(255) DEFAULT NULL COMMENT \u0026#39;方法参数\u0026#39;, `cron_expression` varchar(255) DEFAULT NULL COMMENT \u0026#39;cron表达式\u0026#39;, `task_status` tinyint(1) DEFAULT NULL COMMENT \u0026#39;状态（1正常 0暂停）\u0026#39;, `remark` varchar(500) DEFAULT NULL COMMENT \u0026#39;备注\u0026#39;, `create_time` datetime DEFAULT NULL COMMENT \u0026#39;创建时间\u0026#39;, `update_time` datetime DEFAULT NULL COMMENT \u0026#39;更新时间\u0026#39;, PRIMARY KEY (`task_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=\u0026#39;系统定时任务\u0026#39;; import java.util.Date; import java.io.Serializable; /** * 系统定时任务(SystemTask)实体类 * * @since 2022-08-26 15:29:44 */ public class SystemTask implements Serializable { private static final long serialVersionUID = -54922456361386370L; /** * 任务id */ private String taskId; /** * bean名称 */ private String beanName; /** * 方法名称 */ private String methodName; /** * 方法参数 */ private String methodParams; /** * cron表达式 */ private String cronExpression; /** * 状态（1正常 0暂停） */ private Boolean taskStatus; /** * 备注 */ private String remark; /** * 创建时间 */ private Date createTime; /** * 更新时间 */ private Date updateTime; public String getTaskId() { return taskId; } public void setTaskId(String taskId) { this.taskId = taskId; } public String getBeanName() { return beanName; } public void setBeanName(String beanName) { this.beanName = beanName; } public String getMethodName() { return methodName; } public void setMethodName(String methodName) { this.methodName = methodName; } public String getMethodParams() { return methodParams; } public void setMethodParams(String methodParams) { this.methodParams = methodParams; } public String getCronExpression() { return cronExpression; } public void setCronExpression(String cronExpression) { this.cronExpression = cronExpression; } public Boolean getTaskStatus() { return taskStatus; } public void setTaskStatus(Boolean taskStatus) { this.taskStatus = taskStatus; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public Date getUpdateTime() { return updateTime; } public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } } import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; /** * 系统定时任务(SystemTask)表数据库访问层 * * @since 2022-08-26 15:29:43 */ @Mapper public interface SystemTaskDao { /** * 通过ID查询单条数据 * * @param taskId 主键 * @return 实例对象 */ SystemTask queryById(String taskId); /** * 新增数据 * * @param systemTask 实例对象 * @return 影响行数 */ int insert(SystemTask systemTask); /** * 修改数据 * * @param systemTask 实例对象 * @return 影响行数 */ int update(SystemTask systemTask); /** * 通过主键删除数据 * * @param taskId 主键 * @return 影响行数 */ int deleteById(String taskId); /** * 根据任务状态查询所有任务 * @param status * @return */ List\u0026lt;SystemTask\u0026gt; selectTasksByStatus(@Param(\u0026#34;taskStatus\u0026#34;)Boolean status); } \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE mapper PUBLIC \u0026#34;-//mybatis.org//DTD Mapper 3.0//EN\u0026#34; \u0026#34;http://mybatis.org/dtd/mybatis-3-mapper.dtd\u0026#34;\u0026gt; \u0026lt;mapper namespace=\u0026#34;top.ygang.task.dao.SystemTaskDao\u0026#34;\u0026gt; \u0026lt;resultMap type=\u0026#34;top.ygang.task.entity.SystemTask\u0026#34; id=\u0026#34;SystemTaskMap\u0026#34;\u0026gt; \u0026lt;result property=\u0026#34;taskId\u0026#34; column=\u0026#34;task_id\u0026#34; jdbcType=\u0026#34;VARCHAR\u0026#34;/\u0026gt; \u0026lt;result property=\u0026#34;beanName\u0026#34; column=\u0026#34;bean_name\u0026#34; jdbcType=\u0026#34;VARCHAR\u0026#34;/\u0026gt; \u0026lt;result property=\u0026#34;methodName\u0026#34; column=\u0026#34;method_name\u0026#34; jdbcType=\u0026#34;VARCHAR\u0026#34;/\u0026gt; \u0026lt;result property=\u0026#34;methodParams\u0026#34; column=\u0026#34;method_params\u0026#34; jdbcType=\u0026#34;VARCHAR\u0026#34;/\u0026gt; \u0026lt;result property=\u0026#34;cronExpression\u0026#34; column=\u0026#34;cron_expression\u0026#34; jdbcType=\u0026#34;VARCHAR\u0026#34;/\u0026gt; \u0026lt;result property=\u0026#34;taskStatus\u0026#34; column=\u0026#34;task_status\u0026#34; jdbcType=\u0026#34;TINYINT\u0026#34;/\u0026gt; \u0026lt;result property=\u0026#34;remark\u0026#34; column=\u0026#34;remark\u0026#34; jdbcType=\u0026#34;VARCHAR\u0026#34;/\u0026gt; \u0026lt;result property=\u0026#34;createTime\u0026#34; column=\u0026#34;create_time\u0026#34; jdbcType=\u0026#34;TIMESTAMP\u0026#34;/\u0026gt; \u0026lt;result property=\u0026#34;updateTime\u0026#34; column=\u0026#34;update_time\u0026#34; jdbcType=\u0026#34;TIMESTAMP\u0026#34;/\u0026gt; \u0026lt;/resultMap\u0026gt; \u0026lt;!--查询单个--\u0026gt; \u0026lt;select id=\u0026#34;queryById\u0026#34; resultMap=\u0026#34;SystemTaskMap\u0026#34;\u0026gt; select task_id, bean_name, method_name, method_params, cron_expression, task_status, remark, create_time, update_time from system_task where task_id = #{taskId} \u0026lt;/select\u0026gt; \u0026lt;!--新增所有列--\u0026gt; \u0026lt;insert id=\u0026#34;insert\u0026#34;\u0026gt; insert into system_task(bean_name, method_name, method_params, cron_expression, task_status, remark, create_time, update_time) values (#{beanName}, #{methodName}, #{methodParams}, #{cronExpression}, #{taskStatus}, #{remark}, #{createTime}, #{updateTime}) \u0026lt;/insert\u0026gt; \u0026lt;!--通过主键修改数据--\u0026gt; \u0026lt;update id=\u0026#34;update\u0026#34;\u0026gt; update system_task \u0026lt;set\u0026gt; \u0026lt;if test=\u0026#34;beanName != null and beanName != \u0026#39;\u0026#39;\u0026#34;\u0026gt; bean_name = #{beanName}, \u0026lt;/if\u0026gt; \u0026lt;if test=\u0026#34;methodName != null and methodName != \u0026#39;\u0026#39;\u0026#34;\u0026gt; method_name = #{methodName}, \u0026lt;/if\u0026gt; \u0026lt;if test=\u0026#34;methodParams != null and methodParams != \u0026#39;\u0026#39;\u0026#34;\u0026gt; method_params = #{methodParams}, \u0026lt;/if\u0026gt; \u0026lt;if test=\u0026#34;cronExpression != null and cronExpression != \u0026#39;\u0026#39;\u0026#34;\u0026gt; cron_expression = #{cronExpression}, \u0026lt;/if\u0026gt; \u0026lt;if test=\u0026#34;taskStatus != null\u0026#34;\u0026gt; task_status = #{taskStatus}, \u0026lt;/if\u0026gt; \u0026lt;if test=\u0026#34;remark != null and remark != \u0026#39;\u0026#39;\u0026#34;\u0026gt; remark = #{remark}, \u0026lt;/if\u0026gt; \u0026lt;if test=\u0026#34;createTime != null\u0026#34;\u0026gt; create_time = #{createTime}, \u0026lt;/if\u0026gt; \u0026lt;if test=\u0026#34;updateTime != null\u0026#34;\u0026gt; update_time = #{updateTime}, \u0026lt;/if\u0026gt; \u0026lt;/set\u0026gt; where task_id = #{taskId} \u0026lt;/update\u0026gt; \u0026lt;!--通过主键删除--\u0026gt; \u0026lt;delete id=\u0026#34;deleteById\u0026#34;\u0026gt; delete from system_task where task_id = #{taskId} \u0026lt;/delete\u0026gt; \u0026lt;select id=\u0026#34;selectTasksByStatus\u0026#34; resultMap=\u0026#34;SystemTaskMap\u0026#34;\u0026gt; select task_id, bean_name, method_name, method_params, cron_expression, task_status, remark, create_time, update_time from system_task where task_status = #{taskStatus} \u0026lt;/select\u0026gt; \u0026lt;/mapper\u0026gt; 7、编写service # import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; /** * @描述 系统定时任务业务层 * @创建人 yhgh * @创建时间 2022/8/26 15:43 */ @Service public class SystemTaskService { @Autowired private SystemTaskDao systemTaskDao; @Autowired private CronTaskRegistrar cronTaskRegistrar; /** * 新增系统定时任务 * @param systemTask * @return */ public boolean addTask(SystemTask systemTask){ if (!StringUtils.hasText(systemTask.getBeanName()) || !StringUtils.hasText(systemTask.getMethodName()) || !StringUtils.hasText(systemTask.getCronExpression())){ return false; } systemTask.setTaskId(UUID.randomUUID().toString()); Date now = new Date(); systemTask.setCreateTime(now); systemTask.setUpdateTime(now); int insert = systemTaskDao.insert(systemTask); if (insert \u0026lt;= 0) { return false; } else { if (systemTask.getTaskStatus()) { SchedulingRunnable task = new SchedulingRunnable(systemTask.getBeanName(), systemTask.getMethodName(), systemTask.getMethodParams()); cronTaskRegistrar.addCronTask(task, systemTask.getCronExpression()); } } return true; } /** * 根据id更新系统定时任务 * @param systemTask * @return */ public boolean updateTask(SystemTask systemTask){ SystemTask old = systemTaskDao.queryById(systemTask.getTaskId()); systemTask.setUpdateTime(new Date()); int update = systemTaskDao.update(systemTask); if (update \u0026lt;= 0) { return false; } // 删除老的定时任务 SchedulingRunnable task = new SchedulingRunnable(old.getBeanName(), old.getMethodName(), old.getMethodParams()); cronTaskRegistrar.removeCronTask(task); // 如果新定时任务状态为正常，开启新定时任务 SystemTask newTask = systemTaskDao.queryById(systemTask.getTaskId()); if (newTask.getTaskStatus()){ SchedulingRunnable ta = new SchedulingRunnable(newTask.getBeanName(), newTask.getMethodName(), newTask.getMethodParams()); cronTaskRegistrar.addCronTask(ta, newTask.getCronExpression()); } return true; } /** * 根据id删除系统定时任务 * @param systemTask * @return */ public boolean removeTask(SystemTask systemTask){ SystemTask ta = systemTaskDao.queryById(systemTask.getTaskId()); int delete = systemTaskDao.deleteById(systemTask.getTaskId()); if (delete \u0026lt;= 0) return false; else{ if (ta.getTaskStatus()) { SchedulingRunnable task = new SchedulingRunnable(ta.getBeanName(), ta.getMethodName(), ta.getMethodParams()); cronTaskRegistrar.removeCronTask(task); } } return true; } } 8、添加实现了CommandLineRunner接口的类，当spring boot项目启动完成后，加载数据库里状态为正常的定时任务。 # import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.util.List; /** * @描述 项目启动完成后，加载数据库里状态为正常的定时任务 * @创建人 yhgh * @创建时间 2022/8/26 15:22 */ @Component public class SystemTaskRunner implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(SystemTaskRunner.class); @Autowired private SystemTaskDao systemTaskDao; @Autowired private CronTaskRegistrar cronTaskRegistrar; @Override public void run(String... args) { // 初始加载数据库里状态为正常的定时任务 List\u0026lt;SystemTask\u0026gt; taskList = systemTaskDao.selectTasksByStatus(true); if (CollectionUtils.isNotEmpty(taskList)) { for (SystemTask task : taskList) { SchedulingRunnable schedulingRunnable = new SchedulingRunnable(task.getBeanName(), task.getMethodName(), task.getMethodParams()); cronTaskRegistrar.addCronTask(schedulingRunnable, task.getCronExpression()); } logger.info(\u0026#34;定时任务已加载完毕...\u0026#34;); } } } cron表达式 # 可以百度搜索cron表达式生成\nCron表达式是一个字符串，字符串以5或6个空格隔开，分为6或7个域，每一个域代表一个含义\n第一位：Seconds（秒）\u0026ndash;可出现\u0026quot;, - * /\u0026ldquo;四个字符，有效范围为0-59的整数 第二位：Minutes（分）\u0026ndash;可出现\u0026rdquo;, - * /\u0026ldquo;四个字符，有效范围为0-59的整数 第三位：Hours（时）\u0026ndash;可出现\u0026rdquo;, - * /\u0026ldquo;四个字符，有效范围为0-23的整数 第四位：DayofMonth（日）\u0026ndash;可出现\u0026rdquo;, - * / ? L W C\u0026quot;八个字符，有效范围为0-31的整数 第五位：Month（月）\u0026ndash;可出现\u0026quot;, - * /\u0026ldquo;四个字符，有效范围为1-12的整数或JAN-DEc 第六位：DayofWeek（星期）\u0026ndash;可出现\u0026rdquo;, - * / ? L C #\u0026ldquo;四个字符，有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天，2表示星期一， 依次类推 第七位：Year（年）\u0026ndash;可出现\u0026rdquo;, - * /\u0026ldquo;四个字符，有效范围为1970-2099年\n注意事项： 每一个域都使用数字，但还可以出现如下特殊字符，它们的含义是： （1）*：表示匹配该域的任意值。假如在Minutes域使用*, 即表示每分钟都会触发事件。 （2）?：只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值，但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度，不管20日到底是星期几，则只能使用如下写法： 13 13 15 20 * ?, 其中最后一位只能用？，而不能使用*，如果使用*表示不管星期几都会触发，实际上并不是这样。 （3）-：表示范围。例如在Minutes域使用5-20，表示从5分到20分钟每分钟触发一次 （4）/：表示起始时间开始触发，然后每隔固定时间触发一次。例如在Minutes域使用5/20,则意味着5分钟触发一次，而25，45等分别触发一次. （5）,：表示列出枚举值。例如：在Minutes域使用5,20，则意味着在5和20分每分钟触发一次。 （6）L：表示最后，只能出现在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。 （7）W:表示有效工作日(周一到周五),只能出现在DayofMonth域，系统将在离指定日期的最近的有效工作日触发事件。例如：在 DayofMonth使用5W，如果5日是星期六，则将在最近的工作日：星期五，即4日触发。如果5日是星期天，则在6日(周一)触发；如果5日在星期一到星期五中的一天，则就在5日触发。另外一点，W的最近寻找不会跨过月份 。 （8）LW:这两个字符可以连用，表示在某个月最后一个工作日，即最后一个星期五。 （9）#:用于确定每个月第几个星期几，只能出现在DayofMonth域。例如在4#2，表示某月的第二个星期三。 常用表达式例子 （1）0 0 2 1 * ? * 表示在每月的1日的凌晨2点调整任务 （2）0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业 （3）0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作 （4）0 0 10,14,16 * * ? 每天上午10点，下午2点，4点 （5）0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时 （6）0 0 12 ? * WED 表示每个星期三中午12点 （7）0 0 12 * * ? 每天中午12点触发 （8）0 15 10 ? * * 每天上午10:15触发 （9）0 15 10 * * ? 每天上午10:15触发 （10）0 15 10 * * ? * 每天上午10:15触发 （11）0 15 10 * * ? 2005 2005年的每天上午10:15触发 （12）0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发 （13）0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发 （14）0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 （15）0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发 （16）0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发 （17）0 15 10 ? * MON-FRI 周一至周五的上午10:15触发 （18）0 15 10 15 * ? 每月15日上午10:15触发 （19）0 15 10 L * ? 每月最后一日的上午10:15触发 （20）0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发 （21）0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发 （22）0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发 Quartz # 传统的定时任务的缺点就是，如果进行横向扩展为多实例集群部署时，遇到了问题：定时任务在多个应用实例中重复执行了。此时quartz可以很好的解决分布式集群任务调度的问题，使得无论集群中有多少应用实例，定时任务只会触发一次。\nScheduler：与quartz schedule交互的主要api Job：Scheduler执行组件需要实现的接口（你要做什么事） JobDetail：用于定义实现了Job接口的实例 Trigger：用于执行给定作业的计划的组件（你什么时候去做） JobBuilder：用于构建JobDetail实例，或者说定义Job的实例 TriggerBuilder：用于构建触发器实例\nQuartz 官方提供了11张数据表，这里使用mysql（innodb）的数据表，也有其他数据库的表，可以到官网（http://www.quartz-scheduler.org/downloads/）下载\n1、建表\n# # 在Quartz属性文件中，需要设置 # org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_CALENDARS; CREATE TABLE QRTZ_JOB_DETAILS( SCHED_NAME VARCHAR(120) NOT NULL COMMENT \u0026#39;调度名称\u0026#39;, JOB_NAME VARCHAR(190) NOT NULL COMMENT \u0026#39;集群中job的名字\u0026#39;, JOB_GROUP VARCHAR(190) NOT NULL COMMENT \u0026#39;集群中job的所属组的名字\u0026#39;, DESCRIPTION VARCHAR(250) NULL COMMENT \u0026#39;详细描述信息\u0026#39;, JOB_CLASS_NAME VARCHAR(250) NOT NULL COMMENT \u0026#39;集群中个notejob实现类的全限定名,quartz就是根据这个路径到classpath找到该job类\u0026#39;, IS_DURABLE VARCHAR(1) NOT NULL COMMENT \u0026#39;是否持久化,把该属性设置为1，quartz会把job持久化到数据库中\u0026#39;, IS_NONCONCURRENT VARCHAR(1) NOT NULL COMMENT \u0026#39;是否并发执行\u0026#39;, IS_UPDATE_DATA VARCHAR(1) NOT NULL COMMENT \u0026#39;是否更新数据\u0026#39;, REQUESTS_RECOVERY VARCHAR(1) NOT NULL COMMENT \u0026#39;是否接受恢复执行，默认为false，设置了RequestsRecovery为true，则该job会被重新执行\u0026#39;, JOB_DATA BLOB NULL COMMENT \u0026#39;一个blob字段，存放持久化job对象\u0026#39;, PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)) ENGINE=InnoDB COMMENT=\u0026#39;已配置的jobDetail的详细信息\u0026#39;; CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL COMMENT \u0026#39;调度名称\u0026#39;, TRIGGER_NAME VARCHAR(190) NOT NULL COMMENT \u0026#39;触发器的名字\u0026#39;, TRIGGER_GROUP VARCHAR(190) NOT NULL COMMENT \u0026#39;触发器所属组的名字\u0026#39;, JOB_NAME VARCHAR(190) NOT NULL COMMENT \u0026#39;qrtz_job_details表job_name的外键\u0026#39;, JOB_GROUP VARCHAR(190) NOT NULL COMMENT \u0026#39;qrtz_job_details表job_group的外键\u0026#39;, DESCRIPTION VARCHAR(250) NULL COMMENT \u0026#39;详细描述信息\u0026#39;, NEXT_FIRE_TIME BIGINT(13) NULL COMMENT \u0026#39;上一次触发时间（毫秒）\u0026#39;, PREV_FIRE_TIME BIGINT(13) NULL COMMENT \u0026#39;下一次触发时间，默认为-1，意味不会自动触发\u0026#39;, PRIORITY INTEGER NULL COMMENT \u0026#39;优先级\u0026#39;, TRIGGER_STATE VARCHAR(16) NOT NULL COMMENT \u0026#39;当前触发器状态，设置为ACQUIRED,如果设置为WAITING,则job不会触发 （ WAITING:等待 PAUSED:暂停ACQUIRED:正常执行 BLOCKED：阻塞 ERROR：错误）\u0026#39;, TRIGGER_TYPE VARCHAR(8) NOT NULL COMMENT \u0026#39;触发器的类型，使用cron表达式\u0026#39;, START_TIME BIGINT(13) NOT NULL COMMENT \u0026#39;开始时间\u0026#39;, END_TIME BIGINT(13) NULL COMMENT \u0026#39;结束时间\u0026#39;, CALENDAR_NAME VARCHAR(190) NULL COMMENT \u0026#39;日程表名称，表qrtz_calendars的calendar_name字段外键\u0026#39;, MISFIRE_INSTR SMALLINT(2) NULL COMMENT \u0026#39;措施或者是补偿执行的策略\u0026#39;, JOB_DATA BLOB NULL COMMENT \u0026#39;一个blob字段，存放持久化job对象\u0026#39;, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)) ENGINE=InnoDB COMMENT=\u0026#39;触发器的基本信息\u0026#39;; CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL COMMENT \u0026#39;调度名称\u0026#39;, TRIGGER_NAME VARCHAR(190) NOT NULL COMMENT \u0026#39;qrtz_triggers表trigger_ name的外键\u0026#39;, TRIGGER_GROUP VARCHAR(190) NOT NULL COMMENT \u0026#39;qrtz_triggers表trigger_group的外键\u0026#39;, REPEAT_COUNT BIGINT(7) NOT NULL COMMENT \u0026#39;重复的次数统计\u0026#39;, REPEAT_INTERVAL BIGINT(12) NOT NULL COMMENT \u0026#39;重复的间隔时间\u0026#39;, TIMES_TRIGGERED BIGINT(10) NOT NULL COMMENT \u0026#39;已经触发的次数\u0026#39;, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) ENGINE=InnoDB COMMENT=\u0026#39;简单的 Trigger\u0026#39;; CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL COMMENT \u0026#39;调度名称\u0026#39;, TRIGGER_NAME VARCHAR(190) NOT NULL COMMENT \u0026#39;qrtz_triggers表trigger_name的外键\u0026#39;, TRIGGER_GROUP VARCHAR(190) NOT NULL COMMENT \u0026#39;qrtz_triggers表trigger_group的外键\u0026#39;, CRON_EXPRESSION VARCHAR(120) NOT NULL COMMENT \u0026#39;cron表达式\u0026#39;, TIME_ZONE_ID VARCHAR(80) NULL COMMENT \u0026#39;时区\u0026#39;, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) ENGINE=InnoDB COMMENT=\u0026#39;触发器的cron表达式\u0026#39;; CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL COMMENT \u0026#39;调度名称\u0026#39;, TRIGGER_NAME VARCHAR(190) NOT NULL COMMENT \u0026#39;qrtz_triggers表trigger_ name的外键\u0026#39;, TRIGGER_GROUP VARCHAR(190) NOT NULL COMMENT \u0026#39;qrtz_triggers表trigger_group的外键\u0026#39;, STR_PROP_1 VARCHAR(512) NULL COMMENT \u0026#39;String类型的trigger的第一个参数\u0026#39;, STR_PROP_2 VARCHAR(512) NULL COMMENT \u0026#39;String类型的trigger的第二个参数\u0026#39;, STR_PROP_3 VARCHAR(512) NULL COMMENT \u0026#39;String类型的trigger的第三个参数\u0026#39;, INT_PROP_1 INT NULL COMMENT \u0026#39;int类型的trigger的第一个参数\u0026#39;, INT_PROP_2 INT NULL COMMENT \u0026#39;int类型的trigger的第二个参数\u0026#39;, LONG_PROP_1 BIGINT NULL COMMENT \u0026#39;long类型的trigger的第一个参数\u0026#39;, LONG_PROP_2 BIGINT NULL COMMENT \u0026#39;long类型的trigger的第二个参数\u0026#39;, DEC_PROP_1 NUMERIC(13,4) NULL COMMENT \u0026#39;decimal类型的trigger的第一个参数\u0026#39;, DEC_PROP_2 NUMERIC(13,4) NULL COMMENT \u0026#39;decimal类型的trigger的第二个参数\u0026#39;, BOOL_PROP_1 VARCHAR(1) NULL COMMENT \u0026#39;Boolean类型的trigger的第一个参数\u0026#39;, BOOL_PROP_2 VARCHAR(1) NULL COMMENT \u0026#39;Boolean类型的trigger的第二个参数\u0026#39;, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) ENGINE=InnoDB COMMENT=\u0026#39;存储CalendarIntervalTrigger和DailyTimeIntervalTrigger\u0026#39;; CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL COMMENT \u0026#39;调度名称\u0026#39;, TRIGGER_NAME VARCHAR(190) NOT NULL COMMENT \u0026#39;qrtz_triggers表trigger_name的外键\u0026#39;, TRIGGER_GROUP VARCHAR(190) NOT NULL COMMENT \u0026#39;qrtz_triggers表trigger_group的外键\u0026#39;, BLOB_DATA BLOB NULL COMMENT \u0026#39;一个blob字段，存放持久化Trigger对象\u0026#39;, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) ENGINE=InnoDB COMMENT=\u0026#39;Trigger作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型，JobStore 并不知道如何存储实例的时候)\u0026#39;; CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL COMMENT \u0026#39;调度名称\u0026#39;, CALENDAR_NAME VARCHAR(190) NOT NULL COMMENT \u0026#39;日历名称\u0026#39;, CALENDAR BLOB NOT NULL COMMENT \u0026#39;一个blob字段，存放持久化calendar对象\u0026#39;, PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)) ENGINE=InnoDB COMMENT=\u0026#39;以 Blob 类型存储存放日历信息，quartz可配置一个日历来指定一个时间范围\u0026#39;; CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL COMMENT \u0026#39;调度名称\u0026#39;, TRIGGER_GROUP VARCHAR(190) NOT NULL COMMENT \u0026#39;qrtz_triggers表trigger_group的外键\u0026#39;, PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)) ENGINE=InnoDB COMMENT=\u0026#39;存储已暂停的 Trigger 组的信息\u0026#39;; CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL COMMENT \u0026#39;调度名称\u0026#39;, ENTRY_ID VARCHAR(95) NOT NULL COMMENT \u0026#39;调度器实例id\u0026#39;, TRIGGER_NAME VARCHAR(190) NOT NULL COMMENT \u0026#39;qrtz_triggers表trigger_name的外键\u0026#39;, TRIGGER_GROUP VARCHAR(190) NOT NULL COMMENT \u0026#39;qrtz_triggers表trigger_group的外键\u0026#39;, INSTANCE_NAME VARCHAR(190) NOT NULL COMMENT \u0026#39;调度器实例名\u0026#39;, FIRED_TIME BIGINT(13) NOT NULL COMMENT \u0026#39;触发的时间\u0026#39;, SCHED_TIME BIGINT(13) NOT NULL COMMENT \u0026#39;定时器制定的时间\u0026#39;, PRIORITY INTEGER NOT NULL COMMENT \u0026#39;优先级\u0026#39;, STATE VARCHAR(16) NOT NULL COMMENT \u0026#39;状态\u0026#39;, JOB_NAME VARCHAR(190) NULL COMMENT \u0026#39;集群中job的名字\u0026#39;, JOB_GROUP VARCHAR(190) NULL COMMENT \u0026#39;集群中job的所属组的名字\u0026#39;, IS_NONCONCURRENT VARCHAR(1) NULL COMMENT \u0026#39;是否并发\u0026#39;, REQUESTS_RECOVERY VARCHAR(1) NULL COMMENT \u0026#39;是否接受恢复执行，默认为false，设置了RequestsRecovery为true，则会被重新执行\u0026#39;, PRIMARY KEY (SCHED_NAME,ENTRY_ID)) ENGINE=InnoDB COMMENT=\u0026#39;存储与已触发的 Trigger 相关的状态信息，以及相联 Job 的执行信息\u0026#39;; CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL COMMENT \u0026#39;调度名称\u0026#39;, INSTANCE_NAME VARCHAR(190) NOT NULL COMMENT \u0026#39;之前配置文件中org.quartz.scheduler.instanceId配置的名字，就会写入该字段\u0026#39;, LAST_CHECKIN_TIME BIGINT(13) NOT NULL COMMENT \u0026#39;上次检查时间\u0026#39;, CHECKIN_INTERVAL BIGINT(13) NOT NULL COMMENT \u0026#39;检查间隔时间\u0026#39;, PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)) ENGINE=InnoDB COMMENT=\u0026#39;存储集群中note实例信息，quartz会定时读取该表的信息判断集群中每个实例的当前状态\u0026#39;; CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL COMMENT \u0026#39;调度名称\u0026#39;, LOCK_NAME VARCHAR(40) NOT NULL COMMENT \u0026#39;悲观锁名称\u0026#39;, PRIMARY KEY (SCHED_NAME,LOCK_NAME)) ENGINE=InnoDB COMMENT=\u0026#39;存储程序的悲观锁的信息(假如使用了悲观锁)\u0026#39;; CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME); CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE); CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME); CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY); CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP); CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP); CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); commit; 2、项目中引入依赖\n\u0026lt;!--springboot依赖--\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!--quartz依赖--\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-quartz\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!--mysql--\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;mysql\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mysql-connector-java\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;8.0.15\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!--druid--\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;druid-spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.6\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 3、application.yml配置文件\nserver: port: 18000 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 url: jdbc:mysql://127.0.0.1:3306/quartz-test?characterEncoding=utf8\u0026amp;useSSL=false\u0026amp;autoReconnect=true\u0026amp;serverTimezone=Asia/Shanghai type: com.alibaba.druid.pool.DruidDataSource #数据库连接池 druid: # 初始化时建立物理连接的个数 initial-size: 10 # 最小连接池数量 min-idle: 5 # 最大连接池数量 max-active: 20 # 获取连接时最大等待时间，单位毫秒 max-wait: 60000 # 申请连接的时候检测，如果空闲时间大于timeBetweenEvictionRunsMillis，执行validationQuery检测连接是否有效。 test-while-idle: true # 既作为检测的间隔时间又作为testWhileIdel执行的依据 time-between-eviction-runs-millis: 60000 #销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时，关闭当前连接 min-evictable-idle-time-millis: 30000 # 使用jdbc的方式持久化定时任务 quartz: job-store-type: jdbc 4、TaskInfo实体类，为了传参方便\npackage top.ygang.quartztest.quartz; import org.quartz.JobDataMap; import java.util.Date; /** * @描述 任务信息 * @创建人 yhgh */ public class TaskInfo { /** * 任务名称 */ private String jobName; /** * 任务组 */ private String jobGroup; /** * 任务描述 */ private String description; /** * Job实现类全类名 */ private String className; /** * 触发器名称 */ private String triggerName; /** * 触发器组 */ private String triggerGroup; /** * cron表达式 */ private String cron; /** * 创建时间 */ private Date createTime; /** * execute的执行参数 */ private JobDataMap jobDataMap; /** * 任务状态 */ private String state; public String getJobName() { return jobName; } public void setJobName(String jobName) { this.jobName = jobName; } public String getJobGroup() { return jobGroup; } public void setJobGroup(String jobGroup) { this.jobGroup = jobGroup; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public String getTriggerName() { return triggerName; } public void setTriggerName(String triggerName) { this.triggerName = triggerName; } public String getTriggerGroup() { return triggerGroup; } public void setTriggerGroup(String triggerGroup) { this.triggerGroup = triggerGroup; } public String getCron() { return cron; } public void setCron(String cron) { this.cron = cron; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public JobDataMap getJobDataMap() { return jobDataMap; } public void setJobDataMap(JobDataMap jobDataMap) { this.jobDataMap = jobDataMap; } public String getState() { return state; } public void setState(String state) { this.state = state; } } 5、业务层，处理各种关于定时任务的业务\npackage top.ygang.quartztest.quartz; import org.quartz.*; import org.quartz.impl.matchers.GroupMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.*; /** * @描述 quartz业务层 * @创建人 yhgh */ @Service public class QuartzService { Logger logger = LoggerFactory.getLogger(QuartzService.class); @Autowired private Scheduler scheduler; /** * 获取所有任务列表 * @return */ public List\u0026lt;TaskInfo\u0026gt; getJobList() { List\u0026lt;TaskInfo\u0026gt; list = new ArrayList\u0026lt;\u0026gt;(); try { List\u0026lt;String\u0026gt; jobGroupNames = scheduler.getJobGroupNames(); for (String groupJob : jobGroupNames) { for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.\u0026lt;JobKey\u0026gt;groupEquals(groupJob))) { List\u0026lt;? extends Trigger\u0026gt; triggers = scheduler.getTriggersOfJob(jobKey); Trigger trigger = triggers.get(0); JobDetail jobDetail = scheduler.getJobDetail(jobKey); TriggerKey triggerKey = trigger.getKey(); String state = scheduler.getTriggerState(trigger.getKey()).name(); String jName = jobKey.getName(); String jGroup = jobKey.getGroup(); String tGroup = triggerKey.getGroup(); String tName = triggerKey.getName(); CronTrigger cronTrigger = (CronTrigger) trigger; String cron = cronTrigger.getCronExpression(); String description = jobDetail.getDescription(); String className = jobDetail.getJobClass().getName(); JobDataMap jobDataMap = jobDetail.getJobDataMap(); Date createTime = (Date) jobDataMap.get(\u0026#34;createTime\u0026#34;); jobDataMap.remove(\u0026#34;createTime\u0026#34;); TaskInfo info = new TaskInfo(); info.setJobName(jName); info.setJobGroup(jGroup); info.setClassName(className); info.setDescription(description); info.setCron(cron); info.setTriggerName(tName); info.setTriggerGroup(tGroup); info.setCreateTime(createTime); info.setJobDataMap(jobDataMap); info.setState(state); list.add(info); } } }catch (Exception e){ logger.error(\u0026#34;获取任务列表失败\u0026#34;,e); return list; } return list; } /** * 新增定时任务 * * @param taskInfo */ public void addJob(TaskInfo taskInfo) { try { // 根据传入的类名加载Job实现类 Class\u0026lt;? extends Job\u0026gt; jobClass = (Class\u0026lt;? extends Job\u0026gt;) Class.forName(taskInfo.getClassName()); // 将创建时间放到参数中保存 taskInfo.getJobDataMap().put(\u0026#34;createTime\u0026#34;,new Date()); // 构建JobDetail JobDetail jobDetail = JobBuilder.newJob(jobClass) .withIdentity(taskInfo.getJobName(), taskInfo.getJobGroup()) .withDescription(taskInfo.getDescription()) .usingJobData(taskInfo.getJobDataMap()) .build(); // 按新的cronExpression表达式构建一个新的trigger CronTrigger trigger = TriggerBuilder.newTrigger() // 此处默认使用任务名称、组名作为触发器名，也可以自定义 .withIdentity(taskInfo.getJobName(), taskInfo.getJobGroup()) .startNow() .withSchedule(CronScheduleBuilder.cronSchedule(taskInfo.getCron())) .build(); // 启动调度器 scheduler.start(); scheduler.scheduleJob(jobDetail, trigger); } catch (Exception e) { logger.error(\u0026#34;创建定时任务失败\u0026#34;,e); } } /** * 暂停定时任务 * @param taskInfo * @throws SchedulerException */ public void pauseJob(TaskInfo taskInfo) throws SchedulerException { scheduler.pauseJob(JobKey.jobKey(taskInfo.getJobName(), taskInfo.getJobGroup())); } /** * 开始定时任务 * @param taskInfo * @throws SchedulerException */ public void resumeJob(TaskInfo taskInfo) throws SchedulerException { scheduler.resumeJob(JobKey.jobKey(taskInfo.getJobName(), taskInfo.getJobGroup())); } /** * 更新任务 * 注意：只能用来更新cron * @param taskInfo * @throws SchedulerException */ public void rescheduleJob(TaskInfo taskInfo) throws SchedulerException { String jobName = taskInfo.getJobName(); String jobGroup = taskInfo.getJobGroup(); String description = taskInfo.getDescription(); String cron = taskInfo.getCron(); JobDataMap jobDataMap = taskInfo.getJobDataMap(); TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup); JobKey jobKey = new JobKey(jobName, jobGroup); Trigger trigger = scheduler.getTrigger(triggerKey); // 如果修改cron if (StringUtils.hasText(cron)){ CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder .cronSchedule(cron) .withMisfireHandlingInstructionDoNothing(); trigger = TriggerBuilder.newTrigger() .withIdentity(triggerKey) .withSchedule(cronScheduleBuilder).build(); } JobDetail jobDetail = scheduler.getJobDetail(jobKey); JobBuilder jobBuilder = jobDetail.getJobBuilder(); // 如果修改描述 if (StringUtils.hasText(description)){ jobBuilder = jobBuilder.withDescription(description); } // 如果修改参数 if (jobDataMap != null \u0026amp;\u0026amp; jobDataMap.size() \u0026gt; 0){ Date createTime = (Date) jobDetail.getJobDataMap().get(\u0026#34;createTime\u0026#34;); jobDataMap.put(\u0026#34;createTime\u0026#34;,createTime); jobBuilder = jobBuilder.usingJobData(jobDataMap); } jobDetail = jobBuilder.build(); Set\u0026lt;Trigger\u0026gt; triggerSet = new HashSet\u0026lt;\u0026gt;(); triggerSet.add(trigger); scheduler.scheduleJob(jobDetail, triggerSet,true); } /** * 删除定时任务 * @param taskInfo * @throws SchedulerException */ public void deleteJob(TaskInfo taskInfo) throws SchedulerException { scheduler.pauseTrigger(TriggerKey.triggerKey(taskInfo.getJobName(), taskInfo.getJobGroup())); scheduler.unscheduleJob(TriggerKey.triggerKey(taskInfo.getJobName(), taskInfo.getJobGroup())); scheduler.deleteJob(JobKey.jobKey(taskInfo.getJobName(), taskInfo.getJobGroup())); } } 6、控制层\npackage top.ygang.quartztest.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import top.ygang.quartztest.quartz.QuartzService; import top.ygang.quartztest.quartz.TaskInfo; import java.util.List; /** * @描述 * @创建人 yhgh */ @RestController public class QuartzController { @Autowired private QuartzService quartzService; /** * 新增定时任务 * @param taskInfo * @return */ @PostMapping(\u0026#34;/addJob\u0026#34;) public String addJob(@RequestBody TaskInfo taskInfo){ try{ quartzService.addJob(taskInfo); return \u0026#34;success\u0026#34;; }catch (Exception e){ e.printStackTrace(); return \u0026#34;failed\u0026#34;; } } /** * 删除定时任务 * @param taskInfo * @return */ @PostMapping(\u0026#34;/deleteJob\u0026#34;) public String deleteJob(@RequestBody TaskInfo taskInfo){ try{ quartzService.deleteJob(taskInfo); return \u0026#34;success\u0026#34;; }catch (Exception e){ e.printStackTrace(); return \u0026#34;failed\u0026#34;; } } /** * 更新定时任务 * @param taskInfo * @return */ @PostMapping(\u0026#34;/rescheduleJob\u0026#34;) public String rescheduleJob(@RequestBody TaskInfo taskInfo){ try{ quartzService.rescheduleJob(taskInfo); return \u0026#34;success\u0026#34;; }catch (Exception e){ e.printStackTrace(); return \u0026#34;failed\u0026#34;; } } /** * 暂停定时任务 * @param taskInfo * @return */ @PostMapping(\u0026#34;/pauseJob\u0026#34;) public String pauseJob(@RequestBody TaskInfo taskInfo){ try{ quartzService.pauseJob(taskInfo); return \u0026#34;success\u0026#34;; }catch (Exception e){ e.printStackTrace(); return \u0026#34;failed\u0026#34;; } } /** * 开始定时任务 * @param taskInfo * @return */ @PostMapping(\u0026#34;/resumeJob\u0026#34;) public String resumeJob(@RequestBody TaskInfo taskInfo){ try{ quartzService.resumeJob(taskInfo); return \u0026#34;success\u0026#34;; }catch (Exception e){ e.printStackTrace(); return \u0026#34;failed\u0026#34;; } } /** * 查询定时任务列表 * @return */ @PostMapping(\u0026#34;/getJobList\u0026#34;) public List\u0026lt;TaskInfo\u0026gt; getJobList(){ try{ List\u0026lt;TaskInfo\u0026gt; jobList = quartzService.getJobList(); return jobList; }catch (Exception e){ e.printStackTrace(); return null; } } } 7、定义一个定时任务\npackage top.ygang.quartztest.quartz.task; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** * @描述 * @创建人 yhgh */ public class TestTask implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap(); String msg = (String) jobDataMap.get(\u0026#34;msg\u0026#34;); System.out.println(\u0026#34;执行定时任务 : \u0026#34; + msg); } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/0f3856f3/492f00fa/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJava实现定时器的方式 \n    \u003cdiv id=\"java实现定时器的方式\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#java%e5%ae%9e%e7%8e%b0%e5%ae%9a%e6%97%b6%e5%99%a8%e7%9a%84%e6%96%b9%e5%bc%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e1、\u003cstrong\u003eTimer\u003c/strong\u003e：这是Java自带的\u003ccode\u003ejava.util.Timer\u003c/code\u003e类，这个类允许你调度一个\u003ccode\u003ejava.util.TimerTask\u003c/code\u003e任务。使用这种方式可以让你的程序按照某一个频度执行，但不能在指定时间运行，一般使用比较少\u003c/p\u003e","title":"2、定时器","type":"posts"},{"content":" 网络应用体系结构 # 客户机/服务器结构（Client-Server，C/S） 点对点结构（Peer-to-peer，P2P） 混合结构（Hybird） C/S # 服务器 7*24小时提供服务 永久性访问地址/域名 利用大量服务器实现可扩展性 客户机 与服务器通信，使用服务器提供的服务 间歇性接入网络 可能使用动态IP地址 不会和其他客户机直接通信 P2P # 没有永远在线的服务器 任意端系统/节点之间可以直接通信 节点间歇性接入网络 节点可能改变IP地址 网络应用进程通信 # 进程：主机上运行的程序\n同一主机进程通信：1、进程间通信机制2、操作系统提供\n不同主机进程通信：消息交换（报文交换）\nSocket套接字 # 进程间通信利用socket发送/接收消息实现，网络应用的API由操作系统提供\n寻址进程 # 不同主机上进程进行通信，每个进程必须由标识符\n需要先使用IP地址寻址主机，但是一个主机上面会有多个进程，那么就需要通过端口号（Port Numer）来为每个需要通信的进程做唯一表示\n特殊端口号： HTTP Server：80 Mail Server：25 ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/c343b13e/45c36453/d9e258ac/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e网络应用体系结构 \n    \u003cdiv id=\"网络应用体系结构\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%bd%91%e7%bb%9c%e5%ba%94%e7%94%a8%e4%bd%93%e7%b3%bb%e7%bb%93%e6%9e%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e客户机/服务器结构（Client-Server，C/S）\u003c/li\u003e\n\u003cli\u003e点对点结构（Peer-to-peer，P2P）\u003c/li\u003e\n\u003cli\u003e混合结构（Hybird）\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003eC/S \n    \u003cdiv id=\"cs\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#cs\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e服务器\n\u003cul\u003e\n\u003cli\u003e7*24小时提供服务\u003c/li\u003e\n\u003cli\u003e永久性访问地址/域名\u003c/li\u003e\n\u003cli\u003e利用大量服务器实现可扩展性\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e客户机\n\u003cul\u003e\n\u003cli\u003e与服务器通信，使用服务器提供的服务\u003c/li\u003e\n\u003cli\u003e间歇性接入网络\u003c/li\u003e\n\u003cli\u003e可能使用动态IP地址\u003c/li\u003e\n\u003cli\u003e不会和其他客户机直接通信\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003eP2P \n    \u003cdiv id=\"p2p\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#p2p\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e没有永远在线的服务器\u003c/li\u003e\n\u003cli\u003e任意端系统/节点之间可以直接通信\u003c/li\u003e\n\u003cli\u003e节点间歇性接入网络\u003c/li\u003e\n\u003cli\u003e节点可能改变IP地址\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e网络应用进程通信 \n    \u003cdiv id=\"网络应用进程通信\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%bd%91%e7%bb%9c%e5%ba%94%e7%94%a8%e8%bf%9b%e7%a8%8b%e9%80%9a%e4%bf%a1\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e进程：主机上运行的程序\u003c/p\u003e","title":"2、应用层","type":"posts"},{"content":"类加载器子系统负责从文件系统或者网络中加载Class文件，class文件在开头有特定的文件标识\n类加载器子系统只负责class文件的加载，至于是否可以运行，由Execution Engine执行引擎决定\n加载的类的信息存放于一块称为方法区的内存空间中。除了类的信息外，方法区还会存放运行时常量池信息，可能还包含字符串字面量和数字常量\n类加载的过程 # 加载 # 1、通过一个类的全限定名（全类名）获取此类的二进制字节流\n2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构\n3、在内存中生成一个代表这个类的java.lang.Class对象，作为方法区中这个类的各种数据访问入口\n加载.class文件的方式 # 通过本地系统直接加载 通过网络获取，典型场景：Web、Applet 从zip压缩包中读取，称为日后jar、war格式的基础 运行时计算生成，典型场景：动态代理技术 由其他文件生成，典型场景：JSP 由专有数据库中提取 从加密文件中获取，典型场景：防class文件被反编译 链接 # 验证 # 目的在于确保class文件的字节流中包含信息符合当前虚拟机要求，保证被加载类的正确性，不会危害虚拟机自身安全\n主要包括四种验证：文件格式验证、元数据验证、字节码验证、符号引用验证\n准备 # ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/ebb2f342/81e7e5df/3af88ada/","section":"文章","summary":"\u003cp\u003e类加载器子系统负责从文件系统或者网络中加载Class文件，class文件在开头有特定的文件标识\u003c/p\u003e","title":"2、类加载器子系统","type":"posts"},{"content":" 原理 # 装饰者模式（DecoratorPattern）是指在不改变原对象的基础之上，将功能附加到对象上，提供了比继承更灵活性的替代方案（扩展原有对象的功能），装饰者模式属于结构型模式\n应用场景 # 装饰者在代码程序中适用于以下场景： 1、用于扩展一个类的功能或给一个类添加附加职责。 2、动态的给一个对象添加功能，这些功能可以再动态的撤销。\n装饰者模式优缺点 # 优点 装饰者比继承灵活，可以在不改变原有对象的情况下动态地给一个对象扩展功能，即插即用非常方便 通过不同的装饰器组合，可是实现不同效果 装饰者完全遵守开闭原则。 缺点： 会增加程序复杂性，会增加更多的类 动态装饰时，操作多层装饰复杂 ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/6e08a29a/5d67f48c/65cc0218/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e原理 \n    \u003cdiv id=\"原理\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8e%9f%e7%90%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e装饰者模式（DecoratorPattern）是指在不改变原对象的基础之上，将功能附加到对象上，提供了比继承更灵活性的替代方案（扩展原有对象的功能），装饰者模式属于结构型模式\u003c/p\u003e","title":"2、装饰者模式","type":"posts"},{"content":" 认证 # 1、Controller登录方法 # @RequestMapping(\u0026#34;/login.do\u0026#34;) public String login(User user,ModelMap map){ //获得一个主体对象 Subject subject = SecurityUtils.getSubject(); //将请求得到的用户名和密码放入一个令牌中 UsernamePasswordToken token = new UsernamePasswordToken(user.getUaccount(),user.getUpsw()); //调用主体对象的认证方法login，对令牌进行认证 try{ //触发Realm中doGetAuthenticationInfo() subject.login(token); Session session = subject.getSession(); User nwoLogin = (User)subject.getPrincipal(); session.setAttribute(\u0026#34;nowLogin\u0026#34;, nwoLogin); }catch(UnknownAccountException accountException){ map.put(\u0026#34;ex\u0026#34;, \u0026#34;账号不存在\u0026#34;); return \u0026#34;tologin.do\u0026#34;; }catch(IncorrectCredentialsException passwordExeption){ map.put(\u0026#34;ex\u0026#34;, \u0026#34;密码校验错误\u0026#34;); return \u0026#34;tologin.do\u0026#34;; } return \u0026#34;redirect:toMain.do\u0026#34;; } 2、Realm类中，doGetAuthenticationInfo方法进行登录认证 # //身份认证方法，需要在用户登录系统时触发 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException { //通过方法形参arg0，获取封装了用户账号密码的令牌 UsernamePasswordToken token = (UsernamePasswordToken)arg0; //验证账号是否存在 String uaccount = token.getUsername(); User user = userService.selectByUaccount(uaccount); if(user == null){ //如果用户名不存在，返回null //则shiro底层返回了一个异常(UnknownAccountException) return null; } //验证密码是否正确 //第一个参数为，数据库查出的user对象，第二个参数为正确的密码，第三个参数为当前realm名 //如果密码不正确，该构造器会抛出异常(IncorrectCredentialsException) return new SimpleAuthenticationInfo(user,user.getUpsw(),getName()); } 加密 # 1、ShiroConfig类中，设置加密 # //设置加密类型(身份匹配器)，以及方式 @Bean public HashedCredentialsMatcher credentialsMatcher() { HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); // 说密码的加密方式为MD5 (不可逆的加密方式，只能加密，不能解密) credentialsMatcher.setHashAlgorithmName(\u0026#34;MD5\u0026#34;); // 针对MD5加密的信息，再加密1024次 credentialsMatcher.setHashIterations(1024); return credentialsMatcher; } //配置shiro框架中，用来完成身份认证，授权的域对象 @Bean public LoginAndAuthRealm loginAndAuthRealm() { LoginAndAuthRealm realm = new LoginAndAuthRealm(); // 给Realm中配置身份对比规则 realm.setCredentialsMatcher(credentialsMatcher()); return realm; } 2、修改Realm组件身份认证方法 # //身份认证方法，需要在用户登录系统时触发 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException { //通过方法形参arg0，获取封装了用户账号密码的令牌 UsernamePasswordToken token = (UsernamePasswordToken)arg0; //验证账号是否存在 String uaccount = token.getUsername(); User user = userService.selectByUaccount(uaccount); if(user == null){ //如果用户名不存在，返回null //则shiro底层返回了一个异常(UnknownAccountException) return null; } //验证密码是否正确 //获取当前用户名，创建盐值对象 ByteSource byteSource = ByteSource.Util.bytes(user.getUaccount()); //第一个参数为，数据库查出的user对象，第二个参数为正确的密码，第三个参数为盐值对象，第四个参数为当前Realm名称 //如果密码不正确，该构造器会抛出异常(IncorrectCredentialsException) return new SimpleAuthenticationInfo(user,user.getUpsw(),byteSource,getName()); } 3、控制层注册方法中，对密码进行MD5加密 # String pwd = new SimpleHash(\u0026quot;MD5\u0026quot;, 密码,盐值‘一般为用户名’, 加密次数‘1024’).toString();\npublic String regist(User user){ User nowLogin = userMapper.slecteByUaccount(user.getUaccount()); if(nowLogin != null){ return \u0026#34;用户已存在！！！\u0026#34;; }else{ String upsw = new SimpleHash(\u0026#34;MD5\u0026#34;,user.getUpsw(), user.getUaccount(), 1024).toString(); user.setUpsw(upsw); int i = userMapper.insert(user); UserRole userRole = new UserRole(); userRole.setUid(user.getUid()); userRole.setRid(4); int j = userRoleMapper.insert(userRole); if(i \u0026gt; 0 \u0026amp;\u0026amp; j \u0026gt; 0){ return \u0026#34;注册成功！！！\u0026#34;; }else{ return \u0026#34;注册失败！！！\u0026#34;; } } } 授权 # ShiroFilter工作原理 # Shiro中过滤器的类型及配置 # 1、重写底层Roles规则，用于多个角色可以授权一个路径的访问（或的关系） # public class RolesFilter extends RolesAuthorizationFilter{ @Override public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { final Subject subject = getSubject(request, response); final String[] rolesArray = (String[]) mappedValue; if (rolesArray == null || rolesArray.length == 0) { return true; } for (String roleName : rolesArray) { if (subject.hasRole(roleName)) { return true; } } return false; } } 2、ShiroConfig类中，设置过滤器链以及过滤器 # @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //注入自定义的Roles权限规则 Map\u0026lt;String,Filter\u0026gt; rolesMap = new HashMap\u0026lt;\u0026gt;(); rolesMap.put(\u0026#34;roles\u0026#34;, new RolesFilter()); shiroFilterFactoryBean.setFilters(rolesMap); //注入安全管理器 shiroFilterFactoryBean.setSecurityManager(getDefaultWebSecurityManager()); //添加登陆页面、登陆成功、未授权路径 shiroFilterFactoryBean.setLoginUrl(\u0026#34;/toLogin.do\u0026#34;); shiroFilterFactoryBean.setSuccessUrl(\u0026#34;/toMain.do\u0026#34;); shiroFilterFactoryBean.setUnauthorizedUrl(\u0026#34;/unauthorized.do\u0026#34;); //添加对各种页面的限制 Map\u0026lt;String,String\u0026gt; map = new LinkedHashMap\u0026lt;\u0026gt;(); //登录页面、登录方法、注册方法，不需要限制（匿名） map.put(\u0026#34;/toLogin.do\u0026#34;, \u0026#34;anon\u0026#34;); map.put(\u0026#34;/login.do\u0026#34;, \u0026#34;anon\u0026#34;); map.put(\u0026#34;/regist.do\u0026#34;,\u0026#34;anon\u0026#34;); //查看用户信息，超级管理员或管理员才可以访问 map.put(\u0026#34;/list.do\u0026#34;, \u0026#34;roles[超级管理员,管理员]\u0026#34;); //其他页面，需要认证后才可以进行访问 map.put(\u0026#34;/*\u0026#34;, \u0026#34;authc\u0026#34;); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } 3、Realm授权方法中，对当前登录角色进行授权 # @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) { //授权信息对象 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //获取当前登录用户 User user = (User)SecurityUtils.getSubject().getPrincipal(); //获取当前登录用户的角色信息 List\u0026lt;UserRole\u0026gt; userRoles = user.getUserRoles(); List\u0026lt;String\u0026gt; roles = new ArrayList\u0026lt;\u0026gt;(); for(UserRole ur : userRoles){ roles.add(ur.getRole().getRname()); } //将当前登录用户的角色名称（String）存入授权信息对象中 info.addRoles(roles); return info; } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/c3527798/87c6c501/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e认证 \n    \u003cdiv id=\"认证\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%ae%a4%e8%af%81\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e1、Controller登录方法 \n    \u003cdiv id=\"1controller登录方法\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1controller%e7%99%bb%e5%bd%95%e6%96%b9%e6%b3%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@RequestMapping\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;/login.do\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003elogin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eUser\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eModelMap\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//获得一个主体对象\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eSubject\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esubject\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eSecurityUtils\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetSubject\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//将请求得到的用户名和密码放入一个令牌中\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eUsernamePasswordToken\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etoken\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eUsernamePasswordToken\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetUaccount\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetUpsw\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//调用主体对象的认证方法login，对令牌进行认证\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//触发Realm中doGetAuthenticationInfo()\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003esubject\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elogin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etoken\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSession\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esession\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esubject\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetSession\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eUser\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enwoLogin\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"n\"\u003esubject\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetPrincipal\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003esession\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetAttribute\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;nowLogin\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enwoLogin\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"k\"\u003ecatch\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eUnknownAccountException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eaccountException\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eput\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;ex\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;账号不存在\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;tologin.do\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"k\"\u003ecatch\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eIncorrectCredentialsException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epasswordExeption\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eput\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;ex\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;密码校验错误\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;tologin.do\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;redirect:toMain.do\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e2、Realm类中，doGetAuthenticationInfo方法进行登录认证 \n    \u003cdiv id=\"2realm类中dogetauthenticationinfo方法进行登录认证\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#2realm%e7%b1%bb%e4%b8%addogetauthenticationinfo%e6%96%b9%e6%b3%95%e8%bf%9b%e8%a1%8c%e7%99%bb%e5%bd%95%e8%ae%a4%e8%af%81\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//身份认证方法，需要在用户登录系统时触发\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nd\"\u003e@Override\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003eprotected\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eAuthenticationInfo\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003edoGetAuthenticationInfo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eAuthenticationToken\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003earg0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003ethrows\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eAuthenticationException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//通过方法形参arg0，获取封装了用户账号密码的令牌\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eUsernamePasswordToken\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etoken\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eUsernamePasswordToken\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"n\"\u003earg0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//验证账号是否存在\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003euaccount\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etoken\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetUsername\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eUser\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003euserService\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eselectByUaccount\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euaccount\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//如果用户名不存在，返回null\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//则shiro底层返回了一个异常(UnknownAccountException)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//验证密码是否正确\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//第一个参数为，数据库查出的user对象，第二个参数为正确的密码，第三个参数为当前realm名\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//如果密码不正确，该构造器会抛出异常(IncorrectCredentialsException)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eSimpleAuthenticationInfo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetUpsw\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e\u003cspan class=\"n\"\u003egetName\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e加密 \n    \u003cdiv id=\"加密\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8a%a0%e5%af%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e1、ShiroConfig类中，设置加密 \n    \u003cdiv id=\"1shiroconfig类中设置加密\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1shiroconfig%e7%b1%bb%e4%b8%ad%e8%ae%be%e7%bd%ae%e5%8a%a0%e5%af%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//设置加密类型(身份匹配器)，以及方式\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nd\"\u003e@Bean\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eHashedCredentialsMatcher\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003ecredentialsMatcher\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eHashedCredentialsMatcher\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecredentialsMatcher\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eHashedCredentialsMatcher\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 说密码的加密方式为MD5 (不可逆的加密方式，只能加密，不能解密)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003ecredentialsMatcher\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetHashAlgorithmName\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;MD5\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 针对MD5加密的信息，再加密1024次\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003ecredentialsMatcher\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetHashIterations\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e1024\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecredentialsMatcher\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//配置shiro框架中，用来完成身份认证，授权的域对象\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nd\"\u003e@Bean\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eLoginAndAuthRealm\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eloginAndAuthRealm\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eLoginAndAuthRealm\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erealm\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eLoginAndAuthRealm\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 给Realm中配置身份对比规则\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003erealm\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetCredentialsMatcher\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecredentialsMatcher\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erealm\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e2、修改Realm组件身份认证方法 \n    \u003cdiv id=\"2修改realm组件身份认证方法\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#2%e4%bf%ae%e6%94%b9realm%e7%bb%84%e4%bb%b6%e8%ba%ab%e4%bb%bd%e8%ae%a4%e8%af%81%e6%96%b9%e6%b3%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//身份认证方法，需要在用户登录系统时触发\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nd\"\u003e@Override\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003eprotected\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eAuthenticationInfo\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003edoGetAuthenticationInfo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eAuthenticationToken\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003earg0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003ethrows\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eAuthenticationException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//通过方法形参arg0，获取封装了用户账号密码的令牌\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eUsernamePasswordToken\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etoken\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eUsernamePasswordToken\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"n\"\u003earg0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//验证账号是否存在\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003euaccount\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etoken\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetUsername\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eUser\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003euserService\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eselectByUaccount\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euaccount\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//如果用户名不存在，返回null\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//则shiro底层返回了一个异常(UnknownAccountException)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//验证密码是否正确\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取当前用户名，创建盐值对象\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eByteSource\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebyteSource\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eByteSource\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eUtil\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ebytes\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetUaccount\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//第一个参数为，数据库查出的user对象，第二个参数为正确的密码，第三个参数为盐值对象，第四个参数为当前Realm名称\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//如果密码不正确，该构造器会抛出异常(IncorrectCredentialsException)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eSimpleAuthenticationInfo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetUpsw\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e\u003cspan class=\"n\"\u003ebyteSource\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003egetName\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e3、控制层注册方法中，对密码进行MD5加密 \n    \u003cdiv id=\"3控制层注册方法中对密码进行md5加密\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#3%e6%8e%a7%e5%88%b6%e5%b1%82%e6%b3%a8%e5%86%8c%e6%96%b9%e6%b3%95%e4%b8%ad%e5%af%b9%e5%af%86%e7%a0%81%e8%bf%9b%e8%a1%8cmd5%e5%8a%a0%e5%af%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003eString pwd = new SimpleHash(\u0026quot;MD5\u0026quot;, 密码,盐值‘一般为用户名’, 加密次数‘1024’).toString();\u003c/code\u003e\u003c/p\u003e","title":"2、认证，加密，授权，缓存","type":"posts"},{"content":"STL中最常用的容器为Vector，可以理解为数组，下面我们在这个容器中插入数据、并遍历这个容器\n容器：Vector\n算法：for_each\n迭代器：vector\u0026lt;int\u0026gt;::iterator\n迭代方式1 # #include\u0026lt;iostream\u0026gt; #include\u0026lt;vector\u0026gt; //STL中的容器都有自己的头文件，如果要使用，需要包含 using namespace std; int main(){ //创建一个vector容器 vector\u0026lt;int\u0026gt; v; //push_back(),尾插数据 v.push_back(10); v.push_back(20); v.push_back(30); //第一种遍历方式 //通过迭代器访问vector中的元素 vector\u0026lt;int\u0026gt;::iterator vcBegin = v.begin(); //起始迭代器，指向容器第一个元素 vector\u0026lt;int\u0026gt;::iterator vcEnd = v.end(); //结束迭代器，指向容器最后一个元素下一个位置 //如果起始迭代器，没有和结束迭代器指向同一个位置，那么就遍历、打印 //正向遍历 while (vcBegin != vcEnd){ cout \u0026lt;\u0026lt; *vcBegin \u0026lt;\u0026lt; endl; vcBegin++; } //反向遍历 //需要让起始迭代器回到初始位置 vcBegin = v.begin(); while (vcEnd != vcBegin){ cout \u0026lt;\u0026lt; *(vcEnd - 1) \u0026lt;\u0026lt; endl; vcEnd--; } system(\u0026#34;pause\u0026#34;); return 0; } 迭代方式2（推荐） # #include\u0026lt;iostream\u0026gt; #include\u0026lt;vector\u0026gt; //STL中的容器都有自己的头文件，如果要使用，需要包含 using namespace std; int main(){ //创建一个vector容器 vector\u0026lt;int\u0026gt; v; //push_back(),尾插数据 v.push_back(10); v.push_back(20); v.push_back(30); //第二种遍历方式、和上面的原理一样 for(vector\u0026lt;int\u0026gt;::iterator b = v.begin();b != v.end();b++){ cout \u0026lt;\u0026lt; *b \u0026lt;\u0026lt; endl; } system(\u0026#34;pause\u0026#34;); return 0; } 迭代方式3 # #include\u0026lt;iostream\u0026gt; #include\u0026lt;vector\u0026gt; //STL中的容器都有自己的头文件，如果要使用，需要包含 #include\u0026lt;algorithm\u0026gt; //算法头文件 using namespace std; //用于for_each()中的回调函数 void func(int val){ cout \u0026lt;\u0026lt; val \u0026lt;\u0026lt; endl; } int main(){ //创建一个vector容器 vector\u0026lt;int\u0026gt; v; //push_back(),尾插数据 v.push_back(10); v.push_back(20); v.push_back(30); //第三种遍历方式，使用遍历算法for_each，必须包含算法头文件 //for_each(起始迭代器，结束迭代器，回调函数) for_each(v.begin(),v.end(),func); system(\u0026#34;pause\u0026#34;); return 0; } 存放自定义数据类型并迭代 # #include\u0026lt;iostream\u0026gt; #include\u0026lt;vector\u0026gt; #include\u0026lt;string\u0026gt; #include\u0026lt;algorithm\u0026gt; using namespace std; class Person{ public: string name; int age; Person(string name,int age){ this-\u0026gt;name = name; this-\u0026gt;age = age; } }; void showPerson(Person p){ cout \u0026lt;\u0026lt; \u0026#34;姓名：\u0026#34; \u0026lt;\u0026lt; p.name \u0026lt;\u0026lt; \u0026#34;年龄：\u0026#34; \u0026lt;\u0026lt; p.age \u0026lt;\u0026lt; endl; } int main(){ vector\u0026lt;Person\u0026gt; persons; persons.push_back(Person(\u0026#34;lucy\u0026#34;,12)); persons.push_back(Person(\u0026#34;tom\u0026#34;,18)); vector\u0026lt;int\u0026gt; v; for_each(persons.begin(),persons.end(),showPerson); system(\u0026#34;pause\u0026#34;); return 0; } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/d8245fe1/bf736d53/7973938a/","section":"文章","summary":"\u003cp\u003eSTL中最常用的容器为Vector，可以理解为数组，下面我们在这个容器中插入数据、并遍历这个容器\u003c/p\u003e","title":"2、迭代器入门","type":"posts"},{"content":" 中间件简介 # 在Web应用开发中，中间件（Middleware）是位于应用程序与服务器之间的软件组件，能够拦截HTTP请求和响应，并执行特定的逻辑处理。\n可以将中间件想象成一条管道上的过滤器，每个HTTP请求都必须通过这些过滤器才能到达目标处理函数，同样，响应也会通过这些过滤器返回给客户端。\n中间件的主要作用 # 功能类型 说明 实例 预处理请求 在请求到达业务逻辑前进行验证、转换或过滤 身份验证、请求日志记录、输入验证 后处理响应 在响应返回客户端前进行修改或增强 响应压缩、添加安全头、格式转换 横切关注点 处理与业务逻辑无关但必要的功能 性能监控、错误处理、跨域资源共享 在Gin框架中，中间件是一个接收上下文对象*gin.Context的函数，其基本结构为：\nfunc MyMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 中间件逻辑 } } 中间件的工作原理 # Gin的中间件工作原理基于责任链模式（Chain of Responsibility Pattern）。这种设计模式允许多个处理器依次处理同一个请求，每个处理器专注于自己的职责领域。\n在Gin中，每个中间件可以执行以下操作：\n前置操作：在请求处理前执行某些逻辑 流程控制：通过调用c.Next()将控制权传递给下一个中间件 后置操作：在下游中间件执行完毕后继续执行操作 中断处理：通过调用c.Abort()终止后续中间件的执行 注意：如果中间件调用了c.Abort()，则后续的中间件不会执行，但当前中间件的剩余代码仍会继续执行。\nGin中间件的类型 # 按照特殊性原则组织中间件，将通用功能（如日志、错误处理）设为全局中间件，将特定功能（如认证、权限）设为路由组或单个路由中间件。\n全局中间件 # 应用于所有路由，在任何路由处理之前执行。\n// 全局中间件：应用于所有路由 router.Use(Logger()) 路由组中间件 # 只应用于特定路由组内的路由。\n// 路由组中间件：只应用于authorized组 authorized := router.Group(\u0026#34;/\u0026#34;) authorized.Use(AuthRequired()) 单个路由中间件 # // 单个路由中间件：只应用于此路由 router.GET(\u0026#34;/benchmark\u0026#34;, MyBenchLogger(), benchEndpoint) Gin内置中间件 # Gin框架提供了多种内置中间件，用于解决常见的Web开发需求。这些中间件经过了性能优化，可以直接在项目中使用。\nLogger # Logger中间件用于记录HTTP请求的详细信息，如请求方法、路径、状态码、响应时间等。\n// 创建一个默认的路由器（包含Logger和Recovery中间件） r := gin.Default() // 或者手动添加Logger中间件 r := gin.New() r.Use(gin.Logger()) Logger中间件输出的日志格式例如：\n[GIN] 2023/07/15 - 11:48:16 | 200 | 1.234567ms | ::1 | GET \u0026#34;/ping\u0026#34; 日志包含以下信息：时间戳、状态码、延迟时间、客户端IP、请求方法、请求路径\n在生产环境中，你可能需要自定义日志格式或将日志写入文件，这时可以编写自定义的日志中间件。\nRecovery # Recovery中间件用于捕获处理过程中的panic异常，防止服务器崩溃，并返回500状态码。\nr := gin.New() r.Use(gin.Recovery()) 工作原理：\n使用defer和recover()捕获可能发生的panic 记录错误堆栈信息 返回500错误响应 允许服务继续运行，不会因单个请求处理异常而崩溃 在所有生产环境的Gin应用中，Recovery中间件都是必不可少的，它能确保服务的稳定性和可靠性。\n该中间件也支持自定义：\npackage middlewares import ( \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;net/http\u0026#34; ) func Recovery() gin.HandlerFunc { return gin.CustomRecovery(func(c *gin.Context, err any) { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ \u0026#34;code\u0026#34;: \u0026#34;500\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;服务器内部错误！\u0026#34;, }) }) } BasicAuth # BasicAuth中间件提供了HTTP基本认证功能，用于保护敏感路由。\n// 设置授权用户 authorized := r.Group(\u0026#34;/admin\u0026#34;, gin.BasicAuth(gin.Accounts{ \u0026#34;admin\u0026#34;: \u0026#34;password123\u0026#34;, \u0026#34;user\u0026#34;: \u0026#34;secret\u0026#34;, })) // 受保护的路由 authorized.GET(\u0026#34;/secrets\u0026#34;, func(c *gin.Context) { // 获取用户名 user := c.MustGet(gin.AuthUserKey).(string) c.JSON(200, gin.H{ \u0026#34;user\u0026#34;: user, \u0026#34;message\u0026#34;: \u0026#34;You have access to the secrets\u0026#34;, }) }) BasicAuth工作流程：\n检查请求头中的Authorization字段 如果不存在或格式不正确，返回401 Unauthorized响应，并带有WWW-Authenticate头 如果存在且有效，设置用户信息到上下文并继续处理 在处理函数中可以通过c.MustGet(gin.AuthUserKey)获取用户名 BasicAuth的凭证在传输过程中只是Base64编码，未加密，因此应始终与HTTPS一起使用。\nCORS # 可以使用第三方包github.com/gin-contrib/cors：\ngo get -u github.com/gin-contrib/cors r.Use(cors.New(cors.Config{ AllowOrigins: []string{\u0026#34;*\u0026#34;}, AllowMethods: []string{\u0026#34;GET\u0026#34;, \u0026#34;POST\u0026#34;, \u0026#34;PUT\u0026#34;, \u0026#34;PATCH\u0026#34;, \u0026#34;DELETE\u0026#34;, \u0026#34;OPTIONS\u0026#34;}, AllowHeaders: []string{\u0026#34;Origin\u0026#34;, \u0026#34;Content-Type\u0026#34;, \u0026#34;Accept\u0026#34;, \u0026#34;Authorization\u0026#34;}, ExposeHeaders: []string{\u0026#34;Content-Length\u0026#34;}, AllowCredentials: true, AllowOriginFunc: func(origin string) bool { return origin == \u0026#34;https://github.com\u0026#34; }, MaxAge: 12 * time.Hour, })) CORS配置选项说明：\nAllowOrigins：允许的源域名 AllowMethods：允许的HTTP方法 AllowHeaders：允许的请求头 ExposeHeaders：向客户端暴露的响应头 AllowCredentials：是否允许携带凭证 AllowOriginFunc：动态判断源是否允许 MaxAge：预检请求结果的缓存时间 自定义中间件开发 # 中间件的基本结构 # Gin中间件本质上是一个返回gin.HandlerFunc的函数，其基本结构如下：\nfunc MiddlewareName() gin.HandlerFunc { // 初始化中间件所需的资源或配置 // 返回实际的中间件处理函数 return func(c *gin.Context) { // 前置处理逻辑：在请求到达路由处理函数前执行 // 调用下一个中间件 c.Next() // 后置处理逻辑：在所有中间件和处理函数执行完后执行 } } 这种结构有两个主要优势：\n闭包特性：可以在外层函数中接收配置参数，初始化资源 两阶段处理：可以轻松实现请求前和响应后的不同逻辑 中间件执行流程控制 # 在中间件内部，有两个关键函数控制执行流程：\nc.Next()：暂停当前中间件的执行，执行后续中间件，然后再回来执行当前中间件的剩余代码 c.Abort()：阻止调用链上的后续中间件执行，但不会中断当前中间件的后续代码 注意：调用c.Abort()后，当前中间件的后续代码仍会执行，如果需要完全退出中间件，还需要使用return语句。\n执行流程控制示例：\nfunc MyMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 前置逻辑 fmt.Println(\u0026#34;⬇️ 进入中间件\u0026#34;) // 条件性终止 if unauthorized(c) { // 设置错误响应 c.AbortWithStatusJSON(403, gin.H{\u0026#34;error\u0026#34;: \u0026#34;Forbidden\u0026#34;}) // 注意：即使Abort，下面的代码仍会执行 fmt.Println(\u0026#34;❌ 请求被拒绝\u0026#34;) return // 使用return阻止执行后续代码 } // 继续调用链 c.Next() // 后置逻辑，只有未被Abort时才会执行 fmt.Println(\u0026#34;⬆️ 离开中间件\u0026#34;) } } 中间件之间的数据传递 # 中间件可以通过Gin上下文在整个请求生命周期内传递数据：\n// 第一个中间件设置数据 func Middleware1() gin.HandlerFunc { return func(c *gin.Context) { // 使用Set存储数据 c.Set(\u0026#34;requestID\u0026#34;, uuid.New().String()) c.Set(\u0026#34;startTime\u0026#34;, time.Now()) c.Next() // 可以在这里访问和修改数据 } } // 第二个中间件或处理器读取数据 func Middleware2() gin.HandlerFunc { return func(c *gin.Context) { // 使用Get获取数据 requestID, exists := c.Get(\u0026#34;requestID\u0026#34;) if exists { fmt.Printf(\u0026#34;处理请求: %v\\n\u0026#34;, requestID) } // 使用MustGet获取数据(如果不存在会panic) startTime := c.MustGet(\u0026#34;startTime\u0026#34;).(time.Time) c.Next() // 计算处理时间 duration := time.Since(startTime) fmt.Printf(\u0026#34;请求处理时间: %v\\n\u0026#34;, duration) } } c.Get()返回的是interface{}类型，需要进行类型断言才能安全使用。如果确定键一定存在，可以使用c.MustGet()，但要注意它会在键不存在时引发panic。\n常见的自定义中间件 # 日志中间件 # // middleware/logger.go package middleware import ( \u0026#34;log\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; ) // Logger 返回一个记录请求详情的日志中间件 func Logger() gin.HandlerFunc { return func(c *gin.Context) { // 开始时间 startTime := time.Now() // 设置变量记录请求处理状态 c.Set(\u0026#34;status\u0026#34;, 200) // 请求路径 path := c.Request.URL.Path // 请求方法 method := c.Request.Method // 处理请求 - 调用下一个中间件或处理函数 c.Next() // 结束时间 endTime := time.Now() // 执行时间 latency := endTime.Sub(startTime) // 获取状态码 statusCode := c.Writer.Status() // 请求IP clientIP := c.ClientIP() // 获取错误信息(如果有) errorMessage := \u0026#34;\u0026#34; if len(c.Errors) \u0026gt; 0 { errorMessage = c.Errors.String() } // 日志格式 log.Printf(\u0026#34;[GIN] %s | %3d | %13v | %15s | %-7s | %s | %s\u0026#34;, endTime.Format(\u0026#34;2006/01/02 - 15:04:05\u0026#34;), // 时间 statusCode, // 状态码 latency, // 耗时 clientIP, // 客户端IP method, // 请求方法 path, // 请求路径 errorMessage, // 错误信息 ) // 示例：特定条件下记录更详细的信息 if statusCode \u0026gt;= 500 { // 记录请求头和响应体等详细信息 log.Printf(\u0026#34;[ERROR] 服务器错误: %v\u0026#34;, c.Errors.String()) } } } 认证中间件 # JWT（JSON Web Token）认证是现代Web应用中常用的认证方式。以下是一个完整的JWT认证中间件实现：\n// middleware/auth.go package middleware import ( \u0026#34;errors\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;github.com/golang-jwt/jwt/v4\u0026#34; ) // JWTConfig JWT配置 type JWTConfig struct { SecretKey string // JWT密钥 Realm string // 认证领域 TokenLookup string // Token查找位置 TokenHeadName string // Token头部名称 Timeout time.Duration // Token超时时间 } // DefaultJWTConfig 默认JWT配置 var DefaultJWTConfig = JWTConfig{ SecretKey: \u0026#34;secret_key\u0026#34;, Realm: \u0026#34;gin jwt\u0026#34;, TokenLookup: \u0026#34;header:Authorization\u0026#34;, TokenHeadName: \u0026#34;Bearer\u0026#34;, Timeout: time.Hour * 24, } // JWTClaims JWT声明 type JWTClaims struct { UserID uint `json:\u0026#34;user_id\u0026#34;` Role string `json:\u0026#34;role\u0026#34;` jwt.RegisteredClaims } // JWTAuth 返回JWT认证中间件 func JWTAuth(config ...JWTConfig) gin.HandlerFunc { // 使用提供的配置或默认配置 var conf JWTConfig if len(config) \u0026gt; 0 { conf = config[0] } else { conf = DefaultJWTConfig } return func(c *gin.Context) { // 从请求中获取Token token, err := extractToken(c, conf) if err != nil { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ \u0026#34;error\u0026#34;: \u0026#34;认证失败: \u0026#34; + err.Error(), }) return } // 验证Token claims, err := validateToken(token, conf.SecretKey) if err != nil { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ \u0026#34;error\u0026#34;: \u0026#34;无效的Token: \u0026#34; + err.Error(), }) return } // 将用户信息存储到上下文 c.Set(\u0026#34;user_id\u0026#34;, claims.UserID) c.Set(\u0026#34;user_role\u0026#34;, claims.Role) c.Set(\u0026#34;claims\u0026#34;, claims) c.Next() } } // 从请求中提取Token func extractToken(c *gin.Context, conf JWTConfig) (string, error) { parts := strings.Split(conf.TokenLookup, \u0026#34;:\u0026#34;) if len(parts) != 2 { return \u0026#34;\u0026#34;, errors.New(\u0026#34;无效的token查找设置\u0026#34;) } switch parts[0] { case \u0026#34;header\u0026#34;: // 从请求头获取Token auth := c.GetHeader(parts[1]) if auth == \u0026#34;\u0026#34; { return \u0026#34;\u0026#34;, errors.New(\u0026#34;认证头不存在\u0026#34;) } // 检查Token前缀 if conf.TokenHeadName != \u0026#34;\u0026#34; { if !strings.HasPrefix(auth, conf.TokenHeadName+\u0026#34; \u0026#34;) { return \u0026#34;\u0026#34;, errors.New(\u0026#34;无效的认证头格式\u0026#34;) } return auth[len(conf.TokenHeadName)+1:], nil } return auth, nil case \u0026#34;query\u0026#34;: // 从URL查询参数获取Token token := c.Query(parts[1]) if token == \u0026#34;\u0026#34; { return \u0026#34;\u0026#34;, errors.New(\u0026#34;未提供token参数\u0026#34;) } return token, nil case \u0026#34;cookie\u0026#34;: // 从Cookie获取Token token, err := c.Cookie(parts[1]) if err != nil { return \u0026#34;\u0026#34;, errors.New(\u0026#34;未提供token Cookie\u0026#34;) } return token, nil } return \u0026#34;\u0026#34;, errors.New(\u0026#34;不支持的token获取方法\u0026#34;) } // 验证Token并返回声明 func validateToken(tokenString, secretKey string) (*JWTClaims, error) { // 解析Token token, err := jwt.ParseWithClaims(tokenString, \u0026amp;JWTClaims{}, func(token *jwt.Token) (interface{}, error) { // 验证签名算法 if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New(\u0026#34;无效的签名算法\u0026#34;) } return []byte(secretKey), nil }) if err != nil { return nil, err } // 检查Token是否有效 if !token.Valid { return nil, errors.New(\u0026#34;无效的token\u0026#34;) } // 类型断言 claims, ok := token.Claims.(*JWTClaims) if !ok { return nil, errors.New(\u0026#34;无效的token声明\u0026#34;) } return claims, nil } 错误处理中间件 # 统一的错误处理可以使应用更加健壮和用户友好：\n// middleware/error_handler.go package middleware import ( \u0026#34;errors\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;runtime/debug\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;github.com/go-playground/validator/v10\u0026#34; ) // 自定义错误类型 type AppError struct { Code int `json:\u0026#34;code\u0026#34;` Message string `json:\u0026#34;message\u0026#34;` Details interface{} `json:\u0026#34;details,omitempty\u0026#34;` } func (e *AppError) Error() string { return e.Message } // ValidationError 验证错误 type ValidationError struct { Field string `json:\u0026#34;field\u0026#34;` Message string `json:\u0026#34;message\u0026#34;` } // ErrorHandler 返回统一的错误处理中间件 func ErrorHandler() gin.HandlerFunc { return func(c *gin.Context) { // 设置一个recover，防止应用崩溃 defer func() { if err := recover(); err != nil { // 记录堆栈信息 log.Printf(\u0026#34;Panic: %v\\nStack: %s\u0026#34;, err, debug.Stack()) // 返回500错误 c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ \u0026#34;error\u0026#34;: gin.H{ \u0026#34;code\u0026#34;: http.StatusInternalServerError, \u0026#34;message\u0026#34;: \u0026#34;服务器内部错误\u0026#34;, }, }) } }() // 处理请求 c.Next() // 检查是否有错误 if len(c.Errors) \u0026gt; 0 { // 获取最后一个错误 err := c.Errors.Last().Err // 根据错误类型响应不同的状态码 switch e := err.(type) { case *AppError: // 自定义应用错误 c.JSON(e.Code, gin.H{ \u0026#34;error\u0026#34;: gin.H{ \u0026#34;code\u0026#34;: e.Code, \u0026#34;message\u0026#34;: e.Message, \u0026#34;details\u0026#34;: e.Details, }, }) case validator.ValidationErrors: // 验证错误 var validationErrors []ValidationError for _, err := range e { validationErrors = append(validationErrors, ValidationError{ Field: err.Field(), Message: getValidationErrorMsg(err), }) } c.JSON(http.StatusBadRequest, gin.H{ \u0026#34;error\u0026#34;: gin.H{ \u0026#34;code\u0026#34;: http.StatusBadRequest, \u0026#34;message\u0026#34;: \u0026#34;请求数据验证失败\u0026#34;, \u0026#34;details\u0026#34;: validationErrors, }, }) default: // 其他错误 c.JSON(http.StatusInternalServerError, gin.H{ \u0026#34;error\u0026#34;: gin.H{ \u0026#34;code\u0026#34;: http.StatusInternalServerError, \u0026#34;message\u0026#34;: \u0026#34;服务器内部错误\u0026#34;, \u0026#34;details\u0026#34;: err.Error(), }, }) } } } } // 获取验证错误的友好消息 func getValidationErrorMsg(err validator.FieldError) string { switch err.Tag() { case \u0026#34;required\u0026#34;: return \u0026#34;此字段为必填项\u0026#34; case \u0026#34;email\u0026#34;: return \u0026#34;必须是有效的电子邮箱地址\u0026#34; case \u0026#34;min\u0026#34;: return \u0026#34;值太小\u0026#34; case \u0026#34;max\u0026#34;: return \u0026#34;值太大\u0026#34; default: return \u0026#34;验证失败\u0026#34; } } // 在控制器中使用 func SomeController(c *gin.Context) { if somethingWrong { // 添加自定义错误到上下文 c.Error(\u0026amp;AppError{ Code: http.StatusBadRequest, Message: \u0026#34;请求参数错误\u0026#34;, Details: \u0026#34;无效的ID格式\u0026#34;, }) return } // 继续处理... } 限流中间件 # 使用令牌桶算法实现API限流，防止服务被过度使用：\n// middleware/rate_limiter.go package middleware import ( \u0026#34;net/http\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;golang.org/x/time/rate\u0026#34; ) // IPRateLimiter IP限流器 type IPRateLimiter struct { ips map[string]*rate.Limiter mu *sync.RWMutex rate rate.Limit burst int expiry time.Duration lastSeen map[string]time.Time } // NewIPRateLimiter 创建新的IP限流器 func NewIPRateLimiter(r rate.Limit, b int, expiry time.Duration) *IPRateLimiter { return \u0026amp;IPRateLimiter{ ips: make(map[string]*rate.Limiter), mu: \u0026amp;sync.RWMutex{}, rate: r, burst: b, expiry: expiry, lastSeen: make(map[string]time.Time), } } // GetLimiter 获取给定IP的限流器 func (i *IPRateLimiter) GetLimiter(ip string) *rate.Limiter { i.mu.RLock() limiter, exists := i.ips[ip] now := time.Now() // 检查上次访问时间，清理过期的限流器 if exists { lastSeen, ok := i.lastSeen[ip] if ok \u0026amp;\u0026amp; now.Sub(lastSeen) \u0026gt; i.expiry { exists = false } } i.mu.RUnlock() if !exists { i.mu.Lock() // 创建新的限流器 limiter = rate.NewLimiter(i.rate, i.burst) i.ips[ip] = limiter i.lastSeen[ip] = now // 清理过期的限流器 if len(i.ips) \u0026gt; 10000 { // 避免无限增长 for ip, t := range i.lastSeen { if now.Sub(t) \u0026gt; i.expiry { delete(i.ips, ip) delete(i.lastSeen, ip) } } } i.mu.Unlock() } else { // 更新最后访问时间 i.mu.Lock() i.lastSeen[ip] = now i.mu.Unlock() } return limiter } // RateLimiter 返回IP限流中间件 // r: 每秒请求速率 // b: 突发请求数 func RateLimiter(r float64, b int) gin.HandlerFunc { // 创建IP限流器实例，限流器过期时间1小时 limiter := NewIPRateLimiter(rate.Limit(r), b, time.Hour) return func(c *gin.Context) { // 获取客户端IP ip := c.ClientIP() // 获取该IP的限流器 ipLimiter := limiter.GetLimiter(ip) // 检查是否允许请求 if !ipLimiter.Allow() { c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{ \u0026#34;error\u0026#34;: \u0026#34;请求频率超限，请稍后再试\u0026#34;, }) return } c.Next() } } 第三方中间件 # https://github.com/gin-gonic/contrib\n","date":"2025-06-26","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/1bd46fa5/38331b58/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e中间件简介 \n    \u003cdiv id=\"中间件简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%ad%e9%97%b4%e4%bb%b6%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在Web应用开发中，中间件（Middleware）是位于应用程序与服务器之间的软件组件，能够拦截HTTP请求和响应，并执行特定的逻辑处理。\u003c/p\u003e","title":"3、中间件","type":"posts"},{"content":"Go语言的错误处理思想及设计包含以下特征：\n一个可能造成错误的函数，需要返回值中返回一个错误接口（error），如果调用是成功的，错误接口将返回 nil，否则返回错误。 在函数调用后需要检查错误，如果发生错误，则进行必要的错误处理。 Go语言没有类似Java或.NET中的异常处理机制，虽然可以使用 defer、panic、recover 模拟，但官方并不主张这样做，Go语言的设计者认为其他语言的异常机制已被过度使用，上层逻辑需要为函数发生的异常付出太多的资源，同时，如果函数使用者觉得错误处理很麻烦而忽略错误，那么程序将在不可预知的时刻崩溃。\nGo语言希望开发者将错误处理视为正常开发必须实现的环节，正确地处理每一个可能发生错误的函数，同时，Go语言使用返回值返回错误的机制，也能大幅降低编译器、运行时处理错误的复杂度，让开发者真正地掌握错误的处理。\n错误处理基础 # 错误处理哲学 # Go语言的错误处理遵循几个核心原则：\n显式优于隐式：错误必须被显式检查和处理 简单明了：使用简单的返回值而非复杂的异常机制 错误是值：错误在Go中是普通值，可以像其他值一样传递和操作 错误处理是正常控制流的一部分：而不是特殊路径 error接口 # Go中的所有错误都满足内置的error接口：\ntype error interface { Error() string } 这个简单的接口只有一个方法，用于返回错误描述字符串。任何实现了Error()方法的类型都可以作为错误使用。\n基本错误处理方式 # Go的标准错误处理模式是将错误作为函数的最后一个返回值：\n这种模式的关键点是：\n使用多返回值，最后一个返回值为错误 调用者必须显式检查错误 错误的零值是nil，表示没有错误 正常情况下返回实际结果和nil错误 package main import ( \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; ) // readFile 读取文件 func readFile(fn string) (string, error) { if fn == \u0026#34;\u0026#34; { return \u0026#34;\u0026#34;, errors.New(\u0026#34;file name is empty\u0026#34;) } return \u0026#34;file content\u0026#34;, nil } func main() { content, err := readFile(\u0026#34;\u0026#34;) if err != nil { // err.Error() 获取错误信息 fmt.Println(err.Error()) return } fmt.Println(content) } 创建和使用错误 # errors.New # errors包实现了一个基本的 error，具体实现方式如下\nfunc New(text string) error { return \u0026amp;errorString{text} } type errorString struct { s string } func (e *errorString) Error() string { return e.s } 一般情况下，我们可以使用errors.New来创建一个error\nimport \u0026#34;errors\u0026#34; err1 := errors.New(\u0026#34;数据库连接失败\u0026#34;) 自定义错误类型 # 对于需要携带额外信息的错误，可以创建自定义错误类型：\n自定义错误类型的优势：\n可以携带上下文信息 允许调用者通过类型断言获取详细信息 可以实现错误分层和包装 package main import \u0026#34;fmt\u0026#34; type FileReadError struct { Msg string } func (f *FileReadError) Error() string { return f.Msg } func NewFileReadError(msg string) error { return \u0026amp;FileReadError{msg} } func main() { err := NewFileReadError(\u0026#34;file name is empty\u0026#34;) if err != nil { if _, ok := err.(*FileReadError); ok { fmt.Printf(\u0026#34;FileReadError %s\\n\u0026#34;, err.Error()) } else { fmt.Printf(\u0026#34;Other Error %s\\n\u0026#34;, err.Error()) } } } 错误包装 # 在 Go 语言中，错误包装（Error Wrapping） 是一种通过链式结构记录错误上下文的技术，允许在错误传播过程中保留原始错误信息，同时附加额外的调试或日志信息。自 Go 1.13 起，标准库通过 fmt.Errorf 的 %w 动词和 errors.Unwrap/errors.Is/errors.As 等函数原生支持错误包装。\nfmt.Errorf(\u0026quot;msg %w\u0026quot;, err)：用 %w 包裹原始错误 err，生成新错误。 errors.Unwrap(err)：解包错误，返回被包裹的原始错误。 errors.Is(err, target)：检查错误链中是否存在 target 错误。 errors.As(err, target)：将错误链中的错误转换为指定类型。 errors.Unwrap 解包错误 # package main import ( \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; ) func main() { err1 := errors.New(\u0026#34;error-1\u0026#34;) err2 := fmt.Errorf(\u0026#34;error-2 %w\u0026#34;, err1) err3 := fmt.Errorf(\u0026#34;error-3 %w\u0026#34;, err2) // 逐层解包 for err3 != nil { fmt.Println(err3) err3 = errors.Unwrap(err3) } } /* error-3 error-2 error-1 error-2 error-1 error-1 */ errors.Is 检查特定错误 # 使用 errors.Is 判断错误链中是否包含指定错误：\npackage main import ( \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; ) type FileNotFoundError struct { Msg string } func (e *FileNotFoundError) Error() string { return e.Msg } func NewFileNotFoundError(msg string) error { return \u0026amp;FileNotFoundError{msg} } func main() { notFoundErr := NewFileNotFoundError(\u0026#34;File not found\u0026#34;) readRrr := fmt.Errorf(\u0026#34;File Read Error %w\u0026#34;, notFoundErr) fmt.Println(errors.Is(readRrr, notFoundErr)) // true } errors.As 类型断言 # 使用 errors.As 将错误转换为具体类型：\npackage main import ( \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; ) type FileNotFoundError struct { Msg string } func (e *FileNotFoundError) Error() string { return e.Msg } func NewFileNotFoundError(msg string) error { return \u0026amp;FileNotFoundError{msg} } func main() { notFoundErr := NewFileNotFoundError(\u0026#34;File not found\u0026#34;) readRrr := fmt.Errorf(\u0026#34;File Read Error %w\u0026#34;, notFoundErr) var nf *FileNotFoundError if b := errors.As(readRrr, \u0026amp;nf); b { fmt.Println(nf.Error()) // File not found } } 错误处理策略与模式 # 仅处理一次错误 # 一个好的原则是每个错误只处理一次。处理错误意味着：\n修复错误并继续 将错误包装并向上传播 记录错误并停止处理 // 不推荐的方式 func processFile(filename string) error { data, err := ioutil.ReadFile(filename) if err != nil { log.Printf(\u0026#34;读取文件失败: %v\u0026#34;, err) // 记录错误 return err // 又返回错误 } // ... } // 推荐的方式 func processFile(filename string) error { data, err := ioutil.ReadFile(filename) if err != nil { return fmt.Errorf(\u0026#34;读取文件 %s 失败: %w\u0026#34;, filename, err) } // ... } // 或者如果这是调用链的终点 func handleRequest() { err := processFile(\u0026#34;config.json\u0026#34;) if err != nil { log.Printf(\u0026#34;请求处理失败: %v\u0026#34;, err) http.Error(w, \u0026#34;内部服务器错误\u0026#34;, 500) return } // ... } 错误处理模式 # 哨兵错误（Sentinel Errors） # 预定义特定错误值用于比较：\nvar ( ErrNotFound = errors.New(\u0026#34;资源未找到\u0026#34;) ErrPermission = errors.New(\u0026#34;权限不足\u0026#34;) ) func GetResource(id string) (*Resource, error) { // ... return nil, ErrNotFound } // 使用 res, err := GetResource(\u0026#34;123\u0026#34;) if err == ErrNotFound { // 处理\u0026#34;未找到\u0026#34;情况 } 错误类型检查 # 通过类型断言或errors.As检查错误类型：\ntype NotFoundError struct { Resource string } func (e NotFoundError) Error() string { return fmt.Sprintf(\u0026#34;资源 %s 未找到\u0026#34;, e.Resource) } // 使用 if err != nil { var notFound NotFoundError if errors.As(err, \u0026amp;notFound) { // 处理NotFoundError } } 错误行为检查 # Go 1.20+新增，检查错误是否实现了特定接口，关注错误能做什么，而非错误是什么：\n// 定义行为接口 type Temporary interface { Temporary() bool } // 实现接口的错误 type NetworkError struct { Msg string IsTemp bool } func (e NetworkError) Error() string { return e.Msg } func (e NetworkError) Temporary() bool { return e.IsTemp } // 基于行为处理错误 func handleConnection() { for { err := connect() if err != nil { // 检查错误行为 var temp interface{ Temporary() bool } if errors.As(err, \u0026amp;temp) \u0026amp;\u0026amp; temp.Temporary() { // 临时错误，稍后重试 time.Sleep(time.Second) continue } // 永久错误，停止尝试 log.Fatalf(\u0026#34;连接失败: %v\u0026#34;, err) } break } } 多错误处理 # Go 1.20引入了errors.Join函数，用于组合多个错误：\n// 组合多个错误 err1 := errors.New(\u0026#34;错误1\u0026#34;) err2 := errors.New(\u0026#34;错误2\u0026#34;) err3 := errors.New(\u0026#34;错误3\u0026#34;) combinedErr := errors.Join(err1, err2, err3) fmt.Println(combinedErr) // 检查组合错误中是否包含特定错误 if errors.Is(combinedErr, err2) { fmt.Println(\u0026#34;组合错误包含err2\u0026#34;) } panic与recover机制 # Go语言的类型系统会在编译时捕获很多错误，但有些错误只能在运行时检查，如数组访问越界、空指针引用等，这些运行时错误会引起宕机。\n一般而言，当宕机发生时，程序会中断运行，并立即执行在该 goroutine（可以先理解成线程）中被延迟的函数（defer 机制），随后，程序崩溃并输出日志信息，日志信息包括 panic() 的参数以及函数调用的堆栈跟踪信息， panic() 的参数通常是某种错误信息。\n虽然Go语言的 panic 机制类似于其他语言的异常，但 panic 的适用场景有一些不同，由于panic 会引起程序的崩溃，因此 panic 一般用于严重错误，如程序内部的逻辑不一致。任何崩溃都表明了我们的代码中可能存在漏洞，所以对于大部分漏洞，我们应该使用Go语言提供的错误机制，而不是 panic。\npanic 基础 # panic是Go中的异常机制，用于处理不可恢复的错误：\nfunc divide(a, b int) int { if b == 0 { panic(\u0026#34;除数不能为零\u0026#34;) } return a / b } 当调用panic时：\n当前函数执行立即停止 任何defer语句正常执行 控制权返回给调用者 过程递归向上，直到程序崩溃或被recover捕获 recover捕获panic # recover() 是一个Go语言的内建函数，recover允许程序捕获panic并恢复正常执行：\nrecover仅在延迟函数defer中直接调用才有效，在正常的执行过程中，调用 recover 会返回 nil 并且没有其他任何效果，如果当前的 goroutine 陷入panic，调用 recover 可以捕获到 panic 的输入值，并且恢复正常的执行。\npanic/recover应该仅用于以下场景：\n真正的异常情况：如不可恢复的逻辑错误 初始化失败：程序启动时如果关键初始化失败 防止程序崩溃：作为最后的保护机制 简化复杂错误处理：在特定递归场景中，如解析器实现 由于函数ff()中有recover()，所以当panic发生时，会执行包含了recover()的defer语句，然后退出当前函数ff()，程序继续执行\npackage main import \u0026#34;fmt\u0026#34; func main() { ff() fmt.Println(\u0026#34;CC\u0026#34;) } func ff() { defer func() { if err := recover(); err != nil { fmt.Printf(\u0026#34;has panic : %v\\n\u0026#34;, err) } }() fmt.Println(\u0026#34;AA\u0026#34;) panic(\u0026#34;Test Panic\u0026#34;) fmt.Println(\u0026#34;BB\u0026#34;) } /* AA has panic : Test Panic CC */ 小心隐藏的panic # 某些操作可能会引发隐藏的panic，应小心处理：\n// 可能引发panic的操作 var users []User fmt.Println(users[0]) // 索引越界 var m map[string]int m[\u0026#34;key\u0026#34;] = 1 // nil map赋值 var p *Person fmt.Println(p.Name) // 空指针解引用 // 安全的替代方案 if len(users) \u0026gt; 0 { fmt.Println(users[0]) } m := make(map[string]int) m[\u0026#34;key\u0026#34;] = 1 if p != nil { fmt.Println(p.Name) } panic和defer # 当 panic() 触发的宕机发生时，panic() 后面的代码将不会被运行\n但是在panic()函数前程序执行到的 defer 语句依然会在panic()前执行\npackage main import \u0026#34;fmt\u0026#34; func main() { fmt.Println(\u0026#34;start\u0026#34;) defer fmt.Println(\u0026#34;AA\u0026#34;) defer fmt.Println(\u0026#34;BB\u0026#34;) panic(\u0026#34;Test Panic\u0026#34;) defer fmt.Println(\u0026#34;CC\u0026#34;) } /* start BB AA panic: Test Panic goroutine 1 [running]: main.main() /Users/yanggang/Desktop/helloworld/main.go:9 +0xe0 exit status 2 */ ","date":"2025-06-25","externalUrl":null,"permalink":"/posts/2d3150b2/0e0cf2f4/cd7d5be7/","section":"文章","summary":"\u003cp\u003eGo语言的错误处理思想及设计包含以下特征：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e一个可能造成错误的函数，需要返回值中返回一个错误接口（error），如果调用是成功的，错误接口将返回 \u003ccode\u003enil\u003c/code\u003e，否则返回错误。\u003c/li\u003e\n\u003cli\u003e在函数调用后需要检查错误，如果发生错误，则进行必要的错误处理。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eGo语言没有类似Java或.NET中的异常处理机制，虽然可以使用 defer、panic、recover 模拟，但官方并不主张这样做，Go语言的设计者认为其他语言的异常机制已被过度使用，上层逻辑需要为函数发生的异常付出太多的资源，同时，如果函数使用者觉得错误处理很麻烦而忽略错误，那么程序将在不可预知的时刻崩溃。\u003c/p\u003e","title":"3、错误处理与异常机制","type":"posts"},{"content":" go-redis # **支持多种客户端：**支持单机Redis Server、Redis Cluster、Redis Sentinel、Redis分片服务器 **自动处理数据类型：**go-redis会根据不同的redis命令处理成指定的数据类型，不必进行繁琐的数据类型转换 **功能完善：**go-redis支持管道(pipeline)、事务、pub/sub、Lua脚本、mock、分布式锁等功能 官网：https://redis.uptrace.dev/zh/\n安装 # go get -u github.com/redis/go-redis/v9 获取连接 # package database import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/redis/go-redis/v9\u0026#34; \u0026#34;time\u0026#34; ) type RedisConfig struct { Host string // redis服务地址 Port string // redis服务端口 Password string // redis密码 DB int // 使用的DB } func InitRedisClient(config *RedisConfig) *redis.Client { // 配置连接信息 option := redis.Options{ Addr: fmt.Sprintf(\u0026#34;%s:%s\u0026#34;, config.Host, config.Port), Password: config.Password, // 密码 DB: config.DB, // redis一共16个库，指定其中一个库即可 //连接池容量及闲置连接数量 PoolSize: 15, // 连接池最大socket连接数，默认为4倍CPU数， 4 * runtime.NumCPU MinIdleConns: 10, // 在启动阶段创建指定数量的Idle连接，并长期维持idle状态的连接数不少于指定数量；。 //超时 DialTimeout: 5 * time.Second, // 连接建立超时时间，默认5秒。 ReadTimeout: 3 * time.Second, // 读超时，默认3秒， -1表示取消读超时 WriteTimeout: 3 * time.Second, // 写超时，默认等于读超时 PoolTimeout: 4 * time.Second, // 当所有连接都处在繁忙状态时，客户端等待可用连接的最大等待时长，默认为读超时+1秒。 //命令执行失败时的重试策略 MaxRetries: 0, // 命令执行失败时，最多重试多少次，默认为0即不重试 MinRetryBackoff: 8 * time.Millisecond, // 每次计算重试间隔时间的下限，默认8毫秒，-1表示取消间隔 MaxRetryBackoff: 512 * time.Millisecond, // 每次计算重试间隔时间的上限，默认512毫秒，-1表示取消间隔 } // 获取客户端实例 client := redis.NewClient(\u0026amp;option) // 使用 Ping() 方法测试是否成功连接到 Redis 服务器 ctx := context.Background() pong, err := client.Ping(ctx).Result() if err != nil { fmt.Println(\u0026#34;Failed to connect to Redis:\u0026#34;, err) return nil } fmt.Println(\u0026#34;Connected to Redis:\u0026#34;, pong) return client } 操作 # 字符串 # //新增 Set(\u0026#34;test\u0026#34;, \u0026#34;123456\u0026#34;, 0) //参数三为过期时间，0则为不设置过期时间 SetNX(\u0026#34;test\u0026#34;, \u0026#34;123456\u0026#34;, 0) //不存在则新增 //查询 Get(\u0026#34;test\u0026#34;) GetSet(\u0026#34;test\u0026#34;, \u0026#34;1234\u0026#34;) //插入新值返回旧值 //操作 Incr(\u0026#34;test\u0026#34;) //自增1 Decr(\u0026#34;test\u0026#34;) //自减1 通用操作 # Keys(\u0026#34;t*\u0026#34;) //查询所有n开头的key DBSize() //查询key总数 Exists(\u0026#34;test\u0026#34;, \u0026#34;test1\u0026#34;) //查询key是否存在，返回存在的个数 Del(\u0026#34;test\u0026#34;, \u0026#34;test1\u0026#34;) //批量删除key，返回删除个数 Expire(\u0026#34;test\u0026#34;, 3 * time.Second) //设置过期时间 TTL(\u0026#34;test1\u0026#34;) //查询过期时间，如果不过期返回-1s Persist(\u0026#34;test1\u0026#34;) //去掉过期时间 ","date":"2025-06-23","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/db9fe107/d0f783dc/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003ego-redis \n    \u003cdiv id=\"go-redis\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#go-redis\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e**支持多种客户端：**支持单机Redis Server、Redis Cluster、Redis Sentinel、Redis分片服务器\u003c/li\u003e\n\u003cli\u003e**自动处理数据类型：**go-redis会根据不同的redis命令处理成指定的数据类型，不必进行繁琐的数据类型转换\u003c/li\u003e\n\u003cli\u003e**功能完善：**go-redis支持管道(pipeline)、事务、pub/sub、Lua脚本、mock、分布式锁等功能\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e官网：https://redis.uptrace.dev/zh/\u003c/p\u003e","title":"3、redis","type":"posts"},{"content":" TUI # TUI和CLI的定义和区别 # ‌**TUI（Text-based User Interface）**‌：TUI是通过文本实现交互窗口展示内容，用户通过键盘输入命令进行操作。它提供字符图形界面，使得任务执行更加友好‌。 ‌**CLI（Command Line Interface）**‌：CLI是一种通过命令行解释器与计算机进行交互的用户界面。用户通过输入命令和参数来执行系统提供的各种功能。CLI通常通过终端或控制台访问，具有高度灵活性和资源效率‌。 TUI和CLI的关系 # ‌互补关系‌：TUI和CLI在Linux系统中相互补充，共同提供不同的交互方式。TUI通过字符图形界面简化操作，而CLI则通过命令行提供更高的灵活性和控制能力。 ‌应用场景‌：TUI适用于需要字符图形界面的任务，如文件管理、简单的系统监控等；而CLI则适用于系统管理和配置、自动化脚本编写等需要高度控制和灵活性的任务‌。 bubbletea # bubbletea是一个简单、小巧、可以非常方便地用来编写 TUI（terminal User Interface，控制台界面程序）程序的框架。内置简单的事件处理机制，可以对外部事件做出响应，如键盘按键。\n快速使用 # 创建 Go 项目，并初始化\nmkdir tui-demo \u0026amp;\u0026amp; cd tui-demo go mod init ygang.top/tui-demo 安装bubbletea库：\ngo get -u github.com/charmbracelet/bubbletea bubbletea程序都需要有一个实现bubbletea.Model接口的类型：\ntype Model interface { Init() Cmd Update(Msg) (Model, Cmd) View() string } Init()方法在程序启动时会立刻调用，它会做一些初始化工作，并返回一个Cmd告诉bubbletea要执行什么命令； Update()方法用来响应外部事件，返回一个修改后的模型，和想要bubbletea执行的命令； View()方法用于返回在控制台上显示的文本字符串。 package main import ( \u0026#34;fmt\u0026#34; tea \u0026#34;github.com/charmbracelet/bubbletea\u0026#34; \u0026#34;os\u0026#34; ) type Todo struct { // 所有待完成事项 todos []string // 界面上光标位置 cursor int // 已完成标识 selected map[int]struct{} } var initTodo = \u0026amp;Todo{ todos: []string{\u0026#34;clean house\u0026#34;, \u0026#34;wash clothes\u0026#34;, \u0026#34;write a blog\u0026#34;}, selected: make(map[int]struct{}), } func (t *Todo) Init() tea.Cmd { // 不需要初始化操作，所以返回 nil return nil } /* Update ctrl+c或q：退出程序； up或k：向上移动光标； down或j：向下移动光标； enter或：切换光标处事项的完成状态。 */ func (t *Todo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch model := msg.(type) { case tea.KeyMsg: switch model.String() { case \u0026#34;ctrl+c\u0026#34;, \u0026#34;q\u0026#34;: return t, tea.Quit case \u0026#34;up\u0026#34;, \u0026#34;k\u0026#34;: if t.cursor \u0026gt; 0 { t.cursor-- } case \u0026#34;down\u0026#34;, \u0026#34;j\u0026#34;: if t.cursor \u0026lt; len(t.todos)-1 { t.cursor++ } case \u0026#34;enter\u0026#34;: _, ok := t.selected[t.cursor] if ok { delete(t.selected, t.cursor) } else { t.selected[t.cursor] = struct{}{} } } } return t, nil } /* View 这个方法返回的字符串就是最终显示在控制台上的文本。 我们可以按照自己想要的形式，根据模型数据拼装 */ func (t *Todo) View() string { s := \u0026#34;Todo List:\\n\\n\u0026#34; for i, choice := range t.todos { cursor := \u0026#34;\u0026#34; if t.cursor == i { cursor = \u0026#34;\u0026gt;\u0026#34; } checked := \u0026#34;\u0026#34; if _, ok := t.selected[i]; ok { checked = \u0026#34;x\u0026#34; } s += fmt.Sprintf(\u0026#34;%s [%s] %s\\n\u0026#34;, cursor, checked, choice) } s += \u0026#34;\\nPress q to quit.\\n\u0026#34; return s } func main() { program := tea.NewProgram(initTodo) if _, err := program.Run(); err != nil { fmt.Println(\u0026#34;start failed:\u0026#34;, err) os.Exit(1) } } ","date":"2025-05-22","externalUrl":null,"permalink":"/posts/2d3150b2/a56b86e6/8fd52d27/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eTUI \n    \u003cdiv id=\"tui\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#tui\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eTUI和CLI的定义和区别 \n    \u003cdiv id=\"tui和cli的定义和区别\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#tui%e5%92%8ccli%e7%9a%84%e5%ae%9a%e4%b9%89%e5%92%8c%e5%8c%ba%e5%88%ab\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e‌**TUI（Text-based User Interface）**‌：TUI是通过文本实现交互窗口展示内容，用户通过键盘输入命令进行操作。它提供字符图形界面，使得任务执行更加友好‌。\u003c/li\u003e\n\u003cli\u003e‌**CLI（Command Line Interface）**‌：CLI是一种通过命令行解释器与计算机进行交互的用户界面。用户通过输入命令和参数来执行系统提供的各种功能。CLI通常通过终端或控制台访问，具有高度灵活性和资源效率‌。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003eTUI和CLI的关系 \n    \u003cdiv id=\"tui和cli的关系\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#tui%e5%92%8ccli%e7%9a%84%e5%85%b3%e7%b3%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e‌\u003cstrong\u003e互补关系\u003c/strong\u003e‌：TUI和CLI在Linux系统中相互补充，共同提供不同的交互方式。TUI通过字符图形界面简化操作，而CLI则通过命令行提供更高的灵活性和控制能力。\u003c/li\u003e\n\u003cli\u003e‌\u003cstrong\u003e应用场景\u003c/strong\u003e‌：TUI适用于需要字符图形界面的任务，如文件管理、简单的系统监控等；而CLI则适用于系统管理和配置、自动化脚本编写等需要高度控制和灵活性的任务‌。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003ebubbletea \n    \u003cdiv id=\"bubbletea\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#bubbletea\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003ebubbletea\u003c/code\u003e是一个简单、小巧、可以非常方便地用来编写 TUI（terminal User Interface，控制台界面程序）程序的框架。内置简单的事件处理机制，可以对外部事件做出响应，如键盘按键。\u003c/p\u003e","title":"3、bubbletea","type":"posts"},{"content":" Wails 命令行 # Wails CLI 有许多用于管理项目的命令。 所有命令都以此方式运行：\nwails \u0026lt;命令\u0026gt; \u0026lt;标志\u0026gt; init # wails init 用于生成项目。\n标志 描述 默认 -n \u0026quot;项目名称\u0026quot; 项目名称。 强制必填 -d \u0026quot;项目目录\u0026quot; 要创建的项目目录 项目名 -g 初始化 git 存储库 -l 可用项目模板列表 -q 禁止输出到控制台 -t \u0026quot;模板名称\u0026quot; 要使用的项目模板。 这可能是默认模板的名称或托管在 github 上的远程模板的 URL 。 vanilla -ide 生成集成开发环境项目文件 vscode 或 goland -f 强制构建应用 false 示例：wails init -n test -d mytestproject -g -ide vscode -q，这将在 mytestproject 目录生成一个名为 test 的项目，初始化 git，生成 vscode 项目文件并静默执行。\n远程模板 # 支持远程模板（托管在 GitHub ）并且可以使用模板项目的 URL 进行安装。\n示例： wails init -n test -t https://github.com/leaanthony/testtemplate[@v1.0.0]\n可以在此处找到社区维护的模板列表：https://wails.io/zh-Hans/docs/community/templates\nbuild # wails build 用于将您的项目编译为生产可用的二进制文件。\n标志 描述 默认 -clean 清理 build/bin 目录 -compiler \u0026quot;编译器\u0026quot; 使用不同的 go 编译器来构建，例如 go1.15beta1 go -debug 允许在应用程序窗口中使用 devtools。 -devtools 允许在生产应用程序窗口中使用 devtools（不使用 -debug 时）。可使用 Ctrl/Cmd+Shift+F12 打开 devtools 窗口。注意：此选项将使您的应用程序违反 Mac appstore 指南。仅用于调试。 -dryrun 打印构建命令但不执行它 -f 强制构建应用 -garbleargs 传递给 garble 的参数 -literals -tiny -seed=random -ldflags \u0026quot;标志\u0026quot; 传递给编译器的额外 ldflags -m 编译前跳过 mod tidy -nopackage 不打包应用程序 -nocolour 在输出中禁用颜色 -nosyncgomod 不同步 go.mod 中的 Wails 版本 -nsis 为 Windows 生成 NSIS 安装程序 -o 文件名 输出文件名 -obfuscated 使用garble混淆应用程序 -platform 为指定的平台构建，例如： windows/arm64。 windows/arm64。 注意，如果不给出架构，则使用 runtime.GOARCH。 如果给定环境变量则platform = GOOS；否则等于 runtime.GOOS。 如果给定环境变量则arch = GOARCH 否则等于 runtime.GOARCH. -race 使用 Go 的竞态检测器构建 -s 跳过前端构建 -skipbindings 跳过 bindings 生成 -tags \u0026quot;额外标签\u0026quot; 构建标签以传递给 Go 编译器。 必须引用。 空格或逗号（但不能同时使用）分隔 -trimpath 从生成的可执行文件中删除所有文件系统路径。 -u 更新项目的 go.mod 以使用与 CLI 相同版本的 Wails -upx 使用 “upx” 压缩最终二进制文件 -upxflags 传递给 upx 的标志 -v int 详细级别 (0 - silent, 1 - default, 2 - verbose) 1 -webview2 WebView2 安装策略：download,embed,browser,error. download -windowsconsole 保留Windows构建控制台窗口 示例：wails build -clean -o myproject.exe\n目前支持的平台有：\n平台 描述 darwin MacOS + architecture of build machine darwin/amd64 MacOS 10.13+ AMD64 darwin/arm64 MacOS 11.0+ ARM64 darwin/universal MacOS AMD64+ARM64 universal application windows Windows 10/11 + architecture of build machine windows/amd64 Windows 10/11 AMD64 windows/arm64 Windows 10/11 ARM64 linux Linux + architecture of build machine linux/amd64 Linux AMD64 linux/arm64 Linux ARM64 doctor # wails doctor 将运行诊断程序以确保您的系统已准备好进行开发。\ndev # wails dev 用于以 \u0026ldquo;实时开发\u0026rdquo; 模式运行您的应用。 这意味着：\n应用程序的 go.mod 将被更新为与 Wails CLI 相同的版本 应用程序被编译并自动运行 一个观察者被启动，如果它检测到您的 go 文件的变化，它将触发您的开发应用程序的重新构建 启动一个网络服务器 http://localhost:34115，通过 http 为您的应用程序（不仅仅是前端）提供服务。 这允许您使用您喜欢的浏览器开发扩展。 所有应用程序资源都从磁盘加载。 如果它们被更改，应用程序将自动重新加载（而不是重新构建）。 所有连接的浏览器也将重新加载。 生成的 JS 模块提供以下内容： 带有自动生成的 JSDoc 的 Go 方法的 JavaScript 包装器，提供代码提示 您的 Go 结构的 TypeScript 版本，可以构建并传递给您的 go 方法 生成第二个 JS 模块，为运行时提供包装器 + TS 声明 在 macOS 上，它将应用程序捆绑到一个 .app 文件中并运行它。 开发模式将使用 build/darwin/Info.dev.plist 。 标志 描述 默认 -appargs \u0026quot;参数\u0026quot; 以 shell 样式传递给应用程序的参数 -assetdir \u0026quot;./path/to/assets\u0026quot; 从给定目录提供资产，而不是使用提供的资产 FS wails.json 中的值 -browser 在启动时打开浏览器到 http://localhost:34115 -compiler \u0026quot;编译器\u0026quot; 使用不同的 go 编译器来构建，例如 go1.15beta1 go -debounce 检测到资产更改后等待重新加载的时间 100 (毫秒) -devserver \u0026quot;host:port\u0026quot; 将 wails 开发服务器绑定到的地址 localhost:34115 -extensions 触发重新构建的扩展（逗号分隔） go -forcebuild 强制构建应用程序 -frontenddevserverurl \u0026quot;url\u0026quot; 使用 3rd 方开发服务器 url 提供资产，例如：Vite \u0026quot;\u0026quot; -ldflags \u0026quot;标志\u0026quot; 传递给编译器的额外 ldflags -loglevel \u0026quot;日志级别\u0026quot; 要使用的日志级别 - Trace, Debug, Info, Warning, Error Debug（调试） -nocolour 关闭彩色命令行输出 false -noreload 资产更改时禁用自动重新加载 -nosyncgomod 不同步 go.mod 中的 Wails 版本 false -race 使用 Go 的竞态检测器构建 false -reloaddirs 触发重新加载的附加目录（逗号分隔） wails.json 中的值 -s 跳过前端构建 false -save 将指定的 assetdir、 reloaddirs、 wailsjsdir、 debounce 、 devserver 和 frontenddevserverurl 标志的值保存到 wails.json 以成为后续调用的默认值。 -skipbindings 跳过 bindings 生成 -tags \u0026quot;额外标签\u0026quot; 传递给编译器的构建标签（引号和空格分隔） -v 详细级别 (0 - silent, 1 - standard, 2 - verbose) 1 -wailsjsdir 生成生成的Wails JS模块的目录 wails.json 中的值 示例：wails dev -assetdir ./frontend/dist -wailsjsdir ./frontend/src -browser\n此命令将执行以下操作：\n构建应用程序并运行它 在 ./frontend/src 中生成 Wails JS 模块 监听 ./frontend/dist 中文件的更新并在更改时重新加载 打开浏览器并连接到应用程序 generate # template # Wails 使用模板来生成项目。 wails generate template 命令有助于构建模板，以使它可以用于生成项目。\n标志 描述 -name 模板名称（必填） -frontend \u0026quot;路径\u0026quot; 要在模板中使用的前端项目的路径 module # wails generate module 命令允许您为应用程序手动生成 wailsjs 目录。\n标志 描述 默认 -compiler \u0026quot;编译器\u0026quot; 使用不同的 go 编译器来构建，例如 go1.15beta1 go -tags \u0026quot;额外标签\u0026quot; 传递给编译器的构建标签（引号和空格分隔） update # wails update 将更新 Wails CLI 的版本。\n标志 描述 -pre 更新到最新的预发布版本 -version \u0026quot;版本\u0026quot; 安装指定版本的 CLI version # wails version 仅输出当前的 CLI 版本。\n","date":"2025-05-22","externalUrl":null,"permalink":"/posts/2d3150b2/9f158c96/f88084c8/fe5f9290/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eWails 命令行 \n    \u003cdiv id=\"wails-命令行\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#wails-%e5%91%bd%e4%bb%a4%e8%a1%8c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eWails CLI 有许多用于管理项目的命令。 所有命令都以此方式运行：\u003c/p\u003e","title":"3、Wails命令","type":"posts"},{"content":"Go语言的标准库提供了强大的模板系统，分为两个主要包：text/template和html/template。这两个包使用相同的接口和语法，但html/template包增加了对HTML特定的安全功能，防止跨站脚本攻击（XSS）。在Web开发中，我们主要使用html/template包。\n模板系统 # 模板系统概述 # 模板系统允许我们将数据和表现分离，实现以下目标：\n关注点分离：让前端开发者专注于页面设计，后端开发者专注于业务逻辑 代码重用：通过模板继承和片段复用减少重复代码 动态内容：将数据动态注入到预定义的HTML结构中 安全渲染：自动防止XSS和其他注入攻击 基本使用 # 这个例子展示了模板使用的基本步骤：\n创建模板：使用template.New()创建一个新模板 解析模板：使用.Parse()方法解析模板字符串 准备数据：创建将传递给模板的数据结构 渲染模板：使用.Execute()方法将数据应用到模板并输出结果 在模板字符串中，{{.Name}}是一个特殊的标记，它会被替换为数据中的Name字段。\npackage main import ( \u0026#34;html/template\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;log\u0026#34; ) func main() { // 定义处理函数 http.HandleFunc(\u0026#34;/\u0026#34;, func(w http.ResponseWriter, r *http.Request) { // 创建模板 tmpl, err := template.New(\u0026#34;hello\u0026#34;).Parse(\u0026#34;\u0026lt;h1\u0026gt;Hello, {{.Name}}!\u0026lt;/h1\u0026gt;\u0026#34;) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // 准备数据 data := struct { Name string }{ Name: \u0026#34;Gopher\u0026#34;, } // 渲染模板 err = tmpl.Execute(w, data) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }) log.Println(\u0026#34;Server started at :8080\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } 从文件加载模板 # 在实际应用中，我们通常不会将模板内容硬编码到程序中，而是从文件中加载。\n模板文件 templates/index.html 的内容如下：\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;{{.Title}}\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;{{.Title}}\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;{{.Message}}\u0026lt;/p\u0026gt; \u0026lt;ul\u0026gt; {{range .Items}} \u0026lt;li\u0026gt;{{.}}\u0026lt;/li\u0026gt; {{end}} \u0026lt;/ul\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 搭建web服务，并解析模板。\n使用template.ParseFiles()可以加载一个或多个模板文件。如果加载多个文件，它将返回一个模板集合，默认使用第一个文件作为主模板。\npackage main import ( \u0026#34;html/template\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;log\u0026#34; ) func main() { http.HandleFunc(\u0026#34;/\u0026#34;, func(w http.ResponseWriter, r *http.Request) { // 从文件加载模板 tmpl, err := template.ParseFiles(\u0026#34;templates/index.html\u0026#34;) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // 准备数据 data := struct { Title string Message string }{ Title: \u0026#34;Go Templates\u0026#34;, Message: \u0026#34;Welcome to Go Web Development!\u0026#34;, } // 渲染模板 err = tmpl.Execute(w, data) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }) log.Println(\u0026#34;Server started at :8080\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } 模板缓存与预加载 # 在生产环境中，重复解析模板文件会造成不必要的性能开销。一种常见的优化是在应用启动时预加载所有模板。\n使用template.ParseGlob()可以一次性加载多个模板文件，然后使用ExecuteTemplate()方法执行特定的模板。这种方式不仅提高了性能，还使代码更加简洁和可维护。\npackage main import ( \u0026#34;html/template\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;log\u0026#34; ) // 全局模板集合 var templates *template.Template func init() { // 预加载所有模板 var err error templates, err = template.ParseGlob(\u0026#34;templates/*.html\u0026#34;) if err != nil { log.Fatal(\u0026#34;Failed to parse templates:\u0026#34;, err) } } func renderTemplate(w http.ResponseWriter, name string, data interface{}) { // 通过名称执行模板 err := templates.ExecuteTemplate(w, name, data) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func homeHandler(w http.ResponseWriter, r *http.Request) { data := struct { Title string Message string }{ Title: \u0026#34;Home Page\u0026#34;, Message: \u0026#34;Welcome to our website!\u0026#34;, } renderTemplate(w, \u0026#34;index.html\u0026#34;, data) } func aboutHandler(w http.ResponseWriter, r *http.Request) { data := struct { Title string About string }{ Title: \u0026#34;About Us\u0026#34;, About: \u0026#34;We are a team of Go enthusiasts.\u0026#34;, } renderTemplate(w, \u0026#34;about.html\u0026#34;, data) } func main() { http.HandleFunc(\u0026#34;/\u0026#34;, homeHandler) http.HandleFunc(\u0026#34;/about\u0026#34;, aboutHandler) log.Println(\u0026#34;Server started at :8080\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } 模板语法与数据操作 # 基本语法 # Go模板使用双花括号{{ }}作为分隔符，其中可以包含变量、表达式、控制结构等。\n变量和字段访问 # {{.}} - 当前对象 {{.Field}} - 访问结构体字段 {{.Method}} - 调用方法（返回一个值） {{.Field1.Field2}} - 嵌套字段访问 {{.Field.Method}} - 字段方法调用 例如，对于以下数据结构：\ntype User struct { Name string Email string Age int } func (u User) IsAdult() bool { return u.Age \u0026gt;= 18 } data := struct { User User Title string }{ User: User{ Name: \u0026#34;John Doe\u0026#34;, Email: \u0026#34;john@example.com\u0026#34;, Age: 25, }, Title: \u0026#34;User Profile\u0026#34;, } 可以在模板中这样访问：\n\u0026lt;h1\u0026gt;{{.Title}}\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;Name: {{.User.Name}}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;Email: {{.User.Email}}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;Age: {{.User.Age}}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;Is Adult: {{.User.IsAdult}}\u0026lt;/p\u0026gt; 注释 # 在模板中添加注释：\n{{/* 这是一个注释，不会输出到结果中 */}} 管道操作符 # 管道操作符|类似于Unix管道，将前一个命令的输出作为后一个命令的输入：\n{{ .Name | printf \u0026#34;Name: %s\u0026#34; }} 这相当于printf(\u0026quot;Name: %s\u0026quot;,Name)。\n控制结构 # if-else # {{ if .Condition }} \u0026lt;!-- 当.Condition为真时显示 --\u0026gt; {{ else }} \u0026lt;!-- 当.Condition为假时显示 --\u0026gt; {{ end }} 例如：\n{{ if .User.IsAdult }} \u0026lt;p\u0026gt;User is an adult.\u0026lt;/p\u0026gt; {{ else }} \u0026lt;p\u0026gt;User is not an adult.\u0026lt;/p\u0026gt; {{ end }} with # 也可以使用with来简化嵌套字段的访问：\n{{ with .User }} \u0026lt;p\u0026gt;Name: {{.Name}}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;Email: {{.Email}}\u0026lt;/p\u0026gt; {{ end }} with语句还可以包含条件，只有当值存在（非零）时才执行块：\n{{ with .Error }} \u0026lt;div class=\u0026#34;error\u0026#34;\u0026gt;{{.}}\u0026lt;/div\u0026gt; {{ end }} range # range用于迭代切片、数组、映射或通道：\n{{ range .Items }} \u0026lt;!-- 每个元素都可以通过.访问 --\u0026gt; \u0026lt;p\u0026gt;{{.}}\u0026lt;/p\u0026gt; {{ end }} 如果要访问索引：\n{{ range $index, $element := .Items }} \u0026lt;p\u0026gt;{{$index}}: {{$element}}\u0026lt;/p\u0026gt; {{ end }} 对于映射：\n{{ range $key, $value := .Map }} \u0026lt;p\u0026gt;{{$key}}: {{$value}}\u0026lt;/p\u0026gt; {{ end }} range也可以包含else子句，当集合为空时执行：\n{{ range .Items }} \u0026lt;p\u0026gt;{{.}}\u0026lt;/p\u0026gt; {{ else }} \u0026lt;p\u0026gt;No items found.\u0026lt;/p\u0026gt; {{ end }} 变量与作用域 # 可以使用$符号定义局部变量：\n{{ $name := .Name }} {{ $name }} 变量的作用域限于定义它的块内：\n{{ $title := .Title }} \u0026lt;h1\u0026gt;{{$title}}\u0026lt;/h1\u0026gt; {{ with .User }} {{ $username := .Name }} \u0026lt;p\u0026gt;Username: {{$username}}\u0026lt;/p\u0026gt; \u0026lt;!-- $title仍然可用 --\u0026gt; \u0026lt;p\u0026gt;Page: {{$title}}\u0026lt;/p\u0026gt; {{ end }} \u0026lt;!-- $username在此作用域不可用 --\u0026gt; 内置函数 # Go模板系统提供了许多内置函数来操作数据：\n{{len .Items}} - 返回切片、数组、映射或字符串的长度 {{index .Items 0}} - 访问数组、切片或映射中的元素 {{printf \u0026#34;格式字符串\u0026#34; .Value}} - 格式化输出 {{html .HTML}} - HTML转义（在html/template中自动应用） {{js .Script}} - JavaScript转义 {{urlquery .URL}} - URL查询转义 一些实用的字符串函数：\n{{lower .Title}} - 转换为小写 {{upper .Title}} - 转换为大写 {{trim .Content}} - 删除首尾空白 比较操作 # Go模板支持基本的比较操作：\n{{eq .A .B}} - 等于 {{ne .A .B}} - 不等于 {{lt .A .B}} - 小于 {{le .A .B}} - 小于等于 {{gt .A .B}} - 大于 {{ge .A .B}} - 大于等于 这些可以在if语句中使用：\n{{ if eq .User.Role \u0026#34;admin\u0026#34; }} \u0026lt;p\u0026gt;Welcome, Administrator!\u0026lt;/p\u0026gt; {{ else }} \u0026lt;p\u0026gt;Welcome, User!\u0026lt;/p\u0026gt; {{ end }} 也可以使用and、or和not进行逻辑运算：\n{{ if and (eq .User.Role \u0026#34;admin\u0026#34;) (not .System.Maintenance) }} \u0026lt;p\u0026gt;Administrative actions are available.\u0026lt;/p\u0026gt; {{ end }} 自定义函数 # 除了内置函数，我们还可以定义自定义函数并将其添加到模板中：\npackage main import ( \u0026#34;html/template\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;time\u0026#34; ) func main() { // 创建自定义函数映射 funcMap := template.FuncMap{ \u0026#34;formatDate\u0026#34;: func(t time.Time) string { return t.Format(\u0026#34;2006-01-02\u0026#34;) }, \u0026#34;capitalize\u0026#34;: func(s string) string { return strings.Title(s) }, \u0026#34;add\u0026#34;: func(a, b int) int { return a + b }, } // 创建带自定义函数的模板 tmpl, err := template.New(\u0026#34;page.html\u0026#34;). Funcs(funcMap). ParseFiles(\u0026#34;templates/page.html\u0026#34;) if err != nil { panic(err) } http.HandleFunc(\u0026#34;/\u0026#34;, func(w http.ResponseWriter, r *http.Request) { data := struct { Name string CreatedAt time.Time Count int }{ Name: \u0026#34;example page\u0026#34;, CreatedAt: time.Now(), Count: 5, } tmpl.Execute(w, data) }) http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) } 在模板中使用自定义函数：\n\u0026lt;h1\u0026gt;{{ .Name | capitalize }}\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;Created on: {{ formatDate .CreatedAt }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;Count plus ten: {{ add .Count 10 }}\u0026lt;/p\u0026gt; 模板布局、嵌套和复用 # 在实际Web应用开发中，页面通常共享相同的布局（如页眉、页脚和导航栏）。Go模板系统提供了几种方式来实现代码复用和模板组合。\n简单示例 # base.html（基础布局）：\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;{{ .Title }}\u0026lt;/title\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;/static/css/main.css\u0026#34;\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;header\u0026gt; {{ template \u0026#34;header\u0026#34; . }} \u0026lt;/header\u0026gt; \u0026lt;main\u0026gt; {{ template \u0026#34;content\u0026#34; . }} \u0026lt;/main\u0026gt; \u0026lt;footer\u0026gt; {{ template \u0026#34;footer\u0026#34; . }} \u0026lt;/footer\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; header.html（头部模板）：\n{{ define \u0026#34;header\u0026#34; }} \u0026lt;nav\u0026gt; \u0026lt;ul\u0026gt; \u0026lt;li\u0026gt;\u0026lt;a href=\u0026#34;/\u0026#34;\u0026gt;Home\u0026lt;/a\u0026gt;\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;\u0026lt;a href=\u0026#34;/about\u0026#34;\u0026gt;About\u0026lt;/a\u0026gt;\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;\u0026lt;a href=\u0026#34;/contact\u0026#34;\u0026gt;Contact\u0026lt;/a\u0026gt;\u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; \u0026lt;/nav\u0026gt; {{ end }} footer.html（底部模板）：\n{{ define \u0026#34;footer\u0026#34; }} \u0026lt;p\u0026gt;\u0026amp;copy; {{ .Year }} My Website. All rights reserved.\u0026lt;/p\u0026gt; {{ end }} home.html（首页内容）：\n{{ define \u0026#34;content\u0026#34; }} \u0026lt;h1\u0026gt;Welcome to {{ .Title }}\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;{{ .Message }}\u0026lt;/p\u0026gt; {{ end }} 搭建 HTTP 服务器，渲染模板：\nfunc main() { // 加载所有模板 templates := template.Must(template.ParseFiles( \u0026#34;templates/base.html\u0026#34;, \u0026#34;templates/header.html\u0026#34;, \u0026#34;templates/footer.html\u0026#34;, \u0026#34;templates/home.html\u0026#34;, )) http.HandleFunc(\u0026#34;/\u0026#34;, func(w http.ResponseWriter, r *http.Request) { data := struct { Title string Message string Year int }{ Title: \u0026#34;My Website\u0026#34;, Message: \u0026#34;Welcome to our awesome website!\u0026#34;, Year: time.Now().Year(), } templates.ExecuteTemplate(w, \u0026#34;base.html\u0026#34;, data) }) http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) } define 和 template # define 用于定义一个模板，语法如下：\n{{define \u0026#34;name\u0026#34;}} ... {{end}} template用于引用一个模板，其中，name是要包含的模板的名称，.是传递给被包含模板的数据。如果不需要传递数据，可以使用nil代替。\n{{ template \u0026#34;name\u0026#34; . }} block # block定义一个可覆盖的模板块（Template Block），允许子模板通过同名 {{define \u0026quot;name\u0026quot;}} 覆盖内容。若未被覆盖，则执行块内默认内容。\n例如父模板中定义：\n{{block \u0026#34;content\u0026#34; .}} \u0026lt;h1\u0026gt;Default Content\u0026lt;/h1\u0026gt; {{end}} 子模板可以进行覆盖\n{{define \u0026#34;content\u0026#34;}} \u0026lt;h1\u0026gt;New Content\u0026lt;/h1\u0026gt; {{end}} 性能和组织考虑 # 在使用模板时，有几个重要的考虑因素：\n性能：模板应该在程序启动时解析，而不是在每个请求中解析。 组织：将模板按功能或页面类型组织到不同目录中。 命名约定：使用一致的命名约定，如base.html、partials/header.html等。 错误处理：使用template.Must()简化错误处理，但要注意只在初始化时使用。 ","date":"2025-05-16","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/8b430461/7dccf2f0/","section":"文章","summary":"\u003cp\u003eGo语言的标准库提供了强大的模板系统，分为两个主要包：\u003ccode\u003etext/template\u003c/code\u003e和\u003ccode\u003ehtml/template\u003c/code\u003e。这两个包使用相同的接口和语法，但\u003ccode\u003ehtml/template\u003c/code\u003e包增加了对HTML特定的安全功能，防止跨站脚本攻击（XSS）。在Web开发中，我们主要使用\u003ccode\u003ehtml/template\u003c/code\u003e包。\u003c/p\u003e","title":"3、template","type":"posts"},{"content":" 变量 # Go语言是静态类型语言，因此变量（variable）是有明确类型的，编译器也会检查变量类型的正确性。在数学概念中，变量表示没有固定值且可改变的数。但从计算机系统实现角度来看，变量是一段或多段用来存储数据的内存。\n变量声明 # Go语言的一个重要特性是所有变量都有默认值，也就是在声明变量时，自动对变量对应的内存区域进行初始化操作，称为\u0026quot;零值\u0026quot;。\nGo的零值机制消除了\u0026quot;未初始化变量\u0026quot;的概念，提高了程序安全性，减少了常见错误。\n类型 零值 整数类型 0 浮点类型 0.0 布尔类型 false 字符串 \u0026quot;\u0026quot; (空字符串) 指针、函数、接口、切片、管道、映射 nil 变量一旦声明，就必须使用，不然编译报错！\nGo虽然支持自动类型推导，但仍然是强类型语言！\nvar声明 # // var 是声明变量的关键字，name 是变量名，type 是变量的类型 var name type // 批量声明并赋值 var a,b,c int = 10,20,30 // 自动类型推导并批量声明变量然后赋值 var a,b,c = 10,20.99,true // 批量声明一组数据 var ( a int b string c []float32 ) // 自动类型推导并批量声明一组变量然后赋值 var ( d = 10 e = 20 ) 简短模式 # // 此时就是声明变量并赋值，而且自动类型推导 a := 10 func main(){ s := 32 a,b := 11,\u0026#34;world\u0026#34; } 简短模式（short variable declaration）有以下限制：\n定义变量，同时显式初始化。 :和=中间不能有空格 左值必须最少有一个没有声明过的变量名 不能提供数据类型。 只能用在函数内部，也就是只能声明局部变量。 因为简洁和灵活的特点，简短变量声明被广泛用于大部分的局部变量的声明和初始化。\nvar 形式的声明语句往往是用于需要显式指定变量类型地方，或者因为变量稍后会被重新赋值而初始值无关紧要的地方。\n匿名变量 # 匿名变量的特点是一个下划线_，_本身就是一个特殊的标识符，被称为空白标识符。\n可以像其他标识符那样用于变量的声明或赋值（任何类型都可以赋值给它），但任何赋给这个标识符的值都将被抛弃，因此这些值不能在后续的代码中使用，也不可以使用这个标识符作为变量对其它变量进行赋值或运算。\n匿名变量不占用内存空间，不会分配内存。匿名变量与匿名变量之间也不会因为多次声明而无法使用。\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { //只需要x坐标，所以y坐标就可以使用匿名变量来接收 x, _ := getPoint() fmt.Println(x) } func getPoint() (int, int) { return 10, 200 } 作用域和声明周期 # 函数外定义的变量称为全局变量 在函数体外声明的变量称之为全局变量 全局变量声明必须以 var 关键字开头 首字母小写的全局变量只对当前包可见，类似于protected 首字母大写的全局变量对所有包可见，类似于public 函数内定义的变量称为局部变量 函数的参数和返回值变量都属于局部变量 局部变量不是一直存在的，它只在定义它的函数被调用后存在，函数调用结束后这个局部变量就会被销毁。 生命周期是动态的，从创建这个变量的声明语句开始，到这个变量不再被引用为止 函数定义中的变量称为形式参数 在定义函数时函数名后面括号中的变量叫做形式参数（简称形参）。形式参数只在函数调用时才会生效，函数调用结束后就会被销毁，在函数未被调用时，函数的形参并不占用实际的存储单元，也没有实际值。 形式参数会作为函数的局部变量来使用。 常量 # Go语言中的常量使用关键字const定义，用于存储不会改变的数据，常量是在编译时被创建的，即使定义在函数内部也是如此。由于编译时的限制，定义常量的表达式必须为能被编译器求值的常量表达式。\n注意：常量的数据类型只可以是布尔、数字（整数、浮点和复数）、字符串、枚举\n常量声明后，并不要求必须使用\nconst a [type] = value const a,b,c [type] = value1,value2,value3 //常量也支持自动类型推导 const a = value //常量也支持批量声明 const a,b,c = value1,value2,value3 const ( a = 10 b int = 20 c = \u0026#34;hello\u0026#34; ) // 批量声明相同值常量，此时a、b、c的值都是10 const ( a = 10 b c ) 注意：常量的值必须是能够在编译时就能够确定的，可以在其赋值表达式中涉及计算过程，但是所有用于计算的值必须在编译期间就能获得。\nconst a int = 10 + 2 //正确 const a int = getValue() //错误 iota 常量生成器 # 常量声明可以使用 iota 常量生成器初始化（作为枚举），它用于生成一组以相似规则初始化的常量，但是不用每行都写一遍初始化表达式。在每一个const关键字出现时，都会被重新重置为0，然后每出现一个常量，iota所代表的数值会自动增加1。\nconst ( a int = iota //0 b //1 c //2 ) 自定义枚举类型 # 可以定义类似java的枚举类型\n//将 int 定义为 Weekday 类型 type Weekday int const ( Sunday Weekday = iota //0 Monday //1 Tuesday //2 Wednesday //3 Thursday //4 Friday //5 Saturday //6 ) 修改起始值 # const ( a int = iota + 10 //10 b //11 c //12 ) 跳过中间值 # 使用下划线进行跳过\nconst ( a int = iota //0 _ _ b //3 c //4 ) 修改中间值 # const ( a int = iota //0 b int = 35 //35 c int = iota //2 ) 数据类型 # 布尔型：bool 布尔型的值只可以是常量 true 或者 false，不可参与数值运算，也无法进行类型转换。 固定占用1字节。 数字类型：int、uint、int8、int16、int32、int64、uint8、uint16、uint32、uint64、byte（uint8）、rune(int32)、float32、float64、complex128、complex256 整型 int 和浮点型 float32、float64，Go 语言支持整型和浮点型数字，并且支持复数，其中位的运算采用补码。 其中byte代表一个字节，也是ASCII的一个字符；rune代表Unicode的一个字符 字符串类型：string 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。 派生类型 指针类型：*int 数组类型：[10]int 结构体类型：struct Channel 类型：chan int 函数类型：func 切片类型：[]int 接口类型：interface Map 类型：map 数字类型 # 1、整形 # Go语言的数值类型分为以下几种：整数、浮点数、复数，其中每一种都包含了不同大小的数值类型，例如有符号整数包含 int8、int16、int32、int64 等，每种数值类型都决定了对应的大小范围和是否支持正负符号。\nmath包提供了方法math.Max[Type]和math.Min[Type]查看各类型数据的最大最小值\n例如int8代表该类型变量占8Bit（位），也就是1Byte（字节），可以使用unsafe.Sizeof(variable)查看变量所占Byte（字节）大小\n有符号 说明 无符号 说明 占用字节数 int8 有符号 8 位整型 (-128 到 127) uint8 无符号 8 位整型 (0 到 255) 1 int16 有符号 16 位整型 (-32768 到 32767) uint16 无符号 16 位整型 (0 到 65535) 2 int32 有符号 32 位整型 (-2147483648 到 2147483647) uint32 无符号 32 位整型 (0 到 4294967295) 4 int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807) uint64 无符号 64 位整型 (0 到 18446744073709551615) 8 int 有符号整型，由cpu决定，长度32bit 或 64bit uint 无符号整型，由cpu决定，长度32bit 或 64bit 4/8 大多数情况下，我们只需要 int 一种整型即可，它可以用于循环计数器（for 循环中控制循环次数的变量）、数组和切片的索引，以及任何通用目的的整型运算符，通常 int 类型的处理速度也是最快的。\n除此之外，go还支持一些其他整形\n类型 说明 byte 和 uint8 是等价类型type byte = uint8，byte 类型一般用于强调数值是一个原始的数据（一个字节，也可以代表ASCII 码的一个字符）而不是一个小的整数 rune 和 int32 类型是等价的type rune = int32，通常用于表示一个 Unicode 码点（字符） uintptr 没有指定具体的 bit 大小但是足以容纳指针，uintptr 类型只有在底层编程时才需要，特别是Go语言和C语言函数库或操作系统接口相交互的地方 字面量 # func main() { // 十进制 decimal := 42 // 二进制 binary := 0b101010 // 从Go 1.13开始支持 // 八进制 octal := 0o52 // 从Go 1.13开始推荐使用0o前缀 oldOctal := 052 // 传统写法，不推荐 // 十六进制 hex := 0x2A fmt.Println(decimal, binary, octal, oldOctal, hex) // 都会打印出42 } 2、浮点型 # Go语言提供了两种精度，它们的算术规范由 IEEE754 浮点数国际标准定义，该浮点数规范被所有现代的 CPU 支持。\n类型 描述 精度 float32 32位浮点数 约7位十进制精度 float64 64位浮点数 约15位十进制精度 这些浮点数类型的取值范围可以从很微小到很巨大。浮点数取值范围的极限值可以在 math 包中找到：\n常量 math.MaxFloat32 表示 float32 能取到的最大数值，大约是 3.4e38； 常量 math.MaxFloat64 表示 float64 能取到的最大数值，大约是 1.8e308； float32 和 float64 能表示的最小值分别为 1.4e-45 和4.9e-324。 如果是64位操作系统，则浮点数自动类型推导默认数据类型为float64。\n字面量 # func main() { // 十进制表示 pi := 3.14159 // 科学计数法 avogadro := 6.022e23 // 6.022 × 10²³ plank := 6.626e-34 // 6.626 × 10⁻³⁴ fmt.Println(pi, avogadro, plank) } float精度丢失的问题 # a := 1129.6 fmt.Println(a * 100) // 112959.99999999999 m1 := 8.2 m2 := 3.8 fmt.Println(m1 - m2) // 4.3999999999999995 使用第三方包https://github.com/shopspring/decimal进行解决\ngo get -u github.com/shopspring/decimal a := decimal.NewFromFloat(1129.6) i := decimal.NewFromInt(100) r1 := a.Mul(i) fmt.Println(r1) // 112960 m1 := decimal.NewFromFloat(8.2) m2 := decimal.NewFromFloat(3.8) r2 := m1.Sub(m2) fmt.Println(r2) // 4.4 a.Add(b)：加法，a + b a.Sub(b)：减法，a - b a.Mul(b)：乘法，a * b a.Div(b)：除法，a / b a.Cmp(b)比较大小，a \u0026gt; b返回值\u0026gt; 0；a = b返回值= 0；a \u0026lt; b返回值\u0026lt; 0 3、复数 # 类型 描述 complex64 由两个float32组成的复数 complex128 由两个float64组成的复数 其中 complex128 为复数的默认类型。\n复数的值由三部分组成 RE + IMi，其中RE是实数部分，IM是虚数部分，RE 和IM均为 float 类型，而最后的i是虚数单位。\n声明格式：\n/* name 为复数的变量名 complex128 为复数的类型 complex 为Go语言的内置函数用于为复数赋值 x、y 分别表示构成该复数的两个 float64 类型的数值 x 为实部，y 为虚部 */ var name complex128 = complex(x, y) //获取实部 real(name) //获取虚部 imag(name) 字符串类型 # 一个字符串是一个不可改变的字节序列，字符串可以包含任意的数据，但是通常是用来包含可读的文本，字符串是 UTF-8 字符的一个序列（当字符为 ASCII 码表上的字符时则占用 1 个字节，其它字符根据需要占用 2-4 个字节）。\nUTF-8 是一种被广泛使用的编码格式，由于该编码对占用字节长度的不定性，在Go语言中字符串也可能根据需要占用 1 至 4 个字节\n字符串是一种值类型，且值不可变，即创建某个文本后将无法再次修改这个文本的内容，在golang中，字符串是字节的定长切片（byte slice），如果真要修改字符串中的字符，将 string 转为[]byte修改后，再转为 string 即可。\n字节、字符长度 # 字符串所占的字节长度可以通过函数 len() 来获取\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { x := \u0026#34;abcde\u0026#34; fmt.Println(len(x)) //5 fmt.Println(x[0]) //97 } 注意：方法len()计算的是字节长度，由于ASCII码中1字符等于1字节，可以等价字符长度。如果是Unicode字符，比如字符串\u0026quot;你好\u0026quot;，使用len()计算所占用的字节长度就是6，而不是2。\n因为在Go里面，汉字使用的是UTF-8编码，每个汉字占用3个字节，每个数字、字母、符号占用1个字节。\n如果需要计算Unicode字符长度，那么就需要使用utf8包下的RuneCountInString()函数\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;unicode/utf8\u0026#34; ) func main() { x := \u0026#34;你好\u0026#34; fmt.Println(len(x)) //6 fmt.Println(utf8.RuneCountInString(x)) //2 } 也可以将字符串转为rune数组\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { a := \u0026#34;你好go\u0026#34; runes := []rune(a) fmt.Println(len(runes)) } 多行字符串 # package main import ( \u0026#34;fmt\u0026#34; ) func main() { //使用反引号进行定义 x := `abcdefg hijklmn opqrst uvwxyz ` fmt.Println(x) } 遍历字符 # 对于ASCII编码，直接使用下标进行循环遍历\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { x := \u0026#34;abcde\u0026#34; for i := 0; i \u0026lt; len(x); i++ { fmt.Printf(\u0026#34;%c\\n\u0026#34;, x[i]) } } 对于Unicode编码，需要使用for...range...，或者转为rune数组后遍历\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { x := \u0026#34;你好\u0026#34; for _, c := range x { fmt.Printf(\u0026#34;%c\\n\u0026#34;, c) } } 修改字符串 # 注意：字符串是不可变的字符序列，即使修改后，也是重新分配了内存空间，生成了新的字符串！\ns := \u0026#34;big\u0026#34; sb := []byte(s) sb[0] = \u0026#39;p\u0026#39; s = string(sb) fmt.Println(s) z := \u0026#34;白萝卜\u0026#34; zr := []rune(z) zr[0] = \u0026#39;红\u0026#39; z = string(zr) fmt.Println(z) 字符串拼接 # Go语言拼接字符串有五种方法，分别是如下：\n使用+号拼接，要求+号两边都是string类型\na := \u0026#34;hello\u0026#34; b := \u0026#34; world\u0026#34; c := a + b 使用fmt.Sprint()拼接，可以实现任意数据类型的拼接\na := \u0026#34;hello\u0026#34; b := 1234 c := fmt.Sprint(a, b) 使用strings.Join函数拼接\nstrings.Join([]string{\u0026#34;hello\u0026#34;, \u0026#34;world\u0026#34;}, \u0026#34;-\u0026#34;) 使用bytes.Buffer，性能比上面的方式好\na := \u0026#34;hello\u0026#34; b := \u0026#34;world\u0026#34; var bt bytes.Buffer bt.WriteString(a) bt.WriteString(b) c := bt.String() 使用strings.Builder，性能比bytes.Buffer还要好，官方推荐该方法\na := \u0026#34;hello\u0026#34; b := \u0026#34;world\u0026#34; var sb strings.Builder sb.WriteString(a) sb.WriteString(b) c := sb.String() 字符类型 # Go语言中字符的声明方式如下：\nvar c1 byte = \u0026#39;x\u0026#39; var c2 rune = \u0026#39;你\u0026#39; c3 := \u0026#39;好\u0026#39; fmt.Printf(\u0026#34;%c,%c,%c\\n\u0026#34;, c1, c2, c3) Go语言的字符有以下两种：\ntype byte = uint8：代表了 ASCII 码的一个字符。 type rune = int32：代表一个 UTF-8 字符，当需要处理中文、日文或者其他复合字符时，则需要用到 rune 类型。 //十进制字符 var ch1 byte = 65 fmt.Printf(\u0026#34;%c\\n\u0026#34;, ch1) //A //两位十六进制，使用单引号，并且加\\x前缀 var ch2 byte = \u0026#39;\\x41\u0026#39; fmt.Printf(\u0026#34;%c\\n\u0026#34;, ch2) //A //三位八进制，使用单引号，并且加\\前缀 var ch3 byte = \u0026#39;\\101\u0026#39; fmt.Printf(\u0026#34;%c\\n\u0026#34;, ch3) //A Unicode（Utf8）编码，在文档中，一般使用格式 U+hhhh 来表示，其中 h 表示一个 16 进制数\n在书写 Unicode 字符时，需要在 16 进制数之前加上前缀\\u或者\\U。因为 Unicode 至少占用 2 个字节，所以我们使用 int16 或者 int 类型来表示。如果需要使用到 4 字节，则使用\\u前缀，如果需要使用到 8 个字节，则使用\\U前缀。\nvar ch1 int16 = \u0026#39;\\u4f60\u0026#39; //你 var ch2 rune = \u0026#39;\\u597d\u0026#39; //好 fmt.Printf(\u0026#34;%c%c\\n\u0026#34;, ch1, ch2) 基本数据类型转换 # 在必要以及可行的情况下，一个类型的值可以被转换成另一种类型的值。由于Go语言不存在隐式类型转换，因此所有的类型转换都必须显式的声明：\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { var a int = 10 var b byte = byte(a) fmt.Println(b) // 10 s := \u0026#34;hello\u0026#34; sb := []byte(s) fmt.Println(sb) // [104 101 108 108 111] } 注意： 类型转换只能在定义正确的情况下转换成功，例如从一个取值范围较小的类型转换到一个取值范围较大的类型（将 int16 转换为 int32）。当从一个取值范围较大的类型转换到取值范围较小的类型时（将 int32 转换为 int16 或将 float32 转换为 int），会发生精度丢失（截断）的情况。\n注意： Go中没有自动类型提升，例如int和float相加，需要先把int类型显式的转换为float类型，然后再相加。\n类型别名和类型定义 # //类型定义 type NewType Type //类型别名 type TypeAlias = Type 区别\n//类型定义 type NewInt int //类型别名 type MyInt = int func main() { var a NewInt var b MyInt fmt.Printf(\u0026#34;type of a:%T\\n\u0026#34;, a) // type of a:main.NewInt fmt.Printf(\u0026#34;type of b:%T\\n\u0026#34;, b) // type of b:int } a的类型是main.NewInt，表示main包下定义的NewInt类型。\nb的类型是int。MyInt类型只会在代码中存在，编译完成时并不会有MyInt类型。\n总结： 类型定义type NewType Type是定义了一个新的类型；类型别名type TypeAlias = Type只是给原来的类型起了个别名，在编译时还是以原来的类型为准\n值类型和引用类型 # 值类型：基本数据类型，int，float，bool，string，数组，struct 特点：变量直接存储值，内存通常在栈中分配，栈在函数调用完会被释放 引用类型：指针，slice，map，chan等都是引用类型 特点：变量存储的是一个地址，这个地址存储最终的值。内存通常在堆上分配，通过GC回收。 运算符 # 算术运算符 # 运算符 描述 实例(A=10;B=20) + 相加 A + B 输出结果 30 - 相减 A - B 输出结果 -10 * 相乘 A * B 输出结果 200 / 相除 B / A 输出结果 2 % 求余 B % A 输出结果 0 ++ 自增 A++ 输出结果 11 \u0026ndash; 自减 A-- 输出结果 9 注意： golang中的++和--，只可以写在变量后面；并且是作为单独语句出现而不是表达式，所以不可以运算后赋值\n关系运算符 # 运算符 描述 实例(A=10;B=20) == 检查两个值是否相等，如果相等返回 True 否则返回 False。 A == B为 False != 检查两个值是否不相等，如果不相等返回 True 否则返回 False。 A != B 为 True \u0026gt; 检查左边值是否大于右边值，如果是返回 True 否则返回 False。 A \u0026gt; B为 False \u0026lt; 检查左边值是否小于右边值，如果是返回 True 否则返回 False。 A \u0026lt; B为 True \u0026gt;= 检查左边值是否大于等于右边值，如果是返回 True 否则返回 False。 A \u0026gt;= B为 False \u0026lt;= 检查左边值是否小于等于右边值，如果是返回 True 否则返回 False。 A \u0026lt;= B 为 True 逻辑运算符 # 运算符 描述 实例(A=true;B=false) \u0026amp;\u0026amp; 逻辑 AND 运算符。 如果两边的操作数都是 True，则条件 True，否则为 False。 A \u0026amp;\u0026amp; B 为 False || 逻辑 OR 运算符。 如果两边的操作数有一个 True，则条件 True，否则为 False。 `A ! 逻辑 NOT 运算符。 如果条件为 True，则逻辑 NOT 条件 False，否则为 True。 !B为 True 赋值运算符 # 运算符 描述 实例 = 简单的赋值运算符，将一个表达式的值赋给一个左值 C = A + B 作用为 A + B 表达式结果赋值给 C += 相加后再赋值 C += A 等效于 C = C + A -= 相减后再赋值 C -= A 等效于 C = C - A *= 相乘后再赋值 C *= A 等效于 C = C * A /= 相除后再赋值 C /= A 等效于 C = C / A %= 求余后再赋值 C %= A 等效于 C = C % A \u0026laquo;= 左移后赋值 C \u0026lt;\u0026lt;= 2 等效于 C = C \u0026lt;\u0026lt; 2 \u0026raquo;= 右移后赋值 C \u0026gt;\u0026gt;= 2 等效于 C = C \u0026gt;\u0026gt; 2 \u0026amp;= 按位与后赋值 C \u0026amp;= 2 等效于 C = C \u0026amp; 2 ^= 按位异或后赋值 C ^= 2 等效于 C = C ^ 2 |= 按位或后赋值 `C 位运算符 # 位运算符用于整数在内存中的二进制位进行操作\np q p \u0026amp; q p | q p ^ q 0 0 0 0 0 0 1 0 1 1 1 1 1 1 0 1 0 0 1 1 运算符 描述 实例(A=60;B=13) \u0026amp; 按位与。 其功能是参与运算的两数各对应的二进位相与。 A \u0026amp; B结果为 12, 二进制为 0000 1100 | 按位或。 其功能是参与运算的两数各对应的二进位相或 A | B结果为 61, 二进制为 0011 1101 ^ 按位异或。 其功能是参与运算的两数各对应的二进位相异或，当两对应的二进位相异时，结果为1。 A ^ B结果为 49, 二进制为 0011 0001 \u0026laquo; 左移运算符。左移n位就是乘以2的n次方。 其功能把\u0026lt;\u0026lt;左边的运算数的各二进位全部左移若干位，由\u0026lt;\u0026lt;右边的数指定移动的位数，高位丢弃，低位补0。 A \u0026lt;\u0026lt; 2 结果为 240 ，二进制为 1111 0000 \u0026raquo; 右移运算符。右移n位就是除以2的n次方。 其功能是把\u0026gt;\u0026gt;左边的运算数的各二进位全部右移若干位，\u0026gt;\u0026gt;右边的数指定移动的位数。 A \u0026gt;\u0026gt; 2 结果为 15 ，二进制为 0000 1111 其他运算符 # 运算符 描述 实例 \u0026amp; 返回变量存储地址 \u0026amp;a：获取变量的实际地址。 * 指针变量。 *a：对指针变量a，解引用 nil # 在Go语言中，布尔类型的零值（初始值）为 false，数值类型的零值为 0，字符串类型的零值为空字符串\u0026quot;\u0026quot;。而指针、切片、映射、通道、函数和接口的零值则是nil。\n不同类型中的表现 # 指针：nil指针不指向任何内存地址。 切片：nil切片的长度和容量均为0，没有底层数组。 映射：nil映射没有键值对，读取操作返回零值，写入操作会导致panic。 通道：nil通道无法用于发送或接收操作。 函数：nil函数调用会导致panic。 接口：nil接口不包含任何具体值，动态类型和值均为nil。 特性 # nil 是 map、slice、pointer、channel、func、interface 的零值 nil 标识符是不能比较的：nil == nil是错误的 nil 不是关键字或保留字：标识符可以用nil，但是最好不要这么做 nil 没有默认类型：无法使用fmt.Printf(\u0026quot;%T\u0026quot;,nil)，来获取类型 不同类型 nil 的指针是一样的：所有类型为nil时，指针都是0x0，可以使用fmt.Printf(\u0026quot;%p\u0026quot;,variables)查看指针 面试题 # 类型比较问题 # 比较只限于同类型，此处说的可比较是使用==进行比较 基本类型（int、float、string、bool、complex）可以比较 接口，如果实际值的类型不相同，或者不可比较，就会引起panic；除此之外可比较 struct，字段个数、类型、顺序相同；字段类型是可比较的类型，那么就可以比较 数组，数组长度相同、类型相同、类型可比较，就可以比较 指针，可以比较 slice、map，不可比较 channel，只要通道类元素类型相同，就可以比较 对于不可比较的类型，最常用的是reflect.DeepEqual()函数 ","date":"2025-05-15","externalUrl":null,"permalink":"/posts/2d3150b2/02a0817f/96cd694a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e变量 \n    \u003cdiv id=\"变量\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8f%98%e9%87%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eGo语言是静态类型语言，因此变量（variable）是有明确类型的，编译器也会检查变量类型的正确性。在数学概念中，变量表示没有固定值且可改变的数。但从计算机系统实现角度来看，变量是一段或多段用来存储数据的内存。\u003c/p\u003e","title":"3、变量和数据类型","type":"posts"},{"content":"观察者模式（Observer）定义了对象之间的一对多依赖关系，当一个对象状态改变时，所有依赖它的对象都会得到通知。在Go中，可以使用接口和切片实现观察者模式：\n定义 # package observer import \u0026#34;fmt\u0026#34; // 观察者接口 type Observer interface { Update(subject *Subject) } // 主题结构体 type Subject struct { observers []Observer state int } // 注册观察者 func (s *Subject) Attach(observer Observer) { s.observers = append(s.observers, observer) } // 移除观察者 func (s *Subject) Detach(observer Observer) { for i, obs := range s.observers { if obs == observer { s.observers = append(s.observers[:i], s.observers[i+1:]...) break } } } // 通知所有观察者 func (s *Subject) Notify() { for _, observer := range s.observers { observer.Update(s) } } // 改变状态 func (s *Subject) SetState(state int) { s.state = state fmt.Printf(\u0026#34;主题状态改变为: %d\\n\u0026#34;, state) s.Notify() } // 获取状态 func (s *Subject) GetState() int { return s.state } // 具体观察者A type ConcreteObserverA struct { ID string } func (o ConcreteObserverA) Update(subject *Subject) { fmt.Printf(\u0026#34;观察者A(%s)收到更新: 状态 = %d\\n\u0026#34;, o.ID, subject.GetState()) } // 具体观察者B type ConcreteObserverB struct { ID string } func (o ConcreteObserverB) Update(subject *Subject) { fmt.Printf(\u0026#34;观察者B(%s)收到更新: 状态 = %d\\n\u0026#34;, o.ID, subject.GetState()) } 使用 # // main.go package main import ( \u0026#34;myapp/observer\u0026#34; ) func main() { // 创建主题 subject := \u0026amp;observer.Subject{} // 创建观察者 observerA1 := observer.ConcreteObserverA{ID: \u0026#34;A1\u0026#34;} observerA2 := observer.ConcreteObserverA{ID: \u0026#34;A2\u0026#34;} observerB1 := observer.ConcreteObserverB{ID: \u0026#34;B1\u0026#34;} // 注册观察者 subject.Attach(observerA1) subject.Attach(observerA2) subject.Attach(observerB1) // 改变状态，触发通知 subject.SetState(1) // 取消注册一个观察者 subject.Detach(observerA1) // 再次改变状态 subject.SetState(2) } ","date":"2025-05-14","externalUrl":null,"permalink":"/posts/2d3150b2/6742e0c3/ffd954dd/","section":"文章","summary":"\u003cp\u003e观察者模式（Observer）定义了对象之间的一对多依赖关系，当一个对象状态改变时，所有依赖它的对象都会得到通知。在Go中，可以使用接口和切片实现观察者模式：\u003c/p\u003e","title":"3、观察者模式","type":"posts"},{"content":" JDK # 卸载 # # 卸载Java浏览器插件 sudo rm -rf /Library/Internet\\ Plug-Ins/JavaAppletPlugin.plugin # 卸载Java控制面板首选项 sudo rm -rf /Library/PreferencePanes/JavaControlPanel.prefPane # 根据自己的版本卸载 sudo rm -rf /Library/Java/JavaVirtualMachines/\u0026lt;jdk-version\u0026gt;.jdk 安装 # 下载Oracle JDK或Zulu OpenJDK（https://www.azul.com/downloads/?package=jdk#zulu）的dmg安装文件\n安装完成后，默认安装位置在/Library/Java/JavaVirtualMachines/目录下\n在.bash_profile文件中，配置环境变量\nexport JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:. export PATH=$JAVA_HOME/bin:$PATH BetterDisplay # 用2K显示器连接Mac时，糟糕的显示效果一定会让你崩溃。原生1440P的分辨率字体太小，使用720P HiDPI又特别大，可以使用BetterDisplay这个工具，拓展支持HiDPI的分辨率\n1、安装BetterDisplay-v1.4.15.dmg\n2、进入设置\n3、找到Edit the default system configuration of this display，勾选。然后再找到Add custom scled resolution，勾选。\n输入需要的HiDPI分辨率，如1920X1080，记得点击后面的加号！2K显示器（16:9）推荐1920X1080和2048X1152。可根据显示器分辨率，设置HiDPI分辨率，如2560X1440缩放1.25倍，就变成了2048X1152。\n4、Apply，重启电脑\ntree # tree命令可以以树形结构显示文件目录结构，它非常适合于我们给别人介绍我们的文件目录的组成框架，同时该命令使用适当的参数也可以将命令结果输出到文本文件中。\nbrew install tree tree -I node_modules \u0026gt; ~/Desktop/tree.txt #忽略node_mpdules文件并将目录树存放到 tree.txt文件中 常用命令 # -a 显示所有文件和目录。 -A 使用ASNI绘图字符显示树状图而非以ASCII字符组合。 -C 在文件和目录清单加上色彩，便于区分各种类型。 -d 显示目录名称而非内容。 -D 列出文件或目录的更改时间。 -f 在每个文件或目录之前，显示完整的相对路径名称。 -F 在执行文件，目录，Socket，符号连接，管道名称名称，各自加上\u0026#34;*\u0026#34;,\u0026#34;/\u0026#34;,\u0026#34;=\u0026#34;,\u0026#34;@\u0026#34;,\u0026#34;|\u0026#34;号。 -g 列出文件或目录的所属群组名称，没有对应的名称时，则显示群组识别码。 -i 不以阶梯状列出文件或目录名称。 -I 不显示符合范本样式的文件或目录名称。 -l 如遇到性质为符号连接的目录，直接列出该连接所指向的原始目录。 -n 不在文件和目录清单加上色彩。 -N 直接列出文件和目录名称，包括控制字符。 -p 列出权限标示。 -P 只显示符合范本样式的文件或目录名称。 -q 用\u0026#34;?\u0026#34;号取代控制字符，列出文件和目录名称。 -s 列出文件或目录大小。 -t 用文件和目录的更改时间排序。 -u 列出文件或目录的拥有者名称，没有对应的名称时，则显示用户识别码。 -x 将范围局限在现行的文件系统中，若指定目录下的某些子目录，其存放于另一个文件系统上，则将该子目录予以排除在寻找范围外。 cloc # Cloc（Count Lines of Code）是一款使用Perl语言开发的开源代码统计工具，支持多平台使用、多语言识别，能够计算指定目标文件或文件夹中的文件数（files）、空白行数（blank）、注释行数（comment）和代码行数（code）。\nbrew install cloc cloc [project_path] --exclude-dir=.idea,target #统计代码行数，排除指定目录 ","date":"2025-05-12","externalUrl":null,"permalink":"/posts/26654e7b/5a80d994/96432800/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJDK \n    \u003cdiv id=\"jdk\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jdk\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e卸载 \n    \u003cdiv id=\"卸载\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8d%b8%e8%bd%bd\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 卸载Java浏览器插件\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo rm -rf /Library/Internet\u003cspan class=\"se\"\u003e\\ \u003c/span\u003ePlug-Ins/JavaAppletPlugin.plugin\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 卸载Java控制面板首选项\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo rm -rf /Library/PreferencePanes/JavaControlPanel.prefPane\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 根据自己的版本卸载\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo rm -rf /Library/Java/JavaVirtualMachines/\u0026lt;jdk-version\u0026gt;.jdk\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e安装 \n    \u003cdiv id=\"安装\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%89%e8%a3%85\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e下载\u003ccode\u003eOracle JDK\u003c/code\u003e或\u003ccode\u003eZulu OpenJDK\u003c/code\u003e（https://www.azul.com/downloads/?package=jdk#zulu）的\u003ccode\u003edmg\u003c/code\u003e安装文件\u003c/p\u003e","title":"3、常用软件","type":"posts"},{"content":" 简介 # Node.js 的 Stream 是一种处理流式数据的抽象接口，广泛应用于文件操作、网络通信等场景。\n流式数据处理的一个主要优点是可以在数据传输过程中就开始处理数据，而不需要等待整个数据加载完毕，这使得 Node.js 能够高效地处理大量数据，而不会占用过多的内存。\nStream 是一个抽象接口，Node 中有很多对象实现了这个接口。例如，对http 服务器发起请求的 request 对象就是一个 Stream，还有stdout（标准输出）。\nNode.js，Stream 有四种流类型：\nReadable - 可读操作。 Writable - 可写操作。 Duplex - 可读可写操作. Transform - 操作被写入数据，然后读出结果。 所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有：\ndata - 当有数据可读时触发。 end - 没有更多的数据可读时触发。 error - 在接收和写入过程中发生错误时触发。 finish - 所有数据已被写入到底层系统时触发。 从流中读取数据 # var fs = require(\u0026#34;fs\u0026#34;); var data = \u0026#39;\u0026#39;; // 创建可读流 var readerStream = fs.createReadStream(\u0026#39;input.txt\u0026#39;); // 设置编码为 utf8。 readerStream.setEncoding(\u0026#39;UTF8\u0026#39;); // 处理流事件 --\u0026gt; data, end, and error readerStream.on(\u0026#39;data\u0026#39;, function(chunk) { data += chunk; }); readerStream.on(\u0026#39;end\u0026#39;,function(){ console.log(data); }); readerStream.on(\u0026#39;error\u0026#39;, function(err){ console.log(err.stack); }); console.log(\u0026#34;程序执行完毕\u0026#34;); 流的类型 # 写入流 # var fs = require(\u0026#34;fs\u0026#34;); var data = \u0026#39;hello world\u0026#39;; // 创建一个可以写入的流，写入到文件 output.txt 中 var writerStream = fs.createWriteStream(\u0026#39;output.txt\u0026#39;); // 使用 utf8 编码写入数据 writerStream.write(data,\u0026#39;UTF8\u0026#39;); // 标记文件末尾 writerStream.end(); // 处理流事件 --\u0026gt; finish、error writerStream.on(\u0026#39;finish\u0026#39;, function() { console.log(\u0026#34;写入完成。\u0026#34;); }); writerStream.on(\u0026#39;error\u0026#39;, function(err){ console.log(err.stack); }); console.log(\u0026#34;程序执行完毕\u0026#34;); 双工流（Duplex） # 双工流同时具有可读和可写的能力。\n一个典型的双工流是 TCP 套接字。\nconst net = require(\u0026#39;net\u0026#39;); // 创建一个 TCP 服务器 const server = net.createServer((socket) =\u0026gt; { console.log(\u0026#39;Client connected.\u0026#39;); // 读取客户端数据 socket.on(\u0026#39;data\u0026#39;, (data) =\u0026gt; { console.log(\u0026#39;Received data:\u0026#39;, data.toString()); }); // 向客户端发送数据 socket.write(\u0026#39;Hello, Client!\\n\u0026#39;); // 监听关闭事件 socket.on(\u0026#39;end\u0026#39;, () =\u0026gt; { console.log(\u0026#39;Client disconnected.\u0026#39;); }); }); server.listen(3000, () =\u0026gt; { console.log(\u0026#39;Server listening on port 3000.\u0026#39;); }); 转换流（Transform） # 转换流是一种特殊的双工流，可以修改或转换数据。常见的转换流包括压缩和解压缩流。\nconst zlib = require(\u0026#39;zlib\u0026#39;); const fs = require(\u0026#39;fs\u0026#39;); // 创建一个可读流 const readableStream = fs.createReadStream(\u0026#39;example.txt\u0026#39;); // 创建一个转换流（压缩） const gzip = zlib.createGzip(); // 创建一个可写流 const writableStream = fs.createWriteStream(\u0026#39;example.txt.gz\u0026#39;); // 将可读流管道到转换流，再管道到可写流 readableStream.pipe(gzip).pipe(writableStream); // 监听完成事件 writableStream.on(\u0026#39;finish\u0026#39;, () =\u0026gt; { console.log(\u0026#39;File compressed successfully.\u0026#39;); }); 管道流 # 管道提供了一个输出流到输入流的机制。\n通常我们用于从一个流中获取数据并将数据传递到另外一个流中。\nvar fs = require(\u0026#34;fs\u0026#34;); // 创建一个可读流 var readerStream = fs.createReadStream(\u0026#39;input.txt\u0026#39;); // 创建一个可写流 var writerStream = fs.createWriteStream(\u0026#39;output.txt\u0026#39;); // 管道读写操作 // 读取 input.txt 文件内容，并将内容写入到 output.txt 文件中 readerStream.pipe(writerStream); console.log(\u0026#34;程序执行完毕\u0026#34;); 链式流 # 链式是通过连接输出流到另外一个流并创建多个流操作链的机制。\n链式流一般用于管道操作。\n接下来我们就是用管道和链式来压缩和解压文件。\nvar fs = require(\u0026#34;fs\u0026#34;); var zlib = require(\u0026#39;zlib\u0026#39;); // 压缩 input.txt 文件为 input.txt.gz fs.createReadStream(\u0026#39;input.txt\u0026#39;) .pipe(zlib.createGzip()) .pipe(fs.createWriteStream(\u0026#39;input.txt.gz\u0026#39;)); console.log(\u0026#34;文件压缩完成。\u0026#34;); 暂停和恢复 # 可读流可以暂停和恢复数据的读取。\nconst fs = require(\u0026#39;fs\u0026#39;); const readableStream = fs.createReadStream(\u0026#39;example.txt\u0026#39;, \u0026#39;utf8\u0026#39;); readableStream.on(\u0026#39;data\u0026#39;, (chunk) =\u0026gt; { console.log(\u0026#39;Received chunk:\u0026#39;, chunk); readableStream.pause(); // 暂停读取 setTimeout(() =\u0026gt; { readableStream.resume(); // 恢复读取 }, 1000); }); 销毁 # 可以销毁流，释放资源。\nconst fs = require(\u0026#39;fs\u0026#39;); const readableStream = fs.createReadStream(\u0026#39;example.txt\u0026#39;, \u0026#39;utf8\u0026#39;); readableStream.on(\u0026#39;data\u0026#39;, (chunk) =\u0026gt; { console.log(\u0026#39;Received chunk:\u0026#39;, chunk); readableStream.destroy(); // 销毁流 }); ","date":"2025-04-27","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/65df2b3b/71a0568b/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e简介 \n    \u003cdiv id=\"简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eNode.js 的 Stream 是一种处理流式数据的抽象接口，广泛应用于文件操作、网络通信等场景。\u003c/p\u003e","title":"3、Stream","type":"posts"},{"content":" 什么是模块 # 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信 在node中，一个js文件就是一个模块，每个模块单独运行在一个函数中，所以一个js文件中的变量和函数的作用域不是全局的，如果需要在其他js中使用，那么需要使用引入 node中模块分为两大类：核心模块（node提供或npm下载的，例如fs）；文件模块（自己写的模块） 解决的问题 # 请求过多 首先我们要依赖多个模块，那样就会发送多个请求，导致请求过多 依赖模糊 我们不知道他们的具体依赖关系是什么，也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。 难以维护 以上两种原因就导致了很难维护，很可能出现牵一发而动全身的情况导致项目出现严重的问题。模块化固然有多个好处，然而一个页面需要引入多个js文件，就会出现以上这些问题。 CommonJS和ES6Modules # CommonJS和 ES6 模块系统是 JavaScript 中两种主要的模块处理方式。它们在语法、加载方式和使用场景上都有显著的区别。\nNode.js 中的每个文件都可以作为一个模块，默认处于独立的作用域内。\nNode.js 支持以下几种模块：\n内置模块：Node.js 自带的模块，如 fs、http、path 等。 用户自定义模块：由开发者创建的模块。 第三方模块：通过 npm 安装的模块，如 express、lodash 等。 package.json中type属性 # type字段的产生用于定义package.json文件和该文件所在目录根目录中.js文件和无拓展名文件的处理方式。值为'moduel'则当作es模块处理；值为commonjs则被当作CommonJs模块处理 目前Node默认的是如果pacakage.json没有定义type字段，则按照CommonJs规范处理 Node官方建议包的开发者明确指定package.json中type字段的值 无论package.json中的type字段为何值，.mjs的文件都按照es模块来处理，.cjs的文件都按照CommonJs模块来处理 CommonJS和ES6 模块化的区别 # 语法 CommonJS 使用 module.exports 和 require。 ES6 模块使用 export 和 import。 加载方式 CommonJS 是同步加载，适用于服务器端。 ES6 模块是静态加载，适用于浏览器和服务器端。 动态 vs 静态 CommonJS 支持动态加载，可以在代码运行时决定加载哪些模块。 ES6 模块是静态的，编译时就确定了模块依赖关系。 优化 ES6 模块支持树摇优化，可以在打包时移除未使用的代码。 CommonJS 不支持树摇优化。 作用域 CommonJS 模块在加载时执行，模块的顶层作用域是模块自身。 ES6 模块在导入时不会执行，模块的顶层作用域是模块自身。 兼容性 CommonJS：主要用于 Node.js 环境，广泛支持现有的 Node.js 生态系统。 ES6 模块：现代浏览器和 Node.js（v12+）都支持 ES6 模块，但在某些旧环境中可能需要使用 Babel 等工具进行转译。 CommonJS 和 ES6 模块系统各有优缺点，选择哪种模块系统取决于你的项目需求和运行环境。对于新的前端项目，推荐使用 ES6 模块，因为它是 JavaScript 的官方标准，并且支持更好的优化。对于现有的 Node.js 项目，CommonJS 仍然是一个可靠的选择。\nCommonJS # CommonJs主要为了解决ES6之前无模块概念的问题\n引入核心模块不需要写路径，引入文件模块需要写路径（一般是相对路径./或../开头）\n批量导出与导入 # other.js\nvar msg = \u0026#39;this is a model\u0026#39;; function myFunc(){ console.log(\u0026#39;this is a function by model\u0026#39;); } // 批量导出 module.exports = { msg, myFunc, name: \u0026#39;my name\u0026#39; } index.js\n// 变量名字可以随便起，一般为文件名 // 引入同目录下的model.js，如果是相对路径，必须以./开头 // 此处的model.js的.js可以省略 var other = require(\u0026#39;./other\u0026#39;) other.myFunc(); console.log(other.name); console.log(other.msg); 逐一导出与选择性导入 # other.js\nvar msg = \u0026#39;this is a model\u0026#39;; function myFunc(){ console.log(\u0026#39;this is a function by model\u0026#39;); } // 逐一导出 exports.msg = msg; exports.myFunc = myFunc; exports.name = \u0026#39;my name\u0026#39; index.js\n// 无论是批量导出还是逐一导出都可以如下选择性导入 var {myFunc,msg,name} = require(\u0026#39;./other\u0026#39;) myFunc(); console.log(name); console.log(msg); 区别 # 二者的作用是一样的 二者不可以同时存在，如果同时存在，只有module.exports生效 ES6Modules # export # other.js\nexport var msg = \u0026#39;this is a model\u0026#39;; export function myFunc(){ console.log(\u0026#39;this is a function by model\u0026#39;); } index.js\n// 导入方式一：导入的多项组成一个对象 import * as other from \u0026#39;./other.js\u0026#39; other.myFunc(); console.log(other.msg); // 导入方式二：选择性导入 import {myFunc,msg} from \u0026#39;./other.js\u0026#39; myFunc(); console.log(msg); export default # other.js\nvar msg = \u0026#39;this is a model\u0026#39;; function myFunc(){ console.log(\u0026#39;this is a function by model\u0026#39;); } // 批量导出，一个模块中只能声明一个 export default export default { msg, myFunc, name: \u0026#39;my name\u0026#39; } index.js\nimport other from \u0026#39;./other.js\u0026#39; other.myFunc(); console.log(other.msg); console.log(other.name); 注意：export default导出的不可以使用import {}解构导入！\n导入模块 # 导入内置模块 # 内置模块是由 Node.js 自带的模块，在安装 Node.js 时就已经包含在环境中，因此无需额外安装。常见的内置模块包括 fs、http、path、os、crypto 等。\n导入内置模块只需要使用require()函数并传入模块名称即可。例如，要导入文件系统模块 fs、path、http，可以这样做：\n// 导入文件系统模块 const fs = require(\u0026#39;fs\u0026#39;); // 导入路径模块 const path = require(\u0026#39;path\u0026#39;); // 导入 HTTP 模块 const http = require(\u0026#39;http\u0026#39;); 导入第三方模块 # 第三方模块是开发者或开源社区发布的模块，可以通过 npm（Node 包管理器）安装到项目中，常见的第三方模块有 express、lodash、axios 等。\n第三方模块安装后，这些模块会被放置在项目的 node_modules 目录下。要导入一个第三方模块，同样使用 require() 函数，但传入的是模块的名称。例如，要导入 express 框架，可以这样做：\n# 在导入第三方模块之前，确保已经通过 npm 安装了该模块。 npm install express 然后导入\n// 导入第三方模块 express const express = require(\u0026#39;express\u0026#39;); 导入自定义模块 # 按照导出方式的不同（CommonJS或ES6Modules），使用绝对或相对路径进行导入即可。\n加载模块的路径解析 # **1、核心模块：**如 http 和 fs，在 Node.js 安装时就包含，可以直接加载。\nconst http = require(\u0026#39;http\u0026#39;); **2、本地文件模块：**使用相对路径或绝对路径加载，需指定 ./ 或 /。\nconst myModule = require(\u0026#39;./myModule\u0026#39;); **3、第三方模块：**位于 node_modules 目录中，只需输入模块名称即可加载。\nconst express = require(\u0026#39;express\u0026#39;); 模块缓存 # **模块缓存机制：**Node.js 会将已加载的模块缓存起来，以提高性能。再次 require() 同一模块时，直接返回缓存中的模块，而不是重新加载。\n**刷新缓存：**要重新加载模块，可以删除缓存：\ndelete require.cache[require.resolve(\u0026#39;./myModule\u0026#39;)]; 循环依赖 # 当两个或多个模块相互导入时，称为循环依赖。Node.js 能处理简单的循环依赖，但可能导致部分模块导出的对象未完全初始化。\n// a.js const b = require(\u0026#39;./b\u0026#39;); console.log(\u0026#39;a.js:\u0026#39;, b.message); module.exports = { message: \u0026#39;Hello from a\u0026#39; }; // b.js const a = require(\u0026#39;./a\u0026#39;); console.log(\u0026#39;b.js:\u0026#39;, a.message); module.exports = { message: \u0026#39;Hello from b\u0026#39; }; // main.js require(\u0026#39;./a\u0026#39;); // b.js: undefined // a.js: Hello from b // (node:25426) Warning: Accessing non-existent property \u0026#39;message\u0026#39; of module exports inside circular dependency // (Use `node --trace-warnings ...` to show where the warning was created) b.js 导入 a.js 时，由于 a.js 尚未完全执行，a.message 为 undefined\n模块包装与作用域 # Node.js 会将每个模块包装在一个函数中，使得每个模块都有独立的作用域。\n// Node.js 内部将模块包装为类似这样的函数： (function(exports, require, module, __filename, __dirname) { // 模块代码在这里 }); 这意味着在模块中定义的变量不会污染全局作用域。\n模块的加载 # 1、从文件模块缓存中加载 # 尽管原生模块与文件模块的优先级不同，但是都会优先从文件模块的缓存中加载已经存在的模块。\n2、从原生模块加载 # 原生模块的优先级仅次于文件模块缓存的优先级。require 方法在解析文件名之后，优先检查模块是否在原生模块列表中。以http模块为例，尽管在目录下存在一个 http、http.js、http.node、http.json 文件，require(\u0026quot;http\u0026quot;) 都不会从这些文件中加载，而是从原生模块中加载。\n原生模块也有一个缓存区，同样也是优先从缓存区加载。如果缓存区没有被加载过，则调用原生模块的加载方式进行加载和执行。\n3、从文件加载 # 当文件模块缓存中不存在，而且不是原生模块的时候，Node.js 会解析 require 方法传入的参数，并从文件系统中加载实际的文件。\n","date":"2025-04-27","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/79502aa8/910c7552/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是模块 \n    \u003cdiv id=\"什么是模块\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af%e6%a8%a1%e5%9d%97\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起\u003c/li\u003e\n\u003cli\u003e块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信\u003c/li\u003e\n\u003cli\u003e在node中，一个js文件就是一个模块，每个模块单独运行在一个函数中，所以一个js文件中的变量和函数的作用域不是全局的，如果需要在其他js中使用，那么需要使用引入\u003c/li\u003e\n\u003cli\u003enode中模块分为两大类：核心模块（node提供或npm下载的，例如fs）；文件模块（自己写的模块）\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e解决的问题 \n    \u003cdiv id=\"解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e请求过多\n\u003cul\u003e\n\u003cli\u003e首先我们要依赖多个模块，那样就会发送多个请求，导致请求过多\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e依赖模糊\n\u003cul\u003e\n\u003cli\u003e我们不知道他们的具体依赖关系是什么，也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e难以维护\n\u003cul\u003e\n\u003cli\u003e以上两种原因就导致了很难维护，很可能出现牵一发而动全身的情况导致项目出现严重的问题。模块化固然有多个好处，然而一个页面需要引入多个js文件，就会出现以上这些问题。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eCommonJS和ES6Modules \n    \u003cdiv id=\"commonjs和es6modules\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#commonjs%e5%92%8ces6modules\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eCommonJS和 ES6 模块系统是 JavaScript 中两种主要的模块处理方式。它们在语法、加载方式和使用场景上都有显著的区别。\u003c/p\u003e","title":"3、模块化","type":"posts"},{"content":"Promise 是一个 ECMAScript 6 提供的类，目的是更加优雅地书写复杂的异步任务。\n由于 Promise 是 ES6 新增加的，所以一些旧的浏览器并不支持，苹果的 Safari 10 和 Windows 的 Edge 14 版本以上浏览器才开始支持 ES6 特性。\n构造 Promise # new Promise(function (resolve, reject) { // 要做的事情... }); Promise 构造函数是 JavaScript 中用于创建 Promise 对象的内置构造函数。\nPromise 构造函数接受一个函数作为参数，该函数是同步的并且会被立即执行，所以我们称之为起始函数。起始函数包含两个参数 resolve 和 reject，分别表示 Promise 成功和失败的状态。\n起始函数执行成功时，它应该调用 resolve 函数并传递成功的结果。当起始函数执行失败时，它应该调用 reject 函数并传递失败的原因。\nPromise 构造函数返回一个 Promise 对象，该对象具有以下几个方法：\nthen：用于处理 Promise 成功状态的回调函数。 catch：用于处理 Promise 失败状态的回调函数。 finally：无论 Promise 是成功还是失败，都会执行的回调函数。 注意：resolve 和 reject 并不能够使起始函数停止运行，如有需要，别忘了 return。\n例子 # const promise = new Promise((resolve, reject) =\u0026gt; { // 异步操作 setTimeout(() =\u0026gt; { if (Math.random() \u0026lt; 0.5) { resolve(\u0026#39;success\u0026#39;); } else { reject(\u0026#39;error\u0026#39;); } }, 1000); }); promise.then(result =\u0026gt; { console.log(result); }).catch(error =\u0026gt; { console.log(error); }); Promise 函数 # 一般情况下，我们将一个 Promise 封装在一个函数中进行返回，例如，在delay时间后，输出message到控制台\nfunction print(delay,message){ return new Promise(function(resolve,reject){ setTimeout(function(){ console.log(message) },delay) }); } async 和 await # 对于上面的函数，假如我们需要每个一秒输出一段话\nfunction print(delay,message){ return new Promise(function(resolve,reject){ setTimeout(function(){ console.log(message) resolve() },delay) }); } print(1000,\u0026#39;1\u0026#39;) print(1000,\u0026#39;2\u0026#39;) print(1000,\u0026#39;3\u0026#39;) 我们发现，1、2、3 是同时输出的，因为 print 返回的是一个Promise，所以并不会等待前一个 Promise 执行完再执行下一个\n这个时候就需要将三个 print 变为同步执行\nfunction print(delay,message){ return new Promise(function(resolve,reject){ setTimeout(function(){ console.log(message) resolve() },delay) }); } async function test() { await print(1000,\u0026#39;1\u0026#39;) await print(1000,\u0026#39;2\u0026#39;) await print(1000,\u0026#39;3\u0026#39;) } test() 注意：await 必须使用在 Promise 对象前，且使用 await 的代码必须在 async 函数中\n","date":"2025-04-25","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/0d08345a/47e69f85/7232e444/","section":"文章","summary":"\u003cp\u003ePromise 是一个 ECMAScript 6 提供的类，目的是更加优雅地书写复杂的异步任务。\u003c/p\u003e\n\u003cp\u003e由于 Promise 是 ES6 新增加的，所以一些旧的浏览器并不支持，苹果的 Safari 10 和 Windows 的 Edge 14 版本以上浏览器才开始支持 ES6 特性。\u003c/p\u003e","title":"3、Promise","type":"posts"},{"content":" 条件分支语句 # if-else # if (condition1){ //当条件 1 为 true 时执行的代码 } else if (condition2){ //当条件 2 为 true 时执行的代码 } else{ //当条件 1 和 条件 2 都不为 true 时执行的代码 } 在 JavaScript 中，可以使用 if 来判断undefined、null、 0、NaN或空字符串\u0026quot;\u0026quot;，比如if data{}，如果 data 为undefined、null、 0、NaN或空字符串\u0026quot;\u0026quot;，那么这个表达式就是不成立的。\nswitch-case # switch(n){ case 1: //执行代码块 1 break; case 2: //执行代码块 2 break; default: //与 case 1 和 case 2 不同时执行的代码 } 循环语句 # for # for (var i = 0;i \u0026lt; cars.length;i++){ document.write(cars[i] + \u0026#34;\u0026lt;br\u0026gt;\u0026#34;); } while # while (条件){ //需要执行的代码 } do-while # do { x = x + \u0026#34;The number is \u0026#34; + i + \u0026#34;\u0026lt;br\u0026gt;\u0026#34;; i++; }while (i \u0026lt; 5); break 和 continue # break 语句可用于跳出循环。\ncontinue 语句用于中断当前的循环中的迭代，然后继续循环下一个迭代。\n","date":"2025-04-25","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/0d08345a/1403736b/eb806475/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e条件分支语句 \n    \u003cdiv id=\"条件分支语句\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9d%a1%e4%bb%b6%e5%88%86%e6%94%af%e8%af%ad%e5%8f%a5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eif-else \n    \u003cdiv id=\"if-else\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#if-else\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003econdition1\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"c1\"\u003e//当条件 1 为 true 时执行的代码\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003econdition2\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"c1\"\u003e//当条件 2 为 true 时执行的代码\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"c1\"\u003e//当条件 1 和 条件 2 都不为 true 时执行的代码\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e在 JavaScript 中，可以使用 if 来判断\u003ccode\u003eundefined\u003c/code\u003e、\u003ccode\u003enull\u003c/code\u003e、 \u003ccode\u003e0\u003c/code\u003e、\u003ccode\u003eNaN\u003c/code\u003e或空字符串\u003ccode\u003e\u0026quot;\u0026quot;\u003c/code\u003e，比如\u003ccode\u003eif data{}\u003c/code\u003e，如果 data 为\u003ccode\u003eundefined\u003c/code\u003e、\u003ccode\u003enull\u003c/code\u003e、 \u003ccode\u003e0\u003c/code\u003e、\u003ccode\u003eNaN\u003c/code\u003e或空字符串\u003ccode\u003e\u0026quot;\u0026quot;\u003c/code\u003e，那么这个表达式就是不成立的。\u003c/p\u003e","title":"3、流程控制","type":"posts"},{"content":" QSS # 如果你学习过Web网页开发，就会发现这个名词和 CSS 特别的相似。\n没错，它的语法和用途和 CSS 特别的相似。\n如果在 Designer界面最上层的 MainWindow 对象 styleSheet 属性指定如下的内容\nQPushButton { color: red ; font-size:15px; } 就会发现，所有的按钮上的文字都变成了红色的，并且字体变大了。\n注意这个指定界面元素显示样式的语法，由 selector 和 declaration 组成。\n花括号前面的部分，比如示例中的 QPushButton 称之为 selector。\nselector 选择器 # QSS selector语法几乎和 Web CSS 的 selector语法没有什么区别。\nSelector 示例 说明 Universal Selector * 星号匹配所有的界面元素 Type Selector QPushButton 选择所有 QPushButton类型 （包括其子类） Class Selector .QPushButton 选择所有 QPushButton类型 ，但是不包括其子类 ID Selector QPushButton#okButton 选择所有 对象名为 okButton 的QPushButton类型 Property Selector QPushButton[flat=\u0026quot;false\u0026quot;] 选择所有 flat 属性值为 false 的 QPushButton类型。 Descendant Selector QDialog QPushButton 选择所有 QDialog 内部 QPushButton类型 Child Selector QDialog \u0026gt; QPushButton 选择所有 QDialog 直接子节点 QPushButton类型 Pseudo-States 伪状态 # 我们可以这样指定当鼠标移动到一个元素上方的时候，元素的显示样式\nQPushButton:hover { color: red } 再比如，指定一个元素是disable状态的显示样式\nQPushButton:disabled { color: red } 再比如，指定一个元素是鼠标悬浮，并且处于勾选（checked）状态的显示样式\nQCheckBox:hover:checked { color: white } 样式属性 # 背景 # 可以指定某些元素的背景色，像这样\nQTextEdit { background-color: yellow } 颜色可以使用红绿蓝数字，像这样\nQTextEdit { background-color: #e7d8d8 } 也可以像这样指定背景图片\nQTextEdit { background-image: url(bk1.png); } 边框 # 可以像这样指定边框 border:1px solid #1d649c;\n其中1px 是边框宽度；solid 是边框线为实线， 也可以是 dashed(虚线) 和 dotted（点）\n边框可以指定为无边框 border:none\n字体、大小、颜色 # 可以这样指定元素的文字字体、大小、颜色\n*{ font-family:微软雅黑; font-size:15px; color: #1d649c; } 宽度、高度 # 可以这样指定元素的宽度、高度\nQPushButton { width:50px; height:20px; } margin、padding # 可以这样指定元素的元素的 margin；padding同理。\nQTextEdit { margin:10px 11px 12px 13px } 分别指定了元素的上右下左margin。\n也可以使用 margin-top, margin-right, margin-bottom, margin-left 单独指定元素的上右下左的margin；padding同理。\nQTextEdit { margin:10px 50px; padding:10px 50px; } 通过代码设置QSS # 前面的示例都是在Qt Designer软件中设置样式\n如果要在代码中创建元素，则可以通过对象的setStyleSheet方法进行设置\nfrom PySide6.QtWidgets import QApplication, QMainWindow, QWidget,QHBoxLayout class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(500,300) topWidget = QWidget(self) self.setCentralWidget(topWidget) main_layout = QHBoxLayout(topWidget) naviBar = QWidget(self) naviBar.setFixedHeight(50) main_layout.addWidget(naviBar) # 注意，这里没有使用 selector 选择范围， # 就表示选择的是调用 setStyleSheet的控件本身， # 这里就是 naviBar，而不是其任何下级控件 naviBar.setStyleSheet(\u0026#39;background: #3c8dbc; border:1px solid red\u0026#39;) app = QApplication() mw = MainWindow() mw.show() app.exec() 可以通过 setObjectName 方法 给控件，设置一个id名称，然后这样。\nfrom PySide6.QtWidgets import QApplication, QMainWindow, QWidget,QHBoxLayout,QLabel class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(500,300) topWidget = QWidget(self) self.setCentralWidget(topWidget) main_layout = QHBoxLayout(topWidget) naviBar = QWidget(self) naviBar.setFixedHeight(50) main_layout.addWidget(naviBar) naviBar.setObjectName(\u0026#39;naviBar\u0026#39;) naviBar.setStyleSheet( \u0026#39;\u0026#39;\u0026#39; #naviBar { background: #3c8dbc; border:1px solid red; color:white } QLabel{ color:yellow } \u0026#39;\u0026#39;\u0026#39;) layout = QHBoxLayout(naviBar) layout.setContentsMargins(0,0,0,0) layout.addWidget(QLabel(\u0026#39;tttttttttt\u0026#39;,parent=naviBar)) layout.addStretch() app = QApplication() mw = MainWindow() mw.show() app.exec() ","date":"2025-04-22","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/901ba1e3/d7299dbb/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eQSS \n    \u003cdiv id=\"qss\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#qss\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e如果你学习过Web网页开发，就会发现这个名词和 \u003ccode\u003eCSS\u003c/code\u003e 特别的相似。\u003c/p\u003e","title":"3、QSS","type":"posts"},{"content":"当我们单击的时候，它可以执⾏相应的功能。\n上⽂说到，按钮可以执⾏相应的功能，这⾥的功能我们可以理解为⼀个函数，或者这些功能通过相应的函数去实现。\n绑定⽅式通常有如下⼏种：第⼀种，在按钮组件被声明 的时候⽤ command 属性声明， command 属性接受⼀个函数名， 注意函数名不要加双引号。第⼆种，使⽤ bind⽅法，该⽅法是 Misc 这个类的⼀个⽅法，下⾯我们仔细讲解。\n属性 # 选项 含义 activebackground 1. 设置当 Button 处于活动状态（通过 state 选项设置状态）的背景色 ；2. 默认值由系统指定 activeforeground 1. 设置当 Button 处于活动状态（通过 state 选项设置状态）的前景色 ；2. 默认值由系统指定 anchor 1. 控制文本（或图像）在 Button 中显示的位置；2. \u0026ldquo;n\u0026rdquo;, \u0026ldquo;ne\u0026rdquo;, \u0026ldquo;e\u0026rdquo;, \u0026ldquo;se\u0026rdquo;, \u0026ldquo;s\u0026rdquo;, \u0026ldquo;sw\u0026rdquo;, \u0026ldquo;w\u0026rdquo;, \u0026ldquo;nw\u0026rdquo;, 或者 \u0026ldquo;center\u0026rdquo; 来定位（ewsn 代表东西南北，上北下南左西右东） ；3. 默认值是 \u0026ldquo;center\u0026rdquo; background 1. 设置背景颜色； 2. 默认值由系统指定 bg 跟 background 一样 bitmap 1. 指定显示到 Button 上的位图 ；2. 如果指定了 image 选项，则该选项被忽略 borderwidth 1. 指定 Button 的边框宽度 ；2. 默认值由系统指定，通常是 1 或 2 像素 bd 跟 borderwidth 一样 compound 1. 控制 Button 中文本和图像的混合模式； 2. 默认情况下，如果有指定位图或图片，则不显示文本 ；3.如果该选项设置为 \u0026ldquo;center\u0026rdquo;，文本显示在图像上（文本重叠图像）；4.如果该选项设置为 \u0026ldquo;bottom\u0026rdquo;，\u0026ldquo;left\u0026rdquo;，\u0026ldquo;right\u0026rdquo; 或 \u0026ldquo;top\u0026rdquo;，那么图像显示在文本的旁边（如 \u0026ldquo;bottom\u0026rdquo;，则图像在文本的下方）；5.默认值是 NONE cursor 1. 指定当鼠标在 Button 上飘过的时候的鼠标样式 ；2. 默认值由系统指定 default 1. 如果设置该选项（\u0026ldquo;normal\u0026rdquo;），该按钮会被绘制成默认按钮 ；2. Tkinter 会根据平台的具体指标来绘制（通常就是绘制一个额外的边框）；3. 默认值是 \u0026ldquo;disable\u0026rdquo; disabledforeground 1. 指定当 Button 不可用的时候前景色的颜色 ；2. 默认值由系统指定 font 1. 指定 Button 中文本的字体 ；2. 一个 Button 只能设置一种字体 ；3. 默认值由系统指定 foreground 1. 设置 Button 的文本和位图的颜色 ；2. 默认值由系统指定 fg 跟 foreground 一样 height 1. 设置 Button 的高度 ；2. 如果 Button 显示的是文本，那么单位是文本单元 ；3. 如果 Button 显示的是图像，那么单位是像素（或屏幕单元）； 4. 如果设置为 0 或者干脆不设置，那么会自动根据 Button 的内容计算出高度 highlightbackground 1. 指定当 Button 没有获得焦点的时候高亮边框的颜色 ；2. 默认值由系统指定 highlightcolor 1. 指定当 Button 获得焦点的时候高亮边框的颜色 ；2. 默认值由系统指定 highlightthickness 1. 指定高亮边框的宽度 ；2. 默认值是 0（不带高亮边框） image 1. 指定 Button 显示的图片 ；2. 该值应该是 PhotoImage，BitmapImage，或者能兼容的对象 ；3. 该选项优先于 text 和 bitmap 选项 justify 1. 定义如何对齐多行文本；2. 使用 \u0026ldquo;left\u0026rdquo;，\u0026ldquo;right\u0026rdquo; 或 \u0026ldquo;center\u0026rdquo;； 3. 注意，文本的位置取决于 anchor 选项 4. 默认值是 \u0026ldquo;center\u0026rdquo; overrelief 1. 定义当鼠标飘过时 Button 的样式 ；2. 如果不设置，那么总是使用 relief 选项指定的样式 padx 指定 Button 水平方向上的额外间距（内容和边框间） pady 指定 Button 垂直方向上的额外间距（内容和边框间） relief 1. 指定边框样式 ；2. 通常当按钮被按下时是 \u0026ldquo;sunken\u0026rdquo;，其他时候是 \u0026ldquo;raised\u0026rdquo; ；3. 另外你还可以设置 \u0026ldquo;groove\u0026rdquo;、\u0026ldquo;ridge\u0026rdquo; 或 \u0026ldquo;flat\u0026rdquo; 4. 默认值是 \u0026ldquo;raised\u0026rdquo; repeatdelay 见下方 repeatinterval 选项的描述 repeatinterval 1. 通常当用户鼠标按下按钮并释放的时候系统认为是一次点击动作。如果你希望当用户持续按下按钮的时候（没有松开），根据一定的间隔多次触发按钮，那么你可以设置这个选项。；2.当用户持续按下按钮的时候，经过 repeatdelay 时间后，每 repeatinterval 间隔就触发一次按钮事件。；3.例如设置 repeatdelay=1000，repeatinterval=300，则当用户持续按下按钮，在 1 秒的延迟后开始每 300 毫秒触发一次按钮事件，直到用户释放鼠标。 state 1. 指定 Button 的状态； 2. 默认值是 \u0026ldquo;normal\u0026rdquo; ；3. 另外你还可以设置 \u0026ldquo;active\u0026rdquo; 或 \u0026ldquo;disabled\u0026rdquo; takefocus 1. 指定使用 Tab 键可以将焦点移到该 Button 组件上（这样按下空格键也相当于触发按钮事件） ；2. 默认是开启的，可以将该选项设置为 False 避免焦点在此 Button 上 text 1. 指定 Button 显示的文本 ；2. 文本可以包含换行符 ；3. 如果设置了 bitmap 或 image 选项，该选项则被忽略 textvariable 1. Button 显示 Tkinter 变量（通常是一个 StringVar 变量）的内容 ；2. 如果变量被修改，Button 的文本会自动更新 underline 1. 跟 text 选项一起使用，用于指定哪一个字符画下划线（例如用于表示键盘快捷键） ；2. 默认值是 -1 3. 例如设置为 1，则说明在 Button 的第 2 个字符处画下划线 width 1. 设置 Button 的宽度 ；2. 如果 Button 显示的是文本，那么单位是文本单元； 3. 如果 Button 显示的是图像，那么单位是像素（或屏幕单元） ；4. 如果设置为 0 或者干脆不设置，那么会自动根据 Button 的内容计算出宽度 wraplength 1. 决定 Button 的文本应该被分成多少行 ；2. 该选项指定每行的长度，单位是屏幕单元 ；3. 默认值是 0 事件绑定 # command 绑定 # import tkinter as tk root = tk.Tk() def createLable(): lable = tk.Label(root,text=\u0026#39;hello tkinter\u0026#39;) lable.pack() btn = tk.Button(root,text=\u0026#39;click me to create lable\u0026#39;,command=createLable) btn.pack() root.mainloop() bind 绑定 # bind 绑定不只可以绑定按钮，其他的控件包括主窗口都可以绑定\nimport tkinter as tk root = tk.Tk() # 此处函数，需要添加一个参数 def createLable(event): lable = tk.Label(root,text=\u0026#39;hello tkinter\u0026#39;) lable.pack() btn = tk.Button(root,text=\u0026#39;click me to create lable\u0026#39;) #此处进行绑定 btn.bind(\u0026#39;\u0026lt;Button-1\u0026gt;\u0026#39;,createLable) btn.pack() root.mainloop() bind的第⼀个参数是⽤字符串包含的事件类型，它采⽤的描述⽅式是: \u0026lt;MODIFIER-TYPE-DETAIL\u0026gt;\nMODIFIER，可以声明多个，例如\u0026lt;Control-Shift-Key-a\u0026gt;就是Ctrl + Shift + a\nmodifier 含义 Alt 按下Alt时候 Any 表示任何类型的按键被按下的时候 Control 按下Control的时候 Command Mac 的 Command 键按下 Double 表示后续两个事件被连续触发（如:\u0026lt;Double-Button-1\u0026gt;为用户双击鼠标左键） Lock 打开大写字母锁定键（CapsLock） Shift 按下Shift时候 Triple 跟Double类似，三连击 TYPE\ntype 含义 Configure 组件尺寸发生变化触发 Deactivate 组件状态从激活变为未激活 Destroy 组件被摧毁触发 Enter 当鼠标指针进入时候触发（不是按下回车） Expose 当窗口或组件的某部分不再被覆盖的时候触发 FocusIn 当组件获得焦点时触发（可以用focus_set使组件获得焦点） FousOut 当组件失去焦点触发 KeyPress 键盘，可以简写为Key，一般搭配DETAIL 使用，例如Key-a是键盘 a 键 KeyRelease 释放键盘后的事件 Leave 鼠标指针离开后触发 Map 当组件被映射时候触发，意思是在应用程序中显示该组件的时候，如grid()方法 Motion 鼠标在组件内移动触发事件 MouseWheel 当鼠标滚轮滚动时触发（支持WIN和mac系统，Linux参考Button） Unmap 当组件被取消映射时候触发，如grid_remove()方法时 Visibility 当应用程序至少有一部分在屏幕中是可见的时候触发该事件 Button 按下鼠标按键，一般搭配 DETAIL 使用，例如Button-1 是鼠标左键 ButtonRelease 松开鼠标按键，用法同上 DETAIL，需要搭配 Type 使用\nTYPE-DETAIL 含义 Button-1 按下鼠标左键 Button-2 按下鼠标中键 Button-3 按下鼠标右键 Button-4 滚轮上滑 Button-5 滚轮下滑 Event，当Tkinter去回调你定义的函数，都会带Event对象（作为参数）去调用，Event的属性如下：\n属性 含义 widget 产生该事件的组件 x，y 当前鼠标位置坐标（相对于窗口左上角，像素为单位） x_root，y_root 同上 char 按键对应字符（键盘事件专属） keysym 按键名 keycode 按键码 num 按钮数字（鼠标事件专属） width，height 组件的新尺寸（Configure事件专属） type 该事件的类型 ","date":"2025-04-15","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/a879fe06/10cf8985/","section":"文章","summary":"\u003cp\u003e当我们单击的时候，它可以执⾏相应的功能。\u003c/p\u003e\n\u003cp\u003e上⽂说到，按钮可以执⾏相应的功能，这⾥的功能我们可以理解为⼀个函数，或者这些功能通过相应的函数去实现。\u003c/p\u003e","title":"3、按钮Button","type":"posts"},{"content":" 动态绑定属性、方法 # 正常情况下，当我们定义了一个class，创建了一个class的实例后，我们可以给该实例绑定任何属性和方法，这就是动态语言的灵活性。\nfrom types import MethodType class Person(object): pass per1 = Person() per2 = Person() # 动态绑定属性 per1.name = \u0026#39;lucy\u0026#39; per2.name = \u0026#39;tom\u0026#39; def showName(self): print(self.name) # 动态绑定方法 per1.showName = MethodType(showName,per1) # 可以调用 per1.showName() # 无法调用，报错 per2.showName() 可以通过MethodType()在运行期间动态的给一个实例per1绑定方法，但是，Person类的的其他实例，例如per2无法使用这个方法\n可以通过给Person这个类绑定，来实现所有实例使用showName这个方法\nclass Person(object): pass per1 = Person() per2 = Person() per1.name = \u0026#39;lucy\u0026#39; per2.name = \u0026#39;tom\u0026#39; def showName(self): print(self.name) # 对Person类动态绑定方法 Person.showName = showName per1.showName() per2.showName() __slots__ # 如果我们需要限制实例的属性和方法的话，那么可以使用__slots__属性，这个属性的值是一个元组Tuple\n使用__slots__要注意，__slots__定义的属性仅对当前类实例起作用，对继承的子类是不起作用的\n除非在子类中也定义__slots__，这样，子类实例允许定义的属性就是自身的__slots__加上父类的__slots__\nfrom types import MethodType class Person(object): #声明Person类的实例，只可以有name、age两个属性 __slots__ = (\u0026#39;name\u0026#39;,\u0026#39;age\u0026#39;) @Property # 对于类的方法，装饰器一样起作用，Python内置的@property装饰器就是负责把一个方法变成属性调用的，省去了编写getter、setter的麻烦\n注意：此处的方法名称，不要和属性名一样，以免造成堆栈溢出报错\nfrom msilib.schema import Property class Person(object): #此处实际的属性应该是__name，而对外暴露的是name，必须通过name来操作属性 @property def name(self): return self.__name #__name属性的setter方法 @name.setter def name(self,name): if(len(name) \u0026lt; 3): print(\u0026#39;名字长度必须大于3位\u0026#39;) else: self.__name = name per = Person() per.name = \u0026#39;lucy\u0026#39; print(per.name) __str__() # 由于我们直接打印对象，输出的是\u0026lt;__main__.Student object at 0x109afb190\u0026gt;这样的字符串，所以，可以重写__str__()，来进行定制，类似java的toString方法\n由于__str__()方法是返回给用户的字符串，但是如果在调试的时候，控制台上打印的字符串是__repr__()返回的，所以，在重写完__str__()后，使用__repr__ = __str__即可\nclass Person(object): def __init__(self,name,age): self.__name = name self.__age = age @property def name(self): return self.__name @name.setter def name(self,name): self.__name = name @property def age(self): return self.__age @age.setter def age(self,age): self.__age = age def __str__(self): return \u0026#39;姓名：%s，年龄：%s\u0026#39; % (self.__name,self.__age) __repr__ = __str__ per = Person(\u0026#39;lucy\u0026#39;,\u0026#39;age\u0026#39;) print(per) __iter__() # 如果一个类想被用于for ... in循环，类似list或tuple那样，就必须实现一个__iter__()方法，该方法返回一个迭代对象，然后，Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值，直到遇到StopIteration错误时退出循环。\n例如，1-10000之内的斐波那契数列（除第一第二项，其余每项都等于前两项之和）\nclass Feb(object): def __init__(self): self.a,self.b = 0,1 def __iter__(self): return self def __next__(self): # 计算下一个值,下一个值等于前两个值之和 self.a, self.b = self.b, self.a + self.b if self.a \u0026gt; 100000: # 退出循环的条件 raise StopIteration() return self.a # 返回下一个值 feb = Feb() for i in feb: print(i) __getitem__() # 魔法方法__getitem__ 可以让对象实现迭代功能，这样就可以使用 for…in… 来迭代该对象了\nclass Animal: def __init__(self, animal_list): self.animals_name = animal_list self.other = \u0026#39;hello, world\u0026#39; def __getitem__(self, index): return self.animals_name[index] animals = Animal([\u0026#34;dog\u0026#34;,\u0026#34;cat\u0026#34;,\u0026#34;fish\u0026#34;]) for animal in animals: print(animal) __getattr__() # 正常情况下，当我们调用类的方法或属性时，如果不存在，就会报错，此时除了添加这个属性外，python还提供了一个机制，就是写一个__getattr__()方法，动态返回一个属性，也可以用来返回方法\nclass Person(object): def __init__(self,name): self.name = name def __getattr__(self,attr): if attr == \u0026#39;age\u0026#39;: return 18 if attr == \u0026#39;sayHello\u0026#39;: return lambda :print(\u0026#39;Hello\u0026#39;) per = Person(\u0026#39;lucy\u0026#39;) print(per.age) per.sayHello() 注意，只有在没有找到属性的情况下，才调用__getattr__，已有的属性，比如name，不会在__getattr__中查找，此外，注意到任意调用如per.abc都会返回None，这是因为我们定义的__getattr__默认返回就是None\nclass Chain(object): def __init__(self,path=\u0026#39;\u0026#39;): self.path = path def __getattr__(self,path): return Chain(\u0026#39;%s/%s\u0026#39; % (self.path,path)) def __str__(self): return self.path __reqr__ = __str__ ch = Chain().status.users.list print(ch) # /status/users/list __call__() # 任何类，只需要定义一个__call__()方法，就可以直接对实例进行调用\nclass Person(object): def __init__(self,name,age): self.name = name self.age = age def __call__(self): print(\u0026#39;姓名：%s，年龄：%s\u0026#39; % (self.name,self.age)) per = Person(\u0026#39;lucy\u0026#39;,18) per() 枚举 # Python提供了Enum类来实现这个功能，定义一个class类型，然后，每个常量都是class的一个唯一实例\nvalue属性则是自动赋给成员的int常量，默认从1开始计数\n#导入Enum from enum import Enum #定义枚举类Month，并声明枚举项 Month = Enum(\u0026#39;Month\u0026#39;, (\u0026#39;Jan\u0026#39;, \u0026#39;Feb\u0026#39;, \u0026#39;Mar\u0026#39;, \u0026#39;Apr\u0026#39;, \u0026#39;May\u0026#39;, \u0026#39;Jun\u0026#39;, \u0026#39;Jul\u0026#39;, \u0026#39;Aug\u0026#39;, \u0026#39;Sep\u0026#39;, \u0026#39;Oct\u0026#39;, \u0026#39;Nov\u0026#39;, \u0026#39;Dec\u0026#39;)) for name, member in Month.__members__.items(): print(name, \u0026#39;=\u0026gt;\u0026#39;, member, \u0026#39;=\u0026gt;\u0026#39;, member.value) \u0026#39;\u0026#39;\u0026#39; Jan =\u0026gt; Month.Jan =\u0026gt; 1 Feb =\u0026gt; Month.Feb =\u0026gt; 2 Mar =\u0026gt; Month.Mar =\u0026gt; 3 Apr =\u0026gt; Month.Apr =\u0026gt; 4 May =\u0026gt; Month.May =\u0026gt; 5 Jun =\u0026gt; Month.Jun =\u0026gt; 6 Jul =\u0026gt; Month.Jul =\u0026gt; 7 Aug =\u0026gt; Month.Aug =\u0026gt; 8 Sep =\u0026gt; Month.Sep =\u0026gt; 9 Oct =\u0026gt; Month.Oct =\u0026gt; 10 Nov =\u0026gt; Month.Nov =\u0026gt; 11 Dec =\u0026gt; Month.Dec =\u0026gt; 12 \u0026#39;\u0026#39;\u0026#39; 如果需要更精确地控制枚举类型，可以从Enum派生出自定义类\nfrom enum import Enum,unique # unique装饰器用来帮助检查有没有重复值 @unique class Weekday(Enum): Sun = 0 Mon = 1 Tue = 2 Wed = 3 Thu = 4 Fri = 5 Sat = 6 for name,member in Weekday.__members__.items(): print(name,\u0026#39;=\u0026gt;\u0026#39;,member,\u0026#39;=\u0026gt;\u0026#39;,member.value) \u0026#39;\u0026#39;\u0026#39; Sun =\u0026gt; Weekday.Sun =\u0026gt; 0 Mon =\u0026gt; Weekday.Mon =\u0026gt; 1 Tue =\u0026gt; Weekday.Tue =\u0026gt; 2 Wed =\u0026gt; Weekday.Wed =\u0026gt; 3 Thu =\u0026gt; Weekday.Thu =\u0026gt; 4 Fri =\u0026gt; Weekday.Fri =\u0026gt; 5 Sat =\u0026gt; Weekday.Sat =\u0026gt; 6 \u0026#39;\u0026#39;\u0026#39; 访问枚举 # from enum import Enum,unique # unique装饰器用来帮助检查有没有重复值 @unique class Weekday(Enum): Sun = 0 Mon = 1 Tue = 2 Wed = 3 Thu = 4 Fri = 5 Sat = 6 day = Weekday.Sun print(day) # Weekday.Sun print(Weekday.Mon) # Weekday.Mon print(Weekday[\u0026#39;Tue\u0026#39;]) # Weekday.Tue print(day.value) # 0 print(day == Weekday.Sun) # True print(Weekday(1)) # Weekday.Mon type() # 要定义一个Hello的class，就写一个hello.py模块\nclass Hello(object): def sayHello(self,name = \u0026#39;world\u0026#39;): print(\u0026#39;hello %s\u0026#39; % name) main.py\nfrom hello import Hello he = Hello() he.sayHello() print(type(he)) # \u0026lt;class \u0026#39;hello.Hello\u0026#39;\u0026gt; print(type(Hello)) # \u0026lt;class \u0026#39;type\u0026#39;\u0026gt; type()函数可以查看一个类型或变量的类型，Hello是一个class，它的类型就是type，而he是一个实例，它的类型就是class Hello\npython中class的定义是运行时动态创建的，而创建class的方法就是使用type()函数\ntype()函数既可以返回一个对象的类型，又可以创建出新的类型，可以通过type直接创建一个Hello类型，而不需要通过定义，语法：类名 = type('类名',继承的父类元组,dict(类方法=函数)\ndef func(self,name = \u0026#39;world\u0026#39;): print(\u0026#39;hello %s\u0026#39; % name) Hello = type(\u0026#39;Hello\u0026#39;,(object,),dict(sayHello = func)) he = Hello() he.sayHello() 通过type()函数创建的类和直接写class是完全一样的，因为Python解释器遇到class定义时，仅仅是扫描一下class定义的语法，然后调用type()函数创建出class\nmetaclass # ","date":"2025-04-10","externalUrl":null,"permalink":"/posts/1b37041b/a64b122d/a55a9191/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e动态绑定属性、方法 \n    \u003cdiv id=\"动态绑定属性方法\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8a%a8%e6%80%81%e7%bb%91%e5%ae%9a%e5%b1%9e%e6%80%a7%e6%96%b9%e6%b3%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e正常情况下，当我们定义了一个class，创建了一个class的实例后，我们可以给该实例绑定任何属性和方法，这就是动态语言的灵活性。\u003c/p\u003e","title":"3、面向对象高级","type":"posts"},{"content":" 输出 # 基本输出 # print()\n在python中，不像java一样可以做自动类型提升，所以，一个字符串和一个int，不可以使用+，在一起输出\n# 这样会报错 print(\u0026#34;你好\u0026#34; + 12) #可以接收多个字符串，逗号分隔，会连起来输出，逗号会输出为空格 print(\u0026#39;Hello\u0026#39;,\u0026#39;World\u0026#39;) 默认print输出的最后会添加换行符，如果想要不换行，则指定结束end即可\nprint(\u0026#39;a\u0026#39;,end=\u0026#39;\u0026#39;) print(\u0026#39;b\u0026#39;) #ab 格式化字符串 # 简单理解，%*也就是占位符，只需要在字符串后面，使用%，然后在后面声明这个占位符的值就可以了，如果多个需要按照顺序写在()里面\n字符串 # %s：字符串 %10s：右对齐，占位符10位 %-10s：左对齐，占位符10位 %.2s：截取前2位字符串 %10.2s：10位占位符，截取两位字符串\n整数 # %o：oct 八进制 %d：dec 十进制 %x：hex 十六进制 浮点数 # %f ：保留小数点后面六位有效数字 %.3f：保留3位小数位 %e ：保留小数点后面六位有效数字，指数形式输出 %.3e：保留3位小数位，使用科学计数法 %g ：在保证六位有效数字的前提下，使用小数方式，否则使用科学计数法 %.3g：保留3位有效数字，使用小数或科学计数法 例如，上面使用普通输出会报错，那么就可以使用格式化输出\nprint(\u0026#34;你好%d\u0026#34; % 12) # 你好12 # 多个需要格式化的可以这么写 age = 18 name = \u0026#34;lucy\u0026#34; penNum = 5 print(\u0026#34;我叫%s今年%d岁了,我有%d个铅笔\u0026#34; % (name, age, penNum)) 输入 # python使用input(\u0026quot;msg\u0026quot;)函数，可以进行输入，msg为提示信息，相当于java的Scanner input=new Scanner(System.in);\naccount = input(\u0026#34;请输入你的账号:\u0026#34;) print(\u0026#34;您的账号是：\u0026#34; + str(account)) #请输入你的账号:123 #您的账号是：123 ","date":"2025-04-09","externalUrl":null,"permalink":"/posts/1b37041b/aae761a6/2bca4e57/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e输出 \n    \u003cdiv id=\"输出\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%be%93%e5%87%ba\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e基本输出 \n    \u003cdiv id=\"基本输出\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%9f%ba%e6%9c%ac%e8%be%93%e5%87%ba\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003eprint()\u003c/code\u003e\u003c/p\u003e","title":"3、输入和输出","type":"posts"},{"content":" 修改器 # 修改器是一种自动操作，以非破坏性的方式影响对象的几何形状。使用修改器，可以自动执行许多效果而且不会影响对象的基本几何形状，否则手动执行这些效果(例如细分曲面)会非常麻烦。\n它们的工作原理是改变对象的显示和渲染方式，而不是改变可以直接编辑的几何图形。\n在界面左下角的“ 扳手 ”，可以打开修改器面板。\n修改器有四种类型:\n编辑：修改组修改器是类似于形变修改器 (见下文)，不过不会直接影响物体形状，但会影响物体的一些其他数据，比如顶点组。 生成：这些是建设性/破坏性的工具，能够影响整个网格的拓扑。它们可以改变对象的整体外观，或添加新的几何图形到对象。 形变：与上面的生成不同，它们只改变对象的形状，而不改变其拓扑结构。 物理：这些模拟代表的是物理模拟。在大多数情况下，只要启用了粒子系统或物理模拟，它们就会自动添加到修改器堆栈中。它们唯一的作用是定义其在修改器堆栈中的位置，从这个位置获取它们所表示的模拟的基本数据。因此，它们通常没有属性，由属性编辑器中单独关于模拟的部分中的设置控制。 编辑 # 生成 # 阵列 # 快速复制多个对象\n倒角 # 可以快速完成整体倒角\n布尔 # 可以制作一个可重复编辑的布尔结果模型\n建形 # 可以控制物体生成的过程，获取过程中的模型\n精简 # 可以减少模型面数\n拆边 # 可以快速按照模型的所有边拆开所有的面\n遮罩 # 可以按照所选的定点组进行保留，没有选择的全部隐藏\n镜像 # 可以按照一定的轴向，镜像另一半的模型\n多精度 # 雕刻时使用\n表面细分 # 可以修改物体表面的平滑程度\n螺旋线 # 旋转建模的修改器，比如做弹簧\n蒙皮 # 可以把单点对象转换为实体模型\n可以使用Ctrl + A调整粗细\n实体化 # 给模型增加厚度\n三角化 # 可以优化物体走线\n形变 # 物理 # ","date":"2025-04-03","externalUrl":null,"permalink":"/posts/69064821/f1464f3a/0a459347/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e修改器 \n    \u003cdiv id=\"修改器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bf%ae%e6%94%b9%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e修改器是一种自动操作，以非破坏性的方式影响对象的几何形状。使用修改器，可以自动执行许多效果而且不会影响对象的基本几何形状，否则手动执行这些效果(例如细分曲面)会非常麻烦。\u003c/p\u003e","title":"3、修改器","type":"posts"},{"content":"Cocos Creator 脚本用于实现用户定义的（游戏）行为，支持 JavaScript 和 TypeScript 两种编程语言。通过编写脚本组件，并将它挂载到场景节点中来驱动场景中的物体。\n在组件脚本的编写过程中，开发者可以通过声明属性，将脚本中需要调节的变量映射到 属性检查器 中，以便策划和美术进行调整。与此同时，也可以通过注册特定的回调函数，来帮助初始化、更新甚至销毁节点。\n语言支持 # Cocos Creator 支持 TypeScript 和 JavaScript 两种编程语言。但需要注意的是，JavaScript 只支持以插件脚本的形式导入使用。\nCocos Creator 支持 TypeScript 4.1.0，使用时有以下需要注意：\n不支持 const enums。\n重导出 TypeScript 类型和接口时应该使用 export type。例如使用 export type { Foo } from './foo'; 而不是 export { Foo } from './foo';。\n不支持 export = 和 import =。\n命名空间导出的变量必须声明为 const，而不是 var 或 let。\n同一命名空间的不同声明不会共享作用域，需要显式使用限定符。\n编译过程中的类型错误将被忽略。\n编译时不会读取 tsconfig.json，意味着 tsconfig.json 的编译选项并不会影响编译。\n脚本使用 # 创建脚本 # 在创建脚本时，名称不能为空，输入框默认为 NewComponent。\nimport { _decorator, Component, Node } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; @ccclass(\u0026#39;NewComponent\u0026#39;) export class NewComponent extends Component { start() { } update(deltaTime: number) { } } 项目中所有脚本的类名 ClassName （如上例中的NewComponent） 不允许重复，即使脚本文件在不同的目录下，各自的代码里也不允许有相同的类名。 脚本文件名称和脚本的类名不同，在输入初始的文件名之后，文件名会被处理为类名。脚本文件生成后，对文件的后续操作脚本重命名，新的文件名不会再去生成并替换代码里的类名，不再影响了。 我们推荐用户使用 TypeScript 来编写脚本，目前 资源管理器 中仅支持创建 TypeScript 文件。但如果用户想要使用 JavaScript 来编写脚本的话，可以直接在操作系统的文件夹中创建 JavaScript 文件，或在其他代码编辑软件中创建 JavaScript 文件。 添加脚本到场景节点中 # 将脚本添加到场景节点中，实际上就是为这个节点添加一个脚本组件。\n在 层级管理器 选中某个节点，此时 属性检查器 面板会显示该节点的属性。以下两种添加方式：\n直接将 资源管理器 中的脚本拖拽当前节点的到 属性检查器 中，即为挂载了一个组件。 点击 属性检查器 最下方的 添加组件 按钮，选择（自定义脚本 - NewComponent），即为挂载组件。 脚本运行环境 # Cocos Creator 3.0 引擎的 API 都存在模块 cc 中，使用标准的 ES6 模块导入语法将其导入：\nimport { Component, // 导入类 Component _decorator, // 导入命名空间 _decorator Vec3 // 导入类 Vec3 } from \u0026#39;cc\u0026#39;; // 将整个 Cocos Creator 模块导入为命名空间 Cocos Creator import * as modules from \u0026#39;cc\u0026#39;; @_decorator.ccclass(\u0026#34;MyComponent\u0026#34;) export class MyComponent extends Component { public v = new Vec3(); } 注意，由于历史原因，cc 是 Cocos Creator 3.0 保留使用的标识符，其行为 相当于 在任何模块顶部定义了名为 cc 的对象。因此，开发者不应该将 cc 用作任何 全局对象 的名称。\n装饰器 # ccclass # 将装饰器 ccclass 应用在类上时，此类称为 cc 类。cc 类注入了额外的信息以控制 Cocos Creator 对该类对象的序列化、编辑器对该类对象的展示等。因此，未声明 ccclass 的组件类，也无法作为组件添加到节点上。\nccclass 装饰器的参数 name 指定了 cc 类的名称，cc 类名是 独一无二 的，这意味着即便在不同目录下的同名类也是不允许的。\n组件类装饰器 # 此类装饰器是只能用来修饰 Component 的子类。\nexecuteInEditMode # 默认情况下，所有组件都只会在运行时执行，也就是说它们的生命周期回调在编辑器模式下并不会触发。executeInEditMode 允许当前组件在编辑器模式下运行，默认值为 false。\nconst { ccclass, executeInEditMode } = _decorator; @ccclass(\u0026#39;Example\u0026#39;) @executeInEditMode(true) export class Example extends Component { update (dt: number) { // 会在编辑器下每帧执行 } } requireComponent # requireComponent 参数用来指定当前组件的依赖组件，默认值为 null。当组件添加到节点上时，如果依赖的组件不存在，引擎会自动将依赖组件添加到同一个节点，防止脚本出错。该选项在运行时同样有效。\nconst { ccclass, requireComponent } = _decorator; @ccclass(\u0026#39;Example\u0026#39;) @requireComponent(Sprite) export class Example extends Component { } executionOrder # executionOrder 用来指定脚本生命周期回调的执行优先级。小于 0 的脚本将优先执行，大于 0 的脚本将最后执行。\n对于同一节点上的不同组件，数值小的先执行，数值相同的按组件添加先后顺序执行 对于不同节点上的同一组件，按节点树排列决定执行的先后顺序 该优先级设定只对 onLoad、onEnable、start、update 和 lateUpdate 有效，对 onDisable 和 onDestroy 无效。\nconst { ccclass, executionOrder } = _decorator; @ccclass(\u0026#39;Example\u0026#39;) @executionOrder(3) export class Example extends Component { } disallowMultiple # 同一节点上只允许添加一个同类型（含子类）的组件，防止逻辑发生冲突，默认值为 false。\nconst { ccclass, disallowMultiple } = _decorator; @ccclass(\u0026#39;Example\u0026#39;) @disallowMultiple(true) export class Example extends Component { } menu # @menu(path) 用来将当前组件添加到组件菜单中，方便用户查找。\n需要注意该菜单是添加在 属性检查器 面板中按下添加组件按钮后的下拉框内。\nconst { ccclass, menu } = _decorator; @ccclass(\u0026#39;Example\u0026#39;) @menu(\u0026#39;foo/bar\u0026#39;) export class Example extends Component { } 属性装饰器 # 属性装饰器 property 可以被应用在 cc 类的属性或访问器上。属性装饰器用于控制 Cocos Creator 编辑器中对该属性的序列化、属性检查器 中对该属性的展示等。\n@property({ type: Node, visible: true, }) targetNode: Node | null = null; 生命周期 # Cocos Creator 为组件脚本提供了生命周期的回调函数。开发者只需要定义特定的回调函数，Creator 就会在特定的时期自动执行相关脚本，开发者不需要手工调用它们。\n目前提供给开发者的生命周期回调函数主要有（按生命周期触发先后排列）：\nonLoad onEnable start update lateUpdate onDisable onDestroy onLoad # 组件脚本的初始化阶段，我们提供了 onLoad 回调函数。onLoad 回调会在节点首次激活时触发，比如所在的场景被载入，或者所在节点被激活的情况下。在 onLoad 阶段，保证了你可以获取到场景中的其他节点，以及节点关联的资源数据。onLoad 总是会在任何 start 方法调用前执行，这能用于安排脚本的初始化顺序。\nimport { _decorator, Component, Node, SpriteFrame, find } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; @ccclass(\u0026#34;test\u0026#34;) export class test extends Component { @property({type:SpriteFrame}) bulletSprite=null; @property({type:Node}) gun=null; _bulletRect=null; onLoad(){ this._bulletRect=this.bulletSprite.getRect(); this.gun = find(\u0026#39;hand/weapon\u0026#39;, this.node); } } onEnable # 当组件的 enabled 属性从 false 变为 true 时，或者所在节点的 active 属性从 false 变为 true 时，会激活 onEnable 回调。倘若节点第一次被创建且 enabled 为 true，则会在 onLoad 之后，start 之前被调用。\nstart # start 回调函数会在组件第一次激活前，也就是第一次执行 update 之前触发。start 通常用于初始化一些中间状态的数据，这些数据可能在 update 时会发生改变，并且被频繁的 enable 和 disable。\nimport { _decorator, Component, Node } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; @ccclass(\u0026#34;starttest\u0026#34;) export class starttest extends Component { private _timer: number = 0.0; start () { this._timer = 1.0; } update (deltaTime: number) { this._timer += deltaTime; if(this._timer \u0026gt;= 10.0){ console.log(\u0026#39;I am done!\u0026#39;); this.enabled = false; } } } update # 游戏开发的一个关键点是在每一帧渲染前更新物体的行为，状态和方位。这些更新操作通常都放在 update 回调中。\nimport { _decorator, Component, Node } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; @ccclass(\u0026#34;updatetest\u0026#34;) export class updatetest extends Component { update (deltaTime: number) { this.node.setPosition(0.0,40.0 * deltaTime,0.0); } } 增量时间deltaTime # 首先我们得知道动画是什么。动画，动画，动态的图画，即当图画连续动起来就能形成动画，那么在人类眼中，1秒中需要24张图画快速更换才能在大脑中形成视觉暂缓而成的动画。这里我们就把图画的24张叫做24帧，也就是说人眼中需要1秒24帧的动画才能流畅播放。\n因为硬件设备不同性能，A设备1秒能60帧，B设备1秒能120帧。也就是说：每分钟，A能触发update方法60次，B能触发update方法120次。\n假设，要让一个角色速度为3m/s，连续移动\nupdate(){ // 增量距离： 将要移动的距离 = 速度3m/s * 时长1s distance = speed * 1; // 累计距离：成为新的位置 position = position + distance; } 这样就会导致一个问题，如果A设备为60帧，B设备120帧，那么A设备的玩家一分钟移动了3 * 60 = 180m，B设备的玩家移动了3 * 120 = 360m。\n所以为了解决这个问题，那么就引入了增量事件deltaTime，这个变量代表了，当前帧与上一帧中间的间隔时间，它并不是固定的，根据设备运行动态变动的，随时都不一样的。\nupdate(deltaTime){ // 增量距离： 将要移动的距离 = 速度3m/s * 时长1s distance = speed * deltaTime; // 累计距离：成为新的位置 position = position + distance; } lateUpdate # update 会在所有动画更新前执行，但如果我们要在动效（如动画、粒子、物理等）更新之后才进行一些额外操作，或者希望在所有组件的 update 都执行完之后才进行其它操作，那就需要用到 lateUpdate 回调。\nimport { _decorator, Component, Node } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; @ccclass(\u0026#34;lateupdatetest\u0026#34;) export class lateupdatetest extends Component { lateUpdate (deltaTime: number) { this.node.setPosition(0.0,50,0.0); } } onDisable # 当组件的 enabled 属性从 true 变为 false 时，或者所在节点的 active 属性从 true 变为 false 时，会激活 onDisable 回调。\nonDestroy # 当组件或者所在节点调用了 destroy()，则会调用 onDestroy 回调，并在当帧结束时统一回收组件。\n脚本使用 # 访问节点和组件 # 你可以在 属性检查器 里修改节点和组件，也能在脚本中动态修改。动态修改的好处是能够在一段时间内连续地修改属性、过渡属性，实现渐变效果。脚本还能够响应玩家输入，能够修改、创建和销毁节点或组件，实现各种各样的游戏逻辑。要实现这些效果，你需要先在脚本中获得你要修改的节点或组件。\n获得组件所在的节点 # 获得组件所在的节点很简单，只要在组件方法里访问 this.node 变量\nstart() { let node = this.node; node.setPosition(0.0, 0.0, 0.0); } 获得其他组件 # 如果你经常需要获得同一个节点上的其它组件，这就要用到 getComponent 这个 API。\nimport { _decorator, Component, Label } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; @ccclass(\u0026#34;test\u0026#34;) export class test extends Component { private label: any = null start() { this.label = this.getComponent(Label); let text = this.name + \u0026#39;started\u0026#39;; // Change the text in Label Component this.label.string = text; } } 你也可以为 getComponent 传入一个类名。对用户定义的组件而言，类名就是脚本的文件名，并且 区分大小写。例如 \u0026ldquo;SinRotate.ts\u0026rdquo; 里声明的组件，类名就是 \u0026ldquo;SinRotate\u0026rdquo;。\nlet rotate = this.getComponent(\u0026#34;SinRotate\u0026#34;); 如果在节点上找不到你要的组件，getComponent 将返回 null。\n获得其它节点及其组件 # 仅仅能访问节点自己的组件通常是不够的，脚本通常还需要进行多个节点之间的交互。例如，一门自动瞄准玩家的大炮，就需要不断获取玩家的最新位置。Cocos Creator 提供了一些不同的方法来获得其它节点或组件。\n1、利用属性检查器设置节点 # // Cannon.ts import { _decorator, Component, Node } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; @ccclass(\u0026#34;Cannon\u0026#34;) export class Cannon extends Component { // 声明 Player 属性 @property({ type: Node }) private player = null; } 这段代码在 properties 里面声明了一个 player 属性，默认值为 null，并且指定它的对象类型为 Node，然后就可以将节点拖到这个Player控件中。\n2、查找子节点 # 有时候，游戏场景中会有很多个相同类型的对象，像是炮塔、敌人和特效，它们通常都有一个全局的脚本来统一管理。如果用 属性检查器 来一个一个将它们关联到这个脚本上，那工作就会很繁琐。为了更好地统一管理这些对象，我们可以把它们放到一个统一的父物体下，然后通过父物体来获得所有的子物体\n// CannonManager.ts import { _decorator, Component, Node } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; @ccclass(\u0026#34;CannonManager\u0026#34;) export class CannonManager extends Component { start() { let cannons = this.node.children; //... } } 你还可以使用 getChildByName：\nthis.node.getChildByName(\u0026#34;Cannon 01\u0026#34;); 如果子节点的层次较深，你还可以使用 find，find 将根据传入的路径进行逐级查找：\nfind(\u0026#34;Cannon 01/Barrel/SFX\u0026#34;, this.node); 3、全局名字查找 # 当 find 只传入第一个参数时，将从场景根节点开始逐级查找：\nthis.backNode = find(\u0026#34;Canvas/Menu/Back\u0026#34;); 访问已有变量里的值 # 如果你已经在一个地方保存了节点或组件的引用，你也可以直接访问它们，例如现在可以通过import{ } from + 文件名(不含路径) 来获取到对方 exports 的对象。\nGlobal\n// Global.ts, now the filename matters import { _decorator, Component, Node } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; @ccclass(\u0026#34;Global\u0026#34;) export class Global extends Component { public static backNode: any = null; public static backLabel: any = null; } Back\n// Back.ts import { _decorator, Component, Node, Label } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; // this feels more safe since you know where the object comes from import{Global}from \u0026#34;./Global\u0026#34;; @ccclass(\u0026#34;Back\u0026#34;) export class Back extends Component { onLoad() { Global.backNode = this.node; Global.backLabel = this.getComponent(Label); } } 常用节点和组件接口 # 节点状态和层级操作 # 假设我们在一个组件脚本中，通过 this.node 访问当前脚本所在节点。\n1、激活或关闭节点 # this.node.active = false; 设置 active 属性和在编辑器中切换节点的激活、关闭状态，效果是一样的。当一个节点是关闭状态时，它的所有组件都将被禁用。同时，它所有子节点，以及子节点上的组件也会跟着被禁用。要注意的是，子节点被禁用时，并不会改变它们的 active 属性，因此当父节点重新激活的时候它们就会回到原来的状态。\n2、更改节点的父节点 # this.node.parent = parentNode; 或者\nthis.node.removeFromParent(); parentNode.addChild(this.node); 3、索引节点的子节点 # this.node.children //返回节点的所有子节点数组。 this.node.children.length //返回节点的子节点数量。 更改节点的变换（位置、旋转、缩放） # 1、更改节点位置 # 有以下两种方法：\n使用 setPosition 方法： this.node.setPosition(100, 50, 100); this.node.setPosition(new Vec3(100, 50, 100)); 设置 position 变量： this.node.position = new Vec3(100, 50, 100); 2、更改节点旋转 # this.node.setRotation(90, 90, 90); 或通过欧拉角设置本地旋转：\nthis.node.setRotationFromEuler(90, 90, 90); 3、更改节点缩放 # this.node.setScale(2, 2, 2); 创建和销毁节点 # 创建节点 # import { _decorator, Component, Node } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; @ccclass(\u0026#34;test\u0026#34;) export class test extends Component { start(){ let node =new Node(\u0026#39;box\u0026#39;); node.setPosition(0,0,-10); } } 需要注意的是，在上述的示例中通过 new Node 创建出来的节点并不会主动添加到场景内，直至用户调用 director.getScene().addChild(node) 来添加到场景内或者通过 node.parent = {a valid node} 来作为某个节点的子节点。\n克隆节点 # import { _decorator, Component, Node,instantiate, director } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; @ccclass(\u0026#34;test\u0026#34;) export class test extends Component { @property({type:Node}) private target: Node = null; start(){ let scene = director.getScene(); let node = instantiate(this.target); scene.addChild(node); node.setPosition(0, 0,-10); } } 创建预制节点 # import { _decorator, Component, Prefab, instantiate, director } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; @ccclass(\u0026#34;test\u0026#34;) export class test extends Component { @property({type:Prefab}) private target: Prefab = null; start(){ let scene = director.getScene(); let node = instantiate(this.target); scene.addChild(node); node.setPosition(0,0,0); } } 销毁节点 # 通过 node.destroy() 函数，可以销毁节点。值得一提的是，销毁节点并不会立刻被移除，而是在当前帧逻辑更新结束后，统一执行。当一个节点销毁后，该节点就处于无效状态，可以通过 isValid 判断当前节点是否已经被销毁。\nimport { _decorator, Component, Node } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; @ccclass(\u0026#34;test\u0026#34;) export class test extends Component { @property({type:Node}) private target: Node = null; private positionz: number = -20; start(){ // 5秒后销毁节点 setTimeout(function () { this.target.destroy(); }.bind(this), 5000); } update(deltaTime: number){ console.info(this.target.isValid); this.positionz += 1*deltaTime; if (this.target.isValid) { this.target.setPosition(0.0,0.0,this.positionz); } } } 计时器 # 开始一个计时器将每隔 5s 执行一次\nthis.schedule(function() { // 这里的 this 指向 component this.doSomething(); }, 5); 更灵活的计时器（将在 10 秒后开始计时，每 5 秒执行一次回调，重复 3 + 1 次。）\n// 以秒为单位的时间间隔 let interval = 5; // 重复次数 let repeat = 3; // 开始延时 let delay = 10; this.schedule(function() { // 这里的 this 指向 component this.doSomething(); }, interval, repeat, delay); 只执行一次的计时器\nthis.scheduleOnce(function() { // 这里的 this 指向 component this.doSomething(); }, 2); 取消计时器\nthis.count = 0; this.callback = function () { if (this.count == 5) { // 在第六次执行回调时取消这个计时器 this.unschedule(this.callback); } this.doSomething(); this.count++; } this.schedule(this.callback, 1); 取消组件中所有的计时器：unscheduleAllCallbacks\n加载和切换场景 # 在 Cocos Creator 中，我们使用场景文件名（不包含扩展名）来索引指代场景。并通过以下接口进行加载和切换操作：\ndirector.loadScene(\u0026#34;MyScene\u0026#34;); 通过常驻节点进行场景资源管理和参数传递 # 引擎同时只会运行一个场景，当切换场景时，默认会将场景内所有节点和其他实例销毁。如果我们需要用一个组件控制所有场景的加载，或在场景之间传递参数数据，就需要将该组件所在节点标记为「常驻节点」，使它在场景切换时不被自动销毁，常驻内存。\ndirector.addPersistRootNode(myNode); 上面的接口会将 myNode 变为常驻节点，这样挂在上面的组件都可以在场景之间持续作用，我们可以用这样的方法来储存玩家信息，或下一个场景初始化时需要的各种数据。 需要注意的是，目标节点必须为位于层级的根节点，否则设置无效。\n如果要取消一个节点的常驻属性：\ndirector.removePersistRootNode(myNode); 需要注意的是上面的 API 并不会立即销毁指定节点，只是将节点还原为可在场景切换时销毁的节点。\n场景加载回调 # director.loadScene(\u0026#34;MyScene\u0026#34;, onSceneLaunched); onSceneLaunched 就是声明在本脚本中的一个回调函数，在场景加载后可以用来进一步的进行初始化或数据传递的操作。\n预加载场景 # director.loadScene 会在加载场景之后自动切换运行新场景，有些时候我们需要在后台静默加载新场景，并在加载完成后手动进行切换。那就可以预先使用 preloadScene 接口对场景进行预加载：\ndirector.preloadScene(\u0026#34;table\u0026#34;, function () { console.log(\u0026#39;Next scene preloaded\u0026#39;); }); 之后在合适的时间调用 loadScene，就可以真正切换场景。\ndirector.loadScene(\u0026#34;table\u0026#34;); 就算预加载还没完成，你也可以直接调用 director.loadScene，预加载完成后场景就会启动。\n","date":"2025-03-31","externalUrl":null,"permalink":"/posts/69064821/92082869/92c48f08/","section":"文章","summary":"\u003cp\u003eCocos Creator 脚本用于实现用户定义的（游戏）行为，支持 JavaScript 和 TypeScript 两种编程语言。通过编写脚本组件，并将它挂载到场景节点中来驱动场景中的物体。\u003c/p\u003e","title":"3、脚本","type":"posts"},{"content":" 数据类型 # 对于那些只需要保存数据的类型，我们常常需要为其重写toString、equals等函数，针对于这种情况下，Kotlin为我们提供了专门的数据类，数据类不仅能像普通类一样使用，并且自带我们需要的额外成员函数，比如打印到输出、比较实例、复制实例等。\n//在class前面添加data关键字表示为一个数据类 data class User(val name: String, val age: Int) 数据类声明后，编译器会根据主构造函数中声明的所有属性自动为其生成以下函数：\n.equals()/.hashCode() .toString()生成的字符串格式类似于\u0026quot;User(name=John, age=42)\u0026quot; .componentN()与按声明顺序自动生成用于解构的函数 .copy()用于对对象进行拷贝 fun main() { var s1 = Student(\u0026#34;张三\u0026#34;, 18) println(s1) // Student(name=张三, age=18) var s2 = Student(\u0026#34;张三\u0026#34;, 18) println(s1 == s2) // true var (name, age) = s1 println(\u0026#34;name:$name,age:$age\u0026#34;) // name:张三,age:18 } data class Student(val name: String,val age: Int) 当然，为了确保生成代码的一致性和有效性，数据类必须满足以下要求：\n主构造函数中至少有一个参数。 主构造函数中的参数必须标记为val或var。 数据类不能是抽象的、开放的、密封的或内部的。 注意：编译器会且只会根据主构造函数中定义的属性生成对应函数，如果有些时候我们不希望某些属性被添加到自动生成的函数中，我们需要手动将其移出主构造函数\ndata class Person(val name: String) { var age: Int = 0 //age属性不会被处理 } 枚举类型 # 定义 # //在类前面添加enum表示这是一个枚举类型 enum class LightState { GREEN, YELLOW, RED //直接在枚举类内部写上所有枚举的名称，一般全部用大写字母命名 } 类成员 # 同样的，枚举类也可以具有成员\n//同样可以定义成员变量，但是不能命名为name，因为name拿来返回枚举名称了 enum class LightState(val color: String) { GREEN(\u0026#34;绿灯\u0026#34;), YELLOW(\u0026#34;黄灯\u0026#34;), RED(\u0026#34;红灯\u0026#34;); //枚举在定义时也必须填写参数，如果后面还要编写成员函数之类的其他内容，还需在末尾添加分号结束 fun isGreen() = this == LightState.GREEN //定义一个函数也是没问题的 } 使用 # fun main() { val state: LightState = LightState.RED //直接得到红灯 println(state.name) //自带name属性，也就是我们编写的枚举名称，这里是RED } 枚举类型可以用于when表达式进行判断，因为它的状态是有限的\nval state: LightState = LightState.RED val message: String = when(state) { LightState.RED -\u0026gt; \u0026#34;禁止通行\u0026#34; LightState.YELLOW -\u0026gt; \u0026#34;减速通行\u0026#34; LightState.GREEN -\u0026gt; \u0026#34;正常通行\u0026#34; } println(message) //结果为: 禁止通行 匿名类 # 有些时候，可能我们并不需要那种通过class关键字定义的对象，而是以匿名的形式创建一个临时使用的对象，在使用完之后就不再需要了，这种情况完全没有必要定义一个完整的类型，我们可以使用匿名类的形式去编写。\n// 使用object关键字声明一个匿名类并创建其对象，可以直接使用变量接收得到的对象 val obj = object { val name: String = \u0026#34;\u0026#34; override fun toString(): String = \u0026#34;我是一个匿名类\u0026#34; //匿名类默认继承于Any，可以直接重写其toString } println(obj) 可以看到，匿名类除了没名字之外，也可以定义成员，只不过这种匿名类不能定义任何构造函数，因为它是直接创建的，这种写法我们也可以叫做对象表达式。\n匿名类不仅可以直接定义，也可以作为某个类的子类定义，或是某个接口的实现\ninterface Person { fun chat() } fun main() { val obj: Person = object : Person { //直接实现Person接口 override fun chat() = println(\u0026#34;实现的接口 chat\u0026#34;) } obj.chat() //当做Person的实现类使用 } 函数式接口 # 特别的，对于只存在一个抽象函数的接口称为函数式接口或单一抽象方法（SAM）接口，函数式接口可以有N个非抽象成员，但是只能有一个抽象成员。对于函数式接口，可以使用我们前面介绍的Lambda表达式来使代码更简洁\nfun interface KRunnable { //在接口声明前面添加fun关键字 fun invoke() } fun main() { val runnable = KRunnable { //支持使用Lambda替换 println(\u0026#34;我是函数invoke的实现\u0026#34;) } runnable.invoke() } 单例对象 # object关键字除了用于声明匿名类型，也可以用于声明单例类。单例类是什么意思呢？就像它的名字一样，在整个程序中只能存在一个对象，也就是单个实例，不可以创建其他的对象，始终使用的只能是那一个对象。\n//声明的一个单例类 object Singleton { private var name = \u0026#34;你干嘛\u0026#34; override fun toString() = \u0026#34;我叫$name\u0026#34; } fun main() { //通过类名直接得到此单例类的对象 val singleton = Singleton //不可以通过构造函数的形式创建对象 println(singleton) } object Singleton { fun test() = println(\u0026#34;原神，启动！\u0026#34;) } fun main() { Singleton.test() //单例定义的函数直接使用类名即可调用 } 伴生对象 # 现在我们希望一个类既支持单例类那样的直接调用，又支持像一个普通class那样使用，这时该怎么办呢\n使用companion关键字在内部编写一个伴生对象\nclass Student(val name: String, val age: Int) { //使用companion关键字在内部编写一个伴生对象，它同样是单例的 companion object Tools { //伴生对象定义的函数可以直接通过外部类名调用 fun create(name: String, age: Int) = Student(name, age) } } fun main() { //现在Student不仅具有对象的函数，还可以通过类名直接调用其伴生对象通过的函数 val student = Student.create(\u0026#34;小明\u0026#34;, 18) println(student.toString()) } 密封类型 # 有些时候，我们可能会编写一些类给其他人使用，但是我们不希望他们随意继承使用我们提供的类，我们只希望在我们提供的框架内部自己进行使用，这时我们就可以将类或接口设定为密封的。\npackage com.test sealed class A //声明密封类很简单，直接添加sealed关键字即可 sealed class B: A() //密封类同一个模块或包中可以随意继承，并且子类也可以是密封的 ","date":"2025-03-24","externalUrl":null,"permalink":"/posts/b3f5e6ce/1e30d624/14f95822/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e数据类型 \n    \u003cdiv id=\"数据类型\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%95%b0%e6%8d%ae%e7%b1%bb%e5%9e%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e对于那些只需要保存数据的类型，我们常常需要为其重写\u003ccode\u003etoString\u003c/code\u003e、\u003ccode\u003eequals\u003c/code\u003e等函数，针对于这种情况下，Kotlin为我们提供了专门的数据类，数据类不仅能像普通类一样使用，并且自带我们需要的额外成员函数，比如打印到输出、比较实例、复制实例等。\u003c/p\u003e","title":"3、特殊类型","type":"posts"},{"content":" 选择结构 # if-else # fun main(args: Array\u0026lt;String\u0026gt;) { var a = 10 if (a == 1) println(\u0026#34;a == 1\u0026#34;) if (a == 10) println(\u0026#34;a == 10\u0026#34;) if (a == 1) { println(\u0026#34;a == 1\u0026#34;) } else { println(\u0026#34;a != 1\u0026#34;) } if (a == 1) { println(\u0026#34;a == 1\u0026#34;) } else if (a == 10) { println(\u0026#34;a == 10\u0026#34;) } else { println(\u0026#34;a != 1 \u0026amp;\u0026amp; a != 10\u0026#34;) } } 三元表达式 # Kotlin 中没有Java 那样的三元表达式，但是可以使用 if-else 代替\n注意：如果需要这种返回结果的表达式，那么必须要存在else分支\nfun main(args: Array\u0026lt;String\u0026gt;) { var a = 10 var result1 = if (a == 10) \u0026#34;a = 10\u0026#34; else \u0026#34;a != 10\u0026#34; var result2 = if (a == 10) { var str = \u0026#34;a = 10\u0026#34; str // 最后一行作为结果返回 }else{ var str = \u0026#34;a != 10\u0026#34; str } println(result1) println(result2) } when # when定义具有多个分支的条件表达式。它类似于类似Java和C语言中的switch语句。\nfun main(args: Array\u0026lt;String\u0026gt;) { var a = 10 when(a){ 10 -\u0026gt; println(\u0026#34;a is 10\u0026#34;) 20 -\u0026gt; println(\u0026#34;a is 20\u0026#34;) else -\u0026gt; { println(\u0026#34;a is not 10 or 20\u0026#34;) } } } 同样的，when 也可以作为表达式，类似三元表达式那样。\n如果将when用作表达式，则else分支必须存在，除非编译器能推断出分支条件包含了所有可能的情况。\nfun main(args: Array\u0026lt;String\u0026gt;) { var a = 10 var str = when(a){ 10 -\u0026gt; \u0026#34;a is 10\u0026#34; 20 -\u0026gt; \u0026#34;a is 20\u0026#34; else -\u0026gt; { \u0026#34;a is not 10 or 20\u0026#34; } } println(str) } 循环结构 # for # 首先 Kotlin 提供了一种声明区间的方法，比如想要一个包含1、2、3 的区间，就可以这样声明：1..3\nfun main(args: Array\u0026lt;String\u0026gt;) { for(i in 1..3){ println(i) } } 同时，可以控制步长，也就是每次循环变量的增减。\n比如 java 中的循环for (int i = 1,i \u0026lt;= 10,i + 2)，那么 Kotlin 的步长step 2就相当于i + 2，具体如下\nfun main(args: Array\u0026lt;String\u0026gt;) { for (i in 1..10 step 2){ println(i) } } 那如果我们需要从10到1倒着进行遍历呢，那么就可以使用downTo\nfun main(args: Array\u0026lt;String\u0026gt;) { for (i in 10 downTo 1){ println(i) } } while # fun main() { var i = 100 //比如现在我们想看看i不断除以2得到的结果会是什么，但是循环次数我们并不明确 while (i \u0026gt; 0) { //现在唯一知道的是循环条件，只要大于0那么就可以继续除 println(i) i /= 2 //每次循环都除以2 } } 我们也可以反转循环判断的时机，可以先执行循环内容，然后再做循环条件判断\nfun main() { var i = 0 //比如现在我们想看看i不断除以2得到的结果会是什么，但是循环次数我们并不明确 do { //无论满不满足循环条件，先执行循环体里面的内容 println(\u0026#34;Hello World!\u0026#34;) i++ } while (i \u0026lt; 10) //再做判断，如果判断成功，开启下一轮循环，否则结束 } 循环控制 # continue # 我们可以使用continue关键字来跳过本轮循环，直接开启下一轮\nbreak # 我们可以使用break关键字来提前终止整个循环\n嵌套循环下使用 # 虽然使用break和continue关键字能够更方便的控制循环，但是注意在多重循环嵌套下，它只对离它最近的循环生效（就近原则）\n如果我们需要控制外层循环，那么就需要为循环语句打上标记\nfun main(args: Array\u0026lt;String\u0026gt;) { outer@for (i in 1..10){ for(j in 1 .. 10){ if(i == 5) continue@outer println(\u0026#34;$i x $j = ${i*j}\u0026#34;) } } } ","date":"2025-03-21","externalUrl":null,"permalink":"/posts/b3f5e6ce/129ca5af/eb806475/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e选择结构 \n    \u003cdiv id=\"选择结构\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%80%89%e6%8b%a9%e7%bb%93%e6%9e%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eif-else \n    \u003cdiv id=\"if-else\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#if-else\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-kotlin\" data-lang=\"kotlin\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efun\u003c/span\u003e \u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eArray\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003evar\u003c/span\u003e \u003cspan class=\"py\"\u003ea\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e10\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"m\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;a == 1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"m\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;a == 10\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"m\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;a == 1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;a != 1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"m\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;a == 1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"m\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;a == 10\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;a != 1 \u0026amp;\u0026amp; a != 10\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e三元表达式 \n    \u003cdiv id=\"三元表达式\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%89%e5%85%83%e8%a1%a8%e8%be%be%e5%bc%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cp\u003eKotlin 中没有Java 那样的三元表达式，但是可以使用 if-else 代替\u003c/p\u003e","title":"3、流程控制","type":"posts"},{"content":"uni-app能实现一套代码、多端运行，核心是通过编译器 + 运行时实现的：\n编译器：将uni-app统一代码编译生成每个平台支持的特有代码；如在小程序平台，编译器将.vue文件拆分生成wxml、wxss、js等代码。 运行时：动态处理数据绑定、事件代理，保证Vue和平台宿主数据的一致性； uni-app项目根据所依赖的Vue版本不同，编译器的实现也不同：\nvue2：uni-app编译器基于wepback实现 vue3：uni-app编译器基于Vite实现，编译速度更快 条件编译处理多端差异 # uni-app 已将常用的组件、API封装到框架中，开发者按照 uni-app 规范开发即可保证多平台兼容，大部分业务均可直接满足。\n但每个平台有自己的一些特性，因此会存在一些无法跨平台的情况。\n大量写 if else，会造成代码执行性能低下和管理混乱。 编译到不同的工程后二次修改，会让后续升级变的很麻烦。 为每个平台重写，明明主业务逻辑又一样 在 C 语言中，通过 #ifdef、#ifndef 的方式，为 Windows、Mac 等不同 OS 编译不同的代码。\nuni-app 团队参考这个思路，为 uni-app 提供了条件编译手段，在一个工程里优雅的完成了平台个性化实现。\n使用方式 # 以 #ifdef 或 #ifndef 加 %PLATFORM% 开头，以 #endif 结尾。\n#ifdef：if defined 仅在某平台存在 #ifndef：if not defined 除了某平台均存在 %PLATFORM%：平台名称 #ifdef APP-PLUS 仅出现在 App 平台下的代码 #endif #ifndef H5 除了 H5 平台，其它平台均存在的代码（注意if后面有个n） #endif #ifdef H5 || MP-WEIXIN 在 H5 平台或微信小程序平台存在的代码 #endif %PLATFORM% 可取值如下\n值 生效条件 版本支持 VUE3 uni-app js引擎版用于区分vue2和3 HBuilderX 3.2.0+ VUE2 uni-app js引擎版用于区分vue2和3 UNI-APP-X 用于区分是否是uni-app x项目 HBuilderX 3.9.0+ uniVersion 用于区分编译器的版本 HBuilderX 3.9.0+ APP App APP-PLUS uni-app js引擎版编译为App时 APP-PLUS-NVUE或APP-NVUE App nvue 页面 APP-ANDROID App Android 平台 APP-IOS App iOS 平台 APP-HARMONY App HarmonyOS Next 平台 H5 H5（推荐使用 WEB） WEB web（同H5） HBuilderX 3.6.3+ MP-WEIXIN 微信小程序 MP-ALIPAY 支付宝小程序 MP-BAIDU 百度小程序 MP-TOUTIAO 抖音小程序 MP-LARK 飞书小程序 MP-QQ QQ小程序 MP-KUAISHOU 快手小程序 MP-JD 京东小程序 MP-360 360小程序 MP-HARMONY 鸿蒙元服务 HBuilderX 4.34+ MP-XHS 小红书小程序 MP 微信小程序/支付宝小程序/百度小程序/抖音小程序/飞书小程序/QQ小程序/360小程序/鸿蒙元服务 QUICKAPP-WEBVIEW 快应用通用(包含联盟、华为) QUICKAPP-WEBVIEW-UNION 快应用联盟 QUICKAPP-WEBVIEW-HUAWEI 快应用华为 注意：\n条件编译是利用注释实现的，在不同语法里注释写法不一样，js/uts使用 // 注释、css 使用 /* 注释 */、vue/nvue/uvue 模板里使用 \u0026lt;!-- 注释 --\u0026gt;； 条件编译APP-PLUS包含APP-NVUE和APP-VUE，APP-PLUS-NVUE和APP-NVUE没什么区别，为了简写后面出了APP-NVUE ； 对于未定义平台名称，可能是名称写错了，也可能是低版本HBuilderX还不认识这个平台。此时的条件编译，#ifdef 中的代码不会生效，而 #ifndef 中的代码会生效； 使用条件编译请保证编译前和编译后文件的语法正确性，即要保障无论条件编译是否生效都能通过语法校验。比如：json文件中不能有多余的逗号，js中不能重复导入； { \u0026#34;key\u0026#34;: \u0026#34;a\u0026#34; // #ifdef MP-WEIXIN ,\u0026#34;key\u0026#34;: \u0026#34;b\u0026#34; // #endif } // #ifdef MP-WEIXIN import a as aWx from \u0026#39;a/wx\u0026#39; // #endif // #ifndef MP-WEIXIN import a as aIndex from \u0026#39;a/index\u0026#39; // #endif var a // #ifdef MP-WEIXIN a = aWx // #endif // #ifndef MP-WEIXIN a = aIndex // #endif ","date":"2025-03-17","externalUrl":null,"permalink":"/posts/bafd68f1/f2b2596e/732a0638/","section":"文章","summary":"\u003cp\u003e\u003ccode\u003euni-app\u003c/code\u003e能实现一套代码、多端运行，核心是通过\u003ccode\u003e编译器 + 运行时\u003c/code\u003e实现的：\u003c/p\u003e","title":"3、编译器","type":"posts"},{"content":" OAuth2 # OAuth 2.0 是目前最流行的授权标准（协议），用来授权第三方应用，获取用户数据。\nOAuth 就是一种授权机制。数据的所有者告诉系统，同意授权第三方应用进入系统，获取这些数据。系统从而产生一个短期的进入令牌（token），用来代替密码，供第三方应用使用。\nCAS的单点登录和OAuth2的区别 # SSO ：单点登录（Single sign-on）是在多个应用系统中，用户只需要登录一次就可以访问所有相互信任的应用系统。\nCAS ：中央认证服务（Central Authentication Service），一个基于Kerberos票据方式实现SSO单点登录的框架，为Web 应用系统提供一种可靠的单点登录解决方法（属于 Web SSO ）。\n微服务的认证和授权思路 # 通常微服务的认证和授权思路有两种：\n所有的认证授权都由一个独立的用户认证授权服务器负责，它只负责发放Token，然后网关只负责转发请求到各个微服务模块，由微服务各个模块自行对Token进行校验处理。\n另一种是网关不但承担了流量转发作用，还承担认证授权流程，当前请求的认证信息由网关中继给下游服务器。\n第二种结合了OAuth2体系，网关不仅仅承担流量转发功能，认证授权也是在网关层处理的，令牌会中继给下游服务。这种模式下需要搭建一个UAA（User Account And Authentication）服务。它非常灵活，它可以管理用户，也可以让受信任的客户端自己管理用户，它只负责对客户端进行认证（区别于用户认证）和对客户端进行授权。目前使用OAuth2对微服务进行安全体系建设的都使用这种方式。\noauth2分布式认证架构 # 前后端分离架构设计 # 基本概念 # oauth2包含以下角色：\n客户端：本身不存储资源，需要通过资源拥有者授权去请求资源服务器的资源。 资源拥有者：通常为用户 授权（认证）服务器：用于服务提供商对资源拥有者的身份进行认证、对访问资源进行授权，认证成功颁发令牌（access_token)，作为客户端访问资源服务器的凭据，配置认证服务器必须实现的endpoints： AuthorizationEndpoint，用于认证请求，默认URL：/oauth/authorize TokenEndPoint，用于访问令牌的请求，默认URL：/oauth/token 资源服务器：存储资源的服务器，该资源服务需要实现OAuth2的过滤器（如果使用了GateWay作为服务统一出口，那么这个过滤器再gateway上声明就可以）： OAuth2AuthenticationProcessingFilter：用于对请求给出的身份令牌解析鉴权 四种模式 # 授权码模式（authorization_code） # 需要的角色：资源所有者、应用程序、授权服务器、资源服务器。\n需要的参数：授权码（需要用户认证并授权后获取）、应用的授信凭据\nOAuth 2.0的一个基本授权流程。\n实现步骤如下：\n用户在应用程序中，应用程序尝试获取用户保存在资源服务器上的信息，比如用户的身份信息和头像，应用程序首先让重定向用户到授权服务器，告知申请资源的读权限，并提供自己的client id。 到授权服务器，用户输入用户名和密码，服务器对其认证成功后，提示用户即将要颁发一个读权限给应用程序，在用户确认后，授权服务器颁发一个授权码（authorization code）并重定向用户回到应用程序。 应用程序获取到授权码之后，使用这个授权码和自己的client id/secret向认证服务器申请访问令牌/刷新令牌]（access token/refresh token）。授权服务器对这些信息进行校验，如果一切OK，则颁发给应用程序访问令牌/刷新令牌。 应用程序在拿到访问令牌之后，向资源服务器申请用户的资源信息 资源服务器在获取到访问令牌后，对令牌进行解析（如果令牌已加密，则需要进行使用相应算法进行解密）并校验，并向授权服务器校验其合法性，如果一起OK，则返回应用程序所需要的资源信息。 简化模式、隐式（implicit） # 需要的角色：（资源所有者、应用程序）、授权服务器、资源服务器。\n需要的参数：应用client id、用户的授信凭据\n应用程序运行在客户端，一个最大的变化就是其变成了公开应用程序（Public Client），应用程序的运行完全暴露在用户的控制之中。在这种场景下，应用程序是无法隐藏自己的一些敏感数据，比如client secret和授权码，在这个方式下，再向授权服务器获取授权码是多此一举。为此OAuth 2.0提供简化模式，授权服务器在校验好用户信息后，直接颁发给应用程序访问资源服务器的访问令牌。换句话说，应用程序在获取访问令牌时无需提供授权码和client secret。\n实现步骤如下：\n用户在应用程序中，应用程序尝试获取用户保存在资源服务器上的信息，比如用户的身份信息和头像，应用程序首先让用户重定向到授权服务器，告知申请资源的读权限，并提供自己的client id。在重定向的过程中，应用程序指定使用Implicit Grant授权方式。 在授权服务器，用户输入用户名和密码，服务器对其认证成功后，提示用户即将要颁发一个读权限给应用程序，在用户确认后，授权服务器直接颁发一个访问令牌并重定向用户回到应用程序。 应用程序在拿到访问令牌之后，向资源服务器申请用户的资源信息 资源服务器在获取到访问令牌后，对令牌进行解析（如果令牌已加密，则需要进行使用相应算法进行解密）并校验，并向授权服务器校验其合法性，如果一起OK，则返回应用程序所需要的资源信息。 应用授信模式、客户端模式（client_credentials） # 需要的角色：应用程序、授权服务器、资源服务器。\n需要的参数：应用的授信凭据\n这种模式的资源所有者角色不参与授权交互；应用程序角色本身就是资源所有者。\n实现步骤如下：\n应用程序尝试获取在资源服务器上的信息，应用程序直接向授权服务器申请访问令牌，告知申请资源的读权限，并提供自己的授信凭证（client id/secret）。在申请请求中，应用程序指定使用client credentials授权方式。在授权服务器，服务器对其client credentials校验成功后，授权服务器直接颁发一个访问令牌给应用程序。 应用程序在拿到访问令牌之后，向资源服务器申请用户的资源信息 资源服务器在获取到访问令牌后，对令牌进行解析（如果令牌已加密，则需要进行使用相应算法进行解密）并校验，并向授权服务器校验其合法性，如果一起OK，则返回应用程序所需要的资源信息。 用户授信模式、密码式（password） # 需要的角色：资源所有者、应用程序、授权服务器、资源服务器。\n需要的参数：应用的授信凭据、用户的授信凭据\n在基本的授权码模式中，用户需要跳转到授权服务器上，使用用户名和密码登录后拿到授权码，然后把授权码交给应用程序，然后再去申请访问令牌。但有些时候，能否省去这个来回的跳转过程，把用户名和密码直接交给应用程序，让应用程序去申请访问令牌。\n实现步骤如下：\n用户在应用程序中，应用程序首先让用户到登录页面输入用户名和密码。 应用程序拿到资源所有者的用户名和密码，加上自己的client id/secret一同向认证服务器申请访问令牌/刷新令牌。授权服务器对这些信息进行校验，如果一切OK，则颁发给应用程序访问令牌/刷新令牌。 应用程序在拿到访问令牌/刷新令牌之后，向资源服务器申请用户的资源信息。 资源服务器在获取到访问令牌后，对令牌进行解析（如果令牌已加密，则需要进行使用相应算法进行解密）并校验，并向授权服务器校验其合法性，如果一起OK，则返回应用程序所需要的资源信息。 四种模式的选择 # 资源所有者\n自然人用户：资源所有者是我们普通的自然人。\n应用程序：是指当资源的所有者不是我们普通的自然人，而是应用程序，也就是机器。\n应用程序类型\nWeb应用：运行在后端web服务器上的应用，可以安全存储客户端密钥\n基于user-agent的应用：运行在浏览器JS应用，通常无法安全存储客户端密钥\n原生应用：桌面应用，移动应用（安卓和iOS应用）\n第一方和第三方应用\n第一方应用：指可信赖的应用，其可以接触用户的密码等敏感信息，应用安全可靠。\n第三方应用：指由第三方开发的应用，其安全性不受自己控制，需将用户的密码等敏感信息和第三方应用隔离开，颁发令牌给第三方应用，第三方应用凭借令牌访问资源。\n使用 # 1、新建UAA认证微服务，引入依赖 # \u0026lt;!-- springboot --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- 服务注册中心 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-alibaba-nacos-discovery\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- 公共模块 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;grady-young-common\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- 服务配置中心 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-alibaba-nacos-config\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- spring-security依赖 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-security\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!--oauth2--\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-oauth2\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- jwt依赖 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;io.jsonwebtoken\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jjwt\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- mybatis-plus依赖 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.baomidou\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mybatis-plus-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- 连接池，用于创建多个redis-template实例 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.commons\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;commons-pool2\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- mysql依赖 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;mysql\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mysql-connector-java\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- druid数据库连接池 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;druid-spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、创建授权服务配置 # @Configuration @EnableAuthorizationServer public class AuthorizationServer extends AuthorizationServerConfigurerAdapter { /** * 用来配置支持的客户端详情，在这里初始化客户端详情配置 * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { } /** * 用来配置令牌访问端点和令牌服务，用来生成、颁发令牌 * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { } /** * 对于令牌端点的安全约束 * @param security * @throws Exception */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { } } 客户端详情配置（ClientDetailsServiceConfigurer），可以使用内存存储也可以使用JDBC数据库存储，ClientDetails重要的属性：\nclientId：标识客户端ID\nsecret：客户端安全码（受信任的客户端才有）\nscope：限制客户端访问范围，如果为空（默认），客户端可以访问所有资源\nauthorizedGrantTypes：客户端可以使用的授权类型\nauthorities：客户端的权限（基于spring security authorities）\n/** * 用来配置支持的客户端详情，在这里初始化客户端详情配置 * @param clients * @throws Exception */ public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 使用内存方式配置 clients.inMemory() .withClient(\u0026#34;test\u0026#34;) //客户端id .secret(\u0026#34;abcdef-abcdef\u0026#34;)//客户端密钥 .resourceIds(\u0026#34;r1\u0026#34;) //客户端可访问的资源列表 .authorizedGrantTypes( //客户端可用来申请令牌的方式 \u0026#34;authorization_code\u0026#34;, \u0026#34;password\u0026#34;, \u0026#34;client_credentials\u0026#34;, \u0026#34;implicit\u0026#34;, \u0026#34;refresh_token\u0026#34;) .scopes(\u0026#34;all\u0026#34;) //允许访问的范围 .autoApprove(false) //跳转到授权页面 .redirectUris(\u0026#34;https://www.baidu.com\u0026#34;); //验证回调地址 } 管理令牌服务（AuthorizationServerTokenServices），接口定义了一些操作可以对令牌进行管理\n定义TokenConfig\n@Configuration public class TokenConfig { /** * 配置令牌存储策略 * @return */ @Bean public TokenStore tokenStore(){ // 内存方式，生成普通令牌 return new InMemoryTokenStore(); } } 在上面写的授权服务配置AuthorizationServer中，配置令牌服务\n@Autowired private ClientDetailsService clientDetailsService; @Autowired private TokenStore tokenStore; /** * 配置令牌服务 * @return */ @Bean public AuthorizationServerTokenServices authorizationServerTokenServices(){ DefaultTokenServices services = new DefaultTokenServices(); services.setClientDetailsService(clientDetailsService); // 配置客户端信息服务 services.setSupportRefreshToken(true); //是否产生刷新令牌 services.setTokenStore(tokenStore); //配置令牌存储策略 services.setAccessTokenValiditySeconds(7200); //令牌有效期 services.setRefreshTokenValiditySeconds(259200); //刷新令牌有效期 return services; } 配置令牌访问端点（AuthorizationServerEndpointsConfigurer），这个配置对象有方法pathMapping()可以替换默认端点，第一个参数是要替换的默认端点，第二个参数是自定义端点。默认端点有：\n/oauth/authorize：授权端点\n/oauth/token：令牌端点\n/oauth/confirm_access：用户确认授权提交端点\n/oauth/error：授权服务错误信息端点\n/oauth/check_token：用于资源服务进行令牌解析、校验的端点\n/oauth/token_key：提供公有密钥的端点\n/** * 暂时使用采用内存方式作为授权码存储 * @return */ @Bean public AuthorizationCodeServices authorizationCodeServices(){ return new InMemoryAuthorizationCodeServices(); } @Autowired private AuthenticationManager authenticationManager; @Autowired private AuthorizationCodeServices authorizationCodeServices; /** * 用来配置令牌访问端点和令牌服务，用来生成、颁发令牌 * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager) // 用于密码模式或其他自定义的认证模式 .authorizationCodeServices(authorizationCodeServices) // 授权码模式 .tokenServices(authorizationServerTokenServices()) // 令牌管理服务 .allowedTokenEndpointRequestMethods(HttpMethod.POST); // 允许post方式 } 令牌端点安全约束（AuthorizationServerSecurityConfigurer）\n/** * 对于令牌端点的安全约束 * @param security * @throws Exception */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security .checkTokenAccess(\u0026#34;permitAll()\u0026#34;) //允许任何校验令牌的请求 .tokenKeyAccess(\u0026#34;permitAll()\u0026#34;) //允许提供公钥 .allowFormAuthenticationForClients(); //允许表单认证 } 3、配置SecurityConfig、userDetailsService，具体配置参考前面单体服务\n3、简单测试各个模式 # 授权码模式（authorization_code） # 授权码由第三方（认证服务器）颁发，可以使用授权码获取令牌，具体流程：\n1、资源拥有者打开客户端，客户端要求资源拥有者授权，他将浏览器重定向到授权服务器，附带客户端信息\nGET /oauth/authorize?client_id=test\u0026amp;response_type=code\u0026amp;scope=all\u0026amp;redirect_url=https://www.baidu.com client_id：客户端id\nresponse_type：授权模式，固定为code\nscope：客户端权限\nredirect_url：跳转url，申请授权码成功会跳转到此地址，后面会加上授权码code\n2、认证账号密码\n3、登录成功，跳转授权页面，选择第一个Approve，进行授权\n4、跳转到之前的redirect_url，并且附带了code\n5、使用授权码申请令牌，一个授权码只能使用一次\nPOST /oauth/token application/x-www-form-urlencoded client_id 客户端id client_secret 客户端密钥 grant_type 申请模式(授权码模式固定为authorization_code) code 授权码 redirect_url 跳转地址，和申请授权码时一致 简化模式（token） # 和授权码模式第一步比较相似，只不过grant_type改为token，请求验证成功后，会直接将token以hash模式附带在redirect_url后面，请求地址：\nGET /oauth/authorize?client_id=test\u0026amp;response_type=token\u0026amp;scope=all\u0026amp;redirect_url=https://www.baidu.com 得到结果：\n密码模式（password） # 密码模式可能会泄漏账号密码给客户端，所以一般用在自己写的客户端\nPOST /oauth/token application/x-www-form-urlencoded client_id 客户端id client_secret 客户端密钥 grant_type 申请模式(密码模式固定为password) username 用户名 password 密码 redirect_url 跳转地址，和申请授权码时一致 客户端模式（client_credentials） # 客户端模式比密码模式还要简单，只需要客户端id、客户端密钥就可以申请token\nPOST /oauth/token application/x-www-form-urlencoded client_id 客户端id client_secret 客户端密钥 grant_type 申请模式(客户端模式固定为client_credentials) redirect_url 跳转地址，和申请授权码时一致 集成jwt # 1、在TokenConfig中配置JWT相关，使用对称加密，配置盐值\n@Configuration public class TokenConfig { /** * jwt令牌加密盐值 */ public static final String SIGNING_KEY = \u0026#34;sojff#OJSDF-9IOFH*124\u0026#34;; /** * jwt令牌存储策略 * @return */ @Bean public JwtTokenStore jwtTokenStore(){ return new JwtTokenStore(jwtAccessTokenConverter()); } /** * jwt令牌生成策略 * @return */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey(SIGNING_KEY); return converter; } } 2、在AuthorizationServer中配置令牌增强，使用jwt令牌，使用新的JwtTokenStore，替换之前的TokenStore\n/** * 配置令牌服务 * @return */ @Bean public AuthorizationServerTokenServices authorizationServerTokenServices(){ DefaultTokenServices services = new DefaultTokenServices(); services.setClientDetailsService(clientDetailsService); // 配置客户端信息服务 services.setSupportRefreshToken(true); //是否产生刷新令牌 services.setTokenStore(jwtTokenStore); //配置令牌存储策略 // 配置令牌增强，使用jwt TokenEnhancerChain chain = new TokenEnhancerChain(); chain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter)); services.setTokenEnhancer(chain); services.setAccessTokenValiditySeconds(7200); //令牌有效期 services.setRefreshTokenValiditySeconds(259200); //刷新令牌有效期 return services; } 3、重新请求令牌，发现令牌已经成功使用jwt进行生成\n4、校验令牌，发现用户信息已经存储\noauth2数据持久化 # OAuth2.0的服务端和资源端都不是我们自己写的，都是springsecurity框架给我们写的，既然是springsecurity框架的，那么客户端的信息保存在数据库里面的时候，这个数据库的表结构就需要使用springsecurity框架定义的。\nDROP TABLE IF EXISTS `oauth_client_details`; CREATE TABLE `oauth_client_details` ( `client_id` varchar(255) NOT NULL COMMENT \u0026#39;客户端id\u0026#39;, `resource_ids` varchar(255) DEFAULT NULL COMMENT \u0026#39;客户端所能访问的资源id集合\u0026#39;, `client_secret` varchar(255) DEFAULT NULL COMMENT \u0026#39;用于指定客户端(client)的访问密匙\u0026#39;, `scope` varchar(255) DEFAULT NULL COMMENT \u0026#39;指定客户端申请的权限范围\u0026#39;, `authorized_grant_types` varchar(255) DEFAULT NULL COMMENT \u0026#39;指定客户端支持的grant_type\u0026#39;, `web_server_redirect_uri` varchar(255) DEFAULT NULL COMMENT \u0026#39;客户端的重定向URI,\u0026#39;, `authorities` varchar(255) DEFAULT NULL COMMENT \u0026#39;指定客户端所拥有的Spring Security的权限值\u0026#39;, `access_token_validity` int(11) DEFAULT NULL COMMENT \u0026#39;设定客户端的access_token的有效时间值(单位:秒),可选，默认的有效时间值12小时\u0026#39;, `refresh_token_validity` int(11) DEFAULT NULL COMMENT \u0026#39;设定客户端的refresh_token的有效时间值\u0026#39;, `additional_information` varchar(255) DEFAULT NULL COMMENT \u0026#39;预留的字段\u0026#39;, `autoapprove` varchar(255) DEFAULT NULL COMMENT \u0026#39;设置用户是否自动Approval操作\u0026#39; ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `oauth_access_token`; CREATE TABLE `oauth_access_token` ( `token_id` varchar(255) DEFAULT NULL, `token` longblob, `authentication_id` varchar(255) DEFAULT NULL, `user_name` varchar(255) DEFAULT NULL, `client_id` varchar(255) DEFAULT NULL, `authentication` longblob, `refresh_token` varchar(255) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `oauth_approvals`; CREATE TABLE `oauth_approvals` ( `userId` varchar(255) DEFAULT NULL, `clientId` varchar(255) DEFAULT NULL, `scope` varchar(255) DEFAULT NULL, `status` varchar(10) DEFAULT NULL, `expiresAt` datetime DEFAULT NULL, `lastModifiedAt` datetime DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `oauth_client_token`; CREATE TABLE `oauth_client_token` ( `token_id` varchar(255) DEFAULT NULL, `token` longblob, `authentication_id` varchar(255) DEFAULT NULL, `user_name` varchar(255) DEFAULT NULL, `client_id` varchar(255) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `oauth_code`; CREATE TABLE `oauth_code` ( `code` varchar(255) DEFAULT NULL, `authentication` varbinary(2550) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `oauth_refresh_token`; CREATE TABLE `oauth_refresh_token` ( `token_id` varchar(255) DEFAULT NULL, `token` longblob, `authentication` longblob ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 对于之前存在内存中的信息改为存储到数据库\n@Autowired private DataSource dataSource; /** * 配置使用数据库作为授权码存储方式 * @return */ @Bean public AuthorizationCodeServices authorizationCodeServices(){ return new JdbcAuthorizationCodeServices(dataSource); } /** * 配置从数据库获得客户端详情 * @return */ @Bean public ClientDetailsService clientDetailsService(){ JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource); jdbcClientDetailsService.setPasswordEncoder(passwordEncoder); return jdbcClientDetailsService; } ","date":"2025-02-26","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/82b168e2/652a28c2/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eOAuth2 \n    \u003cdiv id=\"oauth2\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#oauth2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eOAuth 2.0 是目前最流行的授权标准（协议），用来授权第三方应用，获取用户数据。\u003c/p\u003e","title":"3、OAuth2","type":"posts"},{"content":"文件名：.gitignore\n# kdiff3 ignore *.orig # maven ignore .mvn/ target/ # eclipse ignore .settings/ .project .classpath # idea ignore .idea/ *.ipr *.iml *.iws # vscode ignore .vscode/ # temp ignore *.log *.cache *.diff *.patch *.tmp # system ignore .DS_Store Thumbs.db # package ignore (optional) *.war *.zip *.tar *.tar.gz # node node_modules/ ","date":"2025-02-11","externalUrl":null,"permalink":"/posts/f1b56a1d/712987f6/70a10578/","section":"文章","summary":"\u003cp\u003e文件名：\u003ccode\u003e.gitignore\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# kdiff3 ignore\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e*.orig\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# maven ignore\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e.mvn/\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003etarget/\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# eclipse ignore\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e.settings/\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e.project\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e.classpath\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# idea ignore\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e.idea/\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e*.ipr\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e*.iml\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e*.iws\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# vscode ignore\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e.vscode/\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# temp ignore\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e*.log\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e*.cache\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e*.diff\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e*.patch\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e*.tmp\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# system ignore\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e.DS_Store\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eThumbs.db\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# package ignore (optional)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e*.war\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e*.zip\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e*.tar\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e*.tar.gz\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# node\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enode_modules/\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"3、gitignore","type":"posts"},{"content":" Parent # \u0026lt;parent\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-parent\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.3.0.RELEASE\u0026lt;/version\u0026gt; \u0026lt;/parent\u0026gt; 常用依赖 # Web场景启动器 # \u0026lt;!-- Web场景启动器 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 单元测试 # \u0026lt;!-- 单元测试 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-test\u0026lt;/artifactId\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;exclusions\u0026gt; \u0026lt;!-- JUnit Vintage是为了兼容3和4的一个engine，排除 --\u0026gt; \u0026lt;exclusion\u0026gt; \u0026lt;groupId\u0026gt;org.junit.vintage\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;junit-vintage-engine\u0026lt;/artifactId\u0026gt; \u0026lt;/exclusion\u0026gt; \u0026lt;/exclusions\u0026gt; \u0026lt;/dependency\u0026gt; Mybatis场景启动器 # \u0026lt;!-- Mybatis场景启动器 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.mybatis.spring.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mybatis-spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.1.4\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; Mbatis-Plus场景启动器 # \u0026lt;!-- Mbatis-Plus场景启动器 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.baomidou\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mybatis-plus-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.4.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; MySQL # \u0026lt;!-- MySQL --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;mysql\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mysql-connector-java\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;8.0.15\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; Oracle # \u0026lt;!-- Oracle --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.oracle\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;ojdbc6\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;11.2.0.3\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; Druid场景启动器 # \u0026lt;!-- Druid连接池场景启动器 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;druid-spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.6\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; Lombok # \u0026lt;!-- Lombok --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.projectlombok\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;lombok\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.18.22\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; Redis场景启动器 # \u0026lt;!-- Redis场景启动器 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-data-redis\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; Thymeleaf场景启动器 # \u0026lt;!-- Thymeleaf场景启动器 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-thymeleaf\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; JSP解析器 # \u0026lt;!-- jsp依赖 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.tomcat.embed\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;tomcat-embed-jasper\u0026lt;/artifactId\u0026gt; \u0026lt;scope\u0026gt;provided\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;javax.servlet\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jstl\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; Fileupload文件上传组件 # \u0026lt;!-- Fileupload文件上传组件 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;commons-fileupload\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;commons-fileupload\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.3.3\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; fastjson # \u0026lt;!-- fastjson --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;fastjson\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.7\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; pagehelper分页插件 # \u0026lt;!-- pagehelper分页插件 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.github.pagehelper\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;pagehelper-spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.3.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; AOP # \u0026lt;!-- AOP --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.aspectj\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;aspectjweaver\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-aop\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; Shiro # \u0026lt;!-- Shiro --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.shiro\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;shiro-spring\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.4.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.shiro\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;shiro-ehcache\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.3.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.shiro\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;shiro-all\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.shiro\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;shiro-core\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.shiro\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;shiro-web\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; POI # \u0026lt;!-- POI --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.poi\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;poi\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.1.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.poi\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;poi-ooxml\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.1.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.poi\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;poi-ooxml-schemas\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.1.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; Hutool # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;cn.hutool\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;hutool-all\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;5.7.8\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; SpringBoot打包插件 # spring-boot-maven-plugin是Spring Boot提供的maven打包插件。可以将项目直接打包为可运行的jar包或war包。\n插件提供了6个maven goal build-info：生成项目的构建信息文件 build-info.properties help：用于展示spring-boot-maven-plugin的帮助信息。使用命令行mvn spring-boot:help -Ddetail=true -Dgoal=\u0026lt;goal-name\u0026gt;可展示goal的参数描述信息。 repackage：可生成可执行的jar包或war包。插件的核心goal。 run：运行 Spring Boot 应用 start：在集成测试阶段，控制生命周期 stop：在集成测试阶段，控制生命周期 \u0026lt;build\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;!-- maven插件，打包需要 --\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;repackage\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;includeSystemScope\u0026gt;true\u0026lt;/includeSystemScope\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven.plugins\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-compiler-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;source\u0026gt;${java.version}\u0026lt;/source\u0026gt; \u0026lt;target\u0026gt;${java.version}\u0026lt;/target\u0026gt; \u0026lt;encoding\u0026gt;UTF-8\u0026lt;/encoding\u0026gt; \u0026lt;compilerArguments\u0026gt; \u0026lt;verbose/\u0026gt; \u0026lt;bootclasspath\u0026gt;${java.home}/lib/rt.jar${path.separator}${java.home}/lib/jce.jar\u0026lt;/bootclasspath\u0026gt; \u0026lt;/compilerArguments\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/build\u0026gt; 热部署（IDEA） # 1、添加热部署插件 # \u0026lt;!-- 热部署，更改代码不需要重新启动服务 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-devtools\u0026lt;/artifactId\u0026gt; \u0026lt;optional\u0026gt;true\u0026lt;/optional\u0026gt; \u0026lt;/dependency\u0026gt; 2、打开idea自动编译 # 3、开启注册 # 快捷键ctrl+alt+shift+/\n4、重启idea # 静态资源混淆压缩 # 对css、js等进行混淆、压缩\n\u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.primefaces.extensions\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;resources-optimizer-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;!-- 支持jdk8下的最新版本，再往上的版本就不支持jdk8了 --\u0026gt; \u0026lt;version\u0026gt;2.4.4\u0026lt;/version\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;id\u0026gt;optimize\u0026lt;/id\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;optimize\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;!-- 是否不执行压缩 --\u0026gt; \u0026lt;skip\u0026gt;false\u0026lt;/skip\u0026gt; \u0026lt;!-- 压缩级别 --\u0026gt; \u0026lt;compilationLevel\u0026gt;SIMPLE_OPTIMIZATIONS\u0026lt;/compilationLevel\u0026gt; \u0026lt;!-- js规范版本 --\u0026gt; \u0026lt;languageIn\u0026gt;ECMASCRIPT_2015\u0026lt;/languageIn\u0026gt; \u0026lt;resourcesSets\u0026gt; \u0026lt;resourcesSet\u0026gt; \u0026lt;!-- js文件所在的目录，如果不配置的话会将全部js进行压缩 --\u0026gt; \u0026lt;inputDir\u0026gt;${project.build.directory}/classes/static\u0026lt;/inputDir\u0026gt; \u0026lt;includes\u0026gt; \u0026lt;!-- 需要压缩的文件，支持通配符 --\u0026gt; \u0026lt;include\u0026gt;**/*.js\u0026lt;/include\u0026gt; \u0026lt;include\u0026gt;**/*.css\u0026lt;/include\u0026gt; \u0026lt;/includes\u0026gt; \u0026lt;excludes\u0026gt; \u0026lt;!-- 不压缩的文件，支持通配符 --\u0026gt; \u0026lt;exclude\u0026gt;**/*.min.js\u0026lt;/exclude\u0026gt; \u0026lt;exclude\u0026gt;**/*.min.css\u0026lt;/exclude\u0026gt; \u0026lt;/excludes\u0026gt; \u0026lt;/resourcesSet\u0026gt; \u0026lt;/resourcesSets\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; ","date":"2025-01-23","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/4f5b7444/f70b2122/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eParent \n    \u003cdiv id=\"parent\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#parent\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;parent\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.springframework.boot\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003espring-boot-starter-parent\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e2.3.0.RELEASE\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/parent\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e常用依赖 \n    \u003cdiv id=\"常用依赖\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b8%b8%e7%94%a8%e4%be%9d%e8%b5%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eWeb场景启动器 \n    \u003cdiv id=\"web场景启动器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#web%e5%9c%ba%e6%99%af%e5%90%af%e5%8a%a8%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- Web场景启动器 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.springframework.boot\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003espring-boot-starter-web\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e单元测试 \n    \u003cdiv id=\"单元测试\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8d%95%e5%85%83%e6%b5%8b%e8%af%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- 单元测试 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.springframework.boot\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003espring-boot-starter-test\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;scope\u0026gt;\u003c/span\u003etest\u003cspan class=\"nt\"\u003e\u0026lt;/scope\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;exclusions\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c\"\u003e\u0026lt;!-- JUnit Vintage是为了兼容3和4的一个engine，排除 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;exclusion\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.junit.vintage\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003ejunit-vintage-engine\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;/exclusion\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;/exclusions\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eMybatis场景启动器 \n    \u003cdiv id=\"mybatis场景启动器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#mybatis%e5%9c%ba%e6%99%af%e5%90%af%e5%8a%a8%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- Mybatis场景启动器 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.mybatis.spring.boot\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003emybatis-spring-boot-starter\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e2.1.4\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eMbatis-Plus场景启动器 \n    \u003cdiv id=\"mbatis-plus场景启动器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#mbatis-plus%e5%9c%ba%e6%99%af%e5%90%af%e5%8a%a8%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- Mbatis-Plus场景启动器 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003ecom.baomidou\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003emybatis-plus-boot-starter\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e3.4.0\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eMySQL \n    \u003cdiv id=\"mysql\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#mysql\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- MySQL --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003emysql\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003emysql-connector-java\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e8.0.15\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eOracle \n    \u003cdiv id=\"oracle\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#oracle\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- Oracle --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003ecom.oracle\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003eojdbc6\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e11.2.0.3\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eDruid场景启动器 \n    \u003cdiv id=\"druid场景启动器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#druid%e5%9c%ba%e6%99%af%e5%90%af%e5%8a%a8%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- Druid连接池场景启动器 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003ecom.alibaba\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003edruid-spring-boot-starter\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e1.2.6\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eLombok \n    \u003cdiv id=\"lombok\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#lombok\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- Lombok --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.projectlombok\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003elombok\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e1.18.22\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eRedis场景启动器 \n    \u003cdiv id=\"redis场景启动器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#redis%e5%9c%ba%e6%99%af%e5%90%af%e5%8a%a8%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- Redis场景启动器 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.springframework.boot\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003espring-boot-starter-data-redis\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eThymeleaf场景启动器 \n    \u003cdiv id=\"thymeleaf场景启动器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#thymeleaf%e5%9c%ba%e6%99%af%e5%90%af%e5%8a%a8%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- Thymeleaf场景启动器 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.springframework.boot\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003espring-boot-starter-thymeleaf\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eJSP解析器 \n    \u003cdiv id=\"jsp解析器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jsp%e8%a7%a3%e6%9e%90%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- jsp依赖 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.tomcat.embed\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003etomcat-embed-jasper\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;scope\u0026gt;\u003c/span\u003eprovided\u003cspan class=\"nt\"\u003e\u0026lt;/scope\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003ejavax.servlet\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003ejstl\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eFileupload文件上传组件 \n    \u003cdiv id=\"fileupload文件上传组件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#fileupload%e6%96%87%e4%bb%b6%e4%b8%8a%e4%bc%a0%e7%bb%84%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- Fileupload文件上传组件 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003ecommons-fileupload\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003ecommons-fileupload\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e1.3.3\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003efastjson \n    \u003cdiv id=\"fastjson\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#fastjson\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- fastjson --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003ecom.alibaba\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003efastjson\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e1.2.7\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003epagehelper分页插件 \n    \u003cdiv id=\"pagehelper分页插件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#pagehelper%e5%88%86%e9%a1%b5%e6%8f%92%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- pagehelper分页插件 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003ecom.github.pagehelper\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003epagehelper-spring-boot-starter\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e1.3.0\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eAOP \n    \u003cdiv id=\"aop\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#aop\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- AOP --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.aspectj\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003easpectjweaver\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.springframework\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003espring-aop\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eShiro \n    \u003cdiv id=\"shiro\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#shiro\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- Shiro --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.shiro\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003eshiro-spring\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e1.4.0\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.shiro\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003eshiro-ehcache\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e1.3.2\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.shiro\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003eshiro-all\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e1.2.2\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.shiro\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003eshiro-core\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e1.2.2\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.shiro\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003eshiro-web\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e1.2.2\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003ePOI \n    \u003cdiv id=\"poi\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#poi\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026lt;!-- POI --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.poi\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003epoi\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e4.1.2\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.poi\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003epoi-ooxml\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e4.1.2\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.poi\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003epoi-ooxml-schemas\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e4.1.2\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eHutool \n    \u003cdiv id=\"hutool\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#hutool\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003ecn.hutool\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003ehutool-all\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e5.7.8\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003eSpringBoot打包插件 \n    \u003cdiv id=\"springboot打包插件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#springboot%e6%89%93%e5%8c%85%e6%8f%92%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003espring-boot-maven-plugin\u003c/code\u003e是Spring Boot提供的maven打包插件。可以将项目直接打包为可运行的jar包或war包。\u003c/p\u003e","title":"3、Maven依赖","type":"posts"},{"content":" 什么是AJAX技术 # Asynchronous Javascript And XML，（异步JavaScript和XML），是指一种创建交互式、快速动态网页应用的网页开发技术，无需重新加载整个网页的情况下，能够更新部分网页的技术，是一个前端技术\n同步和异步 # 异步：在同一个时间点，双方可以同时执行\n同步：在同一个时间点，一方正在执行，另一方只能等待\nAJAX执行流程 # 实现AJAX # 原生方式（不推荐） # //根据浏览器的不同，获取HttpRequest对象 function getHttpRequest(){ if (window.XMLHttpRequest){ // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码 oRequest=new XMLHttpRequest(); }else{ // IE6, IE5 浏览器执行代码 oRequest=new ActiveXObject(\u0026#34;Microsoft.XMLHTTP\u0026#34;); } return oRequest; } function login(){ //1-获取HttpRequest对象 var req = getHttpRequest(); //2-获得账号密码 var lname = document.getElementsByTagName(\u0026#34;input\u0026#34;)[0].value; var psw= document.getElementsByTagName(\u0026#34;input\u0026#34;)[1].value; //3-请求的准备，参数可以在此处写，也可以在send方法写 req.open(\u0026#34;get\u0026#34;,\u0026#34;login?lname=\u0026#34; + lname + \u0026#34;\u0026amp;psw=\u0026#34; + psw); //4-响应回调函数 onreadystatechange=function (){ //判断状态，结果为4说明响应完成,200说明请求已成功被服务器接收、理解、并接受 if (req.readyState == 4\u0026amp;\u0026amp;req.status==200){ //获取到服务端的响应文本 var result = req.responseText; alert(result); } } //5-发送请求,参数可以在open中写，此处可以参数为null req.send(\u0026#34;lname=\u0026#34; + lname + \u0026#34;\u0026amp;psw=\u0026#34; + psw); } 使用jQuery # $.ajax()方法 # $.ajax({ url:\u0026#34;发送的请求地址\u0026#34;, type:\u0026#34;请求方式post、get\u0026#34;, data:\u0026#34;发送的数据\u0026#34;, // 如果发送json，需要使用JSON.stringify({ key: \u0026#39;value\u0026#39; }), dataType:\u0026#34;服务器返回的数据类型xml、html、script、json、test\u0026#34;, contentType: \u0026#39;application/json\u0026#39;, async:false, //异步true beforeSend:function(data){ //发送请求前执行的代码,return true为允许提交 }, success:function(data){ //发送成功后执行的代码 }, error:function(){ //请求失败执行的代码 } }); 针对get以及post # $.get(\u0026#34;url\u0026#34;,\u0026#34;参数\u0026#34;,function(data){ // 响应成功后的回调函数 },\u0026#34;返回类型\u0026#34;) $.post(\u0026#34;url\u0026#34;,\u0026#34;参数\u0026#34;,function(data){ // 响应成功后的回调函数 },\u0026#34;返回类型\u0026#34;) 多段式提交（用于文件上传） # var formData = new FormData($(\u0026#34;form\u0026#34;)[0]);//传输参数是js中对象 $.ajax({ type: \u0026#34;POST\u0026#34;, url: \u0026#34;url\u0026#34;, enctype: \u0026#34;multipart/form-data\u0026#34;, data: formData, success: function (data) { //回调函数 }, cache: false, contentType: false, processData: false }); ","date":"2025-01-03","externalUrl":null,"permalink":"/posts/bafd68f1/d4aa59a0/70d475ba/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是AJAX技术 \n    \u003cdiv id=\"什么是ajax技术\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%afajax%e6%8a%80%e6%9c%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eAsynchronous Javascript And XML\u003c/strong\u003e，（异步JavaScript和XML），是指一种创建交互式、快速动态网页应用的网页开发技术，无需重新加载整个网页的情况下，能够更新部分网页的技术，是一个前端技术\u003c/p\u003e","title":"3、AJAX","type":"posts"},{"content":" Jedis # 使用步骤（Maven下使用） # 1、pom.xml文件中导入依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;redis.clients\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jedis\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.3.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 2、获取Redis对象，连接Redis # //无参构造为默认ip：127.0.0.1，端口号：6379 Jedis jedis = new Jedis(\u0026#34;127.0.0.1\u0026#34;,6379); 对于String类型的操作 # Jedis jedis = new Jedis(); //清空所有数据库 jedis.flushAll(); for (int i = 0; i \u0026lt; 10; i++) { //insert jedis.set(\u0026#34;key\u0026#34; + i,\u0026#34;value\u0026#34; + i); } //delete jedis.del(\u0026#34;key2\u0026#34;); //update jedis.set(\u0026#34;key1\u0026#34;,\u0026#34;vvvvv\u0026#34;); //findById String s = jedis.get(\u0026#34;key1\u0026#34;); System.out.println(s); //findAll Set\u0026lt;String\u0026gt; keys = jedis.keys(\u0026#34;*\u0026#34;); Iterator\u0026lt;String\u0026gt; iterator = keys.iterator(); while (iterator.hasNext()){ String key = iterator.next(); String value = jedis.get(key); System.out.println(\u0026#34;key:\u0026#34; + key + \u0026#34;value:\u0026#34; + value); } 对于Set类型的操作 # Jedis jedis = new Jedis(); jedis.flushAll(); //insert jedis.sadd(\u0026#34;person\u0026#34;,\u0026#34;lucy1\u0026#34;); jedis.sadd(\u0026#34;person\u0026#34;,\u0026#34;lucy2\u0026#34;); jedis.sadd(\u0026#34;person\u0026#34;,\u0026#34;lucy3\u0026#34;); jedis.sadd(\u0026#34;person\u0026#34;,\u0026#34;lucy4\u0026#34;); jedis.sadd(\u0026#34;person\u0026#34;,\u0026#34;lucy5\u0026#34;); //查询key的数据量 System.out.println(jedis.scard(\u0026#34;person\u0026#34;)); //delete jedis.srem(\u0026#34;person\u0026#34;,\u0026#34;lucy1\u0026#34;); //findAll Set\u0026lt;String\u0026gt; person = jedis.smembers(\u0026#34;person\u0026#34;); System.out.println(person); 对于Zset类型的操作 # Jedis jedis = new Jedis(); jedis.flushAll(); //insert,可以根据第二个参数值，进行排序 jedis.zadd(\u0026#34;person\u0026#34;,18,\u0026#34;lucy18\u0026#34;); jedis.zadd(\u0026#34;person\u0026#34;,9,\u0026#34;lucy9\u0026#34;); jedis.zadd(\u0026#34;person\u0026#34;,26,\u0026#34;lucy26\u0026#34;); jedis.zadd(\u0026#34;person\u0026#34;,4,\u0026#34;lucy4\u0026#34;); //delete jedis.zrem(\u0026#34;person\u0026#34;,\u0026#34;lucy9\u0026#34;); //查看key的数据量 System.out.println(jedis.zcard(\u0026#34;person\u0026#34;)); //findAll Set\u0026lt;String\u0026gt; person = jedis.zrange(\u0026#34;person\u0026#34;, 0, -1); System.out.println(person); 对于List类型的操作 # Jedis jedis = new Jedis(); jedis.flushAll(); for (int i = 0; i \u0026lt; 10; i++) { //在后面添加 jedis.rpush(\u0026#34;key\u0026#34;,\u0026#34;value\u0026#34; + i); } for (int i = 11; i \u0026lt; 20; i++) { //在前面添加 jedis.lpush(\u0026#34;key\u0026#34;,\u0026#34;value\u0026#34; + i); } //findById System.out.println(jedis.lindex(\u0026#34;key\u0026#34;, 3)); //delete jedis.lrem(\u0026#34;key\u0026#34;,1,\u0026#34;value1\u0026#34;); //update jedis.lset(\u0026#34;key\u0026#34;,3,\u0026#34;vvvvv\u0026#34;); //findAll List\u0026lt;String\u0026gt; key = jedis.lrange(\u0026#34;key\u0026#34;, 0, -1); System.out.println(key); //统计该key对应的value数量 System.out.println(jedis.llen(\u0026#34;key\u0026#34;)); 对于Hash类型的操作 # Jedis jedis = new Jedis(); jedis.flushAll(); //insert jedis.hset(\u0026#34;person\u0026#34;,\u0026#34;name\u0026#34;,\u0026#34;lucy\u0026#34;); jedis.hset(\u0026#34;person\u0026#34;,\u0026#34;sex\u0026#34;,\u0026#34;nv\u0026#34;); jedis.hset(\u0026#34;person\u0026#34;,\u0026#34;age\u0026#34;,\u0026#34;18\u0026#34;); //update jedis.hset(\u0026#34;person\u0026#34;,\u0026#34;age\u0026#34;,\u0026#34;25\u0026#34;); //根据id获取值 System.out.println(jedis.hget(\u0026#34;person\u0026#34;, \u0026#34;age\u0026#34;)); //delete jedis.hdel(\u0026#34;person\u0026#34;,\u0026#34;sex\u0026#34;); //findAll Map\u0026lt;String, String\u0026gt; map = jedis.hgetAll(\u0026#34;person\u0026#34;); System.out.println(map); spring-boot-starter-data-redis # 使用步骤 # 1、导入依赖\n在需要使用Redis数据库的SpringBoot项目中 \u0026lt;!-- Spring框架提供的redis操作启动器 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-data-redis\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、配置连接信息\napplication.yml文件\nspring: redis: host: 192.168.6.111 #配置Redis服务器的IP地址 port: 6379 password: 123456 jedis: pool: max-active: 10 #连接池中最大连接数为10（默认是8） max-wait: 2000ms #配置连接的超时时间 -1ms代表永不超时 min-idle: 0 #配置最小空闲数（默认是0） max-idle: 5 #配置最大空闲数（默认是8） 3、 配置配置类\n/** * Redis的配置类 */ @Configuration public class RedisConfiguration { @Resource private RedisConnectionFactory factory; /** * 配置Redis的模板操作类 * @return */ @Bean public RedisTemplate\u0026lt;String, Object\u0026gt; redisTemplate() { // 由于Redis中存储2进制的数据 ，所以我们可能需要针对k-v进行序列化设置 RedisTemplate \u0026lt;String, Object\u0026gt; redisTemplate = new RedisTemplate\u0026lt;\u0026gt;(); // 配置获取连接 redisTemplate.setConnectionFactory(factory); // 针对key的序列化 StringRedisSerializer keyRedisSerializer = new StringRedisSerializer(); redisTemplate.setKeySerializer(keyRedisSerializer); redisTemplate.setHashKeySerializer(keyRedisSerializer); // 针对value的序列化 Jackson2JsonRedisSerializer valueRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); // 创建对象的映射 ObjectMapper mapper = new ObjectMapper(); // 指定需要序列化的域：field getter() 当然也可以控制访问修饰符 mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); valueRedisSerializer.setObjectMapper(mapper); redisTemplate.setValueSerializer(valueRedisSerializer); redisTemplate.setHashValueSerializer(valueRedisSerializer); // 非Spring注入，将使用下面的方法 redisTemplate.afterPropertiesSet(); return redisTemplate; } } String # //判断是否有key所对应的值，有则返回true，没有则返回false redisTemplate.hasKey(key) //有则取出key值所对应的值 redisTemplate.opsForValue().get(key) //删除单个key值 redisTemplate.delete(key) //批量删除key redisTemplate.delete(keys) //其中keys:Collection\u0026lt;K\u0026gt; keys //将当前传入的key值序列化为byte[]类型 redisTemplate.dump(key) //设置过期时间 public Boolean expire(String key, long timeout, TimeUnit unit) { return redisTemplate.expire(key, timeout, unit); } public Boolean expireAt(String key, Date date) { return redisTemplate.expireAt(key, date); } //查找匹配的key值，返回一个Set集合类型 public Set\u0026lt;String\u0026gt; getPatternKey(String pattern) { return redisTemplate.keys(pattern); } //修改redis中key的名称 public void renameKey(String oldKey, String newKey) { redisTemplate.rename(oldKey, newKey); } //返回传入key所存储的值的类型 public DataType getKeyType(String key) { return redisTemplate.type(key); } //如果旧值存在时，将旧值改为新值 public Boolean renameOldKeyIfAbsent(String oldKey, String newKey) { return redisTemplate.renameIfAbsent(oldKey, newKey); } //从redis中随机取出一个key redisTemplate.randomKey() //返回当前key所对应的剩余过期时间 public Long getExpire(String key) { return redisTemplate.getExpire(key); } //返回剩余过期时间并且指定时间单位 public Long getExpire(String key, TimeUnit unit) { return redisTemplate.getExpire(key, unit); } //将key持久化保存 public Boolean persistKey(String key) { return redisTemplate.persist(key); } //将当前数据库的key移动到指定redis中数据库当中 public Boolean moveToDbIndex(String key, int dbIndex) { return redisTemplate.move(key, dbIndex); } //设置当前的key以及value值 redisTemplate.opsForValue().set(key, value) //设置当前的key以及value值并且设置过期时间 redisTemplate.opsForValue().set(key, value, timeout, unit) //返回key中字符串的子字符 public String getCharacterRange(String key, long start, long end) { return redisTemplate.opsForValue().get(key, start, end); } //将旧的key设置为value，并且返回旧的key public String setKeyAsValue(String key, String value) { return redisTemplate.opsForValue().getAndSet(key, value); } //批量获取值 public List\u0026lt;String\u0026gt; multiGet(Collection\u0026lt;String\u0026gt; keys) { return redisTemplate.opsForValue().multiGet(keys); } //在原有的值基础上新增字符串到末尾 redisTemplate.opsForValue().append(key, value) //以增量的方式将double值存储在变量中 public Double incrByDouble(String key, double increment) { return redisTemplate.opsForValue().increment(key, increment); } //通过increment(K key, long delta)方法以增量方式存储long值（正值则自增，负值则自减） public Long incrBy(String key, long increment) { return redisTemplate.opsForValue().increment(key, increment); } //如果对应的map集合名称不存在，则添加否则不做修改 Map valueMap = new HashMap(); valueMap.put(\u0026#34;valueMap1\u0026#34;,\u0026#34;map1\u0026#34;); valueMap.put(\u0026#34;valueMap2\u0026#34;,\u0026#34;map2\u0026#34;); valueMap.put(\u0026#34;valueMap3\u0026#34;,\u0026#34;map3\u0026#34;); redisTemplate.opsForValue().multiSetIfAbsent(valueMap); //设置map集合到redis Map valueMap = new HashMap(); valueMap.put(\u0026#34;valueMap1\u0026#34;,\u0026#34;map1\u0026#34;); valueMap.put(\u0026#34;valueMap2\u0026#34;,\u0026#34;map2\u0026#34;); valueMap.put(\u0026#34;valueMap3\u0026#34;,\u0026#34;map3\u0026#34;); redisTemplate.opsForValue().multiSet(valueMap); //获取字符串的长度 redisTemplate.opsForValue().size(key) //用 value 参数覆写给定 key 所储存的字符串值，从偏移量 offset 开始 redisTemplate.opsForValue().set(key, value, offset) //重新设置key对应的值，如果存在返回false，否则返回true redisTemplate.opsForValue().setIfAbsent(key, value) //将值 value 关联到 key,并将 key 的过期时间设为 timeout redisTemplate.opsForValue().set(key, value, timeout, unit) //将二进制第offset位值变为value redisTemplate.opsForValue().setBit(key, offset, value) //对key所储存的字符串值，获取指定偏移量上的位(bit) redisTemplate.opsForValue().getBit(key, offset) Hash # //获取变量中的指定map键是否有值,如果存在该map键则获取值，没有则返回null。 redisTemplate.opsForHash().get(key, field) //获取变量中的键值对 public Map\u0026lt;Object, Object\u0026gt; hGetAll(String key) { return redisTemplate.opsForHash().entries(key); } //新增hashMap值 redisTemplate.opsForHash().put(key, hashKey, value) //以map集合的形式添加键值对 public void hPutAll(String key, Map\u0026lt;String, String\u0026gt; maps) { redisTemplate.opsForHash().putAll(key, maps); } //仅当hashKey不存在时才设置 public Boolean hashPutIfAbsent(String key, String hashKey, String value) { return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value); } //删除一个或者多个hash表字段 public Long hashDelete(String key, Object... fields) { return redisTemplate.opsForHash().delete(key, fields); } //查看hash表中指定字段是否存在 public boolean hashExists(String key, String field) { return redisTemplate.opsForHash().hasKey(key, field); } //给哈希表key中的指定字段的整数值加上增量increment public Long hashIncrBy(String key, Object field, long increment) { return redisTemplate.opsForHash().increment(key, field, increment); } public Double hIncrByDouble(String key, Object field, double delta) { return redisTemplate.opsForHash().increment(key, field, delta); } //获取所有hash表中字段 redisTemplate.opsForHash().keys(key) //获取hash表中字段的数量 redisTemplate.opsForHash().size(key) //获取hash表中存在的所有的值 public List\u0026lt;Object\u0026gt; hValues(String key) { return redisTemplate.opsForHash().values(key); } //匹配获取键值对，ScanOptions.NONE为获取全部键对 public Cursor\u0026lt;Entry\u0026lt;Object, Object\u0026gt;\u0026gt; hashScan(String key, ScanOptions options) { return redisTemplate.opsForHash().scan(key, options); } List # //通过索引获取列表中的元素 redisTemplate.opsForList().index(key, index) //获取列表指定范围内的元素(start开始位置, 0是开始位置，end 结束位置, -1返回所有) redisTemplate.opsForList().range(key, start, end) //存储在list的头部，即添加一个就把它放在最前面的索引处 redisTemplate.opsForList().leftPush(key, value) //把多个值存入List中(value可以是多个值，也可以是一个Collection value) redisTemplate.opsForList().leftPushAll(key, value) //List存在的时候再加入 redisTemplate.opsForList().leftPushIfPresent(key, value) //如果pivot处值存在则在pivot前面添加 redisTemplate.opsForList().leftPush(key, pivot, value) //按照先进先出的顺序来添加(value可以是多个值，或者是Collection var2) redisTemplate.opsForList().rightPush(key, value) redisTemplate.opsForList().rightPushAll(key, value) //在pivot元素的右边添加值 redisTemplate.opsForList().rightPush(key, pivot, value) //设置指定索引处元素的值 redisTemplate.opsForList().set(key, index, value) //移除并获取列表中第一个元素(如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止) redisTemplate.opsForList().leftPop(key) redisTemplate.opsForList().leftPop(key, timeout, unit) //移除并获取列表最后一个元素 redisTemplate.opsForList().rightPop(key) redisTemplate.opsForList().rightPop(key, timeout, unit) //从一个队列的右边弹出一个元素并将这个元素放入另一个指定队列的最左边 redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey) redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey, timeout, unit) //删除集合中值等于value的元素(index=0, 删除所有值等于value的元素; index\u0026gt;0, 从头部开始删除第一个值等于value的元素; index\u0026lt;0, 从尾部开始删除第一个值等于value的元素) redisTemplate.opsForList().remove(key, index, value) //将List列表进行剪裁 redisTemplate.opsForList().trim(key, start, end) //获取当前key的List列表长度 redisTemplate.opsForList().size(key) Set # //添加元素 redisTemplate.opsForSet().add(key, values) //移除元素(单个值、多个值) redisTemplate.opsForSet().remove(key, values) //删除并且返回一个随机的元素 redisTemplate.opsForSet().pop(key) //获取集合的大小 redisTemplate.opsForSet().size(key) //判断集合是否包含value redisTemplate.opsForSet().isMember(key, value) //获取两个集合的交集(key对应的无序集合与otherKey对应的无序集合求交集) redisTemplate.opsForSet().intersect(key, otherKey) //获取多个集合的交集(Collection var2) redisTemplate.opsForSet().intersect(key, otherKeys) //key集合与otherKey集合的交集存储到destKey集合中(其中otherKey可以为单个值或者集合) redisTemplate.opsForSet().intersectAndStore(key, otherKey, destKey) //key集合与多个集合的交集存储到destKey无序集合中 redisTemplate.opsForSet().intersectAndStore(key, otherKeys, destKey) //获取两个或者多个集合的并集(otherKeys可以为单个值或者是集合) redisTemplate.opsForSet().union(key, otherKeys) //key集合与otherKey集合的并集存储到destKey中(otherKeys可以为单个值或者是集合) redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey) //获取两个或者多个集合的差集(otherKeys可以为单个值或者是集合) redisTemplate.opsForSet().difference(key, otherKeys) //差集存储到destKey中(otherKeys可以为单个值或者集合) redisTemplate.opsForSet().differenceAndStore(key, otherKey, destKey) //随机获取集合中的一个元素 redisTemplate.opsForSet().randomMember(key) //获取集合中的所有元素 redisTemplate.opsForSet().members(key) //随机获取集合中count个元素 redisTemplate.opsForSet().randomMembers(key, count) //获取多个key无序集合中的元素（去重），count表示个数 redisTemplate.opsForSet().distinctRandomMembers(key, count) //遍历set类似于Interator(ScanOptions.NONE为显示所有的) redisTemplate.opsForSet().scan(key, options) zSet # //添加元素(有序集合是按照元素的score值由小到大进行排列) redisTemplate.opsForZSet().add(key, value, score) //删除对应的value,value可以为多个值 redisTemplate.opsForZSet().remove(key, values) //增加元素的score值，并返回增加后的值 redisTemplate.opsForZSet().incrementScore(key, value, delta) //返回元素在集合的排名,有序集合是按照元素的score值由小到大排列 redisTemplate.opsForZSet().rank(key, value) //返回元素在集合的排名,按元素的score值由大到小排列 redisTemplate.opsForZSet().reverseRank(key, value) //获取集合中给定区间的元素(start 开始位置，end 结束位置, -1查询所有) redisTemplate.opsForZSet().reverseRangeWithScores(key, start,end) //按照Score值查询集合中的元素，结果从小到大排序 redisTemplate.opsForZSet().reverseRangeByScore(key, min, max) redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, min, max) //返回值为:Set\u0026lt;ZSetOperations.TypedTuple\u0026lt;V\u0026gt;\u0026gt; //从高到低的排序集中获取分数在最小和最大值之间的元素 redisTemplate.opsForZSet().reverseRangeByScore(key, min, max, start, end) //根据score值获取集合元素数量 redisTemplate.opsForZSet().count(key, min, max) //获取集合的大小 redisTemplate.opsForZSet().size(key) redisTemplate.opsForZSet().zCard(key) //获取集合中key、value元素对应的score值 redisTemplate.opsForZSet().score(key, value) //移除指定索引位置处的成员 redisTemplate.opsForZSet().removeRange(key, start, end) //移除指定score范围的集合成员 redisTemplate.opsForZSet().removeRangeByScore(key, min, max) //获取key和otherKey的并集并存储在destKey中（其中otherKeys可以为单个字符串或者字符串集合） redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey) //获取key和otherKey的交集并存储在destKey中（其中otherKeys可以为单个字符串或者字符串集合） redisTemplate.opsForZSet().intersectAndStore(key, otherKey, destKey) //遍历集合（和iterator一模一样） Cursor\u0026lt;TypedTuple\u0026lt;Object\u0026gt;\u0026gt; scan = opsForZSet.scan(\u0026#34;test3\u0026#34;, ScanOptions.NONE); while (scan.hasNext()){ ZSetOperations.TypedTuple\u0026lt;Object\u0026gt; item = scan.next(); System.out.println(item.getValue() + \u0026#34;:\u0026#34; + item.getScore()); } 工具类 # package top.ygang.huijifindresource.utils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.TimeUnit; /** * @Description: spring-boot redis 工具类 * @Author: yanggang */ @SuppressWarnings(value = { \u0026#34;unchecked\u0026#34;, \u0026#34;rawtypes\u0026#34; }) @Component public class RedisCacheUtil { @Autowired public RedisTemplate redisTemplate; /** * 缓存基本的对象，Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 */ public \u0026lt;T\u0026gt; void setCacheObject(final String key, final T value) { redisTemplate.opsForValue().set(key, value); } /** * 缓存基本的对象，Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 */ public \u0026lt;T\u0026gt; void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, timeout, timeUnit); } /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @return true=设置成功；false=设置失败 */ public boolean expire(final String key, final long timeout) { return expire(key, timeout, TimeUnit.SECONDS); } /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @param unit 时间单位 * @return true=设置成功；false=设置失败 */ public boolean expire(final String key, final long timeout, final TimeUnit unit) { return redisTemplate.expire(key, timeout, unit); } /** * 获取有效时间 * * @param key Redis键 * @return 有效时间 */ public long getExpire(final String key) { return redisTemplate.getExpire(key); } /** * 判断 key是否存在 * * @param key 键 * @return true 存在 false不存在 */ public Boolean hasKey(String key) { return redisTemplate.hasKey(key); } /** * 获得缓存的基本对象。 * * @param key 缓存键值 * @return 缓存键值对应的数据 */ public \u0026lt;T\u0026gt; T getCacheObject(final String key) { ValueOperations\u0026lt;String, T\u0026gt; operation = redisTemplate.opsForValue(); return operation.get(key); } /** * 删除单个对象 * * @param key */ public boolean deleteObject(final String key) { return redisTemplate.delete(key); } /** * 删除集合对象 * * @param collection 多个对象 * @return */ public boolean deleteObject(final Collection collection) { return redisTemplate.delete(collection) \u0026gt; 0; } /** * 缓存List数据 * * @param key 缓存的键值 * @param dataList 待缓存的List数据 * @return 缓存的对象 */ public \u0026lt;T\u0026gt; long setCacheList(final String key, final List\u0026lt;T\u0026gt; dataList) { Long count = redisTemplate.opsForList().rightPushAll(key, dataList); return count == null ? 0 : count; } /** * 获得缓存的list对象 * * @param key 缓存的键值 * @return 缓存键值对应的数据 */ public \u0026lt;T\u0026gt; List\u0026lt;T\u0026gt; getCacheList(final String key) { return redisTemplate.opsForList().range(key, 0, -1); } /** * 缓存Set * * @param key 缓存键值 * @param dataSet 缓存的数据 * @return 缓存数据的对象 */ public \u0026lt;T\u0026gt; BoundSetOperations\u0026lt;String, T\u0026gt; setCacheSet(final String key, final Set\u0026lt;T\u0026gt; dataSet) { BoundSetOperations\u0026lt;String, T\u0026gt; setOperation = redisTemplate.boundSetOps(key); Iterator\u0026lt;T\u0026gt; it = dataSet.iterator(); while (it.hasNext()) { setOperation.add(it.next()); } return setOperation; } /** * 获得缓存的set * * @param key * @return */ public \u0026lt;T\u0026gt; Set\u0026lt;T\u0026gt; getCacheSet(final String key) { return redisTemplate.opsForSet().members(key); } /** * 缓存Map * * @param key * @param dataMap */ public \u0026lt;T\u0026gt; void setCacheMap(final String key, final Map\u0026lt;String, T\u0026gt; dataMap) { if (dataMap != null) { redisTemplate.opsForHash().putAll(key, dataMap); } } /** * 获得缓存的Map * * @param key * @return */ public \u0026lt;T\u0026gt; Map\u0026lt;String, T\u0026gt; getCacheMap(final String key) { return redisTemplate.opsForHash().entries(key); } /** * 往Hash中存入数据 * * @param key Redis键 * @param hKey Hash键 * @param value 值 */ public \u0026lt;T\u0026gt; void setCacheMapValue(final String key, final String hKey, final T value) { redisTemplate.opsForHash().put(key, hKey, value); } /** * 获取Hash中的数据 * * @param key Redis键 * @param hKey Hash键 * @return Hash中的对象 */ public \u0026lt;T\u0026gt; T getCacheMapValue(final String key, final String hKey) { HashOperations\u0026lt;String, String, T\u0026gt; opsForHash = redisTemplate.opsForHash(); return opsForHash.get(key, hKey); } /** * 获取多个Hash中的数据 * * @param key Redis键 * @param hKeys Hash键集合 * @return Hash对象集合 */ public \u0026lt;T\u0026gt; List\u0026lt;T\u0026gt; getMultiCacheMapValue(final String key, final Collection\u0026lt;Object\u0026gt; hKeys) { return redisTemplate.opsForHash().multiGet(key, hKeys); } /** * 删除Hash中的某条数据 * * @param key Redis键 * @param hKey Hash键 * @return 是否成功 */ public boolean deleteCacheMapValue(final String key, final String hKey) { return redisTemplate.opsForHash().delete(key, hKey) \u0026gt; 0; } /** * 获得缓存的基本对象列表 * * @param pattern 字符串前缀 * @return 对象列表 */ public Collection\u0026lt;String\u0026gt; keys(final String pattern) { return redisTemplate.keys(pattern); } } 解决乱码问题 # RedisTemplate 默认使用 JdkSerializationRedisSerializer 进行序列化，这意味着它会将 Java 对象序列化为字节数组存储到 Redis 中。这种方式虽然通用，但可能导致存储的二进制数据不易于人阅读，并且序列化和反序列化的性能较低。\npackage top.ygang.huijifindresource.config; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONReader; import com.alibaba.fastjson2.JSONWriter; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.nio.charset.Charset; /** * @Description: Redis配置 * @Author: yanggang */ @Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { @Bean @SuppressWarnings(value = { \u0026#34;unchecked\u0026#34;, \u0026#34;rawtypes\u0026#34; }) public RedisTemplate\u0026lt;Object, Object\u0026gt; redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate\u0026lt;Object, Object\u0026gt; template = new RedisTemplate\u0026lt;\u0026gt;(); template.setConnectionFactory(connectionFactory); JsonRedisSerializer serializer = new JsonRedisSerializer(Object.class); // 使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); // Hash的key也采用StringRedisSerializer的序列化方式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } static class JsonRedisSerializer\u0026lt;T\u0026gt; implements RedisSerializer\u0026lt;T\u0026gt; { public static final Charset DEFAULT_CHARSET = Charset.forName(\u0026#34;UTF-8\u0026#34;); private Class\u0026lt;T\u0026gt; clazz; public JsonRedisSerializer(Class\u0026lt;T\u0026gt; clazz) { super(); this.clazz = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length \u0026lt;= 0) { return null; } String str = new String(bytes, DEFAULT_CHARSET); return JSON.parseObject(str, clazz,JSONReader.Feature.SupportAutoType); } } } 可切换数据库的RedisTemplate # 原来的切换逻辑，这种逻辑会导致全局redistemplate的数据库都被修改\npublic RedisTemplate\u0026lt;Serializable, Object\u0026gt; initRedis(Integer indexDb){ LettuceConnectionFactory jedisConnectionFactory = (LettuceConnectionFactory) redisTemplate.getConnectionFactory(); jedisConnectionFactory.setDatabase(indexDb); redisTemplate.setConnectionFactory(jedisConnectionFactory); jedisConnectionFactory.resetConnection(); return redisTemplate; } 改进以后的版本，需要引入连接池依赖和spring-boot-starter-data-redis依赖\n\u0026lt;!-- redis依赖 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-data-redis\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- 连接池，用于创建多个redis-template实例 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.commons\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;commons-pool2\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.8.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; yml配置文件，注意：不要写在spring下面，而是写在根\n#redis多数据配置 redis: dbs: 0,1 host: localhost password: root port: 6379 #连接超时，单位：秒 timeout: 100 pool: #最大连接数 max-active: 8 #最大等待连接数 max-idle: 8 #最小等待连接数 min-idle: 0 #最大等待时间 max-wait: -1 编写工厂类\n/** * @描述 * @创建人 yhgh * @创建时间 2022/5/19 20:55 */ @Component @Slf4j public class RedisFactory{ @Value(\u0026#34;${redis.dbs}\u0026#34;) private List\u0026lt;Integer\u0026gt; dbs; @Value(\u0026#34;${redis.host}\u0026#34;) private String host; @Value(\u0026#34;${redis.port}\u0026#34;) private int port; @Value(\u0026#34;${redis.password:}\u0026#34;) private String password; @Value(\u0026#34;${redis.timeout}\u0026#34;) private int timeout; @Value(\u0026#34;${redis.pool.max-active:10}\u0026#34;) private int maxActive; @Value(\u0026#34;${redis.pool.max-idle:10}\u0026#34;) private int maxIdle; @Value(\u0026#34;${redis.pool.min-idle:0}\u0026#34;) private int minIdle; @Value(\u0026#34;${redis.pool.max-wait}\u0026#34;) private int maxWait; private static Map\u0026lt;Integer,StringRedisTemplate\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); @PostConstruct private void produce() { for (Integer index : dbs){ log.info(\u0026#34;###### StringRedisTemplate -- index：\u0026#34; + index + \u0026#34; -- 初始化完成 ######\u0026#34;); map.put(index,getStringRedisTemplate(index)); } } /** * 根据数据库索引获取RedisTemplate * @param index * @return */ public static StringRedisTemplate select(Integer index){ return map.get(index); } /** * 配置连接池 * @return */ private GenericObjectPoolConfig getPoolConfig(){ // 配置redis连接池 GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); poolConfig.setMaxTotal(maxActive); poolConfig.setMaxIdle(maxIdle); poolConfig.setMinIdle(minIdle); poolConfig.setMaxWaitMillis(maxWait); return poolConfig; } /** * 配置RedisTemplate * @param database * @return */ private StringRedisTemplate getStringRedisTemplate(int database) { // 构建工厂对象 RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(); configuration.setHostName(host); configuration.setPort(port); if(!\u0026#34;\u0026#34;.equals(password)){ configuration.setPassword(RedisPassword.of(password)); } LettucePoolingClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder() .commandTimeout(Duration.ofSeconds(timeout)).poolConfig(getPoolConfig()).build(); LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration, clientConfiguration); // 设置使用的redis数据库 factory.setDatabase(database); // 重新初始化工厂 factory.afterPropertiesSet(); return new StringRedisTemplate(factory); } } 测试\n@Test public void test1(){ RedisFactory.select(0).opsForValue().set(\u0026#34;test0\u0026#34;,\u0026#34;test0\u0026#34;); RedisFactory.select(1).opsForValue().set(\u0026#34;test1\u0026#34;,\u0026#34;test1\u0026#34;); } ","date":"2024-12-13","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/caadfbf9/51adda95/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJedis \n    \u003cdiv id=\"jedis\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jedis\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e使用步骤（Maven下使用） \n    \u003cdiv id=\"使用步骤maven下使用\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bd%bf%e7%94%a8%e6%ad%a5%e9%aa%a4maven%e4%b8%8b%e4%bd%bf%e7%94%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003e1、pom.xml文件中导入依赖 \n    \u003cdiv id=\"1pomxml文件中导入依赖\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1pomxml%e6%96%87%e4%bb%b6%e4%b8%ad%e5%af%bc%e5%85%a5%e4%be%9d%e8%b5%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eredis.clients\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003ejedis\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e3.3.0\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e2、获取Redis对象，连接Redis \n    \u003cdiv id=\"2获取redis对象连接redis\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#2%e8%8e%b7%e5%8f%96redis%e5%af%b9%e8%b1%a1%e8%bf%9e%e6%8e%a5redis\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//无参构造为默认ip：127.0.0.1，端口号：6379\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eJedis\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eJedis\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;127.0.0.1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003e6379\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e对于String类型的操作 \n    \u003cdiv id=\"对于string类型的操作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%af%b9%e4%ba%8estring%e7%b1%bb%e5%9e%8b%e7%9a%84%e6%93%8d%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eJedis\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eJedis\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//清空所有数据库\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eflushAll\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//insert\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;key\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;value\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//delete\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003edel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;key2\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//update\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;key1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;vvvvv\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//findById\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003es\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;key1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//findAll\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSet\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ekeys\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ekeys\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;*\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eIterator\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eiterator\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ekeys\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eiterator\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ewhile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eiterator\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehasNext\u003c/span\u003e\u003cspan class=\"p\"\u003e()){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eiterator\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enext\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003evalue\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;key:\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;value:\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e对于Set类型的操作 \n    \u003cdiv id=\"对于set类型的操作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%af%b9%e4%ba%8eset%e7%b1%bb%e5%9e%8b%e7%9a%84%e6%93%8d%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eJedis\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eJedis\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eflushAll\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//insert\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;lucy1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;lucy2\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;lucy3\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;lucy4\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;lucy5\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//查询key的数据量\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003escard\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//delete\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esrem\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;lucy1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//findAll\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSet\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esmembers\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e对于Zset类型的操作 \n    \u003cdiv id=\"对于zset类型的操作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%af%b9%e4%ba%8ezset%e7%b1%bb%e5%9e%8b%e7%9a%84%e6%93%8d%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eJedis\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eJedis\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eflushAll\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//insert,可以根据第二个参数值，进行排序\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ezadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003e18\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;lucy18\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ezadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003e9\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;lucy9\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ezadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003e26\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;lucy26\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ezadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;lucy4\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//delete\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ezrem\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;lucy9\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//查看key的数据量\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ezcard\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//findAll\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSet\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ezrange\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e对于List类型的操作 \n    \u003cdiv id=\"对于list类型的操作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%af%b9%e4%ba%8elist%e7%b1%bb%e5%9e%8b%e7%9a%84%e6%93%8d%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eJedis\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eJedis\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eflushAll\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//在后面添加\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003erpush\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;key\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;value\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e11\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e20\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//在前面添加\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elpush\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;key\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;value\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//findById\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elindex\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;key\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//delete\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elrem\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;key\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;value1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//update\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;key\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;vvvvv\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//findAll\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elrange\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;key\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//统计该key对应的value数量\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ellen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;key\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e对于Hash类型的操作 \n    \u003cdiv id=\"对于hash类型的操作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%af%b9%e4%ba%8ehash%e7%b1%bb%e5%9e%8b%e7%9a%84%e6%93%8d%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eJedis\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eJedis\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eflushAll\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//insert\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;lucy\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;sex\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;nv\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;age\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;18\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//update\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;age\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;25\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//根据id获取值\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;age\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//delete\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehdel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;sex\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//findAll\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eMap\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emap\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejedis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehgetAll\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;person\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003espring-boot-starter-data-redis \n    \u003cdiv id=\"spring-boot-starter-data-redis\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#spring-boot-starter-data-redis\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e使用步骤 \n    \u003cdiv id=\"使用步骤\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bd%bf%e7%94%a8%e6%ad%a5%e9%aa%a4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e1、导入依赖\u003c/p\u003e","title":"3、Jedis与RedisTemplate","type":"posts"},{"content":" 向量和标量 # 标量：只有大小的量。例如：一个物体距离50米。\n向量：既有大小也有方向。例如：一个距离原点x方向50米，y方向20米，也就是(50,20)。\n向量的模：向量的大小。\n单位向量：大小为1的向量，也就是模为1的向量。例如在游戏中，如果只是想获取到一个物体的方向，这个物体可能向量很大例如(989,989)，那么就可以把该向量转换为单位向量(1,1)，这个过程也叫做向量的单位化或向量的归一化。\n向量计算概念 # 加法 # 例如有两个向量，a = (1,2)，b = (2,1)，那么a + b = (3,3)，向量的相加遵循平行四边形法则\n减法 # 例如有两个向量，a = (5,3)，b = (2,1)，那么a + b = (3,2)，向量的相减遵循三角形法则\n乘法 # 向量一般情况下和一个标量相乘，例如向量a = (1,2)和一个标量2进行相乘，得到的结果是将该向量延长，但是并不改变方向\n点乘 # A = (Ax,Yy)，B = (Bx,By)，点乘就是A·B = (Ax*Bx + Ay*By) = n，得到的结果是一个标量n。\n这个标量n又有如下关系：n = |A| * |B| * Cosθ，其中|A|和|B|分别代表两个向量的模，Cosθ代表两个向量之间的夹角的Cos值。\n也就是(Ax*Bx + Ay*By) = |A| * |B| * Cosθ。\n由于我们只想获取两个向量的夹角，所以我们可以计算出两个向量的单位向量C和D。\n由于单位向量的模为1，如此，我们就可以简化上面的公式，(Cx*Dx + Cy*Dy) = Cosθ\nAPI # 计算点乘 # Vector3 right = Vector3.right; // (1,0,0) Vector3 forward = Vector3.forward; // (0,0,1) float angle = Vector3.Dot(forward,right); Debug.Log(angle); // 0 计算夹角 # Vector3 right = Vector3.right; // (1,0,0) Vector3 forward = Vector3.forward; // (0,0,1) float angle = Vector3.Angle(forward,right); Debug.Log(angle); // 90 计算两点距离 # Vector3 right = Vector3.right; // (1,0,0) Vector3 forward = Vector3.forward; // (0,0,1) float angle = Vector3.Distance(forward,right); Debug.Log(angle); // 1.414214 插值 # Vector3 v1 = Vector3.zero; // (0,0,0) Vector3 v2 = Vector3.one; // (1,1,1) Vector3 lerp = Vector3.Lerp(v1,v2,0.8f); Debug.Log(lerp); // (0.80, 0.80, 0.80) 向量的模 # Vector3 v = Vector3.one; // (1,1,1) Debug.Log(v.magnitude); // 1.732051 归一化 # Vector3 v = new Vector3(4,2,2); Debug.Log(v.normalized); // (0.82, 0.41, 0.41) ","date":"2024-09-13","externalUrl":null,"permalink":"/posts/69064821/af96a2fa/9fa9a697/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e向量和标量 \n    \u003cdiv id=\"向量和标量\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%90%91%e9%87%8f%e5%92%8c%e6%a0%87%e9%87%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e标量：只有大小的量。例如：一个物体距离50米。\u003c/p\u003e","title":"3、向量和标量","type":"posts"},{"content":" 判断 # 与Java一样\n语句 描述 if 语句 一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。 if\u0026hellip;else 语句 一个 if 语句 后可跟一个可选的 else 语句，else 语句在布尔表达式为假时执行。 嵌套 if 语句 您可以在一个 if 或 else if 语句内使用另一个 if 或 else if 语句。 switch 语句 一个 switch 语句允许测试一个变量等于多个值时的情况。 嵌套 switch 语句 您可以在一个 switch 语句内使用另一个 switch 语句。 循环 # 与java一样，除了foreach\n循环类型 描述 while 循环 当给定条件为真时，重复语句或语句组。它会在执行循环主体之前测试条件。 for/foreach 循环 多次执行一个语句序列，简化管理循环变量的代码。 do\u0026hellip;while 循环 除了它是在循环主体结尾测试条件外，其他与 while 语句类似。 嵌套循环 您可以在 while、for 或 do..while 循环内使用一个或多个循环。 int count = 0; foreach (int element in fibarray) { count += 1; System.Console.WriteLine(\u0026#34;Element #{0}: {1}\u0026#34;, count, element); } ","date":"2024-09-11","externalUrl":null,"permalink":"/posts/5ab2821f/eb806475/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e判断 \n    \u003cdiv id=\"判断\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%a4%e6%96%ad\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e与Java一样\u003c/p\u003e","title":"3、流程控制","type":"posts"},{"content":"类型声明文件：用来为已存在的 JS 库提供类型信息。\n这样在 TS 项目中使用这些库时，就像用 TS 一样，都会有代码提示、类型保护等机制了。\nTS中文件类型 # .ts 文件： 既包含类型信息又可执行代码。 可以被编译为 .js 文件，然后，执行代码。 用途： 编写程序代码的地方。 .d.ts 文件： 只包含类型信息的类型声明文件。 不会生成 .js 文件，仅用于提供类型信息。 用途： 为 JS 提供类型信息。 ","date":"2024-09-03","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/6d366d27/258e8558/","section":"文章","summary":"\u003cp\u003e类型声明文件：用来为已存在的 JS 库提供类型信息。\u003c/p\u003e\n\u003cp\u003e这样在 TS 项目中使用这些库时，就像用 TS 一样，都会有代码提示、类型保护等机制了。\u003c/p\u003e","title":"3、类型声明文件","type":"posts"},{"content":" web系统架构体系 # B/S（Browser/Server）：浏览器实现 优点： 规范、使用方便、本身实现成本低 容易升级、便于维护 缺点： 没有网络，无法使用 保存数据量有限，和服务器交互频率高、耗费流量 安全性差一点 C/S（Client/Server）：客户端实现 优点： 可以在无网络环境下使用 和服务器交互相对频率低、省流量 安全性高一点 缺点： 需要安装客户端 升级麻烦，维护成本高 开发成本高 什么是HTML # html是超文本标记语言（Hyper Text Markup Language），是一种使用标记标签来描述网页的语言。\n超文本 # 用超链接的方法将各种不同空间的文字信息组织在一起的网状文本。可以用来链接图片，音频，视频，动态图片等\nW3C标准 # 结构化标准语言（XHTML、XML）\n表现标准语言（CSS）\n行为标准语言（DOM、ECMAScript）\nHTML标签 # 标签一般成对存在，如\u0026lt;html\u0026gt;\u0026lt;/html\u0026gt;\n结构体标签 # DOCTYPE # \u0026lt;!DOCTYPE html\u0026gt;说明html5中文档的类型是html文档\nhtml标签 # \u0026lt;!--html的根标签--\u0026gt; \u0026lt;html\u0026gt;\u0026lt;/html\u0026gt; title标签 # \u0026lt;!--设置网页标题--\u0026gt; \u0026lt;title\u0026gt;我的网页\u0026lt;/title\u0026gt; meta标签 # \u0026lt;!-- meta标签，charset 设置网页字符集编码--\u0026gt; \u0026lt;meta charset=\u0026#34;utf-8\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;keywords\u0026#34; content=\u0026#34;关键字\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;description\u0026#34; content=\u0026#34;描述\u0026#34;\u0026gt; body标签 # \u0026lt;!--体标签，网页呈现的内容--\u0026gt; \u0026lt;body\u0026gt;内容\u0026lt;/body\u0026gt; 文本标签 # 注释 # \u0026lt;!-- 注释的内容 --\u0026gt; 标题 # \u0026lt;!-- 数字范围：1 ~ 6，数字越大，字体越小--\u0026gt; \u0026lt;h数字\u0026gt;标题\u0026lt;h数字\u0026gt; 字体 # \u0026lt;!-- font标签 --\u0026gt; \u0026lt;!-- 属性color：文字颜色（英文单词、#+6位16进制的颜色值、rgb(,,)）--\u0026gt; \u0026lt;!-- 大小seize：选值1-7，默认是3 --\u0026gt; \u0026lt;!-- 字体face：选择字体名称 --\u0026gt; \u0026lt;font color=red\u0026gt;HelloWorld\u0026lt;/font\u0026gt; 换行 # \u0026lt;!-- 单标签，换行 --\u0026gt; \u0026lt;br/\u0026gt; 水平分割线 # \u0026lt;!-- hr标签，分割线，单标签 属性 color：颜色 width：可以使用像素（px）或者百分比 align：水平对齐方式，默认居中 --\u0026gt; \u0026lt;hr color=\u0026#34;blue\u0026#34; width=\u0026#34;80%\u0026#34; aligin=left\u0026gt; 加粗 # \u0026lt;b\u0026gt;\u0026lt;/b\u0026gt; 倾斜 # \u0026lt;i\u0026gt;\u0026lt;/i\u0026gt; 下划线 # \u0026lt;u\u0026gt;\u0026lt;/u\u0026gt; 段落 # \u0026lt;p\u0026gt;\u0026lt;/p\u0026gt; 居中 # \u0026lt;center\u0026gt;\u0026lt;/center\u0026gt; 下标 # \u0026lt;sub\u0026gt;\u0026lt;/sub\u0026gt; 上标 # \u0026lt;sup\u0026gt;\u0026lt;/sup\u0026gt; 图片标签 # \u0026lt;img src=\u0026#34;地址\u0026#34; width=\u0026#34;宽度\u0026#34; height=\u0026#34;高度\u0026#34; align=\u0026#34;图文混排时，对齐方式，居中：middle\u0026#34; alt=\u0026#34;图片说明，当图片加载失败显示\u0026#34; title=\u0026#34;鼠标悬停说明\u0026#34;/\u0026gt; 相对路径：\u0026quot;../\u0026quot;代表上一层目录，同一层目录可以直接访问\n绝对路径：1、网络图片，直接写图片地址；2、本机磁盘地址\u0026quot;盘符:/图片名\u0026quot;\n超链接 # \u0026lt;a href=\u0026#34;地址或#+锚链接名\u0026#34; target=\u0026#34;打开新窗口的位置\u0026#34;\u0026gt;链接名或图片\u0026lt;/a\u0026gt; target = \u0026ldquo;_self\u0026rdquo; ：当前窗口打开\ntarget = \u0026ldquo;_blank\u0026rdquo; ：新窗口打开\ntarget = \u0026ldquo;_top\u0026rdquo; ：在顶层页面所在位置打开\ntarget = \u0026ldquo;_parent\u0026rdquo; ：在父页面的窗口位置打开\ntarget = \u0026ldquo;_iframeName\u0026rdquo; ：在iframe位置打开\n使用锚链接需要给目标标签加属性id，唯一的标记\n发送邮件 # \u0026lt;a herf=\u0026#34;mailto:邮箱地址\u0026#34;\u0026gt;连接名或图片\u0026lt;/a\u0026gt; 下载 # \u0026lt;a herf=\u0026#34;只可以是zip文件\u0026#34;\u0026gt;连接名或图片\u0026lt;/a\u0026gt; 表格标签 # \u0026lt;!--table代表表格标签，tr代表行，td代表列--\u0026gt; \u0026lt;table\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;td\u0026gt;编号\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;名称\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;价格\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;td\u0026gt;001\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;苹果\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;12\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;td\u0026gt;002\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;香蕉\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;18\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/table\u0026gt; 属性 # 表格的属性（table） # border = \u0026ldquo;边框线条粗细\u0026rdquo;\ncellspacing = \u0026ldquo;单元格间隙的值\u0026rdquo;\nalign = \u0026ldquo;表格水平对齐方式\u0026rdquo;\nwidth = \u0026ldquo;表格的宽度\u0026rdquo;\nheight = \u0026ldquo;表格的高度\u0026rdquo;\nbgcolor = \u0026ldquo;表格的背景色\u0026rdquo;\nbackground = \u0026ldquo;背景图片地址\u0026rdquo;\n行的属性（tr） # height = \u0026ldquo;行高\u0026rdquo;\nalign = \u0026ldquo;水平对齐方式\u0026rdquo;\nvalign = \u0026ldquo;垂直对齐方式\u0026quot;middle、top、bottom\nbgcolor = \u0026ldquo;行的背景色\u0026rdquo;\nbackground = \u0026ldquo;行背景图片地址\u0026rdquo;\n单元格的属性（td） # width = \u0026ldquo;宽度\u0026quot;会影响整个列\nalign = \u0026ldquo;水平对齐方式\u0026rdquo;\nvalign = \u0026ldquo;垂直对齐方式\u0026quot;middle、top、bottom\nbgcolor = \u0026ldquo;单元格的背景色\u0026rdquo;\n合并单元格 # 1、找到要合并单元格的开始位置\n2、清除合并的是行还是列\n3、合并几行或几列\n4、除开始位置的单元格外，被合并的单元格进行删除\n\u0026lt;!-- 合并开始的单元格 --\u0026gt; \u0026lt;td colspan\\rowspan=\u0026#34;合并的列（横向）、行数\u0026#34;\u0026gt;除开始的单元格、其余删除\u0026lt;/td\u0026gt; 表单标签 # \u0026lt;form method=\u0026#34;get\\post\u0026#34; action=\u0026#34;表单提交的目标地址\u0026#34;\u0026gt; 账号:\u0026lt;input name=\u0026#34;lname\u0026#34;/\u0026gt;\u0026lt;br /\u0026gt; 密码:\u0026lt;input type=\u0026#34;password\u0026#34; name=\u0026#34;psw\u0026#34;/\u0026gt;\u0026lt;br /\u0026gt; \u0026lt;button type=\u0026#34;submit\u0026#34;\u0026gt;登录\u0026lt;/button\u0026gt; \u0026lt;/form\u0026gt; form标签的属性 # method = \u0026ldquo;get\\post提交方式\u0026rdquo;\n默认的提交方式是get,地址栏可以看到的，post方式地址栏不可见 get方式相对post不安全 get方式提交的数据量有限制的，而post理论上没有限制 action = \u0026ldquo;表单提交的目标地址\u0026rdquo;\n文本框标签（\u0026lt;input\u0026gt; \u0026lt;/input\u0026gt;）属性 # name = \u0026ldquo;提交参数名称\u0026rdquo;\ntype = \u0026ldquo;input标签样式\u0026rdquo;\ntype=\u0026ldquo;text\u0026rdquo; 普通文本框 maxlength = \u0026ldquo;最大字符个数\u0026rdquo; type=\u0026ldquo;password\u0026rdquo; 密码框 type=\u0026ldquo;radio\u0026rdquo; 单选框 value = \u0026ldquo;值\u0026rdquo; checked = \u0026ldquo;checked\u0026quot;默认选择 type=\u0026ldquo;date\u0026rdquo; 日期框 type=\u0026ldquo;email\u0026rdquo; 邮箱文本框(自带针对邮箱的校验) type=\u0026ldquo;file\u0026rdquo; 文件文本框(可以选择各种文件，比如说图片，文件) type=\u0026ldquo;checkbox\u0026rdquo; 多选框 value = \u0026ldquo;值\u0026rdquo; checked = \u0026ldquo;checked\u0026quot;默认选择 type=\u0026ldquo;hidden\u0026rdquo; 隐藏文本框 type=\u0026ldquo;color\u0026rdquo; 颜色选择框 type=\u0026ldquo;number\u0026rdquo; 数字选择框 type=\u0026ldquo;button\\submit\\reset\u0026rdquo; 按钮 type=\u0026ldquo;image\u0026rdquo; 图形化按钮，功能相当于submit placeholde = \u0026ldquo;提示文字\u0026rdquo;\n按钮标签（\u0026lt;button\u0026gt;\u0026lt;/button\u0026gt;）属性 # type = \u0026ldquo;按钮功能\u0026rdquo;\ntype=\u0026ldquo;submit\u0026rdquo; 按钮（自带提交功能）\ntype=\u0026ldquo;button\u0026rdquo; 按钮（不带提交功能，就是一个最普通的按钮）\ntype=\u0026ldquo;reset\u0026quot;按钮（重置功能）\n下拉框标签 # \u0026lt;select\u0026gt; \u0026lt;option\u0026gt;下拉框列表标签\u0026lt;/option\u0026gt; \u0026lt;option selected=\u0026#34;selected\u0026#34;\u0026gt;默认选中\u0026lt;/option\u0026gt; \u0026lt;/select\u0026gt; 文本域标签 # \u0026lt;textarea\u0026gt;\u0026lt;/textarea\u0026gt; 列表标签 # 有序列表 # \u0026lt;ol type=\u0026#34;1\u0026#34;\u0026gt; \u0026lt;li\u0026gt;\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;\u0026lt;/li\u0026gt; \u0026lt;/ol\u0026gt; type：序号类型 # 1：数字（默认）\nA：大写字母\nI：大写罗马数字\na：小写字母\ni：小写罗马数字\n无序列表 # 有符号无序列表 # \u0026lt;ul type=\u0026#34;circle\u0026#34;\u0026gt; \u0026lt;li\u0026gt;\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;\u0026lt;/li\u0026gt; \u0026lt;li\u0026gt;\u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; type：符号类型\ncircle：空心圆\ndiscs：实心圆（默认）\nquare：实心方形\n无符号无序列表\n\u0026lt;dl\u0026gt; \u0026lt;dt\u0026gt;\u0026lt;/dt\u0026gt; \u0026lt;!-- 列表标题 --\u0026gt; \u0026lt;dd\u0026gt;\u0026lt;/dd\u0026gt; \u0026lt;dd\u0026gt;\u0026lt;/dd\u0026gt; \u0026lt;dd\u0026gt;\u0026lt;/dd\u0026gt; \u0026lt;/dl\u0026gt; 多媒体标签 # 视频 # \u0026lt;video width=\u0026#34;320\u0026#34; height=\u0026#34;240\u0026#34; controls\u0026gt; \u0026lt;!--controls为视频播放控制键--\u0026gt; \u0026lt;source src=\u0026#34;视频地址\u0026#34; type=\u0026#34;video/文件格式\u0026#34;\u0026gt; 您的浏览器不支持 HTML5 video 标签。 \u0026lt;/video\u0026gt; 音频 # \u0026lt;audio controls\u0026gt; \u0026lt;source src=\u0026#34;音频地址\u0026#34; type=\u0026#34;audio/文件格式\u0026#34;\u0026gt; Your browser does not support this audio format. \u0026lt;/audio\u0026gt; iframe框架标签 # \u0026lt;iframe src=\u0026#34;引用页面地址\u0026#34;\u0026gt;\u0026lt;/iframe\u0026gt; 属性 # width：宽度\nheight：高度\nframeborder：边框粗细\nscrolling：滚动条 yes、no、auto\n","date":"2024-08-27","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/c61b7e4b/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eweb系统架构体系 \n    \u003cdiv id=\"web系统架构体系\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#web%e7%b3%bb%e7%bb%9f%e6%9e%b6%e6%9e%84%e4%bd%93%e7%b3%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eB/S（Browser/Server）：浏览器实现\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e优点：\n\u003cul\u003e\n\u003cli\u003e规范、使用方便、本身实现成本低\u003c/li\u003e\n\u003cli\u003e容易升级、便于维护\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e缺点：\n\u003cul\u003e\n\u003cli\u003e没有网络，无法使用\u003c/li\u003e\n\u003cli\u003e保存数据量有限，和服务器交互频率高、耗费流量\u003c/li\u003e\n\u003cli\u003e安全性差一点\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eC/S（Client/Server）：客户端实现\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e优点：\n\u003cul\u003e\n\u003cli\u003e可以在无网络环境下使用\u003c/li\u003e\n\u003cli\u003e和服务器交互相对频率低、省流量\u003c/li\u003e\n\u003cli\u003e安全性高一点\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e缺点：\n\u003cul\u003e\n\u003cli\u003e需要安装客户端\u003c/li\u003e\n\u003cli\u003e升级麻烦，维护成本高\u003c/li\u003e\n\u003cli\u003e开发成本高\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e什么是HTML \n    \u003cdiv id=\"什么是html\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%afhtml\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003ehtml是超文本标记语言\u003c/strong\u003e（Hyper Text Markup Language），是一种使用标记标签来描述网页的语言。\u003c/p\u003e","title":"3、HTML","type":"posts"},{"content":"1、域名服务商控制台对ssl进行域名解析\n2、下载ssl证书，一共两个文件xxx.key、xxx.pem，上传到服务器\n3、在nginx配置文件中进行配置\nserver { #监听433端口，后面一定要加ssl listen 443 ssl; #此处写需要解析的域名 server_name xxx; charset utf-8; #此处为下载的pem文件路径 ssl_certificate /etc/nginx/cert/xxx.pem; #此处为下载的key文件路径 ssl_certificate_key /etc/nginx/cert/xxx.key; #这四条直接copy ssl_session_timeout 10m; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; location / { root /data/index/; index index.html index.htm; } } #用于将http请求重定向到https server { listen 80; server_name xxx; rewrite ^(.*)$ https://$host$1; } ","date":"2024-04-03","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/2c72a928/f90aa5ed/","section":"文章","summary":"\u003cp\u003e1、域名服务商控制台对ssl进行域名解析\u003c/p\u003e\n\u003cp\u003e2、下载ssl证书，一共两个文件\u003ccode\u003exxx.key\u003c/code\u003e、\u003ccode\u003exxx.pem\u003c/code\u003e，上传到服务器\u003c/p\u003e","title":"3、配置ssl","type":"posts"},{"content":" AOP # AOP的作用 # AOP的最大作用：采用代码模式，将核心业务，与非核心业务进行分离关注\n核心业务，我们采用纵向关注，而非核心业务，我们采用横向关注\n交叉业务：不同的功能模块中，都拥有的业务（几乎都是非核心功能）\n总结：AOP最终目的就是作用在方法上，并且对这个方法进行增强（添加逻辑代码）\nAOP在项目的应用（面试题） # 事务控制 日志记录 异常处理 敏感词过滤 AOP的名词 # Joinpoint（连接点）：切面中需要加入公共程序的地方！(某一个具体的要增强的方法) Pointcut（切入点）：符合某种规则的连接点，好多个连接点用对象化表示出来！(多个要增强的方法的组合) Advice（通知）：需要加入的公共程序！(要增强的逻辑代码)，根据通知增强在方法上的位置不同分为不同的类型 环绕通知：目标方法前/后调用，阻止方法调用 前置通知：在目标方法前调用 后置通知：在目标方法后调用 异常通知：当目标方法抛出异常时调用 返回通知：在我们的目标方法正常返回值后运行 Aspect（切面），在连接点上做的一系列行为！（多个要增强的方法添加增强逻辑后的结果） 切入点表达式 # 用来匹配需要增强的切入点（多个方法）\nexecution()：用于匹配方法声明符合格式的方法 args()：用于匹配方法参数为指定类型的执行方法 this()：用于匹配当前AOP代理对象类型的执行方法；注意是AOP代理对象的类型匹配，这样就可能包括引入接口也类型匹配； target()：用于匹配当前目标对象类型的执行方法；注意是目标对象的类型匹配，这样就不包括引入接口也类型匹配； within()：用于匹配指定类型内的方法执行； @args()：于匹配当前执行的方法传入的参数持有指定注解的执行； @target()：用于匹配当前目标对象类型的执行方法，其中目标对象持有指定的注解； @within()：用于匹配所以持有指定注解的类的方法； @annotation()：用于匹配当前执行方法持有指定注解的方法；\nexecution() # 通过权限修饰符、方法返回值类型、方法所在类路径、方法名称、参数匹配方法\n固定格式：execution([权限修饰符][返回类型][类全路径][方法名称][参数列表]) execution(modifiers-pattern ret-type-pattern declaring-type-pattern name-pattern(param-pattern) throws-pattern) modifiers-pattern：访问修饰符，可以省略 ret-type-pattern：返回类型，不能省略 declaring-type-pattern：类的类路径，可以省略 name-pattern：方法的名称，不能省略 param-pattern：参数列表，不能省略 throws-pattern：异常列表，可以省略 *：通配符 // 匹配top.ygang.service包下所有类的所有方法 execution(* top.ygang.service.*.*(..)) // 匹配所有public方法 execution(public * *(..)) // 匹配save开头的方法 execution(* save*(..)) // 匹配指定类的指定方法, 拦截时候一定要定位到方法 execution(public top.ygang.g_pointcut.OrderDao.save(..)) // 匹配指定类的所有方法 execution(* top.ygang.g_pointcut.UserDao.*(..)) // 匹配指定包，以及其子包下所有类的所有方法 execution(* top..*.*(..)) // || 和 or表示两种满足其一即可，取两个表达式的并集 execution(* top.ygang.g_pointcut.UserDao.save()) || execution(* top.ygang.g_pointcut.OrderDao.save()) execution(* top.ygang.g_pointcut.UserDao.save()) or execution(* top.ygang.g_pointcut.OrderDao.save()) // \u0026amp;amp;\u0026amp;amp; 和 and表示两种都同时满足才行，取交集 execution(* top.ygang.g_pointcut.UserDao.save()) \u0026amp;amp;\u0026amp;amp;execution(* com.ygang.g_pointcut.OrderDao.save()) execution(* top.ygang.g_pointcut.UserDao.save()) and execution(* top.ygang.g_pointcut.OrderDao.save()) // 取非值, !和not表示不在该范围内的作为切点 !execution(* top.ygang.g_pointcut.OrderDao.save()) not execution(* top.ygang.g_pointcut.OrderDao.save()) args() # 通过方法参数匹配\n// 匹配方法第一个参数为User类型，剩余参数无限制 args(top.ygang.User,...) // 匹配方法第一个参数为User类型，且仅有这一个参数 args(top.ygang.User) Spring AOP # 依赖 # 需要引入spring-aspects，才可以使用Spring AOP功能\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-aspects\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;5.2.9.RELEASE\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; Spring AOP的底层原理（动态代理） # Spring AOP的底层原理主要依赖于动态代理技术。它使用了两种主要的代理方式：JDK动态代理和CGLIB代理。\n需要注意的是，Spring AOP是基于代理的方式实现的，只能拦截被代理对象的外部方法调用。如果在目标对象内部方法中进行自我调用，那么切面和通知将不会被触发。\nJDK动态代理 # 当目标对象实现了接口时，Spring AOP使用JDK动态代理来生成代理对象。 JDK动态代理是通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现的。 在运行时，通过Proxy.newProxyInstance()方法创建代理对象，并指定InvocationHandler来处理方法调用。 InvocationHandler中的invoke()方法在方法调用前后织入横切逻辑。 CGlib # 当目标对象没有实现接口时，Spring AOP使用CGLIB代理来生成代理对象。 CGLIB（Code Generation Library）是一个开源的字节码生成库，可以在运行时生成子类来代理目标对象。 CGLIB通过继承的方式创建代理类，重写目标类中的方法，并在方法调用前后织入横切逻辑。 Spring AOP的实现 # 基于XML配置文件 # 使用Spring提供的通知类型接口 # 创建Bean，以及需要被增强的方法\npublic class MyService { public void doSomeThing(String name){ System.out.println(\u0026#34;my service: doSomeThing\u0026#34;); System.out.println(name); } } 创建通知类，根据需求实现通知类型的接口即可\n环绕通知：org.aopalliance.intercepter.MethodInterceptor 前置通知：org.springframework.aop.MethodBeforeAdvice 后置通知：org.springframework.aop.AfterAdvice 异常通知：org.springframework.aop.ThrowsAdvice 返回通知：org.springframework.aop.AfterReturningAdvice // 前置通知 public class TestAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println(\u0026#34;被代理的对象执行的方法：\u0026#34;+ method); System.out.println(\u0026#34;被代理的对象执行的方法的参数是：\u0026#34;+ Arrays.toString(objects)); System.out.println(\u0026#34;被代理的对象是：\u0026#34;+ o); } } Spring配置文件中管理Bean以及定义切面\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; standalone=\u0026#34;no\u0026#34;?\u0026gt; \u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xmlns:aop=\u0026#34;http://www.springframework.org/schema/aop\u0026#34; xsi:schemaLocation=\u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd\u0026#34; \u0026gt; \u0026lt;bean id=\u0026#34;myService\u0026#34; class=\u0026#34;top.ygang.springdemo.MyService\u0026#34;/\u0026gt; \u0026lt;bean id=\u0026#34;testAdvice\u0026#34; class=\u0026#34;top.ygang.springdemo.TestAdvice\u0026#34;/\u0026gt; \u0026lt;aop:config\u0026gt; \u0026lt;aop:advisor advice-ref=\u0026#34;testAdvice\u0026#34; pointcut=\u0026#34;execution(* top.ygang.springdemo.MyService.*(..))\u0026#34;/\u0026gt; \u0026lt;/aop:config\u0026gt; \u0026lt;/beans\u0026gt; main方法测试\npublic static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext(\u0026#34;applicationContext.xml\u0026#34;); MyService bean = applicationContext.getBean(MyService.class); bean.doSomeThing(\u0026#34;grady\u0026#34;); } 输出结果\n被代理的对象执行的方法：public void top.ygang.springdemo.MyService.doSomeThing(java.lang.String) 被代理的对象执行的方法的参数是：[grady] 被代理的对象是：top.ygang.springdemo.MyService@34b7ac2f my service: doSomeThing grady 自定义通知类 # 创建Bean，以及需要被增强的方法\npublic class MyService { public String doSomeThing(String name){ System.out.println(\u0026#34;my service: doSomeThing\u0026#34;); System.out.println(name); return \u0026#34;returnMsg\u0026#34;; } } 编写自定义通知类，注意方法的形参\npublic class TestAdvice { public void beforeMethod(JoinPoint jp) { //获取被代理方法对象 MethodSignature signature = (MethodSignature) jp.getSignature(); Method method = signature.getMethod(); System.out.println(\u0026#34;前置通知\u0026#34;); System.out.println(\u0026#34;被代理的对象是：\u0026#34; + jp.getTarget()); System.out.println(\u0026#34;被代理的方法是：\u0026#34; + method); System.out.println(\u0026#34;被代理的方法形参是：\u0026#34; + Arrays.toString(jp.getArgs())); } public void afterMethod(JoinPoint jp) { System.out.println(\u0026#34;后置通知\u0026#34;); } public void afterReturning(JoinPoint jp, Object c) { System.out.println(\u0026#34;后置返回通知\u0026#34;); System.out.println(\u0026#34;被代理的方法返回值是：\u0026#34; + c); System.out.println(c); } public void afterThrowing(JoinPoint jp,Exception e) { System.out.println(\u0026#34;异常通知\u0026#34;); System.out.println(\u0026#34;异常为：\u0026#34; + e); } public void around(ProceedingJoinPoint pjp) { try { System.out.println(\u0026#34;环绕通知开始\u0026#34;); Object a = pjp.proceed(); System.out.println(\u0026#34;环绕通知结束\u0026#34;); System.out.println(\u0026#34;被代理的方法返回值是：\u0026#34; + a); } catch (Throwable e) { e.printStackTrace(); } } } Spring配置文件中管理Bean以及定义切面\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; standalone=\u0026#34;no\u0026#34;?\u0026gt; \u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xmlns:aop=\u0026#34;http://www.springframework.org/schema/aop\u0026#34; xsi:schemaLocation=\u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd\u0026#34; \u0026gt; \u0026lt;bean id=\u0026#34;myService\u0026#34; class=\u0026#34;top.ygang.springdemo.MyService\u0026#34;/\u0026gt; \u0026lt;bean id=\u0026#34;testAdvice\u0026#34; class=\u0026#34;top.ygang.springdemo.TestAdvice\u0026#34;/\u0026gt; \u0026lt;aop:config\u0026gt; \u0026lt;aop:pointcut id=\u0026#34;pc\u0026#34; expression=\u0026#34;execution(* top.ygang.springdemo.MyService.*(..))\u0026#34;/\u0026gt; \u0026lt;aop:aspect ref=\u0026#34;testAdvice\u0026#34;\u0026gt; \u0026lt;aop:before method=\u0026#34;beforeMethod\u0026#34; pointcut-ref=\u0026#34;pc\u0026#34;/\u0026gt; \u0026lt;aop:before method=\u0026#34;afterMethod\u0026#34; pointcut-ref=\u0026#34;pc\u0026#34;/\u0026gt; \u0026lt;aop:after-returning method=\u0026#34;afterReturning\u0026#34; returning=\u0026#34;c\u0026#34; pointcut-ref=\u0026#34;pc\u0026#34;/\u0026gt; \u0026lt;aop:after-throwing method=\u0026#34;afterThrowing\u0026#34; throwing=\u0026#34;e\u0026#34; pointcut-ref=\u0026#34;pc\u0026#34;/\u0026gt; \u0026lt;aop:around method=\u0026#34;around\u0026#34; pointcut-ref=\u0026#34;pc\u0026#34;/\u0026gt; \u0026lt;/aop:aspect\u0026gt; \u0026lt;/aop:config\u0026gt; \u0026lt;/beans\u0026gt; main方法测试\npublic static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext(\u0026#34;applicationContext.xml\u0026#34;); MyService bean = applicationContext.getBean(MyService.class); bean.doSomeThing(\u0026#34;grady\u0026#34;); } 输出结果\n前置通知 被代理的对象是：top.ygang.springdemo.MyService@a514af7 被代理的方法是：public java.lang.String top.ygang.springdemo.MyService.doSomeThing(java.lang.String) 被代理的方法形参是：[grady] 后置通知 环绕通知开始 my service: doSomeThing grady 环绕通知结束 被代理的方法返回值是：returnMsg 后置返回通知 被代理的方法返回值是：null null 基于注解 # 常用注解 # 注解 说明 @Aspect 把当前类声明为切面类（自定义通知类），该类需要再加@Component注解，这样才可以被spring容器管理 @Before 把当前方法看成是前置通知，value：用于指定切入点表达式，还可以指定切入点表达式的引用 @AfterReturning 把当前方法看成是后置返回通知，value：用于指定切入点表达式，还可以指定切入点表达式的引用 @AfterThrowing 把当前方法看成是异常通知，value：用于指定切入点表达式，还可以指定切入点表达式的引用 @After 把当前方法看成是后置通知，value：用于指定切入点表达式，还可以指定切入点表达式的引用 @Around 把当前方法看成是环绕通知，value：用于指定切入点表达式，还可以指定切入点表达式的引用 @Pointcut 指定切入点表达式，value：指定表达式的内容 @Order 用于多个通知增强同一个方法时，在通知类上设置优先级，value：数值类型，越小优先级越高 注意：要使用以上注解，必须在Spring配置文件中开启自动代理，对Spring AOP相关注解的支持\u0026lt;aop:aspectj-autoproxy/\u0026gt;\n\u0026lt;!-- 启动自动代理，开启对AOP注解的支持 --\u0026gt; \u0026lt;aop:aspectj-autoproxy/\u0026gt; 该标签有几个属性可以选择\nproxy-target-class（默认值为false）：用于指定是否使用基于类的代理，默认情况下使用基于接口的代理。如果设置为true，则将使用CGLIB库创建基于类的代理。 expose-proxy（默认值为false）：用于指定是否将代理对象公开给AopContext，以便在切面内部通过AopContext.currentProxy()方法访问。如果设置为true，则可以在切面内部访问代理对象。 代码示例 # 创建Bean，以及需要被增强的方法\n@Component public class MyService { public String doSomeThing(String name){ System.out.println(\u0026#34;my service: doSomeThing\u0026#34;); System.out.println(name); return \u0026#34;returnMsg\u0026#34;; } } 创建切面类\n@Aspect @Component public class TestAdvice { @Pointcut(\u0026#34;execution(* top.ygang.springdemo.MyService.*(..))\u0026#34;) public void pc(){} @Before(\u0026#34;pc()\u0026#34;) public void before(JoinPoint jp){ //获取被代理方法对象 MethodSignature signature = (MethodSignature) jp.getSignature(); Method method = signature.getMethod(); System.out.println(\u0026#34;前置通知\u0026#34;); System.out.println(\u0026#34;被代理的对象是：\u0026#34; + jp.getTarget()); System.out.println(\u0026#34;被代理的方法是：\u0026#34; + method); System.out.println(\u0026#34;被代理的方法形参是：\u0026#34; + Arrays.toString(jp.getArgs())); } } 配置文件开启Spring各种注解的支持\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; standalone=\u0026#34;no\u0026#34;?\u0026gt; \u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xmlns:aop=\u0026#34;http://www.springframework.org/schema/aop\u0026#34; xmlns:context=\u0026#34;http://www.springframework.org/schema/context\u0026#34; xsi:schemaLocation=\u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd\u0026#34; \u0026gt; \u0026lt;context:component-scan base-package=\u0026#34;top.ygang.*\u0026#34;/\u0026gt; \u0026lt;context:annotation-config/\u0026gt; \u0026lt;!-- 启动自动代理，开启对AOP注解的支持 --\u0026gt; \u0026lt;aop:aspectj-autoproxy/\u0026gt; \u0026lt;/beans\u0026gt; main方法测试\npublic static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext(\u0026#34;applicationContext.xml\u0026#34;); MyService bean = applicationContext.getBean(MyService.class); bean.doSomeThing(\u0026#34;grady\u0026#34;); } 输出结果\n前置通知 被代理的对象是：top.ygang.springdemo.MyService@f2ff811 被代理的方法是：public java.lang.String top.ygang.springdemo.MyService.doSomeThing(java.lang.String) 被代理的方法形参是：[grady] my service: doSomeThing grady AspectJ # AspectJ是一个面向切面的框架，不是Spring框架的一部分，可以单独使用，是目前最好用，最方便的AOP框架，和spring中的aop可以集成在一起使用，通过Aspectj提供的一些功能实现aop代理变得非常方便。\nSpring AOP和AspectJ的区别 # Spring AOP # 基于动态代理来实现，默认如果实现接口的，用JDK提供的动态代理实现，如果没有实现接口则使用CGLIB实现 Spring AOP需要依赖IOC容器来管理，并且只能作用于Spring容器，使用纯Java代码实现 在性能上，由于Spring AOP是基于动态代理来实现的，在容器启动时需要生成代理实例，在方法调用上也会增加栈的深度，使得Spring AOP的性能不如AspectJ的那么好 注意：Spring AOP归根结底也是动态代理，所以和AspectJ的关系并不大，只是引入了Aspect、advice、joinpoint等等概念 AspectJ # AspectJ来自于Eclipse基金会\nAspectJ属于静态织入，通过修改代码来实现，有如下几个织入的时机：\n编译期织入（Compile-time weaving）： 如类 A 使用 AspectJ 添加了一个属性，类 B 引用了它，这个场景就需要编译期的时候就进行织入，否则没法编译类 B。 编译后织入（Post-compile weaving）： 也就是已经生成了 .class 文件，或已经打成 jar 包了，这种情况我们需要增强处理的话，就要用到编译后织入。 类加载后织入（Load-time weaving）： 指的是在加载类的时候进行织入，要实现这个时期的织入，有几种常见的方法。1、自定义类加载器来干这个，这个应该是最容易想到的办法，在被织入类加载到 JVM 前去对它进行加载，这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定 AspectJ 提供的 agent：-javaagent:xxx/xxx/aspectjweaver.jar。 AspectJ可以做Spring AOP干不了的事情，它是AOP编程的完全解决方案，Spring AOP则致力于解决企业级开发中最普遍的AOP（方法织入）。而不是成为像AspectJ一样的AOP方案\n因为AspectJ在实际运行之前就完成了织入，所以说它生成的类是没有额外运行时开销的\n","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/c70ae83e/635a4a4d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eAOP \n    \u003cdiv id=\"aop\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#aop\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eAOP的作用 \n    \u003cdiv id=\"aop的作用\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#aop%e7%9a%84%e4%bd%9c%e7%94%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003eAOP的最大作用：采用代码模式，将核心业务，与非核心业务进行分离关注\u003c/p\u003e","title":"3、AOP","type":"posts"},{"content":" 数据类型的作用 # C++规定在创建一个变量或者常量时，必须要指定出相应的数据类型，否则无法给变量分配内存\nsizeof()关键字 # 作用：利用sizeof关键字可以统计数据类型所占内存大小\n语法： sizeof( 数据类型 / 变量)\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { int a = 12; cout \u0026lt;\u0026lt; sizeof(a) \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; sizeof(int) \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 整形 # 作用：整型变量表示的是整数类型的数据\nC++中能够表示整型的类型有以下几种方式，区别在于所占内存空间不同：\n数据类型 占用空间 取值范围 short(短整型) 2字节 (-2^15 ~ 2^15-1)||(-32768~32767) int(整型) 4字节 (-2^31 ~ 2^31-1) long(长整形) Windows为4字节，Linux为4字节(32位)，8字节(64位) (-2^31 ~ 2^31-1) long long(长长整形) 8字节 (-2^63 ~ 2^63-1) 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { short a = 12; int b = 12; long c = 12; long long d = 12; cout \u0026lt;\u0026lt; \u0026#34;short的a = \u0026#34; \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;int的b = \u0026#34; \u0026lt;\u0026lt; b \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;long的c = \u0026#34; \u0026lt;\u0026lt; c \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;long long 的d = \u0026#34; \u0026lt;\u0026lt; d \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 浮点型 # 作用：用于表示小数\n浮点型变量分为两种：\n单精度float ：建议在值后面加f 双精度double 两者的区别在于表示的有效数字范围不同。\n数据类型 占用空间 有效数字范围 float 4字节 7位有效数字 double 8字节 15～16位有效数字 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { float a = 3.14f; double b = 3.14; cout \u0026lt;\u0026lt; \u0026#34;单精度float的a = \u0026#34; \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;双精度double的b = \u0026#34; \u0026lt;\u0026lt; b \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 字符型 # 作用：字符型变量用于显示单个字符\n语法：char ch = 'a';\n注意1：在显示字符型变量时，用单引号将字符括起来，不要用双引号\n注意2：单引号内只能有一个字符，不可以是字符串\nC和C++中字符型变量只占用1个字节 字符型变量并不是把字符本身放到内存中存储，而是将对应的ASCII编码放入到存储单元 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { char a = \u0026#39;a\u0026#39;; int ascForA = (int)a; cout \u0026lt;\u0026lt; \u0026#34;字符型char的a = \u0026#34; \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;a的ASCII码值 = \u0026#34; \u0026lt;\u0026lt; ascForA \u0026lt;\u0026lt; endl; //97 system(\u0026#34;pause\u0026#34;); return 0; } ASCII码表格：\nASCII值 控制字符 ASCII值 字符 ASCII值 字符 ASCII值 字符 0 NUT 32 (space) 64 @ 96 、 1 SOH 33 ! 65 A 97 a 2 STX 34 \u0026quot; 66 B 98 b 3 ETX 35 # 67 C 99 c 4 EOT 36 $ 68 D 100 d 5 ENQ 37 % 69 E 101 e 6 ACK 38 \u0026amp; 70 F 102 f 7 BEL 39 , 71 G 103 g 8 BS 40 ( 72 H 104 h 9 HT 41 ) 73 I 105 i 10 LF 42 * 74 J 106 j 11 VT 43 + 75 K 107 k 12 FF 44 , 76 L 108 l 13 CR 45 - 77 M 109 m 14 SO 46 . 78 N 110 n 15 SI 47 / 79 O 111 o 16 DLE 48 0 80 P 112 p 17 DCI 49 1 81 Q 113 q 18 DC2 50 2 82 R 114 r 19 DC3 51 3 83 S 115 s 20 DC4 52 4 84 T 116 t 21 NAK 53 5 85 U 117 u 22 SYN 54 6 86 V 118 v 23 TB 55 7 87 W 119 w 24 CAN 56 8 88 X 120 x 25 EM 57 9 89 Y 121 y 26 SUB 58 : 90 Z 122 z 27 ESC 59 ; 91 [ 123 { 28 FS 60 \u0026lt; 92 / 124 | 29 GS 61 = 93 ] 125 } 30 RS 62 \u0026gt; 94 ^ 126 ` 31 US 63 ? 95 _ 127 DEL ASCII 码大致由以下两部分组成：\nASCII 非打印控制字符： ASCII 表上的数字 0-31 分配给了控制字符，用于控制像打印机等一些外围设备。 ASCII 打印字符：数字 32-126 分配给了能在键盘上找到的字符，当查看或打印文档时就会出现。 转义字符 # 作用：用于表示一些不能显示出来的ASCII字符\n现阶段我们常用的转义字符有： \\n \\\\ \\t\n转义字符 含义 ASCII码值（十进制） \\a 警报 007 \\b 退格(BS) ，将当前位置移到前一列 008 \\f 换页(FF)，将当前位置移到下页开头 012 \\n 换行(LF) ，将当前位置移到下一行开头 010 \\r 回车(CR) ，将当前位置移到本行开头 013 \\t 水平制表(HT) （跳到下一个TAB位置） 009 \\v 垂直制表(VT) 011 \\\\ 代表一个反斜线字符\u0026quot;\u0026quot; 092 ' 代表一个单引号（撇号）字符 039 \u0026quot; 代表一个双引号字符 034 ? 代表一个问号 063 \\0 数字0 000 \\ddd 8进制转义字符，d范围0~7 3位8进制 \\xhh 16进制转义字符，h范围09，af，A~F 3位16进制 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { //警报，计算机会发出警报声音 cout \u0026lt;\u0026lt; \u0026#34;\\a\u0026#34; \u0026lt;\u0026lt; endl; //在你好呀后面换行 cout \u0026lt;\u0026lt; \u0026#34;你好呀\\nlucy\u0026#34; \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 字符串型 # 一共有两种方式声明字符串，都可以进行使用\n1、C语言风格 # 声明方式：char 变量名[] = \u0026quot;字符串值\u0026quot;;\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { char a[] = \u0026#34;你好呀！\u0026#34;; cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 2、C++语言风格 # 声明方式：string 变量名 = \u0026quot;字符串值\u0026quot;;\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; //使用c++风格字符串变量，需要包含这个头文件 int main() { string a = \u0026#34;你好啊\u0026#34;; cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 布尔类型 # 作用：布尔数据类型代表真或假的值\nbool类型只有两个值：\ntrue \u0026mdash; 真（本质是1，或者任意非0数） false \u0026mdash; 假（本质是0） bool类型占1个字节大小\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { bool flag = true; cout \u0026lt;\u0026lt; \u0026#34;现在的标志是：\u0026#34; \u0026lt;\u0026lt; flag \u0026lt;\u0026lt; endl; //输出的结果的1 flag = false; cout \u0026lt;\u0026lt; \u0026#34;现在的标志是：\u0026#34; \u0026lt;\u0026lt; flag \u0026lt;\u0026lt; endl; //输出的结果的0 system(\u0026#34;pause\u0026#34;); return 0; } 数据的输入 # 作用：用于从键盘获取数据\n关键字：cin\n语法： cin \u0026gt;\u0026gt; 变量 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { //创建一个变量，用于接受输入数据 int a = 0; cout \u0026lt;\u0026lt; \u0026#34;请输入\u0026#34; \u0026lt;\u0026lt; endl; //获取输入赋值给a cin \u0026gt;\u0026gt; a; cout \u0026lt;\u0026lt; \u0026#34;输入的结果是：\u0026#34; \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/d8245fe1/09e70634/312172fe/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e数据类型的作用 \n    \u003cdiv id=\"数据类型的作用\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%95%b0%e6%8d%ae%e7%b1%bb%e5%9e%8b%e7%9a%84%e4%bd%9c%e7%94%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eC++规定在创建一个变量或者常量时，必须要指定出相应的数据类型，否则无法给变量分配内存\u003c/p\u003e","title":"3、c++的数据类型","type":"posts"},{"content":" 库管理 # 建库操作 # #创建数据库（默认字符集编码） create database test20210420 #创建数据库的时候指定字符集编码以及字符校验规则 create database test20210420 CHARACTER set = utf8 COLLATE utf8_general_ci #切换可用数据库(建表之前一定要切换) use test20210420 #查看服务器的所有数据库 show databases #删除数据库 drop database test20210420 #修改数据库字符集编码以及字符校验规则 alter database test20210420 CHARACTER set = utf8 COLLATE utf8_general_ci #查看数据库信息 show create database test20210420 表管理 # 建表操作 # #创建表：创建表的格式 create table student( student_id int, student_name varchar(20), student_birth int ) #插入数据的命令 insert into student values(1,\u0026#39;姚明\u0026#39;,20) #查询 select * from student #删除表 drop table student 复制表操作 # #结构和数据一起复制(有创建表) create table testchar1 as select * from testchar #结构复制(有创建表) create table testchar2 like testchar 修改表操作 # #1.给表中增加列 alter table testchar add t_age int #2.给修改列名及列定义 alter table testchar change t_name1 t_name2 varchar(50) #3.修改列定义 alter table testchar modify t_name2 varchar(100) #4.删除列 alter table testchar drop t_age 辅助命令 # #查看当前数据库中所有的表 SHOW TABLES; #查看表的定义信息 SHOW CREATE TABLE testchar #删除表 drop table testchar #表重新命名 Rename table testchar to testchar3 约束： # NOT NULL：非空，该字段的值必填\nUNIQUE：唯一，该字段的值不可重复\nDEFAULT：默认，该字段的值不用手动插入有默认值\nCHECK：检查，mysql不支持\nPRIMARY KEY：主键，该字段的值不可重复并且非空 unique+not null\nFOREIGN KEY：外键，该字段的值引用了另外的表的字段\n主键和唯一的异同： # 区别：\n①一个表至多有一个主键，但可以有多个唯一\n②主键不允许为空，唯一可以为空\n相同点\n①都具有唯一性\n②都支持组合键，但不推荐\n主表和从表： # 主表（父表）被引用字段所在的表\n在数据库中建立的表格即Table，其中存在主键(primary key)用于与其它表相关联，并且作为在主表中的唯一性标识。\n从表（子表）\n以主表的主键（primary key）值为外键(Foreign Key)的表，可以通过外键与主表进行关联查询。从表与主表通过外键进行关联查询。\n修改表时添加或删除约束 # #1、非空 #添加非空 alter table 表名 modify column 字段名 字段类型 not null; #删除非空 alter table 表名 modify column 字段名 字段类型 ; #2、默认 #添加默认 alter table 表名 modify column 字段名 字段类型 default 值; #删除默认 alter table 表名 modify column 字段名 字段类型 ; #3、主键 #添加主键 alter table 表名 add【constraint 约束名】 primary key(字段名); #删除主键 alter table 表名 drop primary key; #4、唯一 #添加唯一 alter table 表名 add【 constraint 约束名】 unique(字段名); #删除唯一 alter table 表名 drop index 索引名; #5、外键 #添加外键 alter table 表名 add【 constraint 约束名】 foreign key(字段名) references 主表（被引用列）; #删除外键 alter table 表名 drop foreign key 约束名; #自增长列 #添加自增长列 alter table 表 modify column 字段名 字段类型 约束 auto_increment #删除自增长列 alter table 表 modify column 字段名 字段类型 约束 # ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/a6ece75f/8b824d9b/5d290da9/1daee8a4/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e库管理 \n    \u003cdiv id=\"库管理\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ba%93%e7%ae%a1%e7%90%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e建库操作 \n    \u003cdiv id=\"建库操作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%bb%ba%e5%ba%93%e6%93%8d%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e创建数据库（默认字符集编码）\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ecreate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003edatabase\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etest20210420\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e创建数据库的时候指定字符集编码以及字符校验规则\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ecreate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003edatabase\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etest20210420\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nb\"\u003eCHARACTER\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eset\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eutf8\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eCOLLATE\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eutf8_general_ci\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e切换可用数据库\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"err\"\u003e建表之前一定要切换\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003euse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etest20210420\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e查看服务器的所有数据库\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eshow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edatabases\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e删除数据库\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003edrop\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003edatabase\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etest20210420\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e修改数据库字符集编码以及字符校验规则\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ealter\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003edatabase\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etest20210420\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nb\"\u003eCHARACTER\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eset\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eutf8\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eCOLLATE\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eutf8_general_ci\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e查看数据库信息\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eshow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ecreate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003edatabase\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etest20210420\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e表管理 \n    \u003cdiv id=\"表管理\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a1%a8%e7%ae%a1%e7%90%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e建表操作 \n    \u003cdiv id=\"建表操作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%bb%ba%e8%a1%a8%e6%93%8d%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e创建表：创建表的格式\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ecreate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003etable\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estudent\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estudent_id\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estudent_name\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nb\"\u003evarchar\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e20\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estudent_birth\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e插入数据的命令\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003einsert\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003einto\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estudent\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003evalues\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;姚明\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"mi\"\u003e20\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e查询\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eselect\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003efrom\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estudent\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e删除表\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003edrop\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003etable\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estudent\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e复制表操作 \n    \u003cdiv id=\"复制表操作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%a4%8d%e5%88%b6%e8%a1%a8%e6%93%8d%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e结构和数据一起复制\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"err\"\u003e有创建表\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ecreate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003etable\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etestchar1\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eas\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eselect\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003efrom\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etestchar\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e结构复制\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"err\"\u003e有创建表\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ecreate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003etable\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etestchar2\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003elike\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etestchar\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e修改表操作 \n    \u003cdiv id=\"修改表操作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bf%ae%e6%94%b9%e8%a1%a8%e6%93%8d%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"err\"\u003e给表中增加列\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ealter\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003etable\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etestchar\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eadd\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003et_age\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"err\"\u003e给修改列名及列定义\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ealter\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003etable\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etestchar\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003echange\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003et_name1\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003et_name2\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nb\"\u003evarchar\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e50\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"err\"\u003e修改列定义\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ealter\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003etable\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etestchar\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003emodify\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003et_name2\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nb\"\u003evarchar\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e100\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"err\"\u003e删除列\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ealter\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003etable\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etestchar\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003edrop\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003et_age\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e辅助命令 \n    \u003cdiv id=\"辅助命令\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%be%85%e5%8a%a9%e5%91%bd%e4%bb%a4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e查看当前数据库中所有的表\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eSHOW\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eTABLES\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e查看表的定义信息\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eSHOW\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eCREATE\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eTABLE\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etestchar\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e删除表\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003edrop\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003etable\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etestchar\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e表重新命名\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eRename\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003etable\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etestchar\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eto\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etestchar3\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e约束： \n    \u003cdiv id=\"约束\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%ba%a6%e6%9d%9f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eNOT NULL\u003c/strong\u003e：\u003cstrong\u003e非空\u003c/strong\u003e，该字段的值必填\u003c/p\u003e","title":"3、DDL数据定义语句","type":"posts"},{"content":" File类 # File类的一个对象，代表一个文件或一个文件目录(Directory)\nFile类声明在java.io包下\nFile类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法**，并未涉及到写入或读取文件内容的操作。**如果需要读取或写入文件内容，必须使用IO流（Stream）来完成。\n后续File类的对象常会作为参数传递到流的构造器中，指明读取或写入的终点。\nFile的实例化 # // public File(String pathname) File disc = new File(\u0026#34;D:\\\\\u0026#34;); // public File(String parent, String child) File c1 = new File(\u0026#34;D:\\\\\u0026#34;,\u0026#34;child\u0026#34;); // public File(File parent, String child) File c2 = new File(disc,\u0026#34;child\u0026#34;); // public File(URI uri) File file = new File(URI.create(\u0026#34;file:///d:/\u0026#34;)); 路径分隔符 # 由于java跨平台运行的特性，我们有的时候必须兼顾所有平台的路径分隔符\nwindows和DOS系统：\\\nUNIX和URL：/\nFile类提供了静态变量separator来根据操作系统动态提供路径分隔符\nFile.separator File类的常用方法 # 获取信息 获取绝对路径：public String getAbsolutePath() 获取路径：public String getPath() 获取名称：public String getName() 获取上层目录路径（无则返回null）：public String getParent() 获取文件大小（字节数）：public long length() 获取最后一次修改时间（毫秒时间戳）：public long lastModified() 获取目录下所有子文件或子目录名称：public String[] list() 获取目录下所有子文件或子目录File数组：public File[] listFiles() 重命名（移动文件）：public boolean renameTo(File dest) 判断 判断是否是目录：public boolean isDirectory() 判断是否是文件：public boolean isFile() 判断是否存在：public boolean exists() 判断是否可读：public boolean canRead() 判断是否可写：public boolean canWrite() 判断是否隐藏：public boolean isHidden() 创建 创建文件：public boolean createNewFile() 创建目录：public boolean mkdir() 创建目录（可递归创建多级）：public boolean mkdirs() 删除：public boolean delete() 过滤文件 # File file = new File(\u0026#34;D:\\\\\u0026#34;); File[] files = file.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { // 过滤出所有的文件 return pathname.isFile(); } }); System.out.println(Arrays.toString(files)); IO流（stream） # I：input ; O : output\nI/O即输入输出，是计算机与外界世界的一个接口。IO操作的实际主体是操作系统。在java编程中，一般使用流的方式来处理IO，所有的IO都被视作是单个字节的移动，通过stream对象一次移动一个字节。流IO负责把对象转换为字节，然后再转换为对象。\n流的分类 # 操作数据单位：字节流(8bit)、字符流 (16bit)\n数据的流向：输入流（写入内存）、输出流（从内存写出）\n流的角色：节点流（从一个特定的数据源读写数据）、处理流（连接在已存在的流之上，为程序提供更为强大的读写功能）\nIO 流体系 # 分类 字节输入流 字节输出流 字符输入流 字符输出流 抽象基类 InputStream OutputStream Reader Writer 访问文件 FileInputStream FileOutputStream FileReader FileWriter 访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter 访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter 访问字符串 StringReader StringWriter 缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter 转换流 InputStreamReader OutputSreamWriter 对象流 ObjectInputStream ObjectOutputStream FilterInputStream FilterOutputStream FilterReader FilterWriter 打印流 PrintStream PrintWriter 推回输入流 PushbackInputStream PushbackReader 特殊流 DataInputStream DataOutputStream IO类的继承关系 # 节点流 # 如：InputStream、Reader、OutputStream、Writer\n节点流与具体节点相连接，直接读写节点数据\n字节流操作字节，也就是二进制文件，比如：.mp3，.avi，.rmvb，mp4，.jpg，.doc，.ppt 字符流操作字符，只能操作普通文本文件。最常见的文本文件：.txt，.java，.c，.cpp 等语言的源代码。尤其注意.doc，.excel，.ppt这些不是文本文件。 输入流InputStream \u0026amp; Reader # InputStream（字节流） 和 Reader **（字符流）**是所有输入流的基类。\n程序中打开的文件 IO 资源不属于内存里的资源，垃圾回收机制无法回收该资源，所以应该显式关闭文件 IO 资源（使用close方法）。\nFileInputStream 从文件系统中的某个文件中获得输入字节。FileInputStream 用于读取非文本数据之类的原始字节流。要读取字符流，需要使用 FileReader\n常用方法 # InputStream # // 从输入流读取下一个字节，返回0到255的int字节值，如果到达流末尾则返回-1 int read() // 从输入流中读取最多b.length个字节到byte[] b中，并返回读取字节的长度，如果到达流末尾则返回-1 int read(byte[] b) // 从输入流中读取最多len个字节到byte[] b中，读到的第一个字节会被写到b[off]中并依次往后，并返回读取字节的长度，如果到达流末尾则返回-1 int read(byte[] b,int off,int len) // 返回从该输入流中可读取的剩余字节数之和 int available() // 关闭输入流并释放与该流有关的所有系统资源 public void close() throws IOException Reader # // 从输入流读取单个字符，返回0到65535的int类型Unicode码值，如果到达流末尾则返回-1 int read() // 从输入流读取最多cbuf.length个字符到char[] cbuf中，并返回读取字符的长度，如果到达流末尾则返回-1 int read(char[] cbuf) // 从输入流中读取最多len个字符到char[] cbuf中，读到的第一个字符会被写到cbuf[off]中并依次往后，并返回读取字符的长度，如果到达流末尾则返回-1 int read(char[] cbuf,int off,int len) // 关闭输入流并释放与该流有关的所有系统资源 public void close() throws IOException 输出流OutputStream \u0026amp; Writer # **OutputStream（字节流）和Writer（字符流）**是所有输出流的基类。\n在写入一个文件时，如果使用构造器FileOutputStream(file)，则目录下有同名文件将被覆盖。\n如果使用构造器FileOutputStream(file,true)，则目录下的同名文件不会被覆盖， 在文件内容末尾追加内容。\n因为字符流直接以字符作为操作单位，所以 Writer 可以用字符串来替换字符数组， 即以 String 对象作为参数\n显式关闭IO资源，需要使用flush（防止数据丢失）和close方法\nFileOutputStream 用于写出非文本数据之类的原始字节流。要写出字符流，需要使用 FileWriter\n常用方法 # OutputStream # // 向输出流写入一个0-255范围的int类型的字节值 void write(int b) // 将byte[] b中的所有字节写入输出流 void write(byte[] b) // 将byte[] b数组从b[off]开始的len个字节写入输出流 void write(byte[] b,int off,int len) // 刷新输出流，并强制将缓冲中的所有字节写出到输出流 public void flush()throws IOException // 关闭输出流并释放与该流关联的系统资源 public void close()throws IOException Writer # // 向输出流写入一个0-65535范围的int类型的Unicode码值 void write(int c) // 将char[] cbuf中的所有字符写入输出流 void write(char[] cbuf) // 将char[] cbuf从cbuf[off]开始的len个字符写入输出流 void write(char[] cbuf,int off,int len) // 将字符串写入输出流 void write(String str) // 将字符串从第off个字符开始写入len个字符到输出流 void write(String str,int off,int len) // 刷新输出流，并强制将缓冲中的所有字节写出到输出流 public void flush()throws IOException // 关闭输出流并释放与该流关联的系统资源 public void close()throws IOException 文件的复制 # public static void copy(String copyBy,String copyTo) throws IOException { //输入流 InputStream inputStream = new FileInputStream(new File(copyBy)); //输出流 OutputStream outputStream = new FileOutputStream(new File(copyTo)); //创建缓冲数组 byte[] bytes = new byte[1024]; int i; //循环写入缓冲，然后从缓冲数组中输出到目标文件 while ((i = inputStream.read(bytes)) != -1){ outputStream.write(bytes,0,i); } //释放资源 outputStream.flush(); inputStream.close(); outputStream.flush(); } 处理流 # 缓冲流 # BufferedInputStream 、BufferedOutputStream、BufferedReader、BufferedWriter\n优点：提供流的读取、写入的速度\n提高读写速度的原因：内部提供了一个缓冲区。默认情况下是8kb\nFile f = new File(\u0026#34;D:\\\\test.txt\u0026#34;); FileInputStream inputStream = new FileInputStream(f); // 创建缓冲流，并设置缓冲区大小 BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream,1024); byte[] bytes = new byte[bufferedInputStream.available()]; bufferedInputStream.read(bytes); bufferedInputStream.close(); String s = new String(bytes); System.out.println(s); 转换流 # InputStreamReader：将一个字节的输入流转换为字符的输入流\nFile f = new File(\u0026#34;D:\\\\test.txt\u0026#34;); InputStream inputStream = new FileInputStream(f); // 将字节流转换为字符流 InputStreamReader inputStreamReader = new InputStreamReader(inputStream, Charset.forName(\u0026#34;utf8\u0026#34;)); StringBuilder stringBuilder = new StringBuilder(); while (true){ int read = inputStreamReader.read(); if (read == -1){ break; } stringBuilder.append((char) read); } System.out.println(stringBuilder.toString()); inputStreamReader.close(); OutputStreamWriter：将一个字节的输出流转换为字符的输出流\nFile f = new File(\u0026#34;D:\\\\test.txt\u0026#34;); OutputStream outputStream = new FileOutputStream(f); // 将字节流转换为字符流 OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, Charset.forName(\u0026#34;utf8\u0026#34;)); String target = \u0026#34;Hello Java IO\u0026#34;; outputStreamWriter.write(target); outputStreamWriter.flush(); outputStreamWriter.close(); 常见的编码表 # ASCII：美国标准信息交换码。 ISO8859-1：拉丁码表。欧洲码表 GB2312：中国的中文编码表。最多两个字节编码所有字符。 GBK：中国的中文编码表升级，融合了更多的中文文字符号。最多两个字节编码。 Unicode：国际标准码，融合了目前人类使用的所字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。 UTF-8：变长的编码方式，可用1-4个字节来表示一个字符。 客户端/浏览器端——后台（Java，GO，Python）——数据库\n要求前后使用的字符集都要统一：UTF-8\n对象流 # ObjectInputStream、OjbectOutputSteam\n用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中，也能把对象从数据源中还原回来。\n序列化需注意 # 需要实现接口：java.io.Serializable 当前类提供一个全局常量：serialVersionUID（序列化类新增属性时，请不要修改serialVersionUID字段，避免反序列失败），作用是验证版本一致性。 除了当前类需要实现Serializable接口之外，还必须保证其内部所属性也必须是可序列化的。（默认情况下，基本数据类型可序列化；如果是引用数据类型，那么需要改类型是可序列化类） 如果子类实现Serializable接口而父类未实现时，父类不会被序列化，但此时父类必须有个无参构造方法，否则会抛InvalidClassException异常 ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量 serialVersionUID # private static final long serialVersionUID = 771652260758459933L;\n可序列化类中的版本标识，JVM用这个字段来确定是否能够反序列化出对象。换句话说，只有对象序列化后的二进制数据中的serialVersionUID与当前对象的serialVersionUID相同，反序列化才能成功，否则就会失败。\n即使自己不显式的声明serialVersionUID，在序列化时，JVM也会生成一个。但是，最好还是自己手动声明一个，避免后续程序执行出现问题。\nIdea开启自动生成 # 然后在实现java.io.Serializable接口的类名上，ctrl + enter选择add 'serialVersionUID' field即可\ntransient # Java关键字\n对于transient 修饰的成员变量，在类的实例对象的序列化处理过程中会被忽略。 因此，transient变量不会贯穿对象的序列化和反序列化，生命周期仅存于调用者的内存中而不会写到磁盘里进行持久化。\n在持久化对象时，对于一些特殊的数据成员（如用户的密码，银行卡号等），我们不想用序列化机制来保存它。为了在一个特定对象的一个成员变量上关闭序列化，可以在这个成员变量前加上关键字transient。\nimport java.io.Serializable; public class Student implements Serializable { private static final long serialVersionUID = 771652260758459933L; private String id; private String name; private Integer age; // transient 修饰的属性不会被序列化 private transient String tempNum; public Student(String id, String name, Integer age, String tempNum) { this.id = id; this.name = name; this.age = age; this.tempNum = tempNum; } @Override public String toString() { return \u0026#34;Student{\u0026#34; + \u0026#34;id=\u0026#39;\u0026#34; + id + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, name=\u0026#39;\u0026#34; + name + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, age=\u0026#34; + age + \u0026#34;, tempNum=\u0026#39;\u0026#34; + tempNum + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#39;}\u0026#39;; } } 序列化\nStudent student = new Student(\u0026#34;s1\u0026#34;, \u0026#34;tom\u0026#34;, 18,\u0026#34;t1\u0026#34;); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(\u0026#34;./s1\u0026#34;)); objectOutputStream.writeObject(student); objectOutputStream.flush(); objectOutputStream.close(); 反序列化\nObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(\u0026#34;./s1\u0026#34;)); Student student = (Student)objectInputStream.readObject(); System.out.println(student); objectInputStream.close(); 自定义序列化规则 # 简单的话，可以在实现Serializable接口的类中，编写private void writeObject(ObjectOutputStream out) （序列化时会调用此方法）和private void readObject(ObjectInputStream in)（反序列化时会调用此方法）两个方法来实现，但是性能欠佳\n可以实现java.io.Externalizable接口，并重写writeExternal和readExternal方法来实现，性能不错\nimport java.io.*; public class Student implements Externalizable { private static final long serialVersionUID = 771652260758459933L; private String id; private String name; private Integer age; // transient 修饰的属性不会被序列化 private transient String tempNum; // 实现Externalizable反序列化必须要有无参构造 public Student() { } public Student(String id, String name, Integer age, String tempNum) { this.id = id; this.name = name; this.age = age; this.tempNum = tempNum; } @Override public String toString() { return \u0026#34;Student{\u0026#34; + \u0026#34;id=\u0026#39;\u0026#34; + id + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, name=\u0026#39;\u0026#34; + name + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, age=\u0026#34; + age + \u0026#34;, tempNum=\u0026#39;\u0026#34; + tempNum + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#39;}\u0026#39;; } // 定义序列化方法 @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeUTF(this.id); out.writeUTF(this.name); out.writeInt(this.age); } // 定义反序列化逻辑 @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // 读写顺序一定要一致 this.id = in.readUTF(); this.name = in.readUTF(); this.age = in.readInt(); } } 序列化\nStudent student = new Student(\u0026#34;s1\u0026#34;, \u0026#34;tom\u0026#34;, 18,\u0026#34;t1\u0026#34;); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(\u0026#34;./s1\u0026#34;)); objectOutputStream.writeObject(student); objectOutputStream.flush(); objectOutputStream.close(); 反序列化\nObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(\u0026#34;./s1\u0026#34;)); Student student = (Student)objectInputStream.readObject(); System.out.println(student); objectInputStream.close(); NIO # 很多技术框架都使用NIO技术，学习和掌握Java NIO技术对于高性能、高并发网络的应用是非常关键的。\nNIO简介 # NIO (New lO)也有人称之为Java non-blocking lO是从Java 1.4版本开始引入的一个新的IO API，可以替代标准的Java lO API。NIO与原来的IO有同样的作用和目的，但是使用的方式完全不同，NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。NIO可以理解为非阻塞IO,传统的IO的read和write只能阻塞执行，线程在读写IO期间不能干其他事情，比如调用socket.read()时，如果服务器一直没有数据传输过来，线程就一直阻塞，而NIO中可以配置socket为非阻塞模式。\nNIO相关类都被放在java.nio包及子包下，并且对原java.io包中的很多类进行改写。 NIO有三大核心部分：Channel(通道)，Buffer(缓冲区)，Selector(选择器) Java NlO的非阻塞模式，使一个线程从某通道发送请求或者读取数据，但是它仅能得到目前可用的数据，如果目前没有数据可用时，就什么都不会获取，而不是保持线程阻塞，所以直至数据变的可以读取之前，该线程可以继续做其他的事情。非阻塞写也是如此，一个线程请求写入一些数据到某通道，但不需要等待它完全写入，这个线程同时可以去做别的事情。 通俗理解：NIO是可以做到用一个线程来处理多个操作的。假设有1000个请求过来，根据实际情况，可以分配20或者80个线程来处理。不像之前的阻塞IO那样，非得分配1000个。 NIO VS BIO # BIO # BIO全称是Blocking IO，同步阻塞式IO，是JDK1.4之前的传统IO模型，服务器实现模式为一个连接一个线程，即客户端有连接请求时服务器端就需要启动一个线程进行处理，如下图所示\n虽然此时服务器具备了高并发能力，即能够同时处理多个客户端请求了，但是却带来了一个问题，随着开启的线程数目增多，将会消耗过多的内存资源，导致服务器变慢甚至崩溃，NIO可以一定程度解决这个问题。\nNIO # 同步非阻塞，服务器实现模式为一个线程处理多个请求(连接)，即客户端发送的连接请求都会注册到多路复用器上，多路复用器轮询到连接有I/O请求就进行处理。\n一个线程中就可以调用多路复用接口（java中是select）阻塞同时监听来自多个客户端的IO请求，一旦有收到IO请求就调用对应函数处理，NIO擅长1个线程管理多条连接，节约系统资源。\n关系图的说明:\n每个Channel对应一个 Buffer。 Selector 对应一个线程，一个线程对应多个Channel。 该图反应了有三个Channel注册到该Selector。 程序切换到那个Channel是由事件决定的（Event）。 Selector会根据不同的事件，在各个通道上切换。 Buffer就是一个内存块，底层是一个数组。 数据的读取和写入是通过Buffer，但是需要flip()切换读写模式，而BIO是单向的，要么输入流要么输出流。 Buffer # Buffer本质上就是一块可以重复进行读写的内存空间（底层就是个数组），重要的五个概念如下\ncapacity：容量，缓冲区的总长度（数组的长度），如果缓冲区已满还需要写入数据，就需要先清空再写入，并且创建后不能更改。 position：位置，类似于指针，表示下一个要读取或写入的数据的索引。起始位置为0，随着数据的写入不断的后移，最大为capacity - 1。当从buffer中读取数据时，position重置回0，记录下一个要读取数据的位置。 limit：缓冲区中不可操作的下一个元素的位置（界限），即操作停止的最终位置 + 1，用于限制程序可以写入或者读取的数据量，通常为limit \u0026lt;= capacity 标记 (mark)与重置 (reset)：标记是一个索引， 通过 Buffer 中的mark()方法 指定Buffer中一个特定的position，之后可以通过调用reset()方法恢复到这个 position 四个变量的关系：0 \u0026lt;= mark \u0026lt;= position \u0026lt;= limit \u0026lt;= capacity 创建Buffer # 常见的Buffer子类 ByteBuffer CharBuffer ShortBuffer IntBuffer LongBuffer FloatBuffer DoubleBuffer // 创建非直接缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); // 创建直接缓冲区（只有ByteBuffer有这个方法） ByteBuffer buffer = ByteBuffer.allocateDirect(1024); 直接缓冲区和非直接缓冲区 # 只有ByteBuffer可以区分创建直接缓冲区和非直接缓冲区\n直接缓冲区 ByteBuffer.allocateDirect() 创建在非应用内存（非堆内存）上 数据流：本地IO--\u0026gt;直接内存--\u0026gt;本地IO 使用场景：存储大数据量、长生命周期数据、频繁IO操作 非直接缓冲区 ByteBuffer.allocate() 创建在应用内存（堆内存） 数据流：本地IO--\u0026gt;直接内存--\u0026gt;非直接内存--\u0026gt;直接内存--\u0026gt;本地IO 使用场景：存储小数据量、短生命周期数据、不频繁IO操作 常见方法 # Buffer clear()：清空缓冲区并返回对缓冲区的引用，并且设置position=0，limit=capcity Buffer flip()：为将缓冲区的界限设置为当前位置， 并将当前位置重置为0 int capacity()：返回 Buffer 的 capacity 大小 boolean hasRemaining()： 判断缓冲区中是否还有元素 int limit()：返回 Buffer 的界限(limit) 的位置 Buffer limit(int n)：将设置缓冲区界限为 n, 并返回一个具有新 limit 的缓冲区对象 Buffer mark()：对缓冲区设置标记 int position()：返回缓冲区的当前位置 position Buffer position(int n)：将设置缓冲区的当前位置为 n， 并返回修改后的 Buffer 对象 int remaining()：返回 position 和 limit 之间的元素个数 Buffer reset()：将位置 position 转到以前设置的mark 所在的位置 Buffer rewind()：将位置设为为 0， 取消设置的 mark get()：读取单个字节 get(byte[] dst)：批量读取多个字节到 dst 中 get(int index)：读取指定索引位置的字节(不会移动 position)放到入数据到Buffer中 put(byte b)：将给定单个字节写入缓冲区的当前位置 put(byte[] src)：将 src 中的字节写入缓冲区的当前位置 put(int index, byte b)：将指定字节写入缓冲区的索引 位置(不会移动 position) Buffer读写数据的四个步骤 # 写入数据到Buffer 调用flip()方法，转换为读取模式 从Buffer中读取数据 调用buffer.clear()方法或者buffer.compact()方法清除缓冲区 理解 # 1、创建一个容量capacity为5的buffer，此时缓冲区默认为写模式\nByteBuffer buffer = ByteBuffer.allocate(10); 2、写入数据到Buffer中，写入三个字节\nchannel.read(buffer) 3、切换为读模式，将position指向0位置，并将limit指向上一步position的位置3（最后有效数据的后一位）\nbuffer.flip(); 4、从Buffer中逐个字节读取，直到position = limit位置\nStringBuilder stringBuilder = new StringBuilder(); while (buffer.position() \u0026lt; buffer.limit()){ stringBuilder.append((char) buffer.get()); } 5、清空Buffer，还原为写模式，准备下一次读取，循环至第一步\nbuffer.clear(); Channel # Channel 是 NIO 的核心概念，它表示 IO 源与目标打开的连接，这个连接可以连接到 I/O 设备（例如：磁盘文件，Socket）或者一个支持 I/O 访问的应用程序。 Channel 类似于传统的“流”，只不过 Channel本身不能直接访问数据，Channel 只能与 Buffer 进行交互。\nChannel和Stream的区别 # 通道可以同时进行读写，而流只能读或者只能写 通道可以实现异步读写数据 通道可以从缓冲读数据，也可以写数据到缓冲 创建Channel # 常见Channel实现类 FileChannel：用于读取、写入、映射和操作文件的通道。 DatagramChannel：通过UDP读写网络中的数据通道。 SocketChannel：通过TCP读写网络中的数据。 ServerSocketChannel：可以监听新进来的 TCP 连接，对每一个新进来的连接都会创建一个SocketChannel。 获取Channel的方式 对于支持Channel的对象调用getChannel()方法 FileInputStream FileOutputStream RandomAccessFile DatagramSocket Socket ServerSocket 使用 Files 类的静态方法newByteChannel()获取字节通道 通过通道的静态方法open()打开并返回指定通道 FileChannel # Java NIO FileChannel是连接文件的通道。使用FileChannel，可以从文件中读取数据和将数据写入文件。\nFileChannel 的优点包括：\n在文件的特定位置读取和写入； 将文件的一部分直接加载到内存中，这样效率最高； 可以以更快的速度将文件数据从一个通道传输到另一个通道； 可以锁定文件的一部分以限制其他线程访问； 为了避免数据丢失，我们可以强制将更新的文件立即写入存储。 open # FileChannel可以通过静态方法public static FileChannel open(Path path, OpenOption... options)打开\nOpenOption # package java.nio.file; public enum StandardOpenOption implements OpenOption { READ, // 读 WRITE, // 写 APPEND, // 追加写 TRUNCATE_EXISTING, // 如果文件存在并且以WRITE的方式连接时就会把文件内容清空，如果文件只以READ连接时，该选项会被忽略 CREATE, // 如果文件不存在则创建 CREATE_NEW, // 创建文件如果存在则抛异常 DELETE_ON_CLOSE, // Channel关闭时删除文件 SPARSE, // 创建稀疏文件，与CREATE_NEW选项配合使用 SYNC, // 要求每次写入要把内容和元数据刷到存储设备上 DSYNC; // 要求每次写入要把内容刷到存储设备上 } 常见方法 # int read(ByteBuffer dst)：从Channel 到 中读取数据到 ByteBuffer long read(ByteBuffer[] dsts)： 将Channel中的数据“分散”到 ByteBuffer[] int write(ByteBuffer src)：将 ByteBuffer中的数据写入到 Channel long write(ByteBuffer[] srcs)：将 ByteBuffer[] 到 中的数据“聚集”到 Channel long position()：返回此通道的文件位置 FileChannel position(long p)：设置此通道的文件位置 long size()：返回此通道的文件的当前大小 FileChannel truncate(long s)：将此通道的文件截取为给定大小 void force(boolean metaData)：强制将所有对此通道的文件更新写入到存储设备中 零拷贝 # 零拷贝是指计算机操作的过程中，CPU不需要为数据在内存之间的拷贝消耗资源。\nlong transferTo(long position, long count, WritableByteChannel target)\nlong transferFrom(ReadableByteChannel src, long position, long count)\n这两个方法底层调用了Linux和UNIX系统的sendfile()系统调用实现了零拷贝\n// 源数据通道 FileChannel sChan = FileChannel.open(Paths.get(\u0026#34;D:\\\\test.jpg\u0026#34;), StandardOpenOption.READ); // 目标数据通道 FileChannel tChan = FileChannel.open(Paths.get(\u0026#34;E:\\\\test_copy.jpg\u0026#34;), StandardOpenOption.WRITE,StandardOpenOption.CREATE); // 进行拷贝 sChan.transferTo(0,sChan.size(),tChan); sChan.close(); tChan.close(); Demo # 读数据 # // 1、创建通道 FileChannel channel = FileChannel.open(Paths.get(\u0026#34;D:\\\\test.txt\u0026#34;), StandardOpenOption.READ); // 2、创建字节缓冲区，大小10 ByteBuffer buffer = ByteBuffer.allocate(5); // 3、读数据 StringBuilder stringBuilder = new StringBuilder(); // 4、每次读取字节数量 \u0026gt; 0,并且 \u0026lt;= buffer.capacity() while (channel.read(buffer) \u0026gt; 0){ // 5、切换为读模式 buffer.flip(); // 6、将Buffer中的字节挨个读取 while (buffer.position() \u0026lt; buffer.limit()){ stringBuilder.append((char) buffer.get()); } // 7、清空Buffer切换为写模式 buffer.clear(); } // 8、关闭通道 channel.close(); System.out.println(stringBuilder.toString()); 写数据 # // 1、创建通道 FileChannel channel = FileChannel.open(Paths.get(\u0026#34;D:\\\\test.txt\u0026#34;), StandardOpenOption.WRITE,StandardOpenOption.CREATE); // 2、创建字节缓冲区，大小1024 ByteBuffer buffer = ByteBuffer.allocate(1024); // 3、读数据 String target = \u0026#34;Hello Java Nio!\u0026#34;; byte[] bytes = target.getBytes(StandardCharsets.UTF_8); // 4、依次写入字节数据 for (int i = 0; i \u0026lt; bytes.length ; i++){ buffer.put(bytes[i]); if (buffer.position() == buffer.limit() || i == bytes.length - 1){ // 5、切换写模式 buffer.flip(); // 6、缓冲中的数据写入到通道 channel.write(buffer); // 7、清空缓冲 buffer.clear(); } } // 8、将通道中的数据强制刷出到磁盘 channel.force(false); // 9、关闭通道 channel.close(); ServerSocketChannel和SocketChannel # 新的socket通道类可以运行非阻塞模式并且是可选择的，可以激活大程序（如网络服务器和中间件组件）巨大的可伸缩性和灵活性。\nDatagramChannel 和 SocketChannel实现定义读和写功能的接口而ServerSocketChannel不实现。ServerSocketChannel 负责监听传入的连接和创建新的 SocketChannel 对象，它本身不传输数据。\n（DatagramChannel、SocketChannel、ServerSocketChannel）在被实例化时都会创建一个对等 socket 对象： java.net 包中的（DatagramSocket、Socket、ServerSocket），可以通过Channel的socket()方法获取，此外，这三个 java.net 类现在都有getChannel()方法。\nconfigureBlocking()（参数为false：非阻塞，true：阻塞）的作用：对于ServerSocketChannel来说，非阻塞意味着accept()方法为非阻塞，即时没有客户端连接，也会立即返回null；对于SocketChannel来说，read()方法为非阻塞，即时没有读到数据，也会继续执行下面的逻辑。\nDemo # 服务端 # // 1、开启ServerSocketChannel ServerSocketChannel channel = ServerSocketChannel.open(); // 2、设置监听的端口 channel.bind(new InetSocketAddress(8080)); // 3、设置为非阻塞 channel.configureBlocking(false); SocketChannel socketChannel; while (true){ // 4、接受客户端连接 socketChannel = channel.accept(); // 判断是否获取到连接 if (socketChannel != null){ // 5、设置客户端连接通道为非阻塞 socketChannel.configureBlocking(false); // 6、获取客户端信息 Socket socket = socketChannel.socket(); System.out.println(\u0026#34;客户端连接：\u0026#34; + socket.getRemoteSocketAddress()); // 7、创建Buffer ByteBuffer buffer = ByteBuffer.allocate(1024); // 8、轮询读取客户端发来的消息 while (true){ // 判断是否有消息 if (socketChannel.read(buffer) \u0026gt; 0){ buffer.flip(); byte[] b = new byte[buffer.limit()]; buffer.get(b); System.out.println(\u0026#34;客户端说：\u0026#34; + new String(b, StandardCharsets.UTF_8)); buffer.clear(); } } } } 客户端 # // 1、建立和服务端的连接通道 SocketChannel channel = SocketChannel.open(new InetSocketAddress(\u0026#34;127.0.0.1\u0026#34;,8080)); // 2、设置通道为非阻塞 channel.configureBlocking(false); // 3、创建Buffer ByteBuffer buffer = ByteBuffer.allocate(1024); // 4、向服务器发送消息 while (true){ String s = new Scanner(System.in).nextLine(); if (\u0026#34;exit\u0026#34;.equals(s)){ break; } buffer.put(s.getBytes(StandardCharsets.UTF_8)); buffer.flip(); channel.write(buffer); buffer.clear(); } Selector # 选择器（Selector）是SelectableChannle对象的多路复用器，Selector可以同时监控多个SelectableChannel的IO状况，也就是说，利用Selector可使一个单独的线程管理多个Channel。Selector是非阻塞IO的核心。\nJava 的 NIO，用非阻塞的 IO 方式。可以用一个线程，处理多个的客户端连接，就会使用到 Selector(选择器) Selector 能够检测多个注册的通道上是否有事件发生。注意：多个 Channel 以事件的方式可以注册到同一个Selector，如果有事件发生，便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管 理多个通道，也就是管理多个连接和请求。 只有在连接/通道真正有读写事件发生时，才会进行读写，就大大地减少了系统开销，并且不必为每个连接都创建一个线程，不用去维护多个线程 避免了多线程之间的上下文切换导致的开销 注意：注册到Selector的Channel必须是非阻塞的以及可选择的（继承实现SelectableChannel抽象类），FileChannel不能注册到Selector，因为FileChannel不能切换为非阻塞模式，SelectableChannel抽象类有一个configureBlocking()方法，SocketChannel, ServerSocketChannel, DatagramChannel 都是直接继承了 AbstractSelectableChannel（SelectableChannel的子类）抽象类。\n常用方法 # Selector.open()：创建选择器\nSelectableChannel.register(Selector sel, int ops)：将一个可选择的通道注册到选择器sel上，第二个参数是选择选择器监听Channel关注什么事件\nSelectionKey.OP_CONNECT：连接就绪，一个客户端成功连接到另一个服务器时触发\nSelectionKey.OP_ACCEPT：接收就绪，当服务端收到客户端的一个连接请求时触发\nSelectionKey.OP_READ：读就绪，有一个数据可读的通道时触发\nSelectionKey.OP_WRITE：写就绪，有一个等待写数据的通道时触发\nint select()：阻塞到至少有一个通道在注册的事件上就绪了\nselect(long timeout)：和select()方法一样，可以设置最长阻塞时间（毫秒）\nselectNow()：不会阻塞，即时没有通道事件也会返回0\nselectedKeys()：一旦调用了select()方法，并且返回值表明有一个或更多个通道就绪了，然后可以调用该方法获取所有就绪通道。\nkeys()：返回当前所有注册在selector中channel的selectionKey，和selectedKeys()的区别就是，这个方法返回的是所有已注册的通道，而不是当前已就绪的通道。\nDemo # 服务端\n// 创建选择器 Selector selector = Selector.open(); // 创建可选择通道ServerSocketChannel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(8080)); // 设置为非阻塞模式 serverSocketChannel.configureBlocking(false); // 注册到选择器，并且关注通道的连接事件 serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT); // select()为阻塞方法，当获取到新事件后，会返回事件的数量，并将就绪事件放入SelectionKey集合中 while (selector.select() \u0026gt; 0){ // 获取事件集合 Set\u0026lt;SelectionKey\u0026gt; selectionKeys = selector.selectedKeys(); Iterator\u0026lt;SelectionKey\u0026gt; iterator = selectionKeys.iterator(); // 处理每一个事件 while (iterator.hasNext()){ SelectionKey selectionKey = iterator.next(); // 如果是连接事件(有新的客户端连接) if (selectionKey.isAcceptable()){ SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); System.out.println(\u0026#34;有新的客户端连接：\u0026#34; + socketChannel.socket().getRemoteSocketAddress()); // 将客户端通道注册到选择器，并且关注通道的可读事件 socketChannel.register(selector,SelectionKey.OP_READ); // 如果是可读取事件 }else if (selectionKey.isReadable()){ // 创建buffer接数据 ByteBuffer buffer = ByteBuffer.allocate(1024); // 获取事件对应的通道 SocketChannel channel = (SocketChannel)selectionKey.channel(); channel.read(buffer); buffer.flip(); System.out.println(\u0026#34;收到客户端信息：\u0026#34; + new String(buffer.array())); } // 处理完事件，切记要移除事件 iterator.remove(); } } 客户端\nSocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(\u0026#34;127.0.0.1\u0026#34;,8080)); socketChannel.configureBlocking(false); ByteBuffer buffer = ByteBuffer.allocate(1024); while (true){ System.out.print(\u0026#34;input:\u0026#34;); String a = new Scanner(System.in).nextLine(); if (\u0026#34;quit\u0026#34;.equals(a)){ break; } byte[] bytes = a.getBytes(StandardCharsets.UTF_8); for (int i = 0 ;i \u0026lt; bytes.length;i ++){ buffer.put(bytes[i]); if (buffer.position() == buffer.limit() || i == bytes.length - 1){ buffer.flip(); socketChannel.write(buffer); buffer.clear(); } } } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/fbeea4f4/5103bed4/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eFile类 \n    \u003cdiv id=\"file类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#file%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eFile类的一个对象，代表一个文件或一个文件目录(Directory)\u003c/p\u003e","title":"3、IO与NIO","type":"posts"},{"content":" 程序流程控制 # 顺序结构 # 分支结构 # 分支语句1： if-else结构 # if-else使用说明 # 1、条件表达式必须是布尔表达式（关系表达式或逻辑表达式）、布尔变量\n2、语句块只有一条执行语句时，一对{}可以省略，但建议保留\n3、if-else语句结构，根据需要可以嵌套使用\n4、当if-else结构是“多选一”时，最后的else是可选的，根据需要可以省略\n5、当多个条件是“互斥”关系时，条件判断语句及执行语句间顺序无所谓\n当多个条件是“包含”关系时，“小上大下 / 子上父下”\n分支语句2： switch-case结构 # 说明 # 1、根据switch中表达式中的值，依次匹配各个case的常量，一旦匹配成功，则进入相应的case结构中，调用其执行语句，当调用完执行语句完以后，则仍然继续向下执行其他case语句中的执行语句，直到遇到break关键字或者switch-case结构末尾结束为止。\n2、break，可以使用在switch-case结构中，表示一旦执行到此关键字，就跳出switch-case结构。\n3、switch-case结构中的表达式，只能是如下的六种数据类型之一：byte、short、char、int、枚举类型（JDK5.0新增）、String类型（JDK7.0新增）。\n4、case之后只能声明常量，不可以声明范围。\n5、break关键字在switch结构中是可选的。\n6、default类似与if-else中的else，default结构是可选的。\n7、如果switch中的多个case的执行语句相同，那么可以考虑合并，例如：\nswitch(expression){ // 合并value1、value2、value3 case value1: case value2: case value3: //TODO: do something break; case value4: //TODO: do something } switch-case和if-else的选择： # 1、凡是可以使用switch-case的结构，都可以使用if-else\n2、当既可以使用switch（表达式取值情况不多时）又可以使用if时，优先选择switch。\n循环结构 # 在某些条件满足的情况下，反复执行特定代码的功能\nfor循环 # while循环 # 初始化部分出了while循环以后仍可以调用\ndo-while循环 # 说明：\n1、运行的时候会先执行一次循环体部分和迭代部分。\n无限循环格式 # for循环 # for ( ; ; ){ }\nfor循环的初始化部分和迭代部分，可以使用,来分割执行多个语句，要求i、j必须是同一类型\nfor(int i = 0,j = i + 10;i \u0026lt; 5;i++,j += 2){ System.out.println(\u0026#34;i = \u0026#34; + i + \u0026#34;,j = \u0026#34; + j); } //\ti = 0,j = 10 //\ti = 1,j = 12 //\ti = 2,j = 14 //\ti = 3,j = 16 //\ti = 4,j = 18 while循环 # while(true){ }\ndo-while循环 # do{ }while(true);\n结束循环的两种方式 # 1、循环条件部分返回false\n2、在循环体中，执行break\n嵌套循环(多重循环) # 将一个循环放在另一个循环体内，就形成了嵌套循环。\n设外层循环次数为m次，内层为n次，则内层循环体实际上需要执行m*n次。\n外层循环控制行数，内层循环控制列数\n优化：计算程序运行时间 # 衡量功能代码的优劣：\n1、保证代码的功能正确性；\n2、代码的可读性；\n3、健壮性；\n4、高效率与低存储（算法的好坏）：时间复杂度、空间复杂度\n//获取当前开始时间距离1970-01-01 00:00:00的毫秒数 long start = System.currentTimeMillis(); .... //获取当前结束时间距离1970-01-01 00:00:00的毫秒数 long end= System.currentTimeMillis(); //时间差计算程序运行时间 System.out.println(\u0026#34;所花费的时间：\u0026#34; + (end - start)); 关键字break、continue # continue和break的后面不可以声明执行语句。\nflag: for(int i = 0;i \u0026lt; 10;i++){ for(int j = 0;j \u0026lt; 100;j++){ if(j \u0026gt; 20){ break flag; //continue flag; } } } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/f742d77f/3427b306/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e程序流程控制 \n    \u003cdiv id=\"程序流程控制\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%a8%8b%e5%ba%8f%e6%b5%81%e7%a8%8b%e6%8e%a7%e5%88%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"说明: untitle.png\"\n    data-zoom-src=\"/posts/3ab7256e/80aacaea/f742d77f/3427b306/image/202109181115362.jpg\"\n    src=\"/posts/3ab7256e/80aacaea/f742d77f/3427b306/image/202109181115362.jpg\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"3、Java基础语法（下）：程序流程控制","type":"posts"},{"content":"用Python来编写脚本简化日常的运维工作是Python的一个重要用途。在Linux下，有许多系统命令可以让我们时刻监控系统运行的状态，如ps，top，free等等。要获取这些系统信息，Python可以通过subprocess模块调用并获取结果。但这样做显得很麻烦，尤其是要写很多解析代码。\n在Python中获取系统信息的另一个好办法是使用psutil这个第三方模块。顾名思义，psutil = process and system utilities，它不仅可以通过一两行代码实现系统监控，还可以跨平台使用，支持Linux／UNIX／OSX／Windows等，是系统管理员和运维小伙伴不可或缺的必备模块。\npsutil是一个开源且跨平台的库，其提供了便利的函数用来获取才做系统的信息，比如CPU，内存，磁盘，网络等。此外，psutil还可以用来进行进程管理，包括判断进程是否存在、获取进程列表、获取进程详细信息等。而且psutil还提供了许多命令行工具提供的功能，包括：ps，top，lsof，netstat，ifconfig， who，df，kill，free，nice，ionice，iostat，iotop，uptime，pidof，tty，taskset，pmap。\npsutil是一个跨平台的库，在官方网站上查到其支持如下操作系统。\n安装 # pip install psutil 检测类型 # CPU # 函数 描述 psutil.cpu_count() cpu_count(,[logical]):默认返回逻辑CPU的个数,当设置logical的参数为False时，返回物理CPU的个数。 psutil.cpu_percent() cpu_percent(,[percpu],[interval])：返回CPU的利用率,percpu为True时显示所有物理核心的利用率,interval不为0时,则阻塞时显示interval执行的时间内的平均利用率 psutil.cpu_times() cpu_times(,[percpu])：以命名元组(namedtuple)的形式返回cpu的时间花费,percpu=True表示获取每个CPU的时间花费 psutil.cpu_times_percent() cpu_times_percent(,[percpu])：功能和cpu_times大致相同，看字面意思就能知道，该函数返回的是耗时比例。 psutil.cpu_stats() cpu_stats()以命名元组的形式返回CPU的统计信息，包括上下文切换，中断，软中断和系统调用次数。 psutil.cpu_freq() cpu_freq([percpu])：返回cpu频率 import psutil print(\u0026#39;逻辑CPU个数：\u0026#39;,psutil.cpu_count()) print(\u0026#39;物理CPU个数：\u0026#39;,psutil.cpu_count(logical=False)) print(\u0026#39;CPU利用率：\u0026#39;,psutil.cpu_percent()) print(\u0026#39;CPU花费时间：\u0026#39;,psutil.cpu_times()) print(\u0026#39;CPU耗时比例：\u0026#39;,psutil.cpu_times_percent()) print(\u0026#39;CPU统计数据：\u0026#39;,psutil.cpu_stats()) print(\u0026#39;CPU频率：\u0026#39;,psutil.cpu_freq()) \u0026#39;\u0026#39;\u0026#39; 逻辑CPU个数： 8 物理CPU个数： 4 CPU利用率： 0.0 CPU花费时间： scputimes(user=1530.0, system=792.0625, idle=28419.765624999996, interrupt=37.53125, dpc=94.859375) CPU耗时比例： scputimes(user=0.0, system=0.0, idle=0.0, interrupt=0.0, dpc=0.0) CPU统计数据： scpustats(ctx_switches=74195467, interrupts=48949992, soft_interrupts=0, syscalls=199058572) CPU频率： scpufreq(current=2419.0, min=0.0, max=2419.0) \u0026#39;\u0026#39;\u0026#39; 内存memory # 函数 描述 virtual_memory() 获取系统内存的使用情况，以命名元组的形式返回内存使用情况，包括总内存，可用内存，内存利用率，buffer和cache等。单位为字节。 swap_memory() 获取系统交换内存的统计信息，以命名元组的形式返回swap/memory使用情况，包含swap中页的换入和换出。 import psutil print(\u0026#39;内存使用情况：\u0026#39;,psutil.virtual_memory()) print(\u0026#39;系统交换内存统计：\u0026#39;,psutil.swap_memory()) \u0026#39;\u0026#39;\u0026#39; 内存使用情况： svmem(total=16805064704, available=6475837440, percent=61.5, used=10329227264, free=6475837440) 系统交换内存统计： sswap(total=5637144576, used=1095626752, free=4541517824, percent=19.4, sin=0, sout=0) \u0026#39;\u0026#39;\u0026#39; 磁盘disk # 函数 描述 psutil.disk_io_counters() disk_io_counters([perdisk])：以命名元组的形式返回磁盘io统计信息(汇总的)，包括读、写的次数，读、写的字节数等。 当perdisk的值为True，则分别列出单个磁盘的统计信息(字典：key为磁盘名称，value为统计的namedtuple)。 psutil.disk_partitions() disk_partitions([all=False])：以命名元组的形式返回所有已挂载的磁盘，包含磁盘名称，挂载点，文件系统类型等信息。 当all等于True时，返回包含/proc等特殊文件系统的挂载信息 psutil.disk_usage() disk_usage(path)：以命名元组的形式返回path所在磁盘的使用情况，包括磁盘的容量、已经使用的磁盘容量、磁盘的空间利用率等。 import psutil print(\u0026#39;磁盘io统计：\u0026#39;,psutil.disk_io_counters()) print(\u0026#39;磁盘io分磁盘统计：\u0026#39;,psutil.disk_io_counters(perdisk=True)) print(\u0026#39;已挂载磁盘：\u0026#39;,psutil.disk_partitions()) print(\u0026#39;c盘所在磁盘使用情况：\u0026#39;,psutil.disk_usage(path=\u0026#39;c:/\u0026#39;)) \u0026#39;\u0026#39;\u0026#39; 磁盘io统计： sdiskio(read_count=227922, write_count=109967, read_bytes=8124451840, write_bytes=3854718464, read_time=220, write_time=33) 磁盘io分磁盘统计： {\u0026#39;PhysicalDrive0\u0026#39;: sdiskio(read_count=227922, write_count=109967, read_bytes=8124451840, write_bytes=3854718464, read_time=220, write_time=33)} 已挂载磁盘： [sdiskpart(device=\u0026#39;C:\\\\\u0026#39;, mountpoint=\u0026#39;C:\\\\\u0026#39;, fstype=\u0026#39;NTFS\u0026#39;, opts=\u0026#39;rw,fixed\u0026#39;, maxfile=255, maxpath=260), sdiskpart(device=\u0026#39;D:\\\\\u0026#39;, mountpoint=\u0026#39;D:\\\\\u0026#39;, fstype=\u0026#39;NTFS\u0026#39;, opts=\u0026#39;rw,fixed\u0026#39;, maxfile=255, maxpath=260), sdiskpart(device=\u0026#39;E:\\\\\u0026#39;, mountpoint=\u0026#39;E:\\\\\u0026#39;, fstype=\u0026#39;NTFS\u0026#39;, opts=\u0026#39;rw,fixed\u0026#39;, maxfile=255, maxpath=260)] c盘所在磁盘使用情况： sdiskusage(total=107374178304, used=75255386112, free=32118792192, percent=70.1) \u0026#39;\u0026#39;\u0026#39; 网络net # 函数 详情 psutil.net_io_counter([pernic]) 以命名元组的形式返回当前系统中每块网卡的网络io统计信息，包括收发字节数，收发包的数量、出错的情况和删包情况。当pernic为True时，则列出所有网卡的统计信息。 psutil.net_connections([kind]) 以列表的形式返回每个网络连接的详细信息(namedtuple)。命名元组包含fd, family, type, laddr, raddr, status, pid等信息。kind表示过滤的连接类型，支持的值如下：(默认为inet) psutil.net_if_addrs() 以字典的形式返回网卡的配置信息，包括IP地址和mac地址、子网掩码和广播地址。 psutil.net_if_stats() 返回网卡的详细信息，包括是否启动、通信类型、传输速度与mtu。 import psutil print(\u0026#39;各网卡io统计：\u0026#39;,psutil.net_io_counters()) print(\u0026#39;当前每个网络连接详细信息：\u0026#39;,psutil.net_connections()) print(\u0026#39;网卡配置信息：\u0026#39;,psutil.net_if_addrs()) print(\u0026#39;网卡详细信息：\u0026#39;,psutil.net_if_stats()) \u0026#39;\u0026#39;\u0026#39; 各网卡io统计： snetio(bytes_sent=84594155, bytes_recv=175658111, packets_sent=113985, packets_recv=243075, errin=0, errout=0, dropin=40, dropout=0) 当前每个网络连接详细信息： [sconn(fd=-1, family=\u0026lt;AddressFamily.AF_INET: 2\u0026gt;, type=\u0026lt;SocketKind.SOCK_STREAM: 1\u0026gt;, laddr=addr(ip=\u0026#39;127.0.0.1\u0026#39;, port=52732), ... , status=\u0026#39;ESTABLISHED\u0026#39;, pid=9624)] 网卡配置信息： {\u0026#39;WLAN\u0026#39;: [snicaddr(family=\u0026lt;AddressFamily.AF_LINK: -1\u0026gt;, address=\u0026#39;D4-1B-81-2F-22-C3\u0026#39;, ... , address=\u0026#39;::1\u0026#39;, netmask=None, broadcast=None, ptp=None)]} 网卡详细信息： {\u0026#39;本地连接* 18\u0026#39;: snicstats(isup=False, duplex=\u0026lt;NicDuplex.NIC_DUPLEX_FULL: 2\u0026gt;, speed=0, mtu=1500)} \u0026#39;\u0026#39;\u0026#39; 进程pid # 函数 描述 psutil.pids() 以列表的形式返回当前正在运行的进程 psutil.pid_exists(1) 判断给点定的pid是否存在 psutil.process_iter() 迭代当前正在运行的进程，返回的是每个进程的Process对象 psutil.Process( pid ) 对进程进行封装，可以使用该类的方法获取进行的详细信息，或者给进程发送信号。传入参数为pid psutil.Process( pid )获取进程相关信息的方法：\nname()：获取进程的名称 cmdline()：获取启动进程的命令行参数 create_time()：获取进程的创建时间(时间戳格式) num_fds()：进程打开的文件个数 num_threads()：进程的子进程个数 is_running()：判断进程是否正在运行 send_signal()：给进程发送信号，类似与os.kill等 kill()：发送SIGKILL信号结束进程 terminate()：发送SIGTEAM信号结束进程 传感器sensors # 函数 描述 psutil.sensors_temperatures(fahrenheit=False) 返回硬件的温度 psutil.sensors_fans() 返回电池状态 psutil.sensors_battery() 返回硬件风扇速度 其他 # 函数 描述 psutil.users() 以命名元组的方式返回当前登陆用户的信息，包括用户名，登陆时间，终端，与主机信息 psutil.boot_time() 以时间戳的形式返回系统的启动时间 ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/1b37041b/46b8de4c/45f822e0/","section":"文章","summary":"\u003cp\u003e用Python来编写脚本简化日常的运维工作是Python的一个重要用途。在Linux下，有许多系统命令可以让我们时刻监控系统运行的状态，如\u003ccode\u003eps\u003c/code\u003e，\u003ccode\u003etop\u003c/code\u003e，\u003ccode\u003efree\u003c/code\u003e等等。要获取这些系统信息，Python可以通过\u003ccode\u003esubprocess\u003c/code\u003e模块调用并获取结果。但这样做显得很麻烦，尤其是要写很多解析代码。\u003c/p\u003e","title":"3、psutil","type":"posts"},{"content":" Servlet # Servlet(Server Applet)是Java Servlet的简称，称为小服务程序或服务连接器，用Java编写的服务器端程序，具有独立于平台和协议的特性，主要功能在于交互式地浏览和生成数据，生成动态Web内容，它是由Sun公司（现在是Oracle公司）开发的，作为Java Servlet API的一部分，包含在Java EE（Enterprise Edition）规范中\n狭义的Servlet是指Java语言实现的一个接口，广义的Servlet是指任何实现了这个Servlet接口的类，一般情况下，将Servlet理解为后者。Servlet运行于支持Java的应用服务器中。从原理上讲，Servlet可以响应任何类型的请求，但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。\nservlet-api # servlet-api.jar并不包含在我们安装的JDK（Java SE）中，它是Java EE规范的一部分，通常作为Web容器（如Tomcat、Jetty、WebLogic等）的一部分提供。\n\u0026lt;!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;javax.servlet\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;javax.servlet-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.0.1\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;provided\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; 如果您想要使用servlet-api.jar来编写servlet应用程序，您可以通过以下途径获取：\n下载并安装一个Java Web容器，如Apache Tomcat。然后在Tomcat安装目录下的lib文件夹中找到servlet-api.jar。\n下载Java EE，其中包括Servlet API的实现，并在安装目录下的lib文件夹中找到servlet-api.jar（Oracle已经不提供Java EE的下载了）\nservlet-api.jar只是在编译的时候需要，运行的时候使用Web容器内置的即可，所以不必打包进项目依赖，以免发生冲突，使用maven可以声明scope为provided\n注意：由于Java EE被Orcale捐献给Apache，并且在2021年更名为Jakarta EE，所以从 Jakarta EE 9 开始对应用的Servlet类名是：jakarta.servlet.Servlet，而之前是javax.servlet.Servlet，所以如果之前的项目还在使用javax.servlet.Servlet，那么你的项目无法直接部署到Tomcat10+版本上。你只能部署到Tomcat9-版本上。在Tomcat9以及Tomcat9之前的版本中还是能够识别javax.servlet这个包。\nJavaWeb三大组件 # Servlet程序、Filter过滤器、Listener监听器\nServlet的继承关系 # javax.servlet.Servlet（接口）：是所有Servlet类的父接口，它定义了Servlet必须实现的方法，包括init()、service()和destroy()等 javax.servlet.ServletConfig（接口）：代表着Servlet的配置信息，例如初始化参数、Servlet名称等。在Servlet的生命周期中，容器会调用其init()方法，并传递一个ServletConfig对象作为参数。 javax.servlet.GenericServlet（抽象类）：实现了Servlet、ServletConfig接口。它提供了一些通用的方法，例如getServletConfig()、getInitParameter()等。通过继承GenericServlet可以简化自定义Servlet的编程，因为只需要实现其中的service()方法即可。 javax.servlet.http.HttpServlet（类）：是GenericServlet的子类，并实现了HttpServletRequest和HttpServletResponse接口。它针对HTTP协议提供了更加方便的处理方式，例如doGet()、doPost()等方法。通过继承HttpServlet，开发人员可以方便地处理HTTP请求和响应 Servlet的常用方法 # void init(ServletConfig config)：由Servlet容器调用，用于初始化Servlet对象 void service(ServletRequest req,ServletResponse res)：由Servlet容器调用，用于处理客户端请求并响应 void destory()：由Servlet容器调用，释放Servlet对象所使用的资源 ServletConfig getServletConfig()：返回ServletConfig对象 String getServletInfo()：返回关于Servlet的信息，比如作者、版本、版权 service()、doGet()、doPost()的区别 # service()、doGet()、doPost()均是Java Servlet中的方法，但是它们在处理请求时有所不同。\nservice()方法是Servlet执行请求的主要方法，当客户端发出请求时，Servlet容器将调用service()方法来处理请求并生成响应。这个方法根据具体的HTTP请求类型（GET、POST、PUT、DELETE等）来决定调用哪个子方法进行处理。\ndoGet()方法处理HTTP GET请求，通常用于获取信息或数据，并且参数会显示在URL中，它不会修改服务器上的任何数据。\ndoPost()方法处理HTTP POST请求，通常用于向服务器提交数据，并且请求参数在请求体内。它可以对服务器上的数据进行更改。\n总结：service()方法是Servlet处理请求的主要方法，而doGet()和doPost()方法是service()方法的具体实现，用于处理GET和POST请求。同时出现的情况下，service()优先。\n使用步骤 # 1、继承HttpServlet # 三种方式 # 1、定义一个类，去实现javax.servlet.Servlet接口\n2、定义一个类，去继承javax.servlet.GenericServlet抽象类\n3、定义一个类，去继承javax.servlet.http.HttpServlet抽象类（推荐）\n//注意：如果一个Servlet类，三个方法一个都没有重写，请求时会报405 class MyServlet extends HttpServlet { doGet() {} doPost() {} service() {} } 2、配置Servlet程序访问地址 # 在web.xml文件中 # \u0026lt;servlet\u0026gt; \u0026lt;servlet-name\u0026gt;名称1\u0026lt;/servlet-name\u0026gt; \u0026lt;servlet-class\u0026gt;Servlet全类名（包+类）\u0026lt;/servlet-class\u0026gt; \u0026lt;/servlet\u0026gt; \u0026lt;servlet-mapping\u0026gt; \u0026lt;servlet-name\u0026gt;名称1\u0026lt;/servlet-name\u0026gt; \u0026lt;url-pattern\u0026gt;/虚拟路径\u0026lt;/url-pattern\u0026gt; \u0026lt;/servlet-mapping\u0026gt; 使用注解配置，不需要写xml文件 # @WebServlet：属于类级别的注解，标注在继承了 HttpServlet 的类之上。常用的写法是将 Servlet 的相对请求路径（即 value）直接写在注解内 属性 name-String：指定Servlet的名称，如果没有指定，默认是Servlet类的全类名 value-String[]：完全等价于urlPatterns属性，二者不能同时指定 urlPatterns-String[]：指定一组Servlet的匹配路径 loadOnStartup-int：指定Servlet的加载顺序 initParams-WebInitParam：指定Servlet的启动参数 asyncSupported-boolean：Servlet是否支持异步操作模式 description-String：Servlet的描述 displayName-String：Servlet的显示名 在继承了Servlet类上方，添加注解，用于配置此Servlet的虚拟地址，三种方法\n@WebServlet(value=\u0026#34;/servletDemo1\u0026#34;) public MyServle extends HttpServlet{} @WebServlet(value={\u0026#34;/servletDemo1\u0026#34;,\u0026#34;/servletDemo2\u0026#34;}) public MyServle extends HttpServlet{} @WebServlet({\u0026#34;/servletDemo1\u0026#34;,\u0026#34;/servletDemo2\u0026#34;}) public MyServle extends HttpServlet{} @WebServlet(urlPatterns=\u0026#34;/servletDemo1\u0026#34;) public MyServle extends HttpServlet{} 3、访问 # http://localhost:8080/项目虚拟目录/servlet虚拟路径\n域对象 # 在JavaWeb中，Servlet中三大域对象分别是request，session，ServletContext，其只要是用来存放共享数据的。\n之所以他们是域对象，原因是他们都内置了Map集合，都有setAttribute、getAttribute和removeAttribute方法用于操作值。\n只要是域对象，都有这3个方法，可以实现数据共享，都是以key-value方式存放数据，key必须是String类型，value是Object类型\n作用域 # 名称 对象类型 作用域 说明 application ServletContext 在整个应用程序中有效 通过request.getServletContext()方法获取，可以在整个应用范围内共享数据 session HttpSession 在当前会话中有效 通过request.getSession()获取，会话代表同一浏览器向服务器的多次请求和响应 request HttpServletRequest 在当前请求中有效 在一次请求的范围内，可以共享资源 page PageContext JSP的请求到响应中有效 作用范围是当前用户请求的JSP页面渲染时，一旦渲染结束响应即失效 Servlet生命周期 # 生命周期方法 # servlet的生命周期是由三个方法体现的，称为生命周期方法：\ninit()：初始化方法，只有在首次访问时调用 service()：执行方法，每次访问，都会调用 destroy()：销毁方法，只有Servlet销毁时调用（WEB工程停止时） 生命周期 # 1、当我们第一次访问servlet的时候，会创建servlet对象（调用构造器），调用servlet的init()，然后调用service()\n2、当我们再一次访问servlet的时候，就不会调用init()，只会调用service()\n3、当我们正常关闭服务器的时候，会调用servlet的destroy()\nHttpServletRequest # 获取请求行的方法 # String getMethod()：获取请求方式 String getContextPath()：获取项目的虚拟目录 String getServletPath()：获取servlet的虚拟路径 String getQueryString()：获取get请求的请求参数 String getRequestURI()：获取请求的URI（资源路径） StringBuffer getRequestURL()：获取请求的URL（绝对路径） String getProtocol()：获取协议/版本号 String getRemoteAddr()：获取客户端的IP地址 获取路径参数 # path?k1=v1\u0026amp;k2=v2获取这种形式的参数\nString getParameter(String name)：根据请求参数的名称获取值 Enumeration\u0026lt;String\u0026gt; getParameterNames()：获取所有的请求参数的名称 String[] getParameterValues(String name)：根据请求参数的名称获取值（多个） Map\u0026lt;String,String[]\u0026gt; getParameterMap()：将所有请求参数的名称和值都封装到了map对象 获取请求头的方法 # String getHeader(String name)：根据请求头的名称获取请求头的值 Enumeration\u0026lt;String\u0026gt; getHeaderNames()：获取所有的请求头的名称 Enumeration\u0026lt;String\u0026gt; getHeaders(String name)：根据请求头的名称获取多个请求头的值 int getIntHeader(String name)：根据请求头的名称获取请求头的值(请求头值为int类型的时候) 获取请求体的方法 # BufferedReader getReader()：获取Reader ServletInputStream getInputStream()：获取InputStream HttpServletResponse # 设置响应状态码 # void setStatus(int sc) 设置响应头 # void setHeader(String name, String value) 设置响应体 # 打印流：PrintWriter getWriter()\n字节流：ServletOutputStream getOutputStream()\n解决中文乱码 # //将tomcat写出的编码由ISO-8859-1变成UTF-8 response.setCharacterEncoding(\u0026#34;utf-8\u0026#34;); //上述方法，只会将服务器写出的编码为utf-8，所以，需要服务器告诉浏览器使用utf-8来打开 //推荐！！！ response.setHeader(\u0026#34;Content-Type\u0026#34;, \u0026#34;text/html;charset=utf-8\u0026#34;); //或 response.setContentType(\u0026#34;text/html;charset=utf-8\u0026#34;); 响应Html文件 # 可以通过Response实例的getOutPutStream()向浏览器进行响应，例如响应html文件\n@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取请求体中的参数，和数据库进比较 String name = req.getParameter(\u0026#34;name\u0026#34;); String psw = req.getParameter(\u0026#34;psw\u0026#34;); SqlSession sqlSession = MyBatisUtil.getSqlSession(); UsersDao usersDao = sqlSession.getMapper(UsersDao.class); Users byNameAndPsw = usersDao.findByNameAndPsw(name, psw); //获取当前项目路径 String contextPath = req.getServletContext().getRealPath(\u0026#34;/\u0026#34;); //登录成功、失败不同的html页面 String path = null; if (byNameAndPsw == null){ path = \u0026#34;fail.html\u0026#34;; }else { path = \u0026#34;success.html\u0026#34;; } //获得对应页面的输入流 InputStream inputStream = new FileInputStream(contextPath + path); //获得输出流 ServletOutputStream outputStream = resp.getOutputStream(); //将输入流的内容，写出到输出流 byte[] bytes = new byte[1024]; int len = 0; while ( (len = inputStream.read(bytes)) \u0026gt;0 ){ outputStream.write(bytes,0,len); } //关闭资源 outputStream.flush(); outputStream.close(); inputStream.close(); } 同一Servlet处理不同的功能 # 1、通过注解或xml文件设置该url的多个URL，例如\n@WebServlet(value={\u0026#34;goodsType/add\u0026#34;,\u0026#34;goodsType/change\u0026#34;}) 2、通过request对象的getRequestURI()方法，获取该请求的URI(如shop/goodsType/add)，然后通过字符串的分割，获取具体的请求\n@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //解析请求的方法 String uri = req.getRequestURI(); String resUri = uri.substring(uri.lastIndexOf(\u0026#34;/\u0026#34;) + 1); //根据请求类型，使用反射获取并执行对应的方法 Method method = null; try { method = this.getClass().getMethod(resUri, HttpServletRequest.class, HttpServletResponse.class); method.setAccessible(true); method.invoke(this,req,resp); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } } public void add(HttpServletRequest req, HttpServletResponse resp){ } public void change(HttpServletRequest req, HttpServletResponse resp){ } 转发和重定向 # 无论转发还是重定向，始终都是请求一次，响应一次\n转发 # 调用服务端的资源（静态、动态）\nrequest.getRequestDispatcher(\u0026#34;/path\u0026#34;).forward(request,response); 属于一次请求\n响应头没有Location\n可以在request范围内共享数据\n只可以使用本应用的资源\n地址栏不会发生变化，就是请求时的地址\n重定向 # 重新定位一下，通过响应头中的Location，告诉浏览器，需要重新请求一次\nresponse.sendRedirect(); 两次请求\n第一次的请求中，响应头有Location\n无法在request范围内共享数据，可以通过session或者application（ServletContext）达到数据的共享\n可以调用应用之外的资源\n地址栏会发生变化，就是最后请求地址\n","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/fb007bea/840b5f5a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eServlet \n    \u003cdiv id=\"servlet\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#servlet\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eServlet\u003c/strong\u003e(\u003cstrong\u003eServ\u003c/strong\u003eer App\u003cstrong\u003elet\u003c/strong\u003e)是Java Servlet的简称，称为\u003cstrong\u003e小服务程序或服务连接器\u003c/strong\u003e，用\u003cstrong\u003eJava编写\u003c/strong\u003e的服务器端程序，具有\u003cstrong\u003e独立于平台和协议的特性\u003c/strong\u003e，主要功能在于\u003cstrong\u003e交互式地浏览和生成数据，生成动态Web内容\u003c/strong\u003e，它是由Sun公司（现在是Oracle公司）开发的，作为Java Servlet API的一部分，包含在Java EE（Enterprise Edition）规范中\u003c/p\u003e","title":"3、Servlet","type":"posts"},{"content":" 生命周期 # 注意：这个Session不是Tomcat的那个，而是Shrio提供的一个Session\n1、在首次调用subject.login()方法时，创建Session对象\n2、默认30分钟过期\n3、在调用subject.logout()方法时，销毁Session对象\n相关方法 # subject.login()：主体登录（认证）方法，同时创建Session对象\nsubject.getSession()：获取Shiro封装的Session对象\nsubject.logout()：主体登出方法，同时会销毁Session对象\nSession监听 # 1、创建自定义监听器，实现SessionListener接口 # public class ShiroSessionListener implements SessionListener{ //使用AtomicInteger，线程安全，统计在线人数 private final AtomicInteger sessionCount = new AtomicInteger(0); public AtomicInteger getOnLineSessionCount(){ return this.sessionCount; } //session过期，执行该方法 @Override public void onExpiration(Session arg0) { System.out.println(\u0026#34;会话过期,在线人数减一\u0026#34;); sessionCount.decrementAndGet(); } //客户端首次访问服务器，创建session对象，执行该方法 @Override public void onStart(Session arg0) { System.out.println(\u0026#34;首次访问服务器时,会话人数加一\u0026#34;); sessionCount.incrementAndGet(); } //session调用stop方法时，执行该方法 @Override public void onStop(Session arg0) { System.out.println(\u0026#34;退出时，会话人数减一\u0026#34;); sessionCount.decrementAndGet(); } } 2、在ShiroConfig中，对SessionListener、SessionManager进行注入 # //自定义的SessionListener组件 @Bean public ShiroSessionListener getShiroSessionListener(){ return new ShiroSessionListener(); } //SessionManager组件 @Bean public SessionManager getSessionManager(){ //获取一个SessionManager实现类 DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); //创建集合，将自定义的SessionListener存入集合 List\u0026lt;SessionListener\u0026gt; list = new ArrayList\u0026lt;\u0026gt;(); list.add(getShiroSessionListener()); //设置自定义SessionListener到SessionManager中 sessionManager.setSessionListeners(list); //默认session存活时间30分钟 sessionManager.setGlobalSessionTimeout(1000*60*30); return sessionManager; } //配置安全管理器 @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //安全管理器管理创建的Realm域对象 securityManager.setRealm(getLoginAndAuthRealm()); //安全管理器管理创建的SessionManager对象 securityManager.setSessionManager(getSessionManager()); return securityManager; } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/c3527798/3f404e8a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e生命周期 \n    \u003cdiv id=\"生命周期\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%94%9f%e5%91%bd%e5%91%a8%e6%9c%9f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e注意\u003c/strong\u003e：这个Session不是Tomcat的那个，而是Shrio提供的一个Session\u003c/p\u003e","title":"3、Session","type":"posts"},{"content":" 使用前提 # 如果需要使用MVC相关注解，需要在Spring核心配置文件applicationContext.xml中开启注解驱动\n\u0026lt;!-- 开启MVC注解支持 --\u0026gt; \u0026lt;mvc:annotation-driven\u0026gt;\u0026lt;/mvc:annotation-driven\u0026gt; 常用注解 # 注意：@Controller、@Service、@Repository等注解并非SpringMVC声明，而是由Spring声明。\n@RequestMapping # @RequestMapping注解的作用就是将请求和处理请求的控制器方法关联起来，建立映射关系。\n可以用来标识类和方法，当标识类的时候，则为设置该类中所有控制器方法的基础请求路径与其他请求限制；当标识方法的时候，则为设置该控制器方法的请求路径与其他请求限制。\n@Controller @RequestMapping(\u0026#34;/info\u0026#34;) public class InfoController{ @RequestMapping(\u0026#34;/test\u0026#34;) public String test(){ return \u0026#34;test\u0026#34;; } } 如上代码所示，test()方法映射的路径为/info/test\nvalue属性 # value属性必须设置，表示通过请求路径映射，是一个字符串类型的数组，表示该请求映射能够匹配多个请求地址所对应的请求，等同于path属性\n@RequestMapping({\u0026#34;/info\u0026#34;,\u0026#34;/infomation\u0026#34;}) public class InfoController{} method属性 # 通过请求的请求方式映射，例如常见的GET、POST，method属性是一个RequestMethod类型的数组，表示该请求映射能够匹配多种请求方式的请求\n若当前请求的请求地址满足请求映射的value属性，但是请求方式不满足method属性，则浏览器报错405：Request method 'POST' not supported\n@RequestMapping( value = \u0026#34;/testRequestMapping\u0026#34;, method = {RequestMethod.GET, RequestMethod.POST} ) public String testRequestMapping(){ return \u0026#34;success\u0026#34;; } consumes和produces属性 # 这两个属性都是用来指定Content-Type的\nproduces指定返回值类型，不仅可设置返回值类型，还可设定返回值的字符编码\nconsumes指定可以处理请求的提交内容类型\n@RequestMapping( value = \u0026#34;/pets\u0026#34;, method = RequestMethod.GET, produces=\u0026#34;application/json;charset=utf-8\u0026#34;, consumes=\u0026#34;application/json\u0026#34; ) params属性 # 通过请求的请求参数匹配请求映射，params属性是一个字符串类型的数组，可以通过四种表达式设置请求参数和请求映射的匹配关系\n\u0026quot;param\u0026quot;：要求请求映射所匹配的请求必须携带param请求参数 \u0026quot;!param\u0026quot;：要求请求映射所匹配的请求必须不能携带param请求参数 \u0026quot;param=value\u0026quot;：要求请求映射所匹配的请求必须携带param请求参数且param=value \u0026quot;param!=value\u0026quot;：要求请求映射所匹配的请求必须携带param请求参数但是param!=value 如果请求不满足params属性，此时页面回报错400：Parameter conditions \u0026quot;username, password!=123456\u0026quot; not met for actual request parameters: username={admin}, password={123456}\nheaders属性 # 通过请求的请求头信息匹配请求映射，headers属性是一个字符串类型的数组，可以通过四种表达式设置请求头信息和请求映射的匹配关系\n\u0026quot;header\u0026quot;：要求请求映射所匹配的请求必须携带header请求头信息 \u0026quot;!header\u0026quot;：要求请求映射所匹配的请求必须不能携带header请求头信息 \u0026quot;header=value\u0026quot;：要求请求映射所匹配的请求必须携带header请求头信息且header=value \u0026quot;header!=value\u0026quot;：要求请求映射所匹配的请求必须携带header请求头信息且header!=value 若当前不满足headers属性，此时页面显示404错误，即资源未找到。\n派生注解 # SpringMVC基于@RequestMapping做为元注解派生除了特定请求方式的注解，例如@GetMapping、@PostMapping、@PutMapping等，例如@GetMapping\n@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @RequestMapping( method = {RequestMethod.GET} ) public @interface GetMapping {} 这些注解使用方式和@RequestMapping一样，只是不可以声明在类上\n@RequestParam # 是将请求参数（?传参）和控制器方法的形参创建映射关系，作用于控制层方法形参\nvalue属性 # 指定为形参赋值的请求参数的参数名\nrequired属性 # 设置是否必须传输此请求参数，默认值为true\n若设置为true时，则当前请求必须传输value所指定的请求参数，若没有传输该请求参数，且没有设置defaultValue属性，则页面报错400：Required String parameter 'xxx' is not present；若设置为false，则当前请求不是必须传输value所指定的请求参数，若没有传输，则注解所标识的形参的值为null\ndefaultValue属性 # 不管required属性值为true或false，当value所指定的请求参数没有传输或传输的值为\u0026quot;\u0026quot;时，则使用默认值为形参赋值\n@RequestHeader # 是将请求头信息和控制器方法的形参创建映射关系，共有三个属性：value、required、defaultValue，用法同@RequestParam\n@CookieValue # 是将Cookie数据和控制器方法的形参创建映射关系，共有三个属性：value、required、defaultValue，用法同@RequestParam\n@RequestBody # 可以获取请求体，需要在控制器方法设置一个形参，使用@RequestBody进行标识，当前请求的请求体就会为当前注解所标识的形参赋值，但是使用时需要额外引入HttpMessageConverter，例如解析JSON则需要引入jackson依赖\n@ResponseBody # 用于标识一个控制器方法，可以将该方法的返回值直接作为响应报文的响应体响应到浏览器\n如果是对象类型，则需要配置类型转换器HttpMessageConverter，否则可能会报500：No converter found for return value of type，常见的就是使用jackson\n@RestController # @RestController注解是springMVC提供的一个复合注解，标识在控制器的类上，就相当于为类添加了@Controller注解，并且为其中的每个方法添加了@ResponseBody注解\n完全注解开发 # 完全注解开发也就是替换掉Spring核心配置文件applicationContext.xml以及web.xml文件，采用配置类的方式对DispatcherServlet、Spring、SpringMVC进行配置\napplicationContext.xml # \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; standalone=\u0026#34;no\u0026#34;?\u0026gt; \u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:aop=\u0026#34;http://www.springframework.org/schema/aop\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xmlns:context=\u0026#34;http://www.springframework.org/schema/context\u0026#34; xmlns:mvc=\u0026#34;http://www.springframework.org/schema/mvc\u0026#34; xsi:schemaLocation=\u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd\u0026#34;\u0026gt; \u0026lt;!-- 配置自动扫描注解路径 --\u0026gt; \u0026lt;context:component-scan base-package=\u0026#34;top.ygang.*\u0026#34;\u0026gt;\u0026lt;/context:component-scan\u0026gt; \u0026lt;!-- 开启MVC注解支持 --\u0026gt; \u0026lt;mvc:annotation-driven\u0026gt;\u0026lt;/mvc:annotation-driven\u0026gt; \u0026lt;!-- 将静态资源交给SpringMVC处理 location:指的是静态资源存放路径，多个路径可以使用逗号分隔 mapping:指的是以static开头的url资源路径 --\u0026gt; \u0026lt;mvc:resources mapping=\u0026#34;/static/**\u0026#34; location=\u0026#34;/static/\u0026#34;\u0026gt;\u0026lt;/mvc:resources\u0026gt; \u0026lt;!-- 视图解析器、设置controller转发、重定向路径默认前缀后缀，可选 --\u0026gt; \u0026lt;bean class=\u0026#34;org.springframework.web.servlet.view.InternalResourceViewResolver\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;prefix\u0026#34; value=\u0026#34;/\u0026#34;\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;property name=\u0026#34;suffix\u0026#34; value=\u0026#34;.jsp\u0026#34;\u0026gt;\u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;/beans\u0026gt; context:component-scan可以使用注解@ComponentScans注解替换 mvc:annotation-driven可以使用SpringMVC提供的注解@EnableWebMvc替换 视图解析器InternalResourceViewResolver，可以在配置类自己往Spring容器中注入一个即可 MVC的其他配置，例如mvc:resources，则需要依靠实现org.springframework.web.servlet.config.annotation.WebMvcConfigurer接口的不同方法进行配置 @Configuration @ComponentScan(\u0026#34;top.ygang.springmvc_demo\u0026#34;) @EnableWebMvc public class SpringMvcConfig implements WebMvcConfigurer { // 配置视图解析器 @Bean public InternalResourceViewResolver internalResourceViewResolver(){ InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix(\u0026#34;/\u0026#34;); resolver.setSuffix(\u0026#34;.jsp\u0026#34;); return resolver; } // 配置静态资源处理方式，等价于mvc:resources @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(\u0026#34;/static/**\u0026#34;).addResourceLocations(\u0026#34;classpath:/static/\u0026#34;); } } @EnableWebMvc # @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented // 引入DelegatingWebMvcConfiguration类 @Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { } 是一个Spring MVC框架提供的注解，它的作用是启用Spring MVC框架的默认配置，例如消息转换器，格式化器和验证器等。它还启用了Spring MVC的注解驱动，这样就可以使用注解来处理HTTP请求和响应。\nWebMvcConfigurer # 实现WebMvcConfigurer接口其实是Spring MVC内部的一种配置方式，采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制，可以自定义一些Handler，Interceptor，ViewResolver，MessageConverter。\n使用时需要创建一个配置类（使用@Configuration注解）并实现WebMvcConfigurer接口\n在Spring Boot 1.5版本都是靠重写WebMvcConfigurerAdapter的方法来添加自定义拦截器，消息转换器等。SpringBoot 2.0 后，该类被标记为@Deprecated（弃用）。官方推荐直接实现WebMvcConfigurer或者直接继承WebMvcConfigurationSupport，\n@Configuration public class MyWebMvcConfig implements WebMvcConfigurer{ //根据需求重写方法 } addInterceptors # 配置拦截器\naddInterceptor：需要一个实现HandlerInterceptor接口的拦截器实例 addPathPatterns：用于设置拦截器的过滤路径规则； addPathPatterns(\u0026quot;/**\u0026quot;)对所有请求都拦截 excludePathPatterns：用于设置不需要拦截的过滤规则 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new TestInterceptor()) .addPathPatterns(\u0026#34;/**\u0026#34;) .excludePathPatterns(\u0026#34;/js/**\u0026#34;,\u0026#34;/css/**\u0026#34;,\u0026#34;/images/**\u0026#34;); } addViewControllers # 控制页面访问规则，例如下面，访问/infomation即可访问/info.jsp\n@Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(\u0026#34;/infomation\u0026#34;).setViewName(\u0026#34;info\u0026#34;); } addResourceHandlers # 配置静态资源处理方式，等价于mvc:resources\naddResoureHandler：指的是对外暴露的访问路径 addResourceLocations：指的是内部文件放置的目录 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(\u0026#34;/static/**\u0026#34;).addResourceLocations(\u0026#34;classpath:/static/\u0026#34;); } configureDefaultServletHandling # 注册一个默认的Handler：DefaultServletHttpRequestHandler，这个Handler也是用来处理静态文件的，它会尝试映射/。\n等价于：mvc:default-servlet-handler\n@Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } configureViewResolvers # 配置视图解析器，默认会自动找Spring容器中的InternalResourceViewResolver实例\n@Bean public InternalResourceViewResolver internalResourceViewResolver(){ InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix(\u0026#34;/\u0026#34;); resolver.setSuffix(\u0026#34;.jsp\u0026#34;); return resolver; } @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.viewResolver(internalResourceViewResolver()); } addCorsMappings # 配置跨越相关\n@Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(\u0026#34;/**\u0026#34;) .allowedOrigins(\u0026#34;*\u0026#34;) .allowedMethods(\u0026#34;POST\u0026#34;, \u0026#34;GET\u0026#34;, \u0026#34;PUT\u0026#34;, \u0026#34;OPTIONS\u0026#34;, \u0026#34;DELETE\u0026#34;) .allowCredentials(true) .allowedHeaders(\u0026#34;*\u0026#34;) .maxAge(3600); } configureMessageConverters # 配置自定义消息转换器，例如使用fastjson作为json消息转换器\n@Override public void configureMessageConverters(List\u0026lt;HttpMessageConverter\u0026lt;?\u0026gt;\u0026gt; converters) { //创建fastJson消息转换器 FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); //创建配置类 FastJsonConfig fastJsonConfig = new FastJsonConfig(); //修改配置返回内容的过滤 fastJsonConfig.setSerializerFeatures( SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty ); fastConverter.setFastJsonConfig(fastJsonConfig); //将fastjson添加到视图消息转换器列表内 converters.add(fastConverter); } web.xml # 在Servlet3.0环境中，容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类，如果找到的话就用它来配置Servlet容器。 Spring提供了这个接口的实现，名为SpringServletContainerInitializer，这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现，名为AbstractAnnotationConfigDispatcherServletInitializer，当我们的类扩展了AbstractAnnotationConfigDispatcherServletInitializer并将其部署到Servlet3.0容器的时候，容器会自动发现它，并用它来配置Servlet上下文。\npublic class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer { /** * 指定spring的配置类 * @return */ @Override protected Class\u0026lt;?\u0026gt;[] getRootConfigClasses() { return new Class[]{SpringMvcConfig.class}; } /** * 指定SpringMVC的配置类 * @return */ @Override protected Class\u0026lt;?\u0026gt;[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; } /** * 指定DispatcherServlet的映射规则，即url-pattern * @return */ @Override protected String[] getServletMappings() { return new String[]{\u0026#34;/\u0026#34;}; } /** * 乱码过滤器 * @return */ @Override protected Filter[] getServletFilters() { CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter(); encodingFilter.setEncoding(\u0026#34;UTF-8\u0026#34;); encodingFilter.setForceRequestEncoding(true); HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter(); return new Filter[]{encodingFilter, hiddenHttpMethodFilter}; } } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/617a1910/ef552846/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e使用前提 \n    \u003cdiv id=\"使用前提\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bd%bf%e7%94%a8%e5%89%8d%e6%8f%90\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e如果需要使用MVC相关注解，需要在Spring核心配置文件\u003ccode\u003eapplicationContext.xml\u003c/code\u003e中开启注解驱动\u003c/p\u003e","title":"3、SpringMVC的注解","type":"posts"},{"content":" strings包 # Golang提供了许多内置的字符串函数，这些函数可在处理字符串数据时帮助执行一些操作。Golang字符串函数是核心部分。使用此功能无需安装，仅需要导入strings包。\nimport strings 重要的Golang字符串函数列表如下：\n查找 # Count() # 查找字符串substr在字符串s中出现的次数\nfunc Count(s, substr string) int Contains() # 判断字符串s中是否包含字符串substr\nfunc Contains(s, substr string) bool ContainsRune() # 判断字符串s中是否包含utf8字符r\nfunc ContainsRune(s string, r rune) bool Index() # 从前往后查找字符串s中，字符串substr第一次出现的位置，没有则返回-1\nfunc Index(s, substr string) int 注意：Index()返回的位置是开始的字节索引，而不是字符，所以对于Unicode字符串，例如中文，则不是字符所在位置，对于中文字符，可以使用如下的方式计算，该方式对于其他...Index()方法同样适用\ns1 := \u0026#34;你好中国\u0026#34; i := strings.Index(s1, \u0026#34;中国\u0026#34;) fmt.Println(i) // 6 fmt.Println(utf8.RuneCountInString(s1[:i])) // 2 LastIndex() # 从后往前查找字符串s中，字符串substr第一次出现的位置，没有则返回-1\nfunc LastIndex(s, substr string) int IndexAny() # 从前往后查找字符串s中，字符序列chars中任意字符第一次出现的位置，没有则返回-1\nfunc IndexAny(s, chars string) int LastIndexAny() # 从后往前查找字符串s中，字符序列chars中任意字符第一次出现的位置，没有则返回-1\nfunc LastIndexAny(s, chars string) int IndexByte() # 从前往后查找字符串s中，ASCII码字符c第一次出现的位置，没有则返回-1\nfunc IndexByte(s string, c byte) int LastIndexByte() # 从后往前查找字符串s中，ASCII码字符c第一次出现的位置，没有则返回-1\nfunc LastIndexByte(s string, c byte) int IndexRune() # 从前往后查找字符串s中，UTF8码字符r第一次出现的位置，没有则返回-1\nfunc IndexRune(s string, r rune) int LastIndexRune() # 从后往前查找字符串s中，UTF8码字符r第一次出现的位置，没有则返回-1\nfunc LastIndexRune(s string, r rune) int IndexFunc() # 从前往后查找字符串s中，使f函数返回true的字符位置，没有则返回-1\nfunc IndexFunc(s string, f func(rune) bool) int LastIndexFunc() # 从后往前查找字符串s中，使f函数返回true的字符位置，没有则返回-1\nfunc LastIndexFunc(s string, f func(rune) bool) int HasPrefix() # 检查字符串s是否以prefix开头，是则返回true\nfunc HasPrefix(s, prefix string) bool HasSuffix() # 检查字符串s是否以prefix结尾，是则返回true\nfunc HasSuffix(s, suffix string) bool 替换 # Title() # 将字符串每个单词的首字母替换为大写，并返回\nfunc Title(s string) string ToTitle() # 将字符串每个单词的所有字母替换为大写，并返回\nfunc ToTitle(s string) string ToLower() # 将字符串中全部字母转换为小写，并返回\nfunc ToLower(s string) string ToUpper() # 将字符串中全部字母转换为大写，并返回\nfunc ToUpper(s string) string Replace() # 从左往右将字符串s中的字符串old替换为new，替换n次结束并返回，如果n小于0，则全部替换\nfunc Replace(s, old, new string, n int) string ReplaceAll() # 将字符串s中的字符串old全部替换为new\nfunc ReplaceAll(s, old, new string) string 去除 # Trim() # 从字符串s的两侧删除字符串cutset，并返回\nfunc Trim(s string, cutset string) string TrimSpace() # 从字符串s的两侧删除空白和其他预定义字符，包括\u0026quot;\\t\u0026quot;,\u0026quot;\\n\u0026quot;,\u0026quot;\\x0B\u0026quot;,\u0026quot;\\r\u0026quot;,\u0026quot; \u0026quot;，并返回\nfunc TrimSpace(s string) string TrimPrefix() # 从字符串s的开头删除前缀字符串cutset，并返回，只删除一次\nfunc TrimPrefix(s string, cutset string) string TrimSuffix() # 从字符串s的开头删除后缀字符串cutset，并返回，只删除一次\nfunc TrimSuffix(s string, cutset string) string TrimLeft() # 从字符串s的左边开始删除字符串cutset，并返回，如果左边有连续的cutset，则会全部删除\nfunc TrimLeft(s string, cutset string) string TrimRight() # 从字符串s的右边开始删除字符串cutset，并返回，如果右边有连续的cutset，则会全部删除\nfunc TrimRight(s string, cutset string) string 比较 # 除了==进行比较以外，strings还提供了两种比较方法\nCompare() # 比较字符串a和b的大小，如果相等返回0，如果a大于b返回大于0的数\nfunc Compare(a, b string) int EqualFold() # 判断两个字符串是否相当，不区分大小写\nfunc EqualFold(s, t string) bool 拆分 # Split() # 将字符串s以字符串sep分割为子串，并返回切片，子串中不包含sep\nfunc Split(S string, sep string) []string SplitN() # 从左往右将字符串s以字符串sep分割为子串n次，并返回切片，最后一个子字符串将是未拆分的部分\nfunc SplitN(s, sep string, n int) []string SplitAfter() # 将字符串s分割为若干个以sep结尾的字串，并返回切片，子串中包含sep\nfunc SplitAfter(S String, sep string) []string 拼接 # Repeat() # 将字符串s重复count次返回，count必须大于等于0\nfunc Repeat(s string, count int) string Join() # 将切片stringSlice中的每个元素字符串以字符串sep拼接为新字符串并返回\nfunc Join(stringSlice []string, sep string) string ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/12d682f6/4ba42c4d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003estrings包 \n    \u003cdiv id=\"strings包\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#strings%e5%8c%85\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eGolang提供了许多内置的字符串函数，这些函数可在处理字符串数据时帮助执行一些操作。Golang字符串函数是核心部分。使用此功能无需安装，仅需要导入\u003ccode\u003estrings\u003c/code\u003e包。\u003c/p\u003e","title":"3、strings","type":"posts"},{"content":" 数据类型 # 在 C 语言中，数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。变量的类型决定了变量存储占用的空间，以及如何解释存储的位模式。\n数据类型分类 # 基本类型 它们是算术类型，包括两种类型：整数类型和浮点类型。 枚举类型 它们也是算术类型，被用来定义在程序中只能赋予其一定的离散整数值的变量。 void类型 类型说明符 void 表明没有可用的值。 派生类型 它们包括：指针类型、数组类型、结构类型、共用体类型和函数类型。 基本数据类型 # C语言中没有boolean类型，0表示假，非0整数就是真\n整数类型 # 类型 存储大小 值范围 char 1 byte -128 到 127 或 0 到 255 unsigned char 1 byte 0 到 255 signed char 1 byte -128 到 127 int 2 或 4 bytes -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647 unsigned int 2 或 4 bytes 0 到 65,535 或 0 到 4,294,967,295 short 2 bytes -32,768 到 32,767 unsigned short 2 bytes 0 到 65,535 long 4 bytes -2,147,483,648 到 2,147,483,647 unsigned long 4 bytes 0 到 4,294,967,295 浮点类型 # 类型 存储大小 值范围 精度 float 4 byte 1.2E-38 到 3.4E+38 6 位小数 double 8 byte 2.3E-308 到 1.7E+308 15 位小数 long double 10 byte 3.4E-4932 到 1.1E+4932 19 位小数 void类型 # void 类型指定没有可用的值。它通常用于以下三种情况下\n函数返回为空 C 中有各种函数都不返回值，或者您可以说它们返回空。不返回值的函数的返回类型为空。例如 void exit (int status); 函数参数为空 C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如 int rand(void); 指针指向 void 类型为 void * 的指针代表对象的地址，而不是类型。例如，内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针，可以转换为任何数据类型。 变量 # 变量其实只不过是程序可操作的存储区的名称。C 中每个变量都有特定的类型，类型决定了变量存储的大小和布局，该范围内的值都可以存储在内存中，运算符可应用于变量上。\n变量的名称可以由字母、数字和下划线字符组成。它必须以字母或下划线开头。大写字母和小写字母是不同的，因为 C 是大小写敏感的。\n操作系统和C库经常使用以一个或两个下划线字符开始的标识符（如_kcab ），因此最好避免在自己的程序中使用这种名称。\n定义 # 语法：type variable_list; type 必须是一个有效的 C 数据类型，可以是 char、w_char、int、float、double、bool 或任何用户自定义的对象。 variable_list 可以由一个或多个标识符名称组成，多个标识符之间用逗号分隔。 int a,b,c; float f; 初始化 # 变量可以在声明的时候被初始化（指定一个初始值）。初始化器由一个等号，后跟一个常量表达式组成。\n语法：type variable_name = value;\nint a = 10,b = 20; int be = 33; 不带初始化的定义：带有静态存储持续时间的变量会被隐式初始化为 NULL（所有字节的值都是 0），其他所有变量的初始值是未定义的。\n声明 # 变量声明向编译器保证变量以给定的类型和名称存在，这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。变量声明只在编译时有它的意义，在程序连接时编译器需要实际的变量声明。\n当您使用多个文件且只在其中一个文件中定义变量时（定义变量的文件在程序连接时是可用的），变量声明就显得非常有用。您可以使用 extern 关键字在任何地方声明一个变量。虽然您可以在程序中多次声明一个变量，但变量只能在某个文件、函数或代码块中被定义一次。\n#include \u0026lt;stdio.h\u0026gt; // 变量声明 extern int a, b; extern int c; extern float f; int main () { /* 变量定义 */ int a, b; int c; float f; /* 实际初始化 */ a = 10; b = 20; c = a + b; printf(\u0026#34;value of c : %d \\n\u0026#34;, c); f = 70.0/3.0; printf(\u0026#34;value of f : %f \\n\u0026#34;, f); return 0; } 左值和右值 # C 中有两种类型的表达式： 左值（lvalue）：指向内存位置的表达式被称为左值（lvalue）表达式。左值可以出现在赋值号的左边或右边。 右值（rvalue）：术语右值（rvalue）指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式，也就是说，右值可以出现在赋值号的右边，但不能出现在赋值号的左边。 常量 # 常量是固定值，在程序执行期间不会改变。这些固定的值，又叫做字面量。 常量可以是任何的基本数据类型，比如整数常量、浮点常量、字符常量，或字符串字面值，也有枚举常量。 常量就像是常规的变量，只不过常量的值在定义后不能进行修改。 整数常量 # 整数常量可以是十进制、八进制或十六进制的常量，前缀指定基数： 0x 或 0X 表示十六进制 0 表示八进制 不带前缀则默认表示十进制 浮点常量 # 浮点常量由整数部分、小数点、小数部分和指数部分组成。您可以使用小数形式或者指数形式来表示浮点常量。\n当使用小数形式表示时，必须包含小数点、指数，或同时包含两者。当使用指数形式表示时，必须包含整数部分、小数部分，或同时包含两者。带符号的指数是用 e 或 E 引入的。\n字符常量 # 字符常量是括在单引号中，例如，\u0026lsquo;x\u0026rsquo; 可以存储在 char 类型的简单变量中。\n字符常量可以是一个普通的字符（例如 \u0026lsquo;x\u0026rsquo;）、一个转义序列（例如 \u0026lsquo;\\t\u0026rsquo;），或一个通用的字符（例如 \u0026lsquo;\\u02C0\u0026rsquo;）。\n在 C 中，有一些特定的字符，当它们前面有反斜杠时，它们就具有特殊的含义，被用来表示如换行符（\\n）或制表符（\\t）等。\n转义序列 含义 \\ \\ 字符 ' \u0026rsquo; 字符 \u0026quot; \u0026quot; 字符 ? ? 字符 \\a 警报铃声 \\b 退格键 \\f 换页符 \\n 换行符 \\r 回车 \\t 水平制表符 \\v 垂直制表符 \\ooo 一到三位的八进制数 \\xhh . . . 一个或多个数字的十六进制数 字符串常量 # 字符串字面值或常量是括在双引号 \u0026quot;\u0026quot; 中的。一个字符串包含类似于字符常量的字符：普通的字符、转义序列和通用的字符。\n您可以使用空格做分隔符，把一个很长的字符串常量进行分行。\n#include \u0026lt;stdio.h\u0026gt; //三种输出结果一样 int main() { char a[] = \u0026#34;Hello World\\n\u0026#34;; char b[] = \u0026#34;Hello \\ World\\n\u0026#34;; char c[] = \u0026#34;Hello\u0026#34; \u0026#34; \u0026#34; \u0026#34;World\\n\u0026#34;; printf(a); printf(b); printf(c); return } 定义常量 # 在 C 中，有两种简单的定义常量的方式：\n使用 #define 预处理器。 使用 const 关键字。 #include \u0026lt;stdio.h\u0026gt; #define ABC \u0026#34;abc\u0026#34; int main() { printf(ABC); return 0; } #include \u0026lt;stdio.h\u0026gt; int main() { const char abc[] = \u0026#34;abc\u0026#34;; printf(abc); return 0; } 存储类 # 存储类定义 C 程序中变量/函数的范围（可见性）和生命周期。这些说明符放置在它们所修饰的类型之前。\nauto register static extern auto 存储类 # auto 存储类是所有局部变量默认的存储类。\n{ int mount; auto int month; } 上面的实例定义了两个带有相同存储类的变量，auto 只能用在函数内，即 auto 只能修饰局部变量。\nregister 存储类 # register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小（通常是一个字节），且不能对它应用一元的 \u0026lsquo;\u0026amp;\u0026rsquo; 运算符（因为它没有内存位置）。\n{ register int miles; } 寄存器只用于需要快速访问的变量，比如计数器。还应注意的是，定义 \u0026lsquo;register\u0026rsquo; 并不意味着变量将被存储在寄存器中，它意味着变量可能存储在寄存器中，这取决于硬件和实现的限制。\nstatic 存储类 # static 存储类指示编译器在程序的生命周期内保持局部变量的存在，而不需要在每次它进入和离开作用域时进行创建和销毁。因此，使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。\nstatic 修饰符也可以应用于全局变量。当 static 修饰全局变量时，会使变量的作用域限制在声明它的文件内。\n在 C 编程中，当 static 用在类数据成员上时，会导致仅有一个该成员的副本被类的所有对象共享。\nextern 存储类 # extern 存储类用于提供一个全局变量的引用，全局变量对所有的程序文件都是可见的。当您使用 \u0026rsquo;extern\u0026rsquo; 时，对于无法初始化的变量，会把变量名指向一个之前定义过的存储位置。\n当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时，可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解，extern 是用来在另一个文件中声明一个全局变量或函数。\ntest.c\n#include \u0026lt;stdio.h\u0026gt; int count = 10; main.c\n#include \u0026lt;stdio.h\u0026gt; extern int count; int main() { printf(\u0026#34;%d\u0026#34;,count); return 0; } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/f571508d/fa8095d0/e741de0a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e数据类型 \n    \u003cdiv id=\"数据类型\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%95%b0%e6%8d%ae%e7%b1%bb%e5%9e%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在 C 语言中，数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。变量的类型决定了变量存储占用的空间，以及如何解释存储的位模式。\u003c/p\u003e","title":"3、数据类型和变量","type":"posts"},{"content":" 组件 # 组件就是实现应用局部功能的代码和资源的集合\n声明并注册Vue组件的三种方式 # 组件必须在vue实例管理的元素范围内使用，是用Vue.extend可以构造出来一个VueComponent实例\n组件中，Data必须是一个函数data()，返回一个对象，对象内部保存数据，避免多个组件实例之间的data互相干扰\n1、先用Vue.extend声明组件构造，再将模板注册为全局组件 # \u0026lt;div id=\u0026#34;d1\u0026#34;\u0026gt; \u0026lt;!-- 4、使用组件 --\u0026gt; \u0026lt;com1\u0026gt;\u0026lt;/com1\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script\u0026gt; //1、声明组件 let temp = Vue.extend({ //在es6中，可以使用fj\u0026#39;yn``来定义字符串，这个字符串可以换行，不需要拼接 template:` \u0026lt;form\u0026gt; uname:\u0026lt;input /\u0026gt; upwd:\u0026lt;input /\u0026gt; \u0026lt;button\u0026gt;add\u0026lt;/button\u0026gt; \u0026lt;/form\u0026gt; `, data(){ return{} }, name:\u0026#34;temp\u0026#34; }); //2、注册为全局组件Vue.xomponent(\u0026#34;组件名\u0026#34;,组件对象) Vue.component(\u0026#34;com1\u0026#34;,temp); //3、挂载元素 let v = new Vue({ el:\u0026#34;#d1\u0026#34; }) \u0026lt;/script\u0026gt; 2、直接将模板注册为全局组件 # \u0026lt;div id=\u0026#34;d1\u0026#34;\u0026gt; \u0026lt;!-- 3、使用组件 --\u0026gt; \u0026lt;com1\u0026gt;\u0026lt;/com1\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script\u0026gt; //1、声明并注册为全局组件，Vue会自动调用extenddv进行构造 Vue.component(\u0026#34;com1\u0026#34;,{ temlate:` \u0026lt;form\u0026gt; uname:\u0026lt;input /\u0026gt; upwd:\u0026lt;input /\u0026gt; \u0026lt;button\u0026gt;add\u0026lt;/button\u0026gt; \u0026lt;/form\u0026gt; ` }); //2、挂载元素 let v = new Vue({ el:\u0026#34;#d1\u0026#34; }) \u0026lt;/script\u0026gt; 3、在html、vue代码中使用\u0026lt;template\u0026gt;标签编写模板，然后注册组件（组件模板分离，推荐） # \u0026lt;template\u0026gt;标签中，只可以有一个根标签，不然除了第一个根标签中内容，其他的不会显示\n\u0026lt;template\u0026gt;标签也可以使用\u0026lt;script type=\u0026quot;text/x-template\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;替代\n\u0026lt;div id=\u0026#34;d1\u0026#34;\u0026gt; \u0026lt;!-- 4、使用组件 --\u0026gt; \u0026lt;com1\u0026gt;\u0026lt;/com1\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;!-- 1、编写模板 --\u0026gt; \u0026lt;template id=\u0026#34;temp\u0026#34;\u0026gt; \u0026lt;form\u0026gt; uname:\u0026lt;input /\u0026gt; upwd:\u0026lt;input /\u0026gt; \u0026lt;button\u0026gt;add\u0026lt;/button\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; //2、注册为全局组件 Vue.component(\u0026#34;com1\u0026#34;,{ temlate:\u0026#34;#temp\u0026#34; }); //3、挂载元素 let v = new Vue({ el:\u0026#34;#d1\u0026#34; }) \u0026lt;/script\u0026gt; 全局组件和局部组件的区别 # 全局组件 声明方式：Vue.component(\u0026quot;组件名\u0026quot;,组件对象) 作用范围：所有Vue实例管理的元素 局部组件（子组件） 声明方式：在Vue实例中，使用components属性注册组件{组件名,组件对象} 作用范围：当前Vue实例管理的元素中 重要内置关系 # //VueComponent显示原型属性的隐式原型属性等于Vue的显示原型属性 VueComponent.prototype.__proto__ === Vue.prototype 这个关系的作用：让所有组件实例对象可以访问到Vue原型上的属性、方法\n单文件组件 # 单文件组件：文件名以.vue结尾，一个文件中只声明一个组件（主要使用方式） 起始就是将组件声明时extend()函数的参数部分（组件配置）抽离出来，与模板和样式单独作为一个文件 非单文件组件：普通的html文件，一个文件中声明多个组件，不好维护 \u0026lt;!-- 组件的模板 --\u0026gt; \u0026lt;template\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;!-- 组件的js部分 --\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;App\u0026#34;, components: { }, data() { return { }; }, } \u0026lt;/script\u0026gt; \u0026lt;!-- 组件的样式部分 --\u0026gt; \u0026lt;style\u0026gt; \u0026lt;/style\u0026gt; style # 1、lang属性 # css：默认可以解析原生css\nless：脚手架需要安装less-loader\n2、scoped属性 # 在多个组件中声明的style标签中的样式，最终会汇集到一起，这就导致了，如果样式中出现了同名的id、class，会发生冲突，为了解决这个问题，就需要使用scoped，这个属性不适合用与App组件\n\u0026lt;!-- 在style标签上添加scoped属性即可 --\u0026gt; \u0026lt;style scoped\u0026gt;\u0026lt;/style\u0026gt; 组件化编码流程 # 1、实现静态组件、抽取组件，使用组件实现静态页面效果\n2、实现动态组件、并且考虑数据存放位置，操作数据的方法优先和数据放在一起\n3、实现交互功能\n组件的切换 # 可以使用标签\u0026lt;component\u0026gt;作为组件的载体，使用:is=\u0026quot;componentName\u0026quot;设置搭载的组件\n\u0026lt;div id=\u0026#34;div1\u0026#34;\u0026gt; \u0026lt;button @click=\u0026#34;comName=\u0026#39;com1\u0026#39;\u0026#34;\u0026gt;temp1\u0026lt;/button\u0026gt; \u0026lt;button @click=\u0026#34;comName=\u0026#39;com2\u0026#39;\u0026#34;\u0026gt;temp2\u0026lt;/button\u0026gt; \u0026lt;component :is=\u0026#34;comName\u0026#34;\u0026gt;\u0026lt;/component\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;template id=\u0026#34;temp1\u0026#34;\u0026gt; \u0026lt;h1\u0026gt;This is temp1\u0026lt;/h1\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;template id=\u0026#34;temp2\u0026#34;\u0026gt; \u0026lt;h1\u0026gt;This is temp2\u0026lt;/h1\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; Vue.component(\u0026#34;com1\u0026#34;,{ template:\u0026#34;#temp1\u0026#34; }) Vue.component(\u0026#34;com2\u0026#34;,{ template:\u0026#34;#temp2\u0026#34; }) let v = new Vue({ el:\u0026#34;#div1\u0026#34;, data:{ comName:\u0026#34;com1\u0026#34; } }) \u0026lt;/script\u0026gt; mixin混入 # 对于多个组件公用的配置项，可以抽离出来，声明一个独立的js文件\n如果混合中声明了data、methods，那么会将混合中声明的和组件声明的混合一起，全部生效；如果混合中声明了mounted等钩子，那么会都执行\nexport default { //钩子函数，用于打印组件加载情况 mounted () { console.log(\u0026#34;组件\u0026#34;,this.$options.name,\u0026#34;加载完成\u0026#34;) } } 在组件中进行局部引入，并且使用\n//引入 import m from \u0026#39;../mixin/mixin\u0026#39; export default { name: \u0026#34;Login\u0026#34;, //使用 mixins:[m] } 也可以在main.js中进行全局引入混合\n//引入 import m from \u0026#39;../mixin/mixin\u0026#39; //声明全局混合，所有组件都会生效，可以声明多个Vue.mixin() Vue.mixin(m); 插件 # vue支持自定义插件，并且在main.js中，可以使用Vue.use(plugin)进行开启\n定义插件（本质就是一个对象中，包含一个install函数，该函数接收Vue构造函数作为参数）：\nexport default { install(Vue){ //定义全局过滤器 Vue.filter(\u0026#34;priceFormat\u0026#34;,function(value){ return value + \u0026#34;元\u0026#34;; }) //定义全局指令 Vue.directive(\u0026#34;log\u0026#34;,function(element,binding){ console.log(\u0026#34;自定义指令\u0026#34;); console.log(element); console.log(binding); }) //在Vue原型上添加函数，所有组件实例都可调用 Vue.prototype.hello = function(){ console.log(\u0026#34;hello\u0026#34;) } } } 在main.js中引入插件js，并且进行开启\n//引入插件 import p from \u0026#39;./plugin.js\u0026#39;; //开启插件，可以开启多个 Vue.use(p); ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/bafd68f1/b2321cb9/27fc037f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e组件 \n    \u003cdiv id=\"组件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%bb%84%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e组件就是实现应用\u003cstrong\u003e局部功能\u003c/strong\u003e的\u003cstrong\u003e代码和资源\u003c/strong\u003e的\u003cstrong\u003e集合\u003c/strong\u003e\u003c/p\u003e","title":"3、组件","type":"posts"},{"content":" 镜像 # 是一种轻量级、可执行的独立软件包，它包含运行某个软件多需要的所有内容（环境），我们应把应用程序和配置依赖打包好形成一个可交付的运行环境（代码、运行时库、环境变量、配置文件等），这个打包好的运行环境就是image镜像文件\n联合文件系统UnionFS # Union文件系统是一个分层、轻量级并且高性能的文件系统，它支持对文件系统的修改作为一次提交来一层层的叠加，同时可以将不同目录挂载到一个虚拟文件系统下。Union文件系统就是Docker镜像的基础。镜像可以通过分层来继承，基于基础镜像（没有父镜像），可以制作各种具体的应用镜像\nCommit # docker commit提交容器副本使之称为一个新的镜像\ndocker commit -m=\u0026#34;提交的描述信息\u0026#34; -a=\u0026#34;作者\u0026#34; [containername/id] [imagename]:[tag] Push # 阿里云 # 1、进入容器镜像服务、个人实例\n2、创建命名空间\n3、创建镜像仓库，选择本地仓库\n4、然后根据提示登录、推送、拉取镜像即可\n#登录 docker login --username=[username] registry.cn-hangzhou.aliyuncs.com #推送 docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/yhgh/yhgh:[镜像版本号] docker push registry.cn-hangzhou.aliyuncs.com/yhgh/yhgh:[镜像版本号] #拉取 docker pull registry.cn-hangzhou.aliyuncs.com/yhgh/yhgh:[镜像版本号] 私有库 # 搭建私有仓库 # Docker Registry是官方提供的工具，可以用于构建私有镜像仓库\n1、拉取最新版registry\ndocker pull registry 2、运行容器\ndocker run -d -p 5000:5000 -v /data/registry:/tmp/registry --privileged=true --name=registregistry 3、访问http://ip:5000/v2/_catalog，可以查看到当前仓库的镜像\n4、将本地的镜像文件改名为符合registry要求的名字和tag\ndocker tag [localimagename/id] [ip:5000/name:tag] 5、取消docker对http推送镜像的限制，修改/etc/docker/daemon.json，如果不生效需要重启docker\n{ \u0026#34;registry-mirrors\u0026#34;:[\u0026#34;http:...\u0026#34;], \u0026#34;insecure-registries\u0026#34;:[\u0026#34;ip:5000\u0026#34;] } 6、推送镜像\ndocker push ip:5000/name:tag 7、拉取镜像\ndocker pull ip:5000/name:tag 自定义镜像 # 自定义构建镜像：Dockerfile # 镜像的来源，一般可以有3种途径：从平台上获取，容器逆向生成镜像，以及DockerFile文件构建镜像 需要注意的是，我们并不是真正\u0026quot;创建\u0026quot;新镜像，而是基于一个已有的基础镜像，如 centos 或 ubuntu 等，构建新镜像而已 在构建Docker镜像时，需要使用一个文件，文件的名固定为：Dockerfile DockerFile是用来构建Docker镜像的文本文件，是由一条条构建镜像所需要的指令和参数构成的脚本 Dockerfile四部分 # Dockerfile分为四部分：基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令 基础镜像信息 # #设置基础镜像 FROM centos 维护者信息 # MAINTAINER 929880282@qq.com 镜像操作指令 # #复制压缩包到镜像中，并进行解压 ADD ./jdk-8u11-linux-x64.tar.gz /usr/local/java ADD ./apache-tomcat-8.5.20.tar.gz /usr/local/tomcat #set environment variable ENV JAVA_HOME /usr/local/java/jdk1.8.0_11 ENV JRE_HOME $JAVA_HOME/jre ENV CLASSPATH .:$JAVA_HOME/lib:$JRE_HOME/lib ENV PATH $PATH:$JAVA_HOME/bin #EXPOSE 映射端口 EXPOSE 8080 容器启动时执行指令 # #ENTRYPOINT：配置容器启动时，需要执行的文件 ENTRYPOINT /usr/local/tomcat/apache-tomcat-8.5.20/bin/startup.sh \u0026amp;\u0026amp; tail -F /usr/local/tomcat/apache-tomcat-8.5.20/logs/catalina.out Dockerfile的详细命令 # FROM : 指定基础镜像，要在哪个镜像建立：FROM centos\nMAINTAINER：指定维护者信息：MAINTAINER 565599455@qq.com\nRUN：容器构建的时候在镜像中要执行的Linux命令\nADD: 相当于 COPY，但是比 COPY 功能更强大(如果是压缩包，会自动解压，如果是URL会自动下载)\n**COPY **：复制本地主机的 （为 Dockerfile 所在目录的相对路径）到容器中的\nENV：定义环境变量\nWORKDIR：指定当前工作目录，相当于 cd ，为后续的 RUN 、 CMD 、 ENTRYPOINT 指令配置工作目录，也是容器默认目录\nEXPOSE：指定容器要打开的端口\nVOLUME：挂载目录，创建一个可以从本地主机或其他容器挂载的挂载点，一般用来存放数据库和需要保持的数据等。格式为VOLUME [“/data”]\nENTRYPOINT：指定容器启动后，需要执行的文件，或者需要命令的操作的文件，如果和CMD一起使用，CMD会变成给ENTRYPOINT传参，而不是执行命令，例如\nENTRYPOINT [\u0026#34;nginx\u0026#34;,\u0026#34;-c\u0026#34;] CMD [\u0026#34;/etc/nginx/nginx.conf\u0026#34;] #相当于执行 nginx -c /etc/nginx/nginx.conf CMD：指定容器启动后，需要运行的命令，一个dockerfile中只会生效最后一个cmd，也会被docker run命令后的command替换\nUSER：指定该镜像使用什么用户，默认为root\n执行Dockerfile构建镜像 # 此命令需要在Dockerfile的目录中执行\n#注意！！！后面需要有个点 . docker build -t [name:tag] . docker build -t [name:tag] --file dockerFilePath . -t [name:tag] #指定构建的镜像名字和tag --file或-f #指定dockerfile文件路径 Dockerfile的示例 # 构建centosjava8 # tar包内部结构\n#设置基础镜像 FROM centos #指定维护者信息 MAINTAINER yhgh 929880282@qq.com #复制jdk压缩包到镜像中，并进行解压 ADD ./jdk-8u202-linux-x64.tar.gz /usr/local/java #配置JDK环境变量 ENV JAVA_HOME /usr/local/java/jdk1.8.0_202 ENV JRE_HOME $JAVA_HOME/jre ENV CLASSPATH .:$JAVA_HOME/lib:$JRE_HOME/lib ENV PATH $PATH:$JAVA_HOME/bin 构建Tomcat # #设置基础镜像 FROM centos MAINTAINER 929880282@qq.com #复制压缩包到镜像中，并进行解压 ADD ./jdk-8u11-linux-x64.tar.gz /usr/local/java ADD ./apache-tomcat-8.5.20.tar.gz /usr/local/tomcat #set environment variable ENV JAVA_HOME /usr/local/java/jdk1.8.0_11 ENV JRE_HOME $JAVA_HOME/jre ENV CLASSPATH .:$JAVA_HOME/lib:$JRE_HOME/lib ENV PATH $PATH:$JAVA_HOME/bin #EXPOSE 映射端口 EXPOSE 8080 #ENTRYPOINT 配置容器启动时，需要执行的文件 ENTRYPOINT /usr/local/tomcat/apache-tomcat-8.5.20/bin/startup.sh \u0026amp;\u0026amp; tail -F /usr/local/tomcat/apache-tomcat-8.5.20/logs/catalina.out 构建Nginx # # Base images 基础镜像 FROM centos #MAINTAINER 维护者信息 MAINTAINER 929880282@qq.com #ENV 设置环境变量 ENV PATH /usr/local/nginx/sbin:$PATH #ADD 文件放在当前目录下，拷过去会自动解压 ADD nginx-1.8.0.tar.gz /usr/local/ ADD epel-release-latest-7.noarch.rpm /usr/local/ #RUN 执行以下命令 RUN rpm -ivh /usr/local/epel-release-latest-7.noarch.rpm RUN yum install -y wget lftp gcc gcc-c++ make openssl-devel pcre-devel pcre \u0026amp;\u0026amp; yum clean all RUN useradd -s /sbin/nologin -M www #WORKDIR 相当于cd WORKDIR /usr/local/nginx-1.8.0 RUN ./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_ssl_module --with-pcre \u0026amp;\u0026amp; make \u0026amp;\u0026amp; make install RUN echo \u0026#34;daemon off;\u0026#34; \u0026gt;\u0026gt; /etc/nginx.conf #EXPOSE 映射端口 EXPOSE 80 #CMD 运行以下命令 CMD [\u0026#34;nginx\u0026#34;] 构建自己的Tomcat # 1、Linux中选择一个目录/root/docker/，将JDK以及Tomcat的压缩包上传至该目录 # 2、创建Dockerfile文件 # 3、编写Dockerfile内容 # #设置基础镜像 FROM centos MAINTAINER 929880282@qq.com #复制压缩包到镜像中，并进行解压 ADD ./jdk-8u271-linux-x64.tar.gz /usr/local/java ADD ./apache-tomcat-8.5.61.tar.gz /usr/local/tomcat #u ENV JAVA_HOME /usr/local/java/jdk1.8.0_271 ENV JRE_HOME $JAVA_HOME/jre ENV CLASSPATH .:$JAVA_HOME/lib:$JRE_HOME/lib ENV PATH $PATH:$JAVA_HOME/bin #EXPOSE 映射端口 EXPOSE 8080 #ENTRYPOINT 配置容器启动时，需要执行的文件 ENTRYPOINT /usr/local/tomcat/apache-tomcat-8.5.61/bin/startup.sh \u0026amp;\u0026amp; tail -F /usr/local/tomcat/apache-tomcat-8.5.61/logs/catalina.out 4、执行该文件，构建一个Tomcat镜像出来 # Dockerfile在哪个目录下，就在哪个目录执行如下命令： docker build -t ygang/tomcat:latest . 5、运行容器 # docker run -d -p 8080:8080 --name ygangtomcat ygang/tomcat:latest 6、在浏览器，即可查看效果 # ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/61bf7bc9/5e6103fa/36ca2e73/80ddede9/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e镜像 \n    \u003cdiv id=\"镜像\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%95%9c%e5%83%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e是一种轻量级、可执行的独立软件包，它包含运行某个软件多需要的所有内容（环境），我们应把应用程序和配置依赖打包好形成一个可交付的运行环境（代码、运行时库、环境变量、配置文件等），这个打包好的运行环境就是image镜像文件\u003c/p\u003e","title":"3、镜像","type":"posts"},{"content":"C++面向对象的三大特性为：封装、继承、多态\nC++认为万事万物都皆为对象，对象上有其属性和行为，和java特别相似\n封装 # 类 # 语法：class 类名{ 访问权限： 属性 / 行为 };\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; #define PI 3.14 //圆类 class Circle { //声明访问权限 public: //圆的半径 int r; //计算圆的周长 double computePerimeter() { return 2 * PI * r; } }; int main() { Circle c; c.r = 20; double per = c.computePerimeter(); cout \u0026lt;\u0026lt; per \u0026lt;\u0026lt; endl; return 0; } 权限修饰符 # cpp中修饰类成员的修饰符一共有三个：public、private、protected，没有java中的缺省，如果不写，默认是private\npublic：公有成员在程序中类的外部是可访问的\nprivate：私有成员变量或函数在类的外部是不可访问的，甚至是不可查看的。只有类和友元函数可以访问私有成员。\nprotected：受保护成员变量或函数与私有成员十分相似，但有一点不同，protected（受保护）成员在派生类（即子类）中是可访问的。\n例子：\nclass MyClass { int a; //默认是private private: // 私有 int b; protected: //受保护 int c; public: //公共 void func() { } }; 类和结构体的区别 # 在C++中 struct和class唯一的区别就在于 默认的访问权限不同\n区别：\nstruct 默认权限为公共public class 默认权限为私有private 对象的初始化 # C++中的面向对象来源于生活，每个对象也都会有初始设置以及对象销毁前的清理数据的设置\n构造函数和析构函数 # 对象的初始化和清理也是两个非常重要的安全问题\n1、一个对象或者变量没有初始状态，对其使用后果是未知\n2、同样的使用完一个对象或变量，没有及时清理，也会造成一定的安全问题\nc++利用了构造函数和析构函数解决上述问题，这两个函数将会被编译器自动调用，完成对象初始化和清理工作。\n对象的初始化和清理工作是编译器强制要我们做的事情，因此如果我们不提供构造和析构，编译器会提供编译器提供的构造函数和析构函数是空实现。\n构造函数：主要作用在于创建对象时为对象的成员属性赋值，构造函数由编译器自动调用，无须手动调用。 析构函数：主要作用在于对象销毁前系统自动调用，执行一些清理工作。 构造函数 # 语法：类名(){}\n构造函数，没有返回值也不写void 函数名称与类名相同 构造函数可以有参数，因此可以发生重载 程序在调用对象时候会自动调用构造，无须手动调用,而且只会调用一次 可以声明权限修饰符 析构函数 # 语法：~类名(){}\n析构函数，没有返回值也不写void 函数名称与类名相同,在名称前加上符号 ~ 析构函数不可以有参数，因此不可以发生重载 程序在对象销毁前会自动调用析构，无须手动调用,而且只会调用一次 可以声明权限修饰符 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; class Person { public: Person() { cout \u0026lt;\u0026lt; \u0026#34;无参构造函数\u0026#34; \u0026lt;\u0026lt; endl; } Person(int a) { cout \u0026lt;\u0026lt; \u0026#34;有参构造函数\u0026#34; \u0026lt;\u0026lt; endl; } ~Person() { cout \u0026lt;\u0026lt; \u0026#34;析构函数，在对象销毁前调用\u0026#34; \u0026lt;\u0026lt; endl; } }; int main() { //调用无参构造 Person p1; //调用有参构造 Person p2(10); system(\u0026#34;pause\u0026#34;); return 0; } 构造函数的分类和调用 # 两种分类方式： 按参数分为： 有参构造和无参构造 按类型分为： 普通构造和拷贝构造 三种调用方式： 括号法 显式法 隐式转换法 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; class Person { private: int age; public: //无参(默认)构造 Person() { cout \u0026lt;\u0026lt; \u0026#34;无参构造函数\u0026#34; \u0026lt;\u0026lt; endl; } //有参构造 Person(int a) { age = a; cout \u0026lt;\u0026lt; \u0026#34;有参构造函数\u0026#34; \u0026lt;\u0026lt; endl; } //拷贝构造 Person(const Person \u0026amp; p) { age = p.age; cout \u0026lt;\u0026lt; \u0026#34;拷贝构造函数\u0026#34; \u0026lt;\u0026lt; endl; } int getAge() { return age; } }; int main() { //调用无参构造 Person p1; //调用有参构造，括号法调用 Person p2(18); //调用拷贝构造，显式法 Person p3 = Person(p2); //调用拷贝构造，隐式转换法 Person p4 = p2; cout \u0026lt;\u0026lt; p3.getAge() \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 构造函数调用规则 # 默认情况下，c++编译器至少给一个类添加3个函数\n1．默认构造函数(无参，函数体为空)\n2．默认析构函数(无参，函数体为空)\n3．默认拷贝构造函数，对属性进行值拷贝\n构造函数调用规则如下：\n如果用户定义有参构造函数，c++不在提供默认无参构造，但是会提供默认拷贝构造\n如果用户定义拷贝构造函数，c++不会再提供其他构造函数\n深拷贝与浅拷贝 # 浅拷贝：简单的赋值拷贝操作\n深拷贝：在堆区重新申请空间，进行拷贝操作\n如果属性有在堆区开辟的，一定要自己提供拷贝构造函数，防止浅拷贝带来的问题\n静态成员 # 静态成员就是在成员变量和成员函数前加上关键字static，称为静态成员\n静态成员分为：\n静态成员变量 所有对象共享同一份数据 在编译阶段分配内存 类内声明，类外初始化 静态成员函数 所有对象共享同一个函数 静态成员函数只能访问静态成员变量 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; class Person { public: //静态变量也是有权限修饰符的，而且在类内只可以进行声明 static int TYPE; //静态函数也是由权限修饰符，但是可以在类内进行初始化 static void func() { cout \u0026lt;\u0026lt; \u0026#34;静态函数\u0026#34; \u0026lt;\u0026lt; endl; } }; //类外进行静态变量初始化，使用作用域运算符::，需要声明变量类型 int Person::TYPE = 20; int main() { //进行调用，使用作用域运算符:: cout \u0026lt;\u0026lt; Person::TYPE \u0026lt;\u0026lt; endl; Person::func(); system(\u0026#34;pause\u0026#34;); return 0; } C++对象模型和this指针 # 在C++中，类内的成员变量和成员函数分开存储\n只有非静态成员变量才属于类的对象上\nclass Person { public: Person() { mA = 0; } //非静态成员变量占对象空间 int mA; //静态成员变量不占对象空间 static int mB; //函数也不占对象空间，所有函数共享一个函数实例 void func() { cout \u0026lt;\u0026lt; \u0026#34;mA:\u0026#34; \u0026lt;\u0026lt; this-\u0026gt;mA \u0026lt;\u0026lt; endl; } //静态成员函数也不占对象空间 static void sfunc() { } }; int main() { cout \u0026lt;\u0026lt; sizeof(Person) \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } this指针 # 在C++中成员变量和成员函数是分开存储的，每一个非静态成员函数只会诞生一份函数实例，也就是说多个同类型的对象会共用一块代码，那么问题是：这一块代码是如何区分那个对象调用自己的呢？\nc++通过提供特殊的对象指针，this指针，解决上述问题。this指针指向被调用的成员函数所属的对象\nthis指针是隐含每一个非静态成员函数内的一种指针，本质是指针常量，也就是指针的指向是不可以修改的\nthis指针不需要定义，直接使用即可，和java中的this意义一样，只不过使用方式不同\nthis指针的用途：\n当形参和成员变量同名时，可用this指针来区分 在类的非静态成员函数中返回对象本身，可使用return *this 语法：this -\u0026gt; 非静态成员\n例子：\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; class Person { private: int age; string name; public: //和java中的getter、setter一样 void setAge(int age) { //this-\u0026gt;代表，当前对象的 this-\u0026gt;age = age; } void setName(string name) { this-\u0026gt;name = name; } int getAge() { return age; } string getName() { return name; } string getInfo() { return \u0026#34;姓名：\u0026#34; + name + \u0026#34;，年龄：\u0026#34; + to_string(age); } }; int main() { Person person; person.setAge(18); person.setName(\u0026#34;Lucy\u0026#34;); cout \u0026lt;\u0026lt; person.getInfo() \u0026lt;\u0026lt; endl; //姓名：Lucy，年龄：18 system(\u0026#34;pause\u0026#34;); return 0; } 空指针调用成员函数 # C++中空指针也是可以调用成员函数的，但是也要注意有没有用到this指针\n如果用到this指针，需要加以判断保证代码的健壮性\n例子：\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; class Person { private: int age = 12; string name = \u0026#34;Lucy\u0026#34;; public: void showMsg() { cout \u0026lt;\u0026lt; \u0026#34;你好\u0026#34; \u0026lt;\u0026lt; endl; } void showName() { cout \u0026lt;\u0026lt; this-\u0026gt;name \u0026lt;\u0026lt; endl; } void showAge() { cout \u0026lt;\u0026lt; age \u0026lt;\u0026lt; endl; } }; int main() { Person * person = NULL; person-\u0026gt;showMsg(); // 正常运行，因为函数为调用this的成员 person-\u0026gt;showAge(); // 报错，因为this是NULL person-\u0026gt;showName(); // 报错，因为this是NULL system(\u0026#34;pause\u0026#34;); return 0; } 常函数和常对象 # 常函数 # 成员函数后加const后我们称为这个函数为常函数 常函数内不可以修改成员属性 成员属性声明时加关键字mutable后，在常函数中依然可以修改 常对象 # 声明对象前加const称该对象为常对象 常对象只能调用常函数 例子：\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; class Person { private: int a; mutable int b; public: void test1() const{ //a = 10; 常函数中，普通的成员变量不可以进行修改 //只有添加mutable的成员变量，才可以在常函数中进行修改 b = 10; cout \u0026lt;\u0026lt; b \u0026lt;\u0026lt; endl; } void test2() { cout \u0026lt;\u0026lt; \u0026#34;不是常函数\u0026#34; \u0026lt;\u0026lt; endl; } }; int main() { //常对象 const Person person; person.test1(); //person.test2(); 常对象只可以调用常函数 system(\u0026#34;pause\u0026#34;); return 0; } 友元 # 在程序里，有些私有属性也想让类外特殊的一些函数或者类进行访问，就需要用到友元的技术\n友元的关键字为 ：friend\n友元的三种实现\n全局函数做友元 类做友元 其他类成员函数做友元 全局函数做友元 # #include\u0026lt;iostream\u0026gt; using namespace std; class Person { //声明了全局函数test，可以访问Person的私有属性 friend void test(Person * person); private: int a = 10; int b = 20; }; //全局函数test void test(Person * person) { //可以访问Person对象的私有成员 cout \u0026lt;\u0026lt; person-\u0026gt;a \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; person-\u0026gt;b \u0026lt;\u0026lt; endl; } int main() { Person person; test(\u0026amp;person); return 0; } 类做友元 # #include\u0026lt;iostream\u0026gt; using namespace std; class Person { //PersonFriend类作为Person类的友元 friend class PersonFriend; private: int a = 10; int b = 20; }; class PersonFriend { private: Person person; public: PersonFriend(Person \u0026amp; person) { this-\u0026gt;person = person; } void showPerson() { //PersonFriend类中可以访问Person对象的私有成员 cout \u0026lt;\u0026lt; person.a \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; person.b \u0026lt;\u0026lt; endl; } }; int main() { Person person; PersonFriend f(person); f.showPerson(); return 0; } 其他类成员函数做友元 # 这个相对麻烦，顺序需要限制\n#include\u0026lt;iostream\u0026gt; using namespace std; //必须按照这个顺序进行，先声明Person class Person; //然后声明友元函数所在类，其中友元函数只声明，不定义 class PersonFriend{ public: void showPerson(); }; //定义Person类，以及友元 class Person { friend void PersonFriend::showPerson(); private: int a = 10; int b = 20; }; //定义友元函数 void PersonFriend::showPerson() { Person* person = new Person; cout \u0026lt;\u0026lt; person-\u0026gt;a \u0026lt;\u0026lt; endl; } int main() { PersonFriend f; f.showPerson(); return 0; } 继承 # 我们发现，定义这些类时，下级别的成员除了拥有上一级的共性，还有自己的特性。\n这个时候我们就可以考虑利用继承的技术，减少重复代码\n继承的基本语法 # class A : public B; A 类称为子类 或 派生类 B 类称为父类 或 基类 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; //人类 class Person { public: void personMsg() { cout \u0026lt;\u0026lt; \u0026#34;人可以吃饭、睡觉\u0026#34; \u0026lt;\u0026lt; endl; } }; //老师类，继承于人类 class Teacher : public Person { public: void teacherMsg() { cout \u0026lt;\u0026lt; \u0026#34;老师可以教书育人\u0026#34; \u0026lt;\u0026lt; endl; } }; int main() { Teacher t; //老师类对象可以调用人类对象的方法 t.personMsg(); t.teacherMsg(); return 0; } 继承方式 # 继承方式一共有三种：\n公共继承 父类中公共的成员，在子类中还是公共成员；父类中保护的成员，在子类中还是保护的成员 保护继承 父类中公共、保护的成员，在子类中都是保护的成员 私有继承 父类中公共、保护的成员，在子类中都是私有的成员 无论哪种继承方式，父类中私有的成员，子类都无法访问\n继承中的对象模型 # #include\u0026lt;iostream\u0026gt; using namespace std; class A { public: int a = 10; protected: int b = 20; private: int c = 30; }; class B : public A { public: int d = 40; }; int main() { //也就是说，父类里面的所有非静态的成员都会被子类继承 //虽然父类中private的属性子类无法访问，只是被编译器隐藏了，但是确实被继承下去了 cout \u0026lt;\u0026lt; sizeof(B) \u0026lt;\u0026lt; endl; //16 return 0; } 也可以使用Visual Studio的工具来查看 # 打开工具窗口后，定位到当前CPP文件的位置\n然后输入： cl /d1 reportSingleClassLayout查看的类名 所属文件名\n例如，查看demo1.cpp中的B类\n继承中构造和析构的顺序 # 构造：先父类后子类\n析构：先子类后父类\n#include\u0026lt;iostream\u0026gt; using namespace std; class A { public: A() { cout \u0026lt;\u0026lt; \u0026#34;父类A的构造函数\u0026#34; \u0026lt;\u0026lt; endl; } ~A() { cout \u0026lt;\u0026lt; \u0026#34;父类A的析构函数\u0026#34; \u0026lt;\u0026lt; endl; } }; class B : public A { public: B() { cout \u0026lt;\u0026lt; \u0026#34;子类B的构造函数\u0026#34; \u0026lt;\u0026lt; endl; } ~B() { cout \u0026lt;\u0026lt; \u0026#34;子类B的析构函数\u0026#34; \u0026lt;\u0026lt; endl; } }; int main() { //创建子类B的对象 B b; /* 父类A的构造函数 子类B的构造函数 子类B的析构函数 父类A的析构函数 */ return 0; } 继承中同名的成员处理 # 访问子类同名成员 直接访问即可 访问父类同名成员 需要加作用域，语法：子类对象.父类::父类成员 #include\u0026lt;iostream\u0026gt; using namespace std; class A { public: int num = 10; void test() { cout \u0026lt;\u0026lt; \u0026#34;父类的test\u0026#34; \u0026lt;\u0026lt; endl; } }; class B : public A { public: int num = 20; void test() { cout \u0026lt;\u0026lt; \u0026#34;子类的test\u0026#34; \u0026lt;\u0026lt; endl; } }; int main() { B b; //访问子类中的num cout \u0026lt;\u0026lt; b.num \u0026lt;\u0026lt; endl; //访问父类中的num cout \u0026lt;\u0026lt; b.A::num \u0026lt;\u0026lt; endl; //访问子类的test() b.test(); //访问父类的test() b.A::test(); return 0; } 多继承语法 # C++允许一个类继承多个类\n语法： class 子类 ：继承方式 父类1 ， 继承方式 父类2...\n多继承可能会引发父类中有同名成员出现，需要加作用域区分\nC++实际开发中不建议用多继承\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; class A { public: int num = 10; }; class B { public: int num = 20; }; class C : public A , public B{ public: int num = 30; }; int main() { C c; cout \u0026lt;\u0026lt; c.num \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; c.A::num \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; c.B::num \u0026lt;\u0026lt; endl; return 0; } 菱形继承问题 # 菱形继承概念：\n两个派生类继承同一个基类\n又有某个类同时继承者两个派生类\n这种继承被称为菱形继承，或者钻石继承\n菱形继承问题：\n羊继承了动物的数据，驼同样继承了动物的数据，当草泥马使用数据时，就会产生二义性。 草泥马继承自动物的数据继承了两份，其实我们应该清楚，这份数据我们只需要一份就可以。 利用工具，可以看到，YangTuo类继承了两份age\n#include\u0026lt;iostream\u0026gt; using namespace std; //动物 class Animal { public: int age; }; //羊 class Yang : public Animal { }; //驼 class Tuo : public Animal { }; //羊驼 class YangTuo : public Yang, public Tuo { }; int main() { YangTuo y; //问题1： 报错，age不明确 //y.age = 10; //问题2：虽然可以解决问题1，但是一个羊驼不可能有两个变量 y.Yang::age = 18; y.Tuo::age = 28; cout \u0026lt;\u0026lt; y.Yang::age \u0026lt;\u0026lt; endl; //18 cout \u0026lt;\u0026lt; y.Tuo::age \u0026lt;\u0026lt; endl; //28 return 0; } 利用虚继承virtual解决 # #include\u0026lt;iostream\u0026gt; using namespace std; //动物 class Animal { public: int age; }; //继承前加virtual关键字后，变为虚继承 //此时公共的父类Animal称为虚基类 //羊 class Yang :virtual public Animal { }; //驼 class Tuo :virtual public Animal { }; //羊驼 class YangTuo : public Yang, public Tuo { }; int main() { YangTuo y; y.age = 18; cout \u0026lt;\u0026lt; y.Yang::age \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; y.Tuo::age \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; y.age \u0026lt;\u0026lt; endl; return 0; } 此时采用工具查看 # age只有一份了\n此时vbptr（v-virtual；b-base；ptr-pointer）：虚基类指针，指向vbtable（虚基类表），即下方显示的，该指针的偏移量4，刚好就是这个表里面的唯一的数据\n多态 # 多态分为两类\n静态多态: 函数重载、运算符重载属于静态多态，复用函数名 动态多态: 派生类和虚函数实现运行时多态 静态多态和动态多态区别：\n静态多态的函数地址早绑定 - 编译阶段确定函数地址 动态多态的函数地址晚绑定 - 运行阶段确定函数地址 早绑定：\n#include\u0026lt;iostream\u0026gt; using namespace std; //动物 class Animal { public: void eat() { cout \u0026lt;\u0026lt; \u0026#34;动物在吃饭\u0026#34; \u0026lt;\u0026lt; endl; } }; //猫，继承于动物 class Cat : public Animal{ public: void eat() { cout \u0026lt;\u0026lt; \u0026#34;猫吃猫粮\u0026#34; \u0026lt;\u0026lt; endl; } }; //狗，继承于动物 class Dog : public Animal { public: void eat() { cout \u0026lt;\u0026lt; \u0026#34;狗吃狗粮\u0026#34; \u0026lt;\u0026lt; endl; } }; //使用父类引用指向子类对象 void eatTest(Animal \u0026amp; animal) { animal.eat(); } int main() { Cat cat; eatTest(cat); //动物在吃饭，原因是eatTest()中函数的地址是早绑定，在编译时已经确定 return 0; } 晚绑定，在函数前添加virtual关键字：\n#include\u0026lt;iostream\u0026gt; using namespace std; //动物 class Animal { public: //添加关键字virtual，可以使函数虚绑定，也就是在运行时，绑定函数 virtual void eat() { cout \u0026lt;\u0026lt; \u0026#34;动物在吃饭\u0026#34; \u0026lt;\u0026lt; endl; } }; //猫，继承于动物 class Cat : public Animal{ public: void eat() { cout \u0026lt;\u0026lt; \u0026#34;猫吃猫粮\u0026#34; \u0026lt;\u0026lt; endl; } }; //狗，继承于动物 class Dog : public Animal { public: void eat() { cout \u0026lt;\u0026lt; \u0026#34;狗吃狗粮\u0026#34; \u0026lt;\u0026lt; endl; } }; //使用父类引用指向子类对象 void eatTest(Animal \u0026amp; animal) { animal.eat(); } int main() { Cat cat; eatTest(cat); //猫吃猫粮 Dog dog; eatTest(dog); //狗吃狗粮 return 0; } 多态满足条件 # 有继承关系 子类重写父类中的虚函数 重写 # 函数返回值类型 、函数名、参数列表、完全一致称为重写\n多态使用条件 # 父类指针或引用指向子类对象 多态的底层原理 # #include\u0026lt;iostream\u0026gt; using namespace std; class A { public: void test() { } }; class B { public: virtual void test() { } }; int main() { //普通的成员函数是不和类放在一起的，所以A只有类的占位空间1 cout \u0026lt;\u0026lt; sizeof(A) \u0026lt;\u0026lt; endl; //1 //对于虚函数，类中记录的虚函数的指针地址，所以占4 cout \u0026lt;\u0026lt; sizeof(B) \u0026lt;\u0026lt; endl; //4 return 0; } 纯虚函数和抽象类 # 在多态中，通常父类中虚函数的实现是毫无意义的，主要都是调用子类重写的内容\n因此可以将虚函数改为纯虚函数\n纯虚函数语法：virtual 返回值类型 函数名 （参数列表）= 0 ;\n当类中有了纯虚函数，这个类也称为抽象类\n抽象类特点：\n无法实例化对象 子类必须重写抽象类中的纯虚函数，否则也属于抽象类 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; class Person { public: int a = 10; virtual void eat() = 0; }; class Student : public Person { public: void eat() { cout \u0026lt;\u0026lt; \u0026#34;学生吃饭\u0026#34; \u0026lt;\u0026lt; endl; } }; int main() { Student s; cout \u0026lt;\u0026lt; s.a \u0026lt;\u0026lt; endl; s.eat(); } 虚析构和纯虚析构 # 多态使用时，如果子类中有属性开辟到堆区，那么父类指针在释放时无法调用到子类的析构代码\n解决方式：将父类中的析构函数改为虚析构或者纯虚析构\n​\t1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象\n​\t2. 如果子类中没有堆区数据，可以不写为虚析构或纯虚析构\n​\t3. 拥有纯虚析构函数的类也属于抽象类\n虚析构和纯虚析构共性：\n可以解决父类指针释放子类对象 都需要有具体的函数实现 虚析构和纯虚析构区别：\n如果是纯虚析构，该类属于抽象类，无法实例化对象 虚析构语法：virtual ~类名(){}\n纯虚析构语法：virtual ~类名() = 0;然后再定义：类名::~类名(){}\n虚析构 # #include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; class A { public: // 虚析构 virtual ~A() { }; virtual void msg() { }; }; class B : public A { public: string * name = new string(\u0026#34;haha\u0026#34;); //子类析构，清空堆区的name ~B() { cout \u0026lt;\u0026lt; \u0026#34;子类B的析构函数被调用\u0026#34; \u0026lt;\u0026lt; endl; if (name != NULL) { delete(name); } } void msg() { cout \u0026lt;\u0026lt; \u0026#34;重写的msg函数\u0026#34; \u0026lt;\u0026lt; endl; } }; int main() { // 父类指针，指向子类 A * a = new B; a-\u0026gt;msg(); delete a; system(\u0026#34;pause\u0026#34;); return 0; } 纯虚析构 # 必须要声明并定义纯虚析构，因为父类也有可能有属性在堆区\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; class A { public: // 纯虚析构 virtual ~A() = 0; virtual void msg() = 0; }; //定义纯虚析构 A::~A(){} class B : public A { public: string * name = new string(\u0026#34;haha\u0026#34;); //子类析构，清空堆区的name ~B() { cout \u0026lt;\u0026lt; \u0026#34;子类B的析构函数被调用\u0026#34; \u0026lt;\u0026lt; endl; if (name != NULL) { delete(name); } } void msg() { cout \u0026lt;\u0026lt; \u0026#34;重写的msg函数\u0026#34; \u0026lt;\u0026lt; endl; } }; int main() { // 父类指针，指向子类 A * a = new B; a-\u0026gt;msg(); delete a; system(\u0026#34;pause\u0026#34;); return 0; } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/d8245fe1/f50d4583/e74edc4b/","section":"文章","summary":"\u003cp\u003eC++面向对象的三大特性为：\u003cstrong\u003e封装、继承、多态\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eC++认为\u003cstrong\u003e万事万物都皆为对象\u003c/strong\u003e，对象上有其属性和行为，和java特别相似\u003c/p\u003e","title":"3、面向对象","type":"posts"},{"content":"逆向生成也就是使用插件，根据数据库表、字段、类型自动生成对应的Mybatis的Dao、Mapper、Entity\nIDEA逆向生成 # Mybatis Generator # generatorConfig.xml文件 # \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE generatorConfiguration PUBLIC \u0026#34;-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN\u0026#34; \u0026#34;http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd\u0026#34;\u0026gt; \u0026lt;generatorConfiguration\u0026gt; \u0026lt;!-- 数据库驱动:选择你的本地硬盘上面的数据库驱动包--\u0026gt; \u0026lt;classPathEntry location=\u0026#34;E:\\Maven\\maven-jar\\com\\oracle\\ojdbc6\\11.2.0.3\\ojdbc6-11.2.0.3.jar\u0026#34;/\u0026gt; \u0026lt;context id=\u0026#34;DB2Tables\u0026#34; targetRuntime=\u0026#34;MyBatis3\u0026#34;\u0026gt; \u0026lt;commentGenerator\u0026gt; \u0026lt;property name=\u0026#34;suppressDate\u0026#34; value=\u0026#34;true\u0026#34;/\u0026gt; \u0026lt;!-- 是否去除自动生成的注释 true：是 ： false:否 --\u0026gt; \u0026lt;property name=\u0026#34;suppressAllComments\u0026#34; value=\u0026#34;true\u0026#34;/\u0026gt; \u0026lt;/commentGenerator\u0026gt; \u0026lt;!--数据库链接URL，用户名、密码 --\u0026gt; \u0026lt;jdbcConnection driverClass=\u0026#34;oracle.jdbc.OracleDriver\u0026#34; connectionURL=\u0026#34;jdbc:oracle:thin:@localhost:1521:orcl\u0026#34; userId=\u0026#34;EF_DNEVA\u0026#34; password=\u0026#34;123456\u0026#34;\u0026gt; \u0026lt;/jdbcConnection\u0026gt; \u0026lt;javaTypeResolver\u0026gt; \u0026lt;property name=\u0026#34;forceBigDecimals\u0026#34; value=\u0026#34;false\u0026#34;/\u0026gt; \u0026lt;/javaTypeResolver\u0026gt; \u0026lt;!-- 生成实体类的包名和位置--\u0026gt; \u0026lt;javaModelGenerator targetPackage=\u0026#34;top.ygang.entity\u0026#34; targetProject=\u0026#34;E:\\javaproject\\后台代码\\southeast-evaluation\\src\\main\\java\u0026#34;\u0026gt; \u0026lt;/javaModelGenerator\u0026gt; \u0026lt;!-- 生成映射文件xml的包名和位置--\u0026gt; \u0026lt;sqlMapGenerator targetPackage=\u0026#34;mapper\u0026#34; targetProject=\u0026#34;E:\\javaproject\\后台代码\\southeast-evaluation\\src\\main\\resources\u0026#34;\u0026gt; \u0026lt;/sqlMapGenerator\u0026gt; \u0026lt;!-- 生成mapper接口位置--\u0026gt; \u0026lt;javaClientGenerator type=\u0026#34;XMLMAPPER\u0026#34; targetPackage=\u0026#34;top.ygang.dao\u0026#34; targetProject=\u0026#34;E:\\javaproject\\后台代码\\southeast-evaluation\\src\\main\\java\u0026#34;\u0026gt; \u0026lt;/javaClientGenerator\u0026gt; \u0026lt;!-- 自定义要生成的表--\u0026gt; \u0026lt;table schema=\u0026#34;\u0026#34; tableName=\u0026#34;t_sb_bj\u0026#34; \u0026gt;\u0026lt;/table\u0026gt; \u0026lt;table schema=\u0026#34;\u0026#34; tableName=\u0026#34;t_zxjc_ysp\u0026#34; \u0026gt;\u0026lt;/table\u0026gt; \u0026lt;table schema=\u0026#34;\u0026#34; tableName=\u0026#34;t_zxjc_sf6\u0026#34; \u0026gt;\u0026lt;/table\u0026gt; \u0026lt;table schema=\u0026#34;\u0026#34; tableName=\u0026#34;t_zxjc_jczz\u0026#34; \u0026gt;\u0026lt;/table\u0026gt; \u0026lt;table schema=\u0026#34;\u0026#34; tableName=\u0026#34;t_zxjc_jbfd\u0026#34; \u0026gt;\u0026lt;/table\u0026gt; \u0026lt;table schema=\u0026#34;\u0026#34; tableName=\u0026#34;t_sy_sysj\u0026#34; \u0026gt;\u0026lt;/table\u0026gt; \u0026lt;table schema=\u0026#34;\u0026#34; tableName=\u0026#34;t_sy_syjl\u0026#34; \u0026gt;\u0026lt;/table\u0026gt; \u0026lt;table schema=\u0026#34;\u0026#34; tableName=\u0026#34;t_sb_ycsbzb\u0026#34; \u0026gt;\u0026lt;/table\u0026gt; \u0026lt;table schema=\u0026#34;\u0026#34; tableName=\u0026#34;t_sb_dz\u0026#34; \u0026gt;\u0026lt;/table\u0026gt; \u0026lt;table schema=\u0026#34;\u0026#34; tableName=\u0026#34;t_qx_qxjl\u0026#34; \u0026gt;\u0026lt;/table\u0026gt; \u0026lt;table schema=\u0026#34;\u0026#34; tableName=\u0026#34;t_jx_jxjl\u0026#34; \u0026gt;\u0026lt;/table\u0026gt; \u0026lt;table schema=\u0026#34;\u0026#34; tableName=\u0026#34;t_gz_gzjl\u0026#34; \u0026gt;\u0026lt;/table\u0026gt; \u0026lt;table schema=\u0026#34;\u0026#34; tableName=\u0026#34;t_gj_gjjl\u0026#34; \u0026gt;\u0026lt;/table\u0026gt; \u0026lt;/context\u0026gt; \u0026lt;/generatorConfiguration\u0026gt; 其他插件选择 # better-mybatis-generator、EasyCode\nEclipse逆向生成 # Mybatis Generator # 1、将Mysql的jar文件集成进项目 # 2、安装插件 # ==将插件中两个文件夹中的文件，复制进eclipse安装目录，并重启==\n3、编写generatorConfig.xml文件 # \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE generatorConfiguration PUBLIC \u0026#34;-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN\u0026#34; \u0026#34;http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd\u0026#34; \u0026gt; \u0026lt;generatorConfiguration\u0026gt; \u0026lt;!-- 一个数据库一个context --\u0026gt; \u0026lt;context id=\u0026#34;context1\u0026#34;\u0026gt; \u0026lt;!-- 是否去除自动生成的注释 true：是 ： false:否 --\u0026gt; \u0026lt;commentGenerator\u0026gt; \u0026lt;property name=\u0026#34;suppressDate\u0026#34; value=\u0026#34;true\u0026#34; /\u0026gt; \u0026lt;!-- 是否去除自动生成的注释 true：是 ： false:否 --\u0026gt; \u0026lt;property name=\u0026#34;suppressAllComments\u0026#34; value=\u0026#34;true\u0026#34; /\u0026gt; \u0026lt;/commentGenerator\u0026gt; \u0026lt;!-- jdbc连接 --\u0026gt; \u0026lt;jdbcConnection driverClass=\u0026#34;com.mysql.jdbc.Driver\u0026#34; connectionURL=\u0026#34;jdbc:mysql://localhost:3306/stu?characterEncoding=utf-8\u0026#34; userId=\u0026#34;root\u0026#34; password=\u0026#34;123456\u0026#34;\u0026gt; \u0026lt;/jdbcConnection\u0026gt; \u0026lt;!-- 生成实体类地址 --\u0026gt; \u0026lt;javaModelGenerator targetPackage=\u0026#34;top.ygang.entity\u0026#34; targetProject=\u0026#34;web20210629\u0026#34; \u0026gt; \u0026lt;/javaModelGenerator\u0026gt; \u0026lt;!-- 生成mapxml文件 --\u0026gt; \u0026lt;sqlMapGenerator targetPackage=\u0026#34;top.ygang.mapper\u0026#34; targetProject=\u0026#34;web20210629\u0026#34; \u0026gt; \u0026lt;/sqlMapGenerator\u0026gt; \u0026lt;!-- 生成mapxmldao接口 --\u0026gt; \u0026lt;javaClientGenerator targetPackage=\u0026#34;top.ygang.mapper\u0026#34; targetProject=\u0026#34;web20210629\u0026#34; type=\u0026#34;XMLMAPPER\u0026#34; \u0026gt; \u0026lt;/javaClientGenerator\u0026gt; \u0026lt;!-- 配置表信息 --\u0026gt; \u0026lt;table schema=\u0026#34;\u0026#34; tableName=\u0026#34;student\u0026#34; \u0026gt;\u0026lt;/table\u0026gt; \u0026lt;/context\u0026gt; \u0026lt;/generatorConfiguration\u0026gt; 4、右键生成即可 # ","date":"2023-12-20","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/79c2bc57/fcad070e/","section":"文章","summary":"\u003cp\u003e逆向生成也就是使用插件，根据数据库表、字段、类型自动生成对应的Mybatis的Dao、Mapper、Entity\u003c/p\u003e","title":"3、不同IDE逆向生成","type":"posts"},{"content":" 整合SpringBoot+Axis2 # 1、添加依赖 # \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;project xmlns=\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u0026gt; \u0026lt;modelVersion\u0026gt;4.0.0\u0026lt;/modelVersion\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;axisdemo\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;parent\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-parent\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.2.0.RELEASE\u0026lt;/version\u0026gt; \u0026lt;/parent\u0026gt; \u0026lt;packaging\u0026gt;jar\u0026lt;/packaging\u0026gt; \u0026lt;properties\u0026gt; \u0026lt;maven.compiler.source\u0026gt;8\u0026lt;/maven.compiler.source\u0026gt; \u0026lt;maven.compiler.target\u0026gt;8\u0026lt;/maven.compiler.target\u0026gt; \u0026lt;axis2.version\u0026gt;1.7.8\u0026lt;/axis2.version\u0026gt; \u0026lt;/properties\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;!-- spring-boot-web --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- axis2 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.axis2\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;axis2-spring\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${axis2.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.axis2\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;axis2-transport-http\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${axis2.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.axis2\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;axis2-transport-local\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${axis2.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.axis2\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;axis2-xmlbeans\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${axis2.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;/project\u0026gt; 2、在resources下创建目录以及文件 # ServicePath/services/testService/META-INF/services.xml，其中只有目录testService的目录可变，其余目录都是固定\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;serviceGroup\u0026gt; \u0026lt;!-- name为服务名，scope为生命周期，targetNamespace为命名空间 --\u0026gt; \u0026lt;service name=\u0026#34;testService\u0026#34; scope=\u0026#34;application\u0026#34; targetNamespace=\u0026#34;http://top.ygang.test/service\u0026#34;\u0026gt; \u0026lt;!-- 使用BeanName指定Service类 --\u0026gt; \u0026lt;parameter name=\u0026#34;SpringBeanName\u0026#34;\u0026gt;testService\u0026lt;/parameter\u0026gt; \u0026lt;!-- 使用全类名指定Service类 --\u0026gt; \u0026lt;!-- \u0026lt;parameter name=\u0026#34;ServiceClass\u0026#34;\u0026gt;top.ygang.axisdemo.services.TestService\u0026lt;/parameter\u0026gt;--\u0026gt; \u0026lt;!-- 命名空间 --\u0026gt; \u0026lt;schema schemaNamespace=\u0026#34;http://top.ygang.test/service\u0026#34;/\u0026gt; \u0026lt;!-- 服务描述 --\u0026gt; \u0026lt;description\u0026gt; 测试webservice \u0026lt;/description\u0026gt; \u0026lt;messageReceivers\u0026gt; \u0026lt;messageReceiver mep=\u0026#34;http://www.w3.org/ns/wsdl/in-only\u0026#34; class=\u0026#34;org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver\u0026#34;/\u0026gt; \u0026lt;messageReceiver mep=\u0026#34;http://www.w3.org/ns/wsdl/in-out\u0026#34; class=\u0026#34;org.apache.axis2.rpc.receivers.RPCMessageReceiver\u0026#34;/\u0026gt; \u0026lt;/messageReceivers\u0026gt; \u0026lt;parameter name=\u0026#34;ServiceObjectSupplier\u0026#34;\u0026gt;org.apache.axis2.extensions.spring.receivers.SpringServletContextObjectSupplier\u0026lt;/parameter\u0026gt; \u0026lt;/service\u0026gt; \u0026lt;/serviceGroup\u0026gt; 3、ClasspathCopyUtil # 如果使用jar打包项目，那么Axis2无法读取jar包内的配置文件，所以需要拷贝到jar包的同级目录中\npublic class ClasspathCopyUtil { private static InputStream getResource(String location) throws IOException { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); InputStream in = resolver.getResource(location).getInputStream(); byte[] byteArray = IOUtils.toByteArray(in); in.close(); return new ByteArrayInputStream(byteArray); } /** * 获取项目所在文件夹的绝对路径 * * @return */ private static String getCurrentDirPath() { URL url = FileCopyUtils.class.getProtectionDomain().getCodeSource().getLocation(); String path = url.getPath(); if (path.startsWith(\u0026#34;file:\u0026#34;)) { path = path.replace(\u0026#34;file:\u0026#34;, \u0026#34;\u0026#34;); } if (path.contains(\u0026#34;.jar!/\u0026#34;)) { path = path.substring(0, path.indexOf(\u0026#34;.jar!/\u0026#34;) + 4); } File file = new File(path); path = file.getParentFile().getAbsolutePath(); return path; } private static Path getDistFile(String path) throws IOException { String currentRealPath = getCurrentDirPath(); Path dist = Paths.get(currentRealPath + File.separator + path); Path parent = dist.getParent(); if (parent != null) { Files.createDirectories(parent); } Files.deleteIfExists(dist); return dist; } /** * 复制classpath下的文件到jar包的同级目录下 * * @param location 相对路径文件 * @return * @throws IOException */ public static String copy(String location) throws IOException { InputStream in = getResource(\u0026#34;classpath:\u0026#34; + location); Path dist = getDistFile(location); Files.copy(in, dist); in.close(); return dist.toAbsolutePath().toString(); } } 4、创建Axis配置类 # @Configuration public class AxisWebserviceConfig { @Value(\u0026#34;${webservice.serviceName}\u0026#34;) private String serviceName; @Value(\u0026#34;${webservice.path}\u0026#34;) private String webservicePath; private final static Logger log = LoggerFactory.getLogger(AxisWebserviceConfig.class); @Bean public ServletRegistrationBean\u0026lt;AxisServlet\u0026gt; axisServlet(){ ServletRegistrationBean\u0026lt;AxisServlet\u0026gt; registrationBean = new ServletRegistrationBean\u0026lt;\u0026gt;(); registrationBean.setServlet(new AxisServlet()); registrationBean.addUrlMappings(webservicePath + \u0026#34;/*\u0026#34;); // String path = this.getClass().getResource(\u0026#34;/ServicePath\u0026#34;).getPath().toString(); if(path.toLowerCase().startsWith(\u0026#34;file:\u0026#34;)){ path = path.substring(5); } if(path.indexOf(\u0026#34;!\u0026#34;) != -1){ try{ ClasspathCopyUtil.copy(\u0026#34;ServicePath/services/\u0026#34; + serviceName + \u0026#34;/META-INF/services.xml\u0026#34;); }catch (Exception e){ e.printStackTrace(); } path = path.substring(0, path.lastIndexOf(\u0026#34;/\u0026#34;, path.indexOf(\u0026#34;!\u0026#34;))) + \u0026#34;/ServicePath\u0026#34;; } log.info(\u0026#34;xml配置文件path={}\u0026#34;,\u0026#34;{\u0026#34; + path + \u0026#34;}\u0026#34;); registrationBean.addInitParameter(\u0026#34;axis2.repository.path\u0026#34;, path); registrationBean.setLoadOnStartup(1); return registrationBean; } @Bean public ApplicationContextHolder getApplicationContextHolder(){ return new ApplicationContextHolder(); } } 5、创建Webservice服务类 # @Service(\u0026#34;testService\u0026#34;) public class TestService { public String test(String name){ return \u0026#34;hello axis\u0026#34;; } } 6、创建启动类 # @SpringBootApplication @ServletComponentScan public class Start extends SpringBootServletInitializer { public static void main(String[] args) { SpringApplication.run(Start.class,args); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(Start.class); } } 7、application.yml # server: port: 8080 webservice: # 影响webservice服务url的前缀 path: /services # services.xml目录名称，一般与services.xml中服务名一致 serviceName: testService 8、浏览器进行访问 # 访问地址就是http://ip:port/yml中的path/服务名?wsdl\n即：http://127.0.0.1:8080/services/testService?wsdl\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/578bc70a/68da89e9/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e整合SpringBoot+Axis2 \n    \u003cdiv id=\"整合springbootaxis2\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%95%b4%e5%90%88springbootaxis2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e1、添加依赖 \n    \u003cdiv id=\"1添加依赖\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1%e6%b7%bb%e5%8a%a0%e4%be%9d%e8%b5%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;project\u003c/span\u003e \u003cspan class=\"na\"\u003exmlns=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"na\"\u003exmlns:xsi=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"na\"\u003exsi:schemaLocation=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;modelVersion\u0026gt;\u003c/span\u003e4.0.0\u003cspan class=\"nt\"\u003e\u0026lt;/modelVersion\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003etop.ygang\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003eaxisdemo\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e1.0-SNAPSHOT\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;parent\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.springframework.boot\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003espring-boot-parent\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e2.2.0.RELEASE\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;/parent\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;packaging\u0026gt;\u003c/span\u003ejar\u003cspan class=\"nt\"\u003e\u0026lt;/packaging\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;properties\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;maven.compiler.source\u0026gt;\u003c/span\u003e8\u003cspan class=\"nt\"\u003e\u0026lt;/maven.compiler.source\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;maven.compiler.target\u0026gt;\u003c/span\u003e8\u003cspan class=\"nt\"\u003e\u0026lt;/maven.compiler.target\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;axis2.version\u0026gt;\u003c/span\u003e1.7.8\u003cspan class=\"nt\"\u003e\u0026lt;/axis2.version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;/properties\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;dependencies\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c\"\u003e\u0026lt;!--   spring-boot-web    --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.springframework.boot\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003espring-boot-starter-web\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c\"\u003e\u0026lt;!--   axis2     --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.axis2\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003eaxis2-spring\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e${axis2.version}\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.axis2\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003eaxis2-transport-http\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e${axis2.version}\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.axis2\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003eaxis2-transport-local\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e${axis2.version}\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.axis2\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003eaxis2-xmlbeans\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e${axis2.version}\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;/dependencies\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/project\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e2、在resources下创建目录以及文件 \n    \u003cdiv id=\"2在resources下创建目录以及文件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#2%e5%9c%a8resources%e4%b8%8b%e5%88%9b%e5%bb%ba%e7%9b%ae%e5%bd%95%e4%bb%a5%e5%8f%8a%e6%96%87%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003eServicePath/services/testService/META-INF/services.xml\u003c/code\u003e，其中只有目录\u003ccode\u003etestService\u003c/code\u003e的目录可变，其余目录都是固定\u003c/p\u003e","title":"3、Axis2","type":"posts"},{"content":"Base64是一种用64个字符（26个大写字母、26个小写字母、0-9、’+‘、’/‘）来表示任意二进制数据的方法。\n用记事本打开exe、jpg、pdf这些文件时，我们都会看到一大堆乱码，因为二进制文件包含很多无法显示和打印的字符，所以，如果要让记事本这样的文本处理软件能处理二进制数据，就需要一个二进制到字符串的转换方法。Base64是一种最常见的二进制编码方法\n对二进制数据进行处理，每3个字节一组，一共是3x8=24bit，划为4组，每组正好6个bit\n这样我们得到4个数字作为索引，然后查表，获得相应的4个字符，就是编码后的字符串\n如果要编码的二进制数据不是3的倍数，最后会剩下1个或2个字节怎么办？Base64用\\x00字节在末尾补足后，再在编码的末尾加上1个或2个=号，表示补了多少字节，解码的时候，会自动去掉\n编码与解码 # 字符串 # import base64 msg = \u0026#39;你好\u0026#39; # 将字符串转为bytes msgBytes = msg.encode(\u0026#39;utf8\u0026#39;) base64Result = base64.b64encode(msgBytes) print(\u0026#39;编码后的字节码为： %s\u0026#39; % base64Result) print(\u0026#39;编码后的字符串为： %s\u0026#39; % base64Result.decode(\u0026#39;utf8\u0026#39;)) bytesResult = base64.b64decode(b\u0026#39;5L2g5aW9\u0026#39;) print(\u0026#39;解码后字节码为： %s\u0026#39; % bytesResult) print(\u0026#39;解码后字符串为： %s\u0026#39; % bytesResult.decode(\u0026#39;utf8\u0026#39;)) \u0026#39;\u0026#39;\u0026#39; 编码后的字节码为： b\u0026#39;5L2g5aW9\u0026#39; 编码后的字符串为： 5L2g5aW9 解码后字节码为： b\u0026#39;\\xe4\\xbd\\xa0\\xe5\\xa5\\xbd\u0026#39; 解码后字符串为： 你好 \u0026#39;\u0026#39;\u0026#39; 本地图片 # 编码 # import base64 # 以二进制读的方式打开图片test.jpg with open(\u0026#39;./test.jpg\u0026#39;,\u0026#39;rb\u0026#39;) as file: jpgBytes = file.read() # 将图片的字节码转为base64字节码 base64Bytes = base64.b64encode(jpgBytes) # 将base64字节码转为字符串，并添加图片前缀 data:image/jpg;base64, base64Str = \u0026#39;data:image/jpg;base64,%s\u0026#39; % base64Bytes.decode(\u0026#39;utf8\u0026#39;) # 将base64字符串存入文件1.txt with open(\u0026#39;./1.txt\u0026#39;,\u0026#39;w\u0026#39;) as f: f.write(base64Str) 解码 # import base64 #读取1.txt中的base64字符串 with open(\u0026#39;./1.txt\u0026#39;,\u0026#39;r\u0026#39;) as file: base64Str = file.read() #获取文件类型 suf = base64Str[base64Str.find(\u0026#39;/\u0026#39;) + 1:base64Str.find(\u0026#39;;\u0026#39;)] #截掉base64前缀 data:image/jpg;base64, jpgBytes = base64.b64decode(base64Str[base64Str.find(\u0026#39;,\u0026#39;) + 1:]) #将图片字节码写入二进制文件中 with open(\u0026#39;./test1.%s\u0026#39; % suf,\u0026#39;wb\u0026#39;) as f: f.write(jpgBytes) ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/1b37041b/9766939c/e6f0e512/","section":"文章","summary":"\u003cp\u003eBase64是一种用64个字符\u003ccode\u003e（26个大写字母、26个小写字母、0-9、’+‘、’/‘）\u003c/code\u003e来表示任意二进制数据的方法。\u003c/p\u003e","title":"3、base64","type":"posts"},{"content":"在ES7.0之后，types_name只可以写_doc\n方式一：RestFul访问 # 在浏览器或PostMan中进行访问，注意选择请求的methods\n在地址后添加?pretty可以美化返回的json\n根据id查询一条数据 # methods：Get 语法：http://ip:port/index_name/types_name/id 例子：http://192.168.2.101:9200/student/_doc/1 查询index中所有数据 # methods：Get 语法：http://ip:port/index_name/_search 例子：http://192.168.2.101:9200/student/_search 插入一条数据 # methods：PUT\n语法\nhttp://ip:port/index_name/types_name/id { \u0026#34;key\u0026#34;:\u0026#34;value\u0026#34; } 例子\nPUT http://192.168.2.101:9200/student/_doc/2 { \u0026#34;name\u0026#34;:\u0026#34;lucy\u0026#34;, \u0026#34;age\u0026#34;:18 } 根据id删除一条数据 # methods：Delete 语法：http://ip:port/index_name/types_name/id 例子：http://192.168.2.101:9200/student/_doc/1 根据id修改一条数据 # methods：POST\n语法\nhttp://ip:port/index_name/types_name/id { \u0026#34;key\u0026#34;:\u0026#34;value\u0026#34; } 例子\nPUT http://192.168.2.101:9200/student/_doc/2 { \u0026#34;name\u0026#34;:\u0026#34;tom\u0026#34;, \u0026#34;age\u0026#34;:28 } 方式二：Curl使用 # 在Linux下直接使用命令进行CRUD\n新建索引库 # 语法：curl -XPUT 'http://localhost:9200/index_name' 例子：curl -XPUT 'http://localhost:9200/company' 根据id查询一条数据 # 语法：curl -XGET http://localhost:9200/index_name/types_name/id?pretty 例子：curl -XGET http://localhost:9200/company/employee/3?pretty 插入一条数据 # 语法：curl -XPUT http://localhost:9200/index_name/types_name/id -d '{\u0026quot;key\u0026quot;:“value\u0026quot;}'\n例子：curl -XPUT http://localhost:9200/company/employee/1 -d '{\u0026quot;name\u0026quot;:“lucy\u0026quot;,\u0026quot;age\u0026quot;:18}'\n根据id修改一条数据 # 语法：curl -XPOST http://localhost:9200/index_name/types_name/id -d '{\u0026quot;key\u0026quot;:“value\u0026quot;}\n例子：curl -XPOST http://localhost:9200/company/employee/1 -d '{\u0026quot;name\u0026quot;:“lucy\u0026quot;,\u0026quot;age\u0026quot;:18}'\n根据id删除一条数据 # 语法：curl -XDELETE http://localhost:9200/index_name/types_name/id\n例子：curl -XDELETE http://localhost:9200/company/employee/1\n方式三：使用Kibana（常用） # 准备工作 # 开启监控 # 进入工作台 # 关于ES状态的查询 # #查看所有节点 GET /_cat/nodes #查看es健康状况 GET /_cat/health #查看主节点 GET /_cat/master #查看所有索引 GET /_cat/indices 关于索引的操作 # 创建索引 # PUT index_name # 创建索引时设置字典属性（类型、分词器等） PUT index_name { \u0026#34;mappings\u0026#34;: { \u0026#34;properties\u0026#34;: { \u0026#34;objId\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;text\u0026#34; }, \u0026#34;name\u0026#34;:{ \u0026#34;type\u0026#34;: \u0026#34;text\u0026#34;, \u0026#34;analyzer\u0026#34;: \u0026#34;ik_smart\u0026#34;, \u0026#34;search_analyzer\u0026#34;: \u0026#34;ik_smart\u0026#34; } } } } 查看索引的设置详情 # GET index_name/_settings 删除索引 # DELETE index_name 搜索索引 # 管理索引 # 关于数据的CRUD # 查询index中，所有的数据 # GET /index_name/_search 根据id查询一条数据 # GET /index_name/types_name/id 新增一条数据 # POST /index_name/types_name/id { \u0026#34;key\u0026#34;:\u0026#34;value\u0026#34; } 修改一条数据 # 如果不在后面添加_update的话，那么会先删除原来的数据，然后添加新的数据\nPOST /index_name/types_name/id/_update { \u0026#34;key\u0026#34;:\u0026#34;value\u0026#34; } 删除一条数据 # DELETE /index_name/types_name/id 条件查询 # 按照某一key对应的值排序查询 # GET /index_name/_search { \u0026#34;query\u0026#34;:{ \u0026#34;match_all\u0026#34;:{ } }, \u0026#34;sort\u0026#34;:{ \u0026#34;key\u0026#34;:{ \u0026#34;order\u0026#34;:\u0026#34;desc[降序]|asc[升序]\u0026#34; } } } 按照条件精准查询 # GET /index_name/_search { \u0026#34;query\u0026#34;:{ \u0026#34;match_phrase\u0026#34;:{ \u0026#34;key\u0026#34;:\u0026#34;value\u0026#34; } } } 方式四：SpringBoot集成 # 准备工作 # 1、导入依赖 # 注意springboot版本，过高版本可能导致冲突\n\u0026lt;properties\u0026gt; \u0026lt;elasticsearch.version\u0026gt;7.6.0\u0026lt;/elasticsearch.version\u0026gt; \u0026lt;/properties\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-data-elasticsearch\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、编写配置文件application.yml # spring: #配置elasticsearch elasticsearch: rest: uris: http://192.168.52.128:9200 使用 # 1、声明需要用于映射elsticsearch库的Bean # @Data //@Document用于声明Bean对应的index库名 @Document(indexName = \u0026#34;userinfo\u0026#34;) public class UserInfo implements Serializable { //@Id指定该属性对应ES中的Id @Id private Integer userId; //@Field指定该属性对应ES中的字段，字段名就是属性名，类型是Keyword @Field(type = FieldType.Keyword) private String userName; @Field(type = FieldType.Integer) private Integer userAge; //属性analyzer指定了此属性使用哪种分词器 @Field(type = FieldType.Text,analyzer = \u0026#34;ik_max_word\u0026#34;) private String userMsg; } 2、编写该实体类对应的dao接口 # //需要交给spring容器管理 @Component public interface UserInfoDao extends ElasticsearchRepository\u0026lt;UserInfo,Long\u0026gt; { /* 自定义方法 * 会根据方法名进行查询 * 如果在方法名中声明了多个字段，那么需要将这些字段都列为属性 */ List\u0026lt;UserInfo\u0026gt; findByUserNameOrUserAgeOrUserMsg(String userName,Integer userAge,String userMsg); } 3、编写service接口 # public interface UserInfoService { boolean insert(UserInfo userInfo); List\u0026lt;UserInfo\u0026gt; findAllUserInfo(); UserInfo findById(Integer i); List\u0026lt;UserInfo\u0026gt; findByUserNameOrUserAgeOrUserMsg(String userName,Integer userAge,String userMsg); } 4、编写service实现类 # @Service public class UserInfoServiceImpl implements UserInfoService{ @Resource private UserInfoDao userInfoDao; @Override public boolean insert(UserInfo userInfo) { UserInfo save = userInfoDao.save(userInfo); return save != null; } @Override public List\u0026lt;UserInfo\u0026gt; findAllUserInfo() { Iterable\u0026lt;UserInfo\u0026gt; all = userInfoDao.findAll(); List\u0026lt;UserInfo\u0026gt; userInfos = new ArrayList\u0026lt;\u0026gt;(); for (UserInfo userInfo : all) { userInfos.add(userInfo); } return userInfos; } @Override public UserInfo findById(Integer i) { Optional\u0026lt;UserInfo\u0026gt; byId = userInfoDao.findById(i.longValue()); UserInfo userInfo = byId.get(); return userInfo; } @Override public List\u0026lt;UserInfo\u0026gt; findByUserNameOrUserAgeOrUserMsg(String userName,Integer userAge,String userMsg) { return userInfoDao.findByUserNameOrUserAgeOrUserMsg(userName,userAge,userMsg); } } 5、进行测试 # @SpringBootTest public class MyTest { @Resource private UserInfoService userInfoService; //插入一条信息 @Test public void test1(){ UserInfo userInfo = new UserInfo(); userInfo.setUserId(3); userInfo.setUserAge(18); userInfo.setUserName(\u0026#34;tom\u0026#34;); userInfo.setUserMsg(\u0026#34;这是一为名叫tom的老师\u0026#34;); boolean b = userInfoService.insert(userInfo); } //查询所有 @Test public void test2(){ List\u0026lt;UserInfo\u0026gt; list = userInfoService.findAllUserInfo(); System.out.println(list); } //根据id查询 @Test public void test3(){ UserInfo userInfo = userInfoService.findById(1); System.out.println(userInfo); } //根据名字或年龄或信息查询 @Test public void test4(){ String msg = \u0026#34;学生\u0026#34;; List\u0026lt;UserInfo\u0026gt; list = userInfoService.findByUserNameOrUserAgeOrUserMsg(msg,25,msg); System.out.println(list); } } 自定义接口方法的规则 # 关键字 使用示例 等同于的ES查询 And findByNameAndPrice {“bool” : {“must” : [ {“field” : {“name” : “?”}}, {“field” : {“price” : “?”}} ]}} Or findByNameOrPrice {“bool” : {“should” : [ {“field” : {“name” : “?”}}, {“field” : {“price” : “?”}} ]}} Is findByName {“bool” : {“must” : {“field” : {“name” : “?”}}}} Not findByNameNot {“bool” : {“must_not” : {“field” : {“name” : “?”}}}} Between findByPriceBetween {“bool” : {“must” : {“range” : {“price” : {“from” : ?,”to” : ?,”include_lower” : true,”include_upper” : true}}}}} LessThanEqual findByPriceLessThan {“bool” : {“must” : {“range” : {“price” : {“from” : null,”to” : ?,”include_lower” : true,”include_upper” : true}}}}} GreaterThanEqual findByPriceGreaterThan {“bool” : {“must” : {“range” : {“price” : {“from” : ?,”to” : null,”include_lower” : true,”include_upper” : true}}}}} Before findByPriceBefore {“bool” : {“must” : {“range” : {“price” : {“from” : null,”to” : ?,”include_lower” : true,”include_upper” : true}}}}} After findByPriceAfter {“bool” : {“must” : {“range” : {“price” : {“from” : ?,”to” : null,”include_lower” : true,”include_upper” : true}}}}} Like findByNameLike {“bool” : {“must” : {“field” : {“name” : {“query” : “? *”,”analyze_wildcard” : true}}}}} StartingWith findByNameStartingWith {“bool” : {“must” : {“field” : {“name” : {“query” : “? *”,”analyze_wildcard” : true}}}}} EndingWith findByNameEndingWith {“bool” : {“must” : {“field” : {“name” : {“query” : “*?”,”analyze_wildcard” : true}}}}} Contains/Containing findByNameContaining {“bool” : {“must” : {“field” : {“name” : {“query” : “?”,”analyze_wildcard” : true}}}}} In findByNameIn(Collectionnames) {“bool” : {“must” : {“bool” : {“should” : [ {“field” : {“name” : “?”}}, {“field” : {“name” : “?”}} ]}}}} NotIn findByNameNotIn(Collectionnames) {“bool” : {“must_not” : {“bool” : {“should” : {“field” : {“name” : “?”}}}}}} True findByAvailableTrue {“bool” : {“must” : {“field” : {“available” : true}}}} False findByAvailableFalse {“bool” : {“must” : {“field” : {“available” : false}}}} OrderBy findByAvailableTrueOrderByNameDesc {“sort” : [{ “name” : {“order” : “desc”} }],”bool” : {“must” : {“field” : {“available” : true}}}} ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/d1774b5b/a558b3b4/","section":"文章","summary":"\u003cp\u003e在ES7.0之后，\u003ccode\u003etypes_name\u003c/code\u003e只可以写\u003ccode\u003e_doc\u003c/code\u003e\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e方式一：RestFul访问 \n    \u003cdiv id=\"方式一restful访问\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e5%bc%8f%e4%b8%80restful%e8%ae%bf%e9%97%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在浏览器或PostMan中进行访问，注意选择请求的methods\u003c/p\u003e","title":"3、ES的使用","type":"posts"},{"content":"gopsutil是 Python 工具库psutil 的 Golang 移植版，可以帮助我们方便地获取各种系统和硬件信息。gopsutil为我们屏蔽了各个系统之间的差异，具有非常强悍的可移植性。有了gopsutil，我们不再需要针对不同的系统使用syscall调用对应的系统方法。更棒的是gopsutil的实现中没有任何cgo的代码，使得交叉编译成为可能。\n安装 # go get github.com/shirou/gopsutil gopsutil将不同的功能划分到不同的子包中，想要使用对应的功能，要导入对应的子包。\ncpu：CPU 相关； disk：磁盘相关； docker：docker 相关； host：主机相关； mem：内存相关； net：网络相关； process：进程相关； winservices：Windows 服务相关。 CPU # CPU 的核数有两种，一种是物理核数，一种是逻辑核数。物理核数就是主板上实际有多少个 CPU，一个物理 CPU 上可以有多个核心，这些核心被称为逻辑核。gopsutil中 CPU 相关功能在cpu子包中，cpu子包提供了获取物理和逻辑核数、CPU 使用率的接口。\nCounts(logical bool)：传入false，返回物理核数，传入true，返回逻辑核数； Percent(interval time.Duration, percpu bool)：表示获取interval时间间隔内的 CPU 使用率，percpu为false时，获取总的 CPU 使用率，percpu为true时，分别获取每个 CPU 的使用率，返回一个[]float64类型的值。 Info()：获取cpu详细信息 Times(percpu bool)：获取从开机算起，总 CPU 和 每个单独的 CPU 时间占用情况，函数返回一个TimeStat结构 package main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/shirou/gopsutil/cpu\u0026#34; \u0026#34;time\u0026#34; ) func main() { c, _ := cpu.Counts(false) fmt.Printf(\u0026#34;cpu物理核心数：%d\\n\u0026#34;, c) lgc, _ := cpu.Counts(true) fmt.Printf(\u0026#34;cpu逻辑核心数：%d\\n\u0026#34;, lgc) perUse, _ := cpu.Percent(3*time.Second, true) fmt.Printf(\u0026#34;cpu3秒内使用率分别为：%v\\n\u0026#34;, perUse) use, _ := cpu.Percent(3*time.Second, false) fmt.Printf(\u0026#34;cpu3秒内总使用率为：%f\\n\u0026#34;, use) info, _ := cpu.Info() j, _ := json.MarshalIndent(info, \u0026#34;\u0026#34;, \u0026#34; \u0026#34;) fmt.Println(string(j)) perU, _ := cpu.Times(true) fmt.Printf(\u0026#34;从开机到现在，cpu占用时间分别为：%v\\n\u0026#34;, perU) u, _ := cpu.Times(false) fmt.Printf(\u0026#34;从开机到现在，cpu总占用时间为：%v\\n\u0026#34;, u) } 磁盘 # 子包disk用于获取磁盘信息。disk可获取 IO 统计、分区和使用率信息。\nIOCounters()：返回的 IO 统计信息用map[string]IOCountersStat类型表示。每个分区一个结构，键为分区名，值为统计信息。 disk.Usage(path string)：即可获得路径path所在磁盘的使用情况，返回一个UsageStat结构 package main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/shirou/gopsutil/disk\u0026#34; ) func main() { di, _ := disk.IOCounters() diJ, _ := json.MarshalIndent(di, \u0026#34;\u0026#34;, \u0026#34; \u0026#34;) fmt.Println(\u0026#34;每个盘的IO统计\u0026#34;) fmt.Println(string(diJ)) c, _ := disk.Usage(\u0026#34;C:/\u0026#34;) fmt.Printf(\u0026#34;C盘的使用情况%v\\n\u0026#34;, c) } 主机 # 子包host可以获取主机相关信息，如开机时间、内核版本号、平台信息等等。\nBootTime()：返回主机开机时间的时间戳\nKernelVersion()：返回主机内核版本\nPlatformInformation()：返回主机平台信息\nUsers()：返回终端连接上来的用户信息，每个用户一个UserStat结构\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/shirou/gopsutil/host\u0026#34; \u0026#34;time\u0026#34; ) func main() { bt, _ := host.BootTime() t := time.Unix(int64(bt), 0) str := t.Local().Format(\u0026#34;2006-01-02 15:04:05\u0026#34;) fmt.Printf(\u0026#34;主机开机时间为：%s\\n\u0026#34;, str) version, _ := host.KernelVersion() fmt.Printf(\u0026#34;主机内核版本为：%s\\n\u0026#34;, version) platform, family, version, _ := host.PlatformInformation() fmt.Printf(\u0026#34;平台：%s\\n\u0026#34;, platform) fmt.Printf(\u0026#34;型号：%s\\n\u0026#34;, family) fmt.Printf(\u0026#34;平台版本：%s\\n\u0026#34;, version) users, _ := host.Users() fmt.Printf(\u0026#34;当前连接用户：%v\\n\u0026#34;, users) } 内存 # VirtualMemory()：用来获取内存信息，该函数返回的只是物理内存信息。还可以使用mem.SwapMemory()获取交换内存的信息，信息存储在结构SwapMemoryStat中\npackage main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/shirou/gopsutil/mem\u0026#34; ) func main() { v, _ := mem.VirtualMemory() data, _ := json.MarshalIndent(v, \u0026#34;\u0026#34;, \u0026#34; \u0026#34;) fmt.Println(string(data)) } 进程 # process可用于获取系统当前运行的进程信息，创建新进程，对进程进行一些操作等。\npackage main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/shirou/gopsutil/process\u0026#34; ) func main() { processes, _ := process.Processes() data, _ := json.MarshalIndent(processes, \u0026#34;\u0026#34;, \u0026#34; \u0026#34;) fmt.Println(string(data)) } Windows 服务 # winservices子包可以获取 Windows 系统中的服务信息，内部使用了golang.org/x/sys包，在winservices中，一个服务对应一个Service结构。\n注意，调用winservices.ListServices()返回的Service对象信息是不全的，我们通过NewService()以该服务名称创建一个服务，然后调用GetServiceDetail()方法获取该服务的详细信息。不能直接通过service.GetServiceDetail()来调用，因为ListService()返回的对象缺少必要的系统资源句柄（为了节约资源），调用GetServiceDetail()方法会panic！！！\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/shirou/gopsutil/winservices\u0026#34; ) func main() { services, _ := winservices.ListServices() for _, service := range services { newservice, _ := winservices.NewService(service.Name) newservice.GetServiceDetail() fmt.Println(\u0026#34;Name:\u0026#34;, newservice.Name, \u0026#34;Binary Path:\u0026#34;, newservice.Config.BinaryPathName, \u0026#34;State: \u0026#34;, newservice.Status.State) } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/29ede47b/cadf34ba/","section":"文章","summary":"\u003cp\u003e\u003ccode\u003egopsutil\u003c/code\u003e是 Python 工具库psutil 的 Golang 移植版，可以帮助我们方便地获取各种系统和硬件信息。\u003ccode\u003egopsutil\u003c/code\u003e为我们屏蔽了各个系统之间的差异，具有非常强悍的可移植性。有了\u003ccode\u003egopsutil\u003c/code\u003e，我们不再需要针对不同的系统使用\u003ccode\u003esyscall\u003c/code\u003e调用对应的系统方法。更棒的是\u003ccode\u003egopsutil\u003c/code\u003e的实现中没有任何\u003ccode\u003ecgo\u003c/code\u003e的代码，使得交叉编译成为可能。\u003c/p\u003e","title":"3、gopsutil","type":"posts"},{"content":"布局组件\nGroup group = new Group(); Button btn1 = new Button(\u0026#34;btn1\u0026#34;); Button btn2 = new Button(\u0026#34;btn2\u0026#34;); Button btn3 = new Button(\u0026#34;btn3\u0026#34;); //添加组件 group.getChildren().addAll(btn1,btn2,btn3); //移除指定组件 group.getChildren().remove(btn1); Scene scene = new Scene(group); primaryStage.setScene(scene); //设置透明度（会影响子组件） group.setOpacity(0.5); //检测在指定位置上是否有子组件（只能检测到子组件的左上角点） group.contains(0,0); ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/d9073060/d57b26b2/62c1592b/","section":"文章","summary":"\u003cp\u003e布局组件\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eGroup\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003egroup\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eGroup\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eButton\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebtn1\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eButton\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;btn1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eButton\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebtn2\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eButton\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;btn2\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eButton\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebtn3\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eButton\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;btn3\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//添加组件\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003egroup\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetChildren\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003eaddAll\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebtn1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ebtn2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ebtn3\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//移除指定组件\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003egroup\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetChildren\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003eremove\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebtn1\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eScene\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003escene\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eScene\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003egroup\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eprimaryStage\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetScene\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003escene\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//设置透明度（会影响子组件）\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003egroup\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetOpacity\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//检测在指定位置上是否有子组件（只能检测到子组件的左上角点）\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003egroup\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003econtains\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"3、Group","type":"posts"},{"content":"用于在java程序中，解析读取excel文档\n依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.poi\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;poi\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.14\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.poi\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;poi-ooxml\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.14\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.poi\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;poi-ooxml-schemas\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.14\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 工具类 # public class POIUtil { //读取数据 public static List\u0026lt;List\u0026lt;String\u0026gt;\u0026gt; read(String excelPath,int sheetNum){ //根据文件后缀，创建不同的电子表格对象 Workbook workbook = null; File file = new File(excelPath); InputStream input; try { input = new FileInputStream(file); if(excelPath.endsWith(\u0026#34;xlsx\u0026#34;)){ //创建 Excel 2007 电子表格对象 workbook = new XSSFWorkbook(input); }else{ //创建 Excel 2003 工作簿对象 workbook = new HSSFWorkbook(input); } } catch (Exception e) { e.printStackTrace(); } return parse(workbook,sheetNum); } private static List\u0026lt;List\u0026lt;String\u0026gt;\u0026gt; parse(Workbook workbook,int sheetNum){ //获取当前的工作簿 Sheet sheet = workbook.getSheetAt(sheetNum); List\u0026lt;List\u0026lt;String\u0026gt;\u0026gt; excelList = new ArrayList\u0026lt;\u0026gt;(); //遍历表格所有行 for(int i = 0;i \u0026lt;= sheet.getLastRowNum();i++){ //获取行对象 Row row = sheet.getRow(i); //如果当前行为空，则开始下一行循环 if(row == null){ continue; } List\u0026lt;String\u0026gt; rowList = new ArrayList\u0026lt;\u0026gt;(); //遍历行中的所有列 for(int j = 0;j \u0026lt; row.getLastCellNum();j++){ //获取列对象 Cell cell = row.getCell(j); String cellStr = \u0026#34;\u0026#34;; //判断当前列数据类型 if(cell == null){ cellStr = \u0026#34;\u0026#34;; }else if(cell.getCellType() == Cell.CELL_TYPE_BOOLEAN){ //布尔类型 cellStr = String.valueOf(cell.getBooleanCellValue()); }else if(cell.getCellType() == Cell.CELL_TYPE_NUMERIC){ //数值类型 cellStr = (int)cell.getNumericCellValue() + \u0026#34;\u0026#34;; }else if(cell.getCellType() == Cell.CELL_TYPE_FORMULA){ //公式类型 cellStr = cell.getCellFormula(); }else{ //其余按字符串处理 cellStr = cell.getStringCellValue(); } rowList.add(cellStr); } excelList.add(rowList); } return excelList; } //写入到excel public static String write(String[] title, String sheetName, List\u0026lt;List\u0026lt;String\u0026gt;\u0026gt; data,String excelPath){ //创建Excel 2007 电子表格对象 Workbook wb = new XSSFWorkbook(); //设置sheet名称，并创建新的sheet对象 Sheet sheet = wb.createSheet(sheetName); //获取表头行 Row titleRow = sheet.createRow(0); //创建单元格，设置style居中，字体，单元格大小等 CellStyle style = wb.createCellStyle(); Cell cell = null; //把已经写好的标题行写入excel文件中 for (int i = 0; i \u0026lt; title.length; i++) { cell = titleRow.createCell(i); cell.setCellValue(title[i]); cell.setCellStyle(style); } //将数据写入excel Row row = null; for (int i = 0;i \u0026lt; data.size();i++){ row = sheet.createRow(i + 1); List\u0026lt;String\u0026gt; dataList = data.get(i); for (int j = 0;j \u0026lt; dataList.size();j++){ row.createCell(j).setCellValue(dataList.get(j)); } } //设置单元格宽度自适应，在此基础上把宽度调至1.5倍 for (int i = 0; i \u0026lt; title.length; i++) { sheet.autoSizeColumn(i, true); //注意单个单元格的最大列宽是255个字符 //sheet.setColumnWidth(i, sheet.getColumnWidth(i) * 15 / 10); } //创建上传文件目录 File folder = new File(excelPath); //如果文件夹不存在创建对应的文件夹 if (!folder.exists()) { folder.mkdirs(); } //设置文件名 Date date = new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat(\u0026#34;yyyyMMddhhmmss\u0026#34;); String format = dateFormat.format(date); String fileName = format + sheetName + \u0026#34;.xlsx\u0026#34;; String savePath = folder + File.separator + fileName; // System.out.println(savePath); try { OutputStream fileOut = new FileOutputStream(savePath); wb.write(fileOut); fileOut.close(); } catch (Exception e) { e.printStackTrace(); } //返回文件保存全路径 return savePath; } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/0f3856f3/86c18366/","section":"文章","summary":"\u003cp\u003e用于在java程序中，解析读取excel文档\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e依赖 \n    \u003cdiv id=\"依赖\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%be%9d%e8%b5%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.poi\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003epoi\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e3.14\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.poi\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003epoi-ooxml\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e3.14\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.apache.poi\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003epoi-ooxml-schemas\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e3.14\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e工具类 \n    \u003cdiv id=\"工具类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b7%a5%e5%85%b7%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003ePOIUtil\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"c1\"\u003e//读取数据\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eread\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eexcelPath\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esheetNum\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//根据文件后缀，创建不同的电子表格对象\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eWorkbook\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eworkbook\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eexcelPath\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eInputStream\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003einput\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003einput\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFileInputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eexcelPath\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eendsWith\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;xlsx\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"c1\"\u003e//创建 Excel 2007 电子表格对象\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eworkbook\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eXSSFWorkbook\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einput\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"c1\"\u003e//创建 Excel 2003 工作簿对象\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eworkbook\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eHSSFWorkbook\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einput\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ecatch\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintStackTrace\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eparse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eworkbook\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003esheetNum\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eparse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eWorkbook\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eworkbook\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esheetNum\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取当前的工作簿\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSheet\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esheet\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eworkbook\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetSheetAt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esheetNum\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eexcelList\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eArrayList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//遍历表格所有行\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esheet\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetLastRowNum\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取行对象\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eRow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esheet\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetRow\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e//如果当前行为空，则开始下一行循环\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003econtinue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erowList\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eArrayList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e//遍历行中的所有列\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetLastCellNum\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取列对象\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eCell\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetCell\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecellStr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"c1\"\u003e//判断当前列数据类型\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003ecellStr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetCellType\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eCell\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eCELL_TYPE_BOOLEAN\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//布尔类型\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003ecellStr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003evalueOf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetBooleanCellValue\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetCellType\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eCell\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eCELL_TYPE_NUMERIC\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//数值类型\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003ecellStr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetNumericCellValue\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetCellType\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eCell\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eCELL_TYPE_FORMULA\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//公式类型\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003ecellStr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetCellFormula\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//其余按字符串处理\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003ecellStr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetStringCellValue\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003erowList\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecellStr\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eexcelList\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erowList\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eexcelList\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//写入到excel\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003ewrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esheetName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eexcelPath\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//创建Excel 2007 电子表格对象\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eWorkbook\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ewb\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eXSSFWorkbook\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//设置sheet名称，并创建新的sheet对象\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSheet\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esheet\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ewb\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecreateSheet\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esheetName\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取表头行\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eRow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etitleRow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esheet\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecreateRow\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//创建单元格，设置style居中，字体，单元格大小等\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eCellStyle\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estyle\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ewb\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecreateCellStyle\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eCell\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//把已经写好的标题行写入excel文件中\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etitleRow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecreateCell\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetCellValue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetCellStyle\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estyle\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//将数据写入excel\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eRow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esize\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esheet\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecreateRow\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edataList\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edataList\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esize\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecreateCell\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"na\"\u003esetCellValue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edataList\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//设置单元格宽度自适应，在此基础上把宽度调至1.5倍\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003esheet\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eautoSizeColumn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e//注意单个单元格的最大列宽是255个字符\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e//sheet.setColumnWidth(i, sheet.getColumnWidth(i) * 15 / 10);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//创建上传文件目录\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efolder\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eexcelPath\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//如果文件夹不存在创建对应的文件夹\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"n\"\u003efolder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eexists\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003efolder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003emkdirs\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//设置文件名\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eDate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eDate\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSimpleDateFormat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edateFormat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eSimpleDateFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;yyyyMMddhhmmss\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eformat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edateFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eformat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edate\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efileName\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eformat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esheetName\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;.xlsx\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esavePath\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efolder\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eseparator\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efileName\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// System.out.println(savePath);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eOutputStream\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efileOut\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFileOutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esavePath\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003ewb\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ewrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efileOut\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003efileOut\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ecatch\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintStackTrace\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//返回文件保存全路径\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esavePath\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"3、POI","type":"posts"},{"content":" POM # POM（项目对象模型）是 Maven 最基本，也是非常重要的一个概念。通常情况下，我们可以看到 POM 的表现形式是pom.xml，在这个 XML 文件中定义着关于我们工程的方方面面，当我们想要通过 Maven 命令来进行操作的时候，例如：编译，打包等等，Maven 都会从pom.xml文件中来读取工程相关的信息。\npom.xml结构 # 所有的标签总共分为四大类：基本配置、项目信息配置、环境配置、构建配置\n\u0026lt;project xmlns=\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u0026gt; \u0026lt;modelVersion\u0026gt;4.0.0\u0026lt;/modelVersion\u0026gt; \u0026lt;!-- 基本配置 --\u0026gt; \u0026lt;groupId\u0026gt;...\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;...\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;...\u0026lt;/version\u0026gt; \u0026lt;packaging\u0026gt;...\u0026lt;/packaging\u0026gt; \u0026lt;dependencies\u0026gt;...\u0026lt;/dependencies\u0026gt; \u0026lt;parent\u0026gt;...\u0026lt;/parent\u0026gt; \u0026lt;dependencyManagement\u0026gt;...\u0026lt;/dependencyManagement\u0026gt; \u0026lt;modules\u0026gt;...\u0026lt;/modules\u0026gt; \u0026lt;properties\u0026gt;...\u0026lt;/properties\u0026gt; \u0026lt;!-- 项目信息配置 --\u0026gt; \u0026lt;name\u0026gt;...\u0026lt;/name\u0026gt; \u0026lt;description\u0026gt;...\u0026lt;/description\u0026gt; \u0026lt;url\u0026gt;...\u0026lt;/url\u0026gt; \u0026lt;inceptionYear\u0026gt;...\u0026lt;/inceptionYear\u0026gt; \u0026lt;licenses\u0026gt;...\u0026lt;/licenses\u0026gt; \u0026lt;organization\u0026gt;...\u0026lt;/organization\u0026gt; \u0026lt;developers\u0026gt;...\u0026lt;/developers\u0026gt; \u0026lt;contributors\u0026gt;...\u0026lt;/contributors\u0026gt; \u0026lt;!-- 环境配置 --\u0026gt; \u0026lt;issueManagement\u0026gt;...\u0026lt;/issueManagement\u0026gt; \u0026lt;ciManagement\u0026gt;...\u0026lt;/ciManagement\u0026gt; \u0026lt;mailingLists\u0026gt;...\u0026lt;/mailingLists\u0026gt; \u0026lt;scm\u0026gt;...\u0026lt;/scm\u0026gt; \u0026lt;prerequisites\u0026gt;...\u0026lt;/prerequisites\u0026gt; \u0026lt;repositories\u0026gt;...\u0026lt;/repositories\u0026gt; \u0026lt;pluginRepositories\u0026gt;...\u0026lt;/pluginRepositories\u0026gt; \u0026lt;distributionManagement\u0026gt;...\u0026lt;/distributionManagement\u0026gt; \u0026lt;profiles\u0026gt;...\u0026lt;/profiles\u0026gt; \u0026lt;!-- 构建配置 --\u0026gt; \u0026lt;build\u0026gt;...\u0026lt;/build\u0026gt; \u0026lt;reporting\u0026gt;...\u0026lt;/reporting\u0026gt; \u0026lt;/project\u0026gt; 基本配置 # project # 是 pom.xml中描述符的根\n\u0026lt;project xmlns=\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u0026gt; \u0026lt;/project\u0026gt; modelVersion # 指定pom.xml符合哪个版本的描述符。maven 2 和 3 只能为4.0.0。\n\u0026lt;modelVersion\u0026gt;4.0.0\u0026lt;/modelVersion\u0026gt; Maven坐标 # groupId：团体、组织的标识符。团体标识的约定是：它以创建这个项目的组织名称的逆向域名(reverse domain name)开头，一般对应着 java 的包结构。 artifactId：单独项目的唯一标识符。比如我们的 tomcat、commons 等。不要在artifactId中包含点号.。 version：项目的特定版本，maven 在版本管理时候可以使用几个特殊的字符串SNAPSHOT、LATEST、RELEASE，例如：1.0-SNAPSHOT SNAPSHOT：这个版本一般用于开发过程中，表示不稳定的版本。 LATEST：指某个特定构件的最新发布，这个发布可能是一个发布版，也可能是一个snapshot版，具体看哪个时间最后。 RELEASE：指最后一个发布版。 \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;demo\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0\u0026lt;/version\u0026gt; packaging # 项目的类型，描述了项目打包后的输出，默认是jar，常见的输出类型如下：pom、jar、maven-plugin、ejb、war、ear、rar、par\n\u0026lt;packaging\u0026gt;jar\u0026lt;/packaging\u0026gt; dependencies # 指定项目的所有依赖结构\n\u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-embedder\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0\u0026lt;/version\u0026gt; \u0026lt;type\u0026gt;jar\u0026lt;/type\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;optional\u0026gt;true\u0026lt;/optional\u0026gt; \u0026lt;exclusions\u0026gt; \u0026lt;exclusion\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-core\u0026lt;/artifactId\u0026gt; \u0026lt;/exclusion\u0026gt; \u0026lt;/exclusions\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;myjar\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;system\u0026lt;/scope\u0026gt; \u0026lt;systemPath\u0026gt;d:/myjar.jar\u0026lt;/systemPath\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; dependency：指定了单个依赖项的坐标\ngroupId、artifactId、version：指定了依赖的坐标 type：对应 packaging 的类型，如果不使用 type 标签，maven 默认为 jar。 scope：指定了依赖的使用范围，以及依赖的传递性，共有5种可用的限定范围 compile：如果没有指定 scope 标签，maven 默认为这个范围。编译依赖关系在所有 classpath 中都可用。此外，这些依赖关系被传播到依赖项目。 provided：与 compile 类似，但是表示您希望 jdk 或容器在运行时提供它。它只适用于编译和测试 classpath，不可传递。 runtime：表示编译不需要依赖关系，而是用于执行。它是在运行时和测试 classpath，但不是编译 classpath。 test：此范围表示正常使用应用程序不需要依赖关系，仅适用于测试编译和执行阶段。它不是传递的。 system：此范围与 provided 类似，除了您必须提供明确包含它的 jar。该artifact 始终可用，并且不是在仓库中查找。 systemPath：仅当scope为system才使用。否则，如果设置此元素，构建将失败。该路径必须是绝对路径，因此建议使用 propertie 来指定特定的路径。maven 将不会检查项目的仓库，而是检查库文件是否存在。如果没有，maven 将会失败。 optional：设置此依赖项为可选，如果子项目存在该依赖项，则不会传递该依赖给子项目，避免了依赖冲突 exclusions：指定一个或多个排除需要排除的传递而来的依赖，每个排除依赖都包含 groupId 和 artifactId。 parent # maven 支持继承功能。子 POM 可以使用 parent 指定父 POM ，然后继承其配置。\n\u0026lt;parent\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;my-parent\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0\u0026lt;/version\u0026gt; \u0026lt;relativePath\u0026gt;../my-parent\u0026lt;/relativePath\u0026gt; \u0026lt;/parent\u0026gt; relativePath：在搜索本地和远程存储库之前，它不是必需的，但可以用作 maven 的指示符，优先搜索给定该项目父级的路径。 dependencyManagement # \u0026lt;dependencyManagement\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-embedder\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;/dependencyManagement\u0026gt; 表示依赖 jar 包的声明。即你在项目中的 dependencyManagement 下声明了依赖，maven 不会加载该依赖，dependencyManagement 声明可以被子 POM 继承。\n常见的使用方式为，父项目声明\u0026lt;packaging\u0026gt;pom\u0026lt;/packaging\u0026gt;，并且使用dependencyManagement来指定依赖以及版本；子项目只需要使用groupId 和 artifactId来指定依赖，而依赖版本会继承父项目指定的版本。\ndependencyManagement 主要是为了统一管理依赖包的版本，确保所有子项目使用的版本一致，类似的还有plugins和pluginManagement。\ndependencyManagement中可以写一个特殊的scope：\u0026lt;scope\u0026gt;import\u0026lt;/scope\u0026gt;，作用是如果当前的\u0026lt;parent\u0026gt;继承一个不够，那么可以使用这个标签在dependencyManagement继承多个。\nproperties # 属性列表。定义的属性可以在pom.xml文件中任意处使用。使用方式为 ${propertie} 。\n\u0026lt;!-- 常用属性 --\u0026gt; \u0026lt;properties\u0026gt; \u0026lt;!-- 指定maven构建项目时使用的编码，防止乱码 --\u0026gt; \u0026lt;project.build.sourceEncoding\u0026gt;UTF-8\u0026lt;/project.build.sourceEncoding\u0026gt; \u0026lt;!-- 编译代码使用的jdk版本 --\u0026gt; \u0026lt;maven.compiler.source\u0026gt;8\u0026lt;/maven.compiler.source\u0026gt; \u0026lt;!-- 运行代码使用的jdk版本 --\u0026gt; \u0026lt;maven.compiler.target\u0026gt;8\u0026lt;/maven.compiler.target\u0026gt; \u0026lt;/properties\u0026gt; 内置属性\n${project.build.sourceDirectory}：项目的主源码目录，默认为 src/main/java\n${project.build.testSourceDirectory}：项目的测试源码目录，默认为 src/test/java\n${project.build.directory}：项目构件输出目录，默认为 target/\n${project.outputDirectory}：项目主代码编译输出目录，默认为 target/classes/\n${project.testOutputDirectory}：项目测试代码编译输出目录，默认为 target/test-classes/\n${project.groupId}：项目的 groupId\n${project.artifactId}：项目的 artifactId\n${project.version}：项目的 version，与${version}等价\n${project.build.fianlName}：项目打包输出文件的名称。默认为${project.artifactId}-${project.version}\n${project.basedir}：pom.xml所在的目录，与${basedir}等价\n项目信息配置 # 项目信息相关的这部分标签都不是必要的，也就是说完全可以不填写。\n\u0026lt;project xmlns=\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u0026gt; \u0026lt;!--项目名--\u0026gt; \u0026lt;name\u0026gt;maven-demo\u0026lt;/name\u0026gt; \u0026lt;!--项目描述--\u0026gt; \u0026lt;description\u0026gt;maven示例\u0026lt;/description\u0026gt; \u0026lt;!--项目url--\u0026gt; \u0026lt;url\u0026gt;https://github.com/gradyyoung/maven-demo\u0026lt;/url\u0026gt; \u0026lt;!--项目开发年份--\u0026gt; \u0026lt;inceptionYear\u0026gt;2019\u0026lt;/inceptionYear\u0026gt; \u0026lt;!--开源协议--\u0026gt; \u0026lt;licenses\u0026gt; \u0026lt;license\u0026gt; \u0026lt;name\u0026gt;Apache License, Version 2.0\u0026lt;/name\u0026gt; \u0026lt;url\u0026gt;https://www.apache.org/licenses/LICENSE-2.0.txt\u0026lt;/url\u0026gt; \u0026lt;distribution\u0026gt;repo\u0026lt;/distribution\u0026gt; \u0026lt;comments\u0026gt;A business-friendly OSS license\u0026lt;/comments\u0026gt; \u0026lt;/license\u0026gt; \u0026lt;/licenses\u0026gt; \u0026lt;!--组织信息(如公司、开源组织等)--\u0026gt; \u0026lt;organization\u0026gt; \u0026lt;name\u0026gt;...\u0026lt;/name\u0026gt; \u0026lt;url\u0026gt;...\u0026lt;/url\u0026gt; \u0026lt;/organization\u0026gt; \u0026lt;!--开发者列表--\u0026gt; \u0026lt;developers\u0026gt; \u0026lt;developer\u0026gt; \u0026lt;id\u0026gt;1\u0026lt;/id\u0026gt; \u0026lt;name\u0026gt;ygang1\u0026lt;/name\u0026gt; \u0026lt;email\u0026gt;example@email.com\u0026lt;/email\u0026gt; \u0026lt;url\u0026gt;https://www.ygang.top/\u0026lt;/url\u0026gt; \u0026lt;organization\u0026gt;...\u0026lt;/organization\u0026gt; \u0026lt;organizationUrl\u0026gt;...\u0026lt;/organizationUrl\u0026gt; \u0026lt;roles\u0026gt; \u0026lt;role\u0026gt;architect\u0026lt;/role\u0026gt; \u0026lt;role\u0026gt;developer\u0026lt;/role\u0026gt; \u0026lt;/roles\u0026gt; \u0026lt;timezone\u0026gt;+8\u0026lt;/timezone\u0026gt; \u0026lt;properties\u0026gt;...\u0026lt;/properties\u0026gt; \u0026lt;/developer\u0026gt; \u0026lt;/developers\u0026gt; \u0026lt;!--代码贡献者列表--\u0026gt; \u0026lt;contributors\u0026gt; \u0026lt;contributor\u0026gt; \u0026lt;id\u0026gt;1\u0026lt;/id\u0026gt; \u0026lt;name\u0026gt;ygang2\u0026lt;/name\u0026gt; \u0026lt;email\u0026gt;example@email.com\u0026lt;/email\u0026gt; \u0026lt;url\u0026gt;https://www.ygang.top/\u0026lt;/url\u0026gt; \u0026lt;organization\u0026gt;...\u0026lt;/organization\u0026gt; \u0026lt;organizationUrl\u0026gt;...\u0026lt;/organizationUrl\u0026gt; \u0026lt;roles\u0026gt; \u0026lt;role\u0026gt;architect\u0026lt;/role\u0026gt; \u0026lt;role\u0026gt;developer\u0026lt;/role\u0026gt; \u0026lt;/roles\u0026gt; \u0026lt;timezone\u0026gt;+8\u0026lt;/timezone\u0026gt; \u0026lt;properties\u0026gt;...\u0026lt;/properties\u0026gt; \u0026lt;/contributor\u0026gt; \u0026lt;/contributors\u0026gt; \u0026lt;/project\u0026gt; 环境配置 # issueManagement # 这定义了所使用的缺陷跟踪系统（Bugzilla，TestTrack，ClearQuest 等）。虽然没有什么可以阻止插件使用这些信息的东西，但它主要用于生成项目文档。\n\u0026lt;issueManagement\u0026gt; \u0026lt;system\u0026gt;Bugzilla\u0026lt;/system\u0026gt; \u0026lt;url\u0026gt;http://127.0.0.1/bugzilla/\u0026lt;/url\u0026gt; \u0026lt;/issueManagement\u0026gt; ciManagement # CI 构建系统配置，主要是指定通知机制以及被通知的邮箱。\n\u0026lt;ciManagement\u0026gt; \u0026lt;system\u0026gt;continuum\u0026lt;/system\u0026gt; \u0026lt;url\u0026gt;http://127.0.0.1:8080/continuum\u0026lt;/url\u0026gt; \u0026lt;notifiers\u0026gt; \u0026lt;notifier\u0026gt; \u0026lt;type\u0026gt;mail\u0026lt;/type\u0026gt; \u0026lt;sendOnError\u0026gt;true\u0026lt;/sendOnError\u0026gt; \u0026lt;sendOnFailure\u0026gt;true\u0026lt;/sendOnFailure\u0026gt; \u0026lt;sendOnSuccess\u0026gt;false\u0026lt;/sendOnSuccess\u0026gt; \u0026lt;sendOnWarning\u0026gt;false\u0026lt;/sendOnWarning\u0026gt; \u0026lt;configuration\u0026gt;\u0026lt;address\u0026gt;continuum@127.0.0.1\u0026lt;/address\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/notifier\u0026gt; \u0026lt;/notifiers\u0026gt; \u0026lt;/ciManagement\u0026gt; repositories # repositories 是遵循 Maven 存储库目录布局的 artifacts 集合。默认的 Maven 中央存储库位于https://repo.maven.apache.org/maven2/上。\n\u0026lt;repositories\u0026gt; \u0026lt;repository\u0026gt; \u0026lt;releases\u0026gt; \u0026lt;enabled\u0026gt;false\u0026lt;/enabled\u0026gt; \u0026lt;updatePolicy\u0026gt;always\u0026lt;/updatePolicy\u0026gt; \u0026lt;checksumPolicy\u0026gt;warn\u0026lt;/checksumPolicy\u0026gt; \u0026lt;/releases\u0026gt; \u0026lt;snapshots\u0026gt; \u0026lt;enabled\u0026gt;true\u0026lt;/enabled\u0026gt; \u0026lt;updatePolicy\u0026gt;never\u0026lt;/updatePolicy\u0026gt; \u0026lt;checksumPolicy\u0026gt;fail\u0026lt;/checksumPolicy\u0026gt; \u0026lt;/snapshots\u0026gt; \u0026lt;id\u0026gt;codehausSnapshots\u0026lt;/id\u0026gt; \u0026lt;name\u0026gt;Codehaus Snapshots\u0026lt;/name\u0026gt; \u0026lt;url\u0026gt;http://snapshots.maven.codehaus.org/maven2\u0026lt;/url\u0026gt; \u0026lt;layout\u0026gt;default\u0026lt;/layout\u0026gt; \u0026lt;/repository\u0026gt; \u0026lt;/repositories\u0026gt; pluginRepositories # 与 repositories 差不多。\nprofiles # 和settings.xml中的profiles作用一样，但是子标签除了id、activation、repositories、pluginRepositories 和 properties这几个基本的以外，还有类似于dependencies，build等pom.xml标签，可以更加全面的分类配置项目。\n\u0026lt;profiles\u0026gt; \u0026lt;profile\u0026gt; \u0026lt;id\u0026gt;p1\u0026lt;/id\u0026gt; \u0026lt;activation\u0026gt; \u0026lt;activeByDefault\u0026gt;true\u0026lt;/activeByDefault\u0026gt; \u0026lt;/activation\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;mysql\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mysql-connector-java\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;8.0.31\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;/profile\u0026gt; \u0026lt;profile\u0026gt; \u0026lt;id\u0026gt;p2\u0026lt;/id\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;mysql\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mysql-connector-java\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;5.1.46\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;/profile\u0026gt; \u0026lt;/profiles\u0026gt; 激活profile的方式 # 方式一：通过默认\u0026lt;activeByDefault\u0026gt;true\u0026lt;/activeByDefault\u0026gt;激活，优先级最低，一旦使用方式二三，则此方式失效 方式二：通过mvn [command] -P profileId激活，例如mvn package -P p2 方式三：通过activation标签中的各种条件激活 构建配置 # build # 项目的构建配置\n\u0026lt;build\u0026gt; \u0026lt;defaultGoal\u0026gt;install\u0026lt;/defaultGoal\u0026gt; \u0026lt;directory\u0026gt;${basedir}/target\u0026lt;/directory\u0026gt; \u0026lt;finalName\u0026gt;${artifactId}-${version}\u0026lt;/finalName\u0026gt; \u0026lt;resources\u0026gt; \u0026lt;resource\u0026gt; \u0026lt;targetPath\u0026gt;META-INF/plexus\u0026lt;/targetPath\u0026gt; \u0026lt;filtering\u0026gt;false\u0026lt;/filtering\u0026gt; \u0026lt;directory\u0026gt;${basedir}/src/main/plexus\u0026lt;/directory\u0026gt; \u0026lt;includes\u0026gt; \u0026lt;include\u0026gt;configuration.xml\u0026lt;/include\u0026gt; \u0026lt;/includes\u0026gt; \u0026lt;excludes\u0026gt; \u0026lt;exclude\u0026gt;**/*.properties\u0026lt;/exclude\u0026gt; \u0026lt;/excludes\u0026gt; \u0026lt;/resource\u0026gt; \u0026lt;/resources\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven.plugins\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-jar-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.6\u0026lt;/version\u0026gt; \u0026lt;extensions\u0026gt;false\u0026lt;/extensions\u0026gt; \u0026lt;inherited\u0026gt;true\u0026lt;/inherited\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;classifier\u0026gt;test\u0026lt;/classifier\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;dependencies\u0026gt;...\u0026lt;/dependencies\u0026gt; \u0026lt;executions\u0026gt;...\u0026lt;/executions\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/build\u0026gt; defaultGoal：执行构建时默认的goal或phase，如jar:jar或者package等 directory：构建的结果所在的路径，默认为${basedir}/target目录 finalName：构建的最终结果的名字，默认为${artifactId}-${version}，该名字可能在其他plugin中被改变 resources：资源的配置。资源文件通常不是代码，不需要编译，而是在项目需要捆绑使用的内容。 targetPath： 指定从构建中放置资源集的目录结构。目标路径默认为基本目录。将要包装在jar中的资源的通常指定的目标路径是META-INF。 filtering：构建过程中是否对资源进行过滤，默认false directory：资源文件的路径，默认位于${basedir}/src/main/resources/目录下 includes：一组文件名的匹配模式，被匹配的资源文件将被构建过程处理 excludes：一组文件名的匹配模式，被匹配的资源文件将被构建过程忽略。同时被includes和excludes匹配的资源文件，将被忽略。 plugins：构建过程中所用到的插件 groupId、artifactId、version ：和基本配置中的 groupId、artifactId、version 意义相同。 extensions：是否加载该插件的扩展，默认false inherited：该插件的configuration中的配置是否可以被继承，默认true configuration：该插件所需要的特殊配置，在父子项目之间可以覆盖或合并 dependencies：插件本身所需要的依赖 executions：该插件的某个goal（一个插件中可能包含多个goal）的执行方式。 id：唯一标识 goals：要执行的插件的goal（可以有多个），如\u0026lt;goal\u0026gt;run\u0026lt;/goal\u0026gt; phase：插件的goal要嵌入到Maven的phase中执行，如verify inherited：该execution是否可被子项目继承 configuration：该execution的其他配置参数 reporting # 包含特定针对 site 生成阶段的元素。某些 maven 插件可以生成 reporting 元素下配置的报告，例如：生成 javadoc 报告。reporting 与 build 元素配置插件的能力相似。明显的区别在于：在执行块中插件目标的控制不是细粒度的，报表通过配置 reportSet 元素来精细控制。而微妙的区别在于 reporting 元素下的 configuration 元素可以用作 build 下的 configuration ，尽管相反的情况并非如此（ build 下的 configuration 不影响 reporting 元素下的 configuration ）。\n另一个区别就是 plugin 下的 outputDirectory 元素。在报告的情况下，默认输出目录为 ${basedir}/target/site。\n\u0026lt;reporting\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;plugin\u0026gt; ... \u0026lt;reportSets\u0026gt; \u0026lt;reportSet\u0026gt; \u0026lt;id\u0026gt;sunlink\u0026lt;/id\u0026gt; \u0026lt;reports\u0026gt; \u0026lt;report\u0026gt;javadoc\u0026lt;/report\u0026gt; \u0026lt;/reports\u0026gt; \u0026lt;inherited\u0026gt;true\u0026lt;/inherited\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;links\u0026gt; \u0026lt;link\u0026gt;http://java.sun.com/j2se/1.5.0/docs/api/\u0026lt;/link\u0026gt; \u0026lt;/links\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/reportSet\u0026gt; \u0026lt;/reportSets\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/reporting\u0026gt; ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/4ee304f3/3d749d7c/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003ePOM \n    \u003cdiv id=\"pom\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#pom\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003ePOM（项目对象模型）是 Maven 最基本，也是非常重要的一个概念。通常情况下，我们可以看到 POM 的表现形式是\u003ccode\u003epom.xml\u003c/code\u003e，在这个 XML 文件中定义着关于我们工程的方方面面，当我们想要通过 Maven 命令来进行操作的时候，例如：编译，打包等等，Maven 都会从\u003ccode\u003epom.xml\u003c/code\u003e文件中来读取工程相关的信息。\u003c/p\u003e","title":"3、pom.xml","type":"posts"},{"content":"requests是使用Apache2 licensed 许可证的HTTP库。\n用python编写，比urllib2模块更简洁，本质就是封装了urllib3。\nRequest支持HTTP连接保持和连接池，支持使用cookie保持会话，支持文件上传，支持自动响应内容的编码，支持国际化的URL和POST数据自动编码。\n在python内置模块的基础上进行了高度的封装，从而使得python进行网络请求时，变得人性化，使用Requests可以轻而易举的完成浏览器可有的任何操作。\nrequests会自动实现持久连接keep-alive\n安装 # pip install requests 使用 # requests.get(‘https://github.com/timeline.json’) # GET请求 requests.post(“http://httpbin.org/post”) # POST请求 requests.put(“http://httpbin.org/put”) # PUT请求 requests.delete(“http://httpbin.org/delete”) # DELETE请求 requests.head(“http://httpbin.org/get”) # HEAD请求 requests.options(“http://httpbin.org/get” ) # OPTIONS请求 传参 # 字典传递参数params，如果值为None的键不会被添加到url中\n如果是get请求，那么会自动拼接在url后面\n如果是post请求，requests默认使用application/x-www-form-urlencoded对POST数据编码，内部自动将参数序列化为json\nimport requests params = { \u0026#39;wd\u0026#39;:\u0026#39;python\u0026#39; } resp = requests.get(\u0026#39;https://www.baidu.com/s\u0026#39;,params=params) print(type(resp)) print(resp.status_code) \u0026#39;\u0026#39;\u0026#39; \u0026lt;class \u0026#39;requests.models.Response\u0026#39;\u0026gt; 200 \u0026#39;\u0026#39;\u0026#39; Response # requests的get、post等方法的返回值就是一个Response对象\nr = requests.get(\u0026#39;https://www.baidu.com/\u0026#39;) r.encoding #获取当前的编码 r.encoding = \u0026#39;utf-8\u0026#39; #设置编码 r.text #以encoding设置解析返回内容。字符串方式的响应体，会自动根据响应头部的字符编码进行解码。 r.content #以字节形式（二进制）返回。字节方式的响应体，会自动为你解码 gzip 和 deflate 压缩。 r.headers #以字典对象存储服务器响应头，但是这个字典比较特殊，字典键不区分大小写，若键不存在则返回None r.status_code #响应状态码 r.raw #返回原始响应体，也就是 urllib 的 response 对象，使用 r.raw.read() r.ok # 查看r.ok的布尔值便可以知道是否登陆成功 #*特殊方法*# r.json() #Requests中内置的JSON解码器，以json形式返回,前提返回的内容确保是json格式的，不然解析出错会抛异常 r.raise_for_status() #失败请求(非200响应)抛出异常 请求头和cookie # 只需要将字典形式的请求头和cookie传入headers、cookies就可以了\nfrom email import header import requests headers = { \u0026#39;User-Agent\u0026#39;:\u0026#39;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1\u0026#39; } cookies = { \u0026#39;cookieKey\u0026#39;:\u0026#39;cookieValue\u0026#39; } resp = requests.get(\u0026#39;https://www.baidu.com/\u0026#39;,headers=headers,cookies=cookies) print(resp.status_code) \u0026#39;\u0026#39;\u0026#39; 200 \u0026#39;\u0026#39;\u0026#39; 代理 # 将代理以字典形式传入proxies就可以\nfrom email import header import requests proxies = { \u0026#39;http\u0026#39;:\u0026#39;103.37.141.69:80\u0026#39;, \u0026#39;https\u0026#39;:\u0026#39;103.37.141.69:80\u0026#39; } resp = requests.get(\u0026#39;https://www.baidu.com/\u0026#39;,proxies=proxies) print(resp.status_code) \u0026#39;\u0026#39;\u0026#39; 200 \u0026#39;\u0026#39;\u0026#39; 超时 # 单位：秒\nr = requests.get(\u0026#39;url\u0026#39;,timeout=1) 会话对象 # 能够跨请求保持某些参数，访问时使用session对象进行访问就可以了\nimport requests session = requests.Session() headers = { \u0026#39;User-Agent\u0026#39;:\u0026#39;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1\u0026#39; } cookies = { \u0026#39;cookieKey\u0026#39;:\u0026#39;cookieValue\u0026#39; } proxies = { \u0026#39;http\u0026#39;:\u0026#39;103.37.141.69:80\u0026#39;, \u0026#39;https\u0026#39;:\u0026#39;103.37.141.69:80\u0026#39; } resp = session.get(\u0026#39;https://www.baidu.com/\u0026#39;,headers=headers,cookies=cookies,proxies=proxies) print(resp.status_code) \u0026#39;\u0026#39;\u0026#39; 200 \u0026#39;\u0026#39;\u0026#39; 文件上传 # import requests upload_files = {\u0026#39;file\u0026#39;: open(\u0026#39;report.xls\u0026#39;, \u0026#39;rb\u0026#39;)} r = requests.post(url, files=upload_files) ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/1b37041b/339aff44/399969b2/","section":"文章","summary":"\u003cp\u003erequests是使用Apache2 licensed 许可证的HTTP库。\u003c/p\u003e\n\u003cp\u003e用python编写，比urllib2模块更简洁，本质就是封装了urllib3。\u003c/p\u003e","title":"3、requests","type":"posts"},{"content":"主要用来获取屏幕信息\n//获取主屏幕 Screen screen = Screen.getPrimary(); //获取用户的所有屏幕 ObservableList\u0026lt;Screen\u0026gt; screens = Screen.getScreens(); //获取当前屏幕dpi像素 screen.getDpi(); //获取用户可视屏幕的宽高 screen.getVisualBounds(); //获取全部屏幕宽高 screen.getBounds(); ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/d9073060/edd0d20f/e0eb6797/","section":"文章","summary":"\u003cp\u003e主要用来获取屏幕信息\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//获取主屏幕\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eScreen\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003escreen\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eScreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetPrimary\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取用户的所有屏幕\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eObservableList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eScreen\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003escreens\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eScreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetScreens\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取当前屏幕dpi像素\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetDpi\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取用户可视屏幕的宽高\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetVisualBounds\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取全部屏幕宽高\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003escreen\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetBounds\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"3、Screen","type":"posts"},{"content":" 准备工作 # 1、添加依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-amqp\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、修改配置文件 # application.yml\nserver: port: 8080 spring: # 配置RabbitMQ服务器 rabbitmq: host: localhost port: 5672 username: guest password: guest listener: simple: # 手动应答 acknowledge-mode: manual # 限流 prefetch: 1 # 最少消费者数量 concurrency: 1 # 最多消费者数量 max-concurrency: 10 # 支持重试 retry: enabled: true Queue模式 # 简单模式 # 配置类 # @Configuration public class RabbitConfig { //org.springframework.amqp.core.Queue @Bean public Queue queue(){ return new Queue(\u0026#34;boot_queue\u0026#34;); } } 生产者controller # @RestController public class SimpleController { @Resource private RabbitTemplate rabbitTemplate; @GetMapping(\u0026#34;/producer\u0026#34;) public String producer(String msg){ //发送的对象必须实现序列化接口 Map\u0026lt;String,Object\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); map.put(\u0026#34;msg\u0026#34;,msg); map.put(\u0026#34;user\u0026#34;,new User(\u0026#34;lucy\u0026#34;,18)); rabbitTemplate.convertAndSend(\u0026#34;boot_queue\u0026#34;,map); return \u0026#34;ok\u0026#34;; } } 消费者 # @Component public class SimpleConsumer { //接取对象的时候，使用和发送时相同的类型就可以 @RabbitListener(queues = {\u0026#34;boot_queue\u0026#34;}) public void consumer(Map\u0026lt;String,Object\u0026gt; map){ System.out.println(\u0026#34;简单模式消费者：\u0026#34; + map); } } 工作模式 # 生产者与简单模式中相同\n1、在yml配置文件中，声明手动应答和QOS限流 # spring: rabbitmq: listener: simple: # 手动应答 acknowledge-mode: manual # 限流 prefetch: 1 2、消费者中，进行手动应答 # @Component public class SimpleConsumer { @RabbitListener(queues = {\u0026#34;boot_queue\u0026#34;}) public void consumer(Map\u0026lt;String,Object\u0026gt; map, Channel channel, Message message){ System.out.println(\u0026#34;简单模式消费者：\u0026#34; + map); try { //进行手动提交 channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); } catch (IOException ioException) { ioException.printStackTrace(); } } } Exchange模式（订阅模式） # 路由模式（direct） # 配置类 # @Configuration public class RabbitConfig { @Bean public Queue redQueue(){ return new Queue(\u0026#34;redQueue\u0026#34;); } @Bean public Queue greenQueue(){ return new Queue(\u0026#34;greenQueue\u0026#34;); } @Bean public DirectExchange directExchange(){ return new DirectExchange(\u0026#34;directExchange\u0026#34;); } @Bean public Binding bindingRed(Queue redQueue,DirectExchange directExchange){ //将队列redQueue绑定到directExchange，路由键是red return BindingBuilder.bind(redQueue).to(directExchange).with(\u0026#34;red\u0026#34;); } @Bean public Binding bindingGreen(Queue greenQueue,DirectExchange directExchange){ //将队列greenQueue绑定到directExchange，路由键是green return BindingBuilder.bind(greenQueue).to(directExchange).with(\u0026#34;green\u0026#34;); } } 生产者 # @RestController public class SimpleController { @Resource private RabbitTemplate rabbitTemplate; @GetMapping(\u0026#34;/colorProducer\u0026#34;) public String colorProducer(){ for (int i = 0; i \u0026lt; 10; i++) { //发送消息到exchange，参数1：交换机名，参数2：路由键，参数3：消息 rabbitTemplate.convertAndSend(\u0026#34;directExchange\u0026#34;,\u0026#34;red\u0026#34;,\u0026#34;红色\u0026#34; + i); } for (int i = 0; i \u0026lt; 10; i++) { rabbitTemplate.convertAndSend(\u0026#34;directExchange\u0026#34;,\u0026#34;green\u0026#34;,\u0026#34;绿色\u0026#34; + i); } return \u0026#34;ok\u0026#34;; } } 消费者 # @Component public class SimpleConsumer { //声明一个消费者，接收redQueue队列消息 @RabbitListener(queues = {\u0026#34;redQueue\u0026#34;}) public void redConsumer(String msg,Channel channel,Message message){ System.out.println(\u0026#34;红色消费者：\u0026#34; + msg); try { channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); } catch (IOException ioException) { ioException.printStackTrace(); } } //声明一个消费者，接收greenQueue队列消息 @RabbitListener(queues = {\u0026#34;greenQueue\u0026#34;}) public void greenConsumer(String msg,Channel channel,Message message){ System.out.println(\u0026#34;绿色消费者：\u0026#34; + msg); try { channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); } catch (IOException ioException) { ioException.printStackTrace(); } } } 广播模式（fanout） # 配置类 # @Configuration public class RabbitConfig { @Bean public Queue queue1(){ return new Queue(\u0026#34;queue1\u0026#34;); } @Bean public Queue queue2(){ return new Queue(\u0026#34;queue2\u0026#34;); } @Bean public FanoutExchange fanoutExchange(){ return new FanoutExchange(\u0026#34;fanoutExchange\u0026#34;); } @Bean public Binding bindingQueue1(Queue queue1,FanoutExchange fanoutExchange){ //将队列queue1绑定到fanoutExchange return BindingBuilder.bind(queue1).to(fanoutExchange); } @Bean public Binding bindingQueue2(Queue queue2,FanoutExchange fanoutExchange){ //将队列queue2绑定到fanoutExchange return BindingBuilder.bind(queue2).to(fanoutExchange); } } 生产者 # @RestController public class SimpleController { @Resource private RabbitTemplate rabbitTemplate; @GetMapping(\u0026#34;/fanoutProducer\u0026#34;) public String fanoutProducer(){ for (int i = 0; i \u0026lt; 10; i++) { //发送消息到exchange，参数1：交换机名，参数2：路由键，参数3：消息 rabbitTemplate.convertAndSend(\u0026#34;fanoutExchange\u0026#34;,\u0026#34;\u0026#34;,\u0026#34;广播模式：\u0026#34; + i); } return \u0026#34;ok\u0026#34;; } } 消费者 # @Component public class SimpleConsumer { //声明一个消费者，接收queue1队列消息 @RabbitListener(queues = {\u0026#34;queue1\u0026#34;}) public void queue1Consumer(String msg,Channel channel,Message message){ System.out.println(\u0026#34;queue1：\u0026#34; + msg); try { channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); } catch (IOException ioException) { ioException.printStackTrace(); } } //声明一个消费者，接收queue2队列消息 @RabbitListener(queues = {\u0026#34;queue2\u0026#34;}) public void queue2Consumer(String msg,Channel channel,Message message){ System.out.println(\u0026#34;queue2：\u0026#34; + msg); try { channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); } catch (IOException ioException) { ioException.printStackTrace(); } } } 主题模式（topic） # 配置类 # @Configuration public class RabbitConfig { @Bean public Queue queueXian(){ return new Queue(\u0026#34;queueXian\u0026#34;); } @Bean public Queue queueBeijing(){ return new Queue(\u0026#34;queueBeijing\u0026#34;); } @Bean public TopicExchange topicExchange(){ return new TopicExchange(\u0026#34;topicExchange\u0026#34;); } @Bean public Binding bindingXian(Queue queueXian,TopicExchange topicExchange){ //将队列redQueue绑定到directExchange，路由键是red return BindingBuilder.bind(queueXian).to(topicExchange).with(\u0026#34;*.xian\u0026#34;); } @Bean public Binding bindingBeijing(Queue queueBeijing,TopicExchange topicExchange){ //将队列greenQueue绑定到directExchange，路由键是green return BindingBuilder.bind(queueBeijing).to(topicExchange).with(\u0026#34;*.beijing\u0026#34;); } } 生产者 # @RestController public class SimpleController { @Resource private RabbitTemplate rabbitTemplate; @GetMapping(\u0026#34;/topicProducer\u0026#34;) public String topicProducer(){ for (int i = 0; i \u0026lt; 10; i++) { rabbitTemplate.convertAndSend(\u0026#34;topicExchange\u0026#34;,\u0026#34;order.xian\u0026#34;,\u0026#34;西安订单\u0026#34; + i); } for (int i = 0; i \u0026lt; 10; i++) { rabbitTemplate.convertAndSend(\u0026#34;topicExchange\u0026#34;,\u0026#34;order.beijing\u0026#34;,\u0026#34;北京订单\u0026#34; + i); } for (int i = 0; i \u0026lt; 10; i++) { rabbitTemplate.convertAndSend(\u0026#34;topicExchange\u0026#34;,\u0026#34;back.xian\u0026#34;,\u0026#34;西安退货\u0026#34; + i); } for (int i = 0; i \u0026lt; 10; i++) { rabbitTemplate.convertAndSend(\u0026#34;topicExchange\u0026#34;,\u0026#34;order.beijing\u0026#34;,\u0026#34;北京退货\u0026#34; + i); } return \u0026#34;ok\u0026#34;; } } 消费者 # @Component public class SimpleConsumer { //声明一个消费者，接收队列queueXian队列消息 @RabbitListener(queues = {\u0026#34;queueXian\u0026#34;}) public void queueXianConsumer(String msg,Channel channel,Message message){ System.out.println(\u0026#34;西安仓：\u0026#34; + msg); try { channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); } catch (IOException ioException) { ioException.printStackTrace(); } } //声明一个消费者，接收队列queueBeijing队列消息 @RabbitListener(queues = {\u0026#34;queueBeijing\u0026#34;}) public void queueBeijingConsumer(String msg,Channel channel,Message message){ System.out.println(\u0026#34;北京仓：\u0026#34; + msg); try { channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); } catch (IOException ioException) { ioException.printStackTrace(); } } } 死信、延迟队列 # 死信队列 # 死信生成的三个条件 # 消息被拒绝(basic.reject / basic.nack)，并且requeue = false 消息TTL过期 队列达到最大长度 模拟消息丢失 # public class Producer { public static final String QUEUE_NAME = \u0026#34;normalQueue\u0026#34;; public static final String EXCHANGE_NAME = \u0026#34;normalExchange\u0026#34;; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME,\u0026#34;fanout\u0026#34;); Map\u0026lt;String,Object\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); map.put(\u0026#34;x-max-length\u0026#34;,25); channel.queueDeclare(QUEUE_NAME, false, false, false, map); for (int i = 0; i \u0026lt; 100; i++) { String message = \u0026#34;hello rabbitmq world!!\u0026#34;+i; channel.basicPublish(\u0026#34;\u0026#34;,QUEUE_NAME,null,message.getBytes()); } System.out.println(\u0026#34;send ok!\u0026#34;); connection.close(); } } 通过死信队列接收这75条消息 # public class Producer { public static final String QUEUE_NAME = \u0026#34;normalQueue\u0026#34;; public static final String EXCHANGE_NAME = \u0026#34;normalExchange\u0026#34;; public static void main(String[] args) throws IOException, TimeoutException { Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME,\u0026#34;fanout\u0026#34;); Map\u0026lt;String,Object\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); map.put(\u0026#34;x-max-length\u0026#34;,25);\t//生成死信 map.put(\u0026#34;x-dead-letter-exchange\u0026#34;,\u0026#34;deadExchange\u0026#34;); //产生死信时发到哪个交换机上 map.put(\u0026#34;x-dead-letter-routing-key\u0026#34;,\u0026#34;deadletter\u0026#34;); //声明routingkey channel.queueDeclare(QUEUE_NAME, false, false, false, map); channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,\u0026#34;\u0026#34;); channel.exchangeDeclare(\u0026#34;deadExchange\u0026#34;,\u0026#34;direct\u0026#34;); channel.queueDeclare(\u0026#34;deadQueue\u0026#34;,false,false,false,null); channel.queueBind(\u0026#34;deadQueue\u0026#34;,\u0026#34;deadExchange\u0026#34;,\u0026#34;deadletter\u0026#34;); for (int i = 0; i \u0026lt; 100; i++) { String message = \u0026#34;hello rabbitmq world!!\u0026#34;+i; channel.basicPublish(\u0026#34;\u0026#34;,QUEUE_NAME,null,message.getBytes()); } System.out.println(\u0026#34;send ok!\u0026#34;); connection.close(); } } 延迟队列 # 延迟队列是一个特殊的死信队列\n1、按存活时间过期进入死信队列\n2、普通消息没有消费者处理\n插件实现 # 在rabbitmq 3.5.7及以上的版本提供了一个插件（rabbitmq-delayed-message-exchange）来实现延迟队列功能。同时插件依赖Erlang/OPT 18.0及以上。\n1、下载 # https://www.rabbitmq.com/community-plugins.html\n2、安装 # 插件格式为ez，将文件复制到插件目录plugins\n3、启动插件 # rabbitmq-plugins enable rabbitmq_delayed_message_exchange 4、查看 # 5、main方法实现 # 依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.rabbitmq\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;amqp-client\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;5.9.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 生产者 # public class Send { static final String exchangeName = \u0026#34;test_exchange\u0026#34;; static final String queueName = \u0026#34;test_queue\u0026#34;; static final String routingKey = \u0026#34;test_queue\u0026#34;; public static void main(String[] args) throws IOException, TimeoutException { //建立连接，创建通道 ConnectionFactory fc = new ConnectionFactory(); fc.setHost(\u0026#34;192.168.2.100\u0026#34;); fc.setPort(5672); fc.setUsername(\u0026#34;guest\u0026#34;); fc.setPassword(\u0026#34;guest\u0026#34;); Connection conn = fc.newConnection(); Channel channel = conn.createChannel(); //配置交换机类型 Map\u0026lt;String, Object\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); map.put(\u0026#34;x-delayed-type\u0026#34;, \u0026#34;direct\u0026#34;); //创建路由 channel.exchangeDeclare(exchangeName, \u0026#34;x-delayed-message\u0026#34;,false, false,map); //创建队列 channel.queueDeclare(queueName, true, false, false, null); //绑定路由、队列 channel.queueBind(queueName, exchangeName, routingKey); String msg = \u0026#34;延时消息\u0026#34;; //配置延时时间 AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder(); Map headers = new HashMap(); headers.put(\u0026#34;x-delay\u0026#34;, 9000); builder.headers(headers); AMQP.BasicProperties properties = builder.build(); //发送消息 channel.basicPublish(exchangeName, queueName, properties, msg.getBytes()); channel.close(); conn.close(); } } 消费者和以前相同 # 6、Boot实现 # 依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-amqp\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.4.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 配置文件和之前一样 # 配置类 # @Configuration public class RabbitMQConfig { public static final String exchangeName = \u0026#34;test_exchange\u0026#34;; public static final String queueName = \u0026#34;test_queue\u0026#34;; public static final String routingKey = \u0026#34;test_queue\u0026#34;; /** * 创建延迟队列 */ @Bean public Queue createQueue(){ return QueueBuilder.durable(queueName).build(); } /** * 创建路由 */ @Bean public CustomExchange createExchange(){ Map\u0026lt;String,Object\u0026gt; map = new HashMap\u0026lt;String,Object\u0026gt;(); map.put(\u0026#34;x-delayed-type\u0026#34;, \u0026#34;direct\u0026#34;); return new CustomExchange( exchangeName, \u0026#34;x-delayed-message\u0026#34;, true, false, map); } /** * 绑定路由与队列 */ @Bean public Binding exchangeBindingQueue(){ return BindingBuilder.bind(createQueue()). to(createExchange()).with(routingKey).noargs(); } } 生产者 # /** * 延迟消息 发布者 */ @RestController public class SendController { @Autowired RabbitTemplate rabbitTemplate; @GetMapping(\u0026#34;/send\u0026#34;) public String sendMsg(){ String msg = \u0026#34;测试延时消息|\u0026#34;+LocalDateTime.now(); //ES6 rabbitTemplate.convertAndSend(RabbitMQConfig.exchangeName, RabbitMQConfig.routingKey, msg, (message) -\u0026gt;{ message.getMessageProperties().setHeader(\u0026#34;x-delay\u0026#34;, 9000); //延迟9秒 return message; }); return \u0026#34;发送消息成功！\u0026#34;; } } 消费者和之前一样 # ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/e2f9d0f6/ff23a4ed/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e准备工作 \n    \u003cdiv id=\"准备工作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%87%86%e5%a4%87%e5%b7%a5%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e1、添加依赖 \n    \u003cdiv id=\"1添加依赖\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1%e6%b7%bb%e5%8a%a0%e4%be%9d%e8%b5%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.springframework.boot\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003espring-boot-starter-amqp\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.springframework.boot\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003espring-boot-starter-web\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e2、修改配置文件 \n    \u003cdiv id=\"2修改配置文件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#2%e4%bf%ae%e6%94%b9%e9%85%8d%e7%bd%ae%e6%96%87%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003eapplication.yml\u003c/p\u003e","title":"3、SpringBoot整合RabbitMQ","type":"posts"},{"content":" SSH # SSH 为 Secure Shell 的缩写，由 IETF 的网络工作小组（Network Working Group）所制定，SSH 为建立在应用层和传输层基础上的安全协议。\nSSH的优点：\n安全连接： SSH对客户端和服务器之间传输的所有数据进行加密，提供一个安全和私人的连接。 远程访问： SSH允许用户从世界任何地方远程访问和控制服务器和设备。 认证： SSH支持多种认证方法，包括密码、公钥加密法和双因素认证。 多功能性： SSH是一个广泛使用的协议，支持各种应用，包括远程外壳访问、文件传输和端口转发。 SSH的劣势：\n复杂性： SSH的设置和配置可能很复杂，特别是对于缺乏技术专长的用户。 性能： 由于加密和认证的开销，SSH可能比其他协议，如FTP，要慢一些。 防火墙问题： SSH可能需要特殊的防火墙配置以允许访问远程服务器和设备。 连接SSH # Linux 一般作为服务器使用，而服务器一般放在机房，你不可能在机房操作你的 Linux 服务器，一般远程登陆的方式都是基于SSH，常用的工具有XShell、MobaXterm\nMobaXterm有免费版本，而且免安装比较方便：https://mobaxterm.mobatek.net/\n使用前提 # 1、服务器开启了SSH服务 # 操作系统即使是最小安装ssh默认是已经安装的。默认ssh服务也是开启的。\n可以通过命令service sshd status来查看sshd服务是否开启\n2、服务器网络没问题 # 查看系统IP：ip addr\n发现第二个网卡ens33并没有分配IP\n使用命令：vi /etc/sysconfig/network-scripts/ifcfg-ens33，进入配置文件进行修改ONBOOT为yes，表示服务启动时自动执行该文件，:wq保存退出\n重启网络服务，命令：service network restart\n重启完之后，再次查看IP地址，命令：ip addr，发现网卡ens33已经有了局域网IP\n第三方工具连接 # 连接成功\nSSH命令行连接 # 一般情况下，Windows、Mac、Linux操作系统都内置了ssh命令\n可以使用如下命令来连接目标服务器，如果端口是默认22，则不需要指定端口-p\nssh -p [port] [username]@[ip] 修改SSH服务端口号 # 1、编辑 SSH 配置文件：vi /etc/ssh/sshd_config\n2、找到#Port 22、去掉注释并修改\n3、重启SSH服务：service sshd restart\n4、配置防火墙，允许新端口访问\nSFTP # 这是一个建立在SSH之上的协议，以一种安全的方式有效地传输文件。通过使用该协议，它可以很容易地通过互联网连接安全地移动大量数据。它利用了SSH，使信息交换具有更高的保护水平。\nSFTP的优点：\n安全性： SFTP对客户端和服务器之间传输的所有数据进行加密，提供一个安全和私密的连接。 认证： SFTP支持多种认证方法，包括密码和公钥加密法。 简单性： SFTP比SSH更容易设置和配置，特别是对于缺乏技术专长的用户。 文件管理： SFTP提供文件管理功能，包括在远程服务器上重命名、删除和移动文件的能力。 SFTP的缺点：\n支持有限： 并非所有的虚拟主机供应商都支持SFTP，而且在某些设备上可能无法使用。 速度较慢： 由于加密和认证的开销，SFTP的速度可能比其他协议，如FTP，要慢。 功能有限： SFTP的功能比其他文件传输协议(如FTP)少，可能不适合所有的使用情况。 连接SFTP # 由于SFTP是建立在SSH之上的，所以使用前提和SSH一样，端口也是使用SSH的端口。一般情况下，我们使用第三方工具例如XShell、MobaXterm都已经内置了SFTP的功能。\n但是某些特殊情况下，可能无法安装使用第三方工具，那么就需要使用sftp命令，这个命令在Windows、Mac、Linux一般都是默认内置的。\n#如果连接地址存在 path 并且 path 不是一个目录，那么 SFTP 会直接从服务器端取回这个文件。 #-B: buffer_size，指定传输 buffer 的大小，更大的 buffer 会消耗更多的内存，默认为 32768 bytes； #-P: port，指定连接的SSH端口号，默认为 22； #-R: num_requests，指定一次连接的请求数，可以略微提升传输速度，但是会增加内存的使用量。 sftp -P [port] [user_name]@[ip][:path] #例如 C:\\Users\\yhgh\u0026gt;sftp -P 22 root@192.168.253.143:/home/ root@192.168.253.143\u0026#39;s password: Connected to 192.168.253.143. Changing to: /home/ sftp\u0026gt; 连接后，我们会进入sftp的解释器，可以使用help命令查看可用命令\nbye Quit sftp cd path Change remote directory to \u0026#39;path\u0026#39; chgrp [-h] grp path Change group of file \u0026#39;path\u0026#39; to \u0026#39;grp\u0026#39; chmod [-h] mode path Change permissions of file \u0026#39;path\u0026#39; to \u0026#39;mode\u0026#39; chown [-h] own path Change owner of file \u0026#39;path\u0026#39; to \u0026#39;own\u0026#39; df [-hi] [path] Display statistics for current directory or filesystem containing \u0026#39;path\u0026#39; exit Quit sftp get [-afpR] remote [local] Download file help Display this help text lcd path Change local directory to \u0026#39;path\u0026#39; lls [ls-options [path]] Display local directory listing lmkdir path Create local directory ln [-s] oldpath newpath Link remote file (-s for symlink) lpwd Print local working directory ls [-1afhlnrSt] [path] Display remote directory listing lumask umask Set local umask to \u0026#39;umask\u0026#39; mkdir path Create remote directory progress Toggle display of progress meter put [-afpR] local [remote] Upload file pwd Display remote working directory quit Quit sftp reget [-fpR] remote [local] Resume download file rename oldpath newpath Rename remote file reput [-fpR] local [remote] Resume upload file rm path Delete remote file rmdir path Remove remote directory symlink oldpath newpath Symlink remote file version Show SFTP version !command Execute \u0026#39;command\u0026#39; in local shell ! Escape to local shell ? Synonym for help 下载 # #文件 sftp\u0026gt; get remotePath [localPath] #目录 sftp\u0026gt; get -r remotePath [localPath] 上传 # #文件 sftp\u0026gt; put [localPath] remotePath #目录 sftp\u0026gt; put -r [localPath] remotePath 删除 # #文件 sftp\u0026gt; rm path #目录 sftp\u0026gt; rmdir path ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/26654e7b/1a145064/6cca9433/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eSSH \n    \u003cdiv id=\"ssh\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#ssh\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eSSH 为 Secure Shell 的缩写，由 IETF 的网络工作小组（Network Working Group）所制定，SSH 为建立在应用层和传输层基础上的安全协议。\u003c/p\u003e","title":"3、SSH、SFTP","type":"posts"},{"content":" 本质 # string是C++风格的字符串，而string本质上是一个类\nstring和char * 区别 # char * 是一个指针\nstring是一个类，类内部封装了char*，管理这个字符串，是一个char*型的容器。\n特点 # string 类内部封装了很多成员方法\n例如：查找find，拷贝copy，删除delete 替换replace，插入insert\nstring管理char*所分配的内存，不用担心复制越界和取值越界等，由类内部进行负责\nstring构造函数 # 构造函数原型：\nstring();创建一个空的字符串 例如:string str;\nstring(const char* s);使用c语言字符串s初始化\nstring(const string\u0026amp; str);使用一个string对象初始化另一个string对象（拷贝构造）\nstring(int n, char c);使用n个字符c初始化\n例子：\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; int main(){ //1、默认无参构造，创建空字符串 string str1; str1 = \u0026#34;str1\u0026#34;; cout \u0026lt;\u0026lt; str1 \u0026lt;\u0026lt; endl; //2、使用c语言字符串初始化string char * ch = \u0026#34;str2\u0026#34;; string str2(ch); cout \u0026lt;\u0026lt; str2 \u0026lt;\u0026lt; endl; //3、使用string类型的字符串引用初始化新的字符串 string temp = \u0026#34;str3\u0026#34;; string str3(temp); cout \u0026lt;\u0026lt; str3 \u0026lt;\u0026lt; endl; //4、使用n个相同字符来初始化字符串 string str4(5,\u0026#39;a\u0026#39;); cout \u0026lt;\u0026lt; str4 \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 常用API # string的赋值 # 赋值函数原型 # string\u0026amp; operator=(const char* s); char*类型字符串赋值给当前的字符串 string\u0026amp; operator=(const string \u0026amp;s);把字符串s赋给当前的字符串 string\u0026amp; operator=(char c); 字符赋值给当前的字符串 string\u0026amp; assign(const char *s); 把字符串s赋给当前的字符串 string\u0026amp; assign(const char *s, int n);把字符串s的前n个字符赋给当前的字符串 string\u0026amp; assign(const string \u0026amp;s);把字符串s赋给当前字符串 string\u0026amp; assign(int n, char c);用n个字符c赋给当前字符串 例子：\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/d8245fe1/bf736d53/dd1be99e/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e本质 \n    \u003cdiv id=\"本质\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9c%ac%e8%b4%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003estring是C++风格的字符串，而string本质上是一个类\u003c/p\u003e","title":"3、string容器","type":"posts"},{"content":" 包装类（封装类） # Java是一个面向对象的编程语言，但是Java中的八种基本数据类型却是不面向对象的，为了使用方便和解决这个不足，在设计类时为每个基本数据类型设计了一个对应的类进行代表，这样八种基本数据类型对应的类统称为包装类(Wrapper Class)，包装类均位于java.lang包。\n基本数据类型 包装类 基本数据类型 包装类 byte Byte short Short int Integer long Long float Float double Double boolean Boolean char Character 其中，Byte、Short、Integer、Long、Float、Double的父类都是java.lang.Number\n装箱和拆箱 # int val = 10; // 装箱 Integer integerValue = Integer.valueOf(10); // 拆箱 int intValue = integerValue.intValue(); 自动装箱与自动拆箱 # jdk5.0新加入特性，引入了自动拆装箱的语法，也就是在进行基本数据类型和对应的包装类转换时，系统将自动进行装箱和拆箱\nint val = 10; // 自动装箱 Integer integerValue = val; // 自动拆箱 int intValue = integerValue; 包装类的缓存机制 # 在java中，除了Double和Float之外，剩下的包装类都有缓存，也就是说，例如Integer在装箱时，如果数据的范围是-128 ~ 127，那么在内存中使用的是同一个对象\n// 自动装箱使用缓存 Integer a = 125; Integer b = 125; System.out.println(a == b); // true // 手动装箱使用缓存 Integer c = Integer.valueOf(125); Integer d = Integer.valueOf(125); System.out.println(c == d); // true // new对象，不使用缓存 Integer e = new Integer(125); Integer f = new Integer(125); System.out.println(e == f); // false 缓存范围 # Boolean：true和false Byte：-128 ~ 127 Character：0 ~ 127 Short：-128 ~ 127 Integer：-128 ~ 127 Long：-128 ~ 127\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/409de243/4109797d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e包装类（封装类） \n    \u003cdiv id=\"包装类封装类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8c%85%e8%a3%85%e7%b1%bb%e5%b0%81%e8%a3%85%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eJava是一个面向对象的编程语言，但是Java中的八种基本数据类型却是不面向对象的，为了使用方便和解决这个不足，在设计类时为每个基本数据类型设计了一个对应的类进行代表，这样八种基本数据类型对应的类统称为包装类(Wrapper Class)，包装类均位于\u003ccode\u003ejava.lang\u003c/code\u003e包。\u003c/p\u003e","title":"3、包装类","type":"posts"},{"content":" 注册中心以及服务注册 # 注册中心：是一个专用于完成微服务的注册，以及服务发现的东西 它采用的模式：发布/订阅模式 产品：Eureka（国外的），Nacos（国内阿里巴巴），ZooKeeper（动物园管理员） 这种产品非常多，具体使用情况具公司的情况具体使用 常用服务注册中心对比 # Nacos Eureka Consul CoreDns Zookeeper 一致性协议 CP + AP AP CP / CP 健康检查 TCP/HTTP/MySQL/client Beat Client Beat TCP/HTTP/gRPC/Cmd / Client Beat 负载均衡 权重/DSL/metaData/CMDB Ribbon Fabio RR / 雪崩保护 支持 支持 不支持 不支持 不支持 自动注销示例 支持 支持 不支持 不支持 支持 访问协议 HTTP/DNS/UDP HTTP HTTP/DNS NNS TCP 监听支持 支持 支持 支持 不支持 支持 多数据中心 支持 支持 支持 不支持 不支持 跨注册中心 支持 不支持 支持 不支持 不支持 Spring Cloud集成 支持 支持 支持 不支持 不支持 Dubbo集成 支持 不支持 不支持 不支持 支持 k8s集成 支持 不支持 不支持 支持 支持 Eureka # Eureka是SpringCloud体系中，专门用来完成服务的注册和发现的组件\nEureka组件分为2个部分：\nEureka-Server（注册中心） Eureka-client(客户端) 当前目前，Eureka已经从2.0开始，不再更新了，但是Spring还是在维护\nEureka-Server搭建 # 1、创建maven项目，导入依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-netflix-eureka-server\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、在该模块的启动类上，添加注解将该应用程序定位为微服务的注册中心 # @EnableEurekaServer @SpringBootApplication public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } } 3、在application.yml中，对eureka进行配置 # server: port: 9000 spring: #必须添加项目名 application: name: eureka eureka: client: #不需要获得任何其它服务的注册信息 fetch-registry: false #自身的注册 register-with-eureka: false service-url: #地址中的eureka不可以改变 defaultZone: http://localhost:9000/eureka 4、进入eureka界面，证明搭建成功 # Eureka-client注册服务 # 对项目中的消费者以及生产者进行注册，例如，项目订单、商品等微服务\n1、导入依赖（Eureka-Client客户端） # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-netflix-eureka-client\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、修改微服务application.yml文件 # server: port: 8080 spring: #必须添加项目名 application: name: star-product eureka: client: #可以不写，默认值为true fetch-registry: true #可以不写，默认值为true register-with-eureka: true service-url: #配置eureka注册地址 defaultZone: http://localhost:9000/eureka instance: #将自己的ip注册到eureka的server prefer-ip-address: true 3、在启动类上， 添加注解，将该程序定义为：Eureka注册中心的客户端 # @SpringBootApplication @EnableEurekaClient public class ProductApplication { public static void main(String[] args) { SpringApplication.run(ProductApplication.class,args); } } 4、启动该微服务，查看eureka界面是否注册成功 # 5、可以在服务消费方，使用该application名进行访问 # 前提：需要在RestTemplate方法上，添加注解@LoadBalanced\n@Bean @LoadBalanced public RestTemplate getRestTemplate(){ return new RestTemplate(); } 横向扩容（提供者高可用） # 开发环境，有时需要去提前做一些微服务的横向扩容测试\n横向扩容：我们给每个微服务，提供很多的注册实例\n一个微服务程序，被放置在多个Tomcat中，实际上就是横向扩容，横向扩容的目的：\n提示系统整体的响应性能 提升系统整体的可用性（高可用） 针对同一个微服务程序，模拟多台Tomcat\n实现方式（Idea） # 1、编辑添加实例 # 2、选择springboot实例 # 3、配置实例 # 4、查看eureka，查看实例是否注册成功 # eureka高可用 # 实现方式（Idea） # 1、修改eureka微服务的配置文件 # server: port: 9000 spring: application: name: eureka eureka: client: #获得其它服务的注册信息 fetch-registry: true #进行自身的注册 register-with-eureka: true service-url: #需要将自己注册在自身以及其他eureka服务上 defaultZone: http://localhost:9000/eureka,http://localhost:9001/eureka 2、添加新的springboot实例 # 3、启动两个eureka实例，观察 # 4、将其他微服务，注册在所有的eureka上 # server: port: 8090 spring: application: name: star-orders eureka: client: fetch-registry: true register-with-eureka: true service-url: defaultZone: http://localhost:9000/eureka,http://localhost:9001/eureka instance: prefer-ip-address: true 5、观察实例在所有的eureka上都被注册 # 配置actuator # 1、修改微服务在注册中心显示的名称 # server: port: 8080 spring: application: name: star-product eureka: client: #可以不写，默认值为true fetch-registry: true #可以不写，默认值为true register-with-eureka: true service-url: defaultZone: http://localhost:9000/eureka,http://localhost:9001/eureka instance: #配置中心显示的名称 instance-id: productName 2、访问路径显示ip地址 # server: port: 8080 spring: application: name: star-product eureka: client: #可以不写，默认值为true fetch-registry: true #可以不写，默认值为true register-with-eureka: true service-url: defaultZone: http://localhost:9000/eureka,http://localhost:9001/eureka instance: #配置中心显示的名称 instance-id: productName #将自己的ip注册到eureka的server prefer-ip-address: true 服务发现Discovery # 对于注册进eureka里面的微服务，可以通过服务发现来获得该服务的信息 在主启动类上添加注解：@EnableDiscoveryClient @EnableDiscoveryClient和@EnableEurekaClient的区别 # 相同点 都是能够让注册中心能够发现，扫描到该服务 不同点 @EnableEurekaClient只适用于Eureka作为注册中心，@EnableDiscoveryClient 可以适用与Eureka或其他注册中心 自我保护 # Eureak默认开起来自我保护。避免因为意外情况，将正常的节点直接删除。\n进入自我保护模式最直观的体现就是Eureka Server首页的警告\n默认情况下，如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳，Eureka Server将会注销该实例（默认90秒）。但是当网络分区故障发生时，微服务与Eureka Server之间无法正常通信，这就可能变得非常危险了\u0026mdash;-因为微服务本身是健康的，此时本不应该注销这个微服务。\n自我保护模式是一种对网络异常的安全保护措施。使用自我保护模式，而已让Eureka集群更加的健壮、稳定。\nEureka Server通过“自我保护模式”来解决这个问题\u0026mdash;-当Eureka Server节点在短时间内丢失过多客户端时（可能发生了网络分区故障），那么这个节点就会进入自我保护模式。一旦进入该模式，Eureka Server就会保护服务注册表中的信息，不再删除服务注册表中的数据（也就是不会注销任何微服务）。当网络故障恢复后，该Eureka Server节点会自动退出自我保护模式。\n在Spring Cloud中，可以使用\neureka.server.enable-self-preservation=false 来禁用自我保护模式，一般不会去主动禁用自我保护\n心跳间隔 # eureka: instance: #客户端向服务器发送心跳的时间间隔，单位秒（默认30秒） lease-renewal-interval-in-seconds: 30 #服务器在最后一次收到客户端心跳的等待时间，单位秒（默认30秒），超时会剔除该客户端 lease-expiration-duration-in-seconds: 90 Eureka的健康检查 # 在eureka的客户端中，每个微服务需要把自己的健康状况传播发送到EurekaServer。让EurekaServer能够清晰的知道每个微服务，目前的健康状态。\nhttp://10.1.1.121:9003/actuator/info就是健康检查的页面，默认没有开启健康检查\nNacos（Alibaba） # 官网：https://nacos.io/zh-cn/\n使用 # 1、下载nacos服务端，解压缩 # 2、启动bin下的startup.cmd # 3、启动控制台 # http://172.16.4.22:8848/nacos/index.html\n4、注册提供者和消费者到nacos # 4.1添加依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-alibaba-nacos-discovery\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 4.2在启动类添加注解@EnableDiscoveryClient # 4.3修改yml配置文件 # server: port: 8080 spring: application: name: student-system cloud: nacos: discovery: server-addr: http://localhost:8848 4.4查看nacos服务端，添加成功 # Nacos高可用（win下同ip不同端口） # 1、下载nacos，并解压，复制三份 # 2、获取conf文件夹下nacos-mysql.sql脚本，在数据库生成 # 避坑：如果运行报错Invalid default value for 'gmt_create'，那么需要将sql语句中的DEFAULT CURRENT_TIMESTAMP全部删除，创建后的数据库\n3、分别修改三个nacos实例的配置，conf文件夹下 # 3.1、修改application.properties # 修改三个nacos实例的端口，并且三个nacos实例连接同一个数据库就可以\n3.2、重命名cluster.conf.example为cluster.conf，并编辑 # 添加所有的nacos实例的ip:port，包括自己的\n避坑：此处需要使用本机的ip，不可以使用localhost或127.0.0.1\n4、分别启动三个nacos实例 # 在各自的bin目录下，使用命令：startup.cmd -m cluster，进行启动\n5、启动界面，查看效果 # ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/fd17f7dc/7ff8e4ea/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e注册中心以及服务注册 \n    \u003cdiv id=\"注册中心以及服务注册\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%b3%a8%e5%86%8c%e4%b8%ad%e5%bf%83%e4%bb%a5%e5%8f%8a%e6%9c%8d%e5%8a%a1%e6%b3%a8%e5%86%8c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e注册中心：是一个专用于完成微服务的注册，以及服务发现的东西\u003c/li\u003e\n\u003cli\u003e它采用的模式：发布/订阅模式\u003c/li\u003e\n\u003cli\u003e产品：Eureka（国外的），Nacos（国内阿里巴巴），ZooKeeper（动物园管理员）\u003c/li\u003e\n\u003cli\u003e这种产品非常多，具体使用情况具公司的情况具体使用\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e常用服务注册中心对比 \n    \u003cdiv id=\"常用服务注册中心对比\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b8%b8%e7%94%a8%e6%9c%8d%e5%8a%a1%e6%b3%a8%e5%86%8c%e4%b8%ad%e5%bf%83%e5%af%b9%e6%af%94\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e\u003c/th\u003e\n          \u003cth\u003eNacos\u003c/th\u003e\n          \u003cth\u003eEureka\u003c/th\u003e\n          \u003cth\u003eConsul\u003c/th\u003e\n          \u003cth\u003eCoreDns\u003c/th\u003e\n          \u003cth\u003eZookeeper\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e一致性协议\u003c/td\u003e\n          \u003ctd\u003eCP + AP\u003c/td\u003e\n          \u003ctd\u003eAP\u003c/td\u003e\n          \u003ctd\u003eCP\u003c/td\u003e\n          \u003ctd\u003e/\u003c/td\u003e\n          \u003ctd\u003eCP\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e健康检查\u003c/td\u003e\n          \u003ctd\u003eTCP/HTTP/MySQL/client Beat\u003c/td\u003e\n          \u003ctd\u003eClient Beat\u003c/td\u003e\n          \u003ctd\u003eTCP/HTTP/gRPC/Cmd\u003c/td\u003e\n          \u003ctd\u003e/\u003c/td\u003e\n          \u003ctd\u003eClient Beat\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e负载均衡\u003c/td\u003e\n          \u003ctd\u003e权重/DSL/metaData/CMDB\u003c/td\u003e\n          \u003ctd\u003eRibbon\u003c/td\u003e\n          \u003ctd\u003eFabio\u003c/td\u003e\n          \u003ctd\u003eRR\u003c/td\u003e\n          \u003ctd\u003e/\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e雪崩保护\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e自动注销示例\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e访问协议\u003c/td\u003e\n          \u003ctd\u003eHTTP/DNS/UDP\u003c/td\u003e\n          \u003ctd\u003eHTTP\u003c/td\u003e\n          \u003ctd\u003eHTTP/DNS\u003c/td\u003e\n          \u003ctd\u003eNNS\u003c/td\u003e\n          \u003ctd\u003eTCP\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e监听支持\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e多数据中心\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e跨注册中心\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eSpring Cloud集成\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eDubbo集成\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ek8s集成\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n          \u003ctd\u003e不支持\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n          \u003ctd\u003e支持\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2 class=\"relative group\"\u003eEureka \n    \u003cdiv id=\"eureka\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#eureka\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eEureka是SpringCloud体系中，专门用来完成服务的注册和发现的组件\u003c/p\u003e","title":"3、服务注册中心","type":"posts"},{"content":" 结构体 # C语言结构体（Struct）从本质上讲是一种自定义的数据类型，只不过这种数据类型比较复杂，是由 int、char、float 等基本类型组成的。你可以认为结构体是一种聚合类型。\n在实际开发中，我们可以将一组类型不同的、但是用来描述同一件事物的变量放到结构体中。例如，在校学生有姓名、年龄、身高、成绩等属性，将它们都放到结构体中即可。\n结构体是一种自定义的数据类型，是创建变量的模板，不占用内存空间；结构体变量才包含了实实在在的数据，需要内存空间来存储。\n定义 # struct 结构体名{ 结构体所包含的变量或数组 }; 结构体变量 # 方式一，先定义结构体再声明结构体变量\n#include\u0026lt;stdio.h\u0026gt; struct Student{ char *name; int age; }; int main(){ //声明结构体变量 struct Student s1,s2; return 0; } 方式二，定义结构体的同时声明结构体变量\n#include\u0026lt;stdio.h\u0026gt; struct Student{ char *name; int age; }s1,s2; int main(){ return 0; } 结构体名也可以省略\n#include\u0026lt;stdio.h\u0026gt; struct{ char *name; int age; }s1,s2; int main(){ return 0; } 成员获取和赋值 # 格式：结构体变量.成员名\n#include\u0026lt;stdio.h\u0026gt; struct Student{ char *name; int age; }s1; int main(){ s1.name = \u0026#34;Lucy\u0026#34;; s1.age = 18; printf(\u0026#34;姓名：%s\u0026#34;,s1.name); printf(\u0026#34;年龄：%d\u0026#34;,s1.age); return 0; } 也可以在声明变量的时候按照成员顺序整体进行赋值\n#include\u0026lt;stdio.h\u0026gt; struct Student{ char *name; int age; }s1 = {\u0026#34;lucy\u0026#34;,18}; int main(){ struct Student s2 = {\u0026#34;tom\u0026#34;,22}; printf(\u0026#34;姓名：%s\u0026#34;,s1.name); printf(\u0026#34;年龄：%d\\n\u0026#34;,s1.age); printf(\u0026#34;姓名：%s\u0026#34;,s2.name); printf(\u0026#34;年龄：%d\u0026#34;,s2.age); return 0; } 结构体数组 # #include\u0026lt;stdio.h\u0026gt; struct Student{ char *name; int age; }; int main(){ //声明结构体数组 struct Student stus[] = { {\u0026#34;lucy\u0026#34;,18}, {\u0026#34;tom\u0026#34;,25}, {\u0026#34;lily\u0026#34;,16} }; for(int i = 0;i \u0026lt; sizeof(stus)/sizeof(stus[0]);i++){ printf(\u0026#34;第%d个学生，姓名：%s，年龄：%d\\n\u0026#34;,i,stus[i].name,stus[i].age); } return 0; } 第0个学生，姓名：lucy，年龄：18 第1个学生，姓名：tom，年龄：25 第2个学生，姓名：lily，年龄：16 结构体指针 # 定义格式：struct 结构体名 *变量名\n#include\u0026lt;stdio.h\u0026gt; struct Student{ char *name; int age; }; int main(){ struct Student s1 = {\u0026#34;lucy\u0026#34;,18}; //声明结构体指针 struct Student *p = \u0026amp;s1; printf(\u0026#34;%#x\\n\u0026#34;,p); //可以通过-\u0026gt;来访问结构体指针指向的结构体成员 printf(\u0026#34;姓名：%s，年龄：%d\u0026#34;,p-\u0026gt;name,p-\u0026gt;age); //解引用 struct Student s = *p; printf(\u0026#34;姓名：%s，年龄：%d\u0026#34;,s.name,s.age); return 0; } 0x62fe00 姓名：lucy，年龄：18 共用体 # 共用体是一种特殊的数据类型，允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体，但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。\n共用体有时也被称为联合或者联合体，这也是 Union 这个单词的本意。\n结构体和共用体的区别在于：结构体的各个成员会占用不同的内存，互相之间没有影响；而共用体的所有成员占用同一段内存，修改一个成员会影响其余所有成员。\n结构体占用的内存大于等于所有成员占用的内存的总和（成员之间可能会存在缝隙），共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术，同一时刻只能保存一个成员的值，如果对新的成员赋值，就会把原来成员的值覆盖掉。\n定义 # union 共用体名{ 共用体所包含的变量或数组 }; 位域 # 有些数据在存储时并不需要占用一个完整的字节，只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态，用 0 和 1 表示足以，也就是用一个二进位。正是基于这种考虑，C语言又提供了一种叫做位域的数据结构。\n在结构体定义时，我们可以指定某个成员变量所占用的二进制位数（Bit），这就是位域。\nstruct 位域结构名 { 位域列表 type [member_name] : width ; }; 元素 描述 type 只能为 int(整型)，unsigned int(无符号整型)，signed int(有符号整型) 三种类型，决定了如何解释位域的值。 member_name 位域的名称。 width 位域中位的数量。宽度必须小于或等于指定类型的位宽度。 C语言标准规定，位域的宽度不能超过它所依附的数据类型的长度。通俗地讲，成员变量都是有类型的，这个类型限制了成员变量的最大长度，:后面的数字不能超过这个长度。\ntypedef # C语言允许为一个数据类型起一个新的别名，C语言允许为一个数据类型起一个新的别名，例如，有一个结构体Stu\n//声明结构体变量 struct Stu s; 使用typedef可以简化\ntypedef struct Stu{} Stu; //声明结构体变量 Stu ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/f571508d/8c7e092d/566f1bc6/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e结构体 \n    \u003cdiv id=\"结构体\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%bb%93%e6%9e%84%e4%bd%93\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eC语言结构体（Struct）从本质上讲是一种自定义的数据类型，只不过这种数据类型比较复杂，是由 int、char、float 等基本类型组成的。你可以认为结构体是一种聚合类型。\u003c/p\u003e","title":"3、结构体与共用体","type":"posts"},{"content":"golang中内置encoding包的各个子包，提供了各种数据与编码、文本格式之间转换的方法\nJSON # JSON支持以下数据类型：\n数字：整数或浮点数，如 123 或 3.14 字符串：用双引号包围的文本，如 \u0026quot;Hello, World\u0026quot; 布尔值：true 或 false 数组：有序的值集合，用方括号表示，如 [1, 2, 3] 对象：键值对集合，用花括号表示，如 {\u0026quot;name\u0026quot;: \u0026quot;John\u0026quot;, \u0026quot;age\u0026quot;: 30} null：表示空值，如 {\u0026quot;address\u0026quot;: null} Go的encoding/json包会根据以下规则在JSON和Go类型之间进行映射：\nJSON类型 Go类型 对象 struct, map[string]T 数组 slice, array 字符串 string 数字 int, int8, …, int64, uint, uint8, …, uint64, float32, float64 布尔值 bool null nil 结构体标签 # type User struct { Name string `json:\u0026#34;user_name\u0026#34;` // 序列化为 {\u0026#34;user_name\u0026#34;: \u0026#34;Alice\u0026#34;} Age int `json:\u0026#34;age,omitempty\u0026#34;` // 零值时忽略（如 0） Email string `json:\u0026#34;email,omitempty\u0026#34;` // 零值时忽略（如 \u0026#34;\u0026#34;） Password string `json:\u0026#34;-\u0026#34;` // 完全忽略该字段 } Marshal、Unmarshal # Struct tag 可以决定 Marshal 和 Unmarshal 函数，如果没有声明Tag，就会使用字段名 只有 struct 中支持导出的 field 才能被 JSON package 序列化，即首字母大写的 field JSON object key 只支持 string Channel、complex、function 等 type 无法进行序列化 数据中如果存在循环引用，则不能进行序列化，因为序列化时会进行递归 Pointer 序列化之后是其指向的值或者是 nil //将对象v序列化为json格式，并返回byte切片 func Marshal(v any) ([]byte, error) //将对象v序列化为json格式，并返回byte切片,prefix为前缀，indent为缩进 func MarshalIndent(v any, prefix, indent string) ([]byte, error) //将json字符串的byte切片反序列化到对象v中 func Unmarshal(data [] byte, v any) error package main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; ) type Stu struct { Id string `json:\u0026#34;id\u0026#34;` Name string `json:\u0026#34;name\u0026#34;` Age int `json:\u0026#34;age\u0026#34;` } func main() { s1 := Stu{\u0026#34;s1\u0026#34;, \u0026#34;lucy\u0026#34;, 18} s2 := Stu{\u0026#34;s2\u0026#34;, \u0026#34;tom\u0026#34;, 20} data := []Stu{s1, s2} b, err := json.Marshal(data) if err != nil { fmt.Println(err) } fmt.Println(string(b)) //[{\u0026#34;id\u0026#34;:\u0026#34;s1\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;lucy\u0026#34;,\u0026#34;age\u0026#34;:18},{\u0026#34;id\u0026#34;:\u0026#34;s2\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;tom\u0026#34;,\u0026#34;age\u0026#34;:20}] } package main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; ) type Stu struct { Id string `json:\u0026#34;id\u0026#34;` Name string `json:\u0026#34;name\u0026#34;` Age int `json:\u0026#34;age\u0026#34;` } func main() { j := `[{\u0026#34;id\u0026#34;:\u0026#34;s1\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;lucy\u0026#34;,\u0026#34;age\u0026#34;:18},{\u0026#34;id\u0026#34;:\u0026#34;s2\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;tom\u0026#34;,\u0026#34;age\u0026#34;:20}]` var data []Stu err := json.Unmarshal([]byte(j), \u0026amp;data) if err != nil { fmt.Println(err) } fmt.Println(data) //[{s1 lucy 18} {s2 tom 20}] } Encode、Decode # 除了 marshal 和 unmarshal 函数，Go 还提供了 Decoder 和 Encoder 对 stream JSON 进行处理，常见 request 中的 Body、文件等。\npackage main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) type Stu struct { Id string `json:\u0026#34;id\u0026#34;` Name string `json:\u0026#34;name\u0026#34;` Age int `json:\u0026#34;age\u0026#34;` } func main() { s1 := Stu{\u0026#34;s1\u0026#34;, \u0026#34;lucy\u0026#34;, 18} s2 := Stu{\u0026#34;s2\u0026#34;, \u0026#34;tom\u0026#34;, 20} data := []Stu{s1, s2} f, err := os.OpenFile(\u0026#34;./stu.json\u0026#34;, os.O_WRONLY|os.O_CREATE, 0777) if err != nil { fmt.Println(err) } defer f.Close() encoder := json.NewEncoder(f) err = encoder.Encode(data) if err != nil { fmt.Println(err) } } package main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) type Stu struct { Id string `json:\u0026#34;id\u0026#34;` Name string `json:\u0026#34;name\u0026#34;` Age int `json:\u0026#34;age\u0026#34;` } func main() { var data []Stu f, err := os.Open(\u0026#34;./stu.json\u0026#34;) if err != nil { fmt.Println(err) } defer f.Close() decoder := json.NewDecoder(f) err = decoder.Decode(\u0026amp;data) if err != nil { fmt.Println(err) } fmt.Println(data) //[{s1 lucy 18} {s2 tom 20}] } 自定义序列化规则 # 在encoding/json包中有两个非常重要的接口，如果任意自定义类型实现了Marshaler或者Unmarshaler接口，就能实现自定义的序列化或者反序列化规则\ntype Marshaler interface { MarshalJSON() ([]byte, error) } type Unmarshaler interface { UnmarshalJSON([]byte) error } 例如，现在我们有一个结构体，其中的 Time 类型字段按照默认的序列化规则是这样的\npackage main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) type User struct { Name string Birthday time.Time } func main() { u := User{Name: \u0026#34;Tom\u0026#34;, Birthday: time.Now()} marshal, err := json.Marshal(\u0026amp;u) if err != nil { fmt.Println(err) } fmt.Println(string(marshal)) // {\u0026#34;Name\u0026#34;:\u0026#34;Tom\u0026#34;,\u0026#34;Birthday\u0026#34;:\u0026#34;2024-06-26T09:11:28.585563+08:00\u0026#34;} } 现在我们只需要自己声明一个类型Time并且实现Marshaler和Unmarshaler接口即可自定义该字段的序列化规则\npackage main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) type Time time.Time var timeFormat = \u0026#34;2006-01-02 15:04:05\u0026#34; func (t *Time) MarshalJSON() ([]byte, error) { format := time.Time(*t).Format(timeFormat) return []byte(`\u0026#34;` + format + `\u0026#34;`), nil } func (t *Time) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, \u0026amp;s); err != nil { return err } location, err := time.ParseInLocation(timeFormat, s, time.Local) if err != nil { return err } *t = Time(location) return nil } type User struct { Name string Birthday Time } func main() { u := User{Name: \u0026#34;Tom\u0026#34;, Birthday: Time(time.Now())} marshal, err := json.Marshal(\u0026amp;u) if err != nil { fmt.Println(err) } fmt.Println(string(marshal)) // {\u0026#34;Name\u0026#34;:\u0026#34;Tom\u0026#34;,\u0026#34;Birthday\u0026#34;:\u0026#34;2024-06-26 09:25:17\u0026#34;} } ","date":"2025-06-27","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/12d682f6/43e96ea1/","section":"文章","summary":"\u003cp\u003egolang中内置\u003ccode\u003eencoding\u003c/code\u003e包的各个子包，提供了各种数据与编码、文本格式之间转换的方法\u003c/p\u003e","title":"4、encoding","type":"posts"},{"content":"Nacos-sdk-go是Nacos的Go语言客户端，它实现了服务发现和动态配置的功能\n官方文档：https://github.com/nacos-group/nacos-sdk-go/blob/master/README_CN.md\n使用限制 # 支持Go\u0026gt;=v1.15版本\n支持Nacos\u0026gt;2.x版本\n安装SDK # go get -u github.com/nacos-group/nacos-sdk-go/v2 API # constant.ClientConfig{ TimeoutMs uint64 // 请求Nacos服务端的超时时间，默认是10000ms NamespaceId string // ACM的命名空间Id Endpoint string // 当使用ACM时，需要该配置. https://help.aliyun.com/document_detail/130146.html RegionId string // ACM\u0026amp;KMS的regionId，用于配置中心的鉴权 AccessKey string // ACM\u0026amp;KMS的AccessKey，用于配置中心的鉴权 SecretKey string // ACM\u0026amp;KMS的SecretKey，用于配置中心的鉴权 OpenKMS bool // 是否开启kms，默认不开启，kms可以参考文档 https://help.aliyun.com/product/28933.html // 同时DataId必须以\u0026#34;cipher-\u0026#34;作为前缀才会启动加解密逻辑 CacheDir string // 缓存service信息的目录，默认是当前运行目录 UpdateThreadNum int // 监听service变化的并发数，默认20 NotLoadCacheAtStart bool // 在启动的时候不读取缓存在CacheDir的service信息 UpdateCacheWhenEmpty bool // 当service返回的实例列表为空时，不更新缓存，用于推空保护 Username string // Nacos服务端的API鉴权Username Password string // Nacos服务端的API鉴权Password LogDir string // 日志存储路径 RotateTime string // 日志轮转周期，比如：30m, 1h, 24h, 默认是24h MaxAge int64 // 日志最大文件数，默认3 LogLevel string // 日志默认级别，值必须是：debug,info,warn,error，默认值是info } constant.ServerConfig{ ContextPath string // Nacos的ContextPath，默认/nacos，在2.0中不需要设置 IpAddr string // Nacos的服务地址 Port uint64 // Nacos的服务端口 Scheme string // Nacos的服务地址前缀，默认http，在2.0中不需要设置 GrpcPort uint64 // Nacos的 grpc 服务端口, 默认为 服务端口+1000, 不是必填 } 使用 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/nacos-group/nacos-sdk-go/v2/clients\u0026#34; \u0026#34;github.com/nacos-group/nacos-sdk-go/v2/common/constant\u0026#34; \u0026#34;github.com/nacos-group/nacos-sdk-go/v2/vo\u0026#34; \u0026#34;github.com/spf13/viper\u0026#34; \u0026#34;log\u0026#34; \u0026#34;strings\u0026#34; ) func main() { connectNacos() // 死循环，模拟服务器运行 for { } } func connectNacos() { // 客户端配置 clientConfig := constant.ClientConfig{ NamespaceId: \u0026#34;\u0026#34;, // 如果需要支持多namespace，我们可以场景多个client,它们有不同的NamespaceId。当namespace是public时，此处填空字符串。 TimeoutMs: 5000, NotLoadCacheAtStart: true, LogDir: \u0026#34;log\u0026#34;, CacheDir: \u0026#34;cache\u0026#34;, LogLevel: \u0026#34;debug\u0026#34;, } // nacos服务端配置 serverConfigs := []constant.ServerConfig{ { IpAddr: \u0026#34;127.0.0.1\u0026#34;, //此处可以使用网址和ip ContextPath: \u0026#34;/nacos\u0026#34;, Port: 8848, Scheme: \u0026#34;http\u0026#34;, }, } // 创建服务发现客户端 namingClient, err := clients.NewNamingClient( vo.NacosClientParam{ ClientConfig: \u0026amp;clientConfig, ServerConfigs: serverConfigs, }, ) if err != nil { log.Fatal(\u0026#34;创建服务发现客户端失败\u0026#34;, err) } // 将客户端实例注册到nacos r, err := namingClient.RegisterInstance(vo.RegisterInstanceParam{ Ip: \u0026#34;127.0.0.1\u0026#34;, Port: 8848, ServiceName: \u0026#34;myserver\u0026#34;, Weight: 10, Enable: true, Healthy: true, Ephemeral: true, Metadata: map[string]string{\u0026#34;idc\u0026#34;: \u0026#34;shanghai\u0026#34;}, ClusterName: \u0026#34;DEFAULT\u0026#34;, // 默认值DEFAULT GroupName: \u0026#34;DEFAULT_GROUP\u0026#34;, // 默认值DEFAULT_GROUP }) if !r || err != nil { log.Fatal(\u0026#34;客户端实例注册失败\u0026#34;, err) } // 创建动态配置客户端 configClient, err := clients.NewConfigClient( vo.NacosClientParam{ ClientConfig: \u0026amp;clientConfig, ServerConfigs: serverConfigs, }, ) if err != nil { log.Fatal(\u0026#34;创建动态配置客户端失败\u0026#34;, err) } // 获取多个配置 confStr1, err := configClient.GetConfig(vo.ConfigParam{ DataId: \u0026#34;config1.yml\u0026#34;, Group: \u0026#34;DEFAULT_GROUP\u0026#34;, }) confStr2, err := configClient.GetConfig(vo.ConfigParam{ DataId: \u0026#34;config2.yml\u0026#34;, Group: \u0026#34;DEFAULT_GROUP\u0026#34;, }) log.Printf(\u0026#34;读取到配置:\\n%s\\n\u0026#34;, confStr1) // 解析配置文件 config := viper.New() config.SetConfigType(\u0026#34;yml\u0026#34;) config.ReadConfig(strings.NewReader(confStr1)) config.MergeConfig(strings.NewReader(confStr2)) username := config.GetString(\u0026#34;mysql.username\u0026#34;) password := config.GetString(\u0026#34;mysql.password\u0026#34;) port := config.GetString(\u0026#34;server.port\u0026#34;) fmt.Printf(\u0026#34;username=%s,password=%s,port=%s\\n\u0026#34;, username, password, port) } ","date":"2025-06-25","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/e971af3c/","section":"文章","summary":"\u003cp\u003eNacos-sdk-go是Nacos的Go语言客户端，它实现了服务发现和动态配置的功能\u003c/p\u003e","title":"4、nacos","type":"posts"},{"content":" 参数 # options.App 结构包含应用程序配置。 它被传递给 wails.Run() 方法：\nimport ( \u0026#34;github.com/wailsapp/wails/v2/pkg/options\u0026#34; \u0026#34;github.com/wailsapp/wails/v2/pkg/options/assetserver\u0026#34; \u0026#34;github.com/wailsapp/wails/v2/pkg/options/linux\u0026#34; \u0026#34;github.com/wailsapp/wails/v2/pkg/options/mac\u0026#34; \u0026#34;github.com/wailsapp/wails/v2/pkg/options/windows\u0026#34; ) func main() { err := wails.Run(\u0026amp;options.App{ Title: \u0026#34;Menus Demo\u0026#34;, Width: 800, Height: 600, DisableResize: false, Fullscreen: false, WindowStartState: options.Maximised, Frameless: true, MinWidth: 400, MinHeight: 400, MaxWidth: 1280, MaxHeight: 1024, StartHidden: false, HideWindowOnClose: false, BackgroundColour: \u0026amp;options.RGBA{R: 0, G: 0, B: 0, A: 255}, AlwaysOnTop: false, AssetServer: \u0026amp;assetserver.Options{ Assets: assets, Handler: assetsHandler, Middleware: assetsMidldeware, }, Menu: app.applicationMenu(), Logger: nil, LogLevel: logger.DEBUG, LogLevelProduction: logger.ERROR, OnStartup: app.startup, OnDomReady: app.domready, OnShutdown: app.shutdown, OnBeforeClose: app.beforeClose, CSSDragProperty: \u0026#34;--wails-draggable\u0026#34;, CSSDragValue: \u0026#34;drag\u0026#34;, EnableDefaultContextMenu: false, EnableFraudulentWebsiteDetection: false, Bind: []interface{}{ app, }, EnumBind: []interface{}{ AllWeekdays, }, ErrorFormatter: func(err error) any { return err.Error() }, SingleInstanceLock: \u0026amp;options.SingleInstanceLock{ UniqueId: \u0026#34;c9c8fd93-6758-4144-87d1-34bdb0a8bd60\u0026#34;, OnSecondInstanceLaunch: app.onSecondInstanceLaunch, }, DragAndDrop: \u0026amp;options.DragAndDrop{ EnableFileDrop: false, DisableWebViewDrop: false, CSSDropProperty: \u0026#34;--wails-drop-target\u0026#34;, CSSDropValue: \u0026#34;drop\u0026#34;, }, Windows: \u0026amp;windows.Options{ WebviewIsTransparent: false, WindowIsTranslucent: false, BackdropType: windows.Mica, DisablePinchZoom: false, DisableWindowIcon: false, DisableFramelessWindowDecorations: false, WebviewUserDataPath: \u0026#34;\u0026#34;, WebviewBrowserPath: \u0026#34;\u0026#34;, Theme: windows.SystemDefault, CustomTheme: \u0026amp;windows.ThemeSettings{ DarkModeTitleBar: windows.RGB(20, 20, 20), DarkModeTitleText: windows.RGB(200, 200, 200), DarkModeBorder: windows.RGB(20, 0, 20), LightModeTitleBar: windows.RGB(200, 200, 200), LightModeTitleText: windows.RGB(20, 20, 20), LightModeBorder: windows.RGB(200, 200, 200), }, // ZoomFactor is the zoom factor for the WebView2. 这是匹配 Edge 用户激活放大或缩小的选项 ZoomFactor: float64, // IsZoomControlEnabled enables the zoom factor to be changed by the user. IsZoomControlEnabled: bool, // User messages that can be customised Messages: *windows.Messages // OnSuspend is called when Windows enters low power mode OnSuspend: func() // OnResume is called when Windows resumes from low power mode OnResume: func(), // Disable GPU hardware acceleration for the webview WebviewGpuDisabled: false, }, Mac: \u0026amp;mac.Options{ TitleBar: \u0026amp;mac.TitleBar{ TitlebarAppearsTransparent: true, HideTitle: false, HideTitleBar: false, FullSizeContent: false, UseToolbar: false, HideToolbarSeparator: true, OnFileOpen: app.onFileOpen, OnUrlOpen: app.onUrlOpen, }, Appearance: mac.NSAppearanceNameDarkAqua, WebviewIsTransparent: true, WindowIsTranslucent: false, About: \u0026amp;mac.AboutInfo{ Title: \u0026#34;My Application\u0026#34;, Message: \u0026#34;© 2021 Me\u0026#34;, Icon: icon, }, }, Linux: \u0026amp;linux.Options{ Icon: icon, WindowIsTranslucent: false, WebviewGpuPolicy: linux.WebviewGpuPolicyAlways, ProgramName: \u0026#34;wails\u0026#34; }, Debug: options.Debug{ OpenInspectorOnStartup: false, }, }) if err != nil { log.Fatal(err) } } 窗口相关 # 标题 # 窗口标题栏中显示的文本。\n名称：Title 类型：string 尺寸相关 # 宽度 # 窗口的初始宽度。\n名称：Width 类型：int 默认值：1024 高度 # 窗口的初始高度。\n名称：Height 类型：int 默认值：768 最小宽度 # 这将设置窗口的最小宽度。 如果给出的值 Width 小于这个值，窗口将被设置为 MinWidth 默认值。\n名称：MinWidth 类型：int 最小高度 # 这将设置窗口的最小高度。 如果给出的值 Height 小于这个值，窗口将被设置为 MinHeight 默认值。\n名称：MinHeight 类型：int 最大宽度 # 这将设置窗口的最大宽度。 如果给出的值 Width 大于这个值，窗口将被设置为 MaxWidth 默认值。\n名称：MaxWidth\n类型：int\n最大高度 # 这将设置窗口的最大高度。 如果给出的值 Height 大于这个值，窗口将被设置为 MaxHeight 默认值。\n名称：MaxHeight\n类型：int\n禁用调整窗口尺寸 # 默认情况下，主窗口可调整大小。 将此设置为 true 将使其保持固定大小。\n名称：DisableResize 类型：bool 窗口启动状态 # 定义窗口在启动时应如何呈现。\n值 Win Mac Lin Fullscreen（全屏） ✅ ✅ ✅ Maximised（最大化） ✅ ✅ ✅ Minimised（最小化） ✅ ❌ ✅ 名称：WindowStartState 类型：options.WindowStartState 无边框 # 设置为true时，窗口将没有边框或标题栏。\n名称：Frameless 类型：bool 启动时隐藏窗口 # 设置为 true 时，应用程序将被隐藏。\n名称：StartHidden 类型：bool 关闭时隐藏窗口 # 默认情况下，关闭窗口将关闭应用程序。 将此设置为 true 意味着关闭窗口将隐藏窗口。\n名称：HideWindowOnClose 类型：bool 背景颜色 # 此值是窗口的默认背景颜色。 示例：options.NewRGBA(255,0,0,128) ，红色，透明度为 50%\n名称：BackgroundColour 类型：*options.RGBA 默认值：white 窗口固定在最顶层 # 窗口在失去焦点时应保持在其他窗口之上。\n名称：AlwaysOnTop\n类型：bool\n菜单 # 应用程序要使用的菜单。\n名称：Menu 类型：*menu.Menu 日志相关 # 应用程序要使用的记录器。\n名称：Logger 类型：logger.Logger 默认值：Logs to Stdout 日志级别 # 默认日志级别。\n名称：LogLevel 类型：logger.LogLevel 默认值：开发模式为 Info, 生产模式为 Error 生产日志级别 # 生产构建的默认日志级别。\n名称：LogLevelProduction 类型：logger.LogLevel 默认值：Error 生命周期相关 # 应用启动回调 # 此回调在前端创建之后调用，但在 index.html 加载之前调用。 它提供了应用程序上下文。\n名称：OnStartup 类型：func(ctx context.Context) 前端 Dom 加载完成回调 # 在前端加载完毕 index.html 及其资源后调用此回调。 它提供了应用程序上下文。\n名称：OnDomReady 类型：func(ctx context.Context) 应用退出回调 # 在前端被销毁之后，应用程序终止之前，调用此回调。 它提供了应用程序上下文。\n名称：OnShutdown 类型：func(ctx context.Context) 应用关闭前回调 # 如果设置了此回调，它将在通过单击窗口关闭按钮或调用runtime.Quit即将退出应用程序时被调用。\n名称：OnBeforeClose 类型：func(ctx context.Context) bool 返回 true 将导致应用程序继续，false 将继续正常关闭。这有助于与用户确认他们希望退出程序。 func (b *App) beforeClose(ctx context.Context) (prevent bool) { dialog, err := runtime.MessageDialog(ctx, runtime.MessageDialogOptions{ Type: runtime.QuestionDialog, Title: \u0026#34;Quit?\u0026#34;, Message: \u0026#34;Are you sure you want to quit?\u0026#34;, }) if err != nil { return false } return dialog != \u0026#34;Yes\u0026#34; } 窗口拖动相关 # CSS 拖动属性 # 指示用于标识哪些元素可用于拖动窗口的 CSS 属性。 默认值：--wails-draggable\n名称：CSSDragProperty 类型：string CSS 拖动值 # 指示 CSSDragProperty 样式应该具有什么值才能拖动窗口。 默认值：drag\n名称：CSSDragValue 类型：string 启用欺诈网站检测 # EnableFraudulentWebsiteDetection启用了欺诈性内容的扫描服务，如恶意软件或网络钓鱼尝试。这些服务可能会从您的应用程序发送信息，如导航到的URL，以及可能的其他内容，发送到苹果和微软的云服务。\n名称：EnableFraudulentWebsiteDetection 类型：bool 绑定前端相关 # 绑定 # 定义需要绑定到前端的方法的结构体实例的切片。\n名称：Bind 类型：[]interface{} 枚举绑定 # 需要绑定到前端的枚举数组的切片。\n名称：EnumBind 类型： []interface{} 单实例锁定 # 启用单实例锁定。这意味着您的应用程序一次只能运行一个实例。\n名称：SingleInstanceLock\n类型：*options.SingleInstanceLock\n独特的ID # 此ID用于在Windows和macOS上生成互斥名称，在Linux上生成dbus名称。使用UUID来确保ID是唯一的。\n名称：UniqueId\n类型：string\n在SecondInstanceLaunch上 # 启动应用程序的第二个实例时调用的回调。\n名称：OnSecondInstanceLaunch\n类型：func(secondInstanceData SecondInstanceData)\n拖放 # 定义窗口上拖放事件的行为。\n名称：DragAndDrop 类型：options.DragAndDrop 启用文件Drop # EnableFileDrop启用了wails的拖放功能，该功能可以返回文件的绝对路径中的拖放。\n名称：EnableFileDrop 类型：bool 默认：false Windows Option # 名称：Windows 类型：*windows.Options Webview 透明 # 当使用0的alpha值时，将此设置为true将使webview背景透明。这意味着，如果您在CSS中使用rgba(0,0,0,0)作为background-color，主机窗口将显示。\n名称：WebviewIsTransparent\n类型：bool\n窗口透明 # 将此设置为true将使窗口背景半透明。通常与WebviewIsTransparent结合使用。\n名称：WindowIsTranslucent\n类型：bool\n禁用窗口图标 # 将此设置为true将删除标题栏左上角的图标。\n名称：DisableWindowIcon\n类型：bool\n","date":"2025-05-22","externalUrl":null,"permalink":"/posts/2d3150b2/9f158c96/f88084c8/0091cfb9/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e参数 \n    \u003cdiv id=\"参数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8f%82%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eoptions.App\u003c/code\u003e 结构包含应用程序配置。 它被传递给 \u003ccode\u003ewails.Run()\u003c/code\u003e 方法：\u003c/p\u003e","title":"4、App参数","type":"posts"},{"content":" Go 的 IO 模型 # 核心接口 # Go语言的IO操作建立在几个核心接口上，这些接口构成了灵活而一致的IO模型。\n在io包中定义了几个最基础的接口：\ntype Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type Closer interface { Close() error } 这些简单的接口组合成更复杂的接口，如：\ntype ReadWriter interface { Reader Writer } type ReadCloser interface { Reader Closer } type WriteCloser interface { Writer Closer } type ReadWriteCloser interface { Reader Writer Closer } IO操作模式 # Go的IO操作遵循一些常见模式：\n数据块处理：大多数IO函数处理的是字节切片（[]byte），而非单个字节。 错误处理：IO操作总是返回读/写的字节数和一个错误值。 EOF标识：当读取到文件或流的末尾时，返回特殊的io.EOF错误。 资源管理：使用defer和Close()确保资源正确释放。 各个IO相关包的职责 # Go标准库中与IO相关的包有多个，各自负责不同层次的功能：\nio：提供基础的IO接口和函数 io/ioutil：简化常见IO操作（Go 1.16后部分功能迁移到io和os包） os：提供操作系统功能，包括文件操作 bufio：提供缓冲IO，提高性能 bytes和strings：提供对字节切片和字符串的类似文件的访问 os.File # 在 Golang 中内置包os中，打开文件使用 Open 函数（只读）和OpenFile函数（可切换模式），关闭文件使用 Close 函数，打开文件、关闭文件以及大多数文件操作都涉及到一个很重要的结构体 os.File 结构体。\n实现了io.Reader、io.Writer、io.Closer等接口\ntype File struct { *file // file 指针 } type file struct { //是一个FD结构体类型，是一个文件的唯一标志，每一个被打开的文件在操作系统中，都会有一个文件标志符来唯一标识一个文件 pfd poll.FD //文件名 name string //文件的路径信息，也是一个结构体 dirinfo *dirInfo //是一个 bool 类型，表明该文件是否可以被追加写入内容。 appendMode bool } io.EOF # 是 error 类型。\n根据 reader 接口的说明，在 n \u0026gt; 0 且数据被读完了的情况下，返回的 error 有可能是 EOF 也有可能是 nil。\nvar EOF = errors.New(\u0026#34;EOF\u0026#34;) 文件操作 # 打开文件 # 使用os.Open和os.OpenFile打开文件：\nOpen # Open 函数接受一个字符串类型的文件名做为参数，如果打开成功，则返回一个 File 结构体的指针，否则就返回error错误信息。\n底层使用的是OpenFile(name, O_RDONLY, 0)，只读\nos.Open(name string) (*File, error) // 打开文件进行读取 file, err := os.Open(\u0026#34;input.txt\u0026#34;) if err != nil { log.Fatal(err) } defer file.Close() // 确保文件会被关闭 OpenFile # os.OpenFile(name string, flag int, perm FileMode) (file *File, err error) //name:文件路径 //flag:文件打开方式，多个可使用|分隔开 //perm:打开文件的模式，即权限，例如0666、0777 // 打开文件进行读写 rwFile, err := os.OpenFile(\u0026#34;data.txt\u0026#34;, os.O_RDWR|os.O_CREATE, 0666) if err != nil { log.Fatal(err) } defer rwFile.Close() flag可选参数 # 打开方式 说明 os.O_RDONLY 只读方式打开 os.O_WRONLY 只写方式打开 os.O_RDWR 读写方式打开 os.O_APPEND 追加方式打开 os.O_CREATE 不存在，则创建 os.O_EXCL 如果文件存在，且标定了O_CREATE的话，则产生一个错误 os.O_TRUNG 如果文件存在，且它成功地被打开为只写或读写方式，将其长度裁剪唯一。（覆盖） os.O_NOCTTY 如果文件名代表一个终端设备，则不把该设备设为调用进程的控制设备 os.O_NONBLOCK 如果文件名代表一个FIFO,或一个块设备，字符设备文件，则在以后的文件及I/O操作中置为非阻塞模式。 os.O_SYNC 当进行一系列写操作时，每次都要等待上次的I/O操作完成再进行。 // 常用标志组合 // 只读模式 file, err := os.OpenFile(\u0026#34;file.txt\u0026#34;, os.O_RDONLY, 0) // 只写模式，如果文件不存在则创建 file, err := os.OpenFile(\u0026#34;file.txt\u0026#34;, os.O_WRONLY|os.O_CREATE, 0666) // 追加模式，如果文件不存在则创建 file, err := os.OpenFile(\u0026#34;file.txt\u0026#34;, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) // 读写模式，如果文件不存在则创建 file, err := os.OpenFile(\u0026#34;file.txt\u0026#34;, os.O_RDWR|os.O_CREATE, 0666) // 创建新文件，如果文件已存在则截断为空 file, err := os.OpenFile(\u0026#34;file.txt\u0026#34;, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 关闭文件 # 使用 File 指针来调用 Close 函数，如果关闭失败会返回 error 错误信息。\n一般在构建 File 对象后，使用defer file.Close()来保证文件关闭。\nfunc (file *File) Close() error 其他方法 # Seek # Seek设置下一次读/写的位置。\noffset：为相对偏移量\nwhence：决定相对位置：0为相对文件开头，1为相对当前位置，2为相对文件结尾。\n它返回新的偏移量（相对开头）和可能的错误。\nfunc (f *File) Seek(offset int64, whence int) (ret int64, err error) Truncate # Truncate改变文件的大小，它不会改变I/O的当前位置。 如果截断文件，多出的部分就会被丢弃，也就是从头开始保留size个字节。如果出错，错误底层类型是*PathError，size = 0则清空文件。\nfunc (f *File) Truncate(size int64) error 也可以使用os包提供的方法，一样的作用，name为文件名\nfunc Truncate(name string, size int64) error 获取文件信息 # type FileInfo interface { Name() string // 文件的名字 Size() int64 // 普通文件返回值表示其大小；其他文件的返回值含义各系统不同 Mode() FileMode // 文件的模式位 (例-rw-rw-r--) ModTime() time.Time // 文件的修改时间 IsDir() bool // 等价于Mode().IsDir() Sys() interface{} // 底层数据来源（可以返回nil） } os.fileStat实现了FileInfo接口的所有方法，使用os.Stat(path)函数可以获取name对应文件的fileStat实例\nfunc Stat(name string) (FileInfo, error) 示例：\n// 获取文件信息 info, err := os.Stat(\u0026#34;myfile.txt\u0026#34;) if err != nil { if os.IsNotExist(err) { fmt.Println(\u0026#34;文件不存在\u0026#34;) } else { log.Fatal(err) } return } fmt.Println(\u0026#34;名称:\u0026#34;, info.Name()) fmt.Println(\u0026#34;大小:\u0026#34;, info.Size(), \u0026#34;字节\u0026#34;) fmt.Println(\u0026#34;权限:\u0026#34;, info.Mode()) fmt.Println(\u0026#34;修改时间:\u0026#34;, info.ModTime()) fmt.Println(\u0026#34;是目录:\u0026#34;, info.IsDir()) 读写文件 # io包 # 由于os.File实现了io.Reader、io.Writer接口，所以这里实际调用的是File中实现的Read()和Write()方法\n常用方法 # //读，文件读取结束的标志是返回的 n 等于 0，并且err会返回io.EOF，单次读取的大小和b的大小一样 func (f *File) Read(b []byte) (n int, err error) //写，以字节数组写入 func (f *File) Write(b []byte) (n int, err error) //写，以字符串写入 func (f *File) WriteString(s string) (n int, err error) //复制文件 io.Copy(dst io.Writer, src io.Reader) (written int64, err error) 读 # func (f *File) Read(b []byte) (n int, err error)，其中b为保存读到数据的字节切片，n为读到的字节长度，如果读到文件末尾，方法会返回0,io.EOF\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;os\u0026#34; ) func main() { file, err := os.Open(\u0026#34;./test.txt\u0026#34;) if err != nil { fmt.Println(err) } defer file.Close() //定义切片存放每次读取字节 data := make([]byte, 1024) //声明切片，将每次读到的数据存放起来 var result []byte for { i, err := file.Read(data) if err != nil \u0026amp;\u0026amp; err != io.EOF { fmt.Println(err) break } if i == 0 { break } result = append(result, data[:i]...) } fmt.Println(string(result)) } 写 # func (f *File) Write(b []byte) (n int, err error)\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) func main() { file, err := os.OpenFile(\u0026#34;./test.txt\u0026#34;, os.O_RDWR|os.O_CREATE, 0777) if err != nil { fmt.Println(err) } defer file.Close() s := \u0026#34;hello world\\nhello golang\u0026#34; //写入文件,将字符串转字节数组，也可以直接使用WriteString n, err := file.Write([]byte(s)) if err != nil { fmt.Println(err) } else { fmt.Println(\u0026#34;write success,byte count =\u0026#34;, n) } } 复制 # func Copy(dst Writer, src Reader) (written int64, err error)，此处注意，由于File已经实现了Writer、Reader接口，所以可以直接传入File实例\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;os\u0026#34; ) func main() { file, err := os.Open(\u0026#34;./test.txt\u0026#34;) if err != nil { fmt.Println(err) } defer file.Close() fileCp, err := os.OpenFile(\u0026#34;./testCp.txt\u0026#34;, os.O_CREATE|os.O_WRONLY, 0777) if err != nil { fmt.Println(err) } defer fileCp.Close() n, err := io.Copy(fileCp, file) if err != nil { fmt.Println(err) } fmt.Println(\u0026#34;copy success,n =\u0026#34;, n) } ioutil包 # **注意：**此包内的方法，在Go 1.16后，被移动到了io或os包内。\n常用方法 # //读，由于这两个方法都是一次读取文件全部数据，所以不适合大文件，该方法在 Go1.16 被移动到 os 包 ioutil.ReadFile(filename string) ([]byte, error) //读，这个方法需要打开一个文件，并将文件句柄传入参数 ioutil.ReadAll(r io.Reader) ([]byte, error) //读取单层目录下所有文件(夹)，返回FileInfo切片 ioutil.ReadDir(dirname string) ([]fs.FileInfo, error) //写，文件不存在则创建文件，存在则清空文件，FileMode可以直接用0666或者0777 ioutil.WriteFile(filename string, data []byte, perm os.FileMode) 读 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; ) func main() { data, err := ioutil.ReadFile(\u0026#34;./test.txt\u0026#34;) if err != nil { fmt.Println(err) } //将字节数组转为string类型 fmt.Println(string(data)) } 写 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; ) func main() { s := \u0026#34;hello world\\nhello golang\u0026#34; err := ioutil.WriteFile(\u0026#34;./test.txt\u0026#34;, []byte(s), 0777) if err != nil { fmt.Println(err) } } bufio包 # bufio 包实现了缓存IO。它包装了 io.Reader 和 io.Writer 对象，创建了另外的Reader和Writer对象，它们也实现了 io.Reader 和 io.Writer 接口，不过它们是有缓存的。\nio操作本身的效率并不低，低的是频繁的访问本地磁盘的文件。所以bufio就提供了缓冲区(分配一块内存)，读和写都先在缓冲区中，最后再读写文件，来降低访问本地磁盘的次数，从而提高效率。\n缓冲区原理 # 无论读写，设立缓冲区的目的就是：减少磁盘io的次数，以达到提高性能的目的 读的时候，bufio.Read(p []byte) 相当于每次读取大小len(p)的内容 当缓存区有内容的时，将缓存区内容全部填入p并清空缓存区 当缓存区没有内容的时候且len(p)\u0026gt;len(buf)，即要读取的内容比缓存区还要大，直接去文件读取即可 当缓存区没有内容的时候且len(p)\u0026lt;len(buf)，即要读取的内容比缓存区小，缓存区从文件读取内容充满缓存区，并将p填满（此时缓存区有剩余内容，供下一次读取，这样也就减少了下次一读取磁盘io的数据量） 以后再次读取时缓存区有内容，将缓存区内容全部填入p并清空缓存区（此时和情况1一样） 写的时候，bufio.Write(p []byte) 相当于写入len(p)大小的内容 判断buf中可用容量是否可以放下 p 如果能放下，直接把p拼接到buf后面，即把内容放到缓冲区 如果缓冲区的可用容量不足以放下，且此时缓冲区是空的，直接把p写入文件即可 如果缓冲区的可用容量不足以放下，且此时缓冲区有内容，则用p把缓冲区填满，把缓冲区所有内容写入文件，并清空缓冲区 判断p的剩余内容大小能否放到缓冲区，如果能放下（此时和步骤1情况一样）则把内容放到缓冲区 如果p的剩余内容依旧大于缓冲区，（注意此时缓冲区是空的，情况和步骤3一样）则把p的剩余内容直接写入文件 常用方法 # bufio.Reader # //NewReader 函数用来返回一个默认大小 buffer 的 Reader 对象（默认大小是 4096） func NewReader(rd io.Reader) *Reader //NewReaderSize 返回一个指定大小 buffer（size 最小为 16）的 Reader 对象，如果 io.Reader 参数已经是一个足够大的 Reader，它将返回该 Reader func NewReaderSize(rd io.Reader, size int) *Reader //Peek 返回缓存的一个切片，该切片引用缓存中前 n 个字节的数据，该操作不会将数据读出，只是引用 func (b *Reader) Peek(n int) ([]byte, error) //Read 从 b 中读出数据到 p 中，返回读出的字节数和遇到的错误 func (b *Reader) Read(p []byte) (n int, err error) //Buffered 返回缓存中未读取的数据的长度 func (b *Reader) Buffered() int //ReadSlice 从输入中读取，直到遇到第一个界定符（delim）为止，返回一个指向缓存（也就意味着随着缓存改变而改变，所以一般用ReadBytes和ReadString）中字节的 slice，在下次调用读操作（read）时，这些字节会无效，如果在找到界定符之前缓存已经满了，ReadSlice 会返回 bufio.ErrBufferFull 错误 func (b *Reader) ReadSlice(delim byte) (line []byte, err error) //ReadBytes 和ReadSlice区别就是返回的是当前缓存中字节slice的一个拷贝 func (b *Reader) ReadBytes(delim byte) (line []byte, err error) //ReadString 和ReadBytes一样，只不过返回的是字符串，底层调用的是ReadBytes func (b *Reader) ReadString(delim byte) (string, error) //ReadLine 逐行读取，底层调用的是ReadSlice(\u0026#39;\\n\u0026#39;) func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) //读取单个UTF-8编码的Unicode字符，并返回字符及其字节大小 func (b *Reader) ReadRune() (r rune, size int, err error) bufio.Writer # //返回默认缓冲大小的 Writer 对象(默认是4096)，关闭文件前一定要Flush，将缓冲区的数据写入w func NewWriter(w io.Writer) *Writer //指定缓冲大小创建一个 Writer 对象，关闭文件前一定要Flush，将缓冲区的数据写入w func NewWriterSize(w io.Writer, size int) *Writer //写入单个字节，直接写入文件 func (b *Writer) WriteByte(c byte) error //写入单个 Unicode 指针返回写入字节数错误信息，直接写入文件 func (b *Writer) WriteRune(r rune) (size int, err error) //写入字符串并返回写入字节数和错误信息，直接写入文件 func (b *Writer) WriteString(s string) (int, error) bufio.Scanner # // 创建一个用于逐个读取输入缓冲区的扫描器 func NewScanner(r io.Reader) *Scanner // 用于读取输入缓冲区中的下一个数据块，并将其保存在内部的缓冲区中。如果读取成功，则返回 true；如果已经读取了所有数据或者发生了错误，则返回 false。 func (s *Scanner) Scan() bool // 用于获取内部缓冲区中的文本内容，通常与 Scan() 方法一起使用，用于获取读取的数据。 func (s *Scanner) Text() string // 用于获取内部缓冲区中的字节内容，通常与 Scan() 方法一起使用，用于获取读取的数据。 func (s *Scanner) Bytes() []byte // 用于获取在读取输入时发生的错误信息，如果读取过程中没有发生错误，则返回 nil；否则，返回一个非 nil 的错误对象。 func (s *Scanner) Err() error // 用于自定义输入缓冲区大小，接受一个 []byte 类型的参数，用于指定缓冲区的大小。 func (s *Scanner) Buffer(buf []byte, max int) // 用于指定一个分割函数，将输入分割成多个数据块，接受一个 func([]byte) bool 类型的参数，该函数在每次读取输入时被调用，用于判断是否需要将当前数据块分割成多个小块。通常用于处理非常大的数据块，以避免内存溢出等问题。 func (s *Scanner) Split(split SplitFunc) 读 # package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;os\u0026#34; ) func main() { file, err := os.Open(\u0026#34;./test.txt\u0026#34;) if err != nil { fmt.Println(err) } defer file.Close() fileBuf := bufio.NewReader(file) data := make([]byte, 1024) var result []byte for { i, err := fileBuf.Read(data) if err != nil \u0026amp;\u0026amp; err != io.EOF { fmt.Println(err) break } if i == 0 { break } result = append(result, data[:i]...) } fmt.Println(string(result)) } 逐字读取utf8字符 # package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) func main() { file, err := os.OpenFile(\u0026#34;./test.txt\u0026#34;, os.O_RDWR, 777) if err != nil { fmt.Errorf(\u0026#34;%+v\u0026#34;, err) } reader := bufio.NewReader(file) r, i, err := reader.ReadRune() for i \u0026gt; 0 \u0026amp;\u0026amp; err == nil { fmt.Print(string(r)) r, i, err = reader.ReadRune() } file.Close() } 逐行读 # file, err := os.Open(\u0026#34;data.txt\u0026#34;) if err != nil { log.Fatal(err) } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() // 获取当前行文本 // 或者使用scanner.Bytes()获取[]byte fmt.Println(line) } if err := scanner.Err(); err != nil { log.Fatal(err) } 自定义分割函数 # Scanner默认按行分割，但可以自定义分割方式，内置的分割函数：\nbufio.ScanLines：按行分割（默认） bufio.ScanWords：按单词分割 bufio.ScanRunes：按UTF-8编码的Unicode码点分割 bufio.ScanBytes：按字节分割 file, err := os.Open(\u0026#34;words.txt\u0026#34;) if err != nil { log.Fatal(err) } defer file.Close() scanner := bufio.NewScanner(file) // 使用单词分割函数 scanner.Split(bufio.ScanWords) wordCount := 0 for scanner.Scan() { word := scanner.Text() wordCount++ fmt.Printf(\u0026#34;单词%d: %s\\n\u0026#34;, wordCount, word) } if err := scanner.Err(); err != nil { log.Fatal(err) } 写 # 注意：关闭文件前一定要Flush()\npackage main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) func main() { file, err := os.OpenFile(\u0026#34;./test.txt\u0026#34;, os.O_WRONLY|os.O_CREATE, 0777) if err != nil { fmt.Println(err) } defer file.Close() fileBuf := bufio.NewWriter(file) s := \u0026#34;hello world\\nhello golang\u0026#34; fileBuf.Write([]byte(s)) //关闭文件前一定要fulsh，将缓冲区内的数据写入文件 fileBuf.Flush() } 常见读取文件示例 # 一次性读取整个文件到内存（适合小文件） # // Go 1.16前 data, err := ioutil.ReadFile(\u0026#34;input.txt\u0026#34;) if err != nil { log.Fatal(err) } fmt.Println(string(data)) // Go 1.16+ data, err := os.ReadFile(\u0026#34;input.txt\u0026#34;) if err != nil { log.Fatal(err) } fmt.Println(string(data)) 使用缓冲区分块读取（适合大文件） # file, err := os.Open(\u0026#34;largefile.txt\u0026#34;) if err != nil { log.Fatal(err) } defer file.Close() buffer := make([]byte, 1024) // 1KB缓冲区 for { bytesRead, err := file.Read(buffer) if err != nil { if err != io.EOF { log.Fatal(err) } break // 读取完毕 } // 处理读取的数据 fmt.Print(string(buffer[:bytesRead])) } 使用bufio逐行读取（文本文件） # file, err := os.Open(\u0026#34;lines.txt\u0026#34;) if err != nil { log.Fatal(err) } defer file.Close() scanner := bufio.NewScanner(file) // 设置每行最大长度（可选，默认64K） // scanner.Buffer(make([]byte, 1024), 1024*1024) // 缓冲区和最大容量 lineCount := 0 for scanner.Scan() { line := scanner.Text() lineCount++ fmt.Printf(\u0026#34;第%d行: %s\\n\u0026#34;, lineCount, line) } if err := scanner.Err(); err != nil { log.Fatal(err) } 读取特定位置的数据 # file, err := os.Open(\u0026#34;data.bin\u0026#34;) if err != nil { log.Fatal(err) } defer file.Close() // 移动到文件第100个字节 _, err = file.Seek(100, 0) if err != nil { log.Fatal(err) } // 读取10个字节 data := make([]byte, 10) count, err := file.Read(data) if err != nil { log.Fatal(err) } fmt.Printf(\u0026#34;读取了%d字节: %v\\n\u0026#34;, count, data) 常见写入文件示例 # 一次性写入（适合小数据） # // Go 1.16前 data := []byte(\u0026#34;Hello, 世界!\u0026#34;) err := ioutil.WriteFile(\u0026#34;output.txt\u0026#34;, data, 0666) if err != nil { log.Fatal(err) } // Go 1.16+ data := []byte(\u0026#34;Hello, 世界!\u0026#34;) err := os.WriteFile(\u0026#34;output.txt\u0026#34;, data, 0666) if err != nil { log.Fatal(err) } 分块写入（适合大数据） # file, err := os.Create(\u0026#34;output.txt\u0026#34;) if err != nil { log.Fatal(err) } defer file.Close() // 多次写入 for i := 0; i \u0026lt; 5; i++ { data := []byte(fmt.Sprintf(\u0026#34;第%d行数据\\n\u0026#34;, i+1)) _, err := file.Write(data) if err != nil { log.Fatal(err) } } 使用缓冲写入（提高性能） # file, err := os.Create(\u0026#34;buffered.txt\u0026#34;) if err != nil { log.Fatal(err) } defer file.Close() writer := bufio.NewWriter(file) // 写入缓冲区 for i := 0; i \u0026lt; 1000; i++ { fmt.Fprintf(writer, \u0026#34;第%d行\\n\u0026#34;, i+1) } // 确保缓冲区内容写入文件 err = writer.Flush() if err != nil { log.Fatal(err) } 追加到文件 # file, err := os.OpenFile(\u0026#34;log.txt\u0026#34;, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) if err != nil { log.Fatal(err) } defer file.Close() data := []byte(\u0026#34;新的日志条目\\n\u0026#34;) if _, err := file.Write(data); err != nil { log.Fatal(err) } 目录操作 # //创建 os.Mkdir(name string, perm FileMode) error //创建目录，只创建一层，相当于mkdir os.MkdirAll(path string, perm FileMode) error //创建多层目录，相当于mkdir -p os.Create(name string) (file *File, err error) //创建文件，如果存在则覆盖 //移动 os.Rename(oldpath, newpath string) error //重命名、移动文件 //删除 os.Remove(name string) error //删除单个 os.RemoveAll(path string) error //递归删除目录 创建和删除目录 # // 创建单个目录 err := os.Mkdir(\u0026#34;newdir\u0026#34;, 0755) if err != nil { log.Fatal(err) } // 创建多级目录 err = os.MkdirAll(\u0026#34;path/to/nested/dir\u0026#34;, 0755) if err != nil { log.Fatal(err) } // 删除单个目录（必须为空） err = os.Remove(\u0026#34;emptydir\u0026#34;) if err != nil { log.Fatal(err) } // 删除目录及其内容 err = os.RemoveAll(\u0026#34;dir-with-content\u0026#34;) if err != nil { log.Fatal(err) } 遍历目录内容 # 列出单级目录内容 # // 打开目录 dir, err := os.Open(\u0026#34;.\u0026#34;) if err != nil { log.Fatal(err) } defer dir.Close() // 读取目录中的条目 entries, err := dir.ReadDir(-1) // -1表示读取所有条目 if err != nil { log.Fatal(err) } // 遍历条目 for _, entry := range entries { fmt.Println(\u0026#34;名称:\u0026#34;, entry.Name()) fmt.Println(\u0026#34;是目录:\u0026#34;, entry.IsDir()) info, err := entry.Info() if err == nil { fmt.Println(\u0026#34; 大小:\u0026#34;, info.Size()) fmt.Println(\u0026#34; 修改时间:\u0026#34;, info.ModTime()) fmt.Println(\u0026#34; 权限:\u0026#34;, info.Mode()) } fmt.Println() } 递归遍历目录 # // 使用filepath.Walk遍历目录及其子目录 err := filepath.Walk(\u0026#34;.\u0026#34;, func(path string, info os.FileInfo, err error) error { if err != nil { fmt.Printf(\u0026#34;访问 %s 错误: %v\\n\u0026#34;, path, err) return err } fmt.Printf(\u0026#34;%-50s | %-10d | %s\\n\u0026#34;, path, info.Size(), info.Mode()) return nil }) if err != nil { log.Fatal(err) } WalkDir # Go 1.16 新增。\n// 使用更高效的filepath.WalkDir err := filepath.WalkDir(\u0026#34;.\u0026#34;, func(path string, d fs.DirEntry, err error) error { if err != nil { fmt.Printf(\u0026#34;访问 %s 错误: %v\\n\u0026#34;, path, err) return err } // 只处理常规文件 if !d.IsDir() { fmt.Printf(\u0026#34;文件: %s\\n\u0026#34;, path) } return nil }) if err != nil { log.Fatal(err) } 临时文件和目录 # 在开发过程中，经常需要创建临时文件和目录来存储中间数据或缓存信息。Go语言的os包提供了一些函数用于创建和管理临时文件和目录。\nGo 1.16 之前在ioutil包中。\n创建临时文件 # 使用os.CreateTemp函数可以创建一个临时文件。该函数会在指定目录下创建一个具有唯一名称的临时文件，并返回该文件的文件对象和路径。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) func main() { // 创建临时文件 file, err := os.CreateTemp(\u0026#34;\u0026#34;, \u0026#34;example_*.txt\u0026#34;) if err != nil { fmt.Println(\u0026#34;Error creating temporary file:\u0026#34;, err) return } defer os.Remove(file.Name()) // 在使用完后删除临时文件 defer file.Close() fmt.Println(\u0026#34;Temporary file created:\u0026#34;, file.Name()) // 向临时文件写入数据 _, err = file.WriteString(\u0026#34;Hello, Temporary File!\u0026#34;) if err != nil { fmt.Println(\u0026#34;Error writing to temporary file:\u0026#34;, err) return } fmt.Println(\u0026#34;Data written to temporary file successfully\u0026#34;) } 上述代码会在系统默认的临时文件目录下创建一个临时文件，并写入一些数据。临时文件的名称具有唯一性，并包含example_前缀。\n创建临时目录 # 使用os.MkdirTemp函数可以创建一个临时目录。该函数会在指定目录下创建一个具有唯一名称的临时目录，并返回该目录的路径。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) func main() { // 创建临时目录 dir, err := os.MkdirTemp(\u0026#34;\u0026#34;, \u0026#34;exampledir_*\u0026#34;) if err != nil { fmt.Println(\u0026#34;Error creating temporary directory:\u0026#34;, err) return } defer os.RemoveAll(dir) // 在使用完后删除临时目录 fmt.Println(\u0026#34;Temporary directory created:\u0026#34;, dir) // 在临时目录中创建一个文件 tempFile := fmt.Sprintf(\u0026#34;%s/%s\u0026#34;, dir, \u0026#34;tempfile.txt\u0026#34;) file, err := os.Create(tempFile) if err != nil { fmt.Println(\u0026#34;Error creating file in temporary directory:\u0026#34;, err) return } defer file.Close() // 向文件中写入数据 _, err = file.WriteString(\u0026#34;Hello, Temporary Directory!\u0026#34;) if err != nil { fmt.Println(\u0026#34;Error writing to file in temporary directory:\u0026#34;, err) return } fmt.Println(\u0026#34;Data written to file in temporary directory successfully\u0026#34;) } 上述代码会在系统默认的临时文件目录下创建一个临时目录，并在该目录中创建一个文件并写入一些数据。\n清理临时文件与目录 # 在创建临时文件和目录后，记得在使用完毕后进行清理。可以使用os.Remove和os.RemoveAll函数分别删除单个文件和目录树：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) func main() { // 创建临时文件 file, err := os.CreateTemp(\u0026#34;\u0026#34;, \u0026#34;example_*.txt\u0026#34;) if err != nil { fmt.Println(\u0026#34;Error creating temporary file:\u0026#34;, err) return } defer file.Close() // 创建临时目录 dir, err := os.MkdirTemp(\u0026#34;\u0026#34;, \u0026#34;exampledir_*\u0026#34;) if err != nil { fmt.Println(\u0026#34;Error creating temporary directory:\u0026#34;, err) return } // 清理临时文件 err = os.Remove(file.Name()) if err != nil { fmt.Println(\u0026#34;Error removing temporary file:\u0026#34;, err) return } fmt.Println(\u0026#34;Temporary file removed:\u0026#34;, file.Name()) // 清理临时目录 err = os.RemoveAll(dir) if err != nil { fmt.Println(\u0026#34;Error removing temporary directory:\u0026#34;, err) return } fmt.Println(\u0026#34;Temporary directory removed:\u0026#34;, dir) } 上述代码会创建一个临时文件和一个临时目录，并在使用完后将其删除。\n终端操作 # 终端其实是一个文件，相关实例如下： os.Stdin：标准输入的文件实例，类型为*File os.Stdout：标准输出的文件实例，类型为*File os.Stderr：标准错误输出的文件实例，类型为*File package main import \u0026#34;os\u0026#34; func main() { var buf [16]byte os.Stdin.Read(buf[:]) os.Stdin.WriteString(string(buf[:])) } 路径解析 # 路径相关的函数有两个包，path 和 path/filepath，两个包内有一些相同的函数，如IsAbs()、Join()、Dir()，两个包的区别在于：\n‌path包‌：提供了一些基本的路径处理功能，如路径拼接、获取文件名、获取文件扩展名等。这些功能适用于简单的路径处理需求。 ‌filepath包‌：在path包的基础上，增加了对不同操作系统的兼容性处理，能够自动根据不同的操作系统进行路径转换。因此，如果你有跨平台的需求，建议使用filepath包。 跨平台支持：‌filepath包‌：自动根据不同的操作系统进行路径转换，确保跨平台的兼容性。例如，在Windows系统中，路径分隔符是反斜杠（\\），而在Unix和Linux系统中是正斜杠（/）。filepath包能够处理这些差异，使得代码在不同操作系统上都能正常运行。\npath # // 路径拼接 func Join(elem ...string) string // 判断文件是否是绝对路径 func IsAbs(path string) bool // 获取目录 func Dir(path string) string // 获取文件拓展名 func Ext(path string) string filepath # // 获取targpath相对于basepath的相对路径 func Rel(basepath, targpath string) (string, error) // 获取绝对路径，如果path不是绝对路径，会加入当前工作目录以使之成为绝对路径。 func Abs(path string) (string, error) // 获取路径中的最后一个元素 func Base(path string) string // 获取路径中除去最后一个元素后前面的部分 func Dir(path string) string // 获取文件拓展名 func Ext(path string) string // 路径拼接 func Join(elem ...string) string // 将路径拆分为目录和文件名两部分 func Split(path string) (dir, file string) ","date":"2025-05-19","externalUrl":null,"permalink":"/posts/2d3150b2/0e0cf2f4/40d22fb7/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eGo 的 IO 模型 \n    \u003cdiv id=\"go-的-io-模型\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#go-%e7%9a%84-io-%e6%a8%a1%e5%9e%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e核心接口 \n    \u003cdiv id=\"核心接口\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%a0%b8%e5%bf%83%e6%8e%a5%e5%8f%a3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003eGo语言的IO操作建立在几个核心接口上，这些接口构成了灵活而一致的IO模型。\u003c/p\u003e","title":"4、IO操作","type":"posts"},{"content":" Cookie vs Session # 特性 Cookie Session 存储位置 客户端（浏览器） 服务器端 存储容量 小（4KB左右） 大（受服务器内存或存储限制） 生命周期 可长期存在直到过期 通常短期存在，会话结束或超时后失效 安全性 较低（存在客户端可被窃取） 较高（关键数据存在服务端） 使用场景 用户偏好，非敏感数据 用户认证状态，购物车等敏感数据 性能影响 增加请求头大小 需要服务器资源存储和检索 可扩展性 良好（数据在客户端） 需要特殊设计支持分布式系统 Cookie # Cookie是服务器发送到用户浏览器并保存在浏览器上的一小块数据。浏览器会在之后的请求中将Cookie发送回服务器，用于在无状态的HTTP协议中实现有状态的会话管理。\n设置Cookie # Gin提供了简便的方法来设置Cookie：\nfunc SetCookie(c *gin.Context) { // 设置简单的Cookie c.SetCookie(\u0026#34;simple_cookie\u0026#34;, \u0026#34;value\u0026#34;, 3600, \u0026#34;/\u0026#34;, \u0026#34;localhost\u0026#34;, false, true) c.String(200, \u0026#34;Cookie已设置\u0026#34;) } Cookie参数：\n参数 类型 说明 默认值 示例 name string Cookie的名称 (必填) \u0026quot;user_id\u0026quot; value string Cookie的值 (必填) \u0026quot;12345\u0026quot; maxAge int 有效期（秒） 0 3600 (1小时) path string Cookie生效的路径 \u0026quot;/\u0026quot; \u0026quot;/admin\u0026quot; domain string Cookie生效的域名 当前域 \u0026quot;example.com\u0026quot; secure bool 是否只在HTTPS下传输 false true httpOnly bool 是否允许JavaScript访问 false true 读取Cookie # func GetCookie(c *gin.Context) { // 获取Cookie值 cookie, err := c.Cookie(\u0026#34;simple_cookie\u0026#34;) if err != nil { c.String(200, \u0026#34;Cookie not found\u0026#34;) return } c.String(200, \u0026#34;Cookie value: %s\u0026#34;, cookie) } 删除Cookie # 删除Cookie可以通过设置负的过期时间实现：\nfunc DeleteCookie(c *gin.Context) { // 设置maxAge为-1即可删除Cookie c.SetCookie(\u0026#34;simple_cookie\u0026#34;, \u0026#34;\u0026#34;, -1, \u0026#34;/\u0026#34;, \u0026#34;localhost\u0026#34;, false, true) c.String(200, \u0026#34;Cookie已删除\u0026#34;) } Session # Session是服务器用来存储特定用户会话所需信息的机制。Session通常使用唯一的标识符（Session ID）跟踪用户，该标识符通常存储在Cookie中。\nGin本身不提供Session管理，需要使用第三方库，如：\ngithub.com/gin-contrib/sessions github.com/gorilla/sessions 下面我们以gin-contrib/sessions为例：\n安装：\ngo get -u github.com/gin-contrib/sessions 示例代码：\nimport ( \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;github.com/gin-contrib/sessions\u0026#34; \u0026#34;github.com/gin-contrib/sessions/cookie\u0026#34; ) func main() { r := gin.Default() // 创建基于Cookie的存储引擎 store := cookie.NewStore([]byte(\u0026#34;secret_key\u0026#34;)) // 设置Session中间件，指定Session名称为mysession r.Use(sessions.Sessions(\u0026#34;mysession\u0026#34;, store)) // ... 路由设置 r.Run(\u0026#34;:8080\u0026#34;) } Session存储选项 # gin-contrib/sessions支持多种存储后端：\n存储选项 导入包 初始化代码 适用场景 Cookie存储 \u0026quot;github.com/gin-contrib/sessions/cookie\u0026quot; store := cookie.NewStore([]byte(\u0026quot;secret\u0026quot;)) 开发环境，简单应用 Redis存储 \u0026quot;github.com/gin-contrib/sessions/redis\u0026quot; store, _ := redis.NewStore(10, \u0026quot;tcp\u0026quot;, \u0026quot;localhost:6379\u0026quot;, \u0026quot;\u0026quot;, []byte(\u0026quot;secret\u0026quot;)) 生产环境，需要扩展 Memcached存储 \u0026quot;github.com/gin-contrib/sessions/memcached\u0026quot; store, _ := memcached.NewStore([]string{\u0026quot;localhost:11211\u0026quot;}, \u0026quot;\u0026quot;, []byte(\u0026quot;secret\u0026quot;)) 高性能缓存需求 MongoDB存储 \u0026quot;github.com/gin-contrib/sessions/mongo\u0026quot; store, _ := mongo.NewStore(mongoSession, \u0026quot;database\u0026quot;, \u0026quot;collection\u0026quot;, 3600, []byte(\u0026quot;secret\u0026quot;)) 需要持久化会话 内存存储 \u0026quot;github.com/gin-contrib/sessions/memstore\u0026quot; store := memstore.NewStore([]byte(\u0026quot;secret\u0026quot;)) 单机开发环境 Session操作 # 基本的Session操作包括读取、设置和删除：\n// 设置Session值 func SetSession(c *gin.Context) { session := sessions.Default(c) session.Set(\u0026#34;user_id\u0026#34;, 123) session.Set(\u0026#34;username\u0026#34;, \u0026#34;john_doe\u0026#34;) // 保存会话，这一步是必须的 session.Save() c.JSON(200, gin.H{\u0026#34;message\u0026#34;: \u0026#34;Session已设置\u0026#34;}) } // 获取Session值 func GetSession(c *gin.Context) { session := sessions.Default(c) userId := session.Get(\u0026#34;user_id\u0026#34;) username := session.Get(\u0026#34;username\u0026#34;) c.JSON(200, gin.H{ \u0026#34;user_id\u0026#34;: userId, \u0026#34;username\u0026#34;: username, }) } // 删除Session值 func ClearSession(c *gin.Context) { session := sessions.Default(c) session.Clear() session.Save() c.JSON(200, gin.H{\u0026#34;message\u0026#34;: \u0026#34;Session已清除\u0026#34;}) } // 删除特定Session键 func DeleteSessionKey(c *gin.Context) { session := sessions.Default(c) session.Delete(\u0026#34;username\u0026#34;) session.Save() c.JSON(200, gin.H{\u0026#34;message\u0026#34;: \u0026#34;用户名已从Session中删除\u0026#34;}) } 常用方法 # 方法 说明 注意事项 sessions.Default(c) 获取会话实例 必须先配置Sessions中间件 session.Get(key) 获取会话值 返回interface{}，需要类型断言 session.Set(key, value) 设置会话值 修改后必须调用Save() session.Delete(key) 删除特定键 修改后必须调用Save() session.Clear() 清除所有会话数据 修改后必须调用Save() session.Save() 保存会话更改 必须在修改会话后调用 session.Options() 设置会话选项 可设置路径、域名等 session.Flashes() 获取并清除一次性消息 用于跨请求消息传递 session.AddFlash() 添加一次性消息 用于跨请求消息传递 ","date":"2025-05-19","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/1bd46fa5/cbb70e6e/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eCookie vs Session \n    \u003cdiv id=\"cookie-vs-session\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#cookie-vs-session\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e特性\u003c/th\u003e\n          \u003cth\u003eCookie\u003c/th\u003e\n          \u003cth\u003eSession\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e存储位置\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e客户端（浏览器）\u003c/td\u003e\n          \u003ctd\u003e服务器端\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e存储容量\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e小（4KB左右）\u003c/td\u003e\n          \u003ctd\u003e大（受服务器内存或存储限制）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e生命周期\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e可长期存在直到过期\u003c/td\u003e\n          \u003ctd\u003e通常短期存在，会话结束或超时后失效\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e安全性\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e较低（存在客户端可被窃取）\u003c/td\u003e\n          \u003ctd\u003e较高（关键数据存在服务端）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e使用场景\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e用户偏好，非敏感数据\u003c/td\u003e\n          \u003ctd\u003e用户认证状态，购物车等敏感数据\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e性能影响\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e增加请求头大小\u003c/td\u003e\n          \u003ctd\u003e需要服务器资源存储和检索\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e可扩展性\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e良好（数据在客户端）\u003c/td\u003e\n          \u003ctd\u003e需要特殊设计支持分布式系统\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2 class=\"relative group\"\u003eCookie \n    \u003cdiv id=\"cookie\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#cookie\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eCookie是服务器发送到用户浏览器并保存在浏览器上的一小块数据。浏览器会在之后的请求中将Cookie发送回服务器，用于在无状态的HTTP协议中实现有状态的会话管理。\u003c/p\u003e","title":"4、会话","type":"posts"},{"content":" WebSocket基础概念 # WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单，允许服务端主动向客户端推送数据。在WebSocket API中，浏览器和服务器只需要完成一次握手，两者之间就可以创建持久性的连接，并进行双向数据传输。\nWebSocket在2011年被IETF标准化为RFC 6455，并由W3C定义了对应的JavaScript API。\nWebSocket vs HTTP # 传统的HTTP协议有以下局限性：\n单向通信：客户端发起请求，服务器响应，服务器不能主动推送数据 连接不持久：每次请求都需要建立新的TCP连接（虽然HTTP/1.1引入了Keep-Alive，但本质上仍是请求-响应模式） 头部开销大：HTTP请求和响应都包含大量头部信息 相比之下，WebSocket提供以下优势：\n双向通信：服务器可以主动向客户端推送数据 持久连接：一次握手后保持TCP连接，减少连接建立的开销 较低的延迟：没有HTTP请求的额外开销，适合实时数据传输 更高效的数据传输：相比HTTP轮询，大大减少了数据传输量 WebSocket工作原理 # 1、握手阶段：客户端发起HTTP请求，请求升级到WebSocket协议\nGET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13 2、服务器响应：如果服务器支持WebSocket，则返回升级确认\nHTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= 3、建立连接：握手成功后，HTTP连接升级为WebSocket连接，此后的通信遵循WebSocket协议\n4、数据传输：使用帧（frames）进行数据传输，帧可以是文本帧或二进制帧\n5、关闭连接：任何一方都可以发送关闭帧来关闭连接\nGo语言中的WebSocket实现 # Go 语言有两个主要的 WebSocket 库：\ngorilla/websocket：这是一个流行的 WebSocket 库，提供了简单易用的接口。它支持多路复用、自定义头部和心跳等功能。 golang.org/x/net/websocket：这是 Go 标准库中的 WebSocket 库，提供了基本的 WebSocket 功能。它不支持多路复用、自定义头部和心跳等高级功能。 我们将主要关注 gorilla/websocket 库，因为它更加强大和易用。\n安装 # go get github.com/gorilla/websocket 基本概念 # 在 gorilla/websocket 中使用 Conn 来表示一个 WebSocket 连接，它主要有如下作用：\n发送消息给客户端：WriteXXX 方法，如 WriteJSON 发送 JSON 类型消息，又或者 WriteMessage 可以发送普通的文本消息。 接收客户端发送的消息：ReadXXX 方法，如 ReadJSON 和 ReadMessage。 其他功能：关闭连接、获取客户端 IP 地址等 消息被分为以下几种：\n数据消息： TextMessage 文本消息：文本消息被解析为 UTF-8 编码的文本。需要应用程序来确保文本消息是有效的 UTF-8 编码文本。 BinaryMessage 二进制消息：二进制消息的解析留给应用程序。 控制消息：可以调用 Conn 中的 WriteControl、WriteMessage 或 NextWriter 方法，将控制消息发送给对方。 CloseMessage 关闭连接的消息 PingMessage ping 消息 PongMessage pong 消息 WebSocket 服务端 # package main import ( \u0026#34;github.com/gorilla/websocket\u0026#34; \u0026#34;io\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; ) // 配置 WebSocket 升级器 var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, // 允许所有CORS请求，生产环境应当配置更严格的检查 CheckOrigin: func(r *http.Request) bool { return true }, } func handleWebSocket(w http.ResponseWriter, r *http.Request) { // 将 HTTP 连接升级为 WebSocket连接 conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Println(err) } defer conn.Close() // 关闭链接 log.Println(\u0026#34;WebSocket 客户端已连接\u0026#34;, conn.RemoteAddr()) // 无限循环，持续读取客户端消息 for { // 读取消息 messageType, message, err := conn.ReadMessage() if err != nil \u0026amp;\u0026amp; err != io.EOF { log.Println(\u0026#34;服务端读取消息错误:\u0026#34;, err) break } log.Printf(\u0026#34;收到客户端 %v 消息: %s\u0026#34;, conn.RemoteAddr(), string(message)) // 简单地将消息回送给客户端 err = conn.WriteMessage(messageType, message) if err != nil { log.Println(\u0026#34;服务端写入消息错误:\u0026#34;, err) break } } } func main() { http.HandleFunc(\u0026#34;/ws\u0026#34;, handleWebSocket) err := http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) if err != nil { log.Fatalln(err) } } WebSocket 客户端 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/gorilla/websocket\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/url\u0026#34; \u0026#34;sync\u0026#34; ) func main() { url := url.URL{ Scheme: \u0026#34;ws\u0026#34;, Host: \u0026#34;localhost:8080\u0026#34;, Path: \u0026#34;/ws\u0026#34;, } // 创建一个 WebSocket 客户端链接 conn, _, err := websocket.DefaultDialer.Dial(url.String(), nil) if err != nil { log.Println(\u0026#34;无法连接 WebSocket 服务端:\u0026#34;, url.String()) } log.Println(\u0026#34;连接服务器成功\u0026#34;) wg := sync.WaitGroup{} // 启动一个 goroutine 接收消息 wg.Add(1) go func() { defer wg.Done() for { messageType, message, err := conn.ReadMessage() if err != nil { log.Println(\u0026#34;客户端读取消息错误:\u0026#34;, err) break } log.Println(\u0026#34;收到服务端消息:\u0026#34;, string(message), \u0026#34;type:\u0026#34;, messageType) } }() // 另一个 goroutine 监听用户输入发送消息 wg.Add(1) go func() { defer wg.Done() for { var message string fmt.Scanln(\u0026amp;message) err := conn.WriteMessage(websocket.TextMessage, []byte(message)) if err != nil { log.Println(\u0026#34;客户端发送消息错误:\u0026#34;, err) break } } }() wg.Wait() } ","date":"2025-05-16","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/8b430461/8bc2f87d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eWebSocket基础概念 \n    \u003cdiv id=\"websocket基础概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#websocket%e5%9f%ba%e7%a1%80%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eWebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单，允许服务端主动向客户端推送数据。在WebSocket API中，浏览器和服务器只需要完成一次握手，两者之间就可以创建持久性的连接，并进行双向数据传输。\u003c/p\u003e","title":"4、websocket","type":"posts"},{"content":"策略模式（Strategy）定义了一系列算法，并使它们可以互相替换。在Go中，可以使用接口实现策略模式：\n定义 # package strategy import ( \u0026#34;fmt\u0026#34; \u0026#34;sort\u0026#34; ) // 定义排序策略接口 type SortStrategy interface { Sort([]int) []int GetName() string } // 冒泡排序策略 type BubbleSort struct{} func (s BubbleSort) Sort(data []int) []int { fmt.Println(\u0026#34;使用冒泡排序...\u0026#34;) result := make([]int, len(data)) copy(result, data) n := len(result) for i := 0; i \u0026lt; n; i++ { for j := 0; j \u0026lt; n-i-1; j++ { if result[j] \u0026gt; result[j+1] { result[j], result[j+1] = result[j+1], result[j] } } } return result } func (s BubbleSort) GetName() string { return \u0026#34;冒泡排序\u0026#34; } // 快速排序策略 type QuickSort struct{} func (s QuickSort) Sort(data []int) []int { fmt.Println(\u0026#34;使用快速排序...\u0026#34;) result := make([]int, len(data)) copy(result, data) sort.Ints(result) // 使用Go标准库的排序（实际为快排） return result } func (s QuickSort) GetName() string { return \u0026#34;快速排序\u0026#34; } // 上下文 type SortContext struct { strategy SortStrategy } // 设置排序策略 func (c *SortContext) SetStrategy(strategy SortStrategy) { c.strategy = strategy } // 执行排序 func (c *SortContext) ExecuteSort(data []int) []int { return c.strategy.Sort(data) } 使用 # // main.go package main import ( \u0026#34;fmt\u0026#34; \u0026#34;myapp/strategy\u0026#34; \u0026#34;time\u0026#34; ) func main() { // 创建上下文 ctx := \u0026amp;strategy.SortContext{} // 准备数据 data := []int{9, 3, 7, 5, 1, 8, 2, 6, 4} // 使用冒泡排序 ctx.SetStrategy(strategy.BubbleSort{}) start := time.Now() result1 := ctx.ExecuteSort(data) elapsed1 := time.Since(start) fmt.Printf(\u0026#34;排序结果: %v (耗时: %v)\\n\u0026#34;, result1, elapsed1) // 使用快速排序 ctx.SetStrategy(strategy.QuickSort{}) start = time.Now() result2 := ctx.ExecuteSort(data) elapsed2 := time.Since(start) fmt.Printf(\u0026#34;排序结果: %v (耗时: %v)\\n\u0026#34;, result2, elapsed2) } ","date":"2025-05-14","externalUrl":null,"permalink":"/posts/2d3150b2/6742e0c3/b3fb74f0/","section":"文章","summary":"\u003cp\u003e策略模式（Strategy）定义了一系列算法，并使它们可以互相替换。在Go中，可以使用接口实现策略模式：\u003c/p\u003e","title":"4、策略模式","type":"posts"},{"content":" 分支语句 # if\u0026hellip;else # if condition1 { // do something } else if condition2 { // do something else } else { // catch-all or default } 注意：关键字 if 和 else 之后的左大括号{必须和关键字在同一行，如果使用了 else if 结构，则前段代码块的右大括号}必须和 else if 关键字在同一行，这两条规则都是被编译器强制规定的。\n带初始化语句的 if # 例如Connect() 是一个带有返回值的函数，err := Connect() 是一个语句，执行 Connect() 后，将错误保存到 err 变量中。\nerr != nil 才是 if 的判断表达式，当 err 不为空时，打印错误并返回。\nif err := Connect(); err != nil { fmt.Println(err) return } switch\u0026hellip;case # Go语言改进了 switch 的语法设计，case 与 case 之间是独立的代码块，不需要通过 break 语句跳出当前 case 代码块以避免执行到下一行\nvar a = \u0026#34;hello\u0026#34; switch a { case \u0026#34;hello\u0026#34;: fmt.Println(1) case \u0026#34;world\u0026#34;: fmt.Println(2) default: fmt.Println(0) } 一分支多值 # var a = \u0026#34;mum\u0026#34; switch a { case \u0026#34;mum\u0026#34;, \u0026#34;daddy\u0026#34;: fmt.Println(\u0026#34;family\u0026#34;) } 分支表达式 # var r int = 11 switch { case r \u0026gt; 10 \u0026amp;\u0026amp; r \u0026lt; 20: fmt.Println(r) } fallthrough # fallthrough可以实现穿透的效果，从当前case块穿透到下一个case并执行，一次只能穿透一层。\n**注意：**使用fallthrough时，将无条件执行下一个case，而不检查其条件。\na := 1 switch a { case 1: fmt.Println(\u0026#34;1\u0026#34;) fallthrough case 2: fmt.Println(\u0026#34;2\u0026#34;) } /* 1 2 */ 带初始化语句的switch # switch 初始化语句; 表达式 { case 值1: // ... } type switch类型选择 # Go的switch有一个特殊形式，用于判断变量的具体类型：\nswitch x.(type) { case 类型1: // x是类型1 case 类型2: // x是类型2 default: // 其他类型 } 这被称为type switch，是Go处理多态的重要方式之一：\nfunc describe(i interface{}) { switch v := i.(type) { case int: fmt.Printf(\u0026#34;整数: %d\\n\u0026#34;, v) case string: fmt.Printf(\u0026#34;字符串: %s (长度: %d)\\n\u0026#34;, v, len(v)) case bool: fmt.Printf(\u0026#34;布尔值: %t\\n\u0026#34;, v) case nil: fmt.Println(\u0026#34;nil值\u0026#34;) default: fmt.Printf(\u0026#34;其他类型: %T\\n\u0026#34;, v) } } func main() { describe(42) describe(\u0026#34;Hello World!\u0026#34;) describe(true) describe(nil) describe(3.14) } 循环语句 # for # Go语言中的循环语句只支持 for 关键字，而不支持 while 和 do-while 结构，关键字 for 的基本使用方法与C/C++非常接近\nsum := 0 for i := 0; i \u0026lt; 10; i++ { sum += i } 注意：左花括号{必须与 for 处于同一行。\n无限循环 # for{ fmt.Println(\u0026#34;hello\u0026#34;) } continue 和 break # Go语言的 for 循环同样支持 continue 和 break 来控制循环，同时可以使用标签（label）来精确控制多层循环中的跳转\nJLoop: for j := 0; j \u0026lt; 5; j++ { for i := 0; i \u0026lt; 10; i++ { if i \u0026gt; 5 { break JLoop } fmt.Println(i) } } 只有循环条件 # var i int for i \u0026lt;= 10 { i++ } for-range循环 # for-range是Go中遍历数组、切片、映射、通道等内置集合的惯用方式：\nfor 索引, 值 := range 集合 { // 使用索引和值 } // 遍历切片 func processItems(items []string) { for i, item := range items { fmt.Printf(\u0026#34;索引 %d: %s\\n\u0026#34;, i, item) } } // 遍历映射 func displayMap(data map[string]int) { for key, value := range data { fmt.Printf(\u0026#34;%s: %d\\n\u0026#34;, key, value) } } // 只需要索引 func processIndices(items []string) { for i := range items { fmt.Printf(\u0026#34;处理索引 %d\\n\u0026#34;, i) } } // 只需要值 func processValues(items []string) { for _, item := range items { fmt.Println(item) } } 在 Go 1.22.0 中支持对整数进行 range\nfunc main() { for i := range 5 { fmt.Println(i) } } /* 0 1 2 3 4 */ goto # Go语言中 goto 语句通过标签进行代码间的无条件跳转，同时 goto 语句在快速跳出循环、避免重复退出上也有一定的帮助，使用 goto 语句能简化一些代码的实现过程。\ngoto只能跳转到同一函数内的标签，不能跳过变量声明，也不能跳入内部作用域。\n跳出循环 # package main import ( \u0026#34;fmt\u0026#34; ) func main() { i := 0 for { i++ fmt.Println(i) if i == 5 { goto myFlag } } myFlag: fmt.Println(\u0026#34;done\u0026#34;) } ","date":"2025-05-12","externalUrl":null,"permalink":"/posts/2d3150b2/02a0817f/0d2fe65a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e分支语句 \n    \u003cdiv id=\"分支语句\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%86%e6%94%af%e8%af%ad%e5%8f%a5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eif\u0026hellip;else \n    \u003cdiv id=\"ifelse\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#ifelse\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003econdition1\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// do something\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003econdition2\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// do something else\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// catch-all or default\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e注意\u003c/strong\u003e：关键字 if 和 else 之后的左大括号\u003ccode\u003e{\u003c/code\u003e必须和关键字在同一行，如果使用了 else if 结构，则前段代码块的右大括号\u003ccode\u003e}\u003c/code\u003e必须和 else if 关键字在同一行，这两条规则都是被编译器强制规定的。\u003c/p\u003e","title":"4、流程控制语句","type":"posts"},{"content":" Webpack # 从本质上，Webpack是现代JavaScript应用的静态模块打包工具\n必须依赖Node.js环境，Node.js自带了软件包管理工具npm\nDOS下，node -v查看node版本\n安装Webpack # DOS命令：npm install webpack@3.6.0 -g -g代表全局webpack安装，可以在任何终端执行webpack DOS命令：npm install webpack@3.6.0 --save-dev 局部安装，在package.json中定义了script时，使用webpack命令，那么使用的是局部 安装完成使用webpack --version查看webpack版本\n起步 # 项目目录结构 # project \u0026ndash; 项目根路径 src \u0026ndash; 项目原码，在此文件夹开发 dist \u0026ndash; 项目打包后存放的文件夹，用于发布 index.html \u0026ndash; 项目入口 初始化项目package.json # 命令：npm init，执行该命令可以创建package.json，管理项目依赖\n打包 # 命令：webpack ./src/main.js ./dist/bundle.js\nwebpack.config.js # webpack在打包的时候，只会根据入口的webpack.config.js文件，开始依次打包所有导入的js，没有被导入的文件不会被打包\n//获取dist的绝对路径 const path = require(\u0026#34;path\u0026#34;); //声明打包入口js和出口js module.exports = { entry: \u0026#39;./src/main.js\u0026#39;, output: { //此处的path需要是绝对路径，使用path.resolve(__dirname,\u0026#39;出口js目录\u0026#39;)来确定出口js目录 path: path.resolve(__dirname,\u0026#39;dist\u0026#39;), filename: \u0026#39;bundle.js\u0026#39; }, module: { } } 然后使用webpack命令就可以直接打包\n命令映射 # 在package.json中scripts对象中，可以设置命令映射\n{ \u0026#34;name\u0026#34;: \u0026#34;meet\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;1.0.0\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;main\u0026#34;: \u0026#34;index.js\u0026#34;, \u0026#34;scripts\u0026#34;: { \u0026#34;test\u0026#34;: \u0026#34;echo \\\u0026#34;Error: no test specified\\\u0026#34; \u0026amp;\u0026amp; exit 1\u0026#34;, \u0026#34;build\u0026#34;: \u0026#34;webpack\u0026#34;, \u0026#34;dev\u0026#34;: \u0026#34;webpack-dev-server --open\u0026#34; }, \u0026#34;author\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;license\u0026#34;: \u0026#34;ISC\u0026#34;, \u0026#34;devDependencies\u0026#34;: { \u0026#34;webpack\u0026#34;: \u0026#34;^3.6.0\u0026#34; } } 使用npm run 命令，可以执行该命令对应的命令值，这种方法，可以优先执行项目本地的webpack，devDependencies就是本地webpack\nloader # 可以在官网查看对应的loader安装方法：https://www.webpackjs.com/loaders/\n如果出现Module build failed: TypeError: this.getOptions is not a function异常，说明安装的loader版本过高，百度解决\nwebpack@3.6.0常用loader以及plugin版本 # \u0026#34;devDependencies\u0026#34;: { \u0026#34;babel-core\u0026#34;: \u0026#34;^6.26.3\u0026#34;, \u0026#34;babel-loader\u0026#34;: \u0026#34;^7.1.5\u0026#34;, \u0026#34;babel-preset-es2015\u0026#34;: \u0026#34;^6.24.1\u0026#34;, \u0026#34;css-loader\u0026#34;: \u0026#34;^3.6.0\u0026#34;, \u0026#34;file-loader\u0026#34;: \u0026#34;^3.0.1\u0026#34;, \u0026#34;html-webpack-plugin\u0026#34;: \u0026#34;^2.30.1\u0026#34;, \u0026#34;style-loader\u0026#34;: \u0026#34;^2.0.0\u0026#34;, \u0026#34;uglifyjs-webpack-plugin\u0026#34;: \u0026#34;^1.1.1\u0026#34;, \u0026#34;url-loader\u0026#34;: \u0026#34;^2.3.0\u0026#34;, \u0026#34;vue-loader\u0026#34;: \u0026#34;^13.0.0\u0026#34;, \u0026#34;vue-template-compiler\u0026#34;: \u0026#34;^2.6.14\u0026#34;, \u0026#34;webpack\u0026#34;: \u0026#34;^3.6.0\u0026#34;, \u0026#34;webpack-dev-server\u0026#34;: \u0026#34;^2.9.1\u0026#34; }, \u0026#34;dependencies\u0026#34;: { \u0026#34;vue\u0026#34;: \u0026#34;^2.6.14\u0026#34; } 处理css # 如果需要处理css，那么需要对webpack扩展对应的loader就可以了\n1、安装配置css-loader以及style-loader # 如果只安装css-loader，那么只会打包css代码，而样式并不会生效，style-loader的作用就是将模块的导出作为样式添加到 DOM 中\n命令：\nnpm install --save-dev css-loader npm install style-loader --save-dev 配置：\nmodule.exports = { module: { rules: [ { test: /\\.css$/, //读取配置文件是从右向左，所以css-loader需要先被读取 use: [\u0026#39;style-loader\u0026#39;, \u0026#39;css-loader\u0026#39;] } ] } } 2、在入口js，导入需要的css文件 # 处理图片 # 如果在css中，通过url引用了一张图片，那么在打包的时候，会去打包这个图片，需要url-loader\n安装配置url-loader和file-loader # 命令：\nnpm install url-loader --save-dev npm install file-loader --save-dev 配置：\nmodule.exports = { entry: \u0026#39;./src/main.js\u0026#39;, output: { path: path.resolve(__dirname,\u0026#39;dist\u0026#39;), filename: \u0026#39;bundle.js\u0026#39;, //如果不打包index.html,需要加publicPath，打包后使用url的根路径 publicPath: \u0026#39;dist/\u0026#39; }, module: { rules: [ { test: /\\.(png|jpg|gif|jpeg)$/, use: [ { loader: \u0026#39;url-loader\u0026#39;, options: { //当加载的图片小于limit，会将图片编译成base64 //大于limit使用file-loader limit: 8192, //对文件进行命名，采用img/文件名.8位hash.后缀 name: \u0026#39;img/[name].[hash:8].[ext]\u0026#39; } } ] } ] } } ES6打包成ES5 # 安装配置babel-loader、babel-core、babel-preset-es2015 # 命令：npm install babel-loader@7.1.5 babel-core@6.26.3 babel-preset-es2015@6.24.1 --save-dev\n配置：\nmodule: { rules: [ { test: /\\.js$/, //排除转化的文件，只转化src里面的js exclude: /(node_modules|bower_components)/, use: { loader: \u0026#39;babel-loader\u0026#39;, options: { presets: [\u0026#39;es2015\u0026#39;] } } } ] } webpack配置Vue # 1、下载vuejs # 命令：npm install vue --save\n2、引入vuejs # 由于直接import Vue from 'vue'引入的vue版本是runtime-only，这个版本是不支持template编译的，所以需要使用runtime-compiler，在webpack.config.js中，配置vue的别名\nmodule: {} resolve: { alias: { \u0026#39;vue$\u0026#39; : \u0026#39;vue/dist/vue.esm.js\u0026#39; } } el和template的区别 # el用于指定Vue要管理的DOM，可以帮助解析其中的指令、事件监听等等。 如果Vue实例中同时指定了template，那么template模板的内容会替换掉挂载的对应el的模板。 3、创建vue文件 # \u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;button\u0026gt;but\u0026lt;/button\u0026gt; \u0026lt;h1\u0026gt;{{msg}}\u0026lt;/h1\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;app\u0026#34;, data() { return { msg: \u0026#34;hello!!!\u0026#34; } } } \u0026lt;/script\u0026gt; \u0026lt;style scoped\u0026gt; h1{ color: red; } \u0026lt;/style\u0026gt; 4、安装vue-loader、vue-template-compiler # 命令：npm install vue-loader vue-template-compiler --save-dev\n配置：\nmodule: { rules: [ { test: /\\.vue$/, use: [\u0026#39;vue-loader\u0026#39;] } ] } 如果在打包时，提示插件问题，可以将vue-loader版本切换为^13.0.0\n5、引入vue组件文件，并注册组件在Vue实例template中使用 # import Vue from \u0026#39;vue\u0026#39;; import App from \u0026#39;./vue/app.vue\u0026#39; new Vue({ el: \u0026#39;#app\u0026#39;, template: \u0026#39;\u0026lt;app\u0026gt;\u0026lt;/app\u0026gt;\u0026#39;, components: { app : App } }); Plugin # loader和plugin的区别 # loader主要用于转换某些类型的模块，它是一个加载转换器。 plugin是插件，它是对webpack本身的扩展，是一个扩展器。 plugin的使用过程 # 步骤一：通过npm安装需要使用的plugins(某些webpack已经内置的插件不需要安装) 步骤二：在webpack.config.js中的plugins中配置插件。 版权plugin # 此插件webpack自带，不需要安装\n1、在webpack.config.js中导入webpack\nconst webpack = require(\u0026#34;webpack\u0026#34;); 2、在该文件中，module.exports内创建plugins数组\nplugins: [ new webpack.BannerPlugin(\u0026#39;最终版权归yhgh所有\u0026#39;) ] 打包html的plugin # 在真实发布项目时，发布的是dist文件夹中的内容，但是dist文件夹中如果没有index.html文件，那么打包的js等文件也就没有意义了。 所以，我们需要将index.html文件打包到dist文件夹中，这个时候就可以使用HtmlWebpackPlugin插件 功能 # 自动生成一个index.html文件(可以指定模板来生成) 将打包的js文件，自动通过script标签插入到body中 安装 # webpack@3.6.0推荐安装html-webpack-plugin@2.30.1\n由于不是webpack自带的插件，所以需要进行安装\n命令：npm install html-webpack-plugin --save-dev\n使用插件 # webpack.config.js文件中引入插件\nconst htmlWebpackPlugin = require(\u0026#34;html-webpack-plugin\u0026#34;); 修改webpack.config.js文件中plugins部分的内容如下\nplugins: [ new htmlWebpackPlugin({ template:\u0026#39;index.html\u0026#39; }) ] 删除之前在output中添加的publicPath属性否则插入的script标签中的src可能会有问题\njs压缩插件 # 安装插件 # 由于不是webpack自带的插件，所以需要进行安装\n命令：npm install uglifyjs-webpack-plugin@1.1.1 --save-dev\n使用插件 # 在webpack.config.js中引入插件uglifyjs\nconst uglifyJsWebpackPlugin = require(\u0026#34;uglifyjs-webpack-plugin\u0026#34;); 修改webpack.config.js文件，使用插件\nplugins: [ new uglifyJsWebpackPlugin(); ] 此插件使用后，版权插件不会生效，所以，二选一\nwebpack-dev-server服务器搭建 # webpack提供了一个可选的本地开发服务器，这个本地服务器基于node.js搭建，内部使用express框架，可以实现我们想要的让浏览器自动刷新显示我们修改后的结果。\n安装 # 它是一个单独的模块，在webpack中使用之前需要先安装它\n命令：npm install --save-dev webpack-dev-server@2.9.1\n配置 # 需要在webpack.config.js中进行配置，在module同级的devServer中配置\nmodule.exports = { devServer: { contentBase: \u0026#39;./dist\u0026#39;, port: 8088, inline: true } } devserver也是作为webpack中的一个选项，选项本身可以设置如下属性：\ncontentBase：为哪一个文件夹提供本地服务，默认是根文件夹，我们这里要填写./dist port：端口号 inline：页面实时刷新，代码改变就刷新，true historyApiFallback：在SPA页面中，依赖HTML5的history模式 启动服务 # 命令：webpack-dev-server\n推荐使用在package.json中，配置命令映射\n添加--open参数，可以在服务器启动时打开浏览器\n\u0026#34;dev\u0026#34;: \u0026#34;webpack-dev-server --open\u0026#34; ","date":"2025-04-27","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/79502aa8/83bff396/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eWebpack \n    \u003cdiv id=\"webpack\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#webpack\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e从本质上，Webpack是现代JavaScript应用的静态模块打包工具\u003c/p\u003e","title":"4、webpack","type":"posts"},{"content":" global # JavaScript 中有一个特殊的对象，称为全局对象（Global Object），它及其所有属性都可以在程序的任何地方访问，即全局变量。\n在浏览器 JavaScript 中，通常 window 是全局对象， 而 Node.js 中的全局对象是 global，所有全局变量（除了 global 本身以外）都是 global 对象的属性。\n在 Node.js 我们可以直接访问到 global 的属性，而不需要在应用中引入它。\nNode.js中没有web中的全局变量window，node中的全局变量是global\nconsole.log(global); 输出，看到封装了一些函数可以使用\n\u0026lt;ref *1\u0026gt; Object [global] { global: [Circular *1], clearInterval: [Function: clearInterval], clearTimeout: [Function: clearTimeout], setInterval: [Function: setInterval], setTimeout: [Function: setTimeout] { [Symbol(nodejs.util.promisify.custom)]: [Getter] }, queueMicrotask: [Function: queueMicrotask], clearImmediate: [Function: clearImmediate], setImmediate: [Function: setImmediate] { [Symbol(nodejs.util.promisify.custom)]: [Getter] } } 在全局中声明的变量，会作为global的属性保存 在全局中声明的函数，会作为global的函数保存 //声明一个全局变量a，因为每个模块单独运行在一个函数里面 a = 10; console.log(global.a); 全局对象与全局变量 # global 最根本的作用是作为全局变量的宿主。按照 ECMAScript 的定义，满足以下条件的变量是全局变量：\n在最外层定义的变量； 全局对象的属性； 隐式定义的变量（未定义直接赋值的变量）。 当你定义一个全局变量时，这个变量同时也会成为全局对象的属性，反之亦然。\n需要注意的是，在 Node.js 中你不可能在最外层定义变量，因为所有用户代码都是属于当前模块的， 而模块本身不是最外层上下文。\n注意： 最好不要使用 var 定义变量以避免引入全局变量，因为全局变量会污染命名空间，提高代码的耦合风险。\n内置全局变量 # __filename # __filename 表示当前正在执行的脚本的文件名。它将输出文件所在位置的绝对路径，且和命令行参数所指定的文件名不一定相同。 如果在模块中，返回的值是模块文件的路径。\nconsole.log(__filename) // /Users/yanggang/develop/nodejs-project/nodejs-demo/b.js __dirname # __dirname 表示当前执行脚本所在的目录。\nconsole.log(__dirname) // /Users/yanggang/develop/nodejs-project/nodejs-demo console # console 用于提供控制台标准输出，它是由 Internet Explorer 的 JScript 引擎提供的调试工具，后来逐渐成为浏览器的实施标准。\nNode.js 沿用了这个标准，提供与习惯行为一致的 console 对象，用于向标准输出流（stdout）或标准错误流（stderr）输出字符。\n方法 描述 console.log([data][, ...]) 向标准输出流打印字符并以换行符结束。该方法接收若干个参数，如果只有一个参数，则输出这个参数的字符串形式。如果有多个参数，则以类似于C 语言 printf() 命令的格式输出。 console.info([data][, ...]) 该命令的作用是返回信息性消息，这个命令与console.log差别并不大，除了在chrome中只会输出文字外，其余的会显示一个蓝色的惊叹号。 console.error([data][, ...]) 输出错误消息的。控制台在出现错误时会显示是红色的叉子。 console.warn([data][, ...]) 输出警告消息。控制台出现有黄色的惊叹号。 console.dir(obj[, options]) 用来对一个对象进行检查（inspect），并以易于阅读和打印的格式显示。 console.time(label) 输出时间，表示计时开始。 console.timeEnd(label) 结束时间，表示计时结束。 console.trace(message[, ...]) 当前执行的代码在堆栈中的调用路径，这个测试函数运行很有帮助，只要给想测试的函数里面加入 console.trace 就行了。 console.assert(value[, message][, ...]) 用于判断某个表达式或变量是否为真，接收两个参数，第一个参数是表达式，第二个参数是字符串。只有当第一个参数为false，才会输出第二个参数，否则不会有任何结果。 setTimeout # setTimeout(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。setTimeout() 只执行一次指定函数。\nfunction printHello(){ console.log( \u0026#34;Hello, World!\u0026#34;); } // 两秒后执行以上函数 setTimeout(printHello, 2000); clearTimeout # clearTimeout( t ) 全局函数用于停止一个之前通过 setTimeout() 创建的定时器。 参数 t 是通过 setTimeout() 函数创建的定时器。\nfunction printHello(){ console.log( \u0026#34;Hello, World!\u0026#34;); } // 两秒后执行以上函数 var t = setTimeout(printHello, 2000); // 清除定时器 clearTimeout(t); setInterval # setInterval(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。\n返回一个代表定时器的句柄值。可以使用 clearInterval(t) 函数来清除定时器。\nsetInterval() 方法会不停地调用函数，直到 clearInterval() 被调用或窗口被关闭。\nfunction printHello(){ console.log( \u0026#34;Hello, World!\u0026#34;); } // 每隔两秒执行一次 setInterval(printHello, 2000); process # process 是一个全局变量，即 global 对象的属性。\n它用于描述当前Node.js 进程状态的对象，提供了一个与操作系统的简单接口。通常在你写本地命令行程序的时候，少不了要和它打交道。下面将会介绍 process 对象的一些最常用的成员方法。\n事件 事件 \u0026amp; 描述 exit 当进程准备退出时触发。 beforeExit 当 node 清空事件循环，并且没有其他安排时触发这个事件。通常来说，当没有进程安排时 node 退出，但是 \u0026lsquo;beforeExit\u0026rsquo; 的监听器可以异步调用，这样 node 就会继续执行。 uncaughtException 当一个异常冒泡回到事件循环，触发这个事件。如果给异常添加了监视器，默认的操作（打印堆栈跟踪信息并退出）就不会发生。 Signal 事件 当进程接收到信号时就触发。信号列表详见标准的 POSIX 信号名，如 SIGINT、SIGUSR1 等。 process.on(\u0026#39;exit\u0026#39;, function(code) { // 以下代码永远不会执行 setTimeout(function() { console.log(\u0026#34;该代码不会执行\u0026#34;); }, 0); console.log(\u0026#39;退出码为:\u0026#39;, code); }); console.log(\u0026#34;程序执行结束\u0026#34;); 退出状态码 # 状态码 名称 \u0026amp; 描述 1 Uncaught Fatal Exception 有未捕获异常，并且没有被域或 uncaughtException 处理函数处理。 2 Unused 保留，由 Bash 预留用于内置误用 3 Internal JavaScript Parse Error JavaScript的源码启动 Node 进程时引起解析错误。非常罕见，仅会在开发 Node 时才会有。 4 Internal JavaScript Evaluation Failure JavaScript 的源码启动 Node 进程，评估时返回函数失败。非常罕见，仅会在开发 Node 时才会有。 5 Fatal Error V8 里致命的不可恢复的错误。通常会打印到 stderr ，内容为： FATAL ERROR 6 Non-function Internal Exception Handler 未捕获异常，内部异常处理函数不知为何设置为on-function，并且不能被调用。 7 Internal Exception Handler Run-Time Failure 未捕获的异常， 并且异常处理函数处理时自己抛出了异常。例如，如果 process.on('uncaughtException') 或 domain.on('error') 抛出了异常。 8 Unused 保留，在以前版本的 Node.js 中，退出码 8 有时表示未捕获的异常。 9 Invalid Argument 可能是给了未知的参数，或者给的参数没有值。 10 Internal JavaScript Run-Time Failure JavaScript的源码启动 Node 进程时抛出错误，非常罕见，仅会在开发 Node 时才会有。 12 Invalid Debug Argument 设置了参数--debug 和/或 --debug-brk，但是选择了错误端口。 128 Signal Exits 如果 Node 接收到致命信号，比如SIGKILL 或 SIGHUP，那么退出代码就是128 加信号代码。这是标准的 Unix 做法，退出信号代码放在高位。 process 属性 # 属性 描述 stdout 标准输出流。 stderr 标准错误流。 stdin 标准输入流。 argv argv 属性返回一个数组，由命令行执行脚本时的各个参数组成。它的第一个成员总是node，第二个成员是脚本文件名，其余成员是脚本文件的参数。 execPath 返回执行当前脚本的 Node 二进制文件的绝对路径。 execArgv 返回一个数组，成员是命令行下执行脚本时，在Node可执行文件与脚本文件之间的命令行参数。 env 返回一个对象，成员为当前 shell 的环境变量 exitCode 进程退出时的代码，如果进程优通过 process.exit() 退出，不需要指定退出码。 version Node 的版本，比如v0.10.18。 versions 一个属性，包含了 node 的版本和依赖. config 一个包含用来编译当前 node 执行文件的 javascript 配置选项的对象。它与运行 ./configure 脚本生成的 \u0026ldquo;config.gypi\u0026rdquo; 文件相同。 pid 当前进程的进程号。 title 进程名，默认值为\u0026quot;node\u0026quot;，可以自定义该值。 arch 当前 CPU 的架构：\u0026lsquo;arm\u0026rsquo;、\u0026lsquo;ia32\u0026rsquo; 或者 \u0026lsquo;x64\u0026rsquo;。 platform 运行程序所在的平台系统 \u0026lsquo;darwin\u0026rsquo;, \u0026lsquo;freebsd\u0026rsquo;, \u0026rsquo;linux\u0026rsquo;, \u0026lsquo;sunos\u0026rsquo; 或 \u0026lsquo;win32\u0026rsquo; mainModule require.main 的备选方法。不同点，如果主模块在运行时改变，require.main可能会继续返回老的模块。可以认为，这两者引用了同一个模块。 // 输出到终端 process.stdout.write(\u0026#34;Hello World!\u0026#34; + \u0026#34;\\n\u0026#34;); // 通过参数读取 process.argv.forEach(function(val, index, array) { console.log(index + \u0026#39;: \u0026#39; + val); }); // 获取执行路径 console.log(process.execPath); // 平台信息 console.log(process.platform); process 方法 # 方法 描述 abort() 这将导致 node 触发 abort 事件。会让 node 退出并生成一个核心文件。 chdir(directory) 改变当前工作进程的目录，如果操作失败抛出异常。 cwd() 返回当前进程的工作目录 exit([code]) 使用指定的 code 结束进程。如果忽略，将会使用 code 0。 getgid() 获取进程的群组标识（参见 getgid(2)）。获取到的是群组的数字 id，而不是名字。 注意：这个函数仅在 POSIX 平台上可用(例如，非Windows 和 Android)。 setgid(id) 设置进程的群组标识（参见 setgid(2)）。可以接收数字 ID 或者群组名。如果指定了群组名，会阻塞等待解析为数字 ID 。 注意：这个函数仅在 POSIX 平台上可用(例如，非Windows 和 Android)。 getuid() 获取进程的用户标识(参见 getuid(2))。这是数字的用户 id，不是用户名。 注意：这个函数仅在 POSIX 平台上可用(例如，非Windows 和 Android)。 setuid(id) 设置进程的用户标识（参见setuid(2)）。接收数字 ID或字符串名字。如果指定了群组名，会阻塞等待解析为数字 ID 。 注意：这个函数仅在 POSIX 平台上可用(例如，非Windows 和 Android)。 getgroups() 返回进程的群组 ID 数组。POSIX 系统没有保证一定有，但是 node.js 保证有。 注意：这个函数仅在 POSIX 平台上可用(例如，非Windows 和 Android)。 setgroups(groups) 设置进程的群组 ID。这是授权操作，所以你需要有 root 权限，或者有 CAP_SETGID 能力。 注意：这个函数仅在 POSIX 平台上可用(例如，非Windows 和 Android)。 initgroups(user, extra_group) 读取 /etc/group ，并初始化群组访问列表，使用成员所在的所有群组。这是授权操作，所以你需要有 root 权限，或者有 CAP_SETGID 能力。 注意：这个函数仅在 POSIX 平台上可用(例如，非Windows 和 Android)。 kill(pid[, signal]) 发送信号给进程. pid 是进程id，并且 signal 是发送的信号的字符串描述。信号名是字符串，比如 \u0026lsquo;SIGINT\u0026rsquo; 或 \u0026lsquo;SIGHUP\u0026rsquo;。如果忽略，信号会是 \u0026lsquo;SIGTERM\u0026rsquo;。 memoryUsage() 返回一个对象，描述了 Node 进程所用的内存状况，单位为字节。 nextTick(callback) 一旦当前事件循环结束，调用回调函数。 umask([mask]) 设置或读取进程文件的掩码。子进程从父进程继承掩码。如果mask 参数有效，返回旧的掩码。否则，返回当前掩码。 uptime() 返回 Node 已经运行的秒数。 hrtime() 返回当前进程的高分辨时间，形式为 [seconds, nanoseconds]数组。它是相对于过去的任意事件。该值与日期无关，因此不受时钟漂移的影响。主要用途是可以通过精确的时间间隔，来衡量程序的性能。 你可以将之前的结果传递给当前的 process.hrtime() ，会返回两者间的时间差，用来基准和测量时间间隔。 // 输出当前目录 console.log(\u0026#39;当前目录: \u0026#39; + process.cwd()); // 输出当前版本 console.log(\u0026#39;当前版本: \u0026#39; + process.version); // 输出内存使用情况 console.log(process.memoryUsage()); ","date":"2025-04-27","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/65df2b3b/084f0059/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eglobal \n    \u003cdiv id=\"global\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#global\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eJavaScript 中有一个特殊的对象，称为全局对象（Global Object），它及其所有属性都可以在程序的任何地方访问，即全局变量。\u003c/p\u003e","title":"4、全局对象","type":"posts"},{"content":" 函数构造对象 # 使用函数构造对象通常是通过构造函数的方式。构造函数是一种特殊的函数，用于创建具有特定属性和方法的对象。\n在JavaScript中，this通常指向的是我们正在执行的函数本身，或者是指向该函数所属的对象（运行时）\nfunction Person(name, age) { this.name = name; this.age = age; this.greet = function() { console.log(\u0026#34;Hello, my name is \u0026#34; + this.name); }; } const person1 = new Person(\u0026#34;Alice\u0026#34;, 30); person1.greet(); // 输出: Hello, my name is Alice Class类 # 类是用于创建对象的模板。\nES6引入了类（Class）的概念，提供了一种更接近传统面向对象编程语言的语法来创建和管理对象。\n我们使用 class 关键字来创建一个类，类体在一对大括号 {} 中，我们可以在大括号 {} 中定义类成员的位置，如方法或构造函数。\n每个类中包含了一个特殊的方法 constructor()，它是类的构造函数，这种方法用于创建和初始化一个由 class 创建的对象。\nclass ClassName { constructor() { ... } } class WebSite { constructor(name, url) { this.name = name; this.url = url; } } 创建对象 # 定义好类后，我们就可以使用 new 关键字来创建对象：\nclass Student{ constructor(id,name){ this.id = id this.name = name } } var stu = new Student(\u0026#39;1\u0026#39;,\u0026#39;tom\u0026#39;) console.log(stu) 静态方法 # 类中可以使用static关键字来生成类的静态成员\n注意：类的静态成员只能使用类名来调用\nclass Student{ constructor(id,name){ this.id = id this.name = name } static sayHello(){ console.log(\u0026#39;hello\u0026#39;) } } var stu = new Student(\u0026#39;1\u0026#39;,\u0026#39;tom\u0026#39;) console.log(stu) Student.sayHello() 类继承 # // 基类 class Animal { // eat() 函数 // sleep() 函数 }; //派生类 class Dog extends Animal { // bark() 函数 }; 函数构造对象和Class类的区别 # 语法差异：\n函数使用的是传统函数定义和new关键字。 类使用class关键字定义，更接近其他面向对象编程语言的语法。 原型链：\n使用函数构造对象时，可以通过原型（prototype）属性添加方法，这样可以共享方法，节省内存。例如：Person.prototype.greet = function() {...}。 类使用prototype的方式是自动管理的，不需要手动设置，但可以通过静态方法或属性（使用static关键字）来定义。 继承：\n使用函数时，可以通过原型链实现继承，例如使用Object.create()或Person.prototype = Object.create(SuperPerson.prototype)。 类提供了更直观的继承语法，使用extends关键字。例如：class Student extends Person {}。 封装和复用：\n函数更适合于封装私有变量和行为，通过闭包等技术实现。例如，可以使用立即执行函数表达式（IIFE）来封装变量和方法。 类提供了更自然的封装方式，通过#前缀可以定义私有字段（在严格模式下）。例如：#name。 原型对象 # 在 JavaScript 中，原型（prototype）是一个非常重要的概念，它为对象提供了继承和共享属性的机制。每个 JavaScript 对象都有一个与之关联的原型对象，通过原型对象，可以实现属性和方法的共享，从而减少内存占用。\n所有的 JavaScript 对象都会从一个 prototype（原型对象）中继承属性和方法。\n原型是一个对象，它是其他对象的模板或蓝图。 当一个对象试图访问一个属性或方法时，如果在该对象自身没有找到，JavaScript 会沿着原型链向上查找，直到找到对应的属性或方法，或者达到原型链的顶端 null 为止。 对象的 __proto__ 属性 # 每个 JavaScript 对象（除了 null）都自动拥有一个隐藏的属性 __proto__，它指向该对象的原型对象。这个 __proto__ 是实现继承的关键：\nlet obj = {}; console.log(obj.__proto__); // 输出: [object Object], 即 obj 的原型是 Object.prototype 构造函数和原型 # function Person(first, last, age, eyecolor) { this.firstName = first; this.lastName = last; this.age = age; this.eyeColor = eyecolor; } var myFather = new Person(\u0026#34;John\u0026#34;, \u0026#34;Doe\u0026#34;, 50, \u0026#34;blue\u0026#34;); var myMother = new Person(\u0026#34;Sally\u0026#34;, \u0026#34;Rally\u0026#34;, 48, \u0026#34;green\u0026#34;); ","date":"2025-04-27","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/0d08345a/47e69f85/6adc7d32/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e函数构造对象 \n    \u003cdiv id=\"函数构造对象\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%87%bd%e6%95%b0%e6%9e%84%e9%80%a0%e5%af%b9%e8%b1%a1\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e使用函数构造对象通常是通过构造函数的方式。构造函数是一种特殊的函数，用于创建具有特定属性和方法的对象。\u003c/p\u003e","title":"4、对象和类","type":"posts"},{"content":"pipreqs，这个工具的好处是可以通过对项目目录扫描，将项目使用的模块进行统计，生成依赖清单即requirements.txt文件。\n安装 # pip install pipreqs 使用 # 在项目的根目录下，执行命令\npipreqs ./ --encoding=utf8 --force #. /: 在哪个文件生成requirements.txt 文件 #--encoding=utf8 ：为使用utf8编码 #--force ：强制执行，当生成目录下的requirements.txt存在时覆盖 ","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/46b8de4c/3a4ac500/","section":"文章","summary":"\u003cp\u003epipreqs，这个工具的好处是可以通过对项目目录扫描，将项目使用的模块进行统计，生成依赖清单即\u003ccode\u003erequirements.txt\u003c/code\u003e文件。\u003c/p\u003e","title":"4、pipreqs","type":"posts"},{"content":" try-except-finally # 可以通过try-except-finally对错误进行捕获和处理\n当认为某些代码可能会出错时，就可以用try来运行这段代码，如果执行出错，则后续代码不会继续执行，而是直接跳转至错误处理代码，即except语句块，执行完except后，如果有finally语句块，则执行finally语句块，至此，执行完毕。\ntry: a = 1 / 0 # 处理除零错误 except ZeroDivisionError as e: print(e) finally: print(\u0026#39;is finally\u0026#39;) \u0026#39;\u0026#39;\u0026#39; division by zero is finally \u0026#39;\u0026#39;\u0026#39; 处理多个错误\ntry: a = 1 / int(\u0026#39;a\u0026#39;) # 处理除零错误 except ZeroDivisionError as e: print(e) # 处理参数错误 except ValueError as e: print(e) finally: print(\u0026#39;is finally\u0026#39;) \u0026#39;\u0026#39;\u0026#39; invalid literal for int() with base 10: \u0026#39;a\u0026#39; is finally \u0026#39;\u0026#39;\u0026#39; 可以在except语句块后面添加else语句块，如果没有错误发生，将执行else语句块\ntry: a = 1 / int(\u0026#39;1\u0026#39;) # 处理除零错误 except ZeroDivisionError as e: print(e) # 处理参数错误 except ValueError as e: print(e) else: print(\u0026#39;is else\u0026#39;) finally: print(\u0026#39;is finally\u0026#39;) \u0026#39;\u0026#39;\u0026#39; is else is finally \u0026#39;\u0026#39;\u0026#39; with # with 语句是 Python 中用于简化资源管理的语法糖。它确保在进入代码块时自动获取资源，并在退出代码块时自动释放资源。常见的资源包括文件、网络连接、数据库连接等。with 语句的核心思想是“上下文管理”，即在一定范围内自动处理资源的获取和释放，避免了手动管理资源带来的复杂性和潜在错误。\n总结起来使用python 提供的with主要的作用是：\n实现自动调用对象资源的释放\n异常捕获和回滚\n减少用户手动调用的方法\n常见的几个应用场景有：\n1、文件操作。\n2、进程线程之间互斥对象。\n3、支持上下文其他对象\n4、需要进行资源链接和结束的时候进行释放操作\n5、释放锁\n6、创建代码补丁\nwith 语句依赖于 上下文管理器（Context Manager），这是一个实现了 __enter__ 和 __exit__ 方法的对象。__enter__ 方法在进入 with 代码块时调用，通常用于获取资源；__exit__ 方法在退出 with 代码块时调用，通常用于释放资源。\nwith 语句的基本语法如下：\nwith context_manager as variable: # 执行代码块 其中，context_manager 是一个实现了上下文管理协议的对象，variable 是可选的，用于接收 __enter__ 方法返回的值。\nwith 语句的常见用法 # 文件操作 # with open(\u0026#39;example.txt\u0026#39;, \u0026#39;r\u0026#39;) as file: content = file.read() print(content) 网络连接 # import requests with requests.Session() as session: response = session.get(\u0026#39;https://api.example.com/data\u0026#39;) print(response.json()) 数据库连接 # import sqlite3 with sqlite3.connect(\u0026#39;example.db\u0026#39;) as conn: cursor = conn.cursor() cursor.execute(\u0026#39;SELECT * FROM users\u0026#39;) rows = cursor.fetchall() for row in rows: print(row) 自定义上下文管理器 # import time class Timer: def __enter__(self): self.start_time = time.time() return self def __exit__(self, exc_type, exc_value, traceback): end_time = time.time() elapsed_time = end_time - self.start_time print(f\u0026#34;Elapsed time: {elapsed_time:.2f} seconds\u0026#34;) # 使用自定义上下文管理器 with Timer(): time.sleep(2) with语句的高级用法 # 多个上下文管理器 # with 语句支持同时管理多个上下文管理器，只需将它们用逗号分隔即可。这对于需要同时管理多个资源的场景非常有用。\nwith open(\u0026#39;file1.txt\u0026#39;, \u0026#39;r\u0026#39;) as f1, open(\u0026#39;file2.txt\u0026#39;, \u0026#39;r\u0026#39;) as f2: content1 = f1.read() content2 = f2.read() if content1 == content2: print(\u0026#34;Files are identical\u0026#34;) else: print(\u0026#34;Files are different\u0026#34;) 异常处理 # with 语句不仅可以管理资源，还可以捕获和处理异常。__exit__ 方法可以接受三个参数：exc_type、exc_value 和 traceback，分别表示异常类型、异常值和堆栈跟踪。如果 __exit__ 方法返回 True，则表示异常已被处理，不会传播到外部；如果返回 False 或不返回任何值，则异常会继续传播。\n假设我们想在文件读取过程中捕获并处理 FileNotFoundError 异常。我们可以在自定义上下文管理器中实现这一功能。\nclass FileOpener: def __init__(self, filename): self.filename = filename self.file = None def __enter__(self): try: self.file = open(self.filename, \u0026#39;r\u0026#39;) return self.file except FileNotFoundError: print(f\u0026#34;File {self.filename} not found\u0026#34;) return None def __exit__(self, exc_type, exc_value, traceback): if self.file: self.file.close() # 使用自定义上下文管理器 with FileOpener(\u0026#39;nonexistent.txt\u0026#39;) as file: if file: content = file.read() print(content) else: print(\u0026#34;File not found, skipping...\u0026#34;) python错误继承关系 # BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration +-- StopAsyncIteration +-- ArithmeticError | +-- FloatingPointError | +-- OverflowError | +-- ZeroDivisionError +-- AssertionError +-- AttributeError +-- BufferError +-- EOFError +-- ImportError | +-- ModuleNotFoundError +-- LookupError | +-- IndexError | +-- KeyError +-- MemoryError +-- NameError | +-- UnboundLocalError +-- OSError | +-- BlockingIOError | +-- ChildProcessError | +-- ConnectionError | | +-- BrokenPipeError | | +-- ConnectionAbortedError | | +-- ConnectionRefusedError | | +-- ConnectionResetError | +-- FileExistsError | +-- FileNotFoundError | +-- InterruptedError | +-- IsADirectoryError | +-- NotADirectoryError | +-- PermissionError | +-- ProcessLookupError | +-- TimeoutError +-- ReferenceError +-- RuntimeError | +-- NotImplementedError | +-- RecursionError +-- SyntaxError | +-- IndentationError | +-- TabError +-- SystemError +-- TypeError +-- ValueError | +-- UnicodeError | +-- UnicodeDecodeError | +-- UnicodeEncodeError | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning +-- BytesWarning +-- EncodingWarning +-- ResourceWarning 日志 logging # 如果不捕获错误，自然可以让Python解释器来打印出错误堆栈，但程序也被结束了。既然我们能捕获错误，就可以把错误堆栈打印出来，然后分析错误原因，同时，让程序继续执行下去。\nPython内置的logging模块可以非常容易地记录错误信息\nimport logging try: a = 1 / 0 except BaseException as e: print(e) logging.exception(e) finally: print(\u0026#39;is finally\u0026#39;) \u0026#39;\u0026#39;\u0026#39; division by zero ERROR:root:division by zero Traceback (most recent call last): File \u0026#34;d:\\python_project\\demo\\main.py\u0026#34;, line 4, in \u0026lt;module\u0026gt; a = 1 / 0 ZeroDivisionError: division by zero is finally \u0026#39;\u0026#39;\u0026#39; logging错误级别 # CRITICAL \u0026gt; ERROR \u0026gt; WARNING \u0026gt; INFO \u0026gt; DEBUG\ndebug : 打印全部的日志,详细的信息,通常只出现在诊断问题上\ninfo : 打印info,warning,error,critical级别的日志,确认一切按预期运行\nwarning : 打印warning,error,critical级别的日志,一个迹象表明,一些意想不到的事情发生了,或表明一些问题在不久的将来(例如。磁盘空间低”),这个软件还能按预期工作\nerror : 打印error,critical级别的日志,更严重的问题,软件没能执行一些功能，exception属于error，但是会显示堆栈\ncritical : 打印critical级别,一个严重的错误,这表明程序本身可能无法继续运行\n常用api # Logging.Formatter # 这个类配置了日志的格式，在里面自定义设置日期和时间，输出日志的时候将会按照设置的格式显示内容。 Logging.Logger # Logger是Logging模块的主体，进行以下三项工作： # 1. 为程序提供记录日志的接口 # 2. 判断日志所处级别，并判断是否要过滤 # 3. 根据其日志级别将该条日志分发给不同handler # Logger常用函数有： Logger.setLevel() # 设置日志级别 Logger.addHandler() # 和 Logger.removeHandler() 添加和删除一个Handler Logger.addFilter() # 添加一个Filter,过滤作用 Logging.Handler # Handler基于日志级别对日志进行分发，如设置为WARNING级别的Handler只会处理WARNING及以上级别的日志。 # Handler常用函数有： setLevel() 设置级别 setFormatter() 设置Formatter 输出日志到文件 # import logging #创建一个logger logger = logging.getLogger() #Log等级总开关 logger.setLevel(logging.INFO) # 创建一个handler，用于写入日志文件,mode=\u0026#39;w\u0026#39;表示程序重启重写日志 fh = logging.FileHandler(\u0026#39;./logging.log\u0026#39;,mode=\u0026#39;w\u0026#39;) # 定义handler的输出格式 formatter = logging.Formatter(\u0026#34;%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s\u0026#34;) fh.setFormatter(formatter) # 输出到file的log等级的开关 fh.setLevel(logging.ERROR) logger.addHandler(fh) try: a = 1 / 0 except BaseException as e: print(e) logging.exception(e) finally: print(\u0026#39;is finally\u0026#39;) \u0026#39;\u0026#39;\u0026#39; logging.py文件： 2022-02-14 21:11:28,643 - main.py[line:20] - ERROR: division by zero Traceback (most recent call last): File \u0026#34;d:\\python_project\\demo\\main.py\u0026#34;, line 17, in \u0026lt;module\u0026gt; a = 1 / 0 ZeroDivisionError: division by zero \u0026#39;\u0026#39;\u0026#39; format常用格式 # %(levelno)s # 打印日志级别的数值 %(levelname)s # 打印日志级别名称 %(pathname)s # 打印当前执行程序的路径，其实就是sys.argv[0] %(filename)s # 打印当前执行程序名 %(funcName)s # 打印日志的当前函数 %(lineno)d # 打印日志的当前行号 %(asctime)s # 打印日志的时间 %(thread)d # 打印线程ID %(threadName)s # 打印线程名称 %(process)d # 打印进程ID %(message)s # 打印日志信息 自定义错误、抛出错误 # 错误也是一个class，所以需要继承一个合适的错误类\n使用raise可以抛出一个错误\nclass NameError(ValueError): pass def setName(name): if(len(name) \u0026gt; 4): raise NameError(\u0026#39;名称不合法：%s\u0026#39; % name) setName(\u0026#39;一二三四五\u0026#39;) \u0026#39;\u0026#39;\u0026#39; Traceback (most recent call last): File \u0026#34;d:\\python_project\\demo\\main.py\u0026#34;, line 11, in \u0026lt;module\u0026gt; setName(\u0026#39;一二三四五\u0026#39;) File \u0026#34;d:\\python_project\\demo\\main.py\u0026#34;, line 9, in setName raise NameError(\u0026#39;名称不合法：%s\u0026#39; % name) __main__.NameError: 名称不合法：一二三四五 \u0026#39;\u0026#39;\u0026#39; ","date":"2025-04-22","externalUrl":null,"permalink":"/posts/1b37041b/a64b122d/04d49706/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003etry-except-finally \n    \u003cdiv id=\"try-except-finally\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#try-except-finally\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e可以通过\u003ccode\u003etry-except-finally\u003c/code\u003e对错误进行捕获和处理\u003c/p\u003e","title":"4、异常、日志","type":"posts"},{"content":"获取用户输入信息\nfrom pydoc import text import tkinter as tk root = tk.Tk() # 创建 username 的标签和输入框，并配置布局 u_lable = tk.Label(root,text=\u0026#34;用户名：\u0026#34;) u_lable.grid(row=0, column=0, sticky= tk.W) u_entry = tk.Entry(root) u_entry.grid(row = 0, column = 1, sticky = tk.E) # 创建 password 的标签和输入框，并配置布局 p_lable = tk.Label(root,text=\u0026#34;密码：\u0026#34;) p_lable.grid(row=1,column=0,sticky= tk.W) p_entry = tk.Entry(root) p_entry.grid(row = 1, column = 1, sticky = tk.E) p_entry[\u0026#39;show\u0026#39;] = \u0026#39;*\u0026#39; # 密码输入框，所以将所有的输入内容显示为星号 # 登录方法 def login(): username = u_entry.get() password = p_entry.get() print(f\u0026#39;username:{username},password:{password}\u0026#39;) # 创建登录按钮 login_btn = tk.Button(root,text=\u0026#39;登录\u0026#39;,command=login) login_btn.grid(row=2) root.mainloop() 属性 # 选项 含义 background 1. 设置 Entry 的背景颜色 ；2. 默认值由系统指定 bg 跟 background 一样 borderwidth 1. 设置 Entry 的边框宽度 ；2. 默认值是 1 或 2 像素 bd 跟 borderwidth 一样 cursor 1. 指定当鼠标在 Entry 上飘过的时候的鼠标样式 ；2. 默认值由系统指定 exportselection 1. 指定选中的文本是否可以被复制到剪贴板 ；2. 默认值是 True ；3. 可以修改为 False 表示不允许复制文本 font 1. 指定 Entry 中文本的字体 ；2. 默认值由系统指定 foreground 1. 设置 Entry 的文本颜色； 2. 默认值由系统指定 fg 跟 foreground 一样 highlightbackground 1. 指定当 Entry 没有获得焦点的时候高亮边框的颜色 ；2. 默认值由系统指定 highlightcolor 1. 指定当 Entry 获得焦点的时候高亮边框的颜色 ；2. 默认值由系统指定 highlightthickness 1. 指定高亮边框的宽度 ；2. 默认值是 1 或 2 像素 insertbackground 指定输入光标的颜色 insertborderwidth 1. 指定输入光标的边框宽度； 2. 如果被设置为非 0 值，光标样式会被设置为 RAISED ；3.将 insertwidth 设置大一点才能看到效果哦 insertofftime 1. 该选项控制光标的闪烁频率（灭） ；2. 单位是毫秒 insertontime 1. 该选项控制光标的闪烁频率（亮） ；2. 单位是毫秒 insertwidth 1. 指定光标的宽度 ；2. 默认值是 1 或 2 像素 invalidcommand 1. 指定当输入框输入的内容“非法”时调用的函数 ；2. 也就是指定当 validateCommand 选项指定的函数返回 False 时的函数 ；3. 详见本内容最下方小甲鱼关于验证详解 invcmd 跟 invalidcommand 一样 justify 1. 定义如何对齐输入框中的文本 ；2. 使用 \u0026ldquo;left\u0026rdquo;，\u0026ldquo;right\u0026rdquo; 或 \u0026ldquo;center\u0026rdquo; ；3. 默认值是 \u0026ldquo;left\u0026rdquo; relief 1. 指定边框样式 ；2. 默认值是 \u0026ldquo;sunken\u0026rdquo; ；3. 其他可以选择的值是 \u0026ldquo;flat\u0026rdquo;，\u0026ldquo;raised\u0026rdquo;，\u0026ldquo;groove\u0026rdquo; 和 \u0026ldquo;ridge\u0026rdquo; selectbackground 1. 指定输入框的文本被选中时的背景颜色 ；2. 默认值由系统指定 selectborderwidth 1. 指定输入框的文本被选中时的边框宽度（选中边框） ；2. 默认值由系统指定 selectforeground 1. 指定输入框的文本被选中时的字体颜色 ；2. 默认值由系统指定 show 1. 设置输入框如何显示文本的内容； 2. 如果该值非空，则输入框会显示指定字符串代替真正的内容 ；3. 将该选项设置为 \u0026ldquo;*\u0026quot;，则是密码输入框 state 1. Entry 组件可以设置的状态：\u0026ldquo;normal\u0026rdquo;，\u0026ldquo;disabled\u0026rdquo; 或 \u0026ldquo;readonly\u0026rdquo;（注意，它跟 \u0026ldquo;disabled\u0026rdquo; 相似，但它支持选中和拷贝，只是不能修改，而 \u0026ldquo;disabled\u0026rdquo; 是完全禁止） ；2. 默认值是 \u0026ldquo;normal\u0026rdquo; ；3.注意，如果此选项设置为 \u0026ldquo;disabled\u0026rdquo; 或 \u0026ldquo;readonly\u0026rdquo;，那么调用 insert() 和 delete() 方法都会被忽略 takefocus 1. 指定使用 Tab 键可以将焦点移动到输入框中 ；2. 默认是开启的，可以将该选项设置为 False 避免焦点在此输入框中 extvariable 1. 指定一个与输入框的内容相关联的 Tkinter 变量（通常是 StringVar） ；2. 当输入框的内容发生改变时，该变量的值也会相应发生改变 validate 1. 该选项设置是否启用内容验证 validatecommand 1. 该选项指定一个验证函数，用于验证输入框内容是否合法 ；2. 验证函数需要返回 True 或 False 表示验证结果 3. 注意，该选项只有当 validate 的值非 \u0026ldquo;none\u0026rdquo; 时才有效 vcmd 跟 validatecommand 一样 width 1. 设置输入框的宽度，以字符为单位 ；2. 默认值是 20 ；3. 对于变宽字体来说，组件的实际宽度等于字体的平均宽度乘以 width 选项的值 xscrollcommand 1. 与 scrollbar（滚动条）组件相关联 ；2. 如果你觉得用户输入的内容会超过该组件的输入框宽度，那么可以考虑设置该选项 方法 # delete(first, last=None)\n删除参数 first 到 last 范围内（包含 first 和 last）的所有内容 如果忽略 last 参数，表示删除 first 参数指定的选项 使用 delete(0, END) 实现删除输入框的所有内容 get()\n获得当前输入框的内容 icursor(index)\n将光标移动到 index 参数指定的位置 这同时也会设置 INSERT 的值 index(index)\n返回与 index 参数相应的选项的序号（例如 e.index(END)） insert(index, text)\n将 text 参数的内容插入到 index 参数指定的位置 使用 insert(INSERT, text) 将 text 参数指定的字符串插入到光标的位置 使用 insert(END, text) 将 text 参数指定的字符串插入到输入框的末尾 scan_dragto(x)\n见下方 scan_mark(x) scan_mark(x)\n使用这种方式来实现输入框内容的滚动 需要将鼠标按下事件绑定到 scan_mark(x) 方法（x 是鼠标当前的水平位置），然后再将 \u0026lt;motion\u0026gt; 事件绑定到 scan_dragto(x) 方法（x 是鼠标当前的水平位置），就可以实现输入框在当前位置和 sacn_mack(x) 指定位置之间的水平滚动 select_adjust(index)\n与 selection_adjust(index) 相同，见下方解释 select_clear()\n与 selection_clear() 相同，见下方解释 select_from(index)\n与 selection_from(index) 相同，见下方解释 select_present()\n与 selection_present() 相同，见下方解释 select_range(start, end)\n与 selection_range(start, end) 相同，见下方解释 select_to(index)\n与 selection_to(index) 相同，见下方解释 selection_adjust(index)\n该方法是为了确保输入框中选中的范围包含 index 参数所指定的字符 如果选中的范围已经包含了该字符，那么什么事情也不会发生 如果选中的范围不包含该字符，那么会从光标的位置将选中的范围扩展至该字符 selection_clear()\n取消选中状态 selection_from(index)\n开始一个新的选中范围 会设置 ANCHOR 的值 selection_present()\n返回输入框是否有处于选中状态的文本 如果有则返回 True，否则返回 False selection_range(start, end)\n设置选中范围 start 参数必须必 end 参数小 使用 selection_range(0, END) 选中整个输入框的所有内容 selection_to(index)\n选中 ANCHOR 到 index 参数的间的所有内容 xview(index)\n该方法用于确保给定的 index 参数所指定的字符可见 如有必要，会滚动输入框的内容 xview_moveto(fraction)\n根据 fraction 参数给定的比率调整输入框内容的可见范围 fraction 参数的范围是 0.0 ~ 1.0，0.0 表示输入框的开始位置，1.0 表示输入框的结束位置 xview_scroll(number, what)\n根据给定的参数水平滚动输入框的可见范围 number 参数指定滚动的数量，如果是负数则表示反向滚动 what 参数指定滚动的单位，可以是 UNITS 或 PAGES（UNITS 表示一个字符单元，PAGES 表示一页） 验证 # Entry 组件是支持验证输入内容的合法性的，比如要求输入数字，你输入了字母那就是非法。\n实现该功能，需要通过设置 validate、validatecommand 和 invalidcommand 选项。\n首先启用验证的“开关”是 validate 选项，该选项可以设置的值有：\n值 含义 \u0026lsquo;focus\u0026rsquo; 当 Entry 组件获得或失去焦点的时候验证 \u0026lsquo;focusin\u0026rsquo; 当 Entry 组件获得焦点的时候验证 \u0026lsquo;focusout\u0026rsquo; 当 Entry 组件失去焦点的时候验证 \u0026lsquo;key\u0026rsquo; 当输入框被编辑的时候验证 \u0026lsquo;all\u0026rsquo; 当出现上边任何一种情况的时候验证 \u0026rsquo;none' 1. 关闭验证功能 2. 默认设置该选项（即不启用验证） 3. 注意，是字符串的 \u0026rsquo;none\u0026rsquo;，而非 None 其次是为 validatecommand 选项指定一个验证函数，该函数只能返回 True 或 False 表示验证的结果。一般情况下验证函数只需要知道输入框的内容即可，可以通过 Entry 组件的 get() 方法获得该字符串。\n然后，invalidcommand 选项指定的函数只有在 validatecommand 的返回值为 False 的时候才被调用。\nimport tkinter as tk master = tk.Tk() def test(): if e1.get() == \u0026#34;CSDN\u0026#34;: print(\u0026#34;正确！\u0026#34;) return True else: print(\u0026#34;错误！\u0026#34;) e1.delete(0, \u0026#34;end\u0026#34;) return False def test2(): print(\u0026#34;我被调用了......\u0026#34;) return True v = tk.StringVar() e1 = tk.Entry(master, textvariable=v, validate=\u0026#34;focusout\u0026#34;, validatecommand=test, invalidcommand=test2) e2 = tk.Entry(master) e1.pack(padx=10, pady=10) e2.pack(padx=10, pady=10) master.mainloop() Spinbox # Spinbox 组件（Tk8.4 新增）是Entry 组件的变体，用于从一些固定的值中选取一个。\nimport tkinter as tk root = tk.Tk() w = tk.Spinbox(root, from_=0, to=10) w.pack() root.mainloop() 还可以通过元组指定允许输入的值\nimport tkinter as tk root = tk.Tk() w = tk.Spinbox(root, values= (\u0026#34;小新\u0026#34;, \u0026#34;风间\u0026#34;, \u0026#34;正男\u0026#34;, \u0026#34;妮妮\u0026#34;, \u0026#34;阿呆\u0026#34;)) w.pack() root.mainloop() ","date":"2025-04-15","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/a879fe06/5b5612fc/","section":"文章","summary":"\u003cp\u003e获取用户输入信息\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003epydoc\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003etext\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003etkinter\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nn\"\u003etk\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eTk\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 创建 username 的标签和输入框，并配置布局\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eu_lable\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLabel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003etext\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;用户名：\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eu_lable\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egrid\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecolumn\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esticky\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eW\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eu_entry\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eEntry\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eu_entry\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egrid\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecolumn\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esticky\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eE\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 创建 password 的标签和输入框，并配置布局\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ep_lable\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLabel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003etext\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;密码：\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ep_lable\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egrid\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ecolumn\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003esticky\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eW\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ep_entry\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eEntry\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ep_entry\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egrid\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecolumn\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esticky\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eE\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ep_entry\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;show\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;*\u0026#39;\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 密码输入框，所以将所有的输入内容显示为星号\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 登录方法\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003elogin\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eusername\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eu_entry\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epassword\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ep_entry\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;username:\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003eusername\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s1\"\u003e,password:\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003epassword\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 创建登录按钮\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elogin_btn\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eButton\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003etext\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;登录\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ecommand\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003elogin\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elogin_btn\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egrid\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erow\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emainloop\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e属性 \n    \u003cdiv id=\"属性\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b1%9e%e6%80%a7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e\u003cstrong\u003e选项\u003c/strong\u003e\u003c/th\u003e\n          \u003cth\u003e\u003cstrong\u003e含义\u003c/strong\u003e\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ebackground\u003c/td\u003e\n          \u003ctd\u003e1. 设置 Entry 的背景颜色 ；2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ebg\u003c/td\u003e\n          \u003ctd\u003e跟 background 一样\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eborderwidth\u003c/td\u003e\n          \u003ctd\u003e1. 设置 Entry 的边框宽度 ；2. 默认值是 1 或 2 像素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ebd\u003c/td\u003e\n          \u003ctd\u003e跟 borderwidth 一样\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ecursor\u003c/td\u003e\n          \u003ctd\u003e1. 指定当鼠标在 Entry 上飘过的时候的鼠标样式 ；2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eexportselection\u003c/td\u003e\n          \u003ctd\u003e1. 指定选中的文本是否可以被复制到剪贴板 ；2. 默认值是 True ；3. 可以修改为 False 表示不允许复制文本\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003efont\u003c/td\u003e\n          \u003ctd\u003e1. 指定 Entry 中文本的字体 ；2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eforeground\u003c/td\u003e\n          \u003ctd\u003e1. 设置 Entry 的文本颜色； 2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003efg\u003c/td\u003e\n          \u003ctd\u003e跟 foreground 一样\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehighlightbackground\u003c/td\u003e\n          \u003ctd\u003e1. 指定当 Entry 没有获得焦点的时候高亮边框的颜色 ；2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehighlightcolor\u003c/td\u003e\n          \u003ctd\u003e1. 指定当 Entry 获得焦点的时候高亮边框的颜色 ；2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ehighlightthickness\u003c/td\u003e\n          \u003ctd\u003e1. 指定高亮边框的宽度 ；2. 默认值是 1 或 2 像素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003einsertbackground\u003c/td\u003e\n          \u003ctd\u003e指定输入光标的颜色\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003einsertborderwidth\u003c/td\u003e\n          \u003ctd\u003e1. 指定输入光标的边框宽度； 2. 如果被设置为非 0 值，光标样式会被设置为 RAISED ；3.将 insertwidth 设置大一点才能看到效果哦\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003einsertofftime\u003c/td\u003e\n          \u003ctd\u003e1. 该选项控制光标的闪烁频率（灭） ；2. 单位是毫秒\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003einsertontime\u003c/td\u003e\n          \u003ctd\u003e1. 该选项控制光标的闪烁频率（亮） ；2. 单位是毫秒\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003einsertwidth\u003c/td\u003e\n          \u003ctd\u003e1. 指定光标的宽度 ；2. 默认值是 1 或 2 像素\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003einvalidcommand\u003c/td\u003e\n          \u003ctd\u003e1. 指定当输入框输入的内容“非法”时调用的函数 ；2. 也就是指定当 validateCommand 选项指定的函数返回 False 时的函数 ；3. 详见本内容最下方小甲鱼关于验证详解\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003einvcmd\u003c/td\u003e\n          \u003ctd\u003e跟 invalidcommand 一样\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ejustify\u003c/td\u003e\n          \u003ctd\u003e1. 定义如何对齐输入框中的文本 ；2. 使用 \u0026ldquo;left\u0026rdquo;，\u0026ldquo;right\u0026rdquo; 或 \u0026ldquo;center\u0026rdquo; ；3. 默认值是 \u0026ldquo;left\u0026rdquo;\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003erelief\u003c/td\u003e\n          \u003ctd\u003e1. 指定边框样式 ；2. 默认值是 \u0026ldquo;sunken\u0026rdquo; ；3. 其他可以选择的值是 \u0026ldquo;flat\u0026rdquo;，\u0026ldquo;raised\u0026rdquo;，\u0026ldquo;groove\u0026rdquo; 和 \u0026ldquo;ridge\u0026rdquo;\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eselectbackground\u003c/td\u003e\n          \u003ctd\u003e1. 指定输入框的文本被选中时的背景颜色 ；2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eselectborderwidth\u003c/td\u003e\n          \u003ctd\u003e1. 指定输入框的文本被选中时的边框宽度（选中边框） ；2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eselectforeground\u003c/td\u003e\n          \u003ctd\u003e1. 指定输入框的文本被选中时的字体颜色 ；2. 默认值由系统指定\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eshow\u003c/td\u003e\n          \u003ctd\u003e1. 设置输入框如何显示文本的内容； 2. 如果该值非空，则输入框会显示指定字符串代替真正的内容 ；3. 将该选项设置为 \u0026ldquo;*\u0026quot;，则是密码输入框\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003estate\u003c/td\u003e\n          \u003ctd\u003e1. Entry 组件可以设置的状态：\u0026ldquo;normal\u0026rdquo;，\u0026ldquo;disabled\u0026rdquo; 或 \u0026ldquo;readonly\u0026rdquo;（注意，它跟 \u0026ldquo;disabled\u0026rdquo; 相似，但它支持选中和拷贝，只是不能修改，而 \u0026ldquo;disabled\u0026rdquo; 是完全禁止） ；2. 默认值是 \u0026ldquo;normal\u0026rdquo; ；3.注意，如果此选项设置为 \u0026ldquo;disabled\u0026rdquo; 或 \u0026ldquo;readonly\u0026rdquo;，那么调用 insert() 和 delete() 方法都会被忽略\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003etakefocus\u003c/td\u003e\n          \u003ctd\u003e1. 指定使用 Tab 键可以将焦点移动到输入框中 ；2. 默认是开启的，可以将该选项设置为 False 避免焦点在此输入框中\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eextvariable\u003c/td\u003e\n          \u003ctd\u003e1. 指定一个与输入框的内容相关联的 Tkinter 变量（通常是 StringVar） ；2. 当输入框的内容发生改变时，该变量的值也会相应发生改变\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003evalidate\u003c/td\u003e\n          \u003ctd\u003e1. 该选项设置是否启用内容验证\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003evalidatecommand\u003c/td\u003e\n          \u003ctd\u003e1. 该选项指定一个验证函数，用于验证输入框内容是否合法 ；2. 验证函数需要返回 True 或 False 表示验证结果 3. 注意，该选项只有当 validate 的值非 \u0026ldquo;none\u0026rdquo; 时才有效\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003evcmd\u003c/td\u003e\n          \u003ctd\u003e跟 validatecommand 一样\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003ewidth\u003c/td\u003e\n          \u003ctd\u003e1. 设置输入框的宽度，以字符为单位 ；2. 默认值是 20 ；3. 对于变宽字体来说，组件的实际宽度等于字体的平均宽度乘以 width 选项的值\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003exscrollcommand\u003c/td\u003e\n          \u003ctd\u003e1. 与 scrollbar（滚动条）组件相关联 ；2. 如果你觉得用户输入的内容会超过该组件的输入框宽度，那么可以考虑设置该选项\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2 class=\"relative group\"\u003e方法 \n    \u003cdiv id=\"方法\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e6%b3%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003edelete(first, last=None)\u003c/code\u003e\u003c/p\u003e","title":"4、输入框Entry","type":"posts"},{"content":"Bool Tool ：布尔工具\nLoopTools ：平分等功能\nExtra Mesh Objects ：拓展模型\nMaterial Library ：材质库\n","date":"2025-04-12","externalUrl":null,"permalink":"/posts/69064821/f1464f3a/1ae3e84b/","section":"文章","summary":"\u003cp\u003eBool Tool ：布尔工具\u003c/p\u003e\n\u003cp\u003eLoopTools ：平分等功能\u003c/p\u003e\n\u003cp\u003eExtra Mesh Objects ：拓展模型\u003c/p\u003e\n\u003cp\u003eMaterial Library ：材质库\u003c/p\u003e","title":"4、常用插件","type":"posts"},{"content":" 条件和分支 # 总体和java一样，需要注意的是，java的else if在python中是elif\nif\u0026hellip;elif\u0026hellip;else # age = 18 if age \u0026gt; 18: print(\u0026#34;你的年龄大于18岁\u0026#34;) elif age \u0026gt; 10: print(\u0026#34;您的年龄大于10岁，但是小于等于18岁\u0026#34;) else: print(\u0026#34;您的年龄未满10岁\u0026#34;) 循环 # range() # range可以生成整数序列，可以指定开始和结束整数（结果不包含结束整数），如果不指定开始，默认为零\nli = list(range(100)) #0-100的列表 tup = tuple(range(0,100)) #0-100的元组 set = tuple(range(100)) #0-100的集合 range(0,10,2) #0-10的序列，跨度为2，结果：0，2，4，6，8 for-in # 可以依次把list、tuple、set中的每个元素迭代出来，也可以将dictionary中的key迭代出来\nfor i in [1,2,3,4,5,6,7,8,9,10]: print(i) for i in range(10): print(i) for-in-else # 如果for遍历完，会执行else语句块\nfor i in range(10): print(\u0026#39;循环第\u0026#39;,i,\u0026#39;次\u0026#39;) else: print(\u0026#39;循环结束，i = \u0026#39;,i) while # while循环，只要条件满足，就不断循环，条件不满足时退出循环\ni = 0 while(i \u0026lt; 10): print(\u0026#39;循环第\u0026#39;,i,\u0026#39;次\u0026#39;) i = i + 1 while-else # 如果 while 后面的条件语句为 false 时，则执行 else 的语句块\ni = 0 while(i \u0026lt; 10): print(\u0026#39;循环第\u0026#39;,i,\u0026#39;次\u0026#39;) i = i + 1 else: print(\u0026#39;循环结束\u0026#39;) break 和 continue # continue关键字用于：中断本次循环，直接进入下一次循环\nbreak关键字用于：直接结束所在循环\n注意：continue 和 break 在嵌套循环中，只会作用在所在的循环上，无法对上层循环起作用\n","date":"2025-04-09","externalUrl":null,"permalink":"/posts/1b37041b/aae761a6/0d2fe65a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e条件和分支 \n    \u003cdiv id=\"条件和分支\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9d%a1%e4%bb%b6%e5%92%8c%e5%88%86%e6%94%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e总体和java一样，需要注意的是，java的\u003ccode\u003eelse if\u003c/code\u003e在python中是\u003ccode\u003eelif\u003c/code\u003e\u003c/p\u003e","title":"4、流程控制语句","type":"posts"},{"content":" 函数 # fun main() { println(\u0026#34;Hello World\u0026#34;) } 我们程序的入口点就是main函数，我们只需要将我们的程序代码编写到主函数中就可以运行了，不过这个函数只是由我们来定义，而不是我们自己来调用。当然，除了主函数之外，我们一直在使用的println也是一个函数，不过这个函数是标准库中已经实现好了的\n声明函数 # fun 函数名称([函数参数...]): 返回值类型 { //函数体 } fun main(args: Array\u0026lt;String\u0026gt;) { sayHello1() sayHello2() println(add(10,20)) } fun sayHello1(){ println(\u0026#34;Hello\u0026#34;) } fun sayHello2(): Unit{ println(\u0026#34;Hello\u0026#34;) } fun add(num1: Int,num2: Int): Int{ return num1 + num2 } 参数 # 对于上面的函数add，num1和num2就是函数的参数，Kotlin 支持参数声明默认值\nfun main(args: Array\u0026lt;String\u0026gt;) { println(add(10,20)) // 30 println(add(10)) // 20 , 10传给了第一个参数 println(add()) // 11 , 两个都是用默认参数 println(add(num2 = 20)) // 21 , 将 20 指定传给第二个参数 } fun add(num1: Int = 1,num2: Int = 10): Int{ return num1 + num2 } 可变长参数，使用vararg关键字将参数标记为可变长参数\n注意：可变长参数在函数的形参列表里面只能存在一个\nfun main() { test(\u0026#34;zhangsan\u0026#34;,\u0026#34;lisi\u0026#34;,\u0026#34;wangwu\u0026#34;) } fun test(vararg names: String){ for (name in names){ println(name) } } 可变长参数的本质是一个数组，但是不同于 Java，可变长参数不可以直接传入一个数组，而需要使用*来传入\nfun main() { var names = arrayOf(\u0026#34;zhangsan\u0026#34;,\u0026#34;lisi\u0026#34;,\u0026#34;wangwu\u0026#34;) test(\u0026#34;xiaoming\u0026#34;,*names,\u0026#34;zhaoliu\u0026#34;) // 使用*传入数组，并且前后都可以续写 } fun test(vararg names: String){ for (name in names){ println(name) } } 返回值 # 对于上面的函数sayHello实际返回是Unit类型，这个类型表示空，类似于Java中的void，默认情况下可以省略\n返回值简化，对于一些内容比较简单的函数，比如上面仅仅是计算两个参数的和，我们可以直接省略掉花括号\nfun main(args: Array\u0026lt;String\u0026gt;) { println(add(10,20)) // 30 println(sub(20,10)) // 10\t} fun add(num1: Int,num2: Int): Int = num1 + num2 fun sub(num1: Int,num2: Int) = num1 - num2 // 自动推断返回值类型 嵌套函数 # 函数内部也可以定义函数\nfun main() { myFun1() } fun myFun1(){ val a = 1 fun myFun2(){ println(a) } myFun2() } 函数内的函数作用域是受限的，我们只能在函数内部使用；内部函数可以访问外部函数中的变量。\n重载 # 我们不能同时编写多个同名函数，但是如果多个同名函数的参数不一致，是允许的。\n注意：返回值类型不同不能作为重载条件！\nfun add(num1: Int,num2: Int): Int{ return num1 + num2 } fun add(num1: Int,num2: Int,num3: Int): Int{ return num1 + num2 + num3 } 递归函数 # 在Kotlin中，可以使用tailrec修饰符来标记尾递归函数。编译器会识别被tailrec修饰的函数，并进行优化处理，将递归调用转换为循环，从而避免栈溢出问题。\ntailrec fun test(n: Int, sum: Int = 0): Int { if(n \u0026lt;= 0) return sum //到底时返回累加的结果 return test(n - 1, sum + n) //不断累加 } 高阶函数与lambda表达式 # Kotlin中的函数属于一等公民，它支持很多高级特性，甚至可以被存储在变量中，可以作为参数传递给其他高阶函数并从中返回，就想使用普通变量一样。 为了实现这一特性，Kotlin作为一种静态类型的编程语言，使用了一系列函数类型来表示函数，并提供了一套特殊的语言结构，例如lambda表达式。\n声明规则 # 所有函数类型都有一个括号，并在括号中填写参数类型列表和一个返回类型，比如：(A, B) -\u0026gt; C 表示一个函数类型，该类型表示接受类型A和B的两个参数并返回类型C的值的函数。参数类型列表可为空的，比如() -\u0026gt; A，注意，即使是Unit返回类型也不能省略。\n作为变量声明 # var add: (Int,Int) -\u0026gt; Int 此时add为变量名，(Int,Int) -\u0026gt; Int为变量类型\n函数变量的赋值\n// 使用 Lambda 表达式赋值 var add: (Int,Int) -\u0026gt; Int = { num1, num2 -\u0026gt; num1 + num2} // 使用匿名函数赋值 var myFunc: (String) -\u0026gt; Unit = fun (str: String){ println(str) } 赋值已存在的函数\nfun test(str: String){ println(\u0026#34;hello\u0026#34;) } var myFunc: (String) -\u0026gt; Unit = ::test 作为函数参数声明 # fun main() { test { str -\u0026gt; print(str) } } fun test(handle: (String) -\u0026gt; Unit){ handle(\u0026#34;Hello\u0026#34;) } 类型别名 # 如果一个方法中声明了多个函数类型的参数，或者是存在函数类型参数嵌套，那么可以使用类型别名来缩短名称\nfun main() { test { str -\u0026gt; print(str) } } typealias HandleFun = (String) -\u0026gt; Unit fun test(handle: HandleFun){ handle(\u0026#34;Hello\u0026#34;) } Lambda # 一个Lambda表达式只需要直接在花括号中编写函数体即可{params -\u0026gt; returnValue}\n除了上面的可以使用 Lambda 表达式给一个函数类型变量赋值以外，还可以使用 Lambda 表达式传参\nfun main() { test({msg -\u0026gt; println(msg)}) } fun test(handle: (String) -\u0026gt; Unit){ handle(\u0026#34;Hello\u0026#34;) } 如果函数的最后一个形式参数是一个函数类型，可以直接写在括号后面，就像下面这样\ntest(){msg -\u0026gt; println(msg)} 如果函数只有一个参数，那么可以省略小括号\ntest{msg -\u0026gt; println(msg)} 这种语法也被称为尾随lambda表达式，能省的东西都省了，不过只有在最后一个参数是函数类型的情况下才可以，如果不是最后一位，就没办法做到尾随了。\n参数 # 单个参数\nvar myFunc: (String) -\u0026gt; String = { println(\u0026#34;Hello, $it\u0026#34;) // 如果只有一个参数，则使用it来获取 \u0026#34;Hello, $it\u0026#34; // 最后一行作为返回值 } 多个参数\nvar add :(Int,Int) -\u0026gt; Int = { x,y -\u0026gt; // 我们需要手动添加两个参数这里的形参名称，不然没法用他两 x + y // 最后一行作为返回值 } 返回值 # Lambda中没有办法直接使用return语句返回结果，而是默认使用最后一行的值作为返回值。\n但是有的时候，我们需要在函数中提前返回，这就需要用到之前我们学习流程控制时用到的标签。\nvar myFunc: (Boolean) -\u0026gt; String = test@{ b -\u0026gt; if (b){ return@test \u0026#34;True\u0026#34; // 使用标签进行返回 } \u0026#34;False\u0026#34; // 最后一行作为返回值 } 如果是函数调用的尾随lambda表达式，默认的标签名字就是函数的名字\n内联函数 # 使用高阶函数会可能会影响运行时的性能：每个函数都是一个对象，而且函数内可以访问一些局部变量，但是这可能会在内存分配（用于函数对象和类）和虚拟调用时造成额外开销。\n为了优化性能，开销可以通过内联Lambda表达式来消除。使用inline关键字会影响函数本身和传递给它的lambdas，它能够让方法的调用在编译时，直接替换为方法的执行代码。\nfun main() { test() } inline fun test(){ println(\u0026#34;Hello\u0026#34;) println(\u0026#34;Hello\u0026#34;) println(\u0026#34;Hello\u0026#34;) } 由于test函数是内联函数，在编译之后，会原封不动地把代码搬过去\nfun main() { println(\u0026#34;Hello\u0026#34;) println(\u0026#34;Hello\u0026#34;) println(\u0026#34;Hello\u0026#34;) } 同样的，如果是一个高阶函数，效果那就更好了，例如如下函数\nfun main() { test { println(it) } } inline fun test(handle: (String) -\u0026gt; Unit){ println(\u0026#34;test\u0026#34;) handle(\u0026#34;Hello\u0026#34;) } 由于test函数是内联的高阶函数，在编译之后，不仅会原封不动地把代码搬过去，还会自动将传入的函数参数贴到调用的位置\nfun main(){ println(\u0026#34;test\u0026#34;) var it = \u0026#34;Hello\u0026#34; println(it) } ","date":"2025-03-24","externalUrl":null,"permalink":"/posts/b3f5e6ce/129ca5af/8db2e4a0/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e函数 \n    \u003cdiv id=\"函数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%87%bd%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-kotlin\" data-lang=\"kotlin\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efun\u003c/span\u003e \u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\u003cspan class=\"n\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Hello World\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e我们程序的入口点就是\u003ccode\u003emain\u003c/code\u003e函数，我们只需要将我们的程序代码编写到主函数中就可以运行了，不过这个函数只是由我们来定义，而不是我们自己来调用。当然，除了主函数之外，我们一直在使用的\u003ccode\u003eprintln\u003c/code\u003e也是一个函数，不过这个函数是标准库中已经实现好了的\u003c/p\u003e","title":"4、函数","type":"posts"},{"content":" 异常类 # 我们的每一个异常也是一个类，他们都继承自Throwable类\n手动抛出异常 # fun main() { //Exception继承自Throwable类，作为普通的异常类型 throw Exception(\u0026#34;异常啦！\u0026#34;) } 自定义异常类 # class TestException(message: String) : Exception(message) fun main() { throw TestException(\u0026#34;自定义异常\u0026#34;) } 异常处理 # fun main() { println(test(1,0)) } fun test(num1: Int,num2: Int): Int{ try { return num1 / num2 }catch (e: Exception){ e.printStackTrace() return 0 } finally { println(\u0026#34;finally\u0026#34;) } } try也可以当做一个表达式使用，这意味着它可以有一个返回值，也就是 catch 的最后一行，作为返回值。\n所以对于上面的代码，我们可以改造一下\nfun main() { println(test(1,0)) } fun test(num1: Int,num2: Int): Int{ return try { num1 / num2 }catch (e: Exception){ e.printStackTrace() 0 } finally { println(\u0026#34;finally\u0026#34;) } } ","date":"2025-03-24","externalUrl":null,"permalink":"/posts/b3f5e6ce/1e30d624/204b9115/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e异常类 \n    \u003cdiv id=\"异常类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%bc%82%e5%b8%b8%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e我们的每一个异常也是一个类，他们都继承自\u003ccode\u003eThrowable\u003c/code\u003e类\u003c/p\u003e","title":"4、异常处理","type":"posts"},{"content":" SpringSession # SpringSession可以解决在微服务高可用或多个微服务中，JSESSIONID不同的问题\n使用 # 1、启动redis # 2、在需要共享JSESSIONID的微服务中，添加依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.session\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-session-data-redis\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-data-redis\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 3、修改配置文件 # spring: application: name: student-system session: store-type: redis 4、访问微服务，发现JSESSIONID已经被存入redis # JWT令牌认证技术 # 全称：JSON Web Token 官网：jwt.io 专门用来替换：Cookie + Session的应用状态管理方式，通常适合前后端分离的项目 应用程序的状态：后端服务器和浏览器相互识别的内容 传统应用状态管理的缺陷 # 缺陷 前端只能是浏览器或者有Cookie的前端技术 应用程序的服务器在横向扩展时，需要进行Session同步 不适合做前后端分离的项目 具体原因 目前的前端（大前端）包含：各种浏览器，各种手机APP，各种应用小程序，还有其他的设备，但上述的这些前端技术中，Cookie是浏览器所特有的 单点登录SSO # cookie和域\n微服务认证的问题：每个微服务都需要做单独的认证，对于用户而言，太麻烦了。\n单点登录可以解决微服务的鉴权问题\nSSO（Single Sign On） # 所谓单点登录，就是用户在多系统环境下，在一个单一的服务中登录，进而实现在多个系统同时登录的一种技术。\nJWT解决方案 # JWT的流程 # 后端通过JWT的技术，可以生成一个Token令牌的东西，生成出来之后，交给前端，要求前端进行存储，前端的每次请求需要携带该Token令牌到后端来识别\nJWT的组成 # JWT由3段信息构成：头（header）、载荷（payload）、签证（signature）\n头（header）、载荷（payload）采用Base64位加密方法，签证（signature）使用HMACSHA256进行加密运算\n头（Header）\n主要承载2部分内容：声明Token使用JWT技术产生，声明加密的算法通常直接使用HMACSHA256\n完整头的JSON格式：\n{ \u0026#39;typ\u0026#39;: \u0026#39;JWT\u0026#39;, \u0026#39;alg\u0026#39;: \u0026#39;HS256\u0026#39; } 头的内容是固定的，不会发生变化\n对应的字符串是：eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.\n载荷（payload）\n后端通过JWT承载，传递给前端有用的相关数据 组成载荷的2个部分：标准中注册的声明，其它的声明 标准中注册的声明（建议但不强制）： iss：jwt签发者 sub：jwt内容主体 aud：接收jwt的一方 exp：jwt的过期时间，这个过期时间必须要大于签发时间 nbf：定义在什么时间之前，该jwt都是不可用的 iat：jwt的签发时间 jti：jwt的唯一身份标识，主要用来作为一次性token，从而回避重放攻击。 其它的声明 载荷中，可以申明上述的标准信息，也可以附加一些个人信息（个人信息有的是公共的，有的是私有的） 备注：不管是公共的，还是私有的，由于载荷使用Base64位算法，都是可以破解的 其它的声明，不要写一些非常敏感的信息，例如：银行卡卡号，银行卡密码，登录名，登录密码…… 签证(signature)\n签证由3部分构成 header：base64加密后的内容 payload：base64加密后的内容 secret：盐值 将以上3部分内容使用：HMACSHA256算法进行加密 保证令牌颁发的权威性 可能抛出的异常 # ClaimJwtException 获取Claim异常 ExpiredJwtException token过期异常 IncorrectClaimException token无效 MalformedJwtException 密钥验证不一致 MissingClaimException JWT无效 RequiredTypeException 必要类型异常 SignatureException 签名异常 UnsupportedJwtException 不支持JWT异常 JWT完成SSO的基本使用 # JWT令牌的生成 # 生成的时间点：登录成功的时候 一、登录认证微服务 # 1、公共模块，导入JWT依赖，创建工具类 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;io.jsonwebtoken\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jjwt\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;0.9.1\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; JwtPayload\npackage com.hui17.hac.common.util.jwt; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * @Description: Jwt Payload * @Author: yanggang * @Date: 2025/2/21 18:00 */ @Data @AllArgsConstructor @NoArgsConstructor public class JwtPayload { /** * token id */ private String id; /** * subject */ private String subject; /** * 过期时间 */ private Date expiration; /** * 签发时间 */ private Date issuedAt; /** * 签发人 */ private String issuer; } JwtUtil\npackage com.hui17.hac.common.util.jwt; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import java.security.Key; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * @Description: JWT 工具类 * @Author: yanggang * @Date: 2025/2/21 17:55 */ public class JwtUtil { /** * 加密方式 */ private static final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; /** * 对密钥进行加密 * @return */ private static Key getkey(String secret) { byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(secret); return new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); } /** * 生成JWT * @param jwtPayload * @param secret * @return */ public static String createToken(JwtPayload jwtPayload, String secret) { JwtBuilder builder = Jwts.builder(); // 设置头部 Map\u0026lt;String, Object\u0026gt; map = new HashMap\u0026lt;String, Object\u0026gt;(); map.put(\u0026#34;alg\u0026#34;, \u0026#34;HS256\u0026#34;); map.put(\u0026#34;typ\u0026#34;, \u0026#34;JWT\u0026#34;); builder.setHeaderParams(map); // 设置载荷 builder.setSubject(jwtPayload.getSubject()); builder.setId(jwtPayload.getId()); builder.setIssuer(jwtPayload.getIssuer()); // 设置密钥 builder.signWith(signatureAlgorithm, getkey(secret)); // 设置签发时间 Date now = new Date(); builder.setIssuedAt(now); // 设置过期时间 if (jwtPayload.getExpiration() != null) { builder.setExpiration(jwtPayload.getExpiration()); } String token = builder.compact(); return token; } /** * 解密Token查看其是否合法 */ public static boolean verifyToken(String token,String secret) { Claims body = null; try { body = Jwts.parser().setSigningKey(getkey(secret)).parseClaimsJws(token).getBody(); }catch (Exception e){ return false; } return body != null; } /** * 解析载荷中主体和jwt-id * @param token * @return sub+id */ public static JwtPayload getPayload(String token,String secret) { boolean b = verifyToken(token,secret); if (!b){ return null; } Claims body = Jwts.parser().setSigningKey(getkey(secret)).parseClaimsJws(token).getBody(); String id = body.getId(); Date expiration = body.getExpiration(); String subject = body.getSubject(); Date issuedAt = body.getIssuedAt(); String issuer = body.getIssuer(); JwtPayload jwtPayload = new JwtPayload(id, subject, expiration, issuedAt, issuer); return jwtPayload; } } 2、创建认证微服务，用于登录，认证 # 2.1、引入依赖、编写配置文件 # \u0026lt;dependencies\u0026gt; \u0026lt;!-- 公共模块，包含mysql依赖 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;common\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.mybatis.spring.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mybatis-spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-alibaba-nacos-discovery\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-thymeleaf\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; server: port: 8888 spring: datasource: url: jdbc:mysql://localhost:3306/demo?serverTimezone=UTC\u0026amp;characterEncoding=utf8 driver-class-name: com.mysql.jdbc.Driver username: root password: 123456 thymeleaf: prefix: classpath:/templates/ suffix: .html mode: HTML5 cache: false encoding: UTF-8 2.2、编写登录页面 # \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34; xmlns:th=\u0026#34;http://www.w3.org/1999/xhtml\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;form action=\u0026#34;/login\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;hidden\u0026#34; name=\u0026#34;backUrl\u0026#34; th:value=\u0026#34;${backUrl}\u0026#34;\u0026gt; account\u0026lt;input name=\u0026#34;account\u0026#34;\u0026gt;\u0026lt;br\u0026gt; psw\u0026lt;input name=\u0026#34;psw\u0026#34;\u0026gt;\u0026lt;br\u0026gt; \u0026lt;button\u0026gt;sub\u0026lt;/button\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 2.3、编写service对账号密码进行验证 # @Service public class UserInfoServiceImpl implements UserInfoService { @Resource private UserInfoDao userInfoDao; @Override public UserInfo login(String account, String psw) { UserInfoExample userInfoExample = new UserInfoExample(); UserInfoExample.Criteria criteria = userInfoExample.createCriteria(); criteria.andAccountEqualTo(account); criteria.andPswEqualTo(psw); List\u0026lt;UserInfo\u0026gt; userInfos = userInfoDao.selectByExample(userInfoExample); return userInfos.size() \u0026gt; 0 ? userInfos.get(0) : null; } } 2.4、编写controller对令牌进行跳转登录页面和验证登录信息 # @Controller public class LoginController { @Resource private UserInfoService userInfoService; /** * 跳转登录页面方法，同时验证token，验证成功，则跳转回原来的地址 * backUrl，为测试用的原来的地址 */ @RequestMapping(\u0026#34;/toLogin\u0026#34;) public String toLogin(String backUrl, @CookieValue(value = \u0026#34;token\u0026#34;,required = false)String token, ModelMap map){ //判断令牌是否有效 if (!StringUtils.isEmpty(token)){ boolean b = JwtUtil.verifyToken(token); if (b){ return \u0026#34;redirect:\u0026#34; + backUrl + \u0026#34;?token=\u0026#34; + token; } } //令牌失效或不存在 map.put(\u0026#34;backUrl\u0026#34;,backUrl); //跳转登录页面 return \u0026#34;login\u0026#34;; } /** * 登录验证方法 */ @RequestMapping(\u0026#34;/login\u0026#34;) public String login(UserInfo userInfo, String backUrl, HttpServletResponse response){ //验证用户的登录 UserInfo loginUser = userInfoService.login(userInfo.getAccount(),userInfo.getPsw()); //如果账号密码正确，则创建令牌 if (loginUser != null){ String token = JwtUtil.createToken(loginUser.getAccount(), 3000,nul); //将令牌存入cookie Cookie cookie = new Cookie(\u0026#34;token\u0026#34;,token); response.addCookie(cookie); return \u0026#34;redirect:\u0026#34;+backUrl+\u0026#34;?token=\u0026#34; + token; } return \u0026#34;redirect:http://localhost:8888/toLogin?backUrl=\u0026#34; + backUrl; } } 二、其他微服务添加拦截器 # 1、编写拦截器 # @Component public class LoginInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //获取用户实际请求的地址，用于令牌验证通过继续访问 String backUrl = request.getRequestURL().toString(); //从参数中获取token String token_r = request.getParameter(\u0026#34;token\u0026#34;); //从cookie中获取token Cookie cookie = WebUtils.getCookie(request, \u0026#34;token\u0026#34;); String token_c = cookie == null ? null : cookie.getValue(); //从参数或cookie的token中选择有效的token String token = StringUtils.isEmpty(token_r)?token_c : token_r; //验证token if (!StringUtils.isEmpty(token)){ if (JwtUtil.verifyToken(token)){ Cookie cookie1 = new Cookie(\u0026#34;token\u0026#34;,token); response.addCookie(cookie); return true; } } //验证不通过，跳转登录页面 response.sendRedirect(\u0026#34;http://localhost:8888/toLogin?backUrl=\u0026#34; + backUrl); return false; } } 2、配置拦截器到SpringBoot # @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; public void addInterceptors(InterceptorRegistry registry) { InterceptorRegistration registration = registry.addInterceptor(loginInterceptor); registration.addPathPatterns(\u0026#34;/**\u0026#34;); //所有路径都被拦截 registration.excludePathPatterns( //添加不拦截路径 \u0026#34;/**/*.html\u0026#34;, //html静态资源 \u0026#34;/**/*.js\u0026#34;, //js静态资源 \u0026#34;/**/*.css\u0026#34;, //css静态资源 \u0026#34;/**/*.woff\u0026#34;, \u0026#34;/**/*.ttf\u0026#34; ); } } Gateway鉴权+JWT完成SSO # 由于简单使用中，在每个微服务都添加一个拦截器并进行配置，过于繁琐，所以，我们在Gateway上直接添加全局的过滤器用于SSO，所有的微服务都由Gateway进行统一的管理转发\nJWT令牌的认证与置换 # 令牌的产生是在具有登录服务的微服务上，而令牌的校验是在网关服务上对其进行校验\n1、JWT的基本搭建（同上） # 公共类common中添加JWT依赖，编写JwtUtils 创建登录微服务（账号密码认证、跳转登录页面） 编写登录页面 2、创建Gateway微服务，对所有微服务路径进行管理 # server: port: 80 spring: application: name: gateway cloud: nacos: discovery: server-addr: http://localhost:8848 gateway: discovery: locator: # 让gateway从nacos中获取服务信息 enabled: false routes: # 路由标识，必须唯一，默认是UUID - id: student-system # 路由的目标地址，lb(loadblance)表示使用负载均衡，其后是微服务的标识 uri: lb://student-system # 路由的优先级，数字越小代表路由的优先级越高 order: 1 # 当请求路径匹配Path时，就会路由到uri predicates: - Path=/student-system/** # 转发之前，去掉1层路径 filters: - StripPrefix=1 - id: clazz-system uri: lb://clazz-system order: 1 predicates: - Path=/clazz-system/** filters: - StripPrefix=1 3、在Gateway微服务中，添加全局过滤器 # @Component public class LoginGlobalFilter implements GlobalFilter, Ordered { //认证中心的跳转登录页面Url private final String loginServerUrl = \u0026#34;http://localhost:8888/toLogin\u0026#34;; //当前Gateway的Url private final String gatewayUrl = \u0026#34;http://localhost\u0026#34;; @Override public Mono\u0026lt;Void\u0026gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); //获取本来访问的路径 String backUrl = gatewayUrl + request.getPath().toString(); //获取参数中的token String token_r = request.getQueryParams().getFirst(\u0026#34;token\u0026#34;); //获取cookie中的token String token_c = getCookieByName(request, \u0026#34;token\u0026#34;); //在参数或cookie中的token选择有效的token String token = StringUtils.isEmpty(token_r) ? token_c : token_r; //如果token为空 if (StringUtils.isEmpty(token)){ return gotoLogin(response,backUrl); } //如果token无效 if (!JwtUtil.verifyToken(token)){ return gotoLogin(response,backUrl); } //其他情况放行 return chain.filter(exchange); } //前往认证微服务进行认证 private Mono\u0026lt;Void\u0026gt; gotoLogin(ServerHttpResponse response, String backUrl) { //设置状态码 response.setStatusCode(HttpStatus.SEE_OTHER); //设置响应头,重定向到认证中心 response.getHeaders().add(HttpHeaders.LOCATION,loginServerUrl + \u0026#34;?backUrl=\u0026#34; + backUrl); return response.setComplete(); } public String getCookieByName(ServerHttpRequest request,String cookieName){ MultiValueMap\u0026lt;String, HttpCookie\u0026gt; cookies = request.getCookies(); Set\u0026lt;Map.Entry\u0026lt;String, List\u0026lt;HttpCookie\u0026gt;\u0026gt;\u0026gt; entries = cookies.entrySet(); for (Map.Entry\u0026lt;String, List\u0026lt;HttpCookie\u0026gt;\u0026gt; entry : entries) { if (entry.getKey().equals(cookieName)) { List\u0026lt;HttpCookie\u0026gt; value = entry.getValue(); if (value.size() \u0026gt; 0) { return value.get(0).getValue(); } } } return null; } @Override public int getOrder() { return 0; } } 4、测试效果 # 4.1、通过网关访问微服务（未认证） # 4.2、未认证，会被跳转到登录页面，并记录实际访问的地址 # 4.3、输入账号密码认证后，会继续访问刚才的地址 # CSRF # CSRF是什么？ # CSRF（Cross-site request forgery），中文名称：跨站请求伪造，也被称为：one click attack/session riding，缩写为：CSRF/XSRF。\nCSRF可以做什么？ # 你这可以这么理解CSRF攻击：攻击者盗用了你的身份，以你的名义发送恶意请求。CSRF能够做的事情包括：以你名义发送邮件，发消息，盗取你的账号，甚至于购买商品，虚拟货币转账\u0026hellip;\u0026hellip;造成的问题包括：个人隐私泄露以及财产安全。\nCSRF漏洞现状 # CSRF这种攻击方式在2000年已经被国外的安全人员提出，但在国内，直到06年才开始被关注，08年，国内外的多个大型社区和交互网站分别爆出CSRF漏洞，如：NYTimes.com、Metafilter（一个大型的BLOG网站），YouTube和百度HI\u0026hellip;\u0026hellip;而现在，互联网上的许多站点仍对此毫无防备，以至于安全业界称CSRF为“沉睡的巨人”。\nCSRF的原理 # 登录受信任网站A，并在本地生成Cookie。\n在不登出A的情况下，访问危险网站B。\n看到这里，你也许会说：“如果我不满足以上两个条件中的一个，我就不会受到CSRF的攻击”。是的，确实如此，但你不能保证以下情况不会发生：\n你不能保证你登录了一个网站后，不再打开一个tab页面并访问另外的网站。\n你不能保证你关闭浏览器了后，你本地的Cookie立刻过期，你上次的会话已经结束。（事实上，关闭浏览器不能结束一个会话，但大多数人都会错误的认为关闭浏览器就等于退出登录/结束会话了\u0026hellip;\u0026hellip;）\n上图中所谓的攻击网站，可能是一个存在其他漏洞的可信任的经常被人访问的网站。\n","date":"2025-02-24","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/f61ae4d0/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eSpringSession \n    \u003cdiv id=\"springsession\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#springsession\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eSpringSession可以解决在微服务高可用或多个微服务中，JSESSIONID不同的问题\u003c/p\u003e","title":"4、JWT与SpringSession","type":"posts"},{"content":" 父子组件 # 子组件访问父组件属性和方法 # 方式一：props（父组件传参给子组件） # 在子组件中声明props，属性可以是数组（元素必须是字符串）、也可以是对象，声明的每一个属性值都可以作为子组件的属性使用\nprops也是双向绑定的\n注意：不建议子组件直接修改props里面的值，由父组件传值，子组件只使用，如果需要修改，在data中重新声明一个变量来接收prop\n父组件Father.vue\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!-- 父组件向子组件传参 --\u0026gt; \u0026lt;Child :msg=\u0026#34;hello\u0026#34;/\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import Child from \u0026#39;./Child.vue\u0026#39;; export default { name: \u0026#34;Father\u0026#34;, components: { Child }, data() { return { hello: \u0026#39;Hello World\u0026#39;, }; }, } \u0026lt;/script\u0026gt; 子组件Child.vue\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; {{msg}} \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;Child\u0026#34;, // 子组件进行接收 props: [\u0026#39;msg\u0026#39;] /** props还可以是一个对象，可以对传入子组件的数据进行验证,例如字符串和数组 props:{ 类型验证 parentcolor:String, msg:Array, 类型、默认值、必填项 book:{ type:Array, default:[\u0026#34;200\u0026#34;,\u0026#34;201\u0026#34;], required:true } } */ } \u0026lt;/script\u0026gt; 方式二：this.$parent.属性名\\方法名 # \u0026lt;template id=\u0026#34;temp1\u0026#34;\u0026gt; \u0026lt;h1\u0026gt; \u0026lt;!-- 通过this.$parent.属性名访问父组件的属性值、方法 --\u0026gt; \u0026lt;font :color=\u0026#34;this.$parent.parentColor\u0026#34;\u0026gt;test\u0026lt;/font\u0026gt; \u0026lt;/h1\u0026gt; \u0026lt;/template\u0026gt; 父组件访问子组件的属性、方法 # 注意：在vue中的ref属性，相当于原生html的id，给子组件设置一个id，然后this.$refs.id可以进行调用，返回该组件的实例对象；也可以用在原生html标签上，返回该标签的Dom对象\n子组件Child.vue\n\u0026lt;template\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;Child\u0026#34;, data() { return { msg: \u0026#39;Hello World\u0026#39;, }; }, methods: { say(){ alert(this.msg); } } } \u0026lt;/script\u0026gt; 父组件Father.vue\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!-- 在子组件上绑定ref唯一标识 --\u0026gt; \u0026lt;Child ref=\u0026#34;ch1\u0026#34;/\u0026gt; \u0026lt;button @click=\u0026#34;runSay\u0026#34;\u0026gt;click\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import Child from \u0026#39;./Child.vue\u0026#39;; export default { name: \u0026#34;Father\u0026#34;, components: { Child }, methods: { runSay(){ // 利用 this.$ref 调用子组件方法 this.$refs.ch1.say(); // 利用 this.$ref 访问子组件data console.log(this.$refs.ch1.msg); } } } \u0026lt;/script\u0026gt; 自定义事件 # 父组件给子组件绑定一个事件，子组件可以通过触发这个事件来调用父组件的回调，达到通讯的作用\nthis.$emit(\u0026quot;自身上的事件\u0026quot;,方法形参)\n父组件Father.vue\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!-- 在子组件上绑定自定义事件 --\u0026gt; \u0026lt;Child ref=\u0026#34;ch\u0026#34; @runShowMsg=\u0026#34;showMsg\u0026#34;/\u0026gt; \u0026lt;!-- ！！！如果需要在子组件上绑定原生dom事件，需要使用native --\u0026gt; \u0026lt;Child ref=\u0026#34;ch\u0026#34; @click.native=\u0026#34;showMsg\u0026#34;/\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import Child from \u0026#39;./Child.vue\u0026#39;; export default { name: \u0026#34;Father\u0026#34;, components: { Child }, methods: { showMsg(msg){ alert(msg); } }, mounted(){ //！！！也可以通过获取子组件的组件实例对象，直接在实例对象上绑定事件 //this.$refs.ch.$on(\u0026#34;runShowMsg\u0026#34;,this.showMsg); //绑定自定义事件 //this.$refs.ch.$once(); //绑定只可触发一次的自定义事件 } } \u0026lt;/script\u0026gt; 子组件Child.vue\n\u0026lt;template\u0026gt; \u0026lt;button @click=\u0026#34;cl\u0026#34;\u0026gt;click\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;Child\u0026#34;, methods: { cl(){ // 触发自身的事件 this.$emit(\u0026#39;runShowMsg\u0026#39;, \u0026#34;Hello World\u0026#34;); //也可以使用组件实例对象的$off函数解绑事件 //this.$off(\u0026#39;runShowMsg\u0026#39;) //解绑一个事件 //this.$off([\u0026#39;event1\u0026#39;,\u0026#39;event2\u0026#39;]) //解绑多个事件 //this.$off() //解绑s } } } \u0026lt;/script\u0026gt; 全局事件总线 # 可以实现任意组件间进行通讯\nmain.js中安装全局事件总线\nimport Vue from \u0026#39;vue\u0026#39; import App from \u0026#39;./App.vue\u0026#39; Vue.config.productionTip = false // 原理：在Vue原型对象上绑定一个空的组件，组件实例对象都支持$on、$once、$off等方法操作自定义事件 // const Bus = Vue.extend({}) // Vue.prototype.$bus = new Bus() new Vue({ render: h =\u0026gt; h(App), beforeCreate(){ //进阶写法：在组件还未创建前，将当前Vue实例对象，作为全局事件总线使用 Vue.prototype.$bus = this; } }).$mount(\u0026#34;#app\u0026#34;) 接收方组件\n注意：在组件接收方组件销毁之前，最好使用钩子beforeDestroy()删除声明的自定义事件，以免事件重复命名\n\u0026lt;template\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;Comp2\u0026#34;, methods: { getMsg(msg){ alert(msg) } }, mounted () { //接收方，在全局事件总线上绑定一个事件和回调 this.$bus.$on(\u0026#34;msgToComp2\u0026#34;,this.getMsg) }, beforeDestroy () { //组件销毁前，删除绑定在全局事件总线上的事件 this.$bus.$off(\u0026#34;msgToComp2\u0026#34;); } } \u0026lt;/script\u0026gt; 发送方组件\n\u0026lt;template\u0026gt; \u0026lt;button @click=\u0026#34;sendMsg\u0026#34;\u0026gt;click\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;Comp1\u0026#34;, methods: { sendMsg(){ // 发送方，触发全局事件总线的事件，并传参 this.$bus.$emit(\u0026#34;msgToComp2\u0026#34;,\u0026#34;Hello World\u0026#34;); } } } \u0026lt;/script\u0026gt; 消息订阅与发布 # 使用这个功能，需要引入第三方库，例如pubsub-js，该库不依赖与vue，可以在其他js中使用\ncnpm i pubsub-js 订阅方\n\u0026lt;template\u0026gt; \u0026lt;button @click=\u0026#34;sendMsg\u0026#34;\u0026gt;click\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import pubsub from \u0026#39;pubsub-js\u0026#39;; export default { name: \u0026#34;Comp1\u0026#34;, methods: { sendMsg(){ // 发送方，调用pubsub.publish(消息名,接收回调函数的形参)发送消息 pubsub.publish(\u0026#34;msg1\u0026#34;,\u0026#34;Hello World\u0026#34;) } } } \u0026lt;/script\u0026gt; 发布方\n\u0026lt;template\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import pubsub from \u0026#39;pubsub-js\u0026#39;; export default { name: \u0026#34;Comp2\u0026#34;, methods: { //注意：回调函数有两个参数，第一个是消息名，第二个才是消息 getMsg(msgName,msg){ alert(msg) } }, mounted () { //接收方，使用pubsub.subscribe(消息名,回调函数)订阅，并且返回订阅id this.pubId = pubsub.subscribe(\u0026#34;msg1\u0026#34;,this.getMsg); }, beforeDestroy () { //组件销毁前，使用订阅id取消订阅 pubsub.unsubscribe(this.pubId); } } \u0026lt;/script\u0026gt; ","date":"2024-12-10","externalUrl":null,"permalink":"/posts/bafd68f1/b2321cb9/8d323406/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e父子组件 \n    \u003cdiv id=\"父子组件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%88%b6%e5%ad%90%e7%bb%84%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20210803143831123\"\n    data-zoom-src=\"/posts/bafd68f1/b2321cb9/8d323406/image/202109181415109.png\"\n    src=\"/posts/bafd68f1/b2321cb9/8d323406/image/202109181415109.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"4、组件间通讯","type":"posts"},{"content":" 数据库连接池 # 在原生的JDBC中，我们通常是使用对应的数据库驱动例如com.mysql.jdbc.Driver，获取一个java.sql.Connection对象进行操作（创建连接、执行SQL、关闭连接），所有的压力都在这一个Connection上。\n数据库连接池是java.sql.Connection对象的缓冲区，它里面会存放一些Connection，当我们Java程序需要使用Connection的时候，如果连接池中有则直接从连接池获取，不需要去新创建Connection了。连接池让Java程序能够复用连接、管理连接。\n为什么使用连接池 # 避免每次创建和销毁连接带来的系统开销：每次创建和销毁连接都要消耗大概0.05~1s的时间。 提高系统并发能力：可以防止大量用户并发访问数据库服务器。 提高业务响应速度：因为连接在初始化过程中，往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言，直接利用现有可用连接，避免了数据库连接初始化和释放过程的时间开销，从而缩减了系统整体响应时间。 避免数据库连接泄漏：在较为完备的数据库连接池实现中，可根据预先的连接占用超时设定，强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。 连接池的基本原理 # 连接池不是线程池，只不过原理类似\n连接池维护着两个容器空闲池和活动池 空闲池用于存放未使用的连接，活动池用于存放正在使用的连接，活动池中的连接使用完之后要归还回空闲池 当Java程序需要连接时，先判断空闲池中是否有连接，如果空闲池中有连接则取出一个连接放置到活动池供Java程序使用 Java程序需要连接时，如果空闲池中没有连接了，则先判断活动池的连接数是否已经达到了最大连接数，如果未达到最大连接数，则会新创建一个连接放置到活动池，供Java程序使用 如果空闲池中没有连接了，活动池中的连接也已经达到了最大连接数，则不能新创建连接了，那么此时会判断是否等待超时，如果没有等待超时则需要等待活动池中的连接归还回空闲池 如果等待超时了，则可以采取多种处理方式，例如直接抛出超时异常，或者将活动池中使用最久的连接移除掉归还回空闲池以供Java程序使用 DataSource # 在原生JDBC中，获取java.sql.Connection对象的方式如下\nString url = \u0026#34;jdbc:mysql://ip:port/databasename?serverTimezone=Asia/Shanghai\u0026amp;characterEncoding=utf-8\u0026#34;; String user = \u0026#34;username\u0026#34;; String passWord = \u0026#34;password\u0026#34;; Connection conn = DriverManager.getConnection(url,user,passWord); 我们在执行业务逻辑的时候，就要先获取连接、使用、再销毁连接，单单获取连接这一项，DriverManager 形式创建数据库连接需要 300~500 ms 左右，而一般系统的性能要求，单次请求需要稳定在 200 ms。\n上面代码的核心就是获取Connection对象，池化思想，可以完美解决这个问题，类似于线程池，提前创建好连接放在池子里面，用完了也不销毁，可以重复利用。所以sun 公司在 jdbc 2.0 版本推出了java.sql.DataSource接口，用来进行规范约束，对于Connection的使用者，不用再关心Connetion是怎么创建的。接口的声明十分的简单，就是获取Connection的两个方法：\npublic interface DataSource extends CommonDataSource, Wrapper { Connection getConnection() throws SQLException; Connection getConnection(String username, String password) throws SQLException; } 常见的数据库连接池 # DBCP 是Apache提供的数据库连接池，速度相对c3p0较快，但因自身存在BUG，Hibernate3已不再提供支持 C3P0 是一个开源组织提供的一个数据库连接池，速度相对较慢，稳定性还可以 Proxool 是sourceforge下的一个开源项目数据库连接池，有监控连接池状态的功能，稳定性较c3p0差一点 HikariCP 俗称光连接池,是目前速度最快的连接池 Druid 是阿里提供的数据库连接池，据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池 Druid # https://github.com/alibaba/druid\nDruid是目前最好的数据库连接池，在功能、扩展性方面，都超过其他数据库连接池。Druid已经在阿里巴巴部署了超过600个应用，经过一年多生产环境大规模部署的严苛考验。Druid是阿里巴巴开发的号称为监控而生的数据库连接池！\nDruid提供了一个高效、功能强大、可扩展性好的数据库连接池。 可以监控数据库访问性能，Druid内置提供了一个功能强大的StatFilter插件，能够详细统计SQL的执行性能，这对于线上分析数据库访问性能有帮助。 数据库密码加密。直接把数据库密码写在配置文件中，这是不好的行为，容易导致安全问题。DruidDruiver和DruidDataSource都支持PasswordCallback。 SQL执行日志，Druid提供了不同的LogFilter，能够支持Common-Logging、Log4j和JdkLog，你可以按需要选择相应的LogFilter，监控你应用的数据库访问情况。 扩展JDBC，如果你要对JDBC层有编程的需求，可以通过Druid提供的Filter机制，很方便编写JDBC层的扩展插件。 配置 缺省值 说明 name 配置这个属性的意义在于，如果存在多个数据源，监控的时候可以通过名字来区分开来。如果没有配置，将会生成一个名字，格式是：\u0026quot;DataSource-\u0026quot; + System.identityHashCode(this). 另外配置此属性至少在1.0.5版本中是不起作用的，强行设置name会出错。 url 连接数据库的url，不同数据库不一样。例如： mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto username 连接数据库的用户名 password 连接数据库的密码。如果你不希望密码直接写在配置文件中，可以使用ConfigFilter。详细看这里 driverClassName 根据url自动识别 这一项可配可不配，如果不配置druid会根据url自动识别dbType，然后选择相应的driverClassName initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法，或者第一次getConnection时 maxActive 8 最大连接池数量 maxIdle 8 最大连接池数量，已经不再使用，配置了也没效果 minIdle 最小连接池数量 maxWait 获取连接时最大等待时间，单位毫秒。配置了maxWait之后，缺省启用公平锁，并发效率会有所下降，如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 poolPreparedStatements false 是否缓存preparedStatement，也就是PSCache。PSCache对支持游标的数据库性能提升巨大，比如说oracle。在mysql下建议关闭。 maxPoolPreparedStatementPerConnectionSize -1 要启用PSCache，必须配置大于0，当大于0时，poolPreparedStatements自动触发修改为true。在Druid中，不会存在Oracle下PSCache占用内存过多的问题，可以把这个数值配置大一些，比如说100 validationQuery 用来检测连接是否有效的sql，要求是一个查询语句，常用select 'x'。如果validationQuery为null，testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 validationQueryTimeout 单位：秒，检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法 testOnBorrow true 申请连接时执行validationQuery检测连接是否有效，做了这个配置会降低性能。 testOnReturn false 归还连接时执行validationQuery检测连接是否有效，做了这个配置会降低性能。 testWhileIdle false 建议配置为true，不影响性能，并且保证安全性。申请连接的时候检测，如果空闲时间大于timeBetweenEvictionRunsMillis，执行validationQuery检测连接是否有效。 keepAlive false （1.0.28） 连接池中的minIdle数量以内的连接，空闲时间超过minEvictableIdleTimeMillis，则会执行keepAlive操作。 timeBetweenEvictionRunsMillis 1分钟（1.0.14） 有两个含义： 1) Destroy线程会检测连接的间隔时间，如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。 2) testWhileIdle的判断依据，详细看testWhileIdle属性的说明 numTestsPerEvictionRun 30分钟（1.0.14） 不再使用，一个DruidDataSource只支持一个EvictionRun minEvictableIdleTimeMillis 连接保持空闲而不被驱逐的最小时间 connectionInitSqls 物理连接初始化的时候执行的sql exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时，抛弃连接 filters 属性类型是字符串，通过别名的方式配置扩展插件，常用的插件有： 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall proxyFilters 类型是List\u0026lt;com.alibaba.druid.filter.Filter\u0026gt;，如果同时配置了filters和proxyFilters，是组合关系，并非替换关系 替换原生JDBC # 项目导入druid.jar、mysql-connector.jar，或maven依赖\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;druid\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.1.23\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;mysql\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mysql-connector-java\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;8.0.31\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; classpath下创建druid.properties，按照配置列表按需配置\ndriverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/test username=root password=123456 initialSize=5 maxActive=10 maxWait=1000 public static void main(String[] args) throws Exception{ // 读取druid.properties Properties properties = new Properties(); InputStream resourceAsStream = Start.class.getClassLoader().getResourceAsStream(\u0026#34;druid.properties\u0026#34;); properties.load(resourceAsStream); resourceAsStream.close(); // 配置并初始化DruidDataSource DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); // 从DataSource中获取Connection Connection connection = dataSource.getConnection(); // 执行SQL PreparedStatement preparedStatement = connection.prepareStatement(\u0026#34;show tables; \u0026#34;); ResultSet resultSet = preparedStatement.executeQuery(); // 关闭连接，这里的close方法就是将该连接放到空闲连接池中（底层使用动态代理技术用反射实现） connection.close(); } 数据库密码加密 # 数据库密码直接写在配置中，对运维安全来说，是一个很大的挑战。Druid为此提供一种数据库密码加密的手段ConfigFilter。\n在使用的时候只需要找到项目使用的druid.jar，并执行如下命令\njava -cp druid-1.0.16.jar com.alibaba.druid.filter.config.ConfigTools you_password 即可生成密码对应的公钥、私钥、密码\nprivateKey:MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAoE/XdJcMCdI/WJWs2XyZxKjBz0uT+fVIJTBf7Y2jODzkvvKdFzAM0hs/xTaV4rOKzDBAW8pbdrEw3S4RqV/cyQIDAQABAkA//3/A4KgCp7d4MtB9RnvQgZpVmhNp/xydFBHHsPqO8UP6d9Q+wreNImhugGRzC+Q5XIFJoqm6e1M9fEc0d8hxAiEA8lSXhHnj+HdwIK/dgNOD0yEY8Hh26z/NoK7URXvgs9UCIQCpWtlLLTPocP9ufO6EMN9GHPWlXeGUJxD8rUi3YLDjJQIgIzOGjwflxcIT16u0UXJoQ9Ma/FODcsjh5eV/kFheW8ECIHUHerEgg4ZUz66iVpCy1Re2W2ny/jdK58o0VNOj1ZihAiAGFExeGXs76jltOo26RYhMJfxhn5SlcXN0i6Ggit3njA== publicKey:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKBP13SXDAnSP1iVrNl8mcSowc9Lk/n1SCUwX+2Nozg85L7ynRcwDNIbP8U2leKziswwQFvKW3axMN0uEalf3MkCAwEAAQ== password:m66ZbdyyBlOvcbJsvohajIqd1Q+C1J8n6MeZFUVs+xWSO6OJ5A+YftPj/JdxgnYMV7P41VBc3lXTPd/6hVMiDA== 在配置druid的时候需要额外添加修改三个配置，如下\nfilters = config connectionProperties = config.decrypt=true;config.decrypt.key=${publickey} password = ${password} mybatis的数据库连接池 # 在mybatis的配置文件中，声明过如下配置\n\u0026lt;dataSource type=\u0026#34;POOLED\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;driver\u0026#34; value=\u0026#34;com.mysql.jdbc.Driver\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;url\u0026#34; value=\u0026#34;jdbc:mysql://localhost:3306/test\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;username\u0026#34; value=\u0026#34;root\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;password\u0026#34; value=\u0026#34;123456\u0026#34;/\u0026gt; \u0026lt;/dataSource\u0026gt; type属性有三个值，分别为UNPOOLED，POOLED和JNDI。 UNPOOLED：每次请求时打开和关闭连接 POOLED：使用mybatis默认的数据库连接池 JNDI：使用容器的数据源，例如weblogic 其中POOLED，使用的就是mybatis默认的数据库连接池PooledDataSource，如下，是mybatis源码中设置PooledDataSource连接池的方法\npublic class PooledDataSourceFactory extends UnpooledDataSourceFactory { public PooledDataSourceFactory() { this.dataSource = new PooledDataSource(); } } 由此可得，如果需要配置第三方数据库连接池，只需要以下操作即可：\n声明一个自定义类继承UnpooledDataSourceFactory 第三方DataSource实例赋值给this.dataSource 修改mybatis配置文件中\u0026lt;dataSource type=\u0026quot;POOLED\u0026quot;\u0026gt;的POOLED为自定义类的全类名 按照自定义第三方DataSource的配置列表设置property即可 例如使用druid的DruidDataSource替代mybatis默认的PooledDataSource\n自定义类继承UnpooledDataSourceFactory\npublic class DruidDSFactory extends UnpooledDataSourceFactory { public DruidDSFactory(){ this.dataSource = new DruidDataSource(); } } 修改mybatis配置文件\n\u0026lt;dataSource type=\u0026#34;top.ygang.config.DruidDSFactory\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;driverClassName\u0026#34; value=\u0026#34;com.mysql.cj.jdbc.Driver\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;url\u0026#34; value=\u0026#34;jdbc:mysql://localhost:3306/test\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;username\u0026#34; value=\u0026#34;root\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;password\u0026#34; value=\u0026#34;123456\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;initialSize\u0026#34; value=\u0026#34;5\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;maxActive\u0026#34; value=\u0026#34;10\u0026#34;/\u0026gt; \u0026lt;property name=\u0026#34;maxWait\u0026#34; value=\u0026#34;1000\u0026#34;/\u0026gt; \u0026lt;/dataSource\u0026gt; ","date":"2024-12-09","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/79c2bc57/6bd7fc97/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e数据库连接池 \n    \u003cdiv id=\"数据库连接池\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%95%b0%e6%8d%ae%e5%ba%93%e8%bf%9e%e6%8e%a5%e6%b1%a0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在原生的JDBC中，我们通常是使用对应的数据库驱动例如\u003ccode\u003ecom.mysql.jdbc.Driver\u003c/code\u003e，获取一个\u003ccode\u003ejava.sql.Connection\u003c/code\u003e对象进行操作（创建连接、执行SQL、关闭连接），所有的压力都在这一个Connection上。\u003c/p\u003e","title":"4、数据库连接池","type":"posts"},{"content":" css概述 # 层叠样式表（cascading style sheet）\n层叠是指将多个样式施加在一个元素（标签）上\n作用：\n美化页面 将html代码与样式代码分离 好处：\n功能强大 分离代码，降低耦合性，提高重用性，提高维护性，提高开发效率 使用方法 # 1、在HTML标签的style属性书写，这个属性的值，是由一些小的键值对构成\n\u0026lt;div style=\u0026#34;color:red; font-size:100px;\u0026#34;\u0026gt;12345\u0026lt;/div\u0026gt; 缺点： 有可能出现代码冗余 标签属性多，耦合高，不便于维护 2、在页面head标签中添加style标签\n\u0026lt;style\u0026gt; div { color:yellow; font-size:500px; } \u0026lt;/style\u0026gt; 3、在head标签中添加link标签引入css文件\n\u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;css文件地址\u0026#34; /\u0026gt; 使用时机 # 方式一：由于样式设置是写在标签内的，它只能给这个一个标签施加样式，当我们只需要给一个标签施加样式的时候，就可以选择使用第一种方式 方式二：由于样式是写在style 标签，在style标签里面写的是选择器，所以它可以给多个标签施加相同的样式当我们需要给一个页面中的多个标签施加相同的样式的时候，我们就可以选择使用第二种 方式三：由于该方式，是引入了外来的css文件，说明引入css文件，就可以使用该样式了，当我们需要给多个页面中的多个标签施加相同的样式的时候，我们就可以选择使用第三种 推荐使用第二种和第三种 盒子模型 # 对页面进行布局（div + css）\npadding：设置内补丁 # padding：不同参数代表位置 一个参数：上下左右 两个参数：上下 左右 三个参数：上 左右 下 四个参数：上 右 下 左 默认情况，如果我们设置内补丁，会影响到整个盒子的大小，那我们需要设置一个属性box-sizing: border-box\nmargin：设置外补丁 # margin：不同参数代表位置 一个参数：上下左右 两个参数：上下 左右 三个参数：上 左右 下 四个参数：上 右 下 左 float浮动 # 会打破默认的流式布局，一般建议，如果一个元素进行了浮动，其余的和该元素同级别的也进行浮动。\nleft：从左浮动 right：从右浮动 position定位 # 静态定位（static）：默认值，没有定位，元素不会受到top，bottom，left，right影响 固定定位（fixed）：元素相对于浏览器窗口是固定位置 相对定位（relative）：相对定位元素的定位是相对其原来位置的偏移量 绝对定位（absolute）：绝对定位的元素的位置相对于最近的已定位父元素，如果元素没有已定位的父元素，那么它的相对于父元素也需要使用绝对定位 z-index：显示优先级，数值越大，越靠上 opacity：透明度，0完全透明，1完全不透明 属性 # 尺寸 # 支持百分比或者像素\nwidth：宽度 height：高度 display：可见性 文本（字体） # color：字体颜色 font-size：字体大小 font-family：字体样式 font-weight：字体粗细 font-style：倾斜（italic） text-align：水平对齐方式 line-height：行高，文字垂直位置的处理 text-shadow：x轴偏移量 y轴的偏移量 羽化效果 阴影颜色：文本阴影 text-decoration：文本下划线 背景 # background-color：背景颜色 background-image：url（背景图片） background-repeat：是否平铺图片（no-repeat、repeat-x、repeat-y） background-position：背景图片位置（水平偏移量 垂直偏移量） backgroud-image：linear-gradient（to 方向，开始颜色，结束颜色）：渐变背景色 box-shadow：x轴偏移量 y轴的偏移量 羽化效果 阴影颜色：盒子阴影 background-size：背景图片大小 边框 # border-left、right、top、bottom：宽度 颜色 样式 border-color：颜色 border-width：粗细 border-style：样式 border-radius：边框圆角 border-collapse：边框间距合并 border-spacing：属性指定相邻单元格边框之间的距离 outline：点击轮廓 box-sizing：border-box：设置相邻边框覆盖 选择器 # 可以帮助我们快速定位到某一个或者某几个标签的，就称为选择器\n格式：\n选择器 { 小键值对； ... ... } 元素选择器 # 标签名{ 样式属性 key:value; } ID选择器 # #ID{ 样式属性 key:value; } 类选择器 # .类名{ 样式属性 key:value; } 优先级：id选择器 \u0026gt; 类选择器 \u0026gt; 元素选择器\n组合选择器 # 选择器,选择器,选择器{ 样式属性 key:value; } 后代选择器 # 父标签 后代标签 { 样式属性 key:value; } 子元素选择器 # 父标签\u0026gt;子标签 { 样式属性 key:value; } 兄弟选择器 # /*同等级的，标签1后方的*/ 标签1~标签2{ 样式属性 key:value; } 相邻选择器 # /*同等级的，标签1后方的第一个标签*/ 标签1+标签2{ 样式属性 key:value; } 伪类选择器 # 标签:link{ 标签未被访问前的样式属性 key:value; } 标签:hover{ 鼠标悬停的标签样式属性 key:value; } 标签:visited{ 标签已被访问后的样式属性 key:value; } 标签:active{ 鼠标点击标签未释放的样式属性 key:value; } 标签:last-child{ 最后一个该标签的子标签样式属性 key:value; } 标签:first-child{ 第一个该标签的子标签样式属性 key:value; } 标签:nth-child(n){ 该标签的父标签的第n个该类子标签的样式属性 key:value; } 属性选择器 # 标签[key=\u0026#39;value\u0026#39;]{ 样式属性 key:value; } 标签[key*=\u0026#39;a\u0026#39;]{ 属性key的值包含a的标签的样式属性 key:value; } 标签[key^=\u0026#39;a\u0026#39;]{ 属性key的值以a开头的标签的样式属性 key:value; } 标签[key$=\u0026#39;a\u0026#39;]{ 属性key的值以a结尾的标签的样式属性 key:value; } 标签[key~=\u0026#39;hello\u0026#39;]{ 属性key的值包含单词hello的标签的样式属性 key:value; } 2D\\3D转换 # 2D # /*从其当前位置移动元素*/ transform: translate(横向, 纵向); /*旋转一定的角度*/ transform:rotate(角度deg); /*增大或减小元素大小*/ transform:scale(宽度增大倍数,高度增大倍数); 3D # /*元素绕其 X 轴旋转给定角度*/ transform: rotateX(角度deg); /*元素绕其 Y 轴旋转给定角度*/ transform: rotateY(角度deg); /*元素绕其 Z 轴旋转给定角度*/ transform: rotateZ(角度deg); 动画 # @keyframes 动画名 { from{开始样式} to{结束样式} } div{ animation:动画名 时间; } 动画属性 # animation-name：规定 @keyframes 动画的名称。 animation-play-state：规定动画是运行还是暂停。 animation-duration：定义需要多长时间才能完成动画 animation-delay：规定动画开始的延迟时间 animation-iteration-count：动画应运行的次数,infinite无限 animation-direction：属性指定是向前播放、向后播放还是交替播放动画 normal - 动画正常播放（向前）。默认值 reverse - 动画以反方向播放（向后） alternate - 动画先向前播放，然后向后 alternate-reverse - 动画先向后播放，然后向前 animation-timing-function：规定动画的速度曲线 Flex # Flex 布局，即 Flex Box（弹性布局），是一种较为灵活、强大的页面 CSS 布局方式。\nFlex 布局的优点：\n简便、完整、响应式 浏览器支持良好 传统盒状模型中，难以实现的垂直等布局等 ，Flex布局可以完美解决。 Flex相关术语 # flex 容器（flex container） flex 项目（flex item） 主轴（main axis) 交叉轴（cross axis） 占用主轴空间（main size） 占用交叉轴空间（cross size） 线轴起止点（main start、main end、cross start、cross end） 容器与项目 # 使用 Flex 布局的元素（display: flex），称之为 Flex 容器（Flex Container），简称 \u0026ldquo;容器\u0026rdquo;。Flex 的布局发生在父容器和子容器之间，因此，元素一旦被申明为 Flex 布局后，它的所有子元素自动成为容器成员。通常，我们将容器内的成员统称为 Flex 项目（Flex item），简称 \u0026ldquo;项目\u0026rdquo;。\n主轴、交叉轴 # 容器中默认存在两条线轴，即：主轴（main axis）、交叉轴（cross axis）。主轴与交叉轴是垂直关系，值得注意的是主轴不一定是水平方向，而是由 flex-direction 属性所决定的。\nmain axis：主轴。 cross axis：交叉轴。 轴线起始位置 # main start：主轴开始位置 main end：主轴结束位置 cross start：交叉轴开始位置 cross end：交叉轴结束位置 主轴、交叉轴空间 # 项目（flex item）默认是沿主轴方向排列的，单个项目占据主轴的空间称之为 main size，占据交叉轴的空间称之为 cross size\nmain size：占据主轴的空间 cross size：占据交叉轴的空间 容器flex container属性 # flex-direction # flex-direction 属性，定义了在哪个方向上排列项目。\n取值 说明 row 由左到右。（默认） row-reverse 从右到左。 column 从上到下。 column-reverse 从下到上。 flex-wrap # 定义了项目在轴线上的排列方式，在默认情况下，项目都排列在一条轴线上，不会换行，可以通过 flex-wrap 来定义换行的方式。\n取值 说明 nowrap 默认值，不换行 wrap 项目沿着轴线方向排列，一行排不下后，换行排列。 wrap-reverse 反向换行。例如：在主轴线上，第一行会在第二行的下放。 flex-flow # flex-flow 是 flex-direction 与 flex-wrap 的缩写形式。\n语法：flex-flow: \u0026lt;flex-direction\u0026gt; \u0026lt;flex-wrap\u0026gt;\n例子：\nflex-flow: row nowrap （默认值，项目沿着主轴排列，不换行。）\nflex-flow: column wrap-reverse（项目沿着交叉轴排列，反向换行）\njustify-content # justify-content 定义了项目在主轴上的对齐方式。\n取值 说明 flex-start 左对齐 flex-end 右对齐 center 居中 space-between 两端对齐，项目间隔相等 space-around 项目间隔相等，两端也有间隔。 align-items # align-items 属性，定义了项目在交叉轴上的对齐方式，有5个取值。\n取值 说明 flex-start 与交叉轴的起点对齐 flex-end 与交叉轴的终点对齐 center 在交叉轴上居中对齐 baseline 以项目中第一行文字基线对齐 stretch 在未设置高度情况下，将占满容器的高度 align-content # align-content 属性定义了多根主轴线在垂直方向上的对齐方式。\nalign-content 取值 说明 flex-start 与交叉轴起点对齐 flex-end 与交叉轴终点对齐 center 在交叉轴上居中对齐 space-between 与交叉轴两端对齐，轴线之间的间隔平均分布 space-around 轴线之间的间隔平均分布，包括两端 stretch 默认值，轴线占满交叉轴 项目flex item属性 # 项目属性 默认值 说明 order 0 定义了项目的排列顺序。 flex-grow 0 扩大因子，定义了项目的放大比例 flex-shrink 1 收缩因子，定义了项目的缩小比例 flex-basic auto flex-basis属性定义了在分配多余空间之前，项目占据的主轴空间（main size）。 flex 0 1 auto flex-grow、flex-shrink、flex-basis的缩写形式 align-self auto align-self 属性允许单个项目有与其他项目不一样的对齐方式，也就是说改属性可以覆盖父容器设置的 align-items 属性。 ","date":"2024-12-02","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/7beb2552/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003ecss概述 \n    \u003cdiv id=\"css概述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#css%e6%a6%82%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e层叠样式表（cascading style sheet）\u003c/p\u003e","title":"4、CSS","type":"posts"},{"content":" 鼠标 # using System.Collections; using System.Collections.Generic; using UnityEngine; public class KeyTest : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { if(Input.GetMouseButtonDown(0)){ Debug.Log(\u0026#34;左键按下。。。\u0026#34;); } if(Input.GetMouseButtonDown(2)){ Debug.Log(\u0026#34;中键按下。。。\u0026#34;); } if(Input.GetMouseButtonDown(1)){ Debug.Log(\u0026#34;右键按下。。。\u0026#34;); } } } 注意：监听需要写在Update中，每一帧都需要监听\n左键：0，中键：2，右键：1 Input.GetMouseButtonDown()：监听鼠标按键按下 Input.GetMouseButtonUp()：监听鼠标按键松开 Input.GetMouseButton()：监听鼠标按键持续按下 键盘 # using System.Collections; using System.Collections.Generic; using Unity.VisualScripting; using UnityEngine; public class KeyTest : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { if(Input.GetKeyDown(KeyCode.A)){ Debug.Log(\u0026#34;A键按下。。。\u0026#34;); } if(Input.GetKeyUp(KeyCode.A)){ Debug.Log(\u0026#34;A键抬起。。。\u0026#34;); } if(Input.GetKey(KeyCode.A)){ Debug.Log(\u0026#34;A键持续按下。。。\u0026#34;); } } } 注意：监听需要写在Update中，每一帧都需要监听\nInput.GetKeyDown()：监听键盘按键按下 Input.GetKeyUp()：监听键盘按键松开 Input.GetKey()：监听键盘按键持续按下 虚拟轴 # 虚拟轴到底是什么？\n简单来说，虚拟轴就是一个数值在-1~1内的数轴，这个数轴上重要的数值就是-1、0和1。当使用按键模拟一个完整的虚拟轴时需要用到两个按键，即将按键1设置为负轴按键，按键2设置为正轴按键。在没有按下任何按键的时候，虚拟轴的数值为0；在按下按键1的时候，虚拟轴的数值会从0~-1进行过渡；在按下按键2的时候，虚拟轴的数值会从0~1进行过渡。\n系统预设了很多的虚拟轴供我们使用，在（服务-常规设置-输入管理器-轴线）中\n以水平虚拟轴为例，可以看到系统默认配置了一个名为Horizontal的虚拟轴\n然后就可以在脚本中根据虚拟轴名称获取到这个虚拟轴的值，并控制物体的左右移动\nusing System.Collections; using System.Collections.Generic; using Unity.VisualScripting; using UnityEngine; public class KeyTest : MonoBehaviour { float moveSpeed = 5; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { float h = Input.GetAxis(\u0026#34;Horizontal\u0026#34;); Vector3 pos = transform.position; pos.x += Time.deltaTime * h * moveSpeed; transform.position = pos; } } ","date":"2024-09-19","externalUrl":null,"permalink":"/posts/69064821/af96a2fa/3fd32511/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e鼠标 \n    \u003cdiv id=\"鼠标\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%bc%a0%e6%a0%87\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-cs\" data-lang=\"cs\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem.Collections\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem.Collections.Generic\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eUnityEngine\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eKeyTest\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMonoBehaviour\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// Start is called before the first frame update\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003evoid\u003c/span\u003e \u003cspan class=\"n\"\u003eStart\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// Update is called once per frame\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003evoid\u003c/span\u003e \u003cspan class=\"n\"\u003eUpdate\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eInput\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eGetMouseButtonDown\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eDebug\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;左键按下。。。\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eInput\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eGetMouseButtonDown\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e)){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eDebug\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;中键按下。。。\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eInput\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eGetMouseButtonDown\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eDebug\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;右键按下。。。\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e注意\u003c/strong\u003e：监听需要写在Update中，每一帧都需要监听\u003c/p\u003e","title":"4、事件监听","type":"posts"},{"content":" 定义方法 # \u0026lt;Access Specifier\u0026gt; \u0026lt;Return Type\u0026gt; \u0026lt;Method Name\u0026gt;(Parameter List) { Method Body } Access Specifier：访问修饰符，这个决定了变量或方法对于另一个类的可见性。 Return type：返回类型，一个方法可以返回一个值。返回类型是方法返回的值的数据类型。如果方法不返回任何值，则返回类型为 void。 Method name：方法名称，是一个唯一的标识符，且是大小写敏感的。它不能与类中声明的其他标识符相同。 Parameter list：参数列表，使用圆括号括起来，该参数是用来传递和接收方法的数据。参数列表是指方法的参数类型、顺序和数量。参数是可选的，也就是说，一个方法可能不包含参数。 Method body：方法主体，包含了完成任务所需的指令集。 using System; namespace HelloWorldApplication { class Program { static void Main(string[] args) { Program p = new Program(); int c = p.add(1,2); Console.WriteLine(c); } public int add(int a,int b){ return a + b; } } } 参数传递 # 当调用带有参数的方法时，您需要向方法传递参数。在 C# 中，有三种向方法传递参数的方式：\n方式 描述 值参数 这种方式复制参数的实际值给函数的形式参数，实参和形参使用的是两个不同内存中的值。在这种情况下，当形参的值发生改变时，不会影响实参的值，从而保证了实参数据的安全。 引用参数 这种方式复制参数的内存位置的引用给形式参数。这意味着，当形参的值发生改变时，同时也改变实参的值。 输出参数 这种方式可以返回多个值。 按值传递参数 # using System; namespace CalculatorApplication { class NumberManipulator { public void swap(int x, int y) { int temp; temp = x; /* 保存 x 的值 */ x = y; /* 把 y 赋值给 x */ y = temp; /* 把 temp 赋值给 y */ } static void Main(string[] args) { NumberManipulator n = new NumberManipulator(); /* 局部变量定义 */ int a = 100; int b = 200; Console.WriteLine(\u0026#34;在交换之前，a 的值： {0}\u0026#34;, a); // 100 Console.WriteLine(\u0026#34;在交换之前，b 的值： {0}\u0026#34;, b); // 200 /* 调用函数来交换值 */ n.swap(a, b); Console.WriteLine(\u0026#34;在交换之后，a 的值： {0}\u0026#34;, a); // 100 Console.WriteLine(\u0026#34;在交换之后，b 的值： {0}\u0026#34;, b); // 200 Console.ReadLine(); } } } 按引用传递参数 # 引用参数是一个对变量的内存位置的引用。当按引用传递参数时，与值参数不同的是，它不会为这些参数创建一个新的存储位置。引用参数表示与提供给方法的实际参数具有相同的内存位置。\n在 C# 中，使用 ref 关键字声明引用参数。\nusing System; namespace CalculatorApplication { class NumberManipulator { public void swap(ref int x, ref int y) { int temp; temp = x; /* 保存 x 的值 */ x = y; /* 把 y 赋值给 x */ y = temp; /* 把 temp 赋值给 y */ } static void Main(string[] args) { NumberManipulator n = new NumberManipulator(); /* 局部变量定义 */ int a = 100; int b = 200; Console.WriteLine(\u0026#34;在交换之前，a 的值： {0}\u0026#34;, a); // 100 Console.WriteLine(\u0026#34;在交换之前，b 的值： {0}\u0026#34;, b); // 200 /* 调用函数来交换值 */ n.swap(ref a, ref b); Console.WriteLine(\u0026#34;在交换之后，a 的值： {0}\u0026#34;, a); // 200 Console.WriteLine(\u0026#34;在交换之后，b 的值： {0}\u0026#34;, b); // 100 Console.ReadLine(); } } } 按输出传递参数 # return 语句可用于只从函数中返回一个值。但是，可以使用 输出参数 来从函数中返回两个值。输出参数会把方法输出的数据赋给自己，其他方面与引用参数相似。\nusing System; namespace CalculatorApplication { class NumberManipulator { public void getValue(out int x ) { int temp = 5; x = temp; } static void Main(string[] args) { NumberManipulator n = new NumberManipulator(); /* 局部变量定义 */ int a = 100; Console.WriteLine(\u0026#34;在方法调用之前，a 的值： {0}\u0026#34;, a); // 100 /* 调用函数来获取值 */ n.getValue(out a); Console.WriteLine(\u0026#34;在方法调用之后，a 的值： {0}\u0026#34;, a); // 5 Console.ReadLine(); } } } ","date":"2024-09-11","externalUrl":null,"permalink":"/posts/5ab2821f/290c486b/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e定义方法 \n    \u003cdiv id=\"定义方法\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%9a%e4%b9%89%e6%96%b9%e6%b3%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-cs\" data-lang=\"cs\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eAccess\u003c/span\u003e \u003cspan class=\"n\"\u003eSpecifier\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eReturn\u003c/span\u003e \u003cspan class=\"n\"\u003eType\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eMethod\u003c/span\u003e \u003cspan class=\"n\"\u003eName\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;(\u003c/span\u003e\u003cspan class=\"n\"\u003eParameter\u003c/span\u003e \u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"n\"\u003eMethod\u003c/span\u003e \u003cspan class=\"n\"\u003eBody\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eAccess Specifier\u003c/strong\u003e：访问修饰符，这个决定了变量或方法对于另一个类的可见性。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eReturn type\u003c/strong\u003e：返回类型，一个方法可以返回一个值。返回类型是方法返回的值的数据类型。如果方法不返回任何值，则返回类型为 \u003cstrong\u003evoid\u003c/strong\u003e。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eMethod name\u003c/strong\u003e：方法名称，是一个唯一的标识符，且是大小写敏感的。它不能与类中声明的其他标识符相同。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eParameter list\u003c/strong\u003e：参数列表，使用圆括号括起来，该参数是用来传递和接收方法的数据。参数列表是指方法的参数类型、顺序和数量。参数是可选的，也就是说，一个方法可能不包含参数。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eMethod body\u003c/strong\u003e：方法主体，包含了完成任务所需的指令集。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-cs\" data-lang=\"cs\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003enamespace\u003c/span\u003e \u003cspan class=\"nn\"\u003eHelloWorldApplication\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eProgram\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"k\"\u003evoid\u003c/span\u003e \u003cspan class=\"n\"\u003eMain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e[]\u003c/span\u003e \u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eProgram\u003c/span\u003e \u003cspan class=\"n\"\u003ep\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eProgram\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"k\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"m\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"k\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e参数传递 \n    \u003cdiv id=\"参数传递\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8f%82%e6%95%b0%e4%bc%a0%e9%80%92\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e当调用带有参数的方法时，您需要向方法传递参数。在 C# 中，有三种向方法传递参数的方式：\u003c/p\u003e","title":"4、方法","type":"posts"},{"content":"事件系统是游戏开发过程中需要涉及到交互常用的功能。使用事件系统不仅可以将输入行为（例如：键盘、鼠标、触摸）以事件的形式发送到应用程序，也可以将游戏过程中的发生的，需要其他对象关注的事情通过事件的形式回应。例如：游戏胜利后需要打开结算或者奖励界面。\n监听和发射事件 # Cocos Creator 引擎提供了 EventTarget 类，用以实现自定义事件的监听和发射，在使用之前，需要先从 'cc' 模块导入，同时需要实例化一个 EventTarget 对象。\nimport { EventTarget } from \u0026#39;cc\u0026#39;; const eventTarget = new EventTarget(); 注意：虽然 Node 对象也实现了一些 EventTarget 的接口，但是我们不再推荐继续通过 Node 对象来做自定义事件的监听和发射，因为这样子做不够高效，同时我们也希望 Node 对象只监听与 Node 相关的事件。\n监听事件 # // 该事件监听每次都会触发，需要手动取消注册 eventTarget.on(type, func, target?); 其中 type 为事件注册字符串，func 为执行事件监听的回调，target 为事件接收对象。如果 target 没有设置，则回调里的 this 指向的就是当前执行回调的对象。\n// 使用函数绑定 eventTarget.on(\u0026#39;foo\u0026#39;, function ( event ) { this.enabled = false; }.bind(this)); // 使用第三个参数 eventTarget.on(\u0026#39;foo\u0026#39;, (event) =\u0026gt; { this.enabled = false; }, this); 除了使用 on 监听，我们还可以使用 once 接口。once 监听在监听函数响应后就会关闭监听事件。\n取消监听事件 # off 接口的使用方式有以下两种：\n// 取消对象身上所有注册的该类型的事件 eventTarget.off(type); // 取消对象身上该类型指定回调指定目标的事件 eventTarget.off(type, func, target); 需要注意的是，off 方法的参数必须和 on 方法的参数一一对应，才能完成关闭。\nimport { _decorator, Component, EventTarget } from \u0026#39;cc\u0026#39;; const { ccclass } = _decorator; const eventTarget = new EventTarget(); @ccclass(\u0026#34;Example\u0026#34;) export class Example extends Component { onEnable () { // 监听 eventTarget.on(\u0026#39;foobar\u0026#39;, this._sayHello, this); } onDisable () { // 取消监听 eventTarget.off(\u0026#39;foobar\u0026#39;, this._sayHello, this); } _sayHello () { console.log(\u0026#39;Hello World\u0026#39;); } } 事件发射 # 发射事件可以通过 eventTarget.emit() 接口来实现，方法如下：\n// 事件发射的时候可以指定事件参数，参数最多只支持 5 个事件参数 eventTarget.emit(type, ...args); 输入事件系统 # 在 Cocos Creator 3.4.0 中，我们支持了 input 对象，该对象实现了 EventTarget 的事件监听接口，可以通过 input 对象监听全局的系统输入事件。\n定义输入事件 # 上文提到的输入事件，都可以通过接口 input.on(type, callback, target) 注册。可选的 type 类型包括：\n输入事件 type 类型 鼠标事件 Input.EventType.MOUSE_DOWN Input.EventType.MOUSE_MOVE Input.EventType.MOUSE_UP Input.EventType.MOUSE_WHEEL 触摸事件 Input.EventType.TOUCH_START Input.EventType.TOUCH_MOVE Input.EventType.TOUCH_END Input.EventType.TOUCH_CANCEL 键盘事件 Input.EventType.KEY_DOWN（键盘按下） Input.EventType.KEY_PRESSING（键盘持续按下） Input.EventType.KEY_UP（键盘释放） 设备重力传感事件 Input.EventType.DEVICEMOTION 回调函数参数类型\n鼠标事件：EventMouse 触摸事件：EventTouch 键盘事件：EventKeyboard 设备重力传感事件：EventAcceleration 屏幕事件 # 以下是当前支持的全局屏幕事件概述：\n事件名称 描述 支持平台 支持版本 window-resize 监听窗口大小变化 Web, Native, MiniGame 3.8.0 orientation-change 监听屏幕方向变化 Web, Native 3.8.3 fullscreen-change 监听全屏变化 Web 3.8.0 import { _decorator, Component, screen, macro } from \u0026#39;cc\u0026#39;; const { ccclass } = _decorator; @ccclass(\u0026#34;Example\u0026#34;) export class Example extends Component { onLoad() { // Register event listeners with the screen object screen.on(\u0026#39;window-resize\u0026#39;, this.onWindowResize, this); screen.on(\u0026#39;orientation-change\u0026#39;, this.onOrientationChange, this); screen.on(\u0026#39;fullscreen-change\u0026#39;, this.onFullScreenChange, this); } onDestroy() { // Unregister event listeners when the component is destroyed screen.off(\u0026#39;window-resize\u0026#39;, this.onWindowResize, this); screen.off(\u0026#39;orientation-change\u0026#39;, this.onOrientationChange, this); screen.off(\u0026#39;fullscreen-change\u0026#39;, this.onFullScreenChange, this); } onWindowResize(width: number, height: number) { console.log(\u0026#34;Window resized:\u0026#34;, width, height); } onOrientationChange(orientation: number) { if (orientation === macro.ORIENTATION_LANDSCAPE_LEFT || orientation === macro.ORIENTATION_LANDSCAPE_RIGHT) { console.log(\u0026#34;Orientation changed to landscape:\u0026#34;, orientation); } else { console.log(\u0026#34;Orientation changed to portrait:\u0026#34;, orientation); } } onFullScreenChange(width: number, height: number) { console.log(\u0026#34;Fullscreen change:\u0026#34;, width, height); } } 事件API # ","date":"2024-08-29","externalUrl":null,"permalink":"/posts/69064821/92082869/59e04995/","section":"文章","summary":"\u003cp\u003e事件系统是游戏开发过程中需要涉及到交互常用的功能。使用事件系统不仅可以将输入行为（例如：键盘、鼠标、触摸）以事件的形式发送到应用程序，也可以将游戏过程中的发生的，需要其他对象关注的事情通过事件的形式回应。例如：游戏胜利后需要打开结算或者奖励界面。\u003c/p\u003e","title":"4、事件","type":"posts"},{"content":" C中的文件 # C语言具有操作文件的能力，比如打开文件、读取和追加数据、插入和删除数据、关闭文件、删除文件等。\n在操作系统中除了常见的文本文件以及二进制文件，为了统一对各种硬件的操作，简化接口，不同的硬件设备也都被看成一个文件。对这些文件的操作，等同于对磁盘上普通文件的操作。例如：\n通常把显示器称为标准输出文件，printf 就是向这个文件输出数据； 通常把键盘称为标准输入文件，scanf 就是从这个文件读取数据。 常见硬件设备对应的文件 # 文件 硬件设备 stdin 标准输入文件，一般指键盘；scanf()、getchar() 等函数默认从 stdin 获取输入。 stdout 标准输出文件，一般指显示器；printf()、putchar() 等函数默认向 stdout 输出数据。 stderr 标准错误文件，一般指显示器；perror() 等函数默认向 stderr 输出数据。 stdprn 标准打印文件，一般指打印机。 操作文件的正确流程为：打开文件 \u0026ndash;\u0026gt; 读写文件 \u0026ndash;\u0026gt; 关闭文件。文件在进行读写操作之前要先打开，使用完毕要关闭。\n所谓打开文件，就是获取文件的有关信息，例如文件名、文件状态、当前读写位置等，这些信息会被保存到一个 FILE 类型的结构体变量中。关闭文件就是断开与文件之间的联系，释放结构体变量，同时禁止再对该文件进行操作。\n在C语言中，文件有多种读写方式，可以一个字符一个字符地读取，也可以读取一整行，还可以读取若干个字节。文件的读写位置也非常灵活，可以从文件开头读取，也可以从中间位置读取。\n文件流 # 所有的文件（保存在磁盘）都要载入内存才能处理，所有的数据必须写入文件（磁盘）才不会丢失。数据在文件和内存之间传递的过程叫做文件流，类似水从一个地方流动到另一个地方。数据从文件复制到内存的过程叫做输入流，从内存保存到文件的过程叫做输出流。\n文件是数据源的一种，除了文件，还有数据库、网络、键盘等；数据传递到内存也就是保存到C语言的变量（例如整数、字符串、数组、缓冲区等）。我们把数据在数据源和程序（内存）之间传递的过程叫做数据流(Data Stream)。相应的，数据从数据源到程序（内存）的过程叫做输入流(Input Stream)，从程序（内存）到数据源的过程叫做输出流(Output Stream)。\n输入输出（Input output，IO）是指程序（内存）与外部设备（键盘、显示器、磁盘、其他计算机等）进行交互的操作。几乎所有的程序都有输入与输出操作，如从键盘上读取数据，从本地或网络上的文件读取数据或写入数据等。通过输入和输出操作可以从外界接收信息，或者是把信息传递给外界。\n我们可以说，打开文件就是打开了一个流。\n打开文件 # 使用 \u0026lt;stdio.h\u0026gt; 头文件中的fopen()函数即可打开文件 函数原型：FILE *fopen( const char * filename, const char * mode ); filename为文件名（包括文件路径），mode为打开方式，它们都是字符串。 fopen() 会获取文件信息，包括文件名、文件状态、当前读写位置等，并将这些信息保存到一个 FILE 类型的结构体变量中，然后将该变量的地址返回。 //以只写方式打开一个当前路径下test.txt文本文件 //fp 通常被称为文件指针。 FILE *fp = fopen(\u0026#34;./test.txt\u0026#34;,\u0026#34;w\u0026#34;); 判断是否打开成功 # 打开文件出错时，fopen() 将返回一个空指针，也就是 NULL，我们可以利用这一点来判断文件是否打开成功，\nint main(){ FILE *fp = NULL; if((fp = fopne(\u0026#34;./test.txt\u0026#34;,\u0026#34;w\u0026#34;)) == NULL){ printf(\u0026#34;File open failed\u0026#34;); } return 0; } 打开方式 # 读写权限和读写方式可以组合使用，但是必须将读写方式放在读写权限的中间或者尾部（换句话说，不能将读写方式放在读写权限的开头）。\n整体来说，文件打开方式由 r、w、a、t、b、+ 六个字符拼成，各字符的含义是：\nr(read)：读 w(write)：写 a(append)：追加 t(text)：文本文件 b(binary)：二进制文件 +：读和写 控制读写权限的字符串 # 打开方式 说明 \u0026ldquo;r\u0026rdquo; 以“只读”方式打开文件。只允许读取，不允许写入。文件必须存在，否则打开失败。 \u0026ldquo;w\u0026rdquo; 以“写入”方式打开文件。如果文件不存在，那么创建一个新文件；如果文件存在，那么清空文件内容（相当于删除原文件，再创建一个新文件）。 \u0026ldquo;a\u0026rdquo; 以“追加”方式打开文件。如果文件不存在，那么创建一个新文件；如果文件存在，那么将写入的数据追加到文件的末尾（文件原有的内容保留）。 \u0026ldquo;r+\u0026rdquo; 以“读写”方式打开文件。既可以读取也可以写入，也就是随意更新文件。文件必须存在，否则打开失败。 \u0026ldquo;w+\u0026rdquo; 以“写入/更新”方式打开文件，相当于w和r+叠加的效果。既可以读取也可以写入，也就是随意更新文件。如果文件不存在，那么创建一个新文件；如果文件存在，那么清空文件内容（相当于删除原文件，再创建一个新文件）。 \u0026ldquo;a+\u0026rdquo; 以“追加/更新”方式打开文件，相当于a和r+叠加的效果。既可以读取也可以写入，也就是随意更新文件。如果文件不存在，那么创建一个新文件；如果文件存在，那么将写入的数据追加到文件的末尾（文件原有的内容保留）。 控制读写方式的字符串 # 打开方式 说明 \u0026ldquo;t\u0026rdquo; 文本文件。如果不写，默认为\u0026quot;t\u0026quot;。 \u0026ldquo;b\u0026rdquo; 二进制文件。 关闭文件 # 文件一旦使用完毕，应该用 fclose() 函数把文件关闭，以释放相关资源，避免数据丢失。 函数原型：int fclose(FILE *fp); 文件正常关闭时，fclose() 的返回值为0，如果返回非零值则表示有错误发生。 读写文件 # 以字符 # 以字符形式读写文件时，每次可以从文件中读取一个字符，或者向文件中写入一个字符。主要使用两个函数，分别是 fgetc() 和 fputc()。\n读 # fgetc 是 file get char 的缩写，意思是从指定的文件中读取一个字符。\n原型：int fgetc (FILE *fp); fp 为文件指针。fgetc() 读取成功时返回读取到的字符，读取到文件末尾或读取失败时返回EOF。 #include\u0026lt;stdio.h\u0026gt; int main(){ //打开文件 FILE *fp = NULL; if((fp = fopen(\u0026#34;./test.txt\u0026#34;,\u0026#34;r\u0026#34;)) == NULL){ printf(\u0026#34;File open failed\u0026#34;); return 0; } //按照字符读取文件 char c; while((c = fgetc(fp)) != EOF){ printf(\u0026#34;%c\u0026#34;,c); } //关闭文件 fclose(fp); return 0; } EOF\n是 end of file 的缩写，表示文件末尾，是在 stdio.h 中定义的宏，它的值是一个负数，往往是 -1。fgetc() 的返回值类型之所以为 int，就是为了容纳这个负数（char不能是负数），EOF 也不全是 -1，也可以是其他负数，这要看编译器的实现。 EOF 本来表示文件末尾，意味着读取结束，但是很多函数在读取出错时也返回 EOF，那么当返回 EOF 时，到底是文件读取完毕了还是读取出错了？我们可以借助 stdio.h 中的两个函数来判断，分别是 feof() 和 ferror()。 feof() 函数用来判断文件内部指针是否指向了文件末尾 原型：int feof ( FILE * fp ); 当指向文件末尾时返回非零值，否则返回零值。 ferror() 函数用来判断文件操作是否出错 原型：int ferror ( FILE *fp ); 出错时返回非零值，否则返回零值。 文件位置指针\n在文件内部有一个位置指针，用来指向当前读写到的位置，也就是读写到第几个字节。在文件打开时，该指针总是指向文件的第一个字节。使用 fgetc() 函数后，该指针会向后移动一个字节，所以可以连续多次使用 fgetc() 读取多个字符。 注意：这个文件内部的位置指针与C语言中的指针不是一回事。位置指针仅仅是一个标志，表示文件读写到的位置，也就是读写到第几个字节，它不表示地址。文件每读写一次，位置指针就会移动一次，它不需要你在程序中定义和赋值，而是由系统自动设置，对用户是隐藏的。 写 # fputc 是 file output char 的所以，意思是向指定的文件中写入一个字符。\n原型：int fputc ( int ch, FILE *fp ); ch 为要写入的字符，fp 为文件指针。fputc() 写入成功时返回写入的字符，失败时返回 EOF，返回值类型为 int 也是为了容纳这个负数。 注意：\n被写入的文件可以用写、读写、追加方式打开，用写或读写方式打开一个已存在的文件时将清除原有的文件内容，并将写入的字符放在文件开头。如需保留原有文件内容，并把写入的字符放在文件末尾，就必须以追加方式打开文件。不管以何种方式打开，被写入的文件若不存在时则创建该文件。 每写入一个字符，文件内部位置指针向后移动一个字节。 #include\u0026lt;stdio.h\u0026gt; int main(){ //打开文件 FILE *fp = NULL; if((fp = fopen(\u0026#34;./test.txt\u0026#34;,\u0026#34;w\u0026#34;)) == NULL){ printf(\u0026#34;File open failed\u0026#34;); return 0; } //从键盘获取字符，并写入文件，回车结束 char c; while((c = getchar()) != \u0026#39;\\n\u0026#39;){ fputc(c,fp); } fclose(fp); return 0; } 以字符串 # fgetc() 和 fputc() 函数每次只能读写一个字符，速度较慢；实际开发中往往是每次读写一个字符串或者一个数据块，这样能明显提高效率，以字符串读主要是用两个函数：fgets、fputs\n读 # fgets() 函数用来从指定的文件中读取一个字符串，并保存到字符数组中\n原型：char *fgets ( char *str, int n, FILE *fp ); str 为字符数组，n 为要读取的字符数目，fp 为文件指针。 返回值：读取成功时返回字符数组首地址，也即 str；读取失败时返回 NULL；如果开始读取时文件内部指针已经指向了文件末尾，那么将读取不到任何字符，也返回 NULL。 注意：\n读取到的字符串会在末尾自动添加 \u0026lsquo;\\0\u0026rsquo;，n 个字符也包括 \u0026lsquo;\\0\u0026rsquo;。也就是说，实际只读取到了 n-1 个字符，如果希望读取 100 个字符，n 的值应该为 101。 在读取到 n-1 个字符之前如果出现了换行，或者读到了文件末尾，则读取结束。这就意味着，不管 n 的值多大，fgets() 最多只能读取一行数据，不能跨行。在C语言中，没有按行读取文件的函数，我们可以借助 fgets()，将 n 的值设置地足够大，每次就可以读取到一行数据。 fgets() 遇到换行时，会将换行符一并读取到当前字符串。 #include\u0026lt;stdio.h\u0026gt; #define NUM 100 int main(){ FILE *fp = NULL; if((fp = fopen(\u0026#34;./test.txt\u0026#34;,\u0026#34;r\u0026#34;)) == NULL){ printf(\u0026#34;File open failed\u0026#34;); return 0; } char c[NUM]; //逐行读取 while(fgets(c,999,fp) != NULL){ printf(\u0026#34;%s\u0026#34;,c); } fclose(fp); return 0; } 写 # fputs() 函数用来向指定的文件写入一个字符串\n原型：int fputs( char *str, FILE *fp ); str 为要写入的字符串，fp 为文件指针。写入成功返回非负数，失败返回 EOF。 #include\u0026lt;stdio.h\u0026gt; int main(){ FILE *fp = NULL; if((fp = fopen(\u0026#34;./test.txt\u0026#34;,\u0026#34;w\u0026#34;)) == NULL){ printf(\u0026#34;File open failed\u0026#34;); return 0; } char str[] = \u0026#34;Hello World\\nHello C Language\u0026#34;; //写入文件 fputs(str,fp); fclose(fp); return 0; } 以数据块（二进制） # 以二进制读取数据，通常是数组或结构体。如果希望读取多行内容，需要使用 fread() 函数；相应地写入函数为 fwrite()。\n读 # fread() 函数用来从指定文件中读取块数据。所谓块数据，也就是若干个字节的数据，可以是一个字符，可以是一个字符串，可以是多行数据，并没有什么限制。\n原型：size_t fread ( void *ptr, size_t size, size_t count, FILE *fp );\n写 # fwrite() 函数用来向文件中写入块数据\n原型：size_t fwrite ( void * ptr, size_t size, size_t count, FILE *fp );\n说明 # 参数\nptr 为内存区块的指针，它可以是数组、变量、结构体等。fread() 中的 ptr 用来存放读取到的数据，fwrite() 中的 ptr 用来存放要写入的数据。 size：表示每个数据块的字节数。 count：表示要读写的数据块的块数。 fp：表示文件指针。 理论上，每次读写 size*count 个字节的数据。 返回值\nsize_t 是在 stdio.h 和 stdlib.h 头文件中使用 typedef 定义的数据类型，表示无符号整数，也即非负数，常用来表示数量。 返回成功读写的块数，也即 count。如果返回值小于 count： 对于 fwrite() 来说，肯定发生了写入错误，可以用 ferror() 函数检测。 对于 fread() 来说，可能读到了文件末尾，可能发生了错误，可以用 ferror() 或 feof() 检测。 对数组进行读写 # 写 # #include\u0026lt;stdio.h\u0026gt; int main(){ int age[] = {18,22,55,89}; FILE *fp = NULL; if((fp = fopen(\u0026#34;./age\u0026#34;,\u0026#34;wb\u0026#34;)) == NULL){ printf(\u0026#34;File Open Failed\u0026#34;); return 0; } //将数组写入文件，每个数据块为sizeof(int)字节，一共sizeof(age) / sizeof(int)个数据块 fwrite(age,sizeof(int),sizeof(age) / sizeof(int),fp); fclose(fp); return 0; } 读 # #include\u0026lt;stdio.h\u0026gt; int main(){ FILE *fp = NULL; if((fp = fopen(\u0026#34;./age\u0026#34;,\u0026#34;rb\u0026#34;)) == NULL){ printf(\u0026#34;File Open Failed\u0026#34;); return 0; } int age[4]; fread(age,sizeof(int),4,fp); for(int i = 0;i \u0026lt; 4;i++){ printf(\u0026#34;%d\\n\u0026#34;,age[i]); } fclose(fp); return 0; } 对结构体进行读写 # 写 # #include\u0026lt;stdio.h\u0026gt; typedef struct Stu{ int age; char *name; }Stu; int main(){ FILE *fp = NULL; if((fp = fopen(\u0026#34;./stu\u0026#34;,\u0026#34;wb\u0026#34;)) == NULL){ printf(\u0026#34;File Open Failed\u0026#34;); return 0; } Stu s = {18,\u0026#34;lucy\u0026#34;}; //将一个结构体变量的值写入文件，数据块的大小就是一个结构体的大小，只写入一个数据块 //如果是结构体数组，那么第三个参数就是数组长度 fwrite(\u0026amp;s,sizeof(Stu),1,fp); fclose(fp); return 0; } 读 # #include\u0026lt;stdio.h\u0026gt; typedef struct Stu{ int age; char name[50]; }Stu; int main(){ FILE *fp = NULL; if((fp = fopen(\u0026#34;./stu\u0026#34;,\u0026#34;rb\u0026#34;)) == NULL){ printf(\u0026#34;File Open Failed\u0026#34;); return 0; } Stu s; int i = fread(\u0026amp;s,sizeof(Stu),1,fp); printf(\u0026#34;姓名：%s，年龄：%d\u0026#34;,s.name,s.age); fclose(fp); return 0; } 格式化读写文件 # fscanf() 和 fprintf() 函数与前面使用的 scanf() 和 printf() 功能相似，都是格式化读写函数，两者的区别在于 fscanf() 和 fprintf() 的读写对象不是键盘和显示器，而是磁盘文件。\n函数原型\nint fscanf ( FILE *fp, char * format, ... ); int fprintf ( FILE *fp, char * format, ... ); fprintf() 返回成功写入的字符的个数，失败则返回负数。\nfscanf() 返回参数列表中被成功赋值的参数个数。\n例如，将Stu结构体变量写入文件中\n#include\u0026lt;stdio.h\u0026gt; typedef struct Stu{ char name[20]; int age; }Stu; int main(){ Stu s = {\u0026#34;lucy\u0026#34;,18}; //打开文件 FILE *fp; if((fp = fopen(\u0026#34;./stu.txt\u0026#34;,\u0026#34;w\u0026#34;)) == NULL){ printf(\u0026#34;File opne failed\u0026#34;); } //以规定格式写入 fprintf(fp, \u0026#34;%s %d\\n\u0026#34;,s.name,s.age); fclose(fp); return 0; } 此时，stu.txt文件内容为\nlucy 18 然后，按照格式读取就可以了\n#include\u0026lt;stdio.h\u0026gt; typedef struct Stu{ char name[20]; int age; }Stu; int main(){ FILE *fp; if((fp = fopen(\u0026#34;./stu.txt\u0026#34;,\u0026#34;r\u0026#34;)) == NULL){ printf(\u0026#34;File open failed\u0026#34;); } Stu s; //从文件中按照固定格式读取 int i = fscanf(fp,\u0026#34;%s %d\\n\u0026#34;,\u0026amp;s.name,\u0026amp;s.age); //打印 printf(\u0026#34;name-\u0026gt;%s||age-\u0026gt;%d\u0026#34;,s.name,s.age); fclose(fp); return 0; } name-\u0026gt;lucy||age-\u0026gt;18 移动文件位置指针 # 在实际开发中经常需要读写文件的中间部分，要解决这个问题，就得先移动文件内部的位置指针，再进行读写。这种读写方式称为随机读写，也就是说从文件的任意位置开始读写。\n实现随机读写的关键是要按要求移动位置指针，这称为文件的定位。\nrewind() # 函数原型：void rewind ( FILE *fp );\n该函数的作用相当于重置文件位置指针，将指针移到文件开头\nfseek() # 函数原型：int fseek ( FILE *fp, long offset, int origin );\nfp 为文件指针，也就是被移动的文件。 offset 为偏移量，也就是要移动的字节数。之所以为 long 类型，是希望移动的范围更大，能处理的文件更大。offset 为正时，向后移动；offset 为负时，向前移动。 origin 为起始位置，也就是从何处开始计算偏移量。C语言规定的起始位置有三种，分别为文件开头、当前位置和文件末尾，每个位置都用对应的常量来表示 起始点 常量名 常量值 文件开头 SEEK_SET 0 当前位置 SEEK_CUR 1 文件末尾 SEEK_END 2 例如，把位置指针移动到离文件开头100个字节处\nfseek(fp, 100, 0); 注意：值得说明的是，fseek() 一般用于二进制文件，在文本文件中由于要进行字节和字符转换，计算的位置有时会出错。\n","date":"2024-04-03","externalUrl":null,"permalink":"/posts/f571508d/8c7e092d/77d85abb/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eC中的文件 \n    \u003cdiv id=\"c中的文件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#c%e4%b8%ad%e7%9a%84%e6%96%87%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eC语言具有操作文件的能力，比如打开文件、读取和追加数据、插入和删除数据、关闭文件、删除文件等。\u003c/p\u003e","title":"4、文件读写","type":"posts"},{"content":" 运算符 # 作用：用于执行代码的运算\n运算符类型 作用 算术运算符 用于处理四则运算 赋值运算符 用于将表达式的值赋给变量 比较运算符 用于表达式的比较，并返回一个真值或假值 逻辑运算符 用于根据表达式的值返回真值或假值 算术运算符 # 作用：用于处理四则运算\n和java的基本相同\n运算符 术语 示例 结果 + 正号 +3 3 - 负号 -3 -3 + 加 10 + 5 15 - 减 10 - 5 5 * 乘 10 * 5 50 / 除 10 / 5 2 % 取模(取余) 10 % 3 1 ++ 前置递增 a=2; b=++a; a=3; b=3; ++ 后置递增 a=2; b=a++; a=3; b=2; \u0026ndash; 前置递减 a=2; b=\u0026ndash;a; a=1; b=1; \u0026ndash; 后置递减 a=2; b=a\u0026ndash;; a=1; b=2; 字符串拼接 # 如果需要字符串拼接int等数值类型的话，那么不可以和java一样，直接使用+去加，而是需要使用基本命名空间std中的to_string()函数\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; int main() { int a = 10; double b = 25.4; bool c = true; string str = \u0026#34;你好呀\u0026#34;; string joint1 = str + to_string(a); string joint2 = str + to_string(b); string joint3 = str + to_string(c); cout \u0026lt;\u0026lt; joint1 \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; joint2 \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; joint3 \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 赋值运算符 # 作用：用于将表达式的值赋给变量\n和java基本相同\n运算符 术语 示例 结果 = 赋值 a=2; b=3; a=2; b=3; += 加等于 a=0; a+=2; a=2; -= 减等于 a=5; a-=3; a=2; *= 乘等于 a=2; a*=2; a=4; /= 除等于 a=4; a/=2; a=2; %= 模等于 a=3; a%2; a=1; 比较运算符 # 作用：用于表达式的比较，并返回一个真值或假值，也就是bool类型\n运算符 术语 示例 结果 == 相等于 4 == 3 0 != 不等于 4 != 3 1 \u0026lt; 小于 4 \u0026lt; 3 0 \u0026gt; 大于 4 \u0026gt; 3 1 \u0026lt;= 小于等于 4 \u0026lt;= 3 0 \u0026gt;= 大于等于 4 \u0026gt;= 1 1 逻辑运算符 # 作用：用于根据表达式的值返回真值或假值\n和java的用法相同，与和或都是短路的\n运算符 术语 示例 结果 ! 非 !a 如果a为假，则!a为真； 如果a为真，则!a为假。 \u0026amp;\u0026amp; 与 a \u0026amp;\u0026amp; b 如果a和b都为真，则结果为真，否则为假。 || 或 a || b 如果a和b有一个为真，则结果为真，二者都为假时，结果为假。 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { int i = 1; bool a = 10 \u0026gt; 1 || i++; cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; //1 cout \u0026lt;\u0026lt; i \u0026lt;\u0026lt; endl; //1 system(\u0026#34;pause\u0026#34;); return 0; } 运算符重载 # 运算符重载概念：对已有的运算符重新进行定义，赋予其另一种功能，以适应不同的数据类型\n作用：实现两个自定义数据类型的运算\n语法：只需要在需要自定义运算逻辑的类中，声明函数，然后把函数的名字换为operator运算符即可\n加号运算符 # #include\u0026lt;iostream\u0026gt; using namespace std; class Person { private: int age; public: //构造函数，设置age Person(int age) { this-\u0026gt;age = age; } //运算符+重载，返回一个新的Person对象，age为二者age之和 Person operator+(Person \u0026amp; person) { int totalAge = this-\u0026gt;age + person.age; Person p(totalAge); return p; } int getAge() { return this-\u0026gt;age; } }; int main() { Person p1(18); Person p2(20); //使用的时候，就可以直接使用+运算符，而不需要调用函数 Person p3 = p1 + p2; cout \u0026lt;\u0026lt; p3.getAge() \u0026lt;\u0026lt; endl; return 0; } 左移运算符 # 由于之前的输出语句是cout \u0026lt;\u0026lt; \u0026quot;msg\u0026quot;;，cout是ostream类型的对象，那么，我们可以重载在一运算符，实现输出自定义类型对象\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; class Person { //将全局函数声明为友元，可以访问私有属性 friend ostream\u0026amp; operator\u0026lt;\u0026lt;(ostream \u0026amp; out, Person\u0026amp; p); private: int age; string name; public: Person(int age,string name) { this-\u0026gt;age = age; this-\u0026gt;name = name; } //如果作为成员函数出现，那么实质上就是，person.operator\u0026lt;\u0026lt;(person)，这么不符合逻辑 //所以，为了可以达到ostream operator\u0026lt;\u0026lt;(Person person,ostream c)的效果，我们需要把这个声明为全局函数 //ostream\u0026amp; operator\u0026lt;\u0026lt;(Person \u0026amp; person); }; //全局函数，重载左移运算符 ostream\u0026amp; operator\u0026lt;\u0026lt;(ostream \u0026amp; out,Person\u0026amp; p) { string msg = \u0026#34;姓名：\u0026#34; + p.name + \u0026#34;,年龄：\u0026#34; + std::to_string(p.age); out \u0026lt;\u0026lt; msg; return out; } int main() { Person p(18, \u0026#34;Lucy\u0026#34;); cout \u0026lt;\u0026lt; p \u0026lt;\u0026lt; endl; //姓名：Lucy,年龄：18 return 0; } 递增运算符重载 # class MyInteger { friend ostream\u0026amp; operator\u0026lt;\u0026lt;(ostream\u0026amp; out, MyInteger myint); public: MyInteger() { m_Num = 0; } //前置++ MyInteger\u0026amp; operator++() { //先++ m_Num++; //再返回 return *this; } //后置++ MyInteger operator++(int) { //先返回 MyInteger temp = *this; //记录当前本身的值，然后让本身的值加1，但是返回的是以前的值，达到先返回后++； m_Num++; return temp; } private: int m_Num; }; ostream\u0026amp; operator\u0026lt;\u0026lt;(ostream\u0026amp; out, MyInteger myint) { out \u0026lt;\u0026lt; myint.m_Num; return out; } //前置++ 先++ 再返回 void test01() { MyInteger myInt; cout \u0026lt;\u0026lt; ++myInt \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; myInt \u0026lt;\u0026lt; endl; } //后置++ 先返回 再++ void test02() { MyInteger myInt; cout \u0026lt;\u0026lt; myInt++ \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; myInt \u0026lt;\u0026lt; endl; } int main() { test01(); //test02(); system(\u0026#34;pause\u0026#34;); return 0; } 赋值运算符重载 # c++编译器至少给一个类添加4个函数\n默认构造函数(无参，函数体为空) 默认析构函数(无参，函数体为空) 默认拷贝构造函数，对属性进行值拷贝 赋值运算符 operator=, 对属性进行值拷贝 如果类中有属性指向堆区，做赋值操作时也会出现深浅拷贝问题\nclass Person { public: Person(int age) { //将年龄数据开辟到堆区 m_Age = new int(age); } //重载赋值运算符 Person\u0026amp; operator=(Person \u0026amp;p) { if (m_Age != NULL) { delete m_Age; m_Age = NULL; } //编译器提供的代码是浅拷贝 //m_Age = p.m_Age; //提供深拷贝 解决浅拷贝的问题 m_Age = new int(*p.m_Age); //返回自身 return *this; } ~Person() { if (m_Age != NULL) { delete m_Age; m_Age = NULL; } } //年龄的指针 int *m_Age; }; void test01() { Person p1(18); Person p2(20); Person p3(30); p3 = p2 = p1; //赋值操作 cout \u0026lt;\u0026lt; \u0026#34;p1的年龄为：\u0026#34; \u0026lt;\u0026lt; *p1.m_Age \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;p2的年龄为：\u0026#34; \u0026lt;\u0026lt; *p2.m_Age \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;p3的年龄为：\u0026#34; \u0026lt;\u0026lt; *p3.m_Age \u0026lt;\u0026lt; endl; } int main() { test01(); //int a = 10; //int b = 20; //int c = 30; //c = b = a; //cout \u0026lt;\u0026lt; \u0026#34;a = \u0026#34; \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; //cout \u0026lt;\u0026lt; \u0026#34;b = \u0026#34; \u0026lt;\u0026lt; b \u0026lt;\u0026lt; endl; //cout \u0026lt;\u0026lt; \u0026#34;c = \u0026#34; \u0026lt;\u0026lt; c \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 关系运算符重载 # class Person { public: Person(string name, int age) { this-\u0026gt;m_Name = name; this-\u0026gt;m_Age = age; }; bool operator==(Person \u0026amp; p) { if (this-\u0026gt;m_Name == p.m_Name \u0026amp;\u0026amp; this-\u0026gt;m_Age == p.m_Age) { return true; } else { return false; } } bool operator!=(Person \u0026amp; p) { if (this-\u0026gt;m_Name == p.m_Name \u0026amp;\u0026amp; this-\u0026gt;m_Age == p.m_Age) { return false; } else { return true; } } string m_Name; int m_Age; }; void test01() { //int a = 0; //int b = 0; Person a(\u0026#34;孙悟空\u0026#34;, 18); Person b(\u0026#34;孙悟空\u0026#34;, 18); if (a == b) { cout \u0026lt;\u0026lt; \u0026#34;a和b相等\u0026#34; \u0026lt;\u0026lt; endl; } else { cout \u0026lt;\u0026lt; \u0026#34;a和b不相等\u0026#34; \u0026lt;\u0026lt; endl; } if (a != b) { cout \u0026lt;\u0026lt; \u0026#34;a和b不相等\u0026#34; \u0026lt;\u0026lt; endl; } else { cout \u0026lt;\u0026lt; \u0026#34;a和b相等\u0026#34; \u0026lt;\u0026lt; endl; } } int main() { test01(); system(\u0026#34;pause\u0026#34;); return 0; } 函数调用运算符重载 # 函数调用运算符 () 也可以重载 由于重载后使用的方式非常像函数的调用，因此称为仿函数 仿函数没有固定写法，非常灵活 class MyPrint { public: void operator()(string text) { cout \u0026lt;\u0026lt; text \u0026lt;\u0026lt; endl; } }; void test01() { //重载的（）操作符 也称为仿函数 MyPrint myFunc; myFunc(\u0026#34;hello world\u0026#34;); } class MyAdd { public: int operator()(int v1, int v2) { return v1 + v2; } }; void test02() { MyAdd add; int ret = add(10, 10); cout \u0026lt;\u0026lt; \u0026#34;ret = \u0026#34; \u0026lt;\u0026lt; ret \u0026lt;\u0026lt; endl; //匿名对象调用 cout \u0026lt;\u0026lt; \u0026#34;MyAdd()(100,100) = \u0026#34; \u0026lt;\u0026lt; MyAdd()(100, 100) \u0026lt;\u0026lt; endl; } int main() { test01(); test02(); system(\u0026#34;pause\u0026#34;); return 0; } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/d8245fe1/09e70634/340ce84a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e运算符 \n    \u003cdiv id=\"运算符\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%bf%90%e7%ae%97%e7%ac%a6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e作用\u003c/strong\u003e：用于执行代码的运算\u003c/p\u003e","title":"4、c++的运算符","type":"posts"},{"content":" JSP # JSP（全称JavaServer Pages）是由Sun Microsystems公司主导创建的一种动态网页技术标准。JSP部署于网络服务器上，可以响应客户端发送的请求，并根据请求内容动态地生成HTML、XML或其他格式文档的Web网页，然后返回给请求者。\nJSP里面既可以写Java代码，也可以写HTML标签，JSP = Java + HTML\n技术特点 # JSP 和 Servlet 是本质相同的技术。当一个 JSP 文件第一次被请求时，JSP 引擎会将该 JSP编译成一个 Servlet，并执行这个 Servlet。\n如果 JSP 文件被修改了，那么 JSP 引擎会重新编译这个 JSP。\nJSP 引擎对 JSP 编译时会生成两个文件分别是.java的源文件以及编译后的.class文件，并放到Tomcat/work/Catalina下对应的虚拟主机目录（例如localhost）中的 org/apache/jsp目录中。两个文件的名称会使用 JSP 的名称加_jsp表示。如：index_jsp.java、index_jsp.class。\nJSP和Servlet的关系 # JSP以源文件的形式部署到容器中，而Servlet需要编译成.class字节码文件部署到容器中 JSP部署到Web项目的根目录或根目录下的其他子目录，和静态资源位于相同的位置；而Servlet需要部署到WEB-INF/classes目录中 JSP中的HTML代码会被JSP引擎放入Servlet的out.write()方法中；而在Servlet中我们需要自己通过对字符流、输出流的操作生成响应的页面 JSP更擅长页面显示、Servlet更擅长逻辑控制 JSP的脚本 # 一下三个标签中间都可以写java代码，只是当JSP编译为.java文件后，代码存在的位置不同\n\u0026lt;%! 代码 %\u0026gt;（声明标签）：声明成员变量和成员方法，中间的内容会出现在编译后的Servlet的Class的{}中 \u0026lt;% 代码 %\u0026gt;（脚本标签）：会出现在service方法体的内部。 \u0026lt;%= 代码 %\u0026gt;（赋值标签）：标签中的内容会出现在out.printf()方法的参数中，会输出到页面上。 JSP的常用的指令 # 作用 对jsp进行配置、导入一些资源 格式 \u0026lt;%@指令名称 属性名1=属性值1 属性名2=属性值2 %\u0026gt; 常见指令名称 page：主要是用来对jsp进行配置 contentType：是jsp翻译成servlet之后，所设置响应头，该响应头是指服务器告诉浏览器相应数据的类型和编码是什么。 pageEncoding：是指jsp翻译sevlet的时候，所使用的编码 import：导入包 errorPage：如果该jsp页面出现了错误，会跳转到指定的错误页面 isErrorPage：默认值为false，如果为false，那该jsp翻译成的servlet里面就没有exception对象；如果为true，那该jsp翻译成的servlet里面就会自动生成一个exception对象。 include：可以用来包含其他的页面 静态包含：\u0026lt;%@include file=\u0026quot;其他的页面\u0026quot;%\u0026gt;，会将两个页面合并成一个，生成servlet 动态包含：\u0026lt;jsp:include page=\u0026quot;demo4.jsp\u0026quot;/\u0026gt;，每一个页面都会生成各自的servlet 推荐使用静态包含 JSP的注释 # 原生HTML的注释 \u0026lt;!-- abc --\u0026gt; 在JSP翻译成的servlet中是存在的 JSP特有的注释**（推荐）** \u0026lt;%-- abc --%\u0026gt; 在jsp翻译成的servlet中是不存在的 JSP的九大内置对象 # 内置对象：不能自己创建，直接在JSP页面中使用实例名称调用即可\n实例的名称 Java类型 作用 pageContext PageContext 四大域对象之一，代表的范围是当前jsp页面，可以实现数据的共享 session HttpSession 四大域对象之一，代表的范围是一次会话，可以实现数据的共享 request HttpServletRequest 四大域对象之一，代表的范围是一次请求，可以实现数据的共享 application ServletContext 四大域对象之一，代表的范围是整个项目，可以实现数据的共享 response HttpServletResponse 响应对象，可以用来设置响应头，响应状态码，可以实现重定向 config ServletConfig servlet的配置对象，可以配置一些信息 out JspWriter 输出流，向页面写东西。和PrintWrite的功能类似 page Object 是指jsp编译成servlet的servlet对象 exception Throwable 异常对象，可以打印一些异常信息 EL表达式 # **EL(Expression Language)**表达式语言。 目的：为了替换Java代码，使JSP写起来更加简单，简化取值过程\nEl表达式的格式：${表达式}\n注意，如果使用Tomcat作为部署容器，Tomcat已经集成了EL，而JSP在构建阶段（mvn clean package）是不会被编译的，所以使用Tomcat作为部署容器时，不需要加入EL表达式的依赖\n运算符 # 格式：${运算符}\n算术运算符：+、-、*、/\n逻辑运算符：\u0026amp;\u0026amp;（and）、||（or）、!（not）\n比较运算符：\u0026gt;、\u0026lt;、\u0026gt;=、\u0026lt;=、==\n空运算符：empty\n判断数组、字符串、集合的内容，是否为空，是否为null，长度为是否0\n//判断是否为空 ${empty xxx} //判断是否不为空 ${!empty xxx} 注意：如果我们想要在页面上显示的内容就是${1+1}\n方案一：\\${1+1} 方案二：在page指令中写一个属性：isELIgnored=\u0026quot;true\u0026quot;忽略该页面中的所有的EL表达式 获取值 # 注意：想要使用el表达式获取数据的话，只能从域对象中获取数据\n格式一 # ${域名称.键名}\n域名：\npageContext：pageScope request：requestScope session：sessionScope application：applicationScope \u0026lt;% String name = \u0026#34;jack\u0026#34;; request.setAttribute(\u0026#34;name\u0026#34;, name); %\u0026gt; ${requestScope.name} 格式二 # ${键名}\n根据域的范围大小，由小到大进行查找，直到每一个域中都不存在，就不会展示，如果找到了就直接展 pageScope \u0026lt; requestScope \u0026lt; sessionScope \u0026lt; applicationScope \u0026lt;% String name = \u0026#34;jack\u0026#34;; request.setAttribute(\u0026#34;name\u0026#34;, name); %\u0026gt; ${name} 获取对象中的数据 # 普通对象 # ${对象名.对象属性名}\n例如\n\u0026lt;% User user = new User(); user.setName(\u0026#34;tom\u0026#34;); user.setAge(18); request.setAttribute(\u0026#34;user\u0026#34;, user); %\u0026gt; ${user.name} ${user.age } 集合对象 # 单列集合 # ${键名[索引]}\n例子：List类\n\u0026lt;% List list = new ArrayList(); list.add(\u0026#34;aaa\u0026#34;); list.add(\u0026#34;bbb\u0026#34;); list.add(\u0026#34;ccc\u0026#34;); pageContext.setAttribute(\u0026#34;list\u0026#34;, list); %\u0026gt; ${list[0] } ${list[1] } ${list[2] } 双列集合 # 方法一：${域中的键名.map中键名}\n方法二：${键名['map中键名']} 例子：Map类\n\u0026lt;% Map map = new HashMap(); map.put(\u0026#34;name\u0026#34;, \u0026#34;jerry\u0026#34;); map.put(\u0026#34;age\u0026#34;, 18); application.setAttribute(\u0026#34;map\u0026#34;, map); %\u0026gt; ${map.name } ${map.age } ${map[\u0026#39;name\u0026#39;] } ${map[\u0026#39;age\u0026#39;] } 获取当前项目的虚拟目录 # el表达式里面有很多隐式对象，但是我们只需要知道一个即可：pageContext\npageContext隐式对象可以获取到其他的8个内置对象\n格式：${pageContext.request.contextPath}\nJSTL标签库 # JSTL（Java server pages standarded tag library，即JSP标准标签库）是由JCP（Java community Proces）所制定的标准规范，它主要提供给Java Web开发人员一个标准通用的标签库，并由Apache的Jakarta小组来维护。开发人员可以利用这些标签取代JSP页面上的Java代码，从而提高程序的可读性，降低程序的维护难度\n优点 # 1、提供一组标准标签\n2、可用于编写动态JSP页面\n引入JSTL标签库 # 引入JSTL依赖\n\u0026lt;!-- https://mvnrepository.com/artifact/javax.servlet/jstl --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;javax.servlet\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jstl\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- https://mvnrepository.com/artifact/taglibs/standard --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;taglibs\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;standard\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.1.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 在JSP页面添加taglib指令\n\u0026lt;%@ taglib uri=\u0026#34;http://java.sun.com/jsp/jstl/core\u0026#34; prefix=\u0026#34;c\u0026#34;%\u0026gt; 常用标签 # if：判断 # 格式：\n\u0026lt;c:if test=\u0026#34;\u0026#34;\u0026gt;\u0026lt;/c:if\u0026gt; 1、test为判断部分，需要写成el表达式 choose-when-otherwise # 格式：\n\u0026lt;c:choose\u0026gt; \u0026lt;c:when test=\u0026#34;情况一\u0026#34;\u0026gt; 情况一处理 \u0026lt;/c:when\u0026gt; \u0026lt;c:when test=\u0026#34;情况二\u0026#34;\u0026gt; 情况二处理 \u0026lt;/c:when\u0026gt; \u0026lt;c:otherwise\u0026gt; 其他情况处理 \u0026lt;/c:otherwise\u0026gt; \u0026lt;/c:choose\u0026gt; foreach：循环 # 格式：\n\u0026lt;c:forEach begin=\u0026#34;1\u0026#34; end=\u0026#34;5\u0026#34; step=\u0026#34;1\u0026#34; var=\u0026#34;a\u0026#34; varStatus=\u0026#34;s\u0026#34; items=\u0026#34;\u0026#34;\u0026gt; ${a }...${s.index }...${s.count } \u0026lt;/c:forEach\u0026gt; 1. begin：起始索引 2. end：结束索引 3. step：步数（每次循环索引加几） 4. items：要遍历的容器对象 5. var：容器中的元素 6. varStatus：容器中的元素对象对应的状态，该状态有两个参数，index：索引；count：是第几个 ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/fb007bea/4e51db74/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJSP \n    \u003cdiv id=\"jsp\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jsp\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eJSP（全称JavaServer Pages）是由Sun Microsystems公司主导创建的一种动态网页技术标准。JSP部署于网络服务器上，可以响应客户端发送的请求，并\u003cstrong\u003e根据请求内容动态地生成HTML、XML或其他格式文档的Web网页\u003c/strong\u003e，然后返回给请求者。\u003c/p\u003e","title":"4、JSP","type":"posts"},{"content":"Maven 的生命周期是抽象的，这意味着生命周期以及生命周期中每个phase（阶段）本身不做任何实际的工作，实际的任务都是依靠插件来完成。每个阶段都可以绑定一个或多个插件目标，Maven 为大多数构建步骤编写并绑定了默认插件。当用户有特殊需要的时候，可以自己配置插件来定制构建行为，甚至可以使用自己编写的 Maven 插件。\n插件目标（goal） # 一个插件往往能够完成多个任务（目标goal）。比如，maven-dependency-plugin 插件能够基于项目依赖做很多事情，例如：它能够分析项目依赖来帮助找出潜在的无用依赖dependency:analyze，它能够列出项目的依赖树来帮助分析依赖来源dependency:tree，它能够列出项目所有已解析的依赖dependency:list等等。\n这是一种通用的写法，冒号前面是插件前缀，冒号后面是该插件的目标。类似地，还可以写出compiler:compile（这是maven-compiler-plugin的compile目标）和surefire:test（这是maven-surefire-plugin的 test 目标)。\n调用插件的目标，例如大部分插件都有的help目标，用来查看插件的说明以及goal说明\nmvn dependency:help # 如果是第三方的插件，可以使用mvn [groupId]:[artifactId]:[version]:[goal]来调用 # 命令中插件版本是可以省略的，maven会自动找到这个插件最新的版本运行，不过最好我们不要省略版本号，每个版本的插件功能可能不一样，为了保证任何情况下运行效果的一致性，强烈建议指定版本号。 mvn org.apache.maven.plugins:maven-war-plugin:3.2.3:help mvn org.springframework.boot:spring-boot-maven-plugin:2.2.0.RELEASE:help 插件目标与阶段的绑定 # Maven 生命周期中的阶段与插件目标相互绑定，用以完成实际的构建任务。插件目标可能绑定到零个或多个构建阶段。未绑定到任何阶段的插件目标可以通过直接调用从而在生命周期之外执行。执行顺序取决于调用插件目标和阶段的顺序。例如，下面的命令中的clean和package参数是构建阶段，而dependency:copy-dependencies是插件的目标。\nmvn clean dependency:copy-dependencies package 内置插件绑定 # 为了能让用户几乎不用任何配置就能构建 Maven 项目，Maven 为一些主要的生命周期阶段绑定了很多插件目标，当用户通过命令行调用这些内置绑定好的阶段时，对应的插件目标就会执行相应的任务。Maven 在$MAVEN_HOME/lib/maven-core-version.jar/META-INF/plexus/components.xml中定义了三个声明周期以及绑定\nclean lifecycle的绑定 # \u0026lt;component\u0026gt; \u0026lt;role\u0026gt;org.apache.maven.lifecycle.Lifecycle\u0026lt;/role\u0026gt; \u0026lt;implementation\u0026gt;org.apache.maven.lifecycle.Lifecycle\u0026lt;/implementation\u0026gt; \u0026lt;role-hint\u0026gt;clean\u0026lt;/role-hint\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;id\u0026gt;clean\u0026lt;/id\u0026gt; \u0026lt;phases\u0026gt; \u0026lt;phase\u0026gt;pre-clean\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;clean\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;post-clean\u0026lt;/phase\u0026gt; \u0026lt;/phases\u0026gt; \u0026lt;default-phases\u0026gt; \u0026lt;clean\u0026gt;org.apache.maven.plugins:maven-clean-plugin:2.5:clean\u0026lt;/clean\u0026gt; \u0026lt;/default-phases\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/component\u0026gt; 生命周期阶段 插件:目标 执行任务 pre-clean clean maven-clean-plugin:clean 删除项目的输出目录 post-clean site lifecycle的绑定 # \u0026lt;component\u0026gt; \u0026lt;role\u0026gt;org.apache.maven.lifecycle.Lifecycle\u0026lt;/role\u0026gt; \u0026lt;implementation\u0026gt;org.apache.maven.lifecycle.Lifecycle\u0026lt;/implementation\u0026gt; \u0026lt;role-hint\u0026gt;site\u0026lt;/role-hint\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;id\u0026gt;site\u0026lt;/id\u0026gt; \u0026lt;phases\u0026gt; \u0026lt;phase\u0026gt;pre-site\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;site\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;post-site\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;site-deploy\u0026lt;/phase\u0026gt; \u0026lt;/phases\u0026gt; \u0026lt;default-phases\u0026gt; \u0026lt;site\u0026gt;org.apache.maven.plugins:maven-site-plugin:3.3:site\u0026lt;/site\u0026gt; \u0026lt;site-deploy\u0026gt;org.apache.maven.plugins:maven-site-plugin:3.3:deploy\u0026lt;/site-deploy\u0026gt; \u0026lt;/default-phases\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/component\u0026gt; 生命周期阶段 插件:目标 pre-site site maven-site-plugin:site post-site site-deploy maven-site-plugin:deploy default lifecycle的绑定 # \u0026lt;component\u0026gt; \u0026lt;role\u0026gt;org.apache.maven.lifecycle.Lifecycle\u0026lt;/role\u0026gt; \u0026lt;implementation\u0026gt;org.apache.maven.lifecycle.Lifecycle\u0026lt;/implementation\u0026gt; \u0026lt;role-hint\u0026gt;default\u0026lt;/role-hint\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;id\u0026gt;default\u0026lt;/id\u0026gt; \u0026lt;phases\u0026gt; \u0026lt;phase\u0026gt;validate\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;initialize\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;generate-sources\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;process-sources\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;generate-resources\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;process-resources\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;compile\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;process-classes\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;generate-test-sources\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;process-test-sources\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;generate-test-resources\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;process-test-resources\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;test-compile\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;process-test-classes\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;test\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;prepare-package\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;package\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;pre-integration-test\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;integration-test\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;post-integration-test\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;verify\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;install\u0026lt;/phase\u0026gt; \u0026lt;phase\u0026gt;deploy\u0026lt;/phase\u0026gt; \u0026lt;/phases\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/component\u0026gt; 生命周期阶段 插件:目标 执行任务 process-resources maven-resources-plugin:resources 复制主资源文件至主输出目录 compile maven-compiler-plugin:compile 编译主代码至主输出目录 process-test-resources maven-resources-plugin:testResources 复制测试资源文件至测试输出目录 test-compile maven-compiler-plugin:testCompile 编译测试代码至测试输出目录 test maven-surefile-plugin:test 执行测试用例 package maven-jar-plugin:jar 创建项目jar包 install maven-install-plugin:install 将输出构件安装到本地仓库 deploy maven-deploy-plugin:deploy 将输出的构件部署到远程仓库 default 生命周期的插件绑定在 $MAVEN_HOME/lib/maven-core-version.jar/META-INF/plexus/default-bindings.xml 文件中单独定义，因为每个打包类型都不同，例如pom、jar、maven-plugin、war等等。\npom打包绑定 # 在 $MAVEN_HOME/lib/maven-core-version.jar/META-INF/plexus/default-bindings.xml 文件中关于 pom 打包类型的默认绑定如下：\n\u0026lt;component\u0026gt; \u0026lt;role\u0026gt;org.apache.maven.lifecycle.mapping.LifecycleMapping\u0026lt;/role\u0026gt; \u0026lt;role-hint\u0026gt;pom\u0026lt;/role-hint\u0026gt; \u0026lt;implementation\u0026gt;org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping\u0026lt;/implementation\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;lifecycles\u0026gt; \u0026lt;lifecycle\u0026gt; \u0026lt;id\u0026gt;default\u0026lt;/id\u0026gt; \u0026lt;!-- START SNIPPET: pom-lifecycle --\u0026gt; \u0026lt;phases\u0026gt; \u0026lt;install\u0026gt;org.apache.maven.plugins:maven-install-plugin:2.4:install\u0026lt;/install\u0026gt; \u0026lt;deploy\u0026gt;org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy\u0026lt;/deploy\u0026gt; \u0026lt;/phases\u0026gt; \u0026lt;!-- END SNIPPET: pom-lifecycle --\u0026gt; \u0026lt;/lifecycle\u0026gt; \u0026lt;/lifecycles\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/component\u0026gt; jar打包绑定 # 在 $MAVEN_HOME/lib/maven-core-version.jar/META-INF/plexus/default-bindings.xml 文件中关于 jar 打包类型的默认绑定如下：\n\u0026lt;component\u0026gt; \u0026lt;role\u0026gt;org.apache.maven.lifecycle.mapping.LifecycleMapping\u0026lt;/role\u0026gt; \u0026lt;role-hint\u0026gt;jar\u0026lt;/role-hint\u0026gt; \u0026lt;implementation\u0026gt;org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping\u0026lt;/implementation\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;lifecycles\u0026gt; \u0026lt;lifecycle\u0026gt; \u0026lt;id\u0026gt;default\u0026lt;/id\u0026gt; \u0026lt;!-- START SNIPPET: jar-lifecycle --\u0026gt; \u0026lt;phases\u0026gt; \u0026lt;process-resources\u0026gt;org.apache.maven.plugins:maven-resources-plugin:2.6:resources\u0026lt;/process-resources\u0026gt; \u0026lt;compile\u0026gt;org.apache.maven.plugins:maven-compiler-plugin:3.1:compile\u0026lt;/compile\u0026gt; \u0026lt;process-test-resources\u0026gt;org.apache.maven.plugins:maven-resources-plugin:2.6:testResources\u0026lt;/process-test-resources\u0026gt; \u0026lt;test-compile\u0026gt;org.apache.maven.plugins:maven-compiler-plugin:3.1:testCompile\u0026lt;/test-compile\u0026gt; \u0026lt;test\u0026gt;org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test\u0026lt;/test\u0026gt; \u0026lt;package\u0026gt;org.apache.maven.plugins:maven-jar-plugin:2.4:jar\u0026lt;/package\u0026gt; \u0026lt;install\u0026gt;org.apache.maven.plugins:maven-install-plugin:2.4:install\u0026lt;/install\u0026gt; \u0026lt;deploy\u0026gt;org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy\u0026lt;/deploy\u0026gt; \u0026lt;/phases\u0026gt; \u0026lt;!-- END SNIPPET: jar-lifecycle --\u0026gt; \u0026lt;/lifecycle\u0026gt; \u0026lt;/lifecycles\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/component\u0026gt; 自定义插件和使用 # 插件前缀 # 我们之前在调用一个插件的goal的时候，使用的命令是这样的：mvn [groupId]:[artifactId]:[version]:[goal]，比较麻烦，maven也为了我们使用插件方便，提供了插件前缀来帮我们解决这个问题。\n我们在命名项目名称也就是artifactId的时候，如果满足xxx-maven-plugin这样的格式，那么maven默认就会认为xxx就是插件前缀，我们在调用goal的时候，可以使用xxx:goal的格式来进行调用\n注意：maven默认会在仓库org.apache.maven.plugins和 org.codehaus.mojo2个位置查找插件，如果需要在命令行调用（代码中引入则不需要）第三方的插件，就需要在$MAVEN_HOME/conf/settings.xml中配置，例如：\n\u0026lt;pluginGroups\u0026gt; \u0026lt;pluginGroup\u0026gt;top.ygang\u0026lt;/pluginGroup\u0026gt; \u0026lt;/pluginGroups\u0026gt; 简单实现 # pom.xml # 创建maven项目，并修改pom.xml文件\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;project xmlns=\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u0026gt; \u0026lt;modelVersion\u0026gt;4.0.0\u0026lt;/modelVersion\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mydemo-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0\u0026lt;/version\u0026gt; \u0026lt;!-- 更改打包方式为maven插件 --\u0026gt; \u0026lt;packaging\u0026gt;maven-plugin\u0026lt;/packaging\u0026gt; \u0026lt;properties\u0026gt; \u0026lt;maven.compiler.source\u0026gt;8\u0026lt;/maven.compiler.source\u0026gt; \u0026lt;maven.compiler.target\u0026gt;8\u0026lt;/maven.compiler.target\u0026gt; \u0026lt;/properties\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;!-- 引入插件开发需要的相关基础类 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-plugin-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- 引入插件开发需要的相关注解 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven.plugin-tools\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-plugin-annotations\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.0\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;provided\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;/project\u0026gt; 定义插件类 # maven插件是需要对外提供各种实际应用能力的，在这个步骤中，我们将定义我们这个插件对外提供的实际能力，这样其他项目想要使用我们插件的时候，就可以选择对应的goal来执行了。\nMojo是 Maven plain Old Java Object 的缩写，Mojo 映射到一个插件目标，插件由一个或多个 Mojo 组成。\nmaven提供了两种方式定义插件类\n方式一，实现Mojo接口 # 实现org.apache.maven.plugin.Mojo接口，并添加@Mojo注解\n@Mojo(name = \u0026#34;showtime\u0026#34;) public class ShowTimeMojo implements Mojo { /** * 核心方法，当使用mvn命令调用插件的目标的时候，最后具体调用的就是这个方法 * @throws MojoExecutionException * @throws MojoFailureException */ @Override public void execute() throws MojoExecutionException, MojoFailureException { } /** * 注入一个标准的Maven日志记录器，允许这个Mojo向用户传递事件和反馈 * @param log */ @Override public void setLog(Log log) { } /** * 获取注入的日志记录器 * @return */ @Override public Log getLog() { return null; } } 方式二，继承AbstractMojo抽象类（推荐） # 一般来说，我们并不会直接通过实现接口的方式来定义插件，而是会采用继承maven提供的抽象类org.apache.maven.plugin.AbstractMojo来定义我们的插件类。\n这个抽象类实现了Mojo接口，同时这个抽象类还实现了setLog和getLog方法，只留下execute方法给开发人员去实现。这个类中Log默认可以向控制台输出日志信息，maven中自带的插件都继承这个类，一般情况下我们开发插件目标可以直接继承这个类，然后实现execute()方法就可以了。\n需要在类上面使用@Mojo注解。这样maven在执行我们的插件的时候，才能够找到我们的入口类。\n@Mojo(name = \u0026#34;showtime\u0026#34;) public class ShowTimeMojo extends AbstractMojo { /** * 核心方法，当使用mvn命令调用插件的目标的时候，最后具体调用的就是这个方法 * @throws MojoExecutionException * @throws MojoFailureException */ @Override public void execute() throws MojoExecutionException, MojoFailureException { String currentTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern(\u0026#34;yyyy-MM-dd\u0026#34;)); getLog().info(\u0026#34;MyMvnPlugin is running , current time is \u0026#34; + currentTime); } } 打包并发布插件到本地仓库 # 执行命令打包并发布插件到本地仓库，打包完成后我们就可以在我们本地仓库中找到我们的插件了\nmvn clean install 测试插件 # 控制台直接使用 # 由于我们已经将插件发送到仓库了，所以我们可以在控制台使用命令来执行插件\nmvn top.ygang:mymvnplugin:1.0:showtime # 或者使用插件前缀 mvn mydemo:showtime 绑定在其他项目生命周期 # 随便找一个maven项目，在pom.xml文件中将自定义插件的showtime这个goal绑定到pre-clean阶段\n\u0026lt;build\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mydemo-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0\u0026lt;/version\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;id\u0026gt;clean\u0026lt;/id\u0026gt; \u0026lt;phase\u0026gt;pre-clean\u0026lt;/phase\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;showtime\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/build\u0026gt; 执行命令mvn clean即可看到\n常用注解 # @Mojo注解 # name：指定插件的goal defaultPhase：指定当前goal绑定的phase @Mojo(name = \u0026#34;showtime\u0026#34;,defaultPhase = LifecyclePhase.PRE_CLEAN) @Parameter注解 # property：属性名称，默认为字段变量名 defaultValue：属性默认值，可以使用pom.xml中的所有变量（包括用户定义变量），例如${basedir} @Parameter的属性值，可以使用命令行mvn -Dparam=value进行传参，也可以在plugin的configration中传参，不同数据类型传参格式不同\n1、String、boolean、数字类型同理 # @Parameter private String myValue; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mydemo-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0\u0026lt;/version\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;myValue\u0026gt;hello\u0026lt;/myValue\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; 2、File类型 # 如果路径是相对的（不是以/或C:之类的驱动器号开头），则路径是相对于包含POM的目录的。\n@Parameter private File myValue; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mydemo-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0\u0026lt;/version\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;myValue\u0026gt;c:\\temp\u0026lt;/myValue\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; 3、Enum # public enum Color { GREEN, RED, BLUE } @Parameter private Color myValue; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mydemo-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0\u0026lt;/version\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;myValue\u0026gt;BLUE\u0026lt;/myValue\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; 4、Array # @Parameter private String[] myValue; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mydemo-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0\u0026lt;/version\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;myValue\u0026gt; \u0026lt;param\u0026gt;value1\u0026lt;/param\u0026gt; \u0026lt;param\u0026gt;value2\u0026lt;/param\u0026gt; \u0026lt;/myValue\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; 5、Collections # @Parameter private List myValue; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mydemo-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0\u0026lt;/version\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;myValue\u0026gt; \u0026lt;param\u0026gt;value1\u0026lt;/param\u0026gt; \u0026lt;param\u0026gt;value2\u0026lt;/param\u0026gt; \u0026lt;/myValue\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; 6、Map # @Parameter private Map myValue; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mydemo-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0\u0026lt;/version\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;myValue\u0026gt; \u0026lt;key1\u0026gt;value1\u0026lt;/key1\u0026gt; \u0026lt;key2\u0026gt;value2\u0026lt;/key2\u0026gt; \u0026lt;/myValue\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; 7、Properties # @Parameter private Properties myValue; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mydemo-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0\u0026lt;/version\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;myValue\u0026gt; \u0026lt;property\u0026gt; \u0026lt;name\u0026gt;propertyName1\u0026lt;/name\u0026gt; \u0026lt;value\u0026gt;propertyValue1\u0026lt;/value\u0026gt; \u0026lt;property\u0026gt; \u0026lt;property\u0026gt; \u0026lt;name\u0026gt;propertyName2\u0026lt;/name\u0026gt; \u0026lt;value\u0026gt;propertyValue2\u0026lt;/value\u0026gt; \u0026lt;property\u0026gt; \u0026lt;/myValue\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; 8、自定义类型 # public class MyClazz { private String field1; private String field2; } @Parameter private MyClazz myValue; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mydemo-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0\u0026lt;/version\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;myValue\u0026gt; \u0026lt;field1\u0026gt;value1\u0026lt;/field1\u0026gt; \u0026lt;field2\u0026gt;value2\u0026lt;/field2\u0026gt; \u0026lt;/myValue\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/4ee304f3/98a9fd6a/","section":"文章","summary":"\u003cp\u003eMaven 的生命周期是抽象的，这意味着生命周期以及生命周期中每个phase（阶段）本身不做任何实际的工作，实际的任务都是依靠插件来完成。每个阶段都可以绑定一个或多个插件目标，Maven 为大多数构建步骤编写并绑定了默认插件。当用户有特殊需要的时候，可以自己配置插件来定制构建行为，甚至可以使用自己编写的 Maven 插件。\u003c/p\u003e","title":"4、plugins","type":"posts"},{"content":" java.lang.String # 概述 # String：字符串，使用一对\u0026quot;\u0026quot;引起来表示。\nString声明为final的，不可被继承\nString实现了Serializable接口：表示字符串是支持序列化的。\n实现了Comparable接口：表示String可以比较大小\nString内部定义了final char[] value用于存储字符串数据\n通过字面量的方式（区别于new）给一个字符串赋值，此时的字符串值声明在字符串常量池中。\n字符串常量池中是不会存储相同内容（使用String类的equals()比较，返回true）的字符串的。\n常用方法 # int length()：返回字符串字符长度return value.length char charAt(int index)：返回索引处字符return value[index] boolean isEmpty()：判断是否为空字符串return value.length == 0 String toLowerCase()：使用默认语言环境，将String中所有字符转小写 String toUpperCase()：使用默认语言环境，将String中所有字符转大写 String trim()：清空字符串前后，Unicode编码小于等于\\u0020的字符并返回，例如回车、换行、制表、空格等 boolean equals(Object obj)：比较两个字符串内容是否相同 boolean equalsIgnoreCase(String str)：忽略大小写比较两个字符串是否相等 String concat(String str)：连接字符串，等价于+ int compareTo(String str)：比较字符串大小 String substring(int begin)：从begin处开始截取到最后一个字符，并返回 String substring(int begin,int end)：从begin处开始截取到end（不包含），并返回 boolean endsWith(String suffix)：判断字符串是否以suffix结束 boolean startsWith(String prefix)：判断字符串是否以prefix开始 boolean startsWith(String prefix, int toffset)：判断字符串从toffset开始是否以prefix开始 boolean contains(CharSequence s)：判断字符串是否包含指定char值序列 int indexOf(String str)：返回str第一次出现在字符串的开始位置索引，不存在返回-1 int indexOf(String str, int fromIndex)：从fromIndex开始搜索str，并返回第一次出现的开始位置索引，不存在返回-1 int lastIndexOf(Str)：反向搜索，返回str第一次出现在字符串的开始位置索引，不存在返回-1 int lastIndexOf(String str, int fromIndex)：从fromIndex开始反向搜索str，并返回第一次出现的开始位置索引，不存在返回-1 String replace(char oldChar,char newChar)：批量替换字符串中所有的字符oldChar为newChar，并返回新字符串 String replace(CharSequence oldChar,CharSequence newChar)：批量替换字符串中所有的字符序列oldChar为newChar，并返回新字符串 String replaceAll(String regex,String newStr)：批量替换字符串中所有匹配正则表达式regex的字符串为newStr，并返回新字符串 String replaceFirst(String regex,String newStr)：替换字符串中第一个匹配正则表达式regex的字符串为newStr，并返回新字符串 boolean matches(String regex)：返回字符串是否匹配正则表达式regex String[] split(String regex)：按照给定正则表达式匹配拆分字符串 String[] split(String regex, int limit)：按照给定正则表达式匹配拆分字符串，如果拆分大于limit，剩下的就全放到最后一个字符串 String类两种赋值方法的区别 # 一、通过字面量定义的方式=赋值\nString s1 = \u0026#34;abc\u0026#34;; String s2 = \u0026#34;abc\u0026#34;; System.out.println(s1 == s2); 输出 true =赋值，会将\u0026quot;abc\u0026quot;存入字符串常量池中，由于字符串常量池内不会存放重复的字符串，在常量池中指向同一个\u0026quot;abc\u0026quot;对象，所以s1和s2的地址信息一样。\n二、通过new + 构造器的方式\nString s1 = new String(\u0026#34;abc\u0026#34;); String s2 = new String(\u0026#34;abc\u0026#34;); System.out.println(s1 == s2); 输出 false 此类赋值方法，s1和s2指向堆空间中的不同的String类型的对象，而这两个不同的String类型对象的char[]，指向常量池中的同一个“abc”对象，所以，s1和s2的地址信息不一样。\nString s = new String(\u0026quot;abc\u0026quot;);方式创建对象，在内存中创建了几个空间？ 两个，一个是堆空间中char[]，另一个是char[]对应的常量池中的数据：\u0026quot;abc\u0026quot; 两个字符串类型对象连接，如果在编译期，可以就确定值的情况下，那么连接的结果会放入字符串常量池；只要有一个是变量，连接的结果不会进入字符串常量池。\nStringBuffer、StringBuilder类 # 三者的对比 # String : 不可变的字符序列；底层使用char[]存储 StringBuffer : 可变的字符序列；线程安全的，效率低；底层使用char[]存储 StringBuilder : 可变的字符序列；jdk5.0新增的，线程不安全，效率高；底层使用char[]存储 三者的执行效率对比 # 从高到低排列：StringBuilder \u0026gt; StringBuffer \u0026gt; String\n常用方法（StringBuffer、StringBuilder） # 增：append(xxx) 删：delete(int start,int end) 改：setCharAt(int n,char ch) / replace(int start,int end,String str) 查：charAt(int n ) 插：insert(int offset , xxx) 长度：length() 缓冲区长度：capacity() 遍历：for() + charAt() / toString() ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/409de243/96fce71d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003ejava.lang.String \n    \u003cdiv id=\"javalangstring\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#javalangstring\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e概述 \n    \u003cdiv id=\"概述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%a6%82%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eString：字符串，使用一对\u003ccode\u003e\u0026quot;\u0026quot;\u003c/code\u003e引起来表示。\u003c/p\u003e","title":"4、String","type":"posts"},{"content":" 程序、进程、线程的区别 # 程序(program) 概念：是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。 进程(process) 概念：程序的一次执行过程，或是正在运行的一个程序。 说明：进程作为资源分配的单位，系统在运行时会为每个进程分配不同的内存区域 线程(thread) 概念：进程可进一步细化为线程，是一个程序内部的一条单一的执行路径（顺序控制流），可以共享所属进程的数据。 说明：线程作为调度和执行的单位，每个线程拥独立的运行栈和程序计数器(pc)，线程切换的开销小。 多线程程序的优点 # 提高应用程序的响应。对图形化界面更有意义，可增强用户体验。\n提高计算机系统CPU的利用率\n改善程序结构。将既长又复杂的进程分为多个线程，独立运行，利于理解和修改\n何时需要多线程 # 程序需要同时执行两个或多个任务。 程序需要实现一些需要等待的任务时，如用户输入、文件读写操作、网络操作、搜索等。 需要一些后台运行的程序时。 并发和并行 # 并行：多个CPU同时执行多个任务。比如：多个人同时做不同的事。 并发：一个CPU(采用分时间片)执行多个任务。比如：秒杀、多个人做同一件事，同一个时间只能做一件事 创建多线程 # 一个Java应用程序java.exe，其实至少两个线程：\n主线程main()，必然存在 垃圾回收线程gc()，必然存在 异常处理线程，当然如果发生异常，会影响主线程，有可能存在 方式一：继承Thread类 # 创建一个继承于Thread类的子类 重写Thread类的run()：将此线程执行的操作声明在run()中 （线程要完成的任务） 创建Thread类的子类的对象 通过此对象调用start()：启动当前线程，调用当前线程的run() 注意点 我们启动一个线程，必须调用start()，不能调用run()的方式启动线程。\n如果再启动一个线程，必须重新创建一个Thread子类的对象，调用此对象的start().\n// 继承Thread static class MyThread extends Thread{ // 重写run @Override public void run() { while (true){ System.out.println(Thread.currentThread().getName()); } } } public static void main(String[] args) throws Exception { // 创建两个线程 MyThread t1 = new MyThread(); t1.start(); MyThread t2 = new MyThread(); t2.start(); } 方式二：实现Runnable接口 # 创建一个实现了Runnable接口的类 实现类去实现Runnable中的抽象方法：run() 创建实现类的对象 将此对象作为参数传递到Thread类的构造器中，创建Thread类的对象 通过Thread类的对象调用start() // 实现Runnable static class MyThread implements Runnable{ // 实现run @Override public void run() { while (true){ System.out.println(Thread.currentThread().getName()); } } } public static void main(String[] args) throws Exception { // 创建两个线程 Thread t1 = new Thread(new MyThread()); t1.start(); Thread t2 = new Thread(new MyThread()); t2.start(); } 继承Thread和实现Runable的对比 # 开发中：优先选择实现Runnable接口的方式\n原因：\n实现的方式没类的单继承性的局限性 实现的方式更适合来处理多个线程共享数据的情况。 联系：\npublic class Thread implements Runnable 相同点：\n两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。 目前两种方式，要想启动线程，都是调用的Thread类中的start()。 方式三：实现Callable接口 # 创建一个实现Callable的实现类 实现call方法，将此线程需要执行的操作声明在call()中 创建Callable接口实现类的对象 将此Callable接口实现类的对象作为传递到FutureTask构造器中，创建FutureTask的对象 将FutureTask的对象作为参数传递到Thread类的构造器中，创建Thread对象，并调用start() 获取Callable中call()方法的返回值：get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大？\ncall()可以有返回值的。 call()可以抛出异常，被外面的操作捕获，获取异常的信息 Callable是支持泛型的 缺点\n调用get()方法，会导致主线程阻塞 // 实现Runnable static class MyThread implements Callable\u0026lt;String\u0026gt; { @Override public String call() throws Exception { return Thread.currentThread().getName(); } } public static void main(String[] args) throws Exception { // 创建FutureTask FutureTask\u0026lt;String\u0026gt; futureTask = new FutureTask\u0026lt;\u0026gt;(new MyThread()); // 创建Thread Thread t1 = new Thread(futureTask); t1.start(); // 阻塞获取返回值 System.out.println(futureTask.get()); } Thread类 # java.lang.Thread 构造方法 # Thread()：创建新的Thread对象 Thread(String threadname)：创建线程并指定线程名 Thread(Runnable target)：指定创建线程的目标对象 Thread(Runnable target,String threadname)：指定创建线程的目标对象，并指定线程名 常用方法 # void start()：启动线程并执行run()方法\nrun()：线程被调度时执行的操作，不可主动调用！\nString getName()：返回线程名称\nstatic Thread currentThread()：返回当前线程\nstatic void yield()：线程让步，线程进入就绪状态\n暂停当前正在执行的线程，把执行机会让给优先级相同或更高的线程 若队列没有满足条件的线程，此方法会被忽略 join()：立刻执行调用此方法线程，其他线程将被阻塞直到调用方法的线程执行完毕，可以保证线程的串行化执行\n例如在线程a中，调用线程b的join()方法，线程a会进入阻塞，直到线程b执行完毕 static void sleep(long millis)：当前正在活动的线程在millis时间段内放弃CPU控制，时间到后重新排队，线程进入阻塞状态\nstop()：强行结束线程生命周期，不推荐使用\nboolean isAlive()：判断线程是否还活着\nint activeCount()：获取线程组中线程的数量\n获取和设置优先级 # getPriority()：获取线程的优先级\nsetPriority(int p)：设置线程的优先级\n说明：高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲，高优先级的线程高概率的情况下被执行。并不意味着只当高优先级的线程执行完以后，低优先级的线程才执行。\n守护线程和用户线程 # Java中的线程分为两类 守护线程 在调用start()前使用thread.setDaemon(ture)方法将一个用户线程变为守护线程。 它是在后台运行的，当所有用户线程结束时，守护线程也会随之结束。 用户线程 创建的线程默认为用户线程 用户线程是主线程的子线程，当主线程结束时，用户线程也会结束。 Thread userThread = new Thread(\u0026#34;User Thread\u0026#34;) { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }; Thread daemonThread = new Thread(\u0026#34;Daemon Thread\u0026#34;){ @Override public void run() { while (true){ System.out.println(Thread.currentThread().getName()); } } }; userThread.start(); // 设置为守护线程,即使自己有死循环，也会随着所有用户线程的结束而退出 daemonThread.setDaemon(true); daemonThread.start(); 线程的生命周期 # JDK中用Thread.State类定义了线程的几种状态\n要想实现多线程，必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程，在它的一个完整的生命周期中通常要经历如下的五种状态\n新建：当一个Thread类或子类对象被声明和创建时 就绪：处于新建状态的线程调用start()方法后，线程进入队列等待CPU时间片，此时已经具备了运行的条件，只是还没分配到CPU资源 运行：就绪的线程被调度，并且获得CPU资源，进行运行状态，执行run() 阻塞：被人为挂起或输入输出操作时，让出CPU并临时中止自己的执行 死亡：线程完成了全部工作或被强制性终止（例如出现异常） 线程同步 # 通过同步机制，来解决线程的安全问题。\n方式一：synchronized隐式锁 # 同步代码块 # synchronized(同步监视器){ //需要被同步的代码 } 一般用于同步操作共享数据（多个线程共同操作的变量）的代码 同步监视器（Object类型），俗称：锁。任何一个类的对象，都可以充当锁。要求**：多个线程必须要共用同一把锁（不变）**。 synchronized修饰的方法，无论方法正常执行完毕还是抛出异常，都会释放锁 一把锁只能同时被一个线程获取，没有获得锁的线程只能阻塞等待 同步方法 # public synchronized void method(){} 如果操作共享数据的代码完整的声明在一个方法中，我们不妨将此方法声明同步的。\n同步方法仍然涉及到同步监视器，只是不需要我们显式的声明。\n非静态的同步方法，同步监视器是：this 静态的同步方法，同步监视器是：当前类.class 可重入锁（递归锁） # 所谓重入锁，指的是以线程为单位，当一个线程获取对象锁之后，这个线程内部可以再次获取本对象上的锁，而其他的线程是不可以的。\n可以重复使用的锁，锁的创建只有一次，可以重复调用lock()和unlock()\nsynchronized和ReentrantLock都是可重入锁。\npublic class Demo { public static void main(String[] args) { Thread t = new Thread(\u0026#34;A\u0026#34;) { @Override public void run() { // 线程第一次获取锁 synchronized (Demo.class){ for (int i = 0; i \u0026lt; 10000; i++) { // 可重入，再次获取锁 synchronized (Demo.class){ System.out.println(Thread.currentThread().getName()); } } } } }; t.start(); } } 方式二：Lock显式锁 # 常用方法 # void lock()：获取锁，如果锁不可用，则出于线程调度的目的，当前线程将被禁用，并且在获取锁之前处于休眠状态。 boolean tryLock()：如果锁可用立即返回true，如果锁不可用立即返回false； boolean tryLock(long time, TimeUnit unit) throws InterruptedException：如果锁可用，则此方法立即返回true。 如果该锁不可用，则当前线程将出于线程调度目的而被禁用并处于休眠状态，直到发生以下三种情况之一为止 当前线程获取到该锁 当前线程被其他线程中断，并且支持中断获取锁 经过指定的等待时间如果获得了锁，则返回true，没获取到锁返回false。 void unlock()：释放锁。释放锁的操作放在finally块中进行，以保证锁一定被被释放，防止死锁的发生。 ReentrantLock（可重入锁） # 实现Lock接口，是一种可重入锁\npublic class Demo { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(true); Thread t = new Thread(\u0026#34;A\u0026#34;) { @Override public void run() { try { // 线程第一次获取锁 lock.lock(); for (int i = 0; i \u0026lt; 10000; i++) { // 可重入，再次获取锁 lock.lock(); System.out.println(Thread.currentThread().getName()); } }finally { // 释放锁 lock.unlock(); } } }; t.start(); } } ReentrantReadWriteLock（读写锁） # writeLock()：获取写锁 readLock()：获取读锁 两个线程都是写锁：互斥，同步执行\n两个线程一写一读：互斥，同步执行\n两个线程都是读锁：共享，异步执行\n公平锁和非公平锁 # 公平锁的效率比非公平锁低\n公平锁总是可以保证让所有线程中等待时间最长的线程先执行\n在new ReentrantLock(true)或new ReentrantReadWriteLock(true)时，参数为true，创建的就是公平锁；不传参默认为false，就是非公平锁\nsynchronized与Lock的异同 # 相同： 二者都可以解决线程安全问题 不同： synchronized是Java语言的关键字；而Lock是一个接口。 synchronized机制在执行完相应的同步代码以后，自动的释放同步监视器；而Lock需要手动的启动同步lock()，同时结束同步也需要手动的实现unlock()，如果没有主动释放锁，就有可能导致出现死锁现象。 Lock可以配置公平策略，实现线程按照先后顺序获取锁；而synchronized不可以。 Lock提供了trylock()方法可以试图获取锁，获取到或获取不到时，返回不同的返回值让程序可以灵活处理。 lock()和unlock()可以在不同的方法中执行，程序更加灵活。 死锁问题 # 不同的线程分别占用对方需要的同步资源不放弃，都在等待对方放弃自己需要的同步资源，就形成了线程的死锁\n说明：\n出现死锁后，不会出现异常，不会出现提示，只是所的线程都处于阻塞状态，无法继续 我们使用同步时，要避免出现死锁。 解决死锁 # 1、调整锁的顺序，避免可能出现的死锁\n2、调整锁的范围，避免在一个同步代码块中使用另一个同步代码块\n3、使用可重入锁\n判断锁会不会释放 # 会释放 线程的同步方法或同步代码块执行结束 线程的同步方法或同步代码块中遇到break、return终止了代码块中的循环或方法的执行 线程的同步方法或同步代码块出现Error或Exception未处理 线程的同步代码块、同步方法执行了wait()造成线程阻塞并释放锁 不会释放 线程的同步方法或同步代码块中执行Thread.sleep()或Thread.yield() 调用了suspend()将线程挂起（避免使用suspend()或resume()控制线程） 线程通信 # 使用synchronized强同步 # wait()：一旦执行此方法，当前线程就进入阻塞状态，并释放同步监视器。 notify()：一旦执行此方法，就会唤醒被wait的一个线程。如果有多个线程被wait，就唤醒优先级高的那个。 notifyAll()：一旦执行此方法，就会唤醒所有被wait的线程。 wait()，notify()，notifyAll()三个方法必须使用在同步代码块或同步方法中。 wait()，notify()，notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则，会出现IllegalMonitorStateException异常 wait()，notify()，notifyAll()三个方法是定义在java.lang.Object类中。 注意：wait方法等待的线程，在哪等待，被唤醒后，就从哪里开始执行，可能会出现虚假唤醒的问题，所以建议wait循环使用在while循环中\n使用Lock接口下的可重入锁 # 获取Condition对象Condition condition = lock.newCondition(); 使用Condition对象的方法 await()：当前线程等待，对应wait() signal()：唤醒一个线程，对应notify() signalAll()：唤醒所有线程，对应notifyAll() sleep()和wait()的异同 # 相同点 一旦执行方法，都可以使得当前的线程进入阻塞状态。 不同点 两个方法声明的位置不同：Thread类中声明sleep() , Object类中声明wait() 调用的要求不同：sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中 关于是否释放同步监视器 如果两个方法都使用在同步代码块或同步方法中，sleep()不会释放锁，wait()会释放锁。 ThreadLocal类 # 可以在指定线程内存储数据，数据存储以后，只有指定线程可以得到存储数据（可以存放线程范围内的局部变量）\n实际使用场景 # 在JavaWeb项目里面，我们做MyBatis的工具类的时候，为了可以满足多个线程中，各线程只关闭自己的SqlSession对象的需求，而不会产生线程安全的问题，所以，我们需要将SqlSession对象存入到ThreadLocal对象中\n常用方法 # set()用于向ThreadLocal对象中存值 get()用于向ThreadLocal对象中取值 remove()用于从ThreadLocal对象中删除值 CAS自旋锁 # 解决的问题 # 例如，一个int类型的变量，在高并发的情况下进行加减，通常会导致线程不安全，数值不准确的问题，例如\npublic class Demo { static int count = 0; public static void main(String[] args) { // 创建100个线程 for (int i1 = 0; i1 \u0026lt; 100; i1++) { new Thread(new Runnable() { @Override public void run() { for (int i = 0; i \u0026lt; 100; i++) { count++; } } }).start(); } // 确保其他线程都执行完成了，除了GC和主线程 while (Thread.activeCount() \u0026gt; 2){ Thread.yield(); } // 结果打印出来的数据不一定是正确的10000，结果比10000少 System.out.println(count); } } 原因分析 # 由于加减操作例如count++、count = count - 1并不是一个原子操作，在执行的时候分了三步：1、读取值；2、修改值；3、写入值。所以例如现在有两个线程A和B，原子性操作如下：\n线程A：读取值为0 线程B：开始执行 线程B：读取值为0 线程B：修改读到的值为1 线程B：将值1写入变量count 线程A：开始执行 线程A：修改读到的值为1 线程A：将值1写入到变量count 所以造成了变量count的值经过两次加1，应该为2；但是实际上值为1\n解决方式 # 1、使用synchronized同步锁的方式 # 缺点是：每次进行非原子操作前，都需要加锁，操作完解锁，效率低下\npublic class Demo { static int count = 0; public static void main(String[] args) { for (int i1 = 0; i1 \u0026lt; 100; i1++) { new Thread(new Runnable() { @Override public void run() { for (int i = 0; i \u0026lt; 100; i++) { // 使用同步代码块将非原子操作同步 synchronized (Demo.class){ count++; } } } }).start(); } while (Thread.activeCount() \u0026gt; 2){ Thread.yield(); } System.out.println(count); } } 2、通过CAS自旋锁的方式进行 # 通过AtomicInteger，可以在线程提交的时候，用自己读到的数值，与现在变量中的数值进行比对。如果一致的话，就写入变量；如果数值已经发生改变的话，那么会进行自旋，也就是重新进行运算，再进行比较\npublic class Demo { static AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) { for (int i1 = 0; i1 \u0026lt; 100; i1++) { new Thread(new Runnable() { @Override public void run() { for (int i = 0; i \u0026lt; 100; i++) { //读取数据并自增，等同于count++ count.getAndIncrement(); } } }).start(); } while (Thread.activeCount() \u0026gt; 2){ Thread.yield(); } System.out.println(count); } } 解决ABA问题 # 但是，自旋锁的方式，有可能会产生ABA的问题，例如现在有三个线程A、B、C，操作流程如下\n线程A：读取值为0 线程B：开始执行 线程B：读取值为0 线程A：开始执行 线程A：+ 1；验证变量值为0，一致；写入变量；变量值为1 线程C：开始执行 线程C：读取值为1；- 1；验证变量为1，一致；写入变量，变量值为0 线程B：开始执行 线程B：+ 1；验证变量值和自己读取的都为0，一致，写入变量；变量值为1 这样看下来，虽然变量值和预期的值相同，但是中间变量其实已经修改过了，线程B比较的0已经不是自己当时读到的0了，在某些情况下，可能会出现问题\n在CAS的思想上，利用版本戳的思想，从原始数据开始，给每次的数据都加上一个版本，每次对数据发生修改，版本也会进行迭代\n具体的实现类，AtomicStampedReference，这个类在实例化时，第二个参数为版本号\npublic class Demo { // 第一个参数为保存的数据，第二个参数为版本戳 static AtomicStampedReference\u0026lt;Integer\u0026gt; count = new AtomicStampedReference(0,0); public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { for (int i = 0; i \u0026lt; 100; i++) { // 比较并且设置 // 1：比较的原始值，2：设置的新参数值 // 3、比较的原始版本戳，4、设置的新版本戳 count.compareAndSet( count.getReference() , count.getReference() + 1 , count.getStamp() , count.getStamp() + 1 ); } } }).start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i \u0026lt; 100; i++) { count.compareAndSet( count.getReference() , count.getReference() - 1 , count.getStamp() , count.getStamp() + 1 ); } } }).start(); while (Thread.activeCount() \u0026gt; 2){ Thread.yield(); } System.out.println(count.getReference()); System.out.println(count.getStamp()); } } 线程池 # 背景：经常创建和销毁、使用量特别大的资源，比如并发情况下的线程， 对性能影响很大。\n思路：提前创建好多个线程，放入线程池中，使用时直接获取，使用完放回池中。可以避免频繁创建销毁、实现重复利用\n好处：\n提高响应速度（减少了创建新线程的时间） 降低资源消耗（重复利用线程池中线程，不需要每次都创建） 便于线程管理 相关API\nJDK 5.0起提供了线程池相关API：ExecutorService和Executors 线程池所在的包：java.util.concurrent 线程池的执行顺序 # 1、用户提交任务，判断核心线程是否正在处理，如果核心线程有空闲，直接使用核心线程执行\n2、如果核心线程没有空闲，判断队列是否满了，如果没有满，就把任务放进队列中\n3、如果队列已经满了，那么判断线程池中线程+新任务线程是否大于最大线程数，如果大于，抛出异常，拒绝执行\n4、如果小于等于最大线程数，创建新的线程，执行任务\n线程池的状态 # 状态 说明 RUNNING 允许提交并处理任务 SHUTDOWN 不允许提交新的任务，但是会处理完已提交的任务 STOP 不允许提交新的任务，也不会处理阻塞队列中未执行的任务，并设置正在执行的线程的中断标志位 TIDYING 所有任务执行完毕，池中工作的线程数为0，等待执行terminated()勾子方法 TERMINATED terminated()勾子方法执行完毕 线程池的shutdown()方法，将线程池由 RUNNING（运行状态）转换为 SHUTDOWN状态 线程池的shutdownNow()方法，将线程池由RUNNING 或 SHUTDOWN 状态转换为 STOP 状态。 SHUTDOWN 状态 和 STOP 状态 先会转变为 TIDYING 状态，最终都会变为 TERMINATED\n线程池的创建 # 1、ThreadPoolExecutor(建议) # /* 创建线程池 * 参数1：线程池创建后，核心线程数 * 参数2：最大线程数 * 参数3：核心线程外，新创建的线程空闲时，最大存活时间 * 参数4：最大存活时间的单位 * 参数5：用于存放任务的阻塞队列 * 参数6：用于创建线程的线程工厂 * 参数7：任务被拒绝后的策略 */ ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 2 , 5 , 60 , TimeUnit.SECONDS , new ArrayBlockingQueue\u0026lt;\u0026gt;(5) , new ThreadPoolExecutor.AbortPolicy() ); 拒绝策略 # 策略 描述 ThreadPoolExecutor.AbortPolicy() 抛出RejectedExecutionException异常。默认策略 ThreadPoolExecutor.CallerRunsPolicy() 由向线程池提交任务的线程来执行该任务 ThreadPoolExecutor.DiscardPolicy() 抛弃当前的任务 ThreadPoolExecutor.DiscardOldestPolicy() 抛弃最旧的任务（最先提交而没有得到执行的任务） 线程池中，提交任务submit()与execute()之间的区别 # execute()是专门用来提交Runnable接口任务的；submit()既可以提交Runable任务也可以提交Callable任务\nexecute()没有返回结果；submit()可以使用Future接收线程的返回结果\nexecute()方法中不能处理任何异常；submit()支持处理任务中的异常，使用Funture.get()\n2、Executors # 可以使用Executors的静态方法，来创建线程池\n创建方法 线程池 阻塞队列 使用场景 newSingleThreadExecutor() 单线程的线程池 LinkedBlockingQueue 适用于串行执行任务的场景，⼀个任务⼀个任务地执行 newFixedThreadPool(n) 固定数目线程的线程池 LinkedBlockingQueue 适用于处理CPU密集型的任务，适用执行长期的任务 newCachedThreadPool() 可缓存线程的线程池 SynchronousQueue 适用于并发执行大量短期的小任务 newScheduledThreadPool(n) 定时、周期执行的线程池 DelayedWorkQueue 周期性执行任务的场景，需要限制线程数量的场景 例如 # public static void main(String[] args) { // 创建一个固定大小的线程池: ExecutorService es = Executors.newFixedThreadPool(4); Future\u0026lt;Integer\u0026gt; future = es.submit(new Task(\u0026#34;a\u0026#34;)); System.out.println(future); // 关闭线程池: es.shutdown(); } 阿里巴巴Java开发手册规定：线程池不允许使用Executors去创建，要求通过new ThreadPoolExecutor()的方式，这样的处理方式让写的同学更加明确线程池的运行规则，规避资源耗尽的风险。\n","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/fbeea4f4/7b8f2760/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e程序、进程、线程的区别 \n    \u003cdiv id=\"程序进程线程的区别\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%a8%8b%e5%ba%8f%e8%bf%9b%e7%a8%8b%e7%ba%bf%e7%a8%8b%e7%9a%84%e5%8c%ba%e5%88%ab\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e程序(program)\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e概念：是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e进程(process)\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e概念：\u003cstrong\u003e程序的一次执行过程，或是正在运行的一个程序\u003c/strong\u003e。\u003c/li\u003e\n\u003cli\u003e说明：进程作为资源分配的单位，系统在运行时会为每个进程分配不同的内存区域\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e线程(thread)\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e概念：\u003cstrong\u003e进程可进一步细化为线程，是一个程序内部的一条单一的执行路径（顺序控制流），可以共享所属进程的数据\u003c/strong\u003e。\u003c/li\u003e\n\u003cli\u003e说明：线程作为调度和执行的单位，每个线程拥独立的运行栈和程序计数器(pc)，线程切换的开销小。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e多线程程序的优点 \n    \u003cdiv id=\"多线程程序的优点\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%a4%9a%e7%ba%bf%e7%a8%8b%e7%a8%8b%e5%ba%8f%e7%9a%84%e4%bc%98%e7%82%b9\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e提高应用程序的响应。对图形化界面更有意义，可增强用户体验。\u003c/p\u003e","title":"4、多线程","type":"posts"},{"content":" 基础概念 # 程序运行时产生的数据都属于临时数据，程序一旦运行结束都会被释放\n通过文件可以将数据持久化\nC++中对文件操作需要包含头文件 \u0026lt;fstream\u0026gt; 文件类型分为两种：\n文本文件：文件以文本的ASCII码形式存储在计算机中 二进制文件：文件以文本的二进制形式存储在计算机中，用户一般不能直接读懂它们 操作文件的三大类:\nofstream：写操作 ifstream： 读操作 fstream ： 读写操作 文件的打开方式 # 打开方式 解释 ios::in 为读文件而打开文件 ios::out 为写文件而打开文件 ios::ate 初始位置：文件尾 ios::app 追加方式写文件 ios::trunc 如果文件存在先删除，再创建 ios::binary 二进制方式 注意： 文件打开方式可以配合使用，利用|操作符\n例如：用二进制方式写文件 ios::binary | ios:: out\n文本文件 # 写文件 # 写文件步骤如下：\n包含头文件：#include \u0026lt;fstream\\\u0026gt;\n创建流对象：ofstream ofs;\n打开文件：ofs.open(\u0026quot;文件路径\u0026quot;,打开方式);\n写数据：ofs \u0026lt;\u0026lt; \u0026quot;写入的数据\u0026quot;;\n关闭文件：ofs.close();\n例子：\n#include\u0026lt;iostream\u0026gt; //1、添加文件操作头文件 #include\u0026lt;fstream\u0026gt; using namespace std; int main() { //2、创建输出流对象对象 ofstream ofs; //3、打开文件text.txt，文件默认创建在项目根目录，指定打开方式为ios::out ofs.open(\u0026#34;text.txt\u0026#34;, ios::out); //4、写入信息，并进行换行 ofs \u0026lt;\u0026lt; \u0026#34;Hello World！\u0026#34; \u0026lt;\u0026lt; endl; ofs \u0026lt;\u0026lt; \u0026#34;姓名：Lucy\u0026#34; \u0026lt;\u0026lt; endl; //5、关闭文件 ofs.close(); return 0; } 读文件 # 读文件步骤如下：\n包含头文件：#include \u0026lt;fstream\u0026gt;\n创建流对象：ifstream ifs;\n打开文件并判断文件是否打开成功：ifs.open(\u0026quot;文件路径\u0026quot;,打开方式);，ifs.is_open();\n读数据，四种方式读取\n关闭文件：ifs.close();\n例子：\n#include\u0026lt;iostream\u0026gt; //1、添加文件操作头文件 #include\u0026lt;fstream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; int main() { //3、创建输入流对象 ifstream ifs; //4、打开文件，项目根目录的text.txt，并且指定打开方式为ios::in ifs.open(\u0026#34;text.txt\u0026#34;, ios::in); //5、判断是否打开成功 bool isOpen = ifs.is_open(); if (!isOpen) { cout \u0026lt;\u0026lt; \u0026#34;文件打开失败！\u0026#34; \u0026lt;\u0026lt; endl; return 1; } //6、读数据 //方式一、利用字符数组做缓冲区读 //char buf[1024] = { 0 }; //while (ifs \u0026gt;\u0026gt; buf){ //\tcout \u0026lt;\u0026lt; buf \u0026lt;\u0026lt; endl; //} //方式二、利用getline()函数，逐行读取到缓冲区数组 //char buf[1024] = { 0 }; //while (ifs.getline(buf,sizeof(buf))){ //\tcout \u0026lt;\u0026lt; buf \u0026lt;\u0026lt; endl; //} //方式三、利用string做缓冲区，使用全局函数getline进行读取 string buf; while (getline(ifs, buf)) { cout \u0026lt;\u0026lt; buf \u0026lt;\u0026lt; endl; } //方式四、使用get()函数，一个一个字符读取，直到文件尾(不推荐，效率低) //char c; //while ((c = ifs.get()) != EOF) { //EOF：end of file文件尾 //\tcout \u0026lt;\u0026lt; c; //此时不要自己换行 //} //7、关闭文件 ifs.close(); return 0; } 二进制文件 # 以二进制的方式对文件进行读写操作\n打开方式要指定为：ios::binary\n写文件 # 二进制方式写文件主要利用流对象调用成员函数write\n函数原型 ：ostream\u0026amp; write(const char * buffer,int len);\n参数解释：字符指针buffer指向内存中一段存储空间。len是读写的字节数\n例子：\n#include\u0026lt;iostream\u0026gt; //1、引入文件操作头文件 #include\u0026lt;fstream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; class Person { public: //因为要使用二进制写入文件，所以最好不要使用cpp的string，使用c的方法就可以了 char name[64]; int age; }; int main() { //2、创建输出流对象，打开文件也可以写在构造函数里面 ofstream ofs(\u0026#34;person.txt\u0026#34;, ios::out | ios::binary); //3、打开文件，打开方式：二进制|写 //ofs.open(\u0026#34;person.txt\u0026#34;, ios::out | ios::binary); //4、写文件 Person person = { \u0026#34;张三\u0026#34; ,18 }; ofs.write((const char*)\u0026amp;person, sizeof(Person)); //5、关闭文件 ofs.close(); return 0; } 读文件 # 二进制方式读文件主要利用流对象调用成员函数read\n函数原型：istream\u0026amp; read(char *buffer,int len);\n参数解释：字符指针buffer指向内存中一段存储空间，len是读写的字节数\n例子：\n#include\u0026lt;iostream\u0026gt; //1、引入文件操作头文件 #include\u0026lt;fstream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; class Person { public: char name[64]; int age; }; int main() { //2、创建输入流对象，打开文件也可以写在构造函数里面 ifstream ifs(\u0026#34;person.txt\u0026#34;, ios::in | ios::binary); //3、打开文件，打开方式：二进制|写 //ofs.open(\u0026#34;person.txt\u0026#34;, ios::out | ios::binary); //4、判断文件是否打开成功 if (!ifs.is_open()) { cout \u0026lt;\u0026lt; \u0026#34;文件不存在\u0026#34; \u0026lt;\u0026lt; endl; return 1; } //5、读取文件 Person person; ifs.read((char*)\u0026amp;person, sizeof(Person)); //将文件读取到person对象中 cout \u0026lt;\u0026lt; \u0026#34;name：\u0026#34; \u0026lt;\u0026lt; person.name \u0026lt;\u0026lt; \u0026#34;，年龄：\u0026#34; \u0026lt;\u0026lt; person.age \u0026lt;\u0026lt; endl; //6、关闭文件 ifs.close(); return 0; } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/d8245fe1/f50d4583/8cb8fd73/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e基础概念 \n    \u003cdiv id=\"基础概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%9f%ba%e7%a1%80%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e程序运行时产生的数据都属于临时数据，程序一旦运行结束都会被释放\u003c/p\u003e","title":"4、文件操作","type":"posts"},{"content":" 输出 # 在C语言中，有三个函数可以用来在显示器上输出数据，它们分别是：\nputs()：只能输出字符串，并且输出结束后会自动换行。 putchar()：只能输出单个字符。 printf()：可以输出各种类型的数据。 printf() 是最灵活、最复杂、最常用的输出函数，完全可以替代 puts() 和 putchar()\n格式控制符 # 格式控制符 说明 %c 输出一个单一的字符 %hd、%d、%ld 以十进制、有符号的形式输出 short、int、long 类型的整数 %hu、%u、%lu 以十进制、无符号的形式输出 short、int、long 类型的整数 %ho、%o、%lo 以八进制、不带前缀、无符号的形式输出 short、int、long 类型的整数 %#ho、%#o、%#lo 以八进制、带前缀、无符号的形式输出 short、int、long 类型的整数 %hx、%x、%lx %hX、%X、%lX 以十六进制、不带前缀、无符号的形式输出 short、int、long 类型的整数。如果 x 小写，那么输出的十六进制数字也小写；如果 X 大写，那么输出的十六进制数字也大写。 %#hx、%#x、%#lx %#hX、%#X、%#lX 以十六进制、带前缀、无符号的形式输出 short、int、long 类型的整数。如果 x 小写，那么输出的十六进制数字和前缀都小写；如果 X 大写，那么输出的十六进制数字和前缀都大写。 %f、%lf 以十进制的形式输出 float、double 类型的小数 %e、%le %E、%lE 以指数的形式输出 float、double 类型的小数。如果 e 小写，那么输出结果中的 e 也小写；如果 E 大写，那么输出结果中的 E 也大写。 %g、%lg %G、%lG 以十进制和指数中较短的形式输出 float、double 类型的小数，并且小数部分的最后不会添加多余的 0。如果 g 小写，那么当以指数形式输出时 e 也小写；如果 G 大写，那么当以指数形式输出时 E 也大写。 %s 输出一个字符串 printf # %[flag][width][.precision]type [ ] 表示此处的内容可有可无，是可以省略的 type 表示输出类型，比如 %d、%f、%c、%lf，type 就分别对应 d、f、c、lf，type 这一项必须有 width 表示最小输出宽度，也就是至少占用几个字符的位置，例如，%-9d中 width 对应 9 .precision 表示输出精度，也就是小数的位数 当小数部分的位数大于 precision 时，会按照四舍五入的原则丢掉多余的数字； 当小数部分的位数小于 precision 时，会在后面补 0。 flag 是标志字符。例如，%#x中 flag 对应 #，%-9d中 flags 对应- 标志字符 # 标志字符 含 义 - -表示左对齐。如果没有，就按照默认的对齐方式，默认一般为右对齐。 + 用于整数或者小数，表示输出符号（正负号）。如果没有，那么只有负数才会输出符号。 空格 用于整数或者小数，输出值为正时冠以空格，为负时冠以负号。 # 对于八进制（%o）和十六进制（%x / %X）整数，# 表示在输出时添加前缀；八进制的前缀是 0，十六进制的前缀是 0x / 0X。对于小数（%f / %e / %g），# 表示强迫输出小数点。如果没有小数部分，默认是不输出小数点的，加上 # 以后，即使没有小数部分也会带上小数点。 矩形输出 # #include\u0026lt;stdio.h\u0026gt; int main(int argc, char *argv[] ){ int a1 = 10,a2 = 2000,a3 = 14; int b1 = 2345,b2 = 21,b3 = 21341; int c1 = 123,c2 = 12341,c3 = 2134; printf(\u0026#34;%-10d%-10d%-10d\\n\u0026#34;,a1,a2,a3); printf(\u0026#34;%-10d%-10d%-10d\\n\u0026#34;,b1,b2,b3); printf(\u0026#34;%-10d%-10d%-10d\\n\u0026#34;,c1,c2,c3); } 10 2000 14 2345 21 21341 123 12341 2134 输入 # 在C语言中，有多个函数可以从键盘获得用户输入：\nscanf()：和 printf() 类似，scanf() 可以输入多种类型的数据。 getchar()、getche()、getch()：这三个函数都用于输入单个字符。 gets()：获取一行数据，并作为字符串处理。 scanf() 是最灵活、最复杂、最常用的输入函数，但它不能完全取代其他函数\n格式控制符 # 格式控制符 说明 %c 读取一个单一的字符 %hd、%d、%ld 读取一个十进制整数，并分别赋值给 short、int、long 类型 %ho、%o、%lo 读取一个八进制整数（可带前缀也可不带），并分别赋值给 short、int、long 类型 %hx、%x、%lx 读取一个十六进制整数（可带前缀也可不带），并分别赋值给 short、int、long 类型 %hu、%u、%lu 读取一个无符号整数，并分别赋值给 unsigned short、unsigned int、unsigned long 类型 %f、%lf 读取一个十进制形式的小数，并分别赋值给 float、double 类型 %e、%le 读取一个指数形式的小数，并分别赋值给 float、double 类型 %g、%lg 既可以读取一个十进制形式的小数，也可以读取一个指数形式的小数，并分别赋值给 float、double 类型 %s 读取一个字符串（以空白符为结束） scanf # #include\u0026lt;stdio.h\u0026gt; int main(int argc, char *argv[] ){ int a; printf(\u0026#34;input one integer:\u0026#34;); //scanf 的变量前要带一个\u0026amp;符号。\u0026amp;称为取地址符，也就是获取变量在内存中的地址。 scanf(\u0026#34;%d\u0026#34;,\u0026amp;a); printf(\u0026#34;the integer is:%d\u0026#34;,a); return 0; } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/f571508d/fa8095d0/4e137017/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e输出 \n    \u003cdiv id=\"输出\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%be%93%e5%87%ba\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在C语言中，有三个函数可以用来在显示器上输出数据，它们分别是：\u003c/p\u003e","title":"4、输入和输出","type":"posts"},{"content":"Python的hashlib提供了常见的摘要算法，如MD5，SHA1等等。\n什么是摘要算法呢？摘要算法又称哈希算法、散列算法。它通过一个函数，把任意长度的数据转换为一个长度固定的数据串（通常用16进制的字符串表示。\n摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest，目的是为了发现原始数据是否被人篡改过。\n摘要算法之所以能指出数据是否被篡改过，就是因为摘要函数是一个单向函数，计算f(data)很容易，但通过digest反推data却非常困难。而且，对原始数据做一个bit的修改，都会导致计算出的摘要完全不同。\nhash算法就像一座工厂，工厂接收你送来的原材料（可以用m.update()为工厂运送原材料），经过加工返回的产品就是hash值\nMD5 # from base64 import encode import hashlib # 哈希工厂 md5 = hashlib.md5() # 运送原材料（源数据）,update方法内必须传bytes md5.update(\u0026#39;how to use md5 in python hashlib?\u0026#39;.encode(\u0026#39;utf8\u0026#39;)) # 生成哈希值 print(md5.hexdigest()) \u0026#39;\u0026#39;\u0026#39; d26a53750bc40b38b65a520292f69306 \u0026#39;\u0026#39;\u0026#39; 加盐 # 加盐：额外给原始数据添加一点自定义的数据，使得生成的消息摘要不同于普通方式计算的摘要。\nfrom base64 import encode import hashlib # 哈希工厂 md5 = hashlib.md5() # 运送原材料（源数据）,update方法内必须传bytes md5.update(\u0026#39;how to use md5 in python hashlib?\u0026#39;.encode(\u0026#39;utf8\u0026#39;)) #加盐，盐值：yhgh md5.update(\u0026#39;yhgh\u0026#39;.encode(\u0026#39;utf8\u0026#39;)) # 生成哈希值 print(md5.hexdigest()) \u0026#39;\u0026#39;\u0026#39; e440ce3b2c8aee626688775985901e95 \u0026#39;\u0026#39;\u0026#39; 验证文件一致性 # 通过两个文件的hash值，可以判断文件是否一致\nimport hashlib def getFileHash(path): \u0026#34;获取文件的hash值\u0026#34; with open(path,\u0026#39;rb\u0026#39;) as f: md5 = hashlib.md5() md5.update(f.read()) return md5.hexdigest() h1 = getFileHash(\u0026#34;./1.txt\u0026#34;) h2 = getFileHash(\u0026#34;./2.txt\u0026#34;) print(h1 == h2) ","date":"2024-01-08","externalUrl":null,"permalink":"/posts/1b37041b/9766939c/67f0df16/","section":"文章","summary":"\u003cp\u003ePython的\u003ccode\u003ehashlib\u003c/code\u003e提供了常见的摘要算法，如MD5，SHA1等等。\u003c/p\u003e","title":"4、hashlib","type":"posts"},{"content":" 持久化 # 优势Redis是基于缓存的存储，所以如果服务器宕机，会导致数据丢失，所以如果需要保存在磁盘上，需要使用持久化方案，Redis提供两种的持久化方案，RDB和AOF，默认情况下，开启了RDB方案，关闭AOF方案\nRDB（Redis Database） # 也叫做快照模式，只会保存在数据库的最后的结果，并不会保存过程\n手动触发 # save命令 # 127.0.0.1:6379\u0026gt; keys * (empty list or set) 127.0.0.1:6379\u0026gt; set a a OK 127.0.0.1:6379\u0026gt; set b b OK 127.0.0.1:6379\u0026gt; set c c OK 127.0.0.1:6379\u0026gt; save OK 127.0.0.1:6379\u0026gt; 此命令是阻塞式命令，是同步的。执行数据库的备份，把数据库的数据全部存储到一个dump.rdb文件中。并且服务器在每次启动的时候，都从当前配置文件中，检测rdb和aof文件是否存在，如果存在，则使用rdb和aof文件中的内容，做数据恢复。\nbgsave命令 # 127.0.0.1:6379\u0026gt; bgsave Background saving started 127.0.0.1:6379\u0026gt; bgsave属于异步非阻塞式命令，后台执行。不阻塞。不影响其他命令的执行。\n自动触发 # RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘，实际操作过程是fork一个子进程，先将数据集写入临时文件，写入成功后，再替换之前的文件，用二进制压缩存储\n具体实现 # 将内存中的数据以快照的方式写入二进制文件中，默认的文件名是dump.rdb\n1、修改redis.conf配置文件\n此处表示，如果在60秒内，有100次的修改的话，就会触发RDB\n2、需要在运行Redis时，同时加载配置文件，window系统下的做法，创建redis-server.exe的快捷方式，并在启动路径后追加redis.windows.conf\n但是，可能会因此丢失部分数据，在上一次save和这一次save之间的数据，这一次的save还没有发生，此时宕机，则会丢失区间数据。\nAOF（Append Only File） # 采用的是一种日志追加的方式来记录Redis相关操作，AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作，查询操作不会记录，以文本的方式记录，可以打开文件看到详细的操作记录\n具体实现 # 修改redis.conf配置文件，yes为开启\n事务 # Redis中事务仅仅保证了隔离性（串行化），保证了一段事务在开启以后，不会被其他的事务所打扰，例如一致性，Redis就无法保证，同一个事务中，出现异常的命令将不会执行，但是其他的正常的命令会执行。\n命令 # 命令 描述 multi 开始事务用于标记事务块的开始。Redis会将后续的命令逐个放入队列中，然后才能使用EXEC命令原子化地执行这个命令序列 exec 执行事务在一个事务中执行所有先前放入队列的命令，然后恢复正常的连接状态。当使用WATCH命令时，只有当受监控的键没有被修改时，EXEC命令才会执行事务中的命令，这种方式利用了检查再设置（CAS）的机制。这个命令的返回值是一个数组，其中的每个元素分别是原子化事务中的每个命令的返回值。 当使用WATCH命令时，如果事务执行中止，那么EXEC命令就会返回一个Null值 discard 回滚清除所有先前在一个事务中放入队列的命令，然后恢复正常的连接状态。如果使用了WATCH命令，那么DISCARD命令就会将当前连接监控的所有键取消监控 watch 监控指定的key当某个事务需要按条件执行时，就要使用这个命令将给定的键设置为受监控的 unwatch 撤销监控 Redis的Watch # watch命令，需要与事务进行配合，在一个用户修改数据之前，如果watch key这个数据，那么如果在这个事务没有提交之前，有其他的用户进行修改此数据的话，那么当前用户的该事务，将被全部丢弃，当前，可以通过逻辑，做到自旋\n使用watch+自旋，解决秒杀超卖的问题 # public class Test4 { public static void main(String[] args) throws InterruptedException { Jedis jedis = new Jedis(); System.out.println(jedis.ping()); //查询数据库 while(true) { Integer count = Integer.parseInt(jedis.get(\u0026#34;count\u0026#34;));//查询数据库的库存 //做数据操作之前，必须要watch住,才能保证你做这个数据的时候，没有人进行修改 jedis.watch(\u0026#34;count\u0026#34;); Transaction multi = jedis.multi();//事务的开启 multi.set(\u0026#34;count\u0026#34;, (count - 1) + \u0026#34;\u0026#34;); Thread.sleep(20000); List\u0026lt;Object\u0026gt; exec = multi.exec(); if(exec != null){ System.out.println(\u0026#34;购买成功\u0026#34;); return; }else{ System.out.println(\u0026#34;购买失败，自旋\u0026#34;); } } } } ","date":"2024-01-04","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/caadfbf9/99f71637/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e持久化 \n    \u003cdiv id=\"持久化\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8c%81%e4%b9%85%e5%8c%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e优势Redis是基于缓存的存储，所以如果服务器宕机，会导致数据丢失，所以如果需要保存在磁盘上，需要使用持久化方案，Redis提供两种的持久化方案，RDB和AOF，\u003cstrong\u003e默认情况下，开启了RDB方案，关闭AOF方案\u003c/strong\u003e\u003c/p\u003e","title":"4、持久化、事务","type":"posts"},{"content":" 数组的概述 # 数组的特点：数组是有序排列的。 # 1、数组属于引用数据类型的变量。数组的元素既可以是基本数据类型也可以是引用数据类型。\n2、创建数组对象会在内存中开辟一整块连续的空间，而数组名中引用的是这块连续空间的首地址。\n3、数组的长度一旦确定，就不能修改。\n数组的分类 # 按照维度：一维数组、二维数组、三维数组、…\n按照元素的数据类型：基本数据类型元素的数组、引用数据类型元素的数组(即对象数组)\n一维数组的使用 # 一维数组的声明和初始化 # 数组一旦初始化完成，其长度就确定了。\n数组元素的引用 # 数组的角标从0开始，到数组的长度减一结束。\n使用属性length可以获取数组的长度。\n增强遍历一维数组（foreach） # int[] a = new int[]{1,2,3,4}; for(int e : a){ System.out.println(e); } //-------------------------------------- String[] names = {\u0026#34;beibei\u0026#34;, \u0026#34;jingjing\u0026#34;}; for (String name : names) { System.out.println(name); } 数组元素的默认初始化值 # 内存解析 # 内存的简化结构 # 一维数组的内存解析 # 多维数组的使用 # 对于二维数组的理解，我们可以看成是一维数组 array1又作为另一个一维数组array2的元素而存在。其实，从数组底层的运行机制来看，其实没有多维数组。\n二维数组的声明和初始化 # 二维数组的引用 # int[][] arr = new int[][]{{1,2,3,},{4,5},{6,7,8}}; System.out.println(arr[0][1]); 输出 2 二维数组的长度 # int[][] arr = new int[][]{{1,2,3,},{4,5},{6,7,8}}; System.out.println(arr.length); 输出 3 System.out.println(arr[1].length); 输出 2 遍历二维数组 # 使用嵌套循环\nint[][] arr = new int[][]{{1,2,3,},{4,5},{6,7,8}}; for(int i = 0;i \u0026lt; arr.length;i++){ for(int j = 0;j \u0026lt; arr[i].length;j++){ System.out.print(arr[i][j]); } } 输出 12345678 二位数组的元素默认初始值 # int[][] arr = new int[4][3]; System.out.println(arr[0]); System.out.println(arr[0][1]); 输出 [I@15db9742 //外层数组默认值为地址值 0 //内层数组默认值为对应类型一维默认值 二维数组的内存解析 # 数组中涉及到的常见算法 # 二分法查找 # 要求：要查找的数组必须有序\n设置首位索引的方法，不断地取中间值。\n/** * 二分查找法 */ public static int binSearch(int[] a,int b){ int low = 0; int high = a.length - 1; while (low \u0026lt;= high){ int mid = (low + high)/2; if (a[mid] \u0026lt; b){ low = mid + 1; }else if(a[mid] == b){ return mid; }else { high = mid - 1; } } return -1; } 常用方法 # Arrays工具类的使用 # System.arraycopy # //src: 原数组 //srcPos： 原数组起始位置（从这个位置开始复制） //dest： 目标数组 //destPos：目标数组粘贴的起始位置 //length： 复制的个数 public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); 数组常见异常 # ","date":"2024-01-04","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/f742d77f/2192a2c5/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e数组的概述 \n    \u003cdiv id=\"数组的概述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%95%b0%e7%bb%84%e7%9a%84%e6%a6%82%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"说明: untitle.png\"\n    data-zoom-src=\"/posts/3ab7256e/80aacaea/f742d77f/2192a2c5/image/202109181136776.gif\"\n    src=\"/posts/3ab7256e/80aacaea/f742d77f/2192a2c5/image/202109181136776.gif\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"4、数组","type":"posts"},{"content":" 声明配置类 # SpringBoot的配置类的使用方法基本与SpringMVC中相同\n@Configuration public class MyConfig implements WebMvcConfigurer{ //根据需求重写方法 } 配置拦截器 # 1、自定义拦截器 # @Component public class ExetInterceptor extends HandlerInterceptorAdapter{ /** * 实现访问登录拦截 */ @Override public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object o) throws Exception { HttpSession session = req.getSession(); Object ob = session.getAttribute(\u0026#34;nowLogin\u0026#34;); String uri = req.getRequestURI(); if(uri.endsWith(\u0026#34;/fileUpload.do\u0026#34;)||uri.endsWith(\u0026#34;/loginAjax.do\u0026#34;)||uri.endsWith(\u0026#34;/login.do\u0026#34;)){ return true; }else{ if(ob == null){ resp.sendRedirect(\u0026#34;index.jsp\u0026#34;); return false; }else{ return true; } } } } 2、配置拦截器 # @Configuration public class InterceptorConfig implements WebMvcConfigurer{ @Autowired private ExetInterceptor exetInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { InterceptorRegistration registration = registry.addInterceptor(exetInterceptor); registration.addPathPatterns(\u0026#34;/**\u0026#34;); //所有路径都被拦截 registration.excludePathPatterns( //添加不拦截路径 \u0026#34;你的登陆路径\u0026#34;, //登录 \u0026#34;/**/*.html\u0026#34;, //html静态资源 \u0026#34;/**/*.js\u0026#34;, //js静态资源 \u0026#34;/**/*.css\u0026#34;, //css静态资源 \u0026#34;/**/*.woff\u0026#34;, \u0026#34;/**/*.ttf\u0026#34; ); }\t} 配置过滤器 # 1、编写过滤器 # public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { } @Override public void destroy() { } } 2、配置过滤器 # @Configuration public class FilterConfig { @Bean public FilterRegistrationBean\u0026lt;MyFilter\u0026gt; registrationBean(){ FilterRegistrationBean\u0026lt;MyFilter\u0026gt; filterRegistrationBean = new FilterRegistrationBean\u0026lt;\u0026gt;(); filterRegistrationBean.setFilter(new MyFilter()); //setUrlPatterns() 一次性将映射关系配置进去 filterRegistrationBean.addUrlPatterns(\u0026#34;/*\u0026#34;); filterRegistrationBean.setOrder(1); filterRegistrationBean.setName(\u0026#34;myFilter\u0026#34;); return filterRegistrationBean; } } ","date":"2024-01-04","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/4f5b7444/aa523356/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e声明配置类 \n    \u003cdiv id=\"声明配置类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%a3%b0%e6%98%8e%e9%85%8d%e7%bd%ae%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eSpringBoot的配置类的使用方法基本与SpringMVC中相同\u003c/p\u003e","title":"4、配置类","type":"posts"},{"content":" 初始化接口 # 实现Initializable接口，重写initialize方法，可以在视图加载时调用\n@FXMLView(\u0026#34;/fxml/main.fxml\u0026#34;) public class MainController extends AbstractFxmlView implements Initializable { @Override public void initialize(URL location, ResourceBundle resources) { } } WebView # 这个组件可以实现在javafx中加载web页面\n@FXMLView(\u0026#34;/fxml/main.fxml\u0026#34;) public class MainController extends AbstractFxmlView implements Initializable { @FXML private WebView webBox; @Override public void initialize(URL location, ResourceBundle resources) { //获取引擎 WebEngine engine = webBox.getEngine(); //获取resources目录下editor中的index.html绝对路径 URL resource = getClass().getClassLoader().getResource(\u0026#34;editor/index.html\u0026#34;); //引擎加载页面 engine.load(resource.toString()); } } java和js进行通信 # ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/d9073060/4e274972/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e初始化接口 \n    \u003cdiv id=\"初始化接口\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%9d%e5%a7%8b%e5%8c%96%e6%8e%a5%e5%8f%a3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e实现Initializable接口，重写initialize方法，可以在视图加载时调用\u003c/p\u003e","title":"4、API","type":"posts"},{"content":"按钮\nButton btn = new Button(\u0026#34;btn\u0026#34;); //鼠标点击事件 btn.setOnMouseClicked(new EventHandler\u0026lt;MouseEvent\u0026gt;() { @Override public void handle(MouseEvent event) { System.out.println(\u0026#34;click\u0026#34;); } }); //鼠标双击以及鼠标按钮 btn.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler\u0026lt;MouseEvent\u0026gt;() { @Override public void handle(MouseEvent event) { if (event.getClickCount() == 2){ System.out.println(\u0026#34;click double\u0026#34;); MouseButton mouseButton = event.getButton(); // 获取鼠标按钮 String name = mouseButton.name(); System.out.println(name); } } }); ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/d9073060/d57b26b2/7d1df3f4/","section":"文章","summary":"\u003cp\u003e按钮\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eButton\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebtn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eButton\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;btn\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//鼠标点击事件\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ebtn\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetOnMouseClicked\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eEventHandler\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eMouseEvent\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Override\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003ehandle\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eMouseEvent\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eevent\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;click\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//鼠标双击以及鼠标按钮\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003ebtn\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eaddEventHandler\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eMouseEvent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eMOUSE_CLICKED\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eEventHandler\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eMouseEvent\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Override\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003ehandle\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eMouseEvent\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eevent\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eevent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetClickCount\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;click double\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eMouseButton\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emouseButton\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eevent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetButton\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 获取鼠标按钮\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emouseButton\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"4、Button","type":"posts"},{"content":" 插入 # 方式一 # 语法：\ninsert into 表名 (字段名,...) values (值,...); 特点：\n1、要求值的类型和字段的类型要一致或兼容\n2、字段的个数和顺序不一定与原始表中的字段个数和顺序一致\n但必须保证值和字段一一对应\n3、假如表中有可以为null的字段，注意可以通过以下两种方式插入null值\n①字段和值都省略\n②字段写上，值使用null\n4、字段和值的个数必须一致\n5、字段名可以省略，默认所有列\n方式二 # 语法：\ninsert into 表名 set 字段=值,字段=值,...; 两种方式的区别： # 1.方式一支持一次插入多行，语法如下：\ninsert into 表名(字段名,..) values(值，..),(值，...),...; 2.方式二支持子查询，语法如下：\ninsert into 表名 查询语句; 修改 # 一、修改单表的记录\n语法：\nupdate 表名 set 字段=值,字段=值 where 筛选条件;` 二、修改多表的记录【补充】\n语法：\nupdate 表1 别名 left|right|inner join 表2 别名 on 连接条件 set 字段=值,字段=值 where 筛选条件; 删除 # 方式一：使用delete # 一、删除单表的记录\n语法：\ndelete from 表名 where 筛选条件 limit 条目数\n二、级联删除[补充]\n语法：\ndelete 别名1,别名2 from 表1 别名 inner|left|right join 表2 别名 on 连接条件 where 筛选条件 方式二：使用truncate # 语法：\ntruncate table 表名\n两种方式的区别【面试题】 # truncate删除后，如果再插入，标识列从1开始；delete删除后，如果再插入，标识列从断点开始 delete可以添加筛选条件；truncate不可以添加筛选条件 truncate效率较高 truncate没有返回值；delete可以返回受影响的行数 truncate不可以回滚；delete可以回滚 ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/a6ece75f/8b824d9b/5d290da9/3716753f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e插入 \n    \u003cdiv id=\"插入\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8f%92%e5%85%a5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e方式一 \n    \u003cdiv id=\"方式一\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e5%bc%8f%e4%b8%80\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e语法：\u003c/p\u003e","title":"4、DML数据操作语句","type":"posts"},{"content":" term查询 # term query: 会去倒排索引中寻找确切的term，它并不知道分词器的存在。这种查询适合keyword 、numeric、date term：查询某个字段为该关键词的文档（它是相等关系而不是包含关系） term是代表完全匹配，即不进行分词器分析，必须全值匹配。查询不会对查询的字段进行分词查询，会采用精确匹配 可以用它处理数字（numbers）、布尔值（Booleans）、日期（dates）以及文本（text） 语法 # 如果字段类型为text，那么在插入的时候就会被分词，如果使用term查询的话，那么无法命中\nGET /index_name/types_name/_search { \u0026#34;query\u0026#34;: { \u0026#34;term\u0026#34;: { \u0026#34;key\u0026#34;: \u0026#34;value\u0026#34; } } } match查询 # 知道分词器的存在，会对filed进行分词操作，然后再查询\n无论你在任何字段上进行的是全文搜索还是精确查询，match 查询是你可用的标准查询。\n如果你在一个全文字段上使用 match 查询，在执行查询前，它将用正确的分析器去分析查询字符串：\n匹配（Match）查询属于全文（Fulltext）查询，不同于词条查询，ElasticSearch引擎在处理全文搜索时，首先分析（analyze）查询字符串，然后根据分词构建查询，最终返回查询结果。匹配查询共有三种类型，分别是布尔（boolean）、短语（phrase）和短语前缀（phrase_prefix），默认的匹配查询是布尔类型，这意味着，ElasticSearch引擎首先分析查询字符串，根据分析器对其进行分词。\nmatch_all # 查询所有文档\n语法 # GET index_name/_search { \u0026#34;query\u0026#34;:{ \u0026#34;match_all\u0026#34;: {} } } match精确匹配 # 如果在一个精确值的字段上使用它，例如数字、日期、布尔或者一个 not_analyzed(version:5) ,keyword(7)字符串字段，那么它将会精确匹配给定的值\n语法 # 查询结果是所有年龄=18岁的\nGET userinfo/_search { \u0026#34;query\u0026#34;:{ \u0026#34;match\u0026#34;:{\u0026#34;age\u0026#34;: 18} } } match分词匹配 # match query: 知道分词器的存在，会对key的value进行分词操作，然后再查询\nget /index_name/_search { \u0026#34;query\u0026#34;:{ \u0026#34;match\u0026#34;: { \u0026#34;key\u0026#34;: \u0026#34;value\u0026#34; } } } multi_match # 会对value进行分词，然后在key1和key2两个字段的value中进行匹配\nGET index_name/_search { \u0026#34;query\u0026#34;:{ \u0026#34;multi_match\u0026#34;: { \u0026#34;query\u0026#34;: \u0026#34;value\u0026#34;, \u0026#34;fields\u0026#34;:[\u0026#34;key1\u0026#34;,\u0026#34;key2\u0026#34;] } } } match_phrase # 短语匹配查询，ElasticSearch引擎首先分析（analyze）查询字符串，从分析后的文本中构建短语查询，这意味着必须匹配短语中的所有分词，并且保证各个分词的相对位置不变\nGET index_name/_search { \u0026#34;query\u0026#34;:{ \u0026#34;match_phrase\u0026#34;:{\u0026#34;key\u0026#34;: \u0026#34;value\u0026#34;} } } 指定返回的字段 # 可以指定查询结果返回的字段，相当于MySQL中的select uid,uname from user\nGET index_name/_search { \u0026#34;_source\u0026#34;:[\u0026#34;key1\u0026#34;,\u0026#34;key2\u0026#34;], } 排序查询 # GET /index_name/_search { \u0026#34;query\u0026#34;:{ \u0026#34;match_all\u0026#34;:{ } }, \u0026#34;sort\u0026#34;:{ \u0026#34;key\u0026#34;:{ \u0026#34;order\u0026#34;:\u0026#34;desc[降序]|asc[升序]\u0026#34; } } } 复合查询 # bool （布尔）过滤器。 这是个复合过滤器（compound filter） ，它可以接受多个其他过滤器作为参数，并将这些过滤器结合成各式各样的布尔（逻辑）组合。 用户合并其他查询语句，比如一个bool语句，允许你在需要的时候组合其他语句，包括must，must_not，should和filter语句（多条件组合查询）\n格式 # 一个 bool 过滤器由四部分组成\n{ \u0026#34;query\u0026#34;:{ \u0026#34;bool\u0026#34;:{ \u0026#34;must\u0026#34;:[ ], \u0026#34;should\u0026#34;:[ ], \u0026#34;must_not\u0026#34;:[ ], \u0026#34;filter\u0026#34;:[ ] } } } must 所有的语句都 必须（must） 匹配，与 AND 等价。 must_not 所有的语句都 不能（must not） 匹配，与 NOT 等价。 should 至少有一个语句要匹配，与 OR 等价 使用举例 # 查询userinfo中年龄是18随，且姓名不是lucy的信息\nGET /userinfo/_search { \u0026#34;query\u0026#34;: { \u0026#34;bool\u0026#34;: { \u0026#34;must\u0026#34;: [ {\t\u0026#34;match\u0026#34;: { \u0026#34;age\u0026#34;: \u0026#34;18\u0026#34; } } ], \u0026#34;must_not\u0026#34;: [ { \u0026#34;match\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;lucy\u0026#34; } } ] } } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/d1774b5b/5a67ecdb/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eterm查询 \n    \u003cdiv id=\"term查询\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#term%e6%9f%a5%e8%af%a2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eterm query: 会去倒排索引中寻找确切的term，它并不知道分词器的存在。这种查询适合keyword 、numeric、date\u003c/li\u003e\n\u003cli\u003eterm：查询某个字段为该关键词的文档（它是相等关系而不是包含关系）\u003c/li\u003e\n\u003cli\u003eterm是代表完全匹配，即不进行分词器分析，必须全值匹配。查询不会对查询的字段进行分词查询，会采用精确匹配\u003c/li\u003e\n\u003cli\u003e可以用它处理数字（numbers）、布尔值（Booleans）、日期（dates）以及文本（text）\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003e语法 \n    \u003cdiv id=\"语法\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%af%ad%e6%b3%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e如果字段类型为text，那么在插入的时候就会被分词，如果使用term查询的话，那么无法命中\u003c/p\u003e","title":"4、ES的查询","type":"posts"},{"content":" HTMLParser是html.parser模块下，主要是用来解析HTML文件（包括HTML中无效的标记）的模块。 参数convert_charrefs表示是否将所有的字符引用自动转化为Unicode形式，Python3.5以后默认是True。 HTMLParser可以接收相应的HTML内容，并进行解析，遇到HTML的标签会自动调用相应的handler（处理方法）来处理，用户需要自己创建相应的子类来继承HTMLParser，并且复写相应的handler方法。 HTMLParser不会检查开始标签和结束标签是否是一对 常见方法与属性 # 方法 # HTMLParser.feed(data)：接收一个字符串类型的HTML内容，并进行解析。 HTMLParser.close()：当遇到文件结束标签后进行的处理方法。如果子类要复写该方法，需要首先调用HTMLParser累的close()。 HTMLParser.reset()：重置HTMLParser实例，该方法会丢掉未处理的html内容。 HTMLParser.getpos()：返回当前行和相应的偏移量。 HTMLParser.handle_starttag(tag, attrs)：对开始标签的处理方法。例如\u0026lt;div id=\u0026quot;main\u0026quot;\u0026gt;，参数tag指的是div，attrs指的是一个由（name,Value)元组组成的列表。 HTMLParser.handle_endtag(tag)：对结束标签的处理方法。例如\u0026lt;/div\u0026gt;，参数tag指的是div。 HTMLParser.handle_startendtag(tag, attrs)：识别没有结束标签的HTML标签，例如\u0026lt;img /\u0026gt;等。 HTMLParser.handle_data(data)：对标签之间的数据的处理方法。\u0026lt;tag\u0026gt;test\u0026lt;/tag\u0026gt;，data指的是“test”。 HTMLParser.handle_comment(data)：对HTML中注释的处理方法。 属性 # HTMLParser.lasttag：上一个解析的标签名，是字符串 例子 # from html.parser import HTMLParser # 集成HTMLParser，重写方法 class MyHtmlParser(HTMLParser): def handle_starttag(self,tag,attrs): \u0026#34;开始标签\u0026#34; print(\u0026#39;%s标签开始\u0026#39; % tag) print(\u0026#39;%s标签属性：%s\u0026#39; % (tag,attrs)) def handle_endtag(self, tag): \u0026#34;处理结束标签\u0026#34; print(\u0026#39;%s标签结束\u0026#39; % tag) def handle_startendtag(self, tag, attrs): \u0026#34;处理自闭和标签\u0026#34; print(\u0026#39;%s自闭和标签\u0026#39; % tag) print(\u0026#39;%s标签属性：%s\u0026#39; % (tag,attrs)) def handle_data(self, data): \u0026#34;处理标签间数据\u0026#34; print(\u0026#39;%s标签间数据为：%s\u0026#39; % (self.lasttag,data)) def handle_comment(self, data): \u0026#34;处理注释\u0026#34; print(\u0026#39;注释：%s\u0026#39; % data) def handle_entityref(self, name): print(\u0026#39;\u0026amp;%s;\u0026#39; % name) def handle_charref(self, name): print(\u0026#39;\u0026amp;#%s;\u0026#39; % name) html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;-- 这是注释 --\u0026gt; \u0026lt;head\u0026gt;这是头标签\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;!-- test html parser --\u0026gt; \u0026lt;p\u0026gt;Some \u0026lt;a href=\\\u0026#34;#\\\u0026#34;\u0026gt;html\u0026lt;/a\u0026gt; HTML\u0026amp;nbsp;\u0026amp;#1234; Ӓtutorial...\u0026lt;br\u0026gt;END\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; #解析html字符串 p = MyHtmlParser() p.feed(html) #关闭解析 p.close() 转义字符 # 用 Python 来处理转义字符串有多种方式，而且 py2 和 py3 中处理方式不一样，在 python2 中，反转义串的模块是 HTMLParser。\n# python2 import HTMLParser a = HTMLParser().unescape(\u0026#39;\u0026amp;lt;abc\u0026amp;gt;\u0026#39;) print(a) \u0026#39;\u0026#39;\u0026#39; \u0026lt;abc\u0026gt;; \u0026#39;\u0026#39;\u0026#39; Python3 HTMLParser 模块迁移到了 html.parser\n# python3 from html.parser import HTMLParser a = HTMLParser().unescape(\u0026#39;\u0026amp;lt;abc\u0026amp;gt;\u0026#39;) print(a) \u0026#39;\u0026#39;\u0026#39; \u0026lt;abc\u0026gt;; \u0026#39;\u0026#39;\u0026#39; 到 python3.4 以后的版本，在 html 模块新增了 unescape 方法\n# python3.4 import html a = html.unescape(\u0026#39;\u0026amp;lt;abc\u0026amp;gt;\u0026#39;) print(a) \u0026#39;\u0026#39;\u0026#39; \u0026lt;abc\u0026gt;; \u0026#39;\u0026#39;\u0026#39; ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/1b37041b/339aff44/7edad37d/","section":"文章","summary":"\u003cul\u003e\n\u003cli\u003eHTMLParser是\u003ccode\u003ehtml.parser\u003c/code\u003e模块下，主要是用来解析HTML文件（包括HTML中无效的标记）的模块。\u003c/li\u003e\n\u003cli\u003e参数convert_charrefs表示是否将所有的字符引用自动转化为Unicode形式，Python3.5以后默认是True。\u003c/li\u003e\n\u003cli\u003eHTMLParser可以接收相应的HTML内容，并进行解析，遇到HTML的标签\u003cstrong\u003e会自动调用相应的handler\u003c/strong\u003e（处理方法）来处理，用户\u003cstrong\u003e需要自己创建相应的子类来继承HTMLParser，并且复写相应的handler方法\u003c/strong\u003e。\u003c/li\u003e\n\u003cli\u003eHTMLParser不会检查开始标签和结束标签是否是一对\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e常见方法与属性 \n    \u003cdiv id=\"常见方法与属性\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b8%b8%e8%a7%81%e6%96%b9%e6%b3%95%e4%b8%8e%e5%b1%9e%e6%80%a7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e方法 \n    \u003cdiv id=\"方法\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e6%b3%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eHTMLParser.feed(data)\u003c/code\u003e：接收一个字符串类型的HTML内容，并进行解析。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHTMLParser.close()\u003c/code\u003e：当遇到文件结束标签后进行的处理方法。如果子类要复写该方法，需要首先调用HTMLParser累的close()。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHTMLParser.reset()\u003c/code\u003e：重置HTMLParser实例，该方法会丢掉未处理的html内容。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHTMLParser.getpos()\u003c/code\u003e：返回当前行和相应的偏移量。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHTMLParser.handle_starttag(tag, attrs)\u003c/code\u003e：对开始标签的处理方法。例如\u003ccode\u003e\u0026lt;div id=\u0026quot;main\u0026quot;\u0026gt;\u003c/code\u003e，参数tag指的是div，attrs指的是一个由（name,Value)元组组成的列表。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHTMLParser.handle_endtag(tag)\u003c/code\u003e：对结束标签的处理方法。例如\u003ccode\u003e\u0026lt;/div\u0026gt;\u003c/code\u003e，参数tag指的是div。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHTMLParser.handle_startendtag(tag, attrs)\u003c/code\u003e：识别没有结束标签的HTML标签，例如\u003ccode\u003e\u0026lt;img /\u0026gt;\u003c/code\u003e等。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHTMLParser.handle_data(data)\u003c/code\u003e：对标签之间的数据的处理方法。\u003ccode\u003e\u0026lt;tag\u0026gt;\u003c/code\u003etest\u003ccode\u003e\u0026lt;/tag\u0026gt;\u003c/code\u003e，data指的是“test”。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eHTMLParser.handle_comment(data)\u003c/code\u003e：对HTML中注释的处理方法。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003e属性 \n    \u003cdiv id=\"属性\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b1%9e%e6%80%a7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eHTMLParser.lasttag\u003c/code\u003e：上一个解析的标签名，是字符串\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e例子 \n    \u003cdiv id=\"例子\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%be%8b%e5%ad%90\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003ehtml.parser\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eHTMLParser\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 集成HTMLParser，重写方法\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eMyHtmlParser\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eHTMLParser\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ehandle_starttag\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003etag\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eattrs\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;开始标签\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e\u003cspan class=\"si\"\u003e%s\u003c/span\u003e\u003cspan class=\"s1\"\u003e标签开始\u0026#39;\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"n\"\u003etag\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e\u003cspan class=\"si\"\u003e%s\u003c/span\u003e\u003cspan class=\"s1\"\u003e标签属性：\u003c/span\u003e\u003cspan class=\"si\"\u003e%s\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etag\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eattrs\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ehandle_endtag\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etag\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;处理结束标签\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e\u003cspan class=\"si\"\u003e%s\u003c/span\u003e\u003cspan class=\"s1\"\u003e标签结束\u0026#39;\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"n\"\u003etag\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ehandle_startendtag\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etag\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eattrs\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;处理自闭和标签\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e\u003cspan class=\"si\"\u003e%s\u003c/span\u003e\u003cspan class=\"s1\"\u003e自闭和标签\u0026#39;\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"n\"\u003etag\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e\u003cspan class=\"si\"\u003e%s\u003c/span\u003e\u003cspan class=\"s1\"\u003e标签属性：\u003c/span\u003e\u003cspan class=\"si\"\u003e%s\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etag\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eattrs\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ehandle_data\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;处理标签间数据\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e\u003cspan class=\"si\"\u003e%s\u003c/span\u003e\u003cspan class=\"s1\"\u003e标签间数据为：\u003c/span\u003e\u003cspan class=\"si\"\u003e%s\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elasttag\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ehandle_comment\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;处理注释\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;注释：\u003c/span\u003e\u003cspan class=\"si\"\u003e%s\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ehandle_entityref\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u0026amp;\u003c/span\u003e\u003cspan class=\"si\"\u003e%s\u003c/span\u003e\u003cspan class=\"s1\"\u003e;\u0026#39;\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ehandle_charref\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u0026amp;#\u003c/span\u003e\u003cspan class=\"si\"\u003e%s\u003c/span\u003e\u003cspan class=\"s1\"\u003e;\u0026#39;\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ehtml\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e\u0026lt;html\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e    \u0026lt;-- 这是注释 --\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e    \u0026lt;head\u0026gt;这是头标签\u0026lt;/head\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e    \u0026lt;body\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e        \u0026lt;!-- test html parser --\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e        \u0026lt;p\u0026gt;Some \u0026lt;a href=\u003c/span\u003e\u003cspan class=\"se\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan class=\"s2\"\u003e#\u003c/span\u003e\u003cspan class=\"se\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026gt;html\u0026lt;/a\u0026gt; HTML\u0026amp;nbsp;\u0026amp;#1234; Ӓtutorial...\u0026lt;br\u0026gt;END\u0026lt;/p\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e    \u0026lt;/body\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e\u0026lt;/html\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#解析html字符串\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ep\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eMyHtmlParser\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ep\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efeed\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ehtml\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#关闭解析\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ep\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e转义字符 \n    \u003cdiv id=\"转义字符\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%bd%ac%e4%b9%89%e5%ad%97%e7%ac%a6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e用 Python 来处理转义字符串有多种方式，而且 py2 和 py3 中处理方式不一样，在 python2 中，反转义串的模块是 HTMLParser。\u003c/p\u003e","title":"4、HTMLParser","type":"posts"},{"content":" Lombok # Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more. Lombok简单理解就是一个Java类库，通过注解的形式帮助开发减少一些结构化代码的开发工作，提高开发效率，比如通过@Data注解，class在编译的时候会自动生成get，set，equals，hash，toString等方法，避免写大量的代码，减少了代码量，也使代码看起来更加简洁。尤其是一些对象属性需要改动的时候，每次改动都需要重新生成get，set，equals，hash，toString等方法，而使用注解则可以避免此问题。\n使用方法 # 1、如果要使用lombok，首先开发工具IntelliJ IDEA或者Eclipse需要先安装插件lombok支持，其次需要引入依赖。\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.projectlombok\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;lombok\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.18.16\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 2、使用注解，添加需要的结构\n@Data @NoArgsConstructor @AllArgsConstructor @ToString public class Student { private Integer sno; private String sname; private String ssex; private Integer sage; } 常用注解 # @Data：该注解定义在JavaBean上，给JavaBean产生getter，setter，无参构造器，tostring()，hashcode()，equals()\n@NoArgsConstructor：产生无参构造器\n@Getter：产生getter\n@Setter：产生setter\n@ToString：产生toString()\n@RequiredArgsConstructor ：可以用来定义有参构造器\n@NonNull：该注解用在属性或形参上，Lombok会生成一个非空的声明，可用于校验参数，能帮助避免空指针。\npublic static String getName(@NonNull User user) { return user.getName(); } @AllArgsConstructor：产生全属性的有参构造\n@Cleanup：这个注解用在变量前面，可以保证此变量代表的资源会被自动关闭，默认是调用资源的close()方法，如果该资源有其它关闭方法，可使用@Cleanup(\u0026quot;methodName\u0026quot;)来指定要调用的方法。\npublic static void main(String[] args) throws IOException { @Cleanup InputStream in = new FileInputStream(\u0026#34;c:\\\\test.jpg\u0026#34;); } @EqualsAndHashCode：生成hashCode和equals方法。\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/0f3856f3/48b3cd2c/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eLombok \n    \u003cdiv id=\"lombok\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#lombok\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eProject Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.\nNever write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.\nLombok简单理解就是一个Java类库，通过注解的形式帮助开发减少一些结构化代码的开发工作，提高开发效率，比如通过@Data注解，class在编译的时候会自动生成\u003ccode\u003eget\u003c/code\u003e，\u003ccode\u003eset\u003c/code\u003e，\u003ccode\u003eequals\u003c/code\u003e，\u003ccode\u003ehash\u003c/code\u003e，\u003ccode\u003etoString\u003c/code\u003e等方法，避免写大量的代码，减少了代码量，也使代码看起来更加简洁。尤其是一些对象属性需要改动的时候，每次改动都需要重新生成\u003ccode\u003eget\u003c/code\u003e，\u003ccode\u003eset\u003c/code\u003e，\u003ccode\u003eequals\u003c/code\u003e，\u003ccode\u003ehash\u003c/code\u003e，\u003ccode\u003etoString\u003c/code\u003e等方法，而使用注解则可以避免此问题。\u003c/p\u003e","title":"4、Lombok","type":"posts"},{"content":"这是一个第三方的转字符集编码的库\n安装 # go get github.com/axgle/mahonia // ConvertToString src:源字符串;srcCode:源字符串编码;tagCode:目标字符串编码 func ConvertToString(src string, srcCode string, tagCode string) string { srcCoder := mahonia.NewDecoder(srcCode) srcResult := srcCoder.ConvertString(src) tagCoder := mahonia.NewDecoder(tagCode) _, cdata, _ := tagCoder.Translate([]byte(srcResult), true) result := string(cdata) return result } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/29ede47b/3ad20944/","section":"文章","summary":"\u003cp\u003e这是一个第三方的转字符集编码的库\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e安装 \n    \u003cdiv id=\"安装\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%89%e8%a3%85\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ego get github.com/axgle/mahonia\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// ConvertToString src:源字符串;srcCode:源字符串编码;tagCode:目标字符串编码\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eConvertToString\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003esrc\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003esrcCode\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003etagCode\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003esrcCoder\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003emahonia\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNewDecoder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003esrcCode\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003esrcResult\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003esrcCoder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eConvertString\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003esrc\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003etagCoder\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003emahonia\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNewDecoder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003etagCode\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ecdata\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003etagCoder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eTranslate\u003c/span\u003e\u003cspan class=\"p\"\u003e([]\u003c/span\u003e\u003cspan class=\"nb\"\u003ebyte\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003esrcResult\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eresult\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nb\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ecdata\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003eresult\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"4、mahonia","type":"posts"},{"content":"JavaScript的默认对象表示方式{}可以视为其他语言中的Map或Dictionary的数据结构，即一组键值对。\n但是JavaScript的对象有个小问题，就是键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。\n为了解决这个问题，最新的ES6规范引入了新的数据类型Map\nMap # 初始化Map需要一个二维数组，或者直接初始化一个空Map\nvar map = new Map([[\u0026#39;tom\u0026#39;,18],[\u0026#39;lucy\u0026#39;,22],[\u0026#39;lily\u0026#39;,17]]) 常用api # var map = new Map([[\u0026#39;tom\u0026#39;,18],[\u0026#39;lucy\u0026#39;,22],[\u0026#39;lily\u0026#39;,17]]) //获取map的长度 map.size //设置一个key-value，如果已经存在会覆盖value map.set(\u0026#39;james\u0026#39;,25) //判断是否存在一个key map.has(\u0026#39;lucy\u0026#39;) //根据key获取value map.get(\u0026#39;lucy\u0026#39;) //根据key删除 map.delete(\u0026#39;tom\u0026#39;) //清空map map.clear() Set # Set和Map类似，也是一组key的集合，但不存储value。由于key不能重复，所以，在Set中，没有重复的key。\n要创建一个Set，需要提供一个Array作为输入，或者直接创建一个空Set\nvar set = new Set([\u0026#39;lucy\u0026#39;,\u0026#39;tom\u0026#39;,\u0026#39;james\u0026#39;]) 常用api # var set = new Set([\u0026#39;lucy\u0026#39;,\u0026#39;tom\u0026#39;,\u0026#39;james\u0026#39;]) //获取set的长度 set.size //添加一个元素 set.add(\u0026#39;lily\u0026#39;) //删除一个元素 set.delete(\u0026#39;tom\u0026#39;) //清空 set.clear() //判断是否存在元素 set.has(\u0026#39;james\u0026#39;) iterable # 遍历Array可以采用下标循环，遍历Map和Set就无法使用下标。为了统一集合类型，ES6标准引入了新的iterable类型，Array、Map和Set都属于iterable类型。\n具有iterable类型的集合可以通过新的for ... of循环来遍历，for ... of循环是ES6引入的新的语法\nvar set = new Set([\u0026#39;lucy\u0026#39;,\u0026#39;tom\u0026#39;,\u0026#39;james\u0026#39;]) for(name of set){ console.log(name) } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/0d08345a/1403736b/267e6d5e/","section":"文章","summary":"\u003cp\u003eJavaScript的默认对象表示方式\u003ccode\u003e{}\u003c/code\u003e可以视为其他语言中的\u003ccode\u003eMap\u003c/code\u003e或\u003ccode\u003eDictionary\u003c/code\u003e的数据结构，即一组键值对。\u003c/p\u003e","title":"4、Map和Set","type":"posts"},{"content":" Spring完全注解开发 # 完全注解开发是指在Spring应用中完全依赖注解进行配置和开发，而不使用XML配置文件。以下是完全注解开发的一些优点和缺点：\n优点 简化配置：使用注解可以减少XML配置文件的数量和复杂性，简化了项目的配置过程。注解配置通常更为简洁和直观，使得配置更容易理解和维护。 提升开发效率：注解配置方式可以减少样板代码的编写，节省了开发人员的时间和精力。同时，注解也提供了更强大的功能，如自动装配和切面编程，可以减少手动编写的代码量。 更好的可读性：注解使得代码的配置和逻辑更加紧密，使得代码更具可读性和可理解性。通过注解，开发人员可以直观地了解代码的用途和意图，提高了代码的可维护性。 更灵活的开发方式：使用注解可以更灵活地定义和组织代码结构，使得开发过程更加自由。注解可以应用在类、方法、字段等级别，可以更细粒度地控制和定制功能。 更好的集成性：注解配置更加紧密地与Java代码集成，使得与其他框架和工具的集成更加方便。注解可以与Spring的其他特性（如Spring MVC、Spring Data等）无缝协作，提供更一致和统一的开发体验。 缺点 可能过于依赖注解：完全注解开发使得配置信息分散在各个类和方法中，可能导致代码过于依赖注解，降低了代码的可读性和可维护性。同时，如果注解配置错误或不当，可能会导致运行时的问题。 难以处理复杂配置：对于复杂的配置需求，完全注解开发可能不如XML配置灵活。XML配置文件具有更强大的表达能力，可以处理更复杂的配置场景，如条件化配置、属性占位符等。 不够直观：对于新加入的团队成员或不熟悉注解的开发人员来说，完全注解开发可能不够直观。XML配置文件可以提供更明确的配置结构和关系，更容易理解和学习。 难以管理：使用注解进行配置时，配置信息散布在各个类和方法中，可能会使得配置管理变得困难。对于大型项目和团队开发来说，可能需要更多的规范和约束来管理注解配置。 常用注解 # 注解 说明 @Configuration 注入Spring容器，并声明该类为配置类，该类作用等同于Spring配置文件 @ComponentScan 用来扫描单个包，作用等同于context:component-scan @ComponentScans 用来扫描多个包，作用等同于context:component-scan @EnableAspectJAutoProxy 开启AOP注解，作用等同于aop:aspectj-autoproxy @PropertySource 指定要读取的配置文件，作用等同于context:property-placeholder。如果Spring版本小于4.3，还需要在配置类中创建PropertySourcesPlaceholderConfigurer的Bean，4.3之后为自动创建 注意：只有在@Configuration配置类中，才可以使用方法调用的方式获取bean；如果没有@Configuration直接调用带有@Bean注解的方法，会有编译异常Method annotated with @Bean is called directly. Use dependency injection instead.\n@Configuration public class AppConfig { @Bean public TempUser tempUser(){ return new TempUser(); } public void config(){ TempUser u = tempUser(); } } 用法 # 1、自定义配置类 # @Configuration @ComponentScan(\u0026#34;top.ygang\u0026#34;) @PropertySource({ \u0026#34;app.properties\u0026#34; }) public class SpringConfiguration { } 2、测试 # 需要使用AnnotationConfigApplicationContext类来加载配置类\nApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class); User bean = applicationContext.getBean(User.class); System.out.println(bean); ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/c70ae83e/7903f04f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eSpring完全注解开发 \n    \u003cdiv id=\"spring完全注解开发\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#spring%e5%ae%8c%e5%85%a8%e6%b3%a8%e8%a7%a3%e5%bc%80%e5%8f%91\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e完全注解开发是指在Spring应用中完全依赖注解进行配置和开发，而不使用XML配置文件。以下是完全注解开发的一些优点和缺点：\u003c/p\u003e","title":"4、Spring完全注解开发","type":"posts"},{"content":" SSM整合 # SSM整合即Spring + Spring MVC + Mybatis\n整合主要依靠mybatis-spring依赖，帮助我们简化的工作、带来的价值如下\n可以自动获取并使用Spring容器中beanName = dataSource的数据源 SqlSessionFactory创建的工作，使用原生的mybatis需要用配置文件配置mybatis，mybais-spring可以用@Bean代码创建。(虽然mybatis也能用代码构建，不过SqlSessionFactory被spring管理了，在使用的时候只需要@Autowire会更方便) 不再需要每次openSession、close，这些工作在mybatis-spring内部实现了，mybatis-spring帮助我们判断是否要openSession Mapper类变成了bean，需要使用的时候直接@Autowired就可以 提供线程安全的SqlSessionTemplate 整合步骤 # 1、创建Java Enterprise项目 # 创建Java Enterprise项目，选择Maven管理依赖\n2、pom.xml # \u0026lt;!-- mybatis整合spring依赖 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.mybatis\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mybatis-spring\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0.4\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-jdbc\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;5.3.23\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- mybatis核心依赖 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.mybatis\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mybatis\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.5.6\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- 数据库驱动 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;mysql\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mysql-connector-java\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;8.0.25\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- druid数据库连接池 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;druid\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.1.23\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- spring mvc依赖 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-webmvc\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;5.2.9.RELEASE\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- Servlet API --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;javax.servlet\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;javax.servlet-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.0.1\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;provided\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;javax.servlet\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jstl\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- jsp --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;javax.servlet.jsp\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jsp-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;provided\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- log --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.7.36\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;log4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;log4j\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.17\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-log4j12\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.7.30\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 3、搭建SpringMVC项目 # 这里使用完全注解开发的方式，如果要用配置文件的方式也同理，删除web.xml\nSpringMvcConfig替换Spring核心配置文件 # @Configuration @ComponentScan(\u0026#34;top.ygang.sm_demo\u0026#34;) @EnableWebMvc public class SpringMvcConfig implements WebMvcConfigurer { // 配置视图解析器 @Bean public InternalResourceViewResolver internalResourceViewResolver(){ InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix(\u0026#34;/\u0026#34;); resolver.setSuffix(\u0026#34;.jsp\u0026#34;); return resolver; } // 配置静态资源处理方式，等价于mvc:resources @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(\u0026#34;/static/**\u0026#34;).addResourceLocations(\u0026#34;classpath:/static/\u0026#34;); } } WebInit替换web.xml配置文件 # public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer { /** * 指定spring的配置类 * @return */ @Override protected Class\u0026lt;?\u0026gt;[] getRootConfigClasses() { return new Class[]{SpringMvcConfig.class}; } /** * 指定SpringMVC的配置类 * @return */ @Override protected Class\u0026lt;?\u0026gt;[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; } /** * 指定DispatcherServlet的映射规则，即url-pattern * @return */ @Override protected String[] getServletMappings() { return new String[]{\u0026#34;/\u0026#34;}; } /** * 乱码过滤器 * @return */ @Override protected Filter[] getServletFilters() { CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter(); encodingFilter.setEncoding(\u0026#34;UTF-8\u0026#34;); encodingFilter.setForceRequestEncoding(true); HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter(); return new Filter[]{encodingFilter, hiddenHttpMethodFilter}; } } 4、建立数据源配置文件 # resource下建立datasource.properties，用于对数据源进行配置\n由于使用的是druid作为数据库连接池，所以就按照druid的配置方式配置即可\ndriverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/test username=root password=123456 initialSize=5 maxActive=10 maxWait=1000 filters=stat,wall,slf4j 5、创建MybatisConfig # @Configuration @MapperScan(\u0026#34;top.ygang.sm_demo.dao\u0026#34;) public class MyBatisConfig { /** * 注入DataSource * @return * @throws Exception */ @Bean public DataSource dataSource() { InputStream inputStream = MyBatisConfig.class.getClassLoader().getResourceAsStream(\u0026#34;datasource.properties\u0026#34;); Properties properties = new Properties(); try { properties.load(inputStream); inputStream.close(); return DruidDataSourceFactory.createDataSource(properties); }catch (Exception e){ e.printStackTrace(); return null; } } /** * 配置SqlSessionFactoryBean * @param applicationContext * @return * @throws IOException */ @Bean public SqlSessionFactoryBean sessionFactory(ApplicationContext applicationContext) throws IOException { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource()); // 配置mapper文件路径 sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources(\u0026#34;classpath:mappers/*.xml\u0026#34;)); return sqlSessionFactoryBean; } /** * 配置事务管理器 * @return */ @Bean public DataSourceTransactionManager dataSourceTransactionManager(){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource()); return dataSourceTransactionManager; } } 6、创建Entity # public class Student { private String objId; private String name; private String age; public String getObjId() { return objId; } public void setObjId(String objId) { this.objId = objId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } @Override public String toString() { return \u0026#34;Student{\u0026#34; + \u0026#34;objId=\u0026#39;\u0026#34; + objId + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, name=\u0026#39;\u0026#34; + name + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, age=\u0026#39;\u0026#34; + age + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#39;}\u0026#39;; } } 7、创建Dao # @Mapper @Repository public interface StudentDao { List\u0026lt;Student\u0026gt; selectAll(); } 8、创建Dao映射文件 # 在resources/mappers下创建\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; ?\u0026gt; \u0026lt;!DOCTYPE mapper PUBLIC \u0026#34;-//mybatis.org//DTD Mapper 3.0//EN\u0026#34; \u0026#34;http://mybatis.org/dtd/mybatis-3-mapper.dtd\u0026#34;\u0026gt; \u0026lt;mapper namespace=\u0026#34;top.ygang.sm_demo.dao.StudentDao\u0026#34;\u0026gt; \u0026lt;select id=\u0026#34;selectAll\u0026#34; resultType=\u0026#34;top.ygang.sm_demo.entity.Student\u0026#34;\u0026gt; select * from student \u0026lt;/select\u0026gt; \u0026lt;/mapper\u0026gt; 9、log配置 # 在resource下创建log4j.properties\nlog4j.rootLogger = DEBUG, CONSOLE log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Target=System.out log4j.appender.CONSOLE.ImmediateFlush=true log4j.appender.CONSOLE.Threshold = DEBUG log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.encoding=UTF-8 log4j.appender.CONSOLE.layout.conversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%c]-[%p] %m%n ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/617a1910/066ff05a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eSSM整合 \n    \u003cdiv id=\"ssm整合\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#ssm%e6%95%b4%e5%90%88\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eSSM整合即Spring + Spring MVC + Mybatis\u003c/p\u003e","title":"4、SSM整合","type":"posts"},{"content":"分布式开发时，微服务会有很多，但是网关是请求的第一入口，所以一般会把客户端请求的权限验证统一放在网关进行认证与鉴权。SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关，目标是替代 Zuul，为了提升网关的性能，SpringCloud Gateway是基于WebFlux框架实现的，而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。\nSpringMVC和WebFlux中Security概念对应 # WebFlux SpringMVC @EnableWebFluxSecurity @EnableWebSecurity ReactiveSecurityContextHolder SecurityContextHolder AuthenticationWebFilter FilterSecurityInterceptor ReactiveAuthenticationManager AuthenticationManager ReactiveUserDetailsService UserDetailsService ReactiveAuthorizationManager AccessDecisionManager 使用 # 1、引入依赖\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-security\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-gateway\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、自定义授权失败处理类\npublic class AccessDeniedHandlerImpl implements ServerAccessDeniedHandler { @Override public Mono\u0026lt;Void\u0026gt; handle(ServerWebExchange serverWebExchange, AccessDeniedException e) { ServerHttpResponse response = serverWebExchange.getResponse(); response.getHeaders().add(\u0026#34;Content-Type\u0026#34;,\u0026#34;application/json;charset=utf-8\u0026#34;); ResultVO\u0026lt;Object\u0026gt; result = ResultVO.result(\u0026#34;无权限进行此操作\u0026#34;, ResultCode.NOACCESS, null); String jsonString = JSON.toJSONString(result); DataBuffer wrap = response.bufferFactory().wrap(jsonString.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(wrap)); } } 3、自定义认证失败处理类\npublic class AuthenticationEntryPointImpl implements ServerAuthenticationEntryPoint { @Override public Mono\u0026lt;Void\u0026gt; commence(ServerWebExchange serverWebExchange, AuthenticationException e) { ServerHttpResponse response = serverWebExchange.getResponse(); response.getHeaders().add(\u0026#34;Content-Type\u0026#34;,\u0026#34;application/json;charset=utf-8\u0026#34;); ResultVO\u0026lt;Object\u0026gt; result = ResultVO.result(\u0026#34;认证失败或已在别处登录，请进行登录\u0026#34;, ResultCode.UNAUTHORIZED, null); String jsonString = JSON.toJSONString(result); DataBuffer wrap = response.bufferFactory().wrap(jsonString.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(wrap)); } } 4、获取请求头中带过来的token值，解析并认证用户信息\n@Component public class TokenSecurityContextRepository implements ServerSecurityContextRepository { @Autowired private RoleMapper roleMapper; /** * 由于无状态，所以不需要保存用户登录状态 * @param serverWebExchange * @param securityContext * @return */ @Override public Mono\u0026lt;Void\u0026gt; save(ServerWebExchange serverWebExchange, SecurityContext securityContext) { return Mono.empty(); } /** * 对用户身份进行验证 * @param exchange * @return */ @Override public Mono\u0026lt;SecurityContext\u0026gt; load(ServerWebExchange exchange) { // 从请求头中获取jwt String authToken = exchange.getRequest().getHeaders().getFirst(\u0026#34;login-token\u0026#34;); // 如果请求头没有token，直接放行，交给下面的过滤器处理 // 如果当前请求的接口需要认证或权限，下面的过滤器会拦截 if (Objects.isNull(authToken) || !StringUtils.hasText(authToken)){ return Mono.empty(); } // 验证token，以及redis中是否存在该token if (JwtUtil.verifyToken(authToken)){ Map\u0026lt;String, String\u0026gt; payload = JwtUtil.getPayload(authToken); String id = payload.get(\u0026#34;id\u0026#34;); String sub = payload.get(\u0026#34;sub\u0026#34;); String s = RedisFactory.select(0).opsForValue().get(sub); // 验证jwt中的id和redis中的id是否一致 if (!Objects.isNull(s) \u0026amp;\u0026amp; s.equals(id)) { //完成续签,刷新token的有效时间 RedisFactory.select(0).expire(sub, 100000, TimeUnit.SECONDS); //获取用户权限信息 List\u0026lt;String\u0026gt; list = roleMapper.selectUserRolesByEmail(sub); List\u0026lt;SimpleGrantedAuthority\u0026gt; authorities = new ArrayList\u0026lt;\u0026gt;(); for (String authority : list){ authorities.add(new SimpleGrantedAuthority(authority)); } SecurityContext securityContext = new SecurityContextImpl(); // 创建一个认证对象，第一个参数为用户信息，第三个参数为用户权限 UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(sub,null,authorities); // 传入认证对象到SecurityContext中，证明该用户已经认证 securityContext.setAuthentication(token); return Mono.justOrEmpty(securityContext); } } return Mono.empty(); } } 5、SercurityConf配置类\n@Configuration @EnableWebFluxSecurity public class SecurityConfig{ @Autowired private TokenSecurityContextRepository tokenSecurityContextRepository; @Bean SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http .csrf().disable() .formLogin().disable() .logout().disable() .securityContextRepository(tokenSecurityContextRepository) .authorizeExchange() .pathMatchers(HttpMethod.GET).permitAll() // 放开所有get请求 .pathMatchers(\u0026#34;/auth-server/user/login\u0026#34;).permitAll() // 放开账密登录请求 .anyExchange().authenticated() //其余请求都需要验证 .and() .exceptionHandling() .authenticationEntryPoint(new AuthenticationEntryPointImpl()) //授权失败处理器 .accessDeniedHandler(new AccessDeniedHandlerImpl()); // 验证失败处理器 return http.build(); } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/82b168e2/3ca7cd27/","section":"文章","summary":"\u003cp\u003e分布式开发时，微服务会有很多，但是网关是请求的第一入口，所以一般会把客户端请求的权限验证统一放在网关进行认证与鉴权。SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关，目标是替代 Zuul，为了提升网关的性能，SpringCloud Gateway是基于WebFlux框架实现的，而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。\u003c/p\u003e","title":"4、WebFlux中使用","type":"posts"},{"content":" 微服务调用 # 在微服务架构中，最常见的场景就是微服务之间的相互调用。比如，用户微服务调用订单微服务发起一个下单的请求，在进行保存订单之前，需要调用商品微服务查询商品的信息。我们把调用方称为服务消费者，把请求的接收者称为服务提供者\nRestTemplate # RestTemplate有Spring框架提供的一个用于后端服务器模拟发送HTTP请求的一种技术 它支持所有的HTTP标准方法，包含：GET，POST，PUT，DELETE等 常见用法 # getForObject\ngetForObject函数实际上是对getForEntity函数的进一步封装，如果你只关注返回的消息体的内容，对其他信息都不关注，此时可以使用getForObject postForObject\n如果你只关注，返回的消息体，可以直接使用postForObject。用法和getForObject一致。 getForEntity\ngetForEntity方法的返回值是一个ResponseEntity\u0026lt;T\u0026gt;， ResponseEntity\u0026lt;T\u0026gt;是Spring对HTTP请求响应的封装， 包括了几个重要的元素，如响应码、contentType、contentLength、响应消息体等。 可以用一个数字做占位符，最后是一个可变长度的参数，来一一替换前面的占位符 postForEntity\n方法的第一参数表示要调用的服务的地址 第二个参数表示上传的参数 第三个参数表示返回的消息体的数据类型 exchange\nResponseEntity\u0026lt;String\u0026gt; results = restTemplate.exchange(url,HttpMethod.GET, null, String.class, params); /** 说明：1）url: 请求地址； 2）method: 请求类型(如：POST,PUT,DELETE,GET)； 3）requestEntity: 请求实体，封装请求头，请求内容 4）responseType: 响应类型，根据服务接口的返回类型决定 5）uriVariables: url中参数变量值 */ Ribbon # 解决的问题 # RestTemplate可以实现微服务之间的相互调用，但是RestTemplate有一定的问题：\n必须要确定的具体地址 如果具体地址出问题了，就无法正常的做自动切换 微服务都是以集群的方式来部署，无法去选择集群中其他地址 解决方案：Ribbon\nRibbon是NetFlix的一个负载均衡器，例外它还可以同样使用RestTemplate完成对微服务之间的调用\n你可以认为：Ribbon就是在RestTemplate的基础之上，提供了负载均衡的能力\nRibbon一般是配合Eureka注册中心来使用，意味着：Ribbon可以从Eureka中去发现自己需要的微服务实例\n负载均衡 # Ribbon提供的这种负载均衡的原理，是一种客户端的负载均衡机制\n负载均衡：将负载（HTTP的请求）较为平均的分配到服务器上去\n负载均衡常见的算法：轮询，权重，IP地址黏贴，最少连接\n负载均衡的分类\n服务端负载均衡：服务端的这种方式，一定是有服务器（Apache/Nginx）在参与，它的原理：包工头-工人之间的关系，它是属于任务分配的一种方式 客户端负载均衡：客户端的负载均衡一般是发生在分布式应用程序内容，它的原理：是属于调用者与被调用者之间的关系 Ribbon使用 # 1、导入Ribbon的依赖，只要使用Eureka，那么程序已经自带了 # 2、开启负载均衡 # 在调用的一方，开启负载均衡\n配置Ribbon实际上就是开启它的负载均衡\n说Ribbon的底层就是RestTemplate，那么配置Ribbon实际上就是在配置RestTemplate\n//在产生RestTemplate实例时，使用@LoadBalanced注解，那么RestTemplate就拥有了负载均衡的能力 @LoadBalanced @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } 3、消费端调用方式 # public String getUrl(){ // 将之前的return \u0026#34;http://127.0.0.1:9091/\u0026#34;中 // 127.0.0.1:9091替换成 micro-base-server(被调用者的spring.application.name) return \u0026#34;http://micro-base-server/\u0026#34;; } Ribbon提供的负载均衡 # com.netflix.loadbalancer.RoundRobinRule 轮询（默认） com.netflix.loadbalancer.RandomRule 随机 com.netflix.loadbalancer.RetryRul 重试，该策略会先按照RoundRobinRule的策略获取服务，如果获取服务失败则在指定时间内会进行重试，获取可用的服务 com.netflix.loadbalancer.WeightedResponseTimeRule 权重，该策略根据平均响应时间计算所有服务的权重，响应时间越快的服务权重就越大，权重越大被选中的几率就越高。刚启动时如果统计信息不足，则使用RoundRobinRule策略，等到统计信息足够多的时候，则会自动切换到WeightedResponseTimeRule策略 com.netflix.loadbalancer.BestAvailableRule 最佳，该策略会先过滤掉由于多次出现访问故障而处于断路器跳闸状态的服务，然后选择一个并发量最小的服务 com.netflix.loadbalancer.AvailabilityFilteringRule 请求过滤，该策略会过滤掉由于多次出现访问故障而处于断路器跳闸状态的服务，过滤掉并发的连接数超过阈值的服务，然后对剩余的服务列表按照轮询策略进行访问 配置Ribbon # 全局配置 # 在配置类中进行定义\n@Configuration public class loadbanlancerConfig { @Bean public IRule myRandomRule() { return new RandomRule(); } } #ribbon的全局时间配置 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule ConnectTimeout: 250 #Ribbon的连接超时时间(毫秒)，默认1秒 ReadTimeout: 1000 #Ribbon的数据读取超时时间，默认1秒 OkToRetryOnAllOperations: true #是否对所有操作都进行重试 MaxAutoRetriesNextServer: 1 #切换实例的重试次数 MaxAutoRetries: 1 #对当前实例的重试次数 局部配置 # star-orders: #微服务的名称 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule ConnectTimeout: 250 #Ribbon的连接超时时间(毫秒)，默认1秒 ReadTimeout: 1000 #Ribbon的数据读取超时时间，默认1秒 OkToRetryOnAllOperations: true #是否对所有操作都进行重试 MaxAutoRetriesNextServer: 1 #切换实例的重试次数 MaxAutoRetries: 1 #对当前实例的重试次数 当全局配置和局部配置，同时存在时将以 “局部配置为主”\nOpenFeign # 解决的问题 # RestTemplate ，Ribbon都存在这么一个问题：代码耦合度很高。OpenFeign，它就是一种典型的接口式编程方案，从而可以做到微服务之间相互调用时，松散耦合关系\nFeign是Netﬂix开发的声明式，模板化的HTTP客户端\nFeign可帮助我们更加便捷，优雅的调用HTTP API\n在Spring Cloud中，使用Feign非常简单——创建一个接口，并在接口上添加一些注解，代码就完成了。\nSpring Cloud对Feign进行了增强，使Feign支持了Spring MVC注解，并整合了Ribbon和Eureka，从而让Feign的使用更加方便\nOpenFeign实际上就是对Ribbon接口化封装，所以feign的负载均衡和时间配置，也是和ribbon一样的\nFeign和OpenFeign的区别 # OpenFeign组件的使用 # 避坑：使用Feign，如果消费端传递对象类型的参数，那么服务端必须使用@RequestBody接参\n1、在消费者添加依赖 # \u0026lt;!-- OpenFeign的调用插件 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-openfeign\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、消费者开启对OpenFeign的支持 # 在启动类上，添加@EnableFeignClients，开启对OpenFeign的支持\n@EnableFeignClients @EnableEurekaClient @SpringBootApplication public class MicroUserServerApplication { public static void main(String[] args) { SpringApplication.run(MicroUserServerApplication.class, args); } } 3、定义feign接口 # //@FeignClient的value属性，定义了服务生产方的spring.application.name名称 @FeignClient(\u0026#34;star-product\u0026#34;) public interface ProductServerFeign { //无参get请求 @GetMapping(\u0026#34;/product/findAllProduct\u0026#34;) ResultVO findAllProduct(); //有参get请求，这种方法需要使用注解@PathVariable来声明参数位置 @GetMapping(\u0026#34;/product/findByPid/{pid}\u0026#34;) ResultVO findByPid(@PathVariable(\u0026#34;pid\u0026#34;) Integer pid); //有参post请求，这种方法需要在feign和提供者中使用@RequestBody来传参 @PostMapping(\u0026#34;/product/save\u0026#34;) ResultVO save(@RequestBody Product product); } 4、消费者控制层注入该接口实例，并且调用方法 # @Autowired private ProductServerFeign productServerFeign; @GetMapping(\u0026#34;/findAllProduct\u0026#34;) public ResultVO findAllProduct(){ ResultVO resultVO = productServerFeign.findAllProduct(); return resultVO; } @GetMapping(\u0026#34;/findByPid/{pid}\u0026#34;) public ResultVO findByPid(@PathVariable(\u0026#34;pid\u0026#34;) Integer pid){ ResultVO resultVO = productServerFeign.findByPid(); return resultVO; } @PostMapping(\u0026#34;/save\u0026#34;) public ResultVO save(Product product){ ResultVO resultVO = productServerFeign.save(product); return resultVO; } 日志打印 # Feign提供了日志打印功能,我们可以通过配置来调整日志级别，可以让我们程序员查看Feign中Http请求的细节\n开发环境以及测试环境，可以将Feign日志功能开启，但是生产环境下，请将日志功能关闭\n日志级别\nNONE（性能最佳，适用于生产）：不记录任何日志（默认值） BASIC（适用于生产环境追踪问题）：仅记录请求方法、URL、响应状态代码以及执行时间 HEADERS：记录BASIC级别的基础上，记录请求和响应的header FULL（比较适用于开发及测试环境定位问题）：记录请求和响应的header、body和元数据 具体配置\n打开调用方的application.ym并修改 feign: client: config: micro-base-server: #被调用者的微服务名称 loggerLevel: FULL logging: level: root: info #将日志下调至Debug top.ygang.microuserserver.classdemo.openFeign.MicroBaseServerFeign: debug ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/fd17f7dc/76fa8317/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e微服务调用 \n    \u003cdiv id=\"微服务调用\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%be%ae%e6%9c%8d%e5%8a%a1%e8%b0%83%e7%94%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在微服务架构中，最常见的场景就是微服务之间的相互调用。比如，用户微服务调用订单微服务发起一个下单的请求，在进行保存订单之前，需要调用商品微服务查询商品的信息。我们把调用方称为\u003cstrong\u003e服务消费者\u003c/strong\u003e，把请求的接收者称为\u003cstrong\u003e服务提供者\u003c/strong\u003e\u003c/p\u003e","title":"4、微服务互相调用","type":"posts"},{"content":" 背景和目标 # linux系统不能正常启动情况下，如何从系统中把里面的数据copy出来，具体思路如下\n1、进入救援模式，找到需要恢复的数据；\n2、进入救援模式下后，需要配置网络将数据传输出来；\n3、通过scp将数据传输到其他的服务器上；\n进入救援模式 # 1、准备一个Centos7的启动光盘或U盘\n2、挂载光盘或U盘、并进入系统BIOS界面（具体根据主板不同在开机时按不同的键）\n3、在Boot中选择光盘启动或U盘启动\n4、选择Troubleshooting\n5、选择Rescue a CentOS system\n6、选择1并回车\n注意：此时被救援系统将会挂载到救援系统的/mnt/sysimage目录下\n7、回车，就会进入一个Shell了\n配置网络 # 1、用ifconfig命令查看网卡名称\n2、配置局域网IP，自此系统就已经可以在局域网内操作了\nifconfig ens33 192.168.10.43/24 3、如果需要外网，就要配置DNS\nnmcli con mod ens33 ipv4.dns \u0026#34;192.168.10.1\u0026#34; 备份重要数据 # 上面的步骤网络已经打通了，就可以使用scp命令将重要数据传输到其他机器\nscp -r /mnt/sysimage/var/lib/mysql root@192.168.10.55:/data/mysqlback ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/26654e7b/1a145064/f06cc934/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e背景和目标 \n    \u003cdiv id=\"背景和目标\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%83%8c%e6%99%af%e5%92%8c%e7%9b%ae%e6%a0%87\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003elinux系统不能正常启动情况下，如何从系统中把里面的数据copy出来，具体思路如下\u003c/p\u003e","title":"4、救援模式","type":"posts"},{"content":"nvm（Node Version Manager）是Node.js的版本管理器，可以让我们轻松地在不同的Node.js版本之间进行切换。\n中文官网地址：https://nvm.uihtm.com/\nGitHub地址：https://github.com/nvm-sh/nvm\n安装 # Mac # 这里使用homebrew安装\nbrew install nvm # 配置环境变量 export NVM_DIR=~/.nvm source $(brew --prefix nvm)/nvm.sh export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node/ export NVM_IOJS_ORG_MIRROR=https://npmmirror.com/mirrors/iojs # 查看是否安装成功 nvm -v 常用命令 # nvm -v //查看nvm版本 nvm ls //查看本机已经安装node版本 nvm use \u0026lt;version\u0026gt; //指定使用的 node 版本 nvm ls-remote //列出所有可安装的远程node版本 nvm install stable //安装最新版本 nvm install \u0026lt;version\u0026gt; //安装指定版本 nvm uninstall stable //卸载最新版本 nvm uninstall \u0026lt;version\u0026gt; //卸载指定版本 nvm deactivate //取消当前nvm命令行效果，慎用。我使用之后发现会取消之前设置的alias，还有一些奇怪的现象，未做深究 nvm current //查看当前使用的node版本 nvm alias \u0026lt;name\u0026gt; \u0026lt;version\u0026gt; //给版本\u0026lt;version\u0026gt;设置一个别名 nvm alias default \u0026lt;version\u0026gt; //将制定版本设置为默认 nvm unalias \u0026lt;name\u0026gt; //删除\u0026lt;name\u0026gt;的版本别名 npm which [current|\u0026lt;version\u0026gt;] //显示已安装node的安装路径。 nvm cache dir //显示nvm的缓存目录 nvm cache clear //清除nvm的缓存目录 nvm node_mirror [url] //设置node镜像 nvm npm_mirror [url] //设置npm镜像 nvm on //开启node.js版本管理 nvm off //关闭node.js版本管理 ","date":"2025-05-23","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/79502aa8/36ce7762/","section":"文章","summary":"\u003cp\u003envm（Node Version Manager）是Node.js的版本管理器，可以让我们轻松地在不同的Node.js版本之间进行切换。\u003c/p\u003e","title":"5、nvm","type":"posts"},{"content":"time包提供了时间的显示和测量用的函数。日历的计算采用的是公历。\n基本使用 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main() { // 获取当前时间，返回一个time.Time类型的实例 now := time.Now() // 原格式输出 fmt.Println(now) // 2021-01-25 11:07:26.762506 +0800 CST m=+0.000160460 // 获取年月日时分秒 year := now.Year() month := now.Month() day := now.Day() hour := now.Hour() minute := now.Minute() second := now.Second() fmt.Printf(\u0026#34;%d年%02d月%02d日 %02d时%02d分%02d秒\u0026#34;, year, month, day, hour, minute, second) // 2021年01月25日 11时07分26秒 } 创建特定时间 # // 使用time.Date创建特定时间 birthDay := time.Date(1990, time.May, 15, 12, 30, 0, 0, time.Local) fmt.Println(\u0026#34;生日:\u0026#34;, birthDay) // 参数依次为：年、月、日、时、分、秒、纳秒、时区 deadline := time.Date(2023, time.December, 31, 23, 59, 59, 0, time.UTC) fmt.Println(\u0026#34;截止日期:\u0026#34;, deadline) Time与字符串转换 # time.Time时间类型有一个自带的方法Format()进行格式化，需要注意的是Go语言中格式化时间模板不是常见的Y-m-d H:M:S，而是使用Go的诞生时间2006年1月2号15点04分05秒（记忆口诀为2006 1 2 3 4 5），其中1、2、3、4、5，前面可以补0来代表结果的保留位数。\n占位符 含义 示例输出 占位符 含义 示例输出 2006 4位年份 2025 15 24小时制小时（00-23） 14（下午2点） 06 2位年份 25 03 12小时制小时（01-12） 02（下午2点） 01 2位月份（01-12） 05（五月） 04 分钟（00-59） 07 Jan 月份缩写 May 05 秒（00-59） 09 January 全称月份 May PM 上下午标识 PM 02 日期（01-31） 17 MST 时区缩写 CST（中国标准时间） Mon 星期缩写 Sat -0700 时区偏移（UTC±HHMM） +0800 Monday 全称星期 Saturday Time转字符串 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main() { now := time.Now() // 格式化，并且hour为24小时制 s1 := now.Format(\u0026#34;2006-01-02 15:04:05\u0026#34;) // 2021-01-25 16:11:56 fmt.Println(s1) // 格式化并且hour为12小时制 s2 := now.Format(\u0026#34;2006-01-02 03:04:05\u0026#34;) // 2021-01-25 04:11:56 fmt.Println(s2) } 字符串转Time # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main() { s1 := \u0026#34;2021-10-20 09:12:13\u0026#34; // 默认以UTC时区 t1, err := time.Parse(\u0026#34;2006-01-02 03:04:05\u0026#34;, s1) if err != nil { fmt.Println(err) } fmt.Println(t1) // 2021-10-20 09:12:13 +0000 UTC // 自定义时区 loc, err := time.LoadLocation(\u0026#34;Asia/Shanghai\u0026#34;) if err != nil { fmt.Println(err) } t2, err := time.ParseInLocation(\u0026#34;2006-01-02 15:04:05\u0026#34;, s1, loc) if err != nil { fmt.Println(err) } fmt.Println(t2) // 2021-10-20 09:12:13 +0800 CST } 内置格式化模板 # time包内置了一些格式化的模板，方便我们使用：\nconst ( Layout = \u0026#34;01/02 03:04:05PM \u0026#39;06 -0700\u0026#34; ANSIC = \u0026#34;Mon Jan _2 15:04:05 2006\u0026#34; UnixDate = \u0026#34;Mon Jan _2 15:04:05 MST 2006\u0026#34; RubyDate = \u0026#34;Mon Jan 02 15:04:05 -0700 2006\u0026#34; RFC822 = \u0026#34;02 Jan 06 15:04 MST\u0026#34; RFC822Z = \u0026#34;02 Jan 06 15:04 -0700\u0026#34; RFC850 = \u0026#34;Monday, 02-Jan-06 15:04:05 MST\u0026#34; RFC1123 = \u0026#34;Mon, 02 Jan 2006 15:04:05 MST\u0026#34; RFC1123Z = \u0026#34;Mon, 02 Jan 2006 15:04:05 -0700\u0026#34; RFC3339 = \u0026#34;2006-01-02T15:04:05Z07:00\u0026#34; RFC3339Nano = \u0026#34;2006-01-02T15:04:05.999999999Z07:00\u0026#34; Kitchen = \u0026#34;3:04PM\u0026#34; Stamp = \u0026#34;Jan _2 15:04:05\u0026#34; StampMilli = \u0026#34;Jan _2 15:04:05.000\u0026#34; StampMicro = \u0026#34;Jan _2 15:04:05.000000\u0026#34; StampNano = \u0026#34;Jan _2 15:04:05.000000000\u0026#34; DateTime = \u0026#34;2006-01-02 15:04:05\u0026#34; DateOnly = \u0026#34;2006-01-02\u0026#34; TimeOnly = \u0026#34;15:04:05\u0026#34; ) 时间戳与Time转换 # 时间戳代表当前时间距离1970-01-01 00:00:00的毫秒数\nTime转时间戳 # func (t Time) Unix() int64\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main() { now := time.Now() timestamp := now.Unix() // 毫秒时间戳 timestampNano := now.UnixNano() // 纳秒时间戳 fmt.Println(timestamp) // 1706153390 fmt.Println(timestampNano) // 1706153390764293000 } 时间戳转Time # func Unix(sec int64, nsec int64) Time，第一个参数是毫秒，第二个参数是纳秒\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main() { milliTimestamp := 1706153390 nanoTimestamp := 1706153390764293000 t1 := time.Unix(int64(milliTimestamp), 0) t2 := time.Unix(0, int64(nanoTimestamp)) fmt.Println(t1) // 2024-01-25 11:29:50 +0800 CST fmt.Println(t2) // 2024-01-25 11:29:50.764293 +0800 CST } 时间计算 # time.Duration是time包定义的一个类型，它代表两个时间点之间经过的时间，以纳秒为单位。time.Duration表示一段时间间隔，可表示的最长时间段大约290年。\ntime包中定义的时间间隔类型的常量如下：\ntype Duration int64 const ( // 纳秒 Nanosecond Duration = 1 // 微秒 Microsecond = 1000 * Nanosecond // 毫秒 Millisecond = 1000 * Microsecond // 秒 Second = 1000 * Millisecond // 分 Minute = 60 * Second // 时 Hour = 60 * Minute ) time.Duration实例提供了方法，可以将该实例换算为float64类型的时、分、秒、毫秒、纳秒\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main() { d := 6 * time.Hour fmt.Printf(\u0026#34;%T -- %v\\n\u0026#34;, d, d) // time.Duration -- 6h0m0s h := d.Hours() fmt.Printf(\u0026#34;%T -- %v\\n\u0026#34;, h, h) // float64 -- 6 m := d.Minutes() fmt.Printf(\u0026#34;%T -- %v\\n\u0026#34;, m, m) // float64 -- 360 } ADD # func (t Time) Add(d Duration) Time，计算t+d\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main() { now := time.Now() fmt.Println(now) // 2024-01-25 14:44:20.401889 +0800 CST m=+0.000288709 // 在now的基础上再加6小时 later := now.Add(time.Hour * 6) fmt.Println(later) // 2024-01-25 20:44:20.401889 +0800 CST m=+21600.000288709 // 在now的基础上再减6小时 befor := now.Add(-time.Hour * 6) fmt.Println(befor) // 2024-01-25 08:44:20.401889 +0800 CST m=-21599.999711291 } Sub # func (t Time) Sub(u Time) Duration，计算t-u\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main() { now := time.Now() fmt.Println(now) // 2024-01-25 14:44:20.401889 +0800 CST m=+0.000288709 // 在now的基础上再加6小时 later := now.Add(time.Hour * 6) fmt.Println(later) // 2024-01-25 20:44:20.401889 +0800 CST m=+21600.000288709 s := later.Sub(now) fmt.Println(s) // 6h0m0s } Equal # func (t Time) Equal(u Time) bool，判断两个时间是否相同，会考虑时区的影响，因此不同时区标准的时间也可以正确比较。\nBefore # func (t Time) Before(u Time) bool，如果t代表的时间点在u之前，返回真；否则返回假。\nAfter # func (t Time) After(u Time) bool，如果t代表的时间点在u之后，返回真；否则返回假。\n定时器 # Golang原生实现定时器的方法，主要包含如下两种：\n实现方法一 # 通过func NewTicker(d Duration) *Ticker来实现定时器\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main() { // 两秒执行一次 t := time.NewTicker(2 * time.Second) var tc \u0026lt;-chan time.Time = t.C for now := range tc { fmt.Println(now) } } /* 2024-01-25 14:58:48.028014 +0800 CST m=+2.001407710 2024-01-25 14:58:50.027334 +0800 CST m=+4.000730126 2024-01-25 14:58:52.028136 +0800 CST m=+6.001534835 ... */ 也可以使用Go封装好的time.Tick()方法，对时间间隔d进行了有效性判断\n// Tick is a convenience wrapper for NewTicker providing access to the ticking // channel only. While Tick is useful for clients that have no need to shut down // the Ticker, be aware that without a way to shut it down the underlying // Ticker cannot be recovered by the garbage collector; it \u0026#34;leaks\u0026#34;. // Unlike NewTicker, Tick will return nil if d \u0026lt;= 0. func Tick(d Duration) \u0026lt;-chan Time { if d \u0026lt;= 0 { return nil } return NewTicker(d).C } package main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main() { tc := time.Tick(2 * time.Second) for now := range tc { fmt.Println(now) } } /* 2024-01-25 14:58:48.028014 +0800 CST m=+2.001407710 2024-01-25 14:58:50.027334 +0800 CST m=+4.000730126 2024-01-25 14:58:52.028136 +0800 CST m=+6.001534835 ... */ 实现方法二 # 可以利用time.Sleep()时线程睡眠，搭配循环来实现\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main() { for { time.Sleep(2 * time.Second) fmt.Println(time.Now()) } } /* 2024-01-25 14:58:48.028014 +0800 CST m=+2.001407710 2024-01-25 14:58:50.027334 +0800 CST m=+4.000730126 2024-01-25 14:58:52.028136 +0800 CST m=+6.001534835 ... */ ","date":"2025-05-19","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/12d682f6/e572a0ed/","section":"文章","summary":"\u003cp\u003e\u003ccode\u003etime\u003c/code\u003e包提供了时间的显示和测量用的函数。日历的计算采用的是公历。\u003c/p\u003e","title":"5、time","type":"posts"},{"content":" 包（package） # Go语言是使用包来组织源代码的，包（package）是多个 Go 源码的集合，是一种高级的代码复用方案。Go语言中为我们提供了很多内置包，如 fmt、os、io 等。\n任何源代码文件必须属于某个包，同时源码文件的第一行有效代码必须是package pacakgeName语句，通过该语句声明自己所在的包。\n包的习惯用法 # 包可以定义在很深的目录中，包名的定义是不包括目录路径的，但是包在引用时一般使用全路径引用。比如在GOPATH/src/a/b/下定义一个包c。在包 c 的源码中只需声明为package c，而不是声明为package a/b/c，但是在导入 c 包时，需要带上路径，例如import \u0026quot;a/b/c\u0026quot; 包名一般是小写的，使用一个简短且有意义的名称。 包名一般要和所在的目录同名，也可以不同，包名中不能包含-等特殊符号。 包一般使用域名作为目录名称，这样能保证包名的唯一性，比如 GitHub 项目的包一般会放到%GOPATH%/src/github.com/userName/projectName 目录下。 包名为 main 的包为应用程序的入口包，编译不包含 main 包的源码文件时不会得到可执行文件。 一个文件夹下的所有源码文件只能属于同一个包，同样属于同一个包的源码文件不能放在多个文件夹下。 包的导入 # 导入语法 # //单行导入 import \u0026#34;fmt\u0026#34; //多行导入 import( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) 导入路径 # 1、全路径 # 根目录是GOPATH/src/\nimport \u0026#34;lab/test\u0026#34; import \u0026#34;database/sql/driver\u0026#34; 2、相对路径 # 相对路径只能用于导入GOPATH下的包，标准包的导入只能使用全路径导入。\n相对于当前包所在路径\nimport \u0026#34;../a\u0026#34; 包的引用 # 在Golang中，一个包下存在多个源文件，若要在某个源文件中引入其他源文件声明的函数、结构体、全局变量等成员，引用规则如下：\n同包：直接使用成员名称引用 不同包：要引用的成员名称必须首字母大写，并且使用包名.成员名称进行引用 internal包以及其子包中成员仅能在internal的父级包或兄弟包中引用。 例如有项目结构如下\n- controller - c1.go - c2.go - main.go - go.mod go.mod\nmodule ygang.top/demo go 1.21.5 c1.go\npackage controller var Contro1Variable int func Contro1() { } c2.go\npackage controller func Contro2() { Contro1() } main.go\npackage main import \u0026#34;top.ygang/demo/controller\u0026#34; func main() { controller.Contro1() controller.Contro2() controller.Contro1Variable = 10 } 1、标准引用 # 直接使用包名来引用\nimport \u0026#34;fmt\u0026#34; fmt.Println(\u0026#34;hello\u0026#34;) 2、别名引用 # 一般用于引用了两个同名包时区分使用\nimport F \u0026#34;fmt\u0026#34; F.Println(\u0026#34;hello\u0026#34;) 3、省略引用 # 把 fmt 包直接合并到当前程序中，在使用 fmt 包内的方法是可以不用加前缀fmt.，直接引用\nimport . \u0026#34;fmt\u0026#34; Println(\u0026#34;hello\u0026#34;) 4、匿名引用 # 在引用某个包时，如果只是希望执行包初始化的 init函数，而不使用包内部的数据时，可以使用匿名引用格式\nimport _ \u0026#34;fmt\u0026#34; 注意： 匿名导入的包与其他方式导入的包一样都会被编译到可执行文件中。 使用标准格式引用包，但是代码中却没有使用包，编译器会报错。如果包中有 init 初始化函数，则通过import _ \u0026quot;包的路径\u0026quot; 这种方式引用包，仅执行包的初始化函数，即使包没有 init 初始化函数，也不会引发编译器报错。 包加载流程 # Go语言包的初始化有如下特点：\n包初始化程序从 main 函数引用的包开始，逐级查找包的引用，直到找到没有引用其他包的包，最终生成一个包引用的有向无环图。 Go 编译器会将有向无环图转换为一棵树，然后从树的叶子节点开始逐层向上对包进行初始化。 单个包的初始化过程如上图所示，先初始化常量，然后是全局变量，最后执行包的 init 函数。 封装 # 封装的好处：\n隐藏实现细节； 可以对数据进行验证，保证数据安全合理。 如何体现封装：\n对结构体中的属性进行封装； 通过方法，包，实现封装。 封装的实现步骤：\n将结构体、字段的首字母小写； 给结构体所在的包提供一个工厂模式的函数，首字母大写，类似一个构造函数； 提供一个首字母大写的 Set 方法（类似其它语言的 public），用于对属性判断并赋值； 提供一个首字母大写的 Get 方法（类似其它语言的 public），用于获取属性的值。 init函数 # init()函数会在每个包完成初始化后自动执行，并且执行优先级比main函数高。init()函数通常被用来：\n对变量进行初始化 检查/修复程序的状态 注册 运行一次计算 init()函数无参数、无返回值\nfunc init(){ } 整个项目执行顺序遵循：\n依赖倒置原则：被导入的包优先初始化 包级别变量初始化 → init() 函数 → main() 函数 文件内顺序：同一包内多个 init() 按文件中的出现顺序执行 文件间顺序：同一包内多个文件的 init() 按文件名字母顺序执行 包管理 # GOPATH # 最初的时候Go语言所依赖的所有的第三方包都放在 GOPATH 目录下面，这就导致了同一个包只能保存一个版本的代码。\n现已被弃用。\ngodep（govendor） # 注意：该方式现已不推荐使用，原因：放弃了依赖重用，使得冗余度上升。同一个依赖包如果不同工程想重用，都必须各自复制一份在自己的vendor目录下。\ngodep 是一个Go语言官方提供的通过 vender 模式来管理第三方依赖的工具，类似的还有由社区维护的准官方包管理工具 dep。\nGo语言从 1.5 版本开始开始引入 vendor 模式，如果项目目录下有 vendor 目录，那么Go语言编译器会优先使用 vendor 内的包进行编译、测试等。\nGo Modules # Go Modules 是Go语言从 1.11 版本之后官方推出的版本管理工具，项目就不需要再放到GOPATH下了，并且从 Go1.13 版本开始，go module 成为了Go语言默认的依赖管理工具。\nGOMODULE模式和GOPATH模式一样都指定了依赖包存放的位置，而不是如vendor模式放入工程内，区别在于GOMODULE的go.mod文件中记录了所依赖包的具体版本，既实现了工程之间重用依赖包，且解决了GOPATH模式下不同工程无法依赖同一个包的不同版本的问题。\nGo Modules是Go语言的依赖管理系统，它解决了以下核心问题：\n版本管理：明确指定依赖的版本 可重现构建：确保在不同环境下构建结果一致 依赖隔离：不同项目可使用同一个包的不同版本 中心化索引：通过proxy机制加速依赖下载 一个Go Module本质上是一个包含Go包的目录，它有一个go.mod文件定义了模块路径和依赖需求。\n重要概念 # 在Go Modules中，我们需要理解几个核心概念：\n模块（Module）：一个版本化的代码单元，包含一个或多个Go包，由go.mod文件定义。 包（Package）：Go源码文件的集合，编译时的基本单位。 版本（Version）：使用语义化版本标识（如v1.2.3）的代码快照。 主版本（Major Version）：语义化版本的第一个数字，不同主版本可能有不兼容的API变化。 例如，一个模块可能是github.com/user/project，它包含多个包，如github.com/user/project/pkg1和github.com/user/project/pkg2。\nGO111MODULE # 在Go语言 1.12 版本之前，要启用 Go Modules 工具首先要设置环境变量 GO111MODULE，不过在Go语言 1.13 及以后的版本则不再需要设置环境变量。通过 GO111MODULE 可以开启或关闭 Go Modules 工具。\nGO111MODULE=off ：禁用 Go Modules，编译时会从 GOPATH 和 vendor 文件夹中查找包。 GO111MODULE=on：（1.16后默认值） 启用 Go Modules，编译时会忽略 GOPATH 和 vendor 文件夹，只根据 go.mod下载依赖。 GO111MODULE=auto：（1.16前默认值），当项目在 GOPATH/src目录之外，并且项目根目录有 go.mod 文件时，开启 Go Modules。 Windows 下开启 GO111MODULE 的命令为：\nset GO111MODULE=on #或者 set GO111MODULE=auto MacOS 或者 Linux 下开启 GO111MODULE 的命令为：\nexport GO111MODULE=on #或者 export GO111MODULE=auto 使用 Go Modules 的go mod init projectname命令后会在当前目录下生成一个go.mod 文件，并且在编译/运行当前目录下代码或者使用go get命令的时候会在当前目录下生成一个 go.sum 文件。\ngo.mod文件一旦创建后，它的内容将会被go toolchain全面掌控。go toolchain会在各类命令执行时，比如go get、go build、go mod等修改和维护go.mod文件。\ngo.mod # go.mod 文件记录了项目所有的依赖信息，其结构大致如下：\nmodule github.com/user/project go 1.16 require ( github.com/pkg/errors v0.9.1 golang.org/x/text v0.3.6 ) replace github.com/pkg/errors =\u0026gt; github.com/user/errors v0.9.1-custom exclude golang.org/x/net v1.2.3 go.mod 文件内提供了module、go、require、replace和exclude五个关键字。\nmodule：声明模块路径，这是模块的唯一标识符 go：声明编译模块所需的Go语言版本 require：列出模块的依赖及其版本 replace：替换依赖的模块路径或版本 exclude：排除依赖的特定版本 在go.mod文件中，某些依赖可能标记为// indirect，表示：\n这不是直接依赖，而是间接依赖 但由于某种原因（如传递依赖有冲突）需要在go.mod中显式列出 语义化版本控制 # Go Modules采用语义化版本控制（Semantic Versioning，简称SemVer）。版本号格式为：vX.Y.Z，其中：\nX：主版本号，变更表示不兼容的API修改 Y：次版本号，变更表示向后兼容的功能性新增 Z：修订号，变更表示向后兼容的问题修正 一些特殊的版本标识：\nv0.y.z：不稳定版本，API可能随时变化 v1.0.0及以上：稳定版本，遵循向后兼容原则 v2.0.0及以上：需要在模块路径中加入主版本后缀，如/v2 Go Modules的一个重要概念是最小版本选择（Minimal Version Selection, MVS）：当有多个模块依赖同一个模块的不同版本时，Go会选择其中最高的版本。\ngo.sum # go.sum 文件则是用来记录每个依赖包的版本及哈希值，如下所示：\ngithub.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM= github.com/astaxie/beego v1.12.0 h1:MRhVoeeye5N+Flul5PoVfD9CslfdoH+xqC/xvSQ5u2Y= github.com/astaxie/beego v1.12.0/go.mod h1:fysx+LZNZKnvh4GED/xND7jWtjCR6HzydR2Hh2Im57o= 常用的 go mod 命令 # 命令 作用 go mod download 下载依赖包到本地（默认为 GOPATH/pkg/mod 目录） go mod edit 编辑 go.mod 文件 go mod graph 打印模块依赖图 go mod init 初始化当前文件夹（项目），创建 go.mod 文件 go mod tidy 增加缺少的包，删除无用的包 go mod vendor 将依赖复制到 vendor 目录下 go mod verify 校验依赖 go mod why 解释为什么需要依赖 go get # go get命令用于从远程代码仓库（比如 Github ）上下载并安装代码包。\n注意：如果GO111MODULE=on或者auto，在go get下载包时候，会下载到GOPATH/pkg/mod，引入时也是同样的从这个目录开始；如果运行了go env -w GO111MODULE=off，那么在go get下载包时候，会下载到GOPATH/src 目录下\n常用命令标记：\n-d\t# 让命令程序只执行下载动作，而不执行安装动作。 -f\t# 仅在使用-u标记时才有效。该标记会让命令程序忽略掉对已下载代码包的导入路径的检查。如果下载并安装的代码包所属的项目是你从别人那里 Fork 过来的，那么这样做就尤为重要了。 -fix\t# 让命令程序在下载代码包后先执行修正动作，而后再进行编译和安装。 -insecure\t# 允许命令程序使用非安全的 scheme（如 HTTP ）去下载指定的代码包。如果你用的代码仓库（如公司内部的 Gitlab ）没有HTTPS 支持，可以添加此标记。请在确定安全的情况下使用它。 -t\t# 让命令程序同时下载并安装指定的代码包中的测试源码文件中依赖的代码包。 -u\t# 让命令利用网络来更新已有代码包及其依赖包。默认情况下，该命令只会从网络上下载本地不存在的代码包，而不会更新已有的代码包。 运行go get -u命令会将项目中的包升级到最新的次要版本或者修订版本； 运行go get -u=patch命令会将项目中的包升级到最新的修订版本； 运行go get [包名]@[版本号]命令会下载对应包的指定版本或者将对应包升级到指定的版本。 go get [包名]@[版本号]命令中版本号可以是 x.y.z 的形式，例如 go get foo@v1.2.3，也可以是 git 上的分支或 tag，例如 go get foo@master，还可以是 git 提交时的哈希值，例如 go get foo@e3702bed2。\n第三方包查询：https://pkg.go.dev/\n私有模块与认证 # 私有仓库配置 # 在企业环境中，经常需要使用私有Git仓库。Go提供了几种访问私有模块的方式：\n方法1：使用GOPRIVATE环境变量\n# 设置不经过代理的私有仓库 export GOPRIVATE=github.com/mycompany/*,gitlab.com/mycompany/* # 或者具体模块 export GOPRIVATE=github.com/mycompany/project 这告诉Go直接从源获取这些模块，而不是通过GOPROXY。\n方法2：使用Git凭证\nGo模块系统使用底层Git命令访问私有仓库，因此需要配置Git凭证：\n# HTTPS认证 git config --global credential.helper store # SSH认证 # 确保SSH密钥已经设置好并添加到Git服务 内部模块代理 # 对于大型组织，设置内部模块代理服务器是个好主意：\n加速依赖下载 缓存依赖，防止外部依赖不可用 审计和控制使用的第三方依赖 可以使用Athens或GoCenter等开源项目搭建内部代理：\n# 设置模块代理 export GOPROXY=https://goproxy.mycompany.com,direct 工作区模式 # 什么是工作区 # 工作区允许同时处理多个相关模块，特别适合以下场景：\n开发相互依赖的多个模块 同时修改主项目和其依赖 大型单仓库（monorepo）项目 工作区的强大之处在于它覆盖了go.mod文件中的一些设置，但仅在本地开发环境中生效，不会影响已发布的模块。\n创建工作区 # Go 1.18引入了工作区（Workspace）模式，简化了多模块项目的开发：\n# 创建工作区 go work init ./module1 ./module2 # 添加模块到工作区 go work use ./module3 # 移除模块 go work edit -dropuse=./module2 go.work # go.work文件示例：\ngo 1.18 use ( ./module1 ./module2 ) replace github.com/user/module3 =\u0026gt; ./module3 ","date":"2025-05-19","externalUrl":null,"permalink":"/posts/2d3150b2/02a0817f/76811851/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e包（package） \n    \u003cdiv id=\"包package\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8c%85package\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eGo语言是使用包来组织源代码的，包（package）是多个 Go 源码的集合，是一种高级的代码复用方案。Go语言中为我们提供了很多内置包，如 \u003ccode\u003efmt\u003c/code\u003e、\u003ccode\u003eos\u003c/code\u003e、\u003ccode\u003eio\u003c/code\u003e 等。\u003c/p\u003e","title":"5、依赖（包）管理","type":"posts"},{"content":" 文件上传 # 文件上传是Web应用的常见需求，从技术角度看，它是一种特殊的HTTP请求，具有以下特点：\n请求方法: 通常使用POST方法 Content-Type: 必须设置为multipart/form-data 请求体: 包含文件内容和可能的其他表单字段 在HTML中，实现文件上传表单需要：\n\u0026lt;form action=\u0026#34;/upload\u0026#34; method=\u0026#34;post\u0026#34; enctype=\u0026#34;multipart/form-data\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;file\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;submit\u0026#34; value=\u0026#34;上传\u0026#34; /\u0026gt; \u0026lt;/form\u0026gt; 其中enctype=\u0026quot;multipart/form-data\u0026quot;是必不可少的属性，它告诉浏览器使用multipart编码而不是默认的URL编码。\n基本概念 # multipart/form-data格式 # multipart/form-data是一种特殊的HTTP请求体格式，用于发送文件和表单数据。它的特点是：\n使用边界（boundary）字符串分隔不同的表单字段 每个部分都有自己的头部信息（Content-Disposition、Content-Type等） 支持同时发送文本数据和二进制数据 一个典型的multipart/form-data请求如下：\nPOST /upload HTTP/1.1 Host: example.com Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW ------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; name=\u0026#34;title\u0026#34; 这是文件标题 ------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; name=\u0026#34;file\u0026#34;; filename=\u0026#34;example.jpg\u0026#34; Content-Type: image/jpeg [二进制文件数据] ------WebKitFormBoundary7MA4YWxkTrZu0gW-- 这种格式允许我们发送任意数量的表单字段，包括文件和文本数据。\nGin中的文件处理接口 # Gin框架为文件上传提供了简单而强大的API，主要涉及以下几个接口：\nc.FormFile(name string)：获取单个上传的文件 c.MultipartForm()：获取包含所有上传文件的表单 c.SaveUploadedFile(file, dst)：保存上传的文件到指定路径 这些API基于Go标准库的multipart包，但提供了更简洁的使用方式。在处理文件时，Gin会返回以下类型：\n*multipart.FileHeader：包含文件元信息（文件名、大小等） multipart.File：文件内容的读取接口 单文件上传 # func uploadHandler(c *gin.Context) { // 获取上传的文件 file, err := c.FormFile(\u0026#34;file\u0026#34;) if err != nil { c.JSON(http.StatusBadRequest, gin.H{\u0026#34;error\u0026#34;: \u0026#34;获取文件失败\u0026#34;}) return } // 文件保存路径 dst := filepath.Join(\u0026#34;./uploads\u0026#34;, file.Filename) // 保存文件 if err := c.SaveUploadedFile(file, dst); err != nil { c.JSON(http.StatusInternalServerError, gin.H{\u0026#34;error\u0026#34;: \u0026#34;保存文件失败\u0026#34;}) return } c.JSON(http.StatusOK, gin.H{\u0026#34;message\u0026#34;: \u0026#34;文件上传成功\u0026#34;}) } 在路由中注册这个处理函数：\nrouter.POST(\u0026#34;/upload\u0026#34;, uploadHandler) router.MaxMultipartMemory = 8 \u0026lt;\u0026lt; 20 // 8 MiB MaxMultipartMemory用于限制上传文件在内存中的最大大小，超过这个大小的文件会被写入临时文件。\n文件信息获取 # 通过*multipart.FileHeader，我们可以获取上传文件的详细信息：\nfunc getFileInfo(c *gin.Context) { file, err := c.FormFile(\u0026#34;file\u0026#34;) if err != nil { c.JSON(http.StatusBadRequest, gin.H{\u0026#34;error\u0026#34;: \u0026#34;获取文件失败\u0026#34;}) return } // 获取文件信息 fileInfo := gin.H{ \u0026#34;filename\u0026#34;: file.Filename, \u0026#34;size\u0026#34;: file.Size, \u0026#34;header\u0026#34;: file.Header, } // 获取文件内容 f, err := file.Open() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{\u0026#34;error\u0026#34;: \u0026#34;打开文件失败\u0026#34;}) return } defer f.Close() // 读取文件前20个字节 buffer := make([]byte, 20) n, err := f.Read(buffer) if err != nil \u0026amp;\u0026amp; err != io.EOF { c.JSON(http.StatusInternalServerError, gin.H{\u0026#34;error\u0026#34;: \u0026#34;读取文件失败\u0026#34;}) return } fileInfo[\u0026#34;contentPreview\u0026#34;] = fmt.Sprintf(\u0026#34;%x\u0026#34;, buffer[:n]) c.JSON(http.StatusOK, fileInfo) } 保存上传文件 # Gin提供了c.SaveUploadedFile()方法简化文件保存过程，但你也可以自己控制文件保存过程：\nfunc customSaveFile(c *gin.Context) { file, header, err := c.Request.FormFile(\u0026#34;file\u0026#34;) if err != nil { c.JSON(http.StatusBadRequest, gin.H{\u0026#34;error\u0026#34;: \u0026#34;获取文件失败\u0026#34;}) return } defer file.Close() // 自定义文件名（添加时间戳防止重名） filename := fmt.Sprintf(\u0026#34;%d_%s\u0026#34;, time.Now().Unix(), header.Filename) // 确保目录存在 uploadDir := \u0026#34;./uploads\u0026#34; if err := os.MkdirAll(uploadDir, 0755); err != nil { c.JSON(http.StatusInternalServerError, gin.H{\u0026#34;error\u0026#34;: \u0026#34;创建目录失败\u0026#34;}) return } // 创建目标文件 dst, err := os.Create(filepath.Join(uploadDir, filename)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{\u0026#34;error\u0026#34;: \u0026#34;创建文件失败\u0026#34;}) return } defer dst.Close() // 复制文件内容 if _, err = io.Copy(dst, file); err != nil { c.JSON(http.StatusInternalServerError, gin.H{\u0026#34;error\u0026#34;: \u0026#34;保存文件内容失败\u0026#34;}) return } c.JSON(http.StatusOK, gin.H{ \u0026#34;message\u0026#34;: \u0026#34;文件上传成功\u0026#34;, \u0026#34;filename\u0026#34;: filename, }) } 多文件上传 # Gin支持同时上传多个文件，这需要使用c.MultipartForm()方法：\nfunc multipleFilesUpload(c *gin.Context) { // 解析multipart表单 form, err := c.MultipartForm() if err != nil { c.JSON(http.StatusBadRequest, gin.H{\u0026#34;error\u0026#34;: \u0026#34;解析表单失败\u0026#34;}) return } // 获取所有上传的文件 files := form.File[\u0026#34;files\u0026#34;] // 确保目录存在 uploadDir := \u0026#34;./uploads\u0026#34; if err := os.MkdirAll(uploadDir, 0755); err != nil { c.JSON(http.StatusInternalServerError, gin.H{\u0026#34;error\u0026#34;: \u0026#34;创建目录失败\u0026#34;}) return } // 处理每个文件 filenames := []string{} for _, file := range files { // 自定义文件名 filename := fmt.Sprintf(\u0026#34;%d_%s\u0026#34;, time.Now().UnixNano(), file.Filename) dst := filepath.Join(uploadDir, filename) // 保存文件 if err := c.SaveUploadedFile(file, dst); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ \u0026#34;error\u0026#34;: fmt.Sprintf(\u0026#34;保存文件 %s 失败\u0026#34;, file.Filename), }) return } filenames = append(filenames, filename) } c.JSON(http.StatusOK, gin.H{ \u0026#34;message\u0026#34;: \u0026#34;所有文件上传成功\u0026#34;, \u0026#34;filenames\u0026#34;: filenames, \u0026#34;count\u0026#34;: len(files), }) } 批量处理文件 # 对于多个文件的上传，我们通常需要批量处理，可以使用并发处理提高效率：\nfunc concurrentFilesUpload(c *gin.Context) { form, err := c.MultipartForm() if err != nil { c.JSON(http.StatusBadRequest, gin.H{\u0026#34;error\u0026#34;: \u0026#34;解析表单失败\u0026#34;}) return } files := form.File[\u0026#34;files\u0026#34;] // 创建上传目录 uploadDir := \u0026#34;./uploads\u0026#34; if err := os.MkdirAll(uploadDir, 0755); err != nil { c.JSON(http.StatusInternalServerError, gin.H{\u0026#34;error\u0026#34;: \u0026#34;创建目录失败\u0026#34;}) return } // 创建等待组和结果通道 var wg sync.WaitGroup results := make(chan gin.H, len(files)) // 启动多个goroutine处理文件上传 for i, file := range files { wg.Add(1) go func(idx int, fileHeader *multipart.FileHeader) { defer wg.Done() // 自定义文件名 filename := fmt.Sprintf(\u0026#34;%d_%s\u0026#34;, time.Now().UnixNano(), fileHeader.Filename) dst := filepath.Join(uploadDir, filename) // 保存文件 err := c.SaveUploadedFile(fileHeader, dst) // 发送处理结果 results \u0026lt;- gin.H{ \u0026#34;index\u0026#34;: idx, \u0026#34;filename\u0026#34;: fileHeader.Filename, \u0026#34;saved_as\u0026#34;: filename, \u0026#34;success\u0026#34;: err == nil, \u0026#34;error\u0026#34;: err, } }(i, file) } // 等待所有goroutine完成 go func() { wg.Wait() close(results) }() // 收集结果 fileResults := []gin.H{} for result := range results { fileResults = append(fileResults, result) } c.JSON(http.StatusOK, gin.H{ \u0026#34;message\u0026#34;: \u0026#34;文件处理完成\u0026#34;, \u0026#34;results\u0026#34;: fileResults, \u0026#34;count\u0026#34;: len(fileResults), }) } 这段代码使用goroutine并发处理多个文件上传，适合处理大量文件的场景。\n文件验证与安全 # 为了安全起见，通常需要验证上传文件的类型，有以下几种方法：\n检查文件扩展名 # func validateFileExtension(filename string, allowedExts []string) bool { ext := strings.ToLower(filepath.Ext(filename)) for _, allowedExt := range allowedExts { if ext == allowedExt { return true } } return false } // 使用示例 func uploadWithExtensionCheck(c *gin.Context) { file, err := c.FormFile(\u0026#34;file\u0026#34;) if err != nil { c.JSON(http.StatusBadRequest, gin.H{\u0026#34;error\u0026#34;: \u0026#34;获取文件失败\u0026#34;}) return } // 检查文件扩展名 allowedExts := []string{\u0026#34;.jpg\u0026#34;, \u0026#34;.jpeg\u0026#34;, \u0026#34;.png\u0026#34;, \u0026#34;.gif\u0026#34;} if !validateFileExtension(file.Filename, allowedExts) { c.JSON(http.StatusBadRequest, gin.H{ \u0026#34;error\u0026#34;: \u0026#34;不支持的文件类型，仅支持 JPG、JPEG、PNG 和 GIF\u0026#34;, }) return } // ... 保存文件 ... } 检查MIME类型 # func validateMimeType(file *multipart.FileHeader, allowedTypes []string) (bool, error) { f, err := file.Open() if err != nil { return false, err } defer f.Close() // 读取文件前512字节用于类型检测 buffer := make([]byte, 512) _, err = f.Read(buffer) if err != nil \u0026amp;\u0026amp; err != io.EOF { return false, err } // 检测内容类型 contentType := http.DetectContentType(buffer) for _, allowedType := range allowedTypes { if strings.HasPrefix(contentType, allowedType) { return true, nil } } return false, nil } // 使用示例 func uploadWithMimeCheck(c *gin.Context) { file, err := c.FormFile(\u0026#34;file\u0026#34;) if err != nil { c.JSON(http.StatusBadRequest, gin.H{\u0026#34;error\u0026#34;: \u0026#34;获取文件失败\u0026#34;}) return } // 检查MIME类型 allowedTypes := []string{\u0026#34;image/jpeg\u0026#34;, \u0026#34;image/png\u0026#34;, \u0026#34;image/gif\u0026#34;} valid, err := validateMimeType(file, allowedTypes) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{\u0026#34;error\u0026#34;: \u0026#34;检验文件类型失败\u0026#34;}) return } if !valid { c.JSON(http.StatusBadRequest, gin.H{ \u0026#34;error\u0026#34;: \u0026#34;不支持的文件类型，仅支持JPEG、PNG和GIF图片\u0026#34;, }) return } // ... 保存文件 ... } 文件大小限制 # 控制上传文件的大小对于防止服务器资源耗尽至关重要：\n// 设置全局上传文件大小限制 router.MaxMultipartMemory = 8 \u0026lt;\u0026lt; 20 // 8 MiB // 在处理函数中验证文件大小 func uploadWithSizeCheck(c *gin.Context) { file, err := c.FormFile(\u0026#34;file\u0026#34;) if err != nil { c.JSON(http.StatusBadRequest, gin.H{\u0026#34;error\u0026#34;: \u0026#34;获取文件失败\u0026#34;}) return } // 检查文件大小 const maxSize = 5 * 1024 * 1024 // 5 MB if file.Size \u0026gt; maxSize { c.JSON(http.StatusBadRequest, gin.H{ \u0026#34;error\u0026#34;: \u0026#34;文件太大，大小不能超过5MB\u0026#34;, }) return } // ... 保存文件 ... } ","date":"2025-05-19","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/1bd46fa5/938a7b8a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e文件上传 \n    \u003cdiv id=\"文件上传\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%87%e4%bb%b6%e4%b8%8a%e4%bc%a0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e文件上传是Web应用的常见需求，从技术角度看，它是一种特殊的HTTP请求，具有以下特点：\u003c/p\u003e","title":"5、文件上传","type":"posts"},{"content":" 并发编程概念 # 并发从程序设计的角度，就是希望通过某些机制让计算机可以在一个时间段内，执行多个任务。让一个或多个物理 CPU 在多个程序之间多路复用，提高对计算机资源的利用率。\nGo 语言通过编译器运行时（runtime），从语言上支持了并发的特性。Go 语言的并发通过 goroutine 特性完成。goroutine 类似于线程，但是可以根据需要创建多个 goroutine 并发工作**。goroutine 是由 Go 语言的运行时调度完成，而线程是由操作系统调度完成。**\n并发编程实现模型 # 并发模型一般分为：多进程编程、多线程编程、非阻塞/异步 IO 编程以及基于协程的编程。\n多进程 多进程是在操作系统层面进行并发的基本模式。同时也是开销最大的模式。在Linux平台上，很多工具链正是采用这种模式在工作。 比如某个 Web 服务器，它会有专门的进程负责网络端口的监听和链接管理，还会有专门的进程负责事务和运算。这种方法的好处在于简单、进程间互不影响，坏处在于系统开销大，因为所有的进程都是由内核管理的。 多线程 在大部分操作系统上都属于系统层面的并发模式，也是我们使用最多的最有效的一种模式。目前，我们所见的几乎所有工具链都会使用这种模式。它比多进程的开销小很多，但是其开销依旧比较大，且在高并发模式下，效率会有影响。 基于回调的非阻塞/异步 IO 这种架构的诞生实际上来源于多线程模式的危机。在很多高并发服务器开发实践中，使用多线程模式会很快耗尽服务器的内存和 CPU 资源。 而这种模式通过事件驱动的方式使用异步 IO，使服务器持续运转，且尽可能地少用线程，降低开销，它目前在 Node.js 中得到了很好的实践。但是使用这种模式，编程比多线程要复杂，因为它把流程做了分割，对于问题本身的反应不够自然。 协程 协程（Coroutine）本质上是一种用户态线程，不需要操作系统来进行抢占式调度，且在真正的实现中寄存于线程中，因此，系统开销极小，可以有效提高线程的任务并发性，而避免多线程的缺点。 使用协程的优点是编程简单，结构清晰；缺点是需要语言的支持，如果不支持，则需要用户在程序中自行实现调度器。目前，原生支持协程的语言还很少。 常见概念 # 进程/线程 进程是程序在操作系统中的一次执行过程，系统进行资源分配和调度的一个独立单位。 线程是进程的一个执行实体，是 CPU 调度和分派的基本单位，它是比进程更小的能独立运行的基本单位。 一个进程可以创建和撤销多个线程，同一个进程中的多个线程之间可以并发执行。 并发/并行 多线程程序在单核心的 cpu 上运行，也就是没有真正意义上同时执行，称为并发；多线程程序在多核心的 cpu 上运行，称为并行。 并发与并行并不相同，并发主要由切换时间片来实现“同时”运行，并行则是直接利用多核实现多线程的运行，Go程序可以设置使用核心数，以发挥多核计算机的能力。 协程/线程 协程：独立的栈空间，共享堆空间，调度由用户自己控制，本质上有点类似于用户级线程（即go中的goroutine，轻量级线程），这些用户级线程的调度也是自己实现的。 线程：一个线程上可以跑多个协程，协程是轻量级的线程。 goroutine与线程的区别 # 极低的创建和切换成本 goroutine的初始栈大小仅有2KB（Go 1.18后为2KB，此前是8KB） 而线程的栈通常是MB级别 动态栈大小 goroutine的栈大小可以根据需要动态增长和收缩 最大可达1GB（64位系统） 高效的调度模型 Go运行时实现了M:N的调度模型 多个goroutine（N）可以在少量系统线程（M）上运行 避免了上下文切换的高成本 与线程的数量对比 系统可以轻松支持数十万甚至数百万的goroutine同时运行 创建同等数量的线程会耗尽系统资源 GMP模型 # Go调度器基于GMP模型工作\nG：goroutine，Go协程，是参与调度与执行的最小单位 M：machine，操作系统线程 P:：processor，调度上下文，负责连接M和G CSP 模型 # Go的并发哲学基于CSP（Communicating Sequential Processes，通信顺序进程）模型。\nCSP原则 # CSP模型的核心思想是：不要通过共享内存来通信，而要通过通信来共享内存\n这一原则鼓励通过显式的消息传递（channel）而非共享状态来协调并发执行，从而减少锁和同步原语的使用。\n并发模式对比 # 模式 特点 典型语言/库 多线程共享内存 使用锁保护共享状态 Java, C++ Actor模型 独立actors通过消息通信 Erlang, Akka CSP模型 通过通道协调并发进程 Go goroutine # goroutine 是协程的 Go 语言实现，它是语言原生支持的，相对于一般由库实现协程的方式，goroutine 更加强大，它的调度一定程度上是由 go 运行时（runtime）管理。\n其好处之一是，当某 goroutine 发生阻塞时（例如同步IO操作等），会自动出让 CPU 给其它 goroutine。\ngoroutine是非常轻量级的，它就是一段代码，一个函数入口，以及在堆上为其分配的一个堆栈（初始大小为4K，会随着程序的执行自动增长删除）。所以它非常廉价，我们可以很轻松的创建上万个 goroutine。\nGo 程序从 main 包的 main() 函数开始，在程序启动时，Go 程序就会为 main() 函数创建一个默认的 goroutine。\n启动与调度 # 启动 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func sayHello() { fmt.Println(\u0026#34;Hello from goroutine!\u0026#34;) } func main() { // 启动一个goroutine go sayHello() // 防止主goroutine退出太快 time.Sleep(100 * time.Millisecond) fmt.Println(\u0026#34;Main function finished\u0026#34;) } go sayHello() 启动了一个新的goroutine执行sayHello函数 主函数不会等待goroutine完成，需要通过time.Sleep让主goroutine等待 调度时机 # Go运行时会在以下情况触发goroutine调度：\n系统调用：当goroutine执行系统调用时（如I/O操作） 通道操作：在channel上的发送和接收操作可能导致阻塞 垃圾回收：GC工作时可能触发调度 显式调用：通过runtime.Gosched()主动让出CPU 等待锁：当争用sync包中的互斥锁时 函数调用：当函数调用太深且栈需要扩容时 如下，则是主动让出 CPU\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;runtime\u0026#34; ) func main() { go func() { for i := 0; i \u0026lt; 3; i++ { fmt.Println(\u0026#34;Goroutine:\u0026#34;, i) } }() for i := 0; i \u0026lt; 3; i++ { // 让出CPU，给其他goroutine执行的机会 runtime.Gosched() fmt.Println(\u0026#34;Main:\u0026#34;, i) } } channel # channel是Go语言提供的一种数据结构，它像一个管道，可以在不同的goroutine之间安全地传递数据。channel有以下特点：\n类型安全：每个channel只能传递指定类型的数据 并发安全：channel的操作是原子的，不需要额外的锁 FIFO顺序：数据按照先进先出的顺序从channel中读取 阻塞机制：可以用于goroutine之间的同步 从本质上讲，channel是一个数据结构，内部包含一个缓冲区、一个互斥锁以及两个等待队列（发送者队列和接收者队列）。\n为什么需要channel # 在并发编程中，我们通常需要解决以下问题：\n数据共享：多个goroutine需要安全地共享数据 工作分发：将任务分配给多个worker goroutine 信号通知：发送事件信号（如完成、取消等） 同步控制：协调不同goroutine的执行顺序 传统的共享内存并发模型通常使用锁来解决这些问题，但锁机制容易导致复杂性增加、死锁和性能问题。channel提供了一种更简洁、更符合Go语言哲学的方案。\n声明和创建 # 定义一个 channel 时，也需要定义发送到 channel 的值的类型，注意，chan 类型的空值是 nil，必须使用 make 创建 channel\n// 声明，chanName:通道变量名称，chanType:通道内的数据类型 var chanName chan chanType // 声明并定义 var chanName chan chanType := make(chan chanType) // 声明并定义，使用自动类型推导 chanName := make(chan chanType) // 例子 var c chan int = make(chan int) c := make(chan int) c := make(chan string) 关闭通道 # 关闭 channel 非常简单，直接使用Go语言内置的 close() 函数即可\nclose(chName) 关闭channel时应遵循以下原则：\n发送方负责关闭：通常，发送数据的一方应该负责关闭channel 不要关闭只接收的channel：从类型安全的角度，不应该关闭只接收的channel 使用defer关闭：在适当的地方使用defer确保channel被关闭 不确定时不要关闭：如果不确定是否需要关闭，或者不知道何时关闭，可以不关闭它 发送和接收 # 通道创建后，就可以使用通道进行发送和接收操作。\n发送 # 通道的发送使用特殊的操作符\u0026lt;-，将数据通过通道发送的格式为：\nchanName \u0026lt;- value chanName：通过make创建好的通道实例。 value：可以是变量、常量、表达式或者函数返回值等。值的类型必须与ch通道的元素类型一致。 把数据往通道中发送时，如果接收方一直都没有接收，那么发送操作将持续阻塞。Go 程序运行时能智能地发现一些永远无法发送成功的语句并做出提示，例如，现在创建一个chan，但是没有接收方：\npackage main func main() { ch := make(chan string) ch \u0026lt;- \u0026#34;hello\u0026#34; } /* fatal error: all goroutines are asleep - deadlock! */ 运行时发现所有的 goroutine（包括main）都处于等待 goroutine。也就是说所有 goroutine 中的 channel 并没有形成发送和接收对应的代码。\n接收 # 通道的收发操作在不同的两个 goroutine 间进行。 由于通道的数据在没有接收方处理时，数据发送方会持续阻塞，因此通道的接收必定在另外一个 goroutine 中进行。 接收将持续阻塞直到发送方发送数据。 如果接收方接收时，通道中没有发送方发送数据，接收方也会发生阻塞，直到发送方发送数据为止。 每次接收一个元素。 通道一次只能接收一个数据元素。 阻塞接收数据 # 阻塞模式接收数据时，将接收变量作为\u0026lt;-操作符的左值，执行该语句时将会阻塞，直到接收到数据并赋值给 value变量。\nvalue := \u0026lt;-chanName 阻塞接收数据后，忽略从通道返回的数据，执行该语句时将会发生阻塞，直到接收到数据，但接收到的数据会被忽略。这个方式实际上只是通过通道在 goroutine 间阻塞收发实现并发同步。\n\u0026lt;-chanName 非阻塞接收数据 # 使用非阻塞方式从通道接收数据时，语句不会发生阻塞，非阻塞的通道接收方法可能造成高的 CPU 占用，因此使用非常少。\nvalue, ok := \u0026lt;-chanName /* value：表示接收到的数据。未接收到数据时，value 为通道类型的零值。 ok：表示是否接收到数据，false表示通道已经关闭且为空 */ 循环接收 # 通道的数据接收可以借用 for range 语句进行多个元素的接收操作，使用后所在的goroutine将会阻塞循环接收\nch := make(chan int, 3) go func() { ch \u0026lt;- 1 ch \u0026lt;- 2 ch \u0026lt;- 3 close(ch) // 必须关闭，否则下面的range循环会死锁 }() for v := range ch { fmt.Println(v) } 单向通道 # 所谓的单向 channel 概念，其实只是对 channel 的一种使用限制，比如限制一个通道在这个函数中的读写，因此，单向通道有利于代码接口的严谨性。\n// 只写的通道 var chanName chan\u0026lt;- chanType // 只读的通道 var chanName \u0026lt;-chan chanType 类型转换关系\n双向channel可以转换为单向channel，但反之不行 这种转换通常用于函数参数，限制函数对channel的操作 func send(ch chan\u0026lt;- int) { ch \u0026lt;- 42 // 只能发送 // \u0026lt;-ch // 编译错误：不能从只发送channel接收 } func receive(ch \u0026lt;-chan int) { v := \u0026lt;-ch // 只能接收 // ch \u0026lt;- 42 // 编译错误：不能向只接收channel发送 } func main() { ch := make(chan int) // 双向channel go send(ch) // 可以将双向channel传给只发送channel参数 go receive(ch) // 可以将双向channel传给只接收channel参数 } 无缓冲与缓冲channel # 无缓冲通道 # Go语言中无缓冲的通道（unbuffered channel）是指在接收前没有能力保存任何值的通道。这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好，才能完成发送和接收操作。\n如果两个 goroutine 没有同时准备好，通道会导致先执行发送或接收操作的 goroutine 阻塞等待。这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在。\n阻塞指的是由于某种原因数据没有到达，当前协程（线程）持续处于等待状态，直到条件满足才解除阻塞。 同步指的是在两个或多个协程（线程）之间，保持数据内容一致性的机制。 创建无缓冲的通道 # chanName := make(chan chanType) chanName := make(chan chanType,0) // 显式指定缓冲大小为0 缓冲通道 # Go语言中有缓冲的通道（buffered channel）是一种在被接收前能存储一个或者多个值的通道。这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时，接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时，发送动作才会阻塞。\n这导致有缓冲的通道和无缓冲的通道之间的一个很大的不同**：无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换；有缓冲的通道没有这种保证。**\n在无缓冲通道的基础上，为通道增加一个有限大小的存储空间形成带缓冲通道。带缓冲通道在发送时无需等待接收方接收即可完成发送过程，并且不会发生阻塞，只有当存储空间满时才会发生阻塞。同理，如果缓冲通道中有数据，接收时将不会发生阻塞，直到通道中没有数据可读时，通道将会再度阻塞。\n创建带缓冲的通道 # chanName := make(chan chanType,size) /* chanName:通道实例变量名 chanType:通道所内数据类型 size:通道缓冲区大小 */ select 语句 # 工作原理 # 评估所有channel表达式：首先计算所有case中的channel表达式 评估所有发送/接收操作：尝试在所有channel上执行发送或接收操作 阻塞或执行： 如果有一个或多个case准备好（可发送或可接收），Go会随机选择一个执行 如果没有case准备好且有default分支，则执行default分支 如果没有case准备好且没有default分支，则select语句阻塞，直到某个case准备好 语法 # select { case \u0026lt;-ch1: // 如果从ch1成功接收数据，则执行此分支 case ch2 \u0026lt;- value: // 如果成功向ch2发送数据，则执行此分支 case x := \u0026lt;-ch3: // 如果从ch3成功接收数据，则执行此分支，并将接收的值赋给x default: // 如果上面的case都没有准备好，则执行此分支（可选） } 特性与规则 # 随机选择：当多个case同时准备好时，select会随机选择一个执行，这避免了固定顺序可能导致的饥饿问题 零case：空的select语句（select{}）会永远阻塞 无匹配死锁检测：如果select语句中没有default分支，且所有case都阻塞，则当前goroutine会被阻塞；如果所有goroutine都被阻塞，Go运行时会检测到死锁并报错 default避免阻塞：包含default分支的select语句永远不会阻塞 channel 应用场景 # 信号通知 # 使用channel发送信号通知其他goroutine某个事件已经发生\nfunc worker(done chan struct{}) { fmt.Println(\u0026#34;工作开始...\u0026#34;) time.Sleep(3 * time.Second) fmt.Println(\u0026#34;工作完成\u0026#34;) done \u0026lt;- struct{}{} // 发送完成信号 } func main() { done := make(chan struct{}) go worker(done) \u0026lt;-done // 等待工作完成 fmt.Println(\u0026#34;收到完成信号，主程序继续执行\u0026#34;) } 控制超时 # 结合select和time.After实现超时控制：\nfunc main() { ch := make(chan string) go func() { time.Sleep(2 * time.Second) ch \u0026lt;- \u0026#34;操作完成\u0026#34; }() select { case result := \u0026lt;-ch: fmt.Println(result) case \u0026lt;-time.After(1 * time.Second): fmt.Println(\u0026#34;操作超时\u0026#34;) } } 工作池模式 # 使用带缓冲的channel实现工作池，限制并发数量\nfunc worker(id int, jobs \u0026lt;-chan int, results chan\u0026lt;- int) { for job := range jobs { fmt.Printf(\u0026#34;worker %d started job %d\\n\u0026#34;, id, job) time.Sleep(time.Second) // 模拟工作耗时 fmt.Printf(\u0026#34;worker %d finished job %d\\n\u0026#34;, id, job) results \u0026lt;- job * 2 } } func main() { jobs := make(chan int, 100) results := make(chan int, 100) // 启动3个worker for w := 1; w \u0026lt;= 3; w++ { go worker(w, jobs, results) } // 发送9个任务 for j := 1; j \u0026lt;= 9; j++ { jobs \u0026lt;- j } close(jobs) // 收集结果 for a := 1; a \u0026lt;= 9; a++ { \u0026lt;-results } } 流水线模式 # 使用channel组合多个处理阶段，形成数据流水线：\nfunc generator(nums ...int) \u0026lt;-chan int { out := make(chan int) go func() { defer close(out) for _, n := range nums { out \u0026lt;- n } }() return out } func square(in \u0026lt;-chan int) \u0026lt;-chan int { out := make(chan int) go func() { defer close(out) for n := range in { out \u0026lt;- n * n } }() return out } func main() { // 组合成处理流水线: generator -\u0026gt; square -\u0026gt; consumer(打印) for n := range square(generator(1, 2, 3, 4, 5)) { fmt.Println(n) } } sync 包 # 上面的goroutine和channel。这些机制主要基于CSP（通信顺序进程）模型，强调\u0026quot;通过通信来共享内存\u0026quot;。然而，在某些情况下，我们需要直接控制对共享资源的访问。这时，Go语言标准库中的sync包就派上用场了。\nMutex（互斥锁） # Mutex提供了一种互斥机制，确保同一时间只有一个goroutine可以访问共享资源。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) func main() { var mutex sync.Mutex counter := 0 // 并发更新counter var wg sync.WaitGroup for i := 0; i \u0026lt; 1000; i++ { wg.Add(1) go func() { defer wg.Done() mutex.Lock() // 加锁 defer mutex.Unlock() // 解锁 counter++ }() } wg.Wait() fmt.Println(\u0026#34;计数器最终值:\u0026#34;, counter) // 输出: 计数器最终值: 1000 } 常用方法 # Lock()：获取锁。如果锁已被其他goroutine获取，则阻塞直到锁可用 Unlock()：释放锁。应在与Lock()相同的goroutine中调用 TryLock()：（Go 1.18+）尝试获取锁，如果锁不可用则立即返回false而不阻塞 注意点 # 总是使用defer mutex.Unlock()确保锁被释放 尽量减小临界区（加锁和解锁之间的代码） 避免在持有锁的情况下调用可能阻塞的操作 不要在goroutine A中锁定，然后在goroutine B中解锁 RWMutex（读写锁） # RWMutex允许多个读操作并发执行，但写操作是互斥的。当有写锁时，所有的读操作都会被阻塞。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) func main() { var rwMutex sync.RWMutex data := make(map[string]string) // 写入操作 go func() { for i := 0; i \u0026lt; 10; i++ { rwMutex.Lock() // 写锁 key := fmt.Sprintf(\u0026#34;key%d\u0026#34;, i) data[key] = fmt.Sprintf(\u0026#34;value%d\u0026#34;, i) time.Sleep(100 * time.Millisecond) // 模拟写入耗时 rwMutex.Unlock() time.Sleep(200 * time.Millisecond) // 给读操作时间 } }() // 多个并发读取操作 var wg sync.WaitGroup for r := 0; r \u0026lt; 5; r++ { wg.Add(1) go func(id int) { defer wg.Done() for i := 0; i \u0026lt; 10; i++ { rwMutex.RLock() // 读锁 for k, v := range data { fmt.Printf(\u0026#34;读取者 %d: %s = %s\\n\u0026#34;, id, k, v) } rwMutex.RUnlock() time.Sleep(150 * time.Millisecond) } }(r) } wg.Wait() } 常用方法 # Lock()/Unlock()：获取/释放写锁。与Mutex相同，写操作是互斥的 RLock()/RUnlock()：获取/释放读锁。多个goroutine可以同时持有读锁 TryLock()/TryRLock()：（Go 1.18+）尝试获取写锁/读锁，非阻塞 使用场景 # 当共享资源的读操作远多于写操作时，RWMutex比Mutex更有效。例如：\n配置信息：频繁读取，偶尔更新 缓存系统：大量读取，少量写入 统计数据：持续读取，定期更新 WaitGroup（等待组） # WaitGroup用于等待一组goroutine完成执行。它提供了一种简单的方式来协调多个并发操作的完成。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // 工作完成时通知WaitGroup fmt.Printf(\u0026#34;Worker %d starting\\n\u0026#34;, id) time.Sleep(time.Second) // 模拟工作 fmt.Printf(\u0026#34;Worker %d done\\n\u0026#34;, id) } func main() { var wg sync.WaitGroup // 启动5个worker for i := 1; i \u0026lt;= 5; i++ { wg.Add(1) // 增加计数器 go worker(i, \u0026amp;wg) } // 等待所有worker完成 wg.Wait() fmt.Println(\u0026#34;All workers completed\u0026#34;) } 常用方法 # Add(delta int)：增加WaitGroup的计数器值 Done()：减少WaitGroup的计数器值，相当于Add(-1) Wait()：阻塞直到计数器变为0 注意点 # 正确设置计数器：在启动goroutine之前调用Add() 总是通过指针传递WaitGroup：WaitGroup不应被复制 使用defer语句确保Done()被调用 Once（只被执行一次） # Once用于确保某个函数只被执行一次，即使在多个goroutine中并发调用也是如此。它通常用于单例模式、延迟初始化或执行只需要一次的设置操作。\n在下面例子中，尽管有10个goroutine尝试执行初始化函数，但once.Do()确保只有一个goroutine能够执行它。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; ) func main() { var once sync.Once done := make(chan bool) // 尝试在多个goroutine中执行初始化 for i := 0; i \u0026lt; 10; i++ { go func(id int) { fmt.Printf(\u0026#34;Goroutine %d trying to initialize\\n\u0026#34;, id) once.Do(func() { fmt.Printf(\u0026#34;Initialization done by goroutine %d\\n\u0026#34;, id) }) done \u0026lt;- true }(i) } // 等待所有goroutine完成 for i := 0; i \u0026lt; 10; i++ { \u0026lt;-done } } 特性与限制 # Once实例只能用于执行一个指定的函数一次 如果需要确保多个不同的函数都只执行一次，需要为每个函数创建单独的Once实例 一旦Do()方法完成调用，对同一个Once实例的后续Do()调用将不会执行提供的函数 如果在Do()调用的函数中发生panic，Once将认为操作已完成 Once没有重置机制，一旦使用就不能重新使用 Cond（条件变量） # Cond实现了一个条件变量，它是等待或宣布事件发生的goroutine的会合点。它允许goroutine等待某个条件成立，然后在条件成立时得到通知。\n基本概念 # 条件变量总是与互斥锁关联，并通过锁来保护条件的检查和更新。Cond提供了三个主要方法：\nWait()：释放关联的锁，等待通知，被唤醒后重新获取锁 Signal()：唤醒一个等待的goroutine Broadcast()：唤醒所有等待的goroutine package main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) func main() { var mu sync.Mutex cond := sync.NewCond(\u0026amp;mu) ready := false // 消费者goroutine for i := 0; i \u0026lt; 3; i++ { go func(id int) { mu.Lock() defer mu.Unlock() fmt.Printf(\u0026#34;Consumer %d is waiting...\\n\u0026#34;, id) for !ready { // 使用循环检查条件 cond.Wait() // 等待信号 } fmt.Printf(\u0026#34;Consumer %d received signal\\n\u0026#34;, id) }(i) } // 给消费者一点时间启动 time.Sleep(time.Second) // 生产者goroutine go func() { mu.Lock() defer mu.Unlock() fmt.Println(\u0026#34;Producer is ready\u0026#34;) ready = true fmt.Println(\u0026#34;Producer broadcasts signal\u0026#34;) cond.Broadcast() // 通知所有等待的goroutine }() // 等待足够时间让所有goroutine完成 time.Sleep(3 * time.Second) } 在上面的例子中：\n多个消费者goroutine等待ready条件变为true 每个消费者获取锁，检查条件，如果不满足则调用Wait()等待 生产者将条件设置为true，然后调用Broadcast()通知所有等待的消费者 所有消费者被唤醒，重新获取锁，再次检查条件，然后继续执行 使用模式 # 始终在循环中使用Wait()：这是避免虚假唤醒的重要模式 Signal vs Broadcast：选择适当的通知机制 Signal()：唤醒单个等待者，适用于任务队列等场景，只需一个工作者处理 Broadcast()：唤醒所有等待者，适用于状态变化等场景，需要所有人都知道 确保锁的正确使用：在调用Signal或Broadcast时通常需要持有锁 Pool # sync.Pool提供了一个可以重复使用临时对象的池，有助于减少垃圾回收压力，特别是在高并发环境下。\n基本概念 # Pool的主要特性：\n线程安全：可以在多个goroutine之间共享 无容量限制：可以存储任意数量的对象 临时存储：池中的对象可能在任何时候被自动移除（特别是在GC发生时） 高效复用：避免频繁分配和回收对象，减轻GC压力 常用方法 # Pool提供了两个主要方法：\nGet() interface{}：从池中获取对象。如果池为空，则调用New函数创建一个新对象 Put(x interface{})：将对象放回池中以供后续重用 package main import ( \u0026#34;bytes\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; ) func main() { // 创建一个池，用于复用bytes.Buffer var bufferPool = sync.Pool{ New: func() interface{} { fmt.Println(\u0026#34;Creating a new buffer\u0026#34;) return new(bytes.Buffer) }, } // 获取一个Buffer buffer1 := bufferPool.Get().(*bytes.Buffer) buffer1.WriteString(\u0026#34;Hello\u0026#34;) fmt.Println(\u0026#34;Buffer1:\u0026#34;, buffer1.String()) // 清空并放回池中 buffer1.Reset() bufferPool.Put(buffer1) // 获取一个Buffer（可能是刚才放回的那个） buffer2 := bufferPool.Get().(*bytes.Buffer) buffer2.WriteString(\u0026#34;World\u0026#34;) fmt.Println(\u0026#34;Buffer2:\u0026#34;, buffer2.String()) // 清空并放回池中 buffer2.Reset() bufferPool.Put(buffer2) // 同时获取多个Buffer var wg sync.WaitGroup for i := 0; i \u0026lt; 10; i++ { wg.Add(1) go func(id int) { defer wg.Done() // 获取Buffer buf := bufferPool.Get().(*bytes.Buffer) // 使用Buffer buf.WriteString(fmt.Sprintf(\u0026#34;Goroutine %d\u0026#34;, id)) fmt.Printf(\u0026#34;Goroutine %d: %s\\n\u0026#34;, id, buf.String()) // 清空并放回 buf.Reset() bufferPool.Put(buf) }(i) } wg.Wait() } 并发模式 # 并发模式是在并发环境中解决特定问题的常见结构和方法。就像设计模式帮助我们组织代码一样，并发模式帮助我们组织并发逻辑，使其更易于理解、测试和维护。\n好的并发模式应该具备以下特点：\n清晰的责任边界 良好的错误处理 可控的资源使用 优雅的终止机制 掌握并发模式有以下好处：\n提高代码质量和可维护性 减少并发错误（如死锁、竞态条件） 提高性能和资源利用率 简化复杂并发逻辑的实现 使代码更加模块化和可重用 生产者-消费者模式 # 生产者-消费者是最基本也是最常用的并发模式之一。它将\u0026quot;生产数据\u0026quot;和\u0026quot;消费数据\u0026quot;的过程解耦，通过channel在两者之间传递数据。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) func producer(jobs chan\u0026lt;- int) { defer close(jobs) for i := 1; i \u0026lt;= 5; i++ { fmt.Printf(\u0026#34;生产任务: %d\\n\u0026#34;, i) jobs \u0026lt;- i time.Sleep(time.Millisecond * 500) } } func consumer(id int, jobs \u0026lt;-chan int, wg *sync.WaitGroup) { defer wg.Done() for job := range jobs { fmt.Printf(\u0026#34;消费者 %d 处理任务: %d\\n\u0026#34;, id, job) time.Sleep(time.Second) // 模拟处理时间 } } func main() { jobs := make(chan int, 3) var wg sync.WaitGroup // 启动一个生产者 go producer(jobs) // 启动两个消费者 for i := 1; i \u0026lt;= 2; i++ { wg.Add(1) go consumer(i, jobs, \u0026amp;wg) } wg.Wait() fmt.Println(\u0026#34;所有工作完成\u0026#34;) } 这个例子展示了生产者-消费者模式的基本用法：\n生产者生成任务并发送到channel 多个消费者从channel接收任务并处理 生产者完成后关闭channel 主goroutine等待所有消费者完成 带错误处理的生产者-消费者 # 现实应用中，我们通常需要处理错误：\n使用结构体传递任务和结果，包含错误信息 在消费者中处理生产者可能产生的错误 消费者也可能产生错误，并传递给结果收集者 主goroutine处理并整理所有结果 package main import ( \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) type Job struct { ID int Err error } type Result struct { JobID int Value string Err error } func producer(jobs chan\u0026lt;- Job) { defer close(jobs) for i := 1; i \u0026lt;= 5; i++ { var err error if i == 3 { err = errors.New(\u0026#34;模拟生产错误\u0026#34;) } jobs \u0026lt;- Job{ID: i, Err: err} time.Sleep(time.Millisecond * 500) } } func consumer(jobs \u0026lt;-chan Job, results chan\u0026lt;- Result, wg *sync.WaitGroup) { defer wg.Done() for job := range jobs { if job.Err != nil { results \u0026lt;- Result{JobID: job.ID, Err: job.Err} continue } // 模拟处理 time.Sleep(time.Second) var err error if job.ID == 4 { err = errors.New(\u0026#34;模拟处理错误\u0026#34;) } result := Result{ JobID: job.ID, Value: fmt.Sprintf(\u0026#34;处理结果 %d\u0026#34;, job.ID), Err: err, } results \u0026lt;- result } } func main() { jobs := make(chan Job, 5) results := make(chan Result, 5) var wg sync.WaitGroup // 启动生产者 go producer(jobs) // 启动消费者 for i := 1; i \u0026lt;= 3; i++ { wg.Add(1) go consumer(jobs, results, \u0026amp;wg) } // 收集结果 go func() { wg.Wait() close(results) }() // 处理结果 for result := range results { if result.Err != nil { fmt.Printf(\u0026#34;任务 %d 出错: %v\\n\u0026#34;, result.JobID, result.Err) continue } fmt.Printf(\u0026#34;任务 %d 完成: %s\\n\u0026#34;, result.JobID, result.Value) } } 实际应用场景 # Web服务器处理请求：传入的HTTP请求作为生产者，工作池作为消费者 数据处理管道：从数据源读取数据（生产者），经过多阶段处理（消费者） 任务队列：应用程序生成后台任务（生产者），工作者处理任务（消费者） 日志处理：应用程序生成日志事件（生产者），日志处理器写入存储（消费者） 工作池模式 (Worker Pool) # 工作池模式是生产者-消费者模式的扩展，它维护一组工作者（goroutine）来处理一系列任务。与简单的生产者-消费者模式相比，工作池模式更强调工作者的管理和任务的分发。\n基本工作池的特点：\n预先创建固定数量的工作者 所有工作者共享同一个任务队列 任务完成后结果发送到结果队列 主程序等待所有工作者完成并收集结果 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) func worker(id int, jobs \u0026lt;-chan int, results chan\u0026lt;- int, wg *sync.WaitGroup) { defer wg.Done() for job := range jobs { fmt.Printf(\u0026#34;工作者 %d 开始处理任务 %d\\n\u0026#34;, id, job) time.Sleep(time.Second) // 模拟工作耗时 fmt.Printf(\u0026#34;工作者 %d 完成任务 %d\\n\u0026#34;, id, job) results \u0026lt;- job * 2 } } func main() { const numJobs = 10 const numWorkers = 3 jobs := make(chan int, numJobs) results := make(chan int, numJobs) var wg sync.WaitGroup // 启动工作者 for w := 1; w \u0026lt;= numWorkers; w++ { wg.Add(1) go worker(w, jobs, results, \u0026amp;wg) } // 发送任务 for j := 1; j \u0026lt;= numJobs; j++ { jobs \u0026lt;- j } close(jobs) // 等待所有工作者完成 go func() { wg.Wait() close(results) }() // 收集结果 for result := range results { fmt.Printf(\u0026#34;结果: %d\\n\u0026#34;, result) } } 可限流的工作池 # 在实际应用中，我们可能需要限制并发数量或请求速率：\n限流工作池的关键功能：\n通过令牌桶或其他限流算法控制请求速率 过载时请求可以等待或被拒绝 防止系统资源过度使用 保护下游服务免受突发流量影响 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) type RateLimiter struct { interval time.Duration ticker *time.Ticker stopCh chan struct{} } func NewRateLimiter(rps int) *RateLimiter { interval := time.Second / time.Duration(rps) return \u0026amp;RateLimiter{ interval: interval, ticker: time.NewTicker(interval), stopCh: make(chan struct{}), } } func (rl *RateLimiter) Allow() bool { select { case \u0026lt;-rl.ticker.C: return true case \u0026lt;-rl.stopCh: return false default: return false } } func (rl *RateLimiter) Stop() { rl.ticker.Stop() close(rl.stopCh) } func processRequest(id int, limiter *RateLimiter, wg *sync.WaitGroup) { defer wg.Done() start := time.Now() // 等待限流器允许 for !limiter.Allow() { time.Sleep(time.Millisecond * 10) } // 模拟处理请求 time.Sleep(time.Millisecond * 50) fmt.Printf(\u0026#34;请求 %d 处理完成，等待时间: %v\\n\u0026#34;, id, time.Since(start)) } func main() { // 每秒5个请求 limiter := NewRateLimiter(5) defer limiter.Stop() var wg sync.WaitGroup // 模拟20个并发请求 for i := 1; i \u0026lt;= 20; i++ { wg.Add(1) go processRequest(i, limiter, \u0026amp;wg) } wg.Wait() fmt.Println(\u0026#34;所有请求处理完成\u0026#34;) } 实际应用场景 # API服务器：控制对数据库或第三方服务的请求速率 批量数据处理：处理大量数据时限制内存使用 爬虫系统：控制爬取速率，避免对目标站点造成压力 任务调度系统：管理和分配计算密集型任务 微服务通信：限制服务间的调用流量 管道模式 (Pipeline) # 管道模式将数据处理分成多个阶段，每个阶段通过channel连接起来。这种模式特别适合处理数据流，每个阶段都可以并发执行。\n基本管道模式的特点：\n每个阶段是一个函数，接收上一阶段的输出作为输入 每个阶段在单独的goroutine中运行 阶段间通过channel传递数据 当输入channel关闭且处理完所有数据时，该阶段关闭其输出channel package main import ( \u0026#34;fmt\u0026#34; ) // 生成器：生成整数 func generator(nums ...int) \u0026lt;-chan int { out := make(chan int) go func() { defer close(out) for _, n := range nums { out \u0026lt;- n } }() return out } // 平方：计算输入值的平方 func square(in \u0026lt;-chan int) \u0026lt;-chan int { out := make(chan int) go func() { defer close(out) for n := range in { out \u0026lt;- n * n } }() return out } // 过滤：只保留奇数 func onlyOdd(in \u0026lt;-chan int) \u0026lt;-chan int { out := make(chan int) go func() { defer close(out) for n := range in { if n%2 != 0 { out \u0026lt;- n } } }() return out } func main() { // 构建管道 nums := generator(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) squares := square(nums) odds := onlyOdd(squares) // 消费结果 for odd := range odds { fmt.Println(odd) } } 带错误处理的管道 # 实际应用中，我们需要处理每个管道阶段可能出现的错误：\n带错误处理的管道特点：\n每个结果包含值和可能的错误 一旦发生错误，错误会沿管道传播 管道后期阶段可以选择如何处理错误（传递、处理或停止） 主函数可以收集处理所有错误 package main import ( \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; ) type Result struct { Value int Err error } func generator(nums ...int) \u0026lt;-chan Result { out := make(chan Result) go func() { defer close(out) for _, n := range nums { if n \u0026lt; 0 { out \u0026lt;- Result{Err: errors.New(\u0026#34;负数不被允许\u0026#34;)} return } out \u0026lt;- Result{Value: n} } }() return out } func square(in \u0026lt;-chan Result) \u0026lt;-chan Result { out := make(chan Result) go func() { defer close(out) for res := range in { if res.Err != nil { out \u0026lt;- res // 传递错误 continue } out \u0026lt;- Result{Value: res.Value * res.Value} } }() return out } func main() { // 构建管道 values := generator(1, 2, 3, -4, 5) squares := square(values) // 消费结果 for res := range squares { if res.Err != nil { fmt.Printf(\u0026#34;错误: %v\\n\u0026#34;, res.Err) continue } fmt.Println(res.Value) } } 管道模式的优势与实际应用 # 管道模式的主要优势：\n模块化：每个处理阶段都是独立的，易于测试和维护 并发处理：各阶段可以并行执行，提高吞吐量 解耦：数据生产和消费解耦，提高代码可读性 灵活组合：可以根据需要动态组合不同处理阶段 实际应用场景：\nETL处理：数据提取、转换和加载过程 图像处理：图像加载、缩放、滤镜应用等多步骤处理 数据分析：数据读取、清洗、聚合、分析的流水线 实时数据流处理：日志分析、事件处理等 扇入扇出模式 (Fan-in/Fan-out) # 扇出是将任务分配给多个worker并行处理，扇入是将多个结果汇总到一个channel。这种模式适合处理可以并行的任务，然后需要汇总结果的场景。\n扇入扇出模式的关键组件：\n扇出：将一个任务分配给多个worker并行处理 worker：每个worker独立处理分配给它的任务 扇入：将多个worker的结果合并到一个channel package main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; ) // 扇出：将一个输入拆分为多个并行处理 func fanOut(in \u0026lt;-chan int, numWorkers int) []\u0026lt;-chan int { outputs := make([]\u0026lt;-chan int, numWorkers) for i := 0; i \u0026lt; numWorkers; i++ { outputs[i] = processWorker(in, i) } return outputs } // 扇入：将多个输入合并到一个输出 func fanIn(inputs []\u0026lt;-chan int) \u0026lt;-chan int { var wg sync.WaitGroup out := make(chan int) // 为每个输入启动一个goroutine for _, input := range inputs { wg.Add(1) go func(ch \u0026lt;-chan int) { defer wg.Done() for val := range ch { out \u0026lt;- val } }(input) } // 当所有输入处理完毕后关闭输出 go func() { wg.Wait() close(out) }() return out } // 工作者：处理输入并产生输出 func processWorker(in \u0026lt;-chan int, workerID int) \u0026lt;-chan int { out := make(chan int) go func() { defer close(out) for val := range in { // 模拟处理：计算平方 result := val * val fmt.Printf(\u0026#34;工作者 %d 处理 %d -\u0026gt; %d\\n\u0026#34;, workerID, val, result) out \u0026lt;- result } }() return out } func main() { // 创建输入 input := make(chan int) go func() { defer close(input) for i := 1; i \u0026lt;= 10; i++ { input \u0026lt;- i } }() // 扇出：分配给3个工作者 workers := fanOut(input, 3) // 扇入：汇总所有结果 results := fanIn(workers) // 处理最终结果 var sum int for res := range results { sum += res } fmt.Printf(\u0026#34;所有结果的总和: %d\\n\u0026#34;, sum) } 应用场景 # 扇入扇出模式在以下场景特别有用：\n并行计算：将大型计算任务拆分为多个子任务并行处理 数据聚合：从多个源收集数据并合并结果 并行API请求：同时发起多个API请求，然后合并响应 分布式系统：分散工作负载，然后聚合结果 超时与取消模式 # 在并发程序中，我们常常需要处理超时和取消操作。Go的context包和select语句提供了优雅的方式来实现这些功能。\n超时与上下文取消 # Context的核心功能：\n超时控制：context.WithTimeout和context.WithDeadline 手动取消：context.WithCancel 传递值：context.WithValue 优雅取消：通过Done()channel通知取消事件 package main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) // 一个可能耗时的操作 func slowOperation(ctx context.Context) (string, error) { select { case \u0026lt;-time.After(2 * time.Second): return \u0026#34;操作完成\u0026#34;, nil case \u0026lt;-ctx.Done(): return \u0026#34;\u0026#34;, ctx.Err() } } // 使用超时控制 func withTimeout() { // 创建一个带1秒超时的上下文 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() // 确保取消函数被调用 result, err := slowOperation(ctx) if err != nil { fmt.Printf(\u0026#34;超时示例出错: %v\\n\u0026#34;, err) return } fmt.Printf(\u0026#34;超时示例结果: %s\\n\u0026#34;, result) } // 使用手动取消 func withCancellation() { ctx, cancel := context.WithCancel(context.Background()) // 启动一个goroutine在1秒后取消 time.AfterFunc(1*time.Second, cancel) result, err := slowOperation(ctx) if err != nil { fmt.Printf(\u0026#34;取消示例出错: %v\\n\u0026#34;, err) return } fmt.Printf(\u0026#34;取消示例结果: %s\\n\u0026#34;, result) } // 使用上下文控制多个goroutine func workerWithContext(ctx context.Context, id int, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf(\u0026#34;工作者 %d 启动\\n\u0026#34;, id) select { case \u0026lt;-time.After(3 * time.Second): fmt.Printf(\u0026#34;工作者 %d 完成工作\\n\u0026#34;, id) case \u0026lt;-ctx.Done(): fmt.Printf(\u0026#34;工作者 %d 接收到取消信号: %v\\n\u0026#34;, id, ctx.Err()) } } func multipleWorkers() { // 创建一个2秒后自动取消的上下文 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() var wg sync.WaitGroup // 启动多个工作者 for i := 1; i \u0026lt;= 3; i++ { wg.Add(1) go workerWithContext(ctx, i, \u0026amp;wg) } // 等待工作者完成或超时 wg.Wait() fmt.Println(\u0026#34;所有工作者已退出\u0026#34;) } func main() { fmt.Println(\u0026#34;-- 超时示例 --\u0026#34;) withTimeout() fmt.Println(\u0026#34;\\n-- 取消示例 --\u0026#34;) withCancellation() fmt.Println(\u0026#34;\\n-- 多工作者示例 --\u0026#34;) multipleWorkers() } 超时重试模式 # 在网络请求等不稳定操作中，重试是常见需求：\n重试模式的关键点：\n指数退避：连续失败后增加重试间隔 最大重试次数：防止无限重试 超时控制：单次操作不应无限等待 错误处理：保留最后的错误信息供调试 package main import ( \u0026#34;context\u0026#34; \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;math/rand\u0026#34; \u0026#34;time\u0026#34; ) // 模拟可能失败的操作 func doOperation(ctx context.Context) (string, error) { // 模拟随机失败 if rand.Float32() \u0026lt; 0.7 { return \u0026#34;\u0026#34;, errors.New(\u0026#34;操作失败\u0026#34;) } select { case \u0026lt;-time.After(500 * time.Millisecond): return \u0026#34;操作成功\u0026#34;, nil case \u0026lt;-ctx.Done(): return \u0026#34;\u0026#34;, ctx.Err() } } // 带重试的操作 func doOperationWithRetry(maxRetries int, timeout time.Duration) (string, error) { var lastErr error for retry := 0; retry \u0026lt; maxRetries; retry++ { ctx, cancel := context.WithTimeout(context.Background(), timeout) result, err := doOperation(ctx) cancel() // 及时释放资源 if err == nil { return result, nil } lastErr = err fmt.Printf(\u0026#34;尝试 %d 失败: %v，重试中...\\n\u0026#34;, retry+1, err) time.Sleep(time.Millisecond * 200 * time.Duration(retry+1)) // 退避策略 } return \u0026#34;\u0026#34;, fmt.Errorf(\u0026#34;在 %d 次尝试后失败: %w\u0026#34;, maxRetries, lastErr) } func main() { rand.Seed(time.Now().UnixNano()) result, err := doOperationWithRetry(5, 800*time.Millisecond) if err != nil { fmt.Printf(\u0026#34;最终错误: %v\\n\u0026#34;, err) return } fmt.Printf(\u0026#34;最终结果: %s\\n\u0026#34;, result) } 超时与取消的实际应用 # 这些模式在以下场景尤为重要：\nHTTP客户端：防止请求无限等待 数据库操作：控制查询执行时间 分布式系统：优雅处理服务不可用 长时间运行的操作：提供用户取消能力 资源清理：确保临时资源被正确释放 并发控制模式 # 有时我们需要精确控制并发数量或并发操作。这在资源受限的环境中尤为重要。\n信号量模式 # 使用带缓冲的channel实现信号量：\n信号量模式的特点：\n使用带缓冲的channel控制并发数量 每次操作前获取令牌，操作后释放令牌 可应用于任何需要限制并发的场景 简单高效，符合Go的设计理念 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) // 信号量实现 type Semaphore struct { tokens chan struct{} } func NewSemaphore(limit int) *Semaphore { return \u0026amp;Semaphore{ tokens: make(chan struct{}, limit), } } func (s *Semaphore) Acquire() { s.tokens \u0026lt;- struct{}{} } func (s *Semaphore) Release() { \u0026lt;-s.tokens } // 并发执行但限制最大并发数 func processWithLimit(items []int, concurrency int) { sem := NewSemaphore(concurrency) var wg sync.WaitGroup for i, item := range items { wg.Add(1) // 获取令牌 sem.Acquire() go func(id, val int) { defer wg.Done() defer sem.Release() // 释放令牌 // 模拟处理 fmt.Printf(\u0026#34;处理项目 %d: %d 开始\\n\u0026#34;, id, val) time.Sleep(time.Second) fmt.Printf(\u0026#34;处理项目 %d: %d 完成\\n\u0026#34;, id, val) }(i, item) } wg.Wait() } func main() { items := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Println(\u0026#34;限制最大3个并发:\u0026#34;) processWithLimit(items, 3) } 并发与串行结合 # 有些应用需要在某些阶段并发，而在另一些阶段串行执行：\n这种模式适用于：\n数据处理前需要预处理的场景 并行处理后需要聚合结果的场景 某些阶段有依赖关系，无法并行的场景 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; ) // 串行阶段 func prepare(data []int) []int { fmt.Println(\u0026#34;准备阶段（串行）开始\u0026#34;) result := make([]int, len(data)) for i, val := range data { result[i] = val * 2 time.Sleep(100 * time.Millisecond) // 模拟处理 } fmt.Println(\u0026#34;准备阶段（串行）完成\u0026#34;) return result } // 并行阶段 func process(data []int) []int { fmt.Println(\u0026#34;处理阶段（并行）开始\u0026#34;) result := make([]int, len(data)) var wg sync.WaitGroup for i, val := range data { wg.Add(1) go func(idx, value int) { defer wg.Done() // 模拟复杂处理 time.Sleep(500 * time.Millisecond) result[idx] = value * value fmt.Printf(\u0026#34;处理项目 %d 完成\\n\u0026#34;, idx) }(i, val) } wg.Wait() fmt.Println(\u0026#34;处理阶段（并行）完成\u0026#34;) return result } // 串行阶段 func finalize(data []int) int { fmt.Println(\u0026#34;最终阶段（串行）开始\u0026#34;) sum := 0 for _, val := range data { sum += val time.Sleep(100 * time.Millisecond) // 模拟处理 } fmt.Println(\u0026#34;最终阶段（串行）完成\u0026#34;) return sum } func main() { input := []int{1, 2, 3, 4, 5} // 串行-并行-串行处理流程 prepared := prepare(input) // 串行 processed := process(prepared) // 并行 result := finalize(processed) // 串行 fmt.Printf(\u0026#34;最终结果: %d\\n\u0026#34;, result) } ","date":"2025-05-16","externalUrl":null,"permalink":"/posts/2d3150b2/0e0cf2f4/14012a79/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e并发编程概念 \n    \u003cdiv id=\"并发编程概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b9%b6%e5%8f%91%e7%bc%96%e7%a8%8b%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e并发从程序设计的角度，就是希望通过某些机制让计算机可以在一个时间段内，执行多个任务。让一个或多个物理 CPU 在多个程序之间多路复用，提高对计算机资源的利用率。\u003c/p\u003e","title":"5、并发编程","type":"posts"},{"content":"适配器模式（Adapter）允许不兼容的接口一起工作。在Go中，可以使用结构体嵌入和接口实现适配器模式：\n定义 # package adapter import \u0026#34;fmt\u0026#34; // 目标接口 type Target interface { Request() string } // 已存在的接口（不兼容） type Adaptee struct{} // 已存在的方法 func (a Adaptee) SpecificRequest() string { return \u0026#34;适配者的特殊请求\u0026#34; } // 适配器实现Target接口 type Adapter struct { adaptee Adaptee } // 适配器将SpecificRequest转换为Request func (a Adapter) Request() string { fmt.Println(\u0026#34;适配器转换请求...\u0026#34;) return a.adaptee.SpecificRequest() } 使用 # // main.go package main import ( \u0026#34;fmt\u0026#34; \u0026#34;myapp/adapter\u0026#34; ) // 使用Target接口的客户端代码 func ClientCode(target adapter.Target) { result := target.Request() fmt.Println(\u0026#34;客户端收到:\u0026#34;, result) } func main() { // 创建适配者 adaptee := adapter.Adaptee{} // 创建适配器 adapter := adapter.Adapter{adaptee} // 使用适配器 ClientCode(adapter) } ","date":"2025-05-14","externalUrl":null,"permalink":"/posts/2d3150b2/6742e0c3/13019dfb/","section":"文章","summary":"\u003cp\u003e适配器模式（Adapter）允许不兼容的接口一起工作。在Go中，可以使用结构体嵌入和接口实现适配器模式：\u003c/p\u003e","title":"5、适配器模式","type":"posts"},{"content":" 项目配置 # 项目配置在项目目录中的 wails.json 文件中。 配置的结构是：\n{ // 项目配置版本 \u0026#34;version\u0026#34;: \u0026#34;\u0026#34;, // 项目名称 \u0026#34;name\u0026#34;: \u0026#34;\u0026#34;, // 包含已编译资源的目录的相对路径，这通常是推断出来的，可以留空 \u0026#34;assetdir\u0026#34;: \u0026#34;\u0026#34;, // 触发重新加载的其他目录（逗号分隔），这仅用于某些高级资产配置 \u0026#34;reloaddirs\u0026#34;: \u0026#34;\u0026#34;, // 构建文件所在的目录。默认为“build” \u0026#34;build:dir\u0026#34;: \u0026#34;\u0026#34;, // 前端目录的相对路径。默认为“frontend” \u0026#34;frontend:dir\u0026#34;: \u0026#34;\u0026#34;, // 安装节点依赖项的命令，在前端目录中运行-通常是`npm install` \u0026#34;frontend:install\u0026#34;: \u0026#34;\u0026#34;, // 构建资产的命令，在前端目录中运行-通常是`npm run build` \u0026#34;frontend:build\u0026#34;: \u0026#34;\u0026#34;, // 此命令已被frontend:dev:build替换。如果未指定frontend:dev:build，则将回退到此命令。如果也没有指定此命令，则将回退到前端：build \u0026#34;frontend:dev\u0026#34;: \u0026#34;\u0026#34;, // 此命令在开发中相当于frontend:build。如果未指定，则回退到前端：dev \u0026#34;frontend:dev:build\u0026#34;: \u0026#34;\u0026#34;, // 此命令在开发中相当于前端：install。如果未指定，则回退到前端：install \u0026#34;frontend:dev:install\u0026#34;: \u0026#34;\u0026#34;, // 此命令在\u0026#39;wails-dev\u0026#39;上的一个单独进程中运行。适用于第三方观察者或启动第三方开发服务器 \u0026#34;frontend:dev:watcher\u0026#34;: \u0026#34;\u0026#34;, // 用于为资产提供服务的第三方开发服务器EG Vite的URL。如果将其设置为“auto”，则将从Vite输出中推断出devServerUrl \u0026#34;frontend:dev:serverUrl\u0026#34;: \u0026#34;\u0026#34;, // 创建自动生成的JS模块的目录的相对路径 \u0026#34;wailsjsdir\u0026#34;: \u0026#34;\u0026#34;, // 二进制文件的名称 \u0026#34;outputfilename\u0026#34;: \u0026#34;\u0026#34;, // 开发服务器在检测到资产变化时等待重新加载的默认时间 \u0026#34;debounceMS\u0026#34;: 100, // 将wails-dev服务器绑定到的地址。默认值：localhost:34115 \u0026#34;devServer\u0026#34;: \u0026#34;\u0026#34;, // 在开发模式下以shell样式传递给应用程序的参数 \u0026#34;appargs\u0026#34;: \u0026#34;\u0026#34;, // 定义是否应运行构建挂钩，尽管它们是为主机操作系统以外的操作系统定义的。 \u0026#34;runNonNativeBuildHooks\u0026#34;: false, \u0026#34;preBuildHooks\u0026#34;: { // 在构建指定的GOOS/GOARCH:${platform}之前执行的命令将被替换为“GOOS/GORACH”。“GOOS/GOARCH”钩子在“GOOS/*”和“*/*”钩子之前执行。 \u0026#34;GOOS/GOARCH\u0026#34;: \u0026#34;\u0026#34;, // 在构建指定的GOOS:${platform}之前执行的命令将被替换为“GOOS/GOARCH”。“GOOS/*”挂钩在“*/*”钩子之前执行。 \u0026#34;GOOS/*\u0026#34;: \u0026#34;\u0026#34;, // 在每次构建之前执行的命令：${platform}被替换为“GOOS/GOARCH”。 \u0026#34;*/*\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;postBuildHooks\u0026#34;: { // 在构建指定的GOOS/GOARCH:${platform}后执行的命令将被替换为“GOOS/GORACH”，${bin}将被替换成编译二进制文件的路径。“GOOS/GOARCH”钩子在“GOOS/*”和“*/*”钩子之前执行。 \u0026#34;GOOS/GOARCH\u0026#34;: \u0026#34;\u0026#34;, // 在构建指定的GOOS:${platform}后执行的命令将替换为“GOOS/GOARCH”，${bin}替换为编译二进制文件的路径。“GOOS/*”挂钩在“*/*”钩子之前执行。 \u0026#34;GOOS/*\u0026#34;: \u0026#34;\u0026#34;, // 每次构建后执行的命令：${platform}被替换为“GOOS/GOARCH”，${bin}被替换成编译二进制文件的路径。 \u0026#34;*/*\u0026#34;: \u0026#34;\u0026#34; }, // 用于填充清单和版本信息的数据。 \u0026#34;info\u0026#34;: { // 公司名称。默认值：[项目名称] \u0026#34;companyName\u0026#34;: \u0026#34;\u0026#34;, // 产品名称。默认值：[项目名称] \u0026#34;productName\u0026#34;: \u0026#34;\u0026#34;, // 产品的版本。默认值：“1.0.0” \u0026#34;productVersion\u0026#34;: \u0026#34;\u0026#34;, // 产品的版权。默认值：“Copyright.........“ \u0026#34;copyright\u0026#34;: \u0026#34;\u0026#34;, // 应用程序的简短评论。默认值: \u0026#39;Built using Wails (https://wails.app)\u0026#39; \u0026#34;comments\u0026#34;: \u0026#34;\u0026#34;, // 应用程序的文件关联 \u0026#34;fileAssociations\u0026#34;: [ { // 文件拓展名，例如png \u0026#34;ext\u0026#34;: \u0026#34;wails\u0026#34;, // 名称，例如PNG File \u0026#34;name\u0026#34;: \u0026#34;Wails\u0026#34;, // 仅限Windows。描述。它显示在Windows资源管理器的“类型”列上。 \u0026#34;description\u0026#34;: \u0026#34;Wails file\u0026#34;, // 不带扩展名的图标名称。图标应位于构建文件夹中。适用于macOS和Windows的.png文件将生成正确的图标） \u0026#34;iconName\u0026#34;: \u0026#34;fileIcon\u0026#34;, // 仅限macOS。应用程序在类型方面的作用。Corresponds to CFBundleTypeRole. \u0026#34;role\u0026#34;: \u0026#34;Editor\u0026#34; }, ], // 应用程序应打开的自定义URI协议 \u0026#34;protocols\u0026#34;: [ { // 协议域，例如：myapp \u0026#34;scheme\u0026#34;: \u0026#34;myapp\u0026#34;, // 仅限Windows。描述。它显示在Windows资源管理器的“类型”列上。 \u0026#34;description\u0026#34;: \u0026#34;Myapp protocol\u0026#34;, // 仅限macOS。应用程序在类型方面的作用。Corresponds to CFBundleTypeRole. \u0026#34;role\u0026#34;: \u0026#34;Editor\u0026#34; } ] }, // \u0026#39;multiple\u0026#39;: 每个架构一个安装程序。 \u0026#39;single\u0026#39;: 用于当前正在构建的系统架构的单一通用安装程序。 Default: \u0026#39;multiple\u0026#39; \u0026#34;nsisType\u0026#34;: \u0026#34;\u0026#34;, // 应用程序是否应该被混淆。Default: false \u0026#34;obfuscated\u0026#34;: \u0026#34;\u0026#34;, // 使用模糊标志时传递给garble命令的参数 \u0026#34;garbleargs\u0026#34;: \u0026#34;\u0026#34;, // 绑定配置 \u0026#34;bindings\u0026#34;: { // model.ts file generation config \u0026#34;ts_generation\u0026#34;: { // All generated JavaScript entities will be prefixed with this value \u0026#34;prefix\u0026#34;: \u0026#34;\u0026#34;, // All generated JavaScript entities will be suffixed with this value \u0026#34;suffix\u0026#34;: \u0026#34;\u0026#34;, // Type of output to generate (classes|interfaces) \u0026#34;outputType\u0026#34;: \u0026#34;classes\u0026#34;, } } } 该文件将在运行 wails build 或 wails dev 时，由 Wails CLI 读取。\nwails build/dev 命令中的 assetdir、reloaddirs、wailsjsdir、debounceMS、devserver 和 frontenddevserverurl 标志将覆盖项目配置并作为后续运行的默认值。\n","date":"2025-05-11","externalUrl":null,"permalink":"/posts/2d3150b2/9f158c96/f88084c8/40f6989f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e项目配置 \n    \u003cdiv id=\"项目配置\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%a1%b9%e7%9b%ae%e9%85%8d%e7%bd%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e项目配置在项目目录中的 \u003ccode\u003ewails.json\u003c/code\u003e 文件中。 配置的结构是：\u003c/p\u003e","title":"5、项目配置","type":"posts"},{"content":"util 模块是 Node.js 的一个内置模块，包含了实用工具函数，用于支持 JavaScript 编程中的调试、错误处理、格式化等功能。\nutil 提供常用函数的集合，用于弥补核心 JavaScript 的功能过于精简的不足。\nutil 模块中的功能涵盖了从对象检查、继承到格式化字符串等多个方面。\n导入 # const util = require(\u0026#39;util\u0026#39;); 常用方法 # 方法 描述 util.format(format, ...args) 字符串格式化，支持 %s、%d、%j 占位符。 util.inspect(object[, options]) 将对象转换为字符串，用于调试。 util.promisify(function) 将回调风格的函数转换为返回 Promise 的函数。 util.callbackify(fn) 将返回 Promise 的函数转换为回调风格函数。 util.inherits(constructor, superConstructor) 让一个构造函数继承另一个构造函数的原型方法。 util.deprecate(fn, message) 标记函数为废弃，调用时会打印警告消息。 util.types 包含多种类型检测方法的集合，例如 isAnyArrayBuffer、isBigInt64Array。 util.isDeepStrictEqual(val1, val2) 判断两个值是否深度相等，类似于深度比较。 util.getSystemErrorName(err) 根据错误码返回系统错误名称。 util.inspect.custom 一个 Symbol，可以定义自定义的 inspect 行为，用于调试。 util.types # util.types 是一个包含许多类型检测方法的集合，扩展了 JavaScript 的 typeof 和 instanceof。\n方法 描述 util.types.isAnyArrayBuffer(value) 检查值是否为 ArrayBuffer 或 SharedArrayBuffer。 util.types.isArrayBuffer(value) 检查值是否为 ArrayBuffer。 util.types.isAsyncFunction(value) 检查值是否为异步函数。 util.types.isBigInt64Array(value) 检查值是否为 BigInt64Array。 util.types.isBigUint64Array(value) 检查值是否为 BigUint64Array。 util.types.isBooleanObject(value) 检查值是否为布尔对象。 util.types.isDataView(value) 检查值是否为 DataView。 util.types.isDate(value) 检查值是否为 Date。 util.types.isGeneratorFunction(value) 检查值是否为生成器函数。 util.types.isMap(value) 检查值是否为 Map。 util.types.isSet(value) 检查值是否为 Set。 util.types.isRegExp(value) 检查值是否为正则表达式。 util.types.isSymbolObject(value) 检查值是否为符号对象。 ","date":"2025-04-27","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/65df2b3b/74c55356/","section":"文章","summary":"\u003cp\u003e\u003cstrong\u003eutil\u003c/strong\u003e 模块是 Node.js 的一个内置模块，包含了实用工具函数，用于支持 JavaScript 编程中的调试、错误处理、格式化等功能。\u003c/p\u003e","title":"5、util","type":"posts"},{"content":" Pandas # Python的Pandas是一个基于Python构建的开源数据分析库，它提供了强大的数据结构和运算功能。\nPandas 名字衍生自术语 \u0026ldquo;panel data\u0026rdquo;（面板数据）和 \u0026ldquo;Python data analysis\u0026quot;（Python 数据分析）。\nPandas 是一个开放源码、BSD 许可的库，提供高性能、易于使用的数据结构和数据分析工具。\nPandas 一个强大的分析结构化数据的工具集，基础是Numpy（提供高性能的矩阵运算）。\nPandas 应用 # Pandas 可以从各种文件格式比如 CSV、JSON、SQL、Microsoft Excel 导入数据。\nPandas 可以对各种数据进行运算操作，比如归并、再成形、选择，还有数据清洗和数据加工特征。\nPandas 广泛应用在学术、金融、统计学等各个数据分析领域。\n数据结构 # Pandas 的主要数据结构是 Series （一维数据）与 DataFrame（二维数据）。\nSeries是一种类似于一维数组的对象，它由一组数据（各种 Numpy 数据类型）以及一组与之相关的数据标签（即索引）组成。 DataFrame是一个表格型的数据结构，它含有一组有序的列，每列可以是不同的值类型（数值、字符串、布尔型值）。DataFrame 既有行索引也有列索引，它可以被看做由 Series 组成的字典（共同用一个索引）。 安装 # pip install pandas 查看 pandas 版本 # import pandas as pd print(pd.__version__) 简单实例 # import pandas as pd data = { \u0026#39;Name\u0026#39;: [\u0026#39;lucy\u0026#39;,\u0026#39;tom\u0026#39;,\u0026#39;lily\u0026#39;], \u0026#39;Age\u0026#39;: [18,20,35] } df = pd.DataFrame(data) print(df) \u0026gt;\u0026gt;\u0026gt; Name Age 0 lucy 18 1 tom 20 2 lily 35 Series # Series： 类似于一维数组或列表，是由一组数据以及与之相关的数据标签（索引）构成。Series 可以看作是 DataFrame 中的一列，也可以是单独存在的一维数据结构。\nSeries 特点：\n**一维数组：**Series 中的每个元素都有一个对应的索引值。 索引： 每个数据元素都可以通过标签（索引）来访问，默认情况下索引是从 0 开始的整数，但你也可以自定义索引。 数据类型： Series 可以容纳不同数据类型的元素，包括整数、浮点数、字符串、Python 对象等。 **大小不变性：**Series 的大小在创建后是不变的，但可以通过某些操作（如 append 或 delete）来改变。 **操作：**Series 支持各种操作，如数学运算、统计分析、字符串处理等。 **缺失数据：**Series 可以包含缺失数据，Pandas 使用NaN（Not a Number）来表示缺失或无值。 **自动对齐：**当对多个 Series 进行运算时，Pandas 会自动根据索引对齐数据，这使得数据处理更加高效。 我们可以使用 Pandas 库来创建一个 Series 对象，并且可以为其指定索引（Index）、名称（Name）以及值（Values）\nimport pandas as pd # 创建 Series，使用默认索引 s1 = pd.Series([\u0026#39;tom\u0026#39;,\u0026#39;lucy\u0026#39;,\u0026#39;lily\u0026#39;],name=\u0026#39;s1\u0026#39;) print(s1) \u0026gt;\u0026gt;\u0026gt; 0 tom 1 lucy 2 lily Name: s1, dtype: object # 创建 Series，使用自定义索引 s2 = pd.Series([\u0026#39;tom\u0026#39;,\u0026#39;lucy\u0026#39;,\u0026#39;lily\u0026#39;],index=[\u0026#39;stu-1\u0026#39;,\u0026#39;stu-2\u0026#39;,\u0026#39;stu-3\u0026#39;],name=\u0026#39;s2\u0026#39;) print(s2) \u0026gt;\u0026gt;\u0026gt; stu-1 tom stu-2 lucy stu-3 lily Name: s2, dtype: object 创建 Series # 可以使用 pd.Series() 构造函数创建一个 Series 对象，传递一个数据数组（可以是列表、NumPy 数组等）和一个可选的索引数组。\npandas.Series(data=None, index=None, dtype=None, name=None, copy=False, fastpath=False) 参数说明：\ndata：Series 的数据部分，可以是列表、数组、字典、标量值等。如果不提供此参数，则创建一个空的 Series。 index：Series 的索引部分，用于对数据进行标记。可以是列表、数组、索引对象等。如果不提供此参数，则创建一个默认的整数索引。 dtype：指定 Series 的数据类型。可以是 NumPy 的数据类型，例如 np.int64、np.float64 等。如果不提供此参数，则根据数据自动推断数据类型。 name：Series 的名称，用于标识 Series 对象。如果提供了此参数，则创建的 Series 对象将具有指定的名称。 copy：是否复制数据。默认为 False，表示不复制数据。如果设置为 True，则复制输入的数据。 fastpath：是否启用快速路径。默认为 False。启用快速路径可能会在某些情况下提高性能。 除了上面的使用 list 创建，还可以使用字典创建，字典的 key 将会成为索引\nimport pandas as pd data = {\u0026#39;stu-1\u0026#39;: \u0026#39;tom\u0026#39;,\u0026#39;stu-2\u0026#39;: \u0026#39;lucy\u0026#39;,\u0026#39;stu-3\u0026#39;: \u0026#39;lily\u0026#39;} s1 = pd.Series(data) print(s1) \u0026gt;\u0026gt;\u0026gt; stu-1 tom stu-2 lucy stu-3 lily dtype: object 如果只需要字典中的部分数据，可以在使用 index 传入需要的索引\ns1 = pd.Series(data,index=[\u0026#39;stu-1\u0026#39;,\u0026#39;stu-2\u0026#39;]) Series 方法 # 方法名称 功能描述 index 获取 Series 的索引 values 获取 Series 的数据部分（返回 NumPy 数组） head(n) 返回 Series 的前 n 行（默认为 5） tail(n) 返回 Series 的后 n 行（默认为 5） dtype 返回 Series 中数据的类型 shape 返回 Series 的形状（行数） describe() 返回 Series 的统计描述（如均值、标准差、最小值等） isnull() 返回一个布尔 Series，表示每个元素是否为 NaN notnull() 返回一个布尔 Series，表示每个元素是否不是 NaN unique() 返回 Series 中的唯一值（去重） value_counts() 返回 Series 中每个唯一值的出现次数 map(func) 将指定函数应用于 Series 中的每个元素 apply(func) 将指定函数应用于 Series 中的每个元素，常用于自定义操作 astype(dtype) 将 Series 转换为指定的类型 sort_values() 对 Series 中的元素进行排序（按值排序） sort_index() 对 Series 的索引进行排序 dropna() 删除 Series 中的缺失值（NaN） fillna(value) 填充 Series 中的缺失值（NaN） replace(to_replace, value) 替换 Series 中指定的值 cumsum() 返回 Series 的累计求和 cumprod() 返回 Series 的累计乘积 shift(periods) 将 Series 中的元素按指定的步数进行位移 rank() 返回 Series 中元素的排名 corr(other) 计算 Series 与另一个 Series 的相关性（皮尔逊相关系数） cov(other) 计算 Series 与另一个 Series 的协方差 to_list() 将 Series 转换为 Python 列表 to_frame() 将 Series 转换为 DataFrame iloc[] 通过位置索引来选择数据 loc[] 通过标签索引来选择数据 import pandas as pd data = [1,2,3,4,None,6,7] index = [\u0026#39;a\u0026#39;,\u0026#39;b\u0026#39;,\u0026#39;c\u0026#39;,\u0026#39;d\u0026#39;,\u0026#39;e\u0026#39;,\u0026#39;f\u0026#39;,\u0026#39;g\u0026#39;] s = pd.Series(data,index=index) # 基本信息 print(\u0026#39;索引：\u0026#39;,s.index) print(\u0026#39;\\n数据：\u0026#39;,s.values) print(\u0026#39;\\n数据类型：\u0026#39;,s.dtype) print(\u0026#39;\\n前两行数据：\\n\u0026#39;,s.head(2)) # 使用 map 函数对每个元素进行操作，并生成新 Series s_double = s.map(lambda x: x * 2) print(s_double) # 求和 print(\u0026#34;\\n求和：\u0026#34;,s.cumsum) # 查找缺失值 print(\u0026#34;\\n缺失值：\\n\u0026#34;,s.isnull()) # 排序 sorted_s = s.sort_values() print(\u0026#34;\\n排序后：\\n\u0026#34;,sorted_s) 输出结果如下\n索引： Index([\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;, \u0026#39;d\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;f\u0026#39;, \u0026#39;g\u0026#39;], dtype=\u0026#39;object\u0026#39;) 数据： [ 1. 2. 3. 4. nan 6. 7.] 数据类型： float64 前两行数据： a 1.0 b 2.0 dtype: float64 a 2.0 b 4.0 c 6.0 d 8.0 e NaN f 12.0 g 14.0 dtype: float64 求和： \u0026lt;bound method Series.cumsum of a 1.0 b 2.0 c 3.0 d 4.0 e NaN f 6.0 g 7.0 dtype: float64\u0026gt; 缺失值： a False b False c False d False e True f False g False dtype: bool 排序后： a 1.0 b 2.0 c 3.0 d 4.0 f 6.0 g 7.0 e NaN dtype: float64 基本操作 # # 使用列表创建 Series s = pd.Series([1, 2, 3, 4]) # 使用 NumPy 数组创建 Series s = pd.Series(np.array([1, 2, 3, 4])) # 使用字典创建 Series s = pd.Series({\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 2, \u0026#39;c\u0026#39;: 3, \u0026#39;d\u0026#39;: 4}) # 指定索引创建 Series s = pd.Series([1, 2, 3, 4], index=[\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;, \u0026#39;d\u0026#39;]) # 获取值 value = s[2] # 获取索引为2的值 # 获取多个值 subset = s[1:4] # 获取索引为1到3的值 # 使用自定义索引 value = s[\u0026#39;b\u0026#39;] # 获取索引为\u0026#39;b\u0026#39;的值 # 索引和值的对应关系 for index, value in s.items(): print(f\u0026#34;Index: {index}, Value: {value}\u0026#34;) # 使用切片语法来访问 Series 的一部分 print(s[\u0026#39;a\u0026#39;:\u0026#39;c\u0026#39;]) # 返回索引标签 \u0026#39;a\u0026#39; 到 \u0026#39;c\u0026#39; 之间的元素 print(s[:3]) # 返回前三个元素 # 为特定的索引标签赋值 s[\u0026#39;a\u0026#39;] = 10 # 将索引标签 \u0026#39;a\u0026#39; 对应的元素修改为 10 # 通过赋值给新的索引标签来添加元素 s[\u0026#39;e\u0026#39;] = 5 # 在 Series 中添加一个新的元素，索引标签为 \u0026#39;e\u0026#39; # 使用 del 删除指定索引标签的元素。 del s[\u0026#39;a\u0026#39;] # 删除索引标签 \u0026#39;a\u0026#39; 对应的元素 # 使用 drop 方法删除一个或多个索引标签，并返回一个新的 Series。 s_dropped = s.drop([\u0026#39;b\u0026#39;]) # 返回一个删除了索引标签 \u0026#39;b\u0026#39; 的新 Series s.drop([\u0026#39;b\u0026#39;] ,inplace=True) # 在原有 Series 上删除了索引标签 \u0026#39;b\u0026#39; 算数运算 # # 算术运算 result = series * 2 # 所有元素乘以2 # 过滤 filtered_series = series[series \u0026gt; 2] # 选择大于2的元素 # 数学函数 import numpy as np result = np.sqrt(series) # 对每个元素取平方根 计算统计数据 # print(s.sum()) # 输出 Series 的总和 print(s.mean()) # 输出 Series 的平均值 print(s.max()) # 输出 Series 的最大值 print(s.min()) # 输出 Series 的最小值 print(s.std()) # 输出 Series 的标准差 使用布尔表达式：根据条件过滤 Series # import pandas as pd data = [1,2,3,4,None,6,7] index = [\u0026#39;a\u0026#39;,\u0026#39;b\u0026#39;,\u0026#39;c\u0026#39;,\u0026#39;d\u0026#39;,\u0026#39;e\u0026#39;,\u0026#39;f\u0026#39;,\u0026#39;g\u0026#39;] s = pd.Series(data,index=index) print(s \u0026gt; 3) # 返回一个布尔 Series，其中的元素值大于3 \u0026gt;\u0026gt;\u0026gt; a False b False c False d True e False f True g True dtype: bool 转换数据类型 # s = s.astype(\u0026#39;float64\u0026#39;) # 将 Series 中的所有元素转换为 float64 类型 DataFrame # DataFrame： 类似于一个二维表格，它是 Pandas 中最重要的数据结构。DataFrame 可以看作是由多个 Series 按列排列构成的表格，它既有行索引也有列索引，因此可以方便地进行行列选择、过滤、合并等操作。\nDataFrame 可视为由多个 Series 组成的数据结构：\nSeries 组成了 DataFrame # import pandas as pd s1 = pd.Series([1,3,7,4]) s2 = pd.Series([2,6,3,5]) df = pd.DataFrame({ \u0026#39;Apples\u0026#39;: s1, \u0026#39;Bananas\u0026#39;: s2 }) print(df) \u0026gt;\u0026gt;\u0026gt; Apples Bananas 0 1 2 1 3 6 2 7 3 3 4 5 DataFrame 由 Index、Key、Value 组成：\n创建DataFrame # pandas.DataFrame(data=None, index=None, columns=None, dtype=None, copy=False) 参数说明：\ndata：DataFrame 的数据部分，可以是字典、二维数组、Series、DataFrame 或其他可转换为 DataFrame 的对象。如果不提供此参数，则创建一个空的 DataFrame。 index：DataFrame 的行索引，用于标识每行数据。可以是列表、数组、索引对象等。如果不提供此参数，则创建一个默认的整数索引。 columns：DataFrame 的列索引，用于标识每列数据。可以是列表、数组、索引对象等。如果不提供此参数，则创建一个默认的整数索引。 dtype：指定 DataFrame 的数据类型。可以是 NumPy 的数据类型，例如 np.int64、np.float64 等。如果不提供此参数，则根据数据自动推断数据类型。 copy：是否复制数据。默认为 False，表示不复制数据。如果设置为 True，则复制输入的数据。 使用 list 创建时，内部的每个数组是一行\nimport pandas as pd data = [ [\u0026#39;Google\u0026#39;,10], [\u0026#39;Runob\u0026#39;,12], [\u0026#39;Wiki\u0026#39;,16] ] # 创建DataFrame df = pd.DataFrame(data,columns=[\u0026#39;Site\u0026#39;,\u0026#39;Age\u0026#39;]) # 使用astype方法设置每列的数据类型 df[\u0026#39;Site\u0026#39;] = df[\u0026#39;Site\u0026#39;].astype(str) df[\u0026#39;Age\u0026#39;] = df[\u0026#39;Age\u0026#39;].astype(float) print(df) \u0026gt;\u0026gt;\u0026gt; Site Age 0 Google 10.0 1 Runob 12.0 2 Wiki 16.0 使用 dict 创建时，key 作为每一列的 title，value 作为每一列的值\nimport pandas as pd data = { \u0026#39;Site\u0026#39;: [\u0026#39;Google\u0026#39;,\u0026#39;Runob\u0026#39;,\u0026#39;Wiki\u0026#39;], \u0026#39;Age\u0026#39;: [10,12,16] } df = pd.DataFrame(data,columns=[\u0026#39;Site\u0026#39;,\u0026#39;Age\u0026#39;]) df[\u0026#39;Site\u0026#39;] = df[\u0026#39;Site\u0026#39;].astype(str) df[\u0026#39;Age\u0026#39;] = df[\u0026#39;Age\u0026#39;].astype(float) print(df) \u0026gt;\u0026gt;\u0026gt; Site Age 0 Google 10.0 1 Runob 12.0 2 Wiki 16.0 也可以使用一个 list[dict]来创建\nimport pandas as pd data = [ {\u0026#39;Site\u0026#39;:\u0026#39;Google\u0026#39;,\u0026#39;Age\u0026#39;:10}, {\u0026#39;Site\u0026#39;:\u0026#39;Runob\u0026#39;,\u0026#39;Age\u0026#39;:12}, {\u0026#39;Site\u0026#39;:\u0026#39;Wiki\u0026#39;,\u0026#39;Age\u0026#39;:16}, ] df = pd.DataFrame(data,columns=[\u0026#39;Site\u0026#39;,\u0026#39;Age\u0026#39;]) df[\u0026#39;Site\u0026#39;] = df[\u0026#39;Site\u0026#39;].astype(str) df[\u0026#39;Age\u0026#39;] = df[\u0026#39;Age\u0026#39;].astype(float) print(df) \u0026gt;\u0026gt;\u0026gt; Site Age 0 Google 10.0 1 Runob 12.0 2 Wiki 16.0 DataFrame 方法 # 方法名称 功能描述 head(n) 返回 DataFrame 的前 n 行数据（默认前 5 行） tail(n) 返回 DataFrame 的后 n 行数据（默认后 5 行） info() 显示 DataFrame 的简要信息，包括列名、数据类型、非空值数量等 describe() 返回 DataFrame 数值列的统计信息，如均值、标准差、最小值等 shape 返回 DataFrame 的行数和列数（行数, 列数） columns 返回 DataFrame 的所有列名 index 返回 DataFrame 的行索引 dtypes 返回每一列的数值数据类型 sort_values(by) 按照指定列排序 sort_index() 按行索引排序 dropna() 删除含有缺失值（NaN）的行或列 fillna(value) 用指定的值填充缺失值 isnull() 判断缺失值，返回一个布尔值 DataFrame notnull() 判断非缺失值，返回一个布尔值 DataFrame loc[] 按标签索引选择数据 iloc[] 按位置索引选择数据 at[] 访问 DataFrame 中单个元素（比 loc[] 更高效） iat[] 访问 DataFrame 中单个元素（比 iloc[] 更高效） apply(func) 对 DataFrame 或 Series 应用一个函数 applymap(func) 对 DataFrame 的每个元素应用函数（仅对 DataFrame） groupby(by) 分组操作，用于按某一列分组进行汇总统计 pivot_table() 创建透视表 merge() 合并多个 DataFrame（类似 SQL 的 JOIN 操作） concat() 按行或按列连接多个 DataFrame，axis=0默认，按行连接，axis=1表示按列合并 to_csv() 将 DataFrame 导出为 CSV 文件 to_excel() 将 DataFrame 导出为 Excel 文件 to_json() 将 DataFrame 导出为 JSON 格式 to_sql() 将 DataFrame 导出为 SQL 数据库 query() 使用 SQL 风格的语法查询 DataFrame duplicated() 返回布尔值 DataFrame，指示每行是否是重复的 drop_duplicates() 删除重复的行 set_index() 设置 DataFrame 的索引 reset_index() 重置 DataFrame 的索引 transpose() 转置 DataFrame（行列交换） import pandas as pd # 创建 DataFrame data = { \u0026#39;Name\u0026#39;: [\u0026#39;Alice\u0026#39;, \u0026#39;Bob\u0026#39;, \u0026#39;Charlie\u0026#39;, \u0026#39;David\u0026#39;], \u0026#39;Age\u0026#39;: [25, 30, 35, 40], \u0026#39;City\u0026#39;: [\u0026#39;New York\u0026#39;, \u0026#39;Los Angeles\u0026#39;, \u0026#39;Chicago\u0026#39;, \u0026#39;Houston\u0026#39;] } df = pd.DataFrame(data) # 查看前两行数据 print(df.head(2)) # 查看 DataFrame 的基本信息 print(df.info()) # 获取描述统计信息 print(df.describe()) # 按年龄排序 df_sorted = df.sort_values(by=\u0026#39;Age\u0026#39;, ascending=False) print(df_sorted) # 选择指定列 print(df[[\u0026#39;Name\u0026#39;, \u0026#39;Age\u0026#39;]]) # 按索引选择行 print(df.iloc[1:3]) # 选择第二到第三行（按位置） # 按标签选择行 print(df.loc[1:2]) # 选择第二到第三行（按标签） # 计算分组统计（按城市分组，计算平均年龄） print(df.groupby(\u0026#39;City\u0026#39;)[\u0026#39;Age\u0026#39;].mean()) # 处理缺失值（填充缺失值） df[\u0026#39;Age\u0026#39;] = df[\u0026#39;Age\u0026#39;].fillna(30) # 导出为 CSV 文件 df.to_csv(\u0026#39;output.csv\u0026#39;, index=False) 基本操作 # # 通过列名访问 print(df[\u0026#39;Column1\u0026#39;]) print(df.Column1) # 通过 .loc[] 访问 print(df.loc[:, \u0026#39;Column1\u0026#39;]) # 访问第Column1列的全部行 # 通过 .iloc[] 访问 print(df.iloc[:, 0]) # 访问第1列的全部行 # 访问单个元素 print(df[\u0026#39;Name\u0026#39;][0]) # 访问Name列的第一行的元素 # 修改列数据：直接对列进行赋值。 df[\u0026#39;Column1\u0026#39;] = [10, 11, 12] # 添加新列：给新列赋值。 df[\u0026#39;NewColumn\u0026#39;] = [100, 200, 300] # 添加新行：使用 loc、append(弃用) 或 concat 方法。 # 使用 loc 为特定索引添加新行 df.loc[3] = [13, 14, 15, 16] # 使用 append 添加新行到末尾 new_row = {\u0026#39;Column1\u0026#39;: 13, \u0026#39;Column2\u0026#39;: 14, \u0026#39;NewColumn\u0026#39;: 16} df = df.append(new_row, ignore_index=True) # 使用concat添加新行 new_row = pd.DataFrame([[4, 7]], columns=[\u0026#39;A\u0026#39;, \u0026#39;B\u0026#39;]) # 创建一个只包含新行的DataFrame df = pd.concat([df, new_row], ignore_index=True) # 将新的行 new_row 追加在 df 后面 # 删除 DataFrame 元素 # 删除列：使用 drop 方法。 df_dropped = df.drop(\u0026#39;Column1\u0026#39;, axis=1) # 删除行：同样使用 drop 方法。 df_dropped = df.drop(0, axis=0) # 删除索引为 0 的行 DataFrame 的合并与分割 # # 纵向合并 pd.concat([df1, df2], ignore_index=True) # 横向合并 pd.merge(df1, df2, on=\u0026#39;Column1\u0026#39;) 详解DataFrame的loc和iloc # 其实记住字符串索引用loc，整数索引用iloc就行了。\ndf.loc[row_indexer, column_indexer] df.iloc[row_indexer, column_indexer] 区别就在于，loc 的索引值可以用字符串，但是 iloc 的索引值只能用整数\nimport pandas as pd data = {\u0026#39;name\u0026#39;: [\u0026#39;Alice\u0026#39;, \u0026#39;Bob\u0026#39;, \u0026#39;Charlie\u0026#39;], \u0026#39;age\u0026#39;: [25, 30, 35], \u0026#39;city\u0026#39;: [\u0026#39;New York\u0026#39;, \u0026#39;London\u0026#39;, \u0026#39;Paris\u0026#39;]} df = pd.DataFrame(data, index=[\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;]) row_a = df.loc[\u0026#39;a\u0026#39;] import pandas as pd data = {\u0026#39;name\u0026#39;: [\u0026#39;Alice\u0026#39;, \u0026#39;Bob\u0026#39;, \u0026#39;Charlie\u0026#39;], \u0026#39;age\u0026#39;: [25, 30, 35], \u0026#39;city\u0026#39;: [\u0026#39;New York\u0026#39;, \u0026#39;London\u0026#39;, \u0026#39;Paris\u0026#39;]} df = pd.DataFrame(data) row_0 = df.iloc[0] 迭代每一行 # 如果你需要更细致地控制每一行的处理过程，可以使用 iterrows 或 itertuples 遍历每一行。\nmport pandas as pd # 创建一个示例 DataFrame data = {\u0026#39;A\u0026#39;: [1, 2, 3], \u0026#39;B\u0026#39;: [4, 5, 6]} df = pd.DataFrame(data) # 遍历每一行，处理并新增列 for index, row in df.iterrows(): df.loc[index, \u0026#39;C\u0026#39;] = row[\u0026#39;A\u0026#39;] + row[\u0026#39;B\u0026#39;] df.loc[index, \u0026#39;D\u0026#39;] = row[\u0026#39;A\u0026#39;] * row[\u0026#39;B\u0026#39;] print(df) ","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/46b8de4c/85860dcc/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003ePandas \n    \u003cdiv id=\"pandas\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#pandas\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003ePython的Pandas是一个\u003cstrong\u003e基于Python构建的开源数据分析库\u003c/strong\u003e，它提供了强大的数据结构和运算功能。\u003c/p\u003e","title":"5、pandas","type":"posts"},{"content":" File # Python内置的open()函数，传入文件名和标示符，open()将会返回一个File对象，完整的语法格式如下：\nopen(file, mode=\u0026#39;r\u0026#39;, buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) \u0026#39;\u0026#39;\u0026#39; file: 必需，文件路径（相对或者绝对路径）。 mode: 可选，文件打开模式 buffering: 设置缓冲 encoding: 默认使用操作系统的字符集编码，所以为了统一，必须设置为utf-8 errors: 报错级别 newline: 区分换行符 closefd: 传入的file参数类型 opener: 设置自定义开启器，开启器的返回值必须是一个打开的文件描述符。 \u0026#39;\u0026#39;\u0026#39; 模式 描述 t 文本模式 (默认)。 x 写模式，新建一个文件，如果该文件已存在则会报错。 b 二进制模式。 + 打开一个文件进行更新(可读可写)。 U 通用换行模式（Python 3 不支持）。 r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 rb 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。 r+ 打开一个文件用于读写。文件指针将会放在文件的开头。 rb+ 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。 w 打开一个文件只用于写入。如果该文件已存在则打开文件，并从开头开始编辑，即原有内容会被删除。如果该文件不存在，创建新文件。 wb 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件，并从开头开始编辑，即原有内容会被删除。如果该文件不存在，创建新文件。一般用于非文本文件如图片等。 w+ 打开一个文件用于读写。如果该文件已存在则打开文件，并从开头开始编辑，即原有内容会被删除。如果该文件不存在，创建新文件。 wb+ 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件，并从开头开始编辑，即原有内容会被删除。如果该文件不存在，创建新文件。一般用于非文本文件如图片等。 a 打开一个文件用于追加。如果该文件已存在，文件指针将会放在文件的结尾。也就是说，新的内容将会被写入到已有内容之后。如果该文件不存在，创建新文件进行写入。 ab 以二进制格式打开一个文件用于追加。如果该文件已存在，文件指针将会放在文件的结尾。也就是说，新的内容将会被写入到已有内容之后。如果该文件不存在，创建新文件进行写入。 a+ 打开一个文件用于读写。如果该文件已存在，文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在，创建新文件用于读写。 ab+ 以二进制格式打开一个文件用于追加。如果该文件已存在，文件指针将会放在文件的结尾。如果该文件不存在，创建新文件用于读写。 默认为文本模式，如果要以二进制模式打开，加上 b 。\n# 使用try-except-finally，不管有没有出错，都会关闭文件 try: # 以制度r的方式打开文件1.txt，如果文件不存在，程序会抛出IOError file = open(\u0026#39;./1.txt\u0026#39;,\u0026#39;r\u0026#39;) # 调用read()方法可以一次读取文件的全部内容 a = file.read() except IOError as e: print(e) finally: # 关闭文件，文件使用完毕后必须关闭，因为文件对象会占用操作系统的资源 file.close() print(a) 每次都写try-except-finally过于繁琐，所以可以使用with，自动帮我们调用close()方法\nwith open(\u0026#39;./1.txt\u0026#39;,\u0026#39;r+\u0026#39;,encoding=\u0026#39;utf8\u0026#39;) as file: file.write(\u0026#39;你好呀\u0026#39;) a = file.read() print(a) 字符编码 # 要读取非UTF-8编码的文本文件，需要给open()函数传入encoding参数，例如，读取GBK编码的文件\nf = open(\u0026#39;/Users/michael/gbk.txt\u0026#39;, \u0026#39;r\u0026#39;, encoding=\u0026#39;gbk\u0026#39;) File对象api # read(size) # 调用read(size)方法，每次最多读取size个字符的内容，当 size 被忽略了或者为负, 那么该文件的所有内容都将被读取并且返回。\nwith open(\u0026#39;1.txt\u0026#39;, mode=\u0026#39;r\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: content = f.read() for line in content.splitlines(): # 使用splitlines将内容按照换行符进行分割 print(line) readline() # readline() 会从文件中读取单独的一行。换行符为 \u0026lsquo;\\n\u0026rsquo;。f.readline() 如果返回一个空字符串, 说明已经已经读取到最后一行\nwith open(\u0026#39;1.txt\u0026#39;, mode=\u0026#39;r\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: while True: line = f.readline() if not line: break print(line.strip()) # 使用 strip 去除每行末尾的换行符 readlines() # readlines() 将返回该文件中包含的所有行，一个字符串组成的列表（list）\nwith open(\u0026#39;1.txt\u0026#39;, mode=\u0026#39;r\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: lines = f.readlines() for line in lines: print(line.strip()) write(string) # f.write(string) 将 string 写入到文件中, 然后返回写入的字符数\nwith open(\u0026#39;1.txt\u0026#39;,mode=\u0026#39;a\u0026#39;,encoding=\u0026#39;utf-8\u0026#39;) as f: for i in range(10): f.write(f\u0026#39;this is line {i}\\n\u0026#39;) tell() # f.tell() 返回文件对象当前所处的位置, 它是从文件开头开始算起的字节数。\nseek() # 如果要改变文件指针当前的位置, 可以使用 f.seek(offset, from_what) 函数\noffset：文件指针移动的字符数，整数为往结尾方向移动，负数为往开头方向移动\nfrom_what：如果是 0 表示开头, 如果是 1 表示当前位置, 2 表示文件的结尾\nseek(x,0) ： 从起始位置即文件首行首字符开始移动 x 个字符 seek(x,1) ： 表示从当前位置往后移动x个字符 seek(-x,2)：表示从文件的结尾往前移动x个字符 StringIO和BytesIO # 很多时候，数据读写不一定是文件，也可以在内存中读写。\nStringIO # StringIO顾名思义就是在内存中读写str。\n要把str写入StringIO，我们需要先创建一个StringIO，然后，像文件一样写入即可：\nfrom io import StringIO f = StringIO() f.write(\u0026#39;hello\u0026#39;) f.write(\u0026#39; \u0026#39;) f.write(\u0026#39;world!\u0026#39;) print(f.getvalue()) getvalue()方法用于获得写入后的str。\n要读取StringIO，可以用一个str初始化StringIO，然后，像读文件一样读取：\nfrom io import StringIO f = StringIO(\u0026#39;Hello!\\nHi!\\nGoodbye!\u0026#39;) while True: s = f.readline() if s == \u0026#39;\u0026#39;: break print(s.strip()) BytesIO # StringIO操作的只能是str，如果要操作二进制数据，就需要使用BytesIO。\nBytesIO实现了在内存中读写bytes，我们创建一个BytesIO，然后写入一些bytes：\nfrom io import BytesIO f = BytesIO() f.write(\u0026#39;中文\u0026#39;.encode(\u0026#39;utf-8\u0026#39;)) #如果使用getvalue，是将所有内容读出来 print(f.getvalue()) 请注意，写入的不是str，而是经过UTF-8编码的bytes。\n和StringIO类似，可以用一个bytes初始化BytesIO，然后，像读文件一样读取：\nfrom io import BytesIO f = BytesIO(b\u0026#39;\\xe4\\xb8\\xad\\xe6\\x96\\x87\u0026#39;) #如果使用read，需要先将文件指针定位到开头 f.seek(0) f.read() 操作系统接口模块（OS） # 如果我们要操作文件、目录，可以在命令行下面输入操作系统提供的各种命令来完成。比如dir、cp等命令\n如果要在Python程序中执行这些目录和文件的操作怎么办？其实操作系统提供的命令只是简单地调用了操作系统提供的接口函数，Python内置的os模块也可以直接调用操作系统提供的接口函数。\n系统 # os.name，获取操作系统类型\nimport os print(os.name) #如果是posix，说明系统是Linux、Unix或Mac OS X，如果是nt，就是Windows系统 os.uname()，获取详细的系统信息，在Windows上不提供，也就是说，os模块的某些函数是跟操作系统相关的\nos.environ，获取系统的环境变量\nimport os print(os.environ) \u0026#39;\u0026#39;\u0026#39; environ({\u0026#39;ALLUSERSPROFILE\u0026#39;: \u0026#39;C:\\\\ProgramData\u0026#39;, \u0026#39;APPDATA\u0026#39;: \u0026#39;C:\\\\Users\\\\92988\\\\AppData\\\\Roaming\u0026#39;, \u0026#39;PATH\u0026#39;: \u0026#39;D:\\\\python\\\\python3.6.8\\\\Scripts\\\\;D:\\\\python\\\\python3.6.8\\\\;%JAVA_HOME%\\\\bin\u0026#39;}) \u0026#39;\u0026#39;\u0026#39; os.environ.get('key')：获取指定环境变量，可以写为os.getenv('key')\nimport os print(os.environ.get(\u0026#39;JAVA_HOME\u0026#39;)) print(os.getenv(\u0026#39;JAVA_HOME\u0026#39;)) \u0026#39;\u0026#39;\u0026#39; D:\\java\\jdk1.8.0_301 \u0026#39;\u0026#39;\u0026#39; os.sep：获取当前系统的路径分隔符\n命令 # os.system('command')：执行系统命令，并返回是否执行成功，0则表示执行成功，为其他值则表示执行不成功\nos.popen('command')：执行系统命令，返回命令输出内容\n除了这两个命令，python2.4开始提供新的模块subprocess来处理系统命令\nimport subprocess #stdout = subprocess.PIPE,stderr = subprocess.STDOUT这两个参数主要是为了限制控制台输出 cmd = subprocess.Popen(\u0026#39;java\u0026#39;,stdout = subprocess.PIPE,stderr = subprocess.STDOUT) #由于在windows环境下使用，所以编码为gbk print(cmd.stdout.read().decode(\u0026#39;gbk\u0026#39;)) 目录和路径操作 # os.getcwd()：查看当前所在的工作目录\nos.chdir(path)：改变当前工作目录到另外的路径\nos.mkdir('path')：创建一个目录\nimport os os.mkdir(\u0026#39;d:/test\u0026#39;) os.makedirs('path')：递归生成目录，如果目录全部存在，抛出错误，exist_ok=True 指定了，如果某个要创建的目录已经存在，也不报错\nos.rmdir('path')：删除一个目录，如果目录非空，则抛出一个OSError\nos.removedirs('path')：递归删除一个目录\nos.remove('path')：删除一个文件，如果路径是一个目录，会抛出OSError\nos.rename('old','new')：重命名文件或目录\nos.renames('old','new')：递归重命名文件或目录\nos.listdir('path')：返回path指定的目录包含的文件或文件夹的名字的列表\nimport os # renames可以递归对路径中的所有目录重命名 os.renames(\u0026#39;d:/test/test\u0026#39;,\u0026#39;d:/test3/test4\u0026#39;) 路径（os.path） # 该模块主要用于获取文件或目录的属性\n# 查看当前目录的绝对路径 p1 = os.path.abspath(\u0026#39;.\u0026#39;) # 在某个目录下创建一个新目录，返回新目录的完整路径 p2 = os.path.join(\u0026#39;/Users/michael\u0026#39;, \u0026#39;testdir\u0026#39;) 方法 说明 os.path.abspath(path) 返回绝对路径 os.path.basename(path) 返回文件名 os.path.commonprefix(list) 返回list(多个路径)中，所有path共有的最长的路径 os.path.dirname(path) 返回文件路径 os.path.exists(path) 路径存在则返回True,路径损坏返回False os.path.lexists 路径存在则返回True,路径损坏也返回True os.path.expanduser(path) 把path中包含的\u0026quot;~\u0026ldquo;和\u0026rdquo;~user\u0026quot;转换成用户目录 os.path.expandvars(path) 根据环境变量的值替换path中包含的\u0026quot;$name\u0026quot;和\u0026quot;${name}\u0026quot; os.path.getatime(path) 返回最近访问时间（浮点型秒数） os.path.getmtime(path) 返回最近文件修改时间 os.path.getctime(path) 返回文件 path 创建时间 os.path.getsize(path) 返回文件大小，如果文件不存在就返回错误 os.path.isabs(path) 判断是否为绝对路径 os.path.isfile(path) 判断路径是否为文件 os.path.isdir(path) 判断路径是否为目录 os.path.islink(path) 判断路径是否为链接 os.path.ismount(path) 判断路径是否为挂载点 os.path.join(path1[, path2[, ...]]) 把目录和文件名合成一个路径 os.path.normcase(path) 转换path的大小写和斜杠 os.path.normpath(path) 规范path字符串形式 os.path.realpath(path) 返回path的真实路径 os.path.relpath(path[, start]) 从start开始计算相对路径 os.path.samefile(path1, path2) 判断目录或文件是否相同 os.path.sameopenfile(fp1, fp2) 判断fp1和fp2是否指向同一文件 os.path.samestat(stat1, stat2) 判断stat tuple stat1和stat2是否指向同一个文件 os.path.split(path) 把路径分割成 dirname 和 basename，返回一个元组 os.path.splitdrive(path) 一般用在 windows 下，返回驱动器名和路径组成的元组 os.path.splitext(path) 分割路径中的文件名与拓展名 os.path.splitunc(path) 把路径分割为加载点与文件 os.walk(path) for (dirpath, dirnames, filenames) in os.walk(targetDir)；dirpath 代表当前遍历到的目录名； dirnames 是列表对象，存放当前dirpath中的所有子目录名；filenames 是列表对象，存放当前dirpath中的所有文件名 os.path.supports_unicode_filenames 设置是否支持unicode路径名 遍历目录 # os.walk(path)方法会深入遍历 path 以及 path 下所有子目录。\nfor (dirpath,dirnames,filenames) in os.walk(\u0026#39;./\u0026#39;): print(f\u0026#39;当前遍历的目录是:{dirpath}\u0026#39;) print(f\u0026#39;{dirpath}下存在的子目录有:{dirnames}\u0026#39;) print(f\u0026#39;{dirpath}下存在的文件有:{filenames}\u0026#39;) shutil # shutil 模块里面有很多目录文件操作的函数\n拷贝文件 # 注意，如果拷贝前，e:/first.py 已经存在，则会被拷贝覆盖，所以使用该函数一定要小心。\nfrom shutil import copyfile # 拷贝 d:/tools/first.py 到 e:/first.py copyfile(\u0026#39;d:/tools/first.py\u0026#39;, \u0026#39;e:/first.py\u0026#39;) 拷贝目录 # 如果我们要拷贝一个目录里面所有的内容（包括子目录和文件）到另外一个目录中，可以使用 shutil的copytree函数。\n注意拷贝前， 目标目录必须不存在 ，否则会报错。\nfrom shutil import copytree # 拷贝 d:/tools/aaa 目录中所有的内容 到 e:/bbb 中 copytree(\u0026#39;d:/tools/aaa\u0026#39;, \u0026#39;e:/new/bbb\u0026#39;) 序列化 # 我们把变量从内存中变成可存储或传输的过程称之为序列化，在Python中叫pickling，在其他语言中也被称之为serialization，marshalling，flattening等等，都是一个意思。\n序列化之后，就可以把序列化后的内容写入磁盘，或者通过网络传输到别的机器上。\nPython提供了pickle模块来实现序列化。\ndumps # dumps可以将对象序列化为一个bytes\nimport pickle #声明一个dict字典 a = {\u0026#39;name\u0026#39;:\u0026#39;lucy\u0026#39;,\u0026#39;age\u0026#39;:18} #将对象序列化为bytes result = pickle.dumps(a) print(result) \u0026#39;\u0026#39;\u0026#39; b\u0026#39;\\x80\\x03}q\\x00(X\\x04\\x00\\x00\\x00nameq\\x01X\\x04\\x00\\x00\\x00lucyq\\x02X\\x03\\x00\\x00\\x00ageq\\x03K\\x12u.\u0026#39; \u0026#39;\u0026#39;\u0026#39; dump # dump可以将对象序列化，并且写入一个文件\nimport pickle #声明一个dict字典 a = {\u0026#39;name\u0026#39;:\u0026#39;lucy\u0026#39;,\u0026#39;age\u0026#39;:18} #声明需要写入的File对象，以二进制模式打开并写入 file = open(\u0026#39;./person.txt\u0026#39;,\u0026#39;wb\u0026#39;) #将对象序列化,并写入文件 pickle.dump(a,file) file.close() loads # loads可以将读到到bytes反序列化为对象\nimport pickle a = pickle.loads(b\u0026#39;\\x80\\x03}q\\x00(X\\x04\\x00\\x00\\x00nameq\\x01X\\x04\\x00\\x00\\x00lucyq\\x02X\\x03\\x00\\x00\\x00ageq\\x03K\\x12u.\u0026#39;) print(a) \u0026#39;\u0026#39;\u0026#39; {\u0026#39;name\u0026#39;: \u0026#39;lucy\u0026#39;, \u0026#39;age\u0026#39;: 18} \u0026#39;\u0026#39;\u0026#39; load # load可以将文件中的数据反序列化为python对象\nimport pickle file = open(\u0026#39;./person.txt\u0026#39;,\u0026#39;rb\u0026#39;) a = pickle.load(file) print(a) file.close() \u0026#39;\u0026#39;\u0026#39; {\u0026#39;name\u0026#39;: \u0026#39;lucy\u0026#39;, \u0026#39;age\u0026#39;: 18} \u0026#39;\u0026#39;\u0026#39; ","date":"2025-04-22","externalUrl":null,"permalink":"/posts/1b37041b/a64b122d/bfb2f662/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eFile \n    \u003cdiv id=\"file\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#file\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003ePython内置的\u003ccode\u003eopen()\u003c/code\u003e函数，传入文件名和标示符，\u003ccode\u003eopen()\u003c/code\u003e将会返回一个File对象，完整的语法格式如下：\u003c/p\u003e","title":"5、IO流","type":"posts"},{"content":" 内置函数 # 数学相关 # abs(a) # 求取绝对值。abs(-1) \u0026gt;\u0026gt;\u0026gt; 1 max(list) # 求取list最大值。max([1,2,3]) \u0026gt;\u0026gt;\u0026gt; 3 min(list) # 求取list最小值。min([1,2,3]) \u0026gt;\u0026gt;\u0026gt; 1 sum(list) # 求取list元素的和。 sum([1,2,3]) \u0026gt;\u0026gt;\u0026gt; 6 sorted(list) # 排序，返回排序后的list。 len(list) # list长度，len([1,2,3]) \u0026gt;\u0026gt;\u0026gt; 3 divmod(a,b) # 获取商和余数。 divmod(5,2) \u0026gt;\u0026gt;\u0026gt; (2,1) pow(a,b) # 获取乘方数。pow(2,3) \u0026gt;\u0026gt;\u0026gt; 8 round(a,b) # 获取指定位数的小数。a代表浮点数，b代表要保留的位数。round(3.1415926,2) \u0026gt;\u0026gt;\u0026gt; 3.14 range(a[,b]) # 生成一个a到b的数组,左闭右开。 range(1,10) \u0026gt;\u0026gt;\u0026gt; [1,2,3,4,5,6,7,8,9] 类型转换 # int(str) # 转换为int型。int(\u0026#39;1\u0026#39;) \u0026gt;\u0026gt;\u0026gt; 1 float(int/str) # 将int型或字符型转换为浮点型。float(\u0026#39;1\u0026#39;) \u0026gt;\u0026gt;\u0026gt; 1.0 str(int) # 转换为字符型。str(1) \u0026gt;\u0026gt;\u0026gt; \u0026#39;1\u0026#39; bool(int) # 转换为布尔类型。 str(0) \u0026gt;\u0026gt;\u0026gt; False str(None) \u0026gt;\u0026gt;\u0026gt; False bytes(str,code) # 接收一个字符串，与所要编码的格式，返回一个字节流类型。bytes(\u0026#39;abc\u0026#39;, \u0026#39;utf-8\u0026#39;) \u0026gt;\u0026gt;\u0026gt; b\u0026#39;abc\u0026#39; list(iterable) # 转换为list。 list((1,2,3)) \u0026gt;\u0026gt;\u0026gt; [1,2,3] iter(iterable) # 返回一个可迭代的对象。 iter([1,2,3]) \u0026gt;\u0026gt;\u0026gt; \u0026lt;list_iterator object at 0x0000000003813B00\u0026gt; dict(iterable) # 转换为dict。 dict([(\u0026#39;a\u0026#39;, 1), (\u0026#39;b\u0026#39;, 2), (\u0026#39;c\u0026#39;, 3)]) \u0026gt;\u0026gt;\u0026gt; {\u0026#39;a\u0026#39;:1, \u0026#39;b\u0026#39;:2, \u0026#39;c\u0026#39;:3} enumerate(iterable) # 返回一个枚举对象。 tuple(iterable) # 转换为tuple。 tuple([1,2,3]) \u0026gt;\u0026gt;\u0026gt;(1,2,3) set(iterable) # 转换为set。 set([1,4,2,4,3,5]) \u0026gt;\u0026gt;\u0026gt; {1,2,3,4,5} set({1:\u0026#39;a\u0026#39;,2:\u0026#39;b\u0026#39;,3:\u0026#39;c\u0026#39;}) \u0026gt;\u0026gt;\u0026gt; {1,2,3} hex(int) # 转换为16进制。hex(1024) \u0026gt;\u0026gt;\u0026gt; \u0026#39;0x400\u0026#39; oct(int) # 转换为8进制。 oct(1024) \u0026gt;\u0026gt;\u0026gt; \u0026#39;0o2000\u0026#39; bin(int) # 转换为2进制。 bin(1024) \u0026gt;\u0026gt;\u0026gt; \u0026#39;0b10000000000\u0026#39; chr(int) # 转换数字为相应ASCI码字符。 chr(65) \u0026gt;\u0026gt;\u0026gt; \u0026#39;A\u0026#39; ord(str) # 转换ASCI字符为相应的数字。 ord(\u0026#39;A\u0026#39;) \u0026gt;\u0026gt;\u0026gt; 65 功能相关 # eval() # 执行一个表达式，或字符串作为运算。 eval(\u0026#39;1+1\u0026#39;) \u0026gt;\u0026gt;\u0026gt; 2 exec() # 执行python语句。 exec(\u0026#39;print(\u0026#34;Python\u0026#34;)\u0026#39;) \u0026gt;\u0026gt;\u0026gt; Python filter(func, iterable) # 通过判断函数fun，筛选符合条件的元素。 filter(lambda x: x\u0026gt;3, [1,2,3,4,5,6]) \u0026gt;\u0026gt;\u0026gt; \u0026lt;filter object at 0x0000000003813828\u0026gt; map(func, *iterable) # 将func用于每个iterable对象。 map(lambda a,b: a+b, [1,2,3,4], [5,6,7]) \u0026gt;\u0026gt;\u0026gt; [6,8,10] zip(*iterable) # 将iterable分组合并。返回一个zip对象。 list(zip([1,2,3],[4,5,6])) \u0026gt;\u0026gt;\u0026gt; [(1, 4), (2, 5), (3, 6)] type() # 返回一个对象的类型。 id() # 返回一个对象的唯一标识值。 hash(object) # 返回一个对象的hash值，具有相同值的object具有相同的hash值。 hash(\u0026#39;python\u0026#39;) \u0026gt;\u0026gt;\u0026gt; 7070808359261009780 help() # 调用系统内置的帮助系统。 isinstance() # 判断一个对象是否为该类的一个实例。 issubclass() # 判断一个类是否为另一个类的子类。 globals() # 返回当前全局变量的字典。 next(iterator[, default]) # 接收一个迭代器，返回迭代器中的数值，如果设置了default，则当迭代器中的元素遍历后，输出default内容。 reversed(sequence) # 生成一个反转序列的迭代器。 reversed(\u0026#39;abc\u0026#39;) \u0026gt;\u0026gt;\u0026gt; [\u0026#39;c\u0026#39;,\u0026#39;b\u0026#39;,\u0026#39;a\u0026#39;] 函数的定义 # def 函数名称(参数列表): \u0026#34;\u0026#34;\u0026#34; 函数说明 :param a:参数 a 的说明 :param b:参数 b 的说明 :return 返回值的说明 \u0026#34;\u0026#34;\u0026#34; 函数体 return 返回值 函数代码块以 def 关键词开头，后接函数标识符名称和圆括号 ()。 任何传入参数和自变量必须放在圆括号中间，圆括号之间可以用于定义参数。 函数的第一行语句可以选择性地使用文档字符串（单行\u0026quot;\u0026quot;，多行\u0026quot;\u0026quot;\u0026quot; \u0026quot;\u0026quot;\u0026quot;）用于存放函数说明。 函数内容以冒号 : 起始，并且缩进。 return [表达式] 结束函数，选择性地返回一个值给调用方，不带表达式的 return 相当于返回 None。 返回值 # python的返回值，可以同时返回多个值，被多个变量按顺序接收\n实际上，函数返回值只有一个，是Tuple类型的元组，只是在赋值的时候会按照位置赋值给变量\ndef address(): \u0026#34;生成x、y坐标值\u0026#34; return 98,77 x,y = address() print(x) print(y) 参数 # 必须参数 # def printStr(str): \u0026#34;打印字符串，str为必填参数\u0026#34; print(str) printStr(\u0026#34;hello\u0026#34;) 关键字参数 # 由于传参时必须按照参数列表顺序，使用参数名，可以不按照顺序进行传参\ndef msg(name,age): \u0026#34;打印个人信息\u0026#34; print(\u0026#39;姓名：\u0026#39;,name,\u0026#39;年龄：\u0026#39;,age) msg(age=18,name=\u0026#39;lucy\u0026#39;) 默认参数 # 声明函数时默认参数必须声明在非默认参数后面\n传参时可以不传，使用默认值\n默认参数必须指向不变对象\ndef msg(name,age,sex = \u0026#39;男\u0026#39;): \u0026#34;打印个人信息，性别默认值为男\u0026#34; print(\u0026#39;姓名：\u0026#39;,name,\u0026#39;年龄：\u0026#39;,age,\u0026#39;性别：\u0026#39;,sex) msg(age=18,name=\u0026#39;lucy\u0026#39;,sex = \u0026#39;女\u0026#39;) msg(\u0026#39;tom\u0026#39;,25) 可变参数 # def add(*nums): total = 0 for i in nums: total += i return total #多个参数可以由逗号分隔传入 print(add(1,2,3,4,5)) #列表、元组、集合传入可以在变量名前加*进行解构，以参数的形式传入 li = [1,2,3,4,5] tup = (1,2,3,4,5) se = {1,2,3,4,5} print(add(*li)) print(add(*tup)) print(add(*se)) 传入字典 # 传入字符串的时候，可以不加引号\ndef showDect(**dec): print(dec) showDect(lucy = 18,tom = 25) \u0026gt;\u0026gt;\u0026gt;{\u0026#39;lucy\u0026#39;: 18, \u0026#39;tom\u0026#39;: 25} 类型标注 # 声明一个函数参数的类型，只要在参数名称的后面加个:号，带上类型名称就行了。声明函数的返回值类型，只要在函数声明结束之前，也就是:号之前加入一个-\u0026gt;，带上类型名称。\nint,long,float: 整型,长整形,浮点型 bool,str: 布尔型，字符串类型 list, tuple, dict, set:列表，元组，字典, 集合 Iterable,Iterator:可迭代类型，迭代器类型 Generator：生成器类型 None: 空 def address(a:int,b:int) -\u0026gt; tuple[int,int]: \u0026#34;生成x、y坐标值\u0026#34; return 98,77 函数式编程 # #此处可以看到，函数名也是变量 print(abs) #将内置函数赋值给自定义变量 myAbs = abs print(myAbs) #调用函数 print(myAbs(-1)) 函数作为形参传入 # def add(a,b): \u0026#34;两数相加\u0026#34; return a + b def handle(num1,num2,func): \u0026#34;对两数进行运算\u0026#34; return func(num1,num2) a = handle(5,6,add) print(a) 函数作为返回值 # 在函数handle中定义了add函数，内部函数add可以引用handle的参数和局部变量，当调用handle返回add时，所有的参数和变量都保存在add中，这种称为闭包\n#调用handle时并没有执行add函数，而是返回add函数 #nums会在add函数中保存 def handle(*nums): def add(): total = 0 for i in nums: total = total + i return total return add #接收返回的add函数 addFunc = handle(1,2,3,4,5) #调用函数 print(addFunc()) 匿名函数 # python 使用 lambda 来创建匿名函数。\n所谓匿名，意即不再使用 def 语句这样标准的形式定义一个函数。\nlambda 只是一个表达式，函数体比 def 简单很多。 lambda的主体是一个表达式，而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。 lambda 函数拥有自己的命名空间，且不能访问自己参数列表之外或全局命名空间里的参数。 虽然lambda函数看起来只能写一行，却不等同于C或C++的内联函数，后者的目的是调用小函数时不占用栈内存从而增加运行效率。 #lambda声明格式：lambda 参数：返回值 lambda x:x * x #相当于 def func(x): return x * x def handle(num1,num2,func): \u0026#34;对两数进行运算，运算逻辑可以由func传入\u0026#34; return func(num1,num2) a = handle(5,6,lambda num1,num2:num1 + num2) print(a) 装饰器 # 使用装饰器（装饰器设计模式Decorator），可以在代码运行期间动态增加功能\n原理 # def log(func): print(\u0026#39;函数\u0026#39;,func.__name__,\u0026#39;被执行\u0026#39;) return func @log def now(): \u0026#34;获取当前日期\u0026#34; return \u0026#39;2022-2-11\u0026#39; print(now()) \u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt;相当于\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt; def log(func): print(\u0026#39;函数\u0026#39;,func.__name__,\u0026#39;被执行\u0026#39;) return func def now(): \u0026#34;获取当前日期\u0026#34; return \u0026#39;2022-2-11\u0026#39; print(log(now)()) 对于有参 # def log(func): def handle(a,b): print(\u0026#39;函数\u0026#39;,func.__name__,\u0026#39;被执行\u0026#39;) return func(a,b) return handle @log def now(a,b): \u0026#34;获取当前日期\u0026#34; return \u0026#39;2022-2-11\u0026#39; print(now(1,2)) \u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt;相当于\u0026lt;\u0026lt;\u0026lt;\u0026lt;\u0026lt; def log(func): def handle(a,b): print(\u0026#39;函数\u0026#39;,func.__name__,\u0026#39;被执行\u0026#39;) return func(a,b) return handle def now(a,b): \u0026#34;获取当前日期\u0026#34; return \u0026#39;2022-2-11\u0026#39; print(log(now)(1,2)) 例子 # def log(func): def handle(*args,**dicArg): print(\u0026#39;函数\u0026#39;,func.__name__,\u0026#39;被调用\u0026#39;) print(\u0026#39;参数列表\u0026#39;,args,dicArg) ret = func(*args,**dicArg) print(\u0026#39;返回值\u0026#39;,ret) return ret return handle @log def add(num1,num2): return num1 + num2 @log def printMsg(msg): print(msg) @log def sayHello(): print(\u0026#39;Hello\u0026#39;) add(4,5) printMsg(\u0026#39;lucy\u0026#39;) sayHello() \u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt; 函数 add 被调用 参数列表 (4, 5) {} 返回值 9 \u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt; 函数 printMsg 被调用 参数列表 (\u0026#39;lucy\u0026#39;,) {} lucy 返回值 None \u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026gt; 函数 sayHello 被调用 参数列表 () {} Hello 返回值 None ","date":"2025-04-22","externalUrl":null,"permalink":"/posts/1b37041b/aae761a6/50243d25/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e内置函数 \n    \u003cdiv id=\"内置函数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%86%85%e7%bd%ae%e5%87%bd%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e数学相关 \n    \u003cdiv id=\"数学相关\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%95%b0%e5%ad%a6%e7%9b%b8%e5%85%b3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eabs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 求取绝对值。abs(-1) \u0026gt;\u0026gt;\u0026gt; 1 \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003emax\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 求取list最大值。max([1,2,3]) \u0026gt;\u0026gt;\u0026gt; 3\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003emin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 求取list最小值。min([1,2,3]) \u0026gt;\u0026gt;\u0026gt; 1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003esum\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 求取list元素的和。 sum([1,2,3]) \u0026gt;\u0026gt;\u0026gt; 6\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003esorted\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 排序，返回排序后的list。\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003elen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# list长度，len([1,2,3]) \u0026gt;\u0026gt;\u0026gt; 3\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003edivmod\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 获取商和余数。 divmod(5,2) \u0026gt;\u0026gt;\u0026gt; (2,1)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003epow\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 获取乘方数。pow(2,3) \u0026gt;\u0026gt;\u0026gt; 8\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eround\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 获取指定位数的小数。a代表浮点数，b代表要保留的位数。round(3.1415926,2) \u0026gt;\u0026gt;\u0026gt; 3.14\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003erange\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e[,\u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 生成一个a到b的数组,左闭右开。 range(1,10) \u0026gt;\u0026gt;\u0026gt; [1,2,3,4,5,6,7,8,9]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e类型转换 \n    \u003cdiv id=\"类型转换\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%b1%bb%e5%9e%8b%e8%bd%ac%e6%8d%a2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 转换为int型。int(\u0026#39;1\u0026#39;) \u0026gt;\u0026gt;\u0026gt; 1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003efloat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 将int型或字符型转换为浮点型。float(\u0026#39;1\u0026#39;) \u0026gt;\u0026gt;\u0026gt; 1.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 转换为字符型。str(1) \u0026gt;\u0026gt;\u0026gt; \u0026#39;1\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ebool\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 转换为布尔类型。 str(0) \u0026gt;\u0026gt;\u0026gt; False str(None) \u0026gt;\u0026gt;\u0026gt; False\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ebytes\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 接收一个字符串，与所要编码的格式，返回一个字节流类型。bytes(\u0026#39;abc\u0026#39;, \u0026#39;utf-8\u0026#39;) \u0026gt;\u0026gt;\u0026gt; b\u0026#39;abc\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eiterable\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 转换为list。 list((1,2,3)) \u0026gt;\u0026gt;\u0026gt; [1,2,3]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eiter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eiterable\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 返回一个可迭代的对象。 iter([1,2,3]) \u0026gt;\u0026gt;\u0026gt; \u0026lt;list_iterator object at 0x0000000003813B00\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003edict\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eiterable\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 转换为dict。 dict([(\u0026#39;a\u0026#39;, 1), (\u0026#39;b\u0026#39;, 2), (\u0026#39;c\u0026#39;, 3)]) \u0026gt;\u0026gt;\u0026gt; {\u0026#39;a\u0026#39;:1, \u0026#39;b\u0026#39;:2, \u0026#39;c\u0026#39;:3}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eenumerate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eiterable\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 返回一个枚举对象。\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003etuple\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eiterable\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 转换为tuple。 tuple([1,2,3]) \u0026gt;\u0026gt;\u0026gt;(1,2,3)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eiterable\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 转换为set。 set([1,4,2,4,3,5]) \u0026gt;\u0026gt;\u0026gt; {1,2,3,4,5} set({1:\u0026#39;a\u0026#39;,2:\u0026#39;b\u0026#39;,3:\u0026#39;c\u0026#39;}) \u0026gt;\u0026gt;\u0026gt; {1,2,3}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ehex\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 转换为16进制。hex(1024) \u0026gt;\u0026gt;\u0026gt; \u0026#39;0x400\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eoct\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 转换为8进制。 oct(1024) \u0026gt;\u0026gt;\u0026gt; \u0026#39;0o2000\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ebin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 转换为2进制。 bin(1024) \u0026gt;\u0026gt;\u0026gt; \u0026#39;0b10000000000\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003echr\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 转换数字为相应ASCI码字符。 chr(65) \u0026gt;\u0026gt;\u0026gt; \u0026#39;A\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eord\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 转换ASCI字符为相应的数字。 ord(\u0026#39;A\u0026#39;) \u0026gt;\u0026gt;\u0026gt; 65\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e功能相关 \n    \u003cdiv id=\"功能相关\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8a%9f%e8%83%bd%e7%9b%b8%e5%85%b3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eeval\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 执行一个表达式，或字符串作为运算。 eval(\u0026#39;1+1\u0026#39;) \u0026gt;\u0026gt;\u0026gt; 2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eexec\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 执行python语句。 exec(\u0026#39;print(\u0026#34;Python\u0026#34;)\u0026#39;) \u0026gt;\u0026gt;\u0026gt; Python\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003efilter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efunc\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eiterable\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 通过判断函数fun，筛选符合条件的元素。 filter(lambda x: x\u0026gt;3, [1,2,3,4,5,6]) \u0026gt;\u0026gt;\u0026gt; \u0026lt;filter object at 0x0000000003813828\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efunc\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003eiterable\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 将func用于每个iterable对象。 map(lambda a,b: a+b, [1,2,3,4], [5,6,7]) \u0026gt;\u0026gt;\u0026gt; [6,8,10]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ezip\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003eiterable\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 将iterable分组合并。返回一个zip对象。 list(zip([1,2,3],[4,5,6])) \u0026gt;\u0026gt;\u0026gt; [(1, 4), (2, 5), (3, 6)]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 返回一个对象的类型。\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 返回一个对象的唯一标识值。\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ehash\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eobject\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 返回一个对象的hash值，具有相同值的object具有相同的hash值。 hash(\u0026#39;python\u0026#39;) \u0026gt;\u0026gt;\u0026gt; 7070808359261009780\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ehelp\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 调用系统内置的帮助系统。\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eisinstance\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 判断一个对象是否为该类的一个实例。\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eissubclass\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 判断一个类是否为另一个类的子类。\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eglobals\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 返回当前全局变量的字典。\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003enext\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eiterator\u003c/span\u003e\u003cspan class=\"p\"\u003e[,\u003c/span\u003e \u003cspan class=\"n\"\u003edefault\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 接收一个迭代器，返回迭代器中的数值，如果设置了default，则当迭代器中的元素遍历后，输出default内容。\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ereversed\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esequence\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# 生成一个反转序列的迭代器。 reversed(\u0026#39;abc\u0026#39;) \u0026gt;\u0026gt;\u0026gt; [\u0026#39;c\u0026#39;,\u0026#39;b\u0026#39;,\u0026#39;a\u0026#39;]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e函数的定义 \n    \u003cdiv id=\"函数的定义\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%87%bd%e6%95%b0%e7%9a%84%e5%ae%9a%e4%b9%89\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e函数名称\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e参数列表\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e    函数说明\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e    :param a:参数 a 的说明\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e    :param b:参数 b 的说明\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e    :return 返回值的说明\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e    \u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003e函数体\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003e返回值\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e函数代码块以 \u003cstrong\u003edef\u003c/strong\u003e 关键词开头，后接函数标识符名称和圆括号 \u003cstrong\u003e()\u003c/strong\u003e。\u003c/li\u003e\n\u003cli\u003e任何传入参数和自变量必须放在圆括号中间，圆括号之间可以用于定义参数。\u003c/li\u003e\n\u003cli\u003e函数的第一行语句可以选择性地使用文档字符串（单行\u003ccode\u003e\u0026quot;\u0026quot;\u003c/code\u003e，多行\u003ccode\u003e\u0026quot;\u0026quot;\u0026quot; \u0026quot;\u0026quot;\u0026quot;\u003c/code\u003e）用于存放函数说明。\u003c/li\u003e\n\u003cli\u003e函数内容以冒号 \u003cstrong\u003e:\u003c/strong\u003e 起始，并且缩进。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ereturn [表达式]\u003c/strong\u003e 结束函数，选择性地返回一个值给调用方，不带表达式的 return 相当于返回 None。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003e返回值 \n    \u003cdiv id=\"返回值\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%bf%94%e5%9b%9e%e5%80%bc\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003epython的返回值，可以同时返回多个值，被多个变量按顺序接收\u003c/p\u003e","title":"5、函数","type":"posts"},{"content":"菜单的信息量是⾮常⼤的，给用户提供导航使用，由于菜单⼜可以有⼦菜单，因此菜单的信息量⾮常⼤。菜单的分类也较多，通常可以分为下拉菜单、弹出菜单等等。\n顶层菜单 # 我们可以使⽤ Menu 类来新建⼀个菜单， Menu 和其他的组件⼀样，第⼀个是parent ，这⾥通常可以为窗⼝。 然后我们可以⽤ add_commmand ⽅法来为它添加菜单项， 如果该菜单是顶层菜单，则添加的菜单项依次向右添加。 如果该菜单时顶层菜单的⼀个菜单项，则它添加的是下拉菜单的菜单项。 add_command 中的参数常⽤的有 label 属性，⽤来指定的 是菜单项的名称， command属性⽤来指定被点击的时候调⽤的⽅法， acceletor属性指定的是快捷键， underline 属性是是否拥有下划线。 最后可以⽤窗⼝的 menu 属性指定我们使⽤哪⼀个作为它的顶层菜单。 如果有子菜单的话，那么使用add_cascade方法添加子菜单即可。 import tkinter as tk root = tk.Tk() # 主菜单 root_menu = tk.Menu(root) f_menu = tk.Menu(root_menu) # 文件菜单 f_menu.add_command(label=\u0026#39;Open\u0026#39;,command=lambda :print(\u0026#39;File - Open\u0026#39;)) f_menu.add_command(label=\u0026#39;Save\u0026#39;,command=lambda :print(\u0026#39;File - Save\u0026#39;)) e_menu = tk.Menu(root_menu) # 编辑菜单 e_menu.add_command(label=\u0026#39;Copy\u0026#39;,command=lambda :print(\u0026#39;Edit - Copy\u0026#39;)) e_menu.add_command(label=\u0026#39;Undo\u0026#39;,command=lambda :print(\u0026#39;Edit - Undo\u0026#39;)) # 将一级菜单绑定到主菜单 root_menu.add_cascade(label=\u0026#39;File\u0026#39;,menu=f_menu) root_menu.add_cascade(label=\u0026#39;Edit\u0026#39;,menu=e_menu) # 将主菜单绑定到窗口 root[\u0026#39;menu\u0026#39;] = root_menu root.mainloop() 弹出菜单 # 弹出菜单⼜叫“上下⽂菜单”，也叫“右键菜单”，它通常是⿏标单击右键产⽣的菜单，因此会有“右键菜单” 的说法，但是弹出菜单的触发方式不一定非得右键触发。\n⼤体思路就是：我们先新建⼀个菜单，然后向菜单项中添加各种功能，最后我们监听⿏标右键消息，如果是⿏标右键被单击，此时可以根据需要判断下⿏标位置来确定是哪个弹出菜单被弹出，然后使⽤ Menu 类的 pop ⽅法来弹出菜单。\nMenu 类⾥⾯有⼀个 post ⽅法，它接收两个参数，即 x 和 y 坐标，它会在相应的位置弹出菜单。\nimport tkinter as tk root = tk.Tk() # 创建菜单 pop_menu = tk.Menu(root) # 创建菜单item pop_menu.add_command(label=\u0026#39;copy\u0026#39;,command=lambda :print(\u0026#39;copy\u0026#39;)) pop_menu.add_command(label=\u0026#39;undodo\u0026#39;,command=lambda :print(\u0026#39;undodo\u0026#39;)) # 绑定监听右键事件，弹出菜单 root.bind(\u0026#39;\u0026lt;Button-3\u0026gt;\u0026#39;,lambda e : pop_menu.post(e.x_root,e.y_root)) root.mainloop() ","date":"2025-04-15","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/a879fe06/a3107015/","section":"文章","summary":"\u003cp\u003e菜单的信息量是⾮常⼤的，给用户提供导航使用，由于菜单⼜可以有⼦菜单，因此菜单的信息量⾮常⼤。菜单的分类也较多，通常可以分为下拉菜单、弹出菜单等等。\u003c/p\u003e","title":"5、菜单Menu","type":"posts"},{"content":"选中模型后，在右边可以找到物理系统的菜单\n刚体物理系统 # 刚体一般用于描述两个坚硬物体的碰撞\n可以在右侧物理菜单中为选中（一个）模型添加，如果需要为选中的多个模型添加，使用布局-物体菜单进行添加即可\n布料物理系统 # 布料就是字面意思，给一个模型赋予类似于布的物理特性\n","date":"2025-04-12","externalUrl":null,"permalink":"/posts/69064821/f1464f3a/1f10ffda/","section":"文章","summary":"\u003cp\u003e选中模型后，在右边可以找到物理系统的菜单\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20250412144417556\"\n    data-zoom-src=\"/posts/69064821/f1464f3a/1f10ffda/image/image-20250412144417556.png\"\n    src=\"/posts/69064821/f1464f3a/1f10ffda/image/image-20250412144417556.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e刚体物理系统 \n    \u003cdiv id=\"刚体物理系统\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%9a%e4%bd%93%e7%89%a9%e7%90%86%e7%b3%bb%e7%bb%9f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e刚体一般用于描述两个坚硬物体的碰撞\u003c/p\u003e","title":"5、物理系统","type":"posts"},{"content":"在现有的 Maven 项目中引入 Kotlin 支持，可以通过以下几个步骤来完成\n声明全局 Kotlin 版本，根据项目而定\n\u0026lt;properties\u0026gt; \u0026lt;project.build.sourceEncoding\u0026gt;UTF-8\u0026lt;/project.build.sourceEncoding\u0026gt; \u0026lt;kotlin.code.style\u0026gt;official\u0026lt;/kotlin.code.style\u0026gt; \u0026lt;kotlin.compiler.jvmTarget\u0026gt;1.8\u0026lt;/kotlin.compiler.jvmTarget\u0026gt; \u0026lt;kotlin.version\u0026gt;1.8.0\u0026lt;/kotlin.version\u0026gt; \u0026lt;/properties\u0026gt; 添加 Kotlin 插件 # \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.jetbrains.kotlin\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;kotlin-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${kotlin.version}\u0026lt;/version\u0026gt; \u0026lt;!-- 使用适合你项目的版本 --\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;id\u0026gt;compile\u0026lt;/id\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;compile\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;sourceDirs\u0026gt; \u0026lt;sourceDir\u0026gt;${project.basedir}/src/main/kotlin\u0026lt;/sourceDir\u0026gt; \u0026lt;sourceDir\u0026gt;${project.basedir}/src/main/java\u0026lt;/sourceDir\u0026gt; \u0026lt;/sourceDirs\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;id\u0026gt;test-compile\u0026lt;/id\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;test-compile\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;sourceDirs\u0026gt; \u0026lt;sourceDir\u0026gt;${project.basedir}/src/test/kotlin\u0026lt;/sourceDir\u0026gt; \u0026lt;sourceDir\u0026gt;${project.basedir}/src/test/java\u0026lt;/sourceDir\u0026gt; \u0026lt;/sourceDirs\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;/plugin\u0026gt; 添加 Kotlin 标准库依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.jetbrains.kotlin\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;kotlin-test-junit5\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${kotlin.version}\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.junit.jupiter\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;junit-jupiter-engine\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;5.8.2\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.jetbrains.kotlin\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;kotlin-stdlib-jdk8\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${kotlin.version}\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; ","date":"2025-03-25","externalUrl":null,"permalink":"/posts/b3f5e6ce/1e30d624/2c69b706/","section":"文章","summary":"\u003cp\u003e在现有的 Maven 项目中引入 Kotlin 支持，可以通过以下几个步骤来完成\u003c/p\u003e\n\u003cp\u003e声明全局 Kotlin 版本，根据项目而定\u003c/p\u003e","title":"5、Maven项目","type":"posts"},{"content":"在之前，我们一直在使用顶层定义\nval a = 20 //直接在kt文件中定义变量 fun message() { //直接在kt文件中定义函数 println(\u0026#34;我是测试方法\u0026#34;) } 而学习了类之后，这些内容也可以定义到类中，作为类的属性存在。\n类的定义与对象创建 # class Student { //在没有任何内容时，花括号可以省略 } 除了直接在某个.kt文件中直接编写之外，为了规范，我们一般将一个类单独创建一个文件，也就是类名称与创建的类文件是一模一样的。\n构造函数 # 主要构造函数 # 构造函数也是函数的一种，但是它是专用于对象的创建，Kotlin中的类可以添加一个主构造函数和一个或多个次要构造函数。主构造函数是类定义的一部分，像下面这样编写：\nclass Student constructor(name: String,age: Int) { } 如果主构造函数没有任何注释或可见性修饰符，则可以省略constructor关键字，如果类中没有其他内容要写，可以直接省略花括号，最后就变成这样了：\nclass Student(name: String,age: Int) 次要构造函数 # 除了直接使用主构造函数创建对象外，我们也可以添加一些次要构造函数，比如我们的学生可以只需要一个名字就能完成创建，我们可以直接在类中编写一个次要构造函数\n如果该类有一个主构造函数，则每个次要构造函数需要通过另一个次要构造函数直接或间接委托给主构造函数。委托到同一类的另一个构造函数是this关键字完成的\nclass Student(var name: String, var age: Int){ // 次要构造 constructor(name:String) : this(name, 0) } 如果一个类没有主构造函数，那么我们也可以直接在在类中编写次要构造函数，但是不需要主动委托一次主构造函数，他这里会隐式包含\nclass Student{ var name: String = \u0026#34;\u0026#34; var age: Int = 0 constructor(name: String,age: Int){ this.name = name this.age = age } } 主构造函数和次要构造函数区别 # 主构造函数： 可以直接在主构造函数中定义类属性，使用更方便，但是主构造函数只能存在一个，并且无法编写函数体，只有为类属性做初始化赋值的效果。 辅助（次要）构造函数： 可以存在多个，并且可以自定义函数体，但是无法像主构造函数那样定义类属性，并且当类具有主构造函数时，所有次要构造函数必须直接或间接地调用主构造函数。 属性 # 但是，上面仅仅是定义了构造函数的参数，这还不是类的属性，我们可以为这些属性添加var或val关键字来表示这个属性是可变还是不变的\nclass Student(var name: String, val age: Int) 这样才算是定义了类的属性，我们也可以给这些属性设置初始值\nclass Student(var name: String, val age: Int = 18) //默认每个学生18岁 除了将属性添加到构造函数中，我们也可以将这些属性直接作为类的成员变量写到类中，但是这种情况必须要配一个默认值，否则无法通过编译\nclass Student { var name: String = \u0026#34;\u0026#34; //必须配一个默认值 var age: Int = 0 } 这样我们就可以不编写主构造函数也能定义属性，但是这里仍然会隐式生成一个无参的构造函数，为了构造函数能够方便地传值初始化，也可以像这样写\nclass Student(name: String, age: Int) { var name: String = name //通过构造函数传递过来 var age: Int = age } 懒加载 # 当然，如果不希望这些属性在一开始就有初始值，而是之后某一个时刻去设定初始值，我们也可以为其添加懒加载\nclass Student{ lateinit var name: String var age: Int = 0 } getter 和 setter # 像这样编写的类成员变量，也可以自定义对应的getter和setter属性\nclass Student{ var name: String = \u0026#34;\u0026#34; get() { return field } set (value) { field = value } var age: Int = 0 get() { return field } set(value) { field = value } } 对象的创建 # 跟我们调用普通函数一样，这里的函数名称就是类的名称，如果一个类没有编写构造函数，那么这个类默认情况下使用一个无参构造函数创建\nvar s = Student() 如果是有构造函数的类，我们只需要填写需要的参数即可，调用之后，类的属性就是这里我们给进去的参数了\nvar s = Student(\u0026#34;小明\u0026#34;, 18) 可以使用.来获取和修改对象的属性\nfun main() { var s = Student() s.name = \u0026#34;张三\u0026#34; s.age = 18 println(\u0026#34;name:${s.name},age:${s.age}\u0026#34;) } 对象的初始化 # 在对象创建时，我们可能需要做一些初始化工作，我们可以使用初始化代码块来完成，初始化代码块使用init关键字来完成。\n比如由于主构造函数无法编写函数体，但是我们需要主构造做一些初始化的逻辑，这个时候就可以使用init\nclass Student(var name: String,var age: Int){ init { if(age \u0026lt; 18){ age = 18 } } } 存在多个初始化操作时，从上往下按顺序执行\nclass Student(var name: String,var age: Int){ init { println(\u0026#34;第一个init\u0026#34;) } init { println(\u0026#34;第二个init\u0026#34;) } } 需要注意一下，次要构造函数实际上需要先执行主构造函数，而在执行主构造函数时，会优先将初始化代码块执行\nclass Student(var name: String,var age: Int){ init { println(\u0026#34;第一个init\u0026#34;) } init { println(\u0026#34;第二个init\u0026#34;) } constructor(name: String):this(name,18){ println(\u0026#34;次要构造函数\u0026#34;) } } //第一个init //第二个init //次要构造函数 类的成员函数 # class Student(var name: String,var age: Int){ fun sayHello(){ println(\u0026#34;Hello, my name is $name, and I am $age years old.\u0026#34;) } } fun main() { var s = Student(\u0026#34;张三\u0026#34;,18) s.sayHello() } 如果函数有和类属性名称相同的参数，如果函数中的变量存在歧义，那么优先使用作用域最近的一个，比如函数形参的name作用域更近，那么这里的name拿到的一个是形参name，而不是类的成员属性name。\nclass Student(var name: String, var age: Int) { //此时函数的参数也有一个name变量，而类的成员也有一个name属性 fun hello(name: String){ //这里得到的name是方法的形参，而不是类属性 println(\u0026#34;大家好啊，我叫$name，今年$age岁了\u0026#34;) //如果我们需要获取的是类中的成员属性，需要使用this关键字来表示当前类 println(\u0026#34;大家好啊，我叫${this.name}，今年$age岁了\u0026#34;) } } 在类中，我们同样可以定义多个同名但不同参数的函数实现重载\nclass Student(private var name: String, private var age: Int) { fun hello() = println(\u0026#34;大家好啊，我叫${this.name}，今年${age}岁了\u0026#34;) fun hello(gender: String) = println(\u0026#34;大家好啊，我叫${this.name}，今年${age}岁了，性别${gender}\u0026#34;) } 运算符重载 # Kotlin支持为程序中已知的运算符集提供自定义实现，这些运算符具有固定的符号表示（如+或*）以及对应的优先级，要实现运算符重载，请为相应类型提供具有对应运算符指定名称的成员函数，而当前的类对象，则直接作为对应运算符左边的操作数，如果是一元运算符（比如++自增运算符，只需要身）则直接作为操作数参与运算。\n其实 Kotlin 本身就有很多类型重载了运算符，例如 Int 类型的变量，plus方法就是重载的运算符，定义如下\npublic operator fun plus(other: Long): Long 这个函数添加了一个operator关键字，这其实是运算符重载，能够自定义运算符实现的功能，我们之前使用这些数字进行运算，比如加减乘除，实际上都是这些基本类型在类中重载了运算符实现的。\n我们可以为自定义类重载运算符\nfun main() { var p1 = Point(1.0,2.0) var p2 = Point(3.0,4.0) var p3 = p1 + p2 println(\u0026#34;p3.x = ${p3.x},p3.y = ${p3.y}\u0026#34;) } class Point(var x: Double,var y: Double){ // 重载加号运算符 operator fun plus(other: Point): Point{ return Point(x + other.x,y + other.y) } } 运算符对应函数名称 # 一元运算符 # 符号 对应的函数名称 +a a.unaryPlus() -a a.unaryMinus() !a a.not() a-- a.dec() a++ a.inc() 二元运算符 # 符号 对应的函数名称 a + b a.plus(b) a - b a.minus(b) a * b a.times(b) a / b a.div(b) a % b a.rem(b) a..b a.rangeTo(b) a..\u0026lt;b a.rangeUntil(b) 对于in这种运算，必须返回Boolean类型的结果\n符号 对应的函数名称 a in b b.contains(a) a !in b !b.contains(a) 自增简化运算符 # 这类运算符都是将运算结果赋值给左边的操作数，比如a = a + b等价于a += b\n符号 对应的函数名称 a += b a.plusAssign(b) a -= b a.minusAssign(b) a *= b a.timesAssign(b) a /= b a.divAssign(b) a %= b a.remAssign(b) 比较运算符 # 所有比较都会转换为compareTo函数调用，此函数返回Int值，这个值用于和 0 比较判断是否满足条件。\n运算符 对应的函数名称 a \u0026gt; b a.compareTo(b) \u0026gt; 0 a \u0026lt; b a.compareTo(b) \u0026lt; 0 a \u0026gt;= b a.compareTo(b) \u0026gt;= 0 a \u0026lt;= b a.compareTo(b) \u0026lt;= 0 小括号 # 运算符 对应的函数名称 a() a.invoke() a(i) a.invoke(i) a(i, j) a.invoke(i, j) a(i_1, ..., i_n) a.invoke(i_1, ..., i_n) 中括号 # 运算符 对应的函数名称 a[i] a.get(i) a[i, j] a.get(i, j) a[i_1, ..., i_n] a.get(i_1, ..., i_n) a[i] = b a.set(i, b) a[i, j] = b a.set(i, j, b) a[i_1, ..., i_n] = b a.set(i_1, ..., i_n, b) 中缀函数 # 实际上中缀函数在我们之前很多时候都有出现，比如位运算\nprintln(i shl 1) 这里的shl并不是一个运算符，而是一段自定义的英文单词，像这种运算符是怎么做到的呢？\n这其实是中缀函数，用infix关键字标记的函数被称为中缀函数，在使用时，可以省略调用的点和括号进行调用，Infix函数必须满足以下要求：\n必须是成员函数。 只能有一个参数。 参数不能有默认值。 fun main() { var s = Student(\u0026#34;张三\u0026#34;, 18) println(s isAge 18) } class Student(var name: String,var age: Int) { infix fun isAge(age: Int): Boolean { return this.age == age } } 中缀函数调用的优先级低于算术运算符、类型转换和rangeTo运算符，例如以下表达式就是等效的：\n1 shl 2 + 3相当于1 shl (2 + 3) 0 until n * 2相当于0 until (n * 2) xs union ys as Set\u0026lt;*\u0026gt;相当于xs union (ys as Set\u0026lt;*\u0026gt;)（类型转换会在下一章多态进行介绍） 另一方面，infix函数调用的优先级高于布尔运算符\u0026amp;\u0026amp;和||、is-和in-checks以及其他一些运算符的优先级。这些表达式也是等价的：\na \u0026amp;\u0026amp; b xor c相当于a \u0026amp;\u0026amp; (b xor c) a xor b in c相当于(a xor b) in c 同时，如果需在类中使用中缀函数，必须明确函数的调用方（接收器）比如\nclass MyStringCollection { infix fun add(s: String) { /*...*/ } fun build() { this add \u0026#34;abc\u0026#34; // 正确 add(\u0026#34;abc\u0026#34;) // 正确 //add \u0026#34;abc\u0026#34; // 错误: 没有指定调用方或无法隐式表达 } } 解构声明 # 有时候，我们在使用对象时可能需要访问它们内部的一些属性\nfun main() { val student = Student(\u0026#34;小明\u0026#34;, 18) println(student.name) println(student.age) } 利用解构，可以直接得到Student对象内部的name和age熟悉作为变量使用\nfun main() { var s = Student(\u0026#34;张三\u0026#34;, 18) var (name,age) = s println(\u0026#34;name:$name,age:$age\u0026#34;) } class Student(var name: String,var age: Int) { // 声明结构出来的每一个变量对应的属性 operator fun component1() = name operator fun component2() = age } 结构同样也适用于Lambda 表达式\n// a,b 就是从 Student 中结构出来的 val func2: (Student, Int) -\u0026gt; Unit = { (a, b), i -\u0026gt; println(\u0026#34;名字: $a, 年龄: $b\u0026#34;) println(i) } 访问权限控制 # 有些时候，我们可能不希望别人使用我们的所有内容。\n在类、对象、接口、构造函数和函数，以及属性上，可以为其添加 可见性修饰符 来控制其可见性，在Kotlin中有四个可见性修饰符，它们分别是：private、protected、internal和public，默认可见性是public，在使用顶级声明时，不同可见性的访问权限如下：\n如果不使用可见性修饰符，则默认使用public，这意味着这里声明的内容将在任何地方可访问。 如果使用private修饰符，那么声明的内容只能在当前文件中可访问。 如果使用internal修饰符，它将在同一模块中可见（当前的项目中可以随意访问，与public没大差别，但是如果别人引用我们的项目，那么无法使用） 顶级声明不支持使用protected修饰符。 private fun inner(){ //我们不希望这个函数能够在其他地方被调用 } 在类中定义成员属性时，不同可见性的访问权限如下：\nprivate意味着该成员仅在此类中可见（包括其所有成员） protected与private的可见性类似，外部无法使用，但在子类中可以使用 internal意味着本项目中任何地方都会看到其internal成员，但是别人引用我们项目时不行。 public意味着任何地方都可以访问。 class Student(private var name: String, //name属性无法被外部访问，因为是私有的 internal var age: Int) { //age可以被外部访问，但是无法再其他项目中访问到 private constructor() : this(\u0026#34;\u0026#34;, 10) //这个无参构造无法被外部访问，因为是私有的 } 封装、继承和多态 # 类的封装 # 封装的目的是为了保证变量的安全性，使用者不必在意具体实现细节，而只是通过外部接口即可访问类的成员，如果不进行封装，类中的实例变量可以直接查看和修改，可能给整个程序带来不好的影响，因此在编写类时一般将成员变量私有化，外部类需要使用Getter和Setter函数来查看和设置变量。\nclass Student(private var name: String, private var age: Int) { fun getName(): String = name fun getAge(): Int = age } 我们甚至还可以将主构造函数改成私有的，需要通过其他的构造函数来构造\nclass Student private constructor(private var name: String, private var age: Int) { constructor() : this(\u0026#34;\u0026#34;, 18) } 类的继承 # 在定义不同类的时候存在一些相同属性，为了方便使用可以将这些共同属性抽象成一个父类，在定义其他子类时可以继承自该父类，减少代码的重复定义，根据前面的访问权限等级，子类可以使用父类中所有非私有的成员。\n在Kotlin中，我们可以使用继承操作来实现这样的结构，默认情况下，Kotlin类是“终态”的（不能被任何类继承）要使类可继承，需要用open关键字标记需要被继承的类\nKotlin 的类只能 单继承 ！\nopen class Student(var name: String,var age: Int) class ArtStudent(var artLevel: Int, name: String, age: Int): Student(name, age) 以上这样是在继承的时候指定调用父类的构造函数，如果父类有多个构造函数，那么可以使用super来指定调用哪个构造函数\nopen class Student(var name: String,var age: Int){ constructor(name: String): this(name, 0) constructor(): this(\u0026#34;\u0026#34;,0) } class ArtStudent: Student{ var score: Int = 0 constructor(score: Int): super(\u0026#34;Tom\u0026#34;,18){ this.score = score } } 如何理解上面这一堆写法：\n构造函数相当于是这个类初始化的最基本函数，在构造对象时一定要调用 主构造函数因为可能存在一些类的属性，所以说必须在初始化时调用，不能让这些属性初始化时没有初始值 子类因为是父类的延展，因此，子类在初始化时，必须先初始化父类，就好比每个学生都有学生证，这是属于父类的属性，如果子类在初始化时可以不去初始化父类，那岂不是美术生可以没有学生证？显然是不对的。 优先级关系：父类初始化 - 子类主构造 - 子类辅助构造\n属性的覆盖 # 我们可以希望子类继承父类的某些属性，但是我们可能希望去修改这些属性的默认实现\n我们可以使用override关键字来表示对于一个属性的重写（覆盖）\nopen class Student { //注意，跟类一样，函数必须添加open关键字才能被子类覆盖 open fun hello() = println(\u0026#34;我会打招呼\u0026#34;) } class ArtStudent : Student() { fun draw() = println(\u0026#34;我会画画\u0026#34;) //在子类中编写一个同名函数，并添加override关键字，我们就可以在子类中进行覆盖了，然后编写自己的实现 override fun hello() = println(\u0026#34;哦哈哟\u0026#34;) } 属性也是一样\nopen class Student { open val name: String = \u0026#34;大明\u0026#34; fun hello() = println(\u0026#34;我会打招呼，我叫: $name\u0026#34;) } //在主构造函数中覆盖，也是可以的，这样会将构造时传入的值进行覆盖 class ArtStudent(override val name: String) : Student() { fun draw() = println(\u0026#34;我会画画\u0026#34;) } 重写父类方法之后，但是还需要调用父类方法，就可以使用super关键字来调用\nopen class Student { open fun hello() = println(\u0026#34;我会打招呼\u0026#34;) } class ArtStudent : Student() { fun draw() = println(\u0026#34;我会画画\u0026#34;) override fun hello() { //覆盖父类函数 super.hello() //使用super.xxx来调用父类的函数实现，这里super同样表示父类 println(\u0026#34;哦哈哟\u0026#34;) //再写自己的逻辑 } } 类的多态 # fun main() { var s: Student = ArtStudent(1, \u0026#34;Tom\u0026#34;, 18) } open class Student(var name: String,var age: Int) class ArtStudent(var artLevel: Int, name: String, age: Int): Student(name, age) 顶层Any类 # 在我们不继承任何类的情况下，实际上Kotlin会有一个默认的父类，所有的类默认情况下都是继承自Any类的。\n这个类的定义如下\n/** * Kotlin类继承结构中的根类. 所有Kotlin中的类都会直接或间接将Any作为父类 */ public open class Any { /** * 判断某个对象是否\u0026#34;等于\u0026#34;当前对象，这里同样是对运算符\u0026#34;==\u0026#34;的重载，而具体判断两个对象相等的操作需要由子类来定义 * 在一些特定情况下，子类在重写此函数时应该保证以下要求: * * 可反身: 对于任意非空值 `x`, 表达式 `x.equals(x)` 应该返回true * * 可交换: 对于任意非空值 `x` 和 `y`, `x.equals(y)` 当且仅当 `y.equals(x)` 返回true时返回true * * 可传递: 对于任意非空值 `x`, `y`, 和 `z`, 如果 `x.equals(y)` 和 `y.equals(z)` 都返回true, 那么 `x.equals(z)` 也应该返回真 * * 一致性: 对于任意非空值 `x` 和 `y`, 在多次调用 `x.equals(y)` 函数时，只要不修改在对象的“equals”比较中使用的信息，那么应当始终返回同样的结果 * * 永不等于空: 对于任意非空值 `x`, `x.equals(null)` 应该始终返回false */ public open operator fun equals(other: Any?): Boolean /** * 返回当前对象的哈希值，它具有以下约束: * * * 对同一对象多次调用该函数时，只要不修改对象上的equals比较中使用的信息，那么此函数就必须始终返回相同的整数 * * 如果两个对象通过`equals`函数判断为true，那么这两个对象的哈希值也应该相同 */ public open fun hashCode(): Int /** * 将此对象转换为一个字符串，具体转换为什么样子的字符串由子类自己决定 */ public open fun toString(): String } 抽象类 # 有些情况下，我们设计的类可能仅仅是作为给其他类继承使用的类，而其本身并不需要创建任何实例对象\n//使用abstract表示这个是一个抽象类 abstract class Student { abstract val type: String //抽象类中可以存在抽象成员属性 abstract fun hello() //抽象类中可以存在抽象函数 //注意抽象的属性不能为private，不然子类就没法重写了 } 当一个子类继承自抽象类时，必须要重写抽象类中定义的抽象属性和抽象函数：\nclass ArtStudent: Student() { override val type: String = \u0026#34;美术生\u0026#34; override fun hello() = println(\u0026#34;$type\u0026#34;) } 当然，抽象类不仅可以具有抽象的属性，同时也具有普通类的性质，同样可以定义非抽象的属性或函数\nabstract class Student { abstract val type: String abstract fun hello() fun test() = println(\u0026#34;不会有人玩到大三了才开始学Java吧\u0026#34;) //定义非抽像属性或函数，在子类中不强制要求重写 } 接口 # 接口只能包含函数或属性的定义，所有的内容只能是abstract的，它不像类那样完整。\nKotlin 的接口是 多实现 ！\ninterface Student { var name: String fun sayHello() } 接口中的函数可以具有默认实现，默认情况下是open的，除非private掉\ninterface Student { var name: String fun sayHello() = println(\u0026#34;Hello $name\u0026#34;) } 实现接口的方式和继承一样，直接写到后面，多个接口用逗号隔开\nclass Student: A,B{ override fun a() { } override fun b() { } } interface A{ fun a() } interface B{ fun b() } 如果实现的多个接口有相同的函数，那么就只需要实现一次。但是要使用接口的默认实现，那么就需要使用super关键字指定。\nclass Student: A,B{ override fun test() { super\u0026lt;A\u0026gt;.test() } } interface A{ fun test() = println(\u0026#34;A\u0026#34;) } interface B{ fun test() = println(\u0026#34;B\u0026#34;) } 类的扩展 # Kotlin提供了扩展类或接口的操作，而无需通过类继承或使用装饰器等设计模式，来为某个类添加一些额外的函数或是属性，我们只需要通过一个被称为扩展的特殊声明来完成。\n需要注意的是，我们在不同包中定义的扩展属性，同样会受到访问权限控制，需要进行导入才可以使用\n扩展方法 # 比如我们想为String类型添加一个自定义的方法test\nfun String.myTest() = this.length fun main() { var s = \u0026#34;Tom\u0026#34; println(s.myTest()) } 扩展属性 # 直接var String.myValue: String = \u0026quot;1234\u0026quot;这样扩展属性是不允许的，因为扩展并不是真的往类中添加属性，因此，扩展属性本质上也不会真的插入一个成员字段到类的定义中，这就导致并没有变量去存储我们的数据。\n我们只能明确定义一个getter和setter来创建扩展属性，才能让它使用起来真的像是类的属性一样。\nvar String.myLength: Int get() = this.length set(value) {} ","date":"2025-03-24","externalUrl":null,"permalink":"/posts/b3f5e6ce/129ca5af/c9de3973/","section":"文章","summary":"\u003cp\u003e在之前，我们一直在使用顶层定义\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-kotlin\" data-lang=\"kotlin\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eval\u003c/span\u003e \u003cspan class=\"py\"\u003ea\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e20\u003c/span\u003e   \u003cspan class=\"c1\"\u003e//直接在kt文件中定义变量\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efun\u003c/span\u003e \u003cspan class=\"nf\"\u003emessage\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e   \u003cspan class=\"c1\"\u003e//直接在kt文件中定义函数\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"n\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;我是测试方法\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e而学习了类之后，这些内容也可以定义到类中，作为类的属性存在。\u003c/p\u003e","title":"5、类和对象","type":"posts"},{"content":" maven-jar-plugin # 默认maven打包的结果没有指定mainClass，所以使用java -jar命令会显示xxx.jar中没有主清单属性，使用此插件可以指定mainClass\n注意：这个插件并不会打包依赖进jar，所以如果有依赖的项目，需要使用maven-dependency-plugin、maven-shade-plugin等插件\n\u0026lt;build\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven.plugins\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-jar-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.2.2\u0026lt;/version\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;archive\u0026gt; \u0026lt;manifest\u0026gt; \u0026lt;addClasspath\u0026gt;true\u0026lt;/addClasspath\u0026gt; \u0026lt;!-- 此处为主入口 --\u0026gt; \u0026lt;mainClass\u0026gt;top.ygang.demo.Start\u0026lt;/mainClass\u0026gt; \u0026lt;/manifest\u0026gt; \u0026lt;/archive\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/build\u0026gt; maven-dependency-plugin # 处理与依赖相关的插件，它有很多可用的goal，大部分是和依赖构建、分析和解决相关的goal，这部分goal可以直接用maven的命令操作，例如：mvn dependency:tree、mvn dependency:analyze 但是我们最常用到的是 dependency:copy 、dependency:copy-dependencies 及dependency:unpack、dependency:unpack-dependencies 这四个。\n例如有的时候，并不希望依赖打进jar、而是放在jar文件的同级目录中，可以与maven-jar-plugin插件搭配，来实现这种效果\n\u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven.plugins\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-jar-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.2.2\u0026lt;/version\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;archive\u0026gt; \u0026lt;manifest\u0026gt; \u0026lt;addClasspath\u0026gt;true\u0026lt;/addClasspath\u0026gt; \u0026lt;!-- 设置classpath的前缀，最终会写在MANIFEST.MF中 --\u0026gt; \u0026lt;classpathPrefix\u0026gt;lib/\u0026lt;/classpathPrefix\u0026gt; \u0026lt;!-- 此处为主入口 --\u0026gt; \u0026lt;mainClass\u0026gt;top.ygang.Start\u0026lt;/mainClass\u0026gt; \u0026lt;/manifest\u0026gt; \u0026lt;/archive\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven.plugins\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-dependency-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.6\u0026lt;/version\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;id\u0026gt;copy-dependencies\u0026lt;/id\u0026gt; \u0026lt;phase\u0026gt;package\u0026lt;/phase\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;copy-dependencies\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;outputDirectory\u0026gt;${project.build.directory}/lib\u0026lt;/outputDirectory\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;/plugin\u0026gt; purge-local-repository # 这个命令会清理pom.xml中的包，并重新下载，但是并不清理不在pom.xml中的依赖包。\nmvn dependency:purge-local-repository #reResolve是否重新解析依赖关系 #actTransitively是否应该对所有传递依赖性起作用。默认值为true。 mvn dependency:purge-local-repository -DactTransitively=false -DreResolve=false maven-shade-plugin # 默认maven打包的结果只包含项目本身的代码，并不包含项目的依赖，maven-shade-plugin插件可以帮助我们将项目的依赖包打进最终jar文件\n采用的是合并依赖项的方式，也就是将所有的依赖jar解压后的字节码等文件和项目的字节码等文件全部打进最终jar，以便在单个JAR文件中包含所有的依赖\n会生成两个jar，一个含有依赖项的jar，另一个以original-开头的不含依赖的jar\n\u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven.plugins\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-shade-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.4.3\u0026lt;/version\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;phase\u0026gt;package\u0026lt;/phase\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;shade\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;transformers\u0026gt; \u0026lt;transformer implementation=\u0026#34;org.apache.maven.plugins.shade.resource.ManifestResourceTransformer\u0026#34;\u0026gt; \u0026lt;mainClass\u0026gt;top.yhgh.demo.Start\u0026lt;/mainClass\u0026gt; \u0026lt;/transformer\u0026gt; \u0026lt;/transformers\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;/plugin\u0026gt; maven-assembly-plugin # Assembly 插件的主要作用是，允许用户将项目输出与它的依赖项、模块、站点文档、和其他文件一起组装成一个可分发的归档文件。说白了就是：结构定制化的打包。\n使用Assembly插件需要一个描述符文件（配置文件），该文件指定了打包格式，包含的文件/过滤的文件等信息，可以同时指定多个描述符文件，打包成不同的格式。\n\u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven.plugins\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-assembly-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.4\u0026lt;/version\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;!-- 绑定到package生命周期阶段上 --\u0026gt; \u0026lt;phase\u0026gt;package\u0026lt;/phase\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;!-- 该打包任务只运行一次 --\u0026gt; \u0026lt;goal\u0026gt;single\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;descriptors\u0026gt; \u0026lt;!-- 描述文件路径 --\u0026gt; \u0026lt;descriptor\u0026gt;src/main/resources/assembly.xml\u0026lt;/descriptor\u0026gt; \u0026lt;/descriptors\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;/plugin\u0026gt; 描述文件 # \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;utf-8\u0026#34;?\u0026gt; \u0026lt;assembly xmlns=\u0026#34;http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd\u0026#34;\u0026gt; \u0026lt;!-- id 标识符，添加到生成文件名称的后缀符。 如果指定 id 的话（这里指定的是项目的版本），目标文件则是 ${artifactId}-${id}.jar。 --\u0026gt; \u0026lt;id\u0026gt;full\u0026lt;/id\u0026gt; \u0026lt;!-- 指定打包格式。 支持的打包格式有zip、tar、tar.gz (or tgz)、tar.bz2 (or tbz2)、jar、dir、war 可以同时指定多个打包格式 --\u0026gt; \u0026lt;formats\u0026gt; \u0026lt;format\u0026gt;jar\u0026lt;/format\u0026gt; \u0026lt;/formats\u0026gt; \u0026lt;!-- 指定打的包是否包含打包层目录（比如finalName是ygang-demo， 当值为true，所有文件被放在包内的ygang-demo目录下，否则直接放在包的根目录下） --\u0026gt; \u0026lt;includeBaseDirectory\u0026gt;true\u0026lt;/includeBaseDirectory\u0026gt; \u0026lt;!-- 指定将工程依赖的包打到包里的指定目录下 --\u0026gt; \u0026lt;dependencySets\u0026gt; \u0026lt;dependencySet\u0026gt; \u0026lt;!-- 指定打包时是否包含工程自身生成的jar包 --\u0026gt; \u0026lt;useProjectArtifact\u0026gt;true\u0026lt;/useProjectArtifact\u0026gt; \u0026lt;!-- 指定将这些依赖包打到包里lib目录下 --\u0026gt; \u0026lt;outputDirectory\u0026gt;lib\u0026lt;/outputDirectory\u0026gt; \u0026lt;!-- 用于管理依赖的部署，runtime表示只在运行时使用 --\u0026gt; \u0026lt;scope\u0026gt;runtime\u0026lt;/scope\u0026gt; \u0026lt;/dependencySet\u0026gt; \u0026lt;/dependencySets\u0026gt; \u0026lt;!-- 指定要包含的文件集，可以定义多个fileSet --\u0026gt; \u0026lt;fileSets\u0026gt; \u0026lt;fileSet\u0026gt; \u0026lt;!-- 指定归档文件（要打的jar包）要包含的目录（下的文件及文件夹） --\u0026gt; \u0026lt;directory\u0026gt;src/main/script/linux/bin\u0026lt;/directory\u0026gt; \u0026lt;!-- 指定要将当前目录（\u0026lt;directory\u0026gt;标签中的目录放在归档文件（要打的jar包）bin目录下） --\u0026gt; \u0026lt;outputDirectory\u0026gt;bin\u0026lt;/outputDirectory\u0026gt; \u0026lt;!-- 精确控制要包含的文件，\u0026lt;excludes\u0026gt;用于精确控制要排除的文件 --\u0026gt; \u0026lt;includes\u0026gt; \u0026lt;include\u0026gt;ygang-demo\u0026lt;/include\u0026gt; \u0026lt;include\u0026gt;server\u0026lt;/include\u0026gt; \u0026lt;/includes\u0026gt; \u0026lt;!-- 设置文件 UNIX 属性，是一种读写权限 --\u0026gt; \u0026lt;fileMode\u0026gt;0755\u0026lt;/fileMode\u0026gt; \u0026lt;/fileSet\u0026gt; \u0026lt;/fileSets\u0026gt; \u0026lt;/assembly\u0026gt; javapackager # 可以用来将java项目打包为可执行项目，例如exe\nGitHub：https://github.com/fvarrui/JavaPackager\n\u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;io.github.fvarrui\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;javapackager\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.7.2\u0026lt;/version\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;phase\u0026gt;package\u0026lt;/phase\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;package\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;!-- 启动类 --\u0026gt; \u0026lt;mainClass\u0026gt;top.ygang.Start\u0026lt;/mainClass\u0026gt; \u0026lt;!-- 是否捆绑一个jre，默认为false --\u0026gt; \u0026lt;bundleJre\u0026gt;true\u0026lt;/bundleJre\u0026gt; \u0026lt;!-- 是否使用精简版jre ，默认为true，可以不写--\u0026gt; \u0026lt;customizedJre\u0026gt;true\u0026lt;/customizedJre\u0026gt; \u0026lt;!-- jre路径，可以不写 --\u0026gt; \u0026lt;jrePath\u0026gt;C:\\Program Files\\Java\\jre1.8.0_311\u0026lt;/jrePath\u0026gt; \u0026lt;!-- jdk路径，可以不写 --\u0026gt; \u0026lt;jdkPath\u0026gt;C:\\Program Files\\Java\\jdk1.8.0_311\u0026lt;/jdkPath\u0026gt; \u0026lt;!-- 是否生成安装包，默认为true，可以不写 --\u0026gt; \u0026lt;generateInstaller\u0026gt;true\u0026lt;/generateInstaller\u0026gt; \u0026lt;!-- 是否使用管理员身份打开应用，默认为false，可以不写 --\u0026gt; \u0026lt;administratorRequired\u0026gt;false\u0026lt;/administratorRequired\u0026gt; \u0026lt;!-- App Name --\u0026gt; \u0026lt;name\u0026gt;My Demo\u0026lt;/name\u0026gt; \u0026lt;displayName\u0026gt;${name}\u0026lt;/displayName\u0026gt; \u0026lt;!-- 平台，默认为auto，可选windows、mac、linux --\u0026gt; \u0026lt;platform\u0026gt;auto\u0026lt;/platform\u0026gt; \u0026lt;!-- 虚拟机参数 --\u0026gt; \u0026lt;vmArgs\u0026gt; \u0026lt;vmArg\u0026gt;--add-opens java.base/java.lang=ALL-UNNAMED\u0026lt;/vmArg\u0026gt; \u0026lt;/vmArgs\u0026gt; \u0026lt;!-- windows平台配置 --\u0026gt; \u0026lt;winConfig\u0026gt; \u0026lt;icoFile\u0026gt;D:\\logo.ico\u0026lt;/icoFile\u0026gt; \u0026lt;/winConfig\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;/plugin\u0026gt; flatten-maven-plugin # flatten-maven-plugin 是一个由 MojoHaus 组织维护的 Maven 插件，主要用于解决 Maven 多模块项目中 POM 文件继承导致的复杂依赖问题。\n在多模块项目中，父模块的版本号通常通过占位符（如 ${revision}）定义，子模块继承父模块的版本号。但在打包时，这些占位符不会被替换为实际值，导致远程仓库无法正确解析。 flatten-maven-plugin 可以在构建过程中将这些占位符替换为实际值，确保生成的 POM 文件可以被正确解析。\n在父工程引入如下插件即可\n\u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.codehaus.mojo\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;flatten-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.5.0\u0026lt;/version\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;flattenMode\u0026gt;resolveCiFriendliesOnly\u0026lt;/flattenMode\u0026gt; \u0026lt;updatePomFile\u0026gt;true\u0026lt;/updatePomFile\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;flatten\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;id\u0026gt;flatten\u0026lt;/id\u0026gt; \u0026lt;phase\u0026gt;process-resources\u0026lt;/phase\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;clean\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;id\u0026gt;flatten.clean\u0026lt;/id\u0026gt; \u0026lt;phase\u0026gt;clean\u0026lt;/phase\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;/plugin\u0026gt; ","date":"2025-02-10","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/4ee304f3/cd016813/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003emaven-jar-plugin \n    \u003cdiv id=\"maven-jar-plugin\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#maven-jar-plugin\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e默认maven打包的结果没有指定\u003ccode\u003emainClass\u003c/code\u003e，所以使用\u003ccode\u003ejava -jar\u003c/code\u003e命令会显示\u003cstrong\u003exxx.jar中没有主清单属性\u003c/strong\u003e，使用此插件可以指定\u003ccode\u003emainClass\u003c/code\u003e\u003c/p\u003e","title":"5、常用插件","type":"posts"},{"content":" 要解决的问题 # 前后端分离之后，前端和后端都拥有自己独立的服务器，但是服务器之间可能为了完成某些业务逻辑，就存在服务器之间的跨域访问\n跨域问题的原因就是违背了同源策略：即客户端协议、域名、端口号和服务器存在不同\n后端解决跨域访问的方式 # 方式一：使用@CrossOrigin注解 # 前后端分离的情况下，在凡是需要前后端交互的Controller身上，添加注解@CrossOrigin\n可以作用在整个Controller类上，也可以作用在特定的一个方法上\n@CrossOrigin的参数 # origins： 允许可访问的域列表 maxAge：准备响应前的缓存持续的最大时间（以秒为单位） 方式二：直接在SpringBoot的配置类中，添加跨域支持（常用） # @Configuration public class CORSConfiguration extends WebMvcConfigurationSupport { @Override protected void addCorsMappings(CorsRegistry registry) { registry.addMapping(\u0026#34;/**\u0026#34;) .allowedOrigins(\u0026#34;http://localhost:63343\u0026#34;,\u0026#34;http://127.0.0.1:5500\u0026#34;) .allowedMethods(\u0026#34;GET\u0026#34;, \u0026#34;HEAD\u0026#34;, \u0026#34;POST\u0026#34;,\u0026#34;PUT\u0026#34;, \u0026#34;DELETE\u0026#34;,\u0026#34;OPTIONS\u0026#34;) .allowedHeaders(\u0026#34;*\u0026#34;) .exposedHeaders(\u0026#34;access-control-allow-headers\u0026#34;, \u0026#34;access-control-allow-methods\u0026#34;, \u0026#34;access-control-allow-origin\u0026#34;, \u0026#34;access-control-max-age\u0026#34;, \u0026#34;X-Frame-Options\u0026#34;) .allowCredentials(true); super.addCorsMappings(registry); } } 或者\n@Configuration public class CORSConfiguration { private CorsConfiguration corsConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin(\u0026#34;*\u0026#34;); corsConfiguration.addAllowedHeader(\u0026#34;*\u0026#34;); corsConfiguration.addAllowedMethod(\u0026#34;*\u0026#34;); corsConfiguration.setAllowCredentials(true); corsConfiguration.setMaxAge(3600L); return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(\u0026#34;/**\u0026#34;, corsConfig()); return new CorsFilter(source); } } 解决跨域Session失效 # 添加注解属性 # //允许客户端发送cookie信息 @CrossOrigin(allowCredentials = \u0026#34;true\u0026#34;) 配置axios # axios.defaults.withCredentials = true 配置ajax # $.ajax({ type:\u0026#34;POST\u0026#34;, url:url, data:{data,data}, dataType:\u0026#34;json\u0026#34;, xhrFields: {withCredentials: true}, //与服务器配合使用标识允许携带cookie success:function(data){ }, error:function(XMLHttpRequest){ }, }); ","date":"2024-12-09","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/4f5b7444/9b5fa4f9/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e要解决的问题 \n    \u003cdiv id=\"要解决的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%a6%81%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"说明: clipboard.png\"\n    data-zoom-src=\"/posts/3ab7256e/3f5635d6/4f5b7444/9b5fa4f9/image/202109181345949.gif\"\n    src=\"/posts/3ab7256e/3f5635d6/4f5b7444/9b5fa4f9/image/202109181345949.gif\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"5、跨域处理CORS","type":"posts"},{"content":" Axios # Ajax要么就原生的JS写，要么就用Jquery的封装来写。要用封装，就必须要导入jquery.js。\nVue提倡：去Jquery化\nAxios是新的与后端进行异步交互的方法，但是Axios不属于Vue，它是属于Vue的周边产品，使用时需要引入axios.js\nnpm i axios --save-dev Axios的官网 # http://www.axios-js.com/zh-cn/docs/#axios-request-config-1\n异步请求方式一：get方法 # 格式 # axios.get(\u0026#34;http://ip:port/project?key=value\u0026#34;).then(function (response){ //response.data获取响应回来的数据 alert(response.data); }).catch(function (e){ //e为异常 alert(e); }); 使用 # new Vue({ el:\u0026#34;div\u0026#34;, data:{ users:null, }, created(){ //此处的then()里面函数必须使用lambda表达式，不然无法获取this.users axios.get(\u0026#34;http://172.16.2.67:8080/findAllUser.do?pageNum=1\u0026#34;).then(response =\u0026gt; { this.users = response.data.list; }).catch(function (e){ alert(e); }); } }) 异步请求方式二：post方法 # 格式 # axios.post(\u0026#34;http://ip:port/project\u0026#34;,\u0026#34;key=value\u0026#34;).then(function (response){ //response.data获取响应回来的数据 alert(response.data); }).catch(function (e){ //e为异常 alert(e); }); 使用 # new Vue({ el:\u0026#34;div\u0026#34;, data:{ users:null, }, methods:{ }, created(){ //此处的then()里面函数必须使用lambda表达式，不然无法获取this.users axios.post(\u0026#34;http://172.16.2.67:8080/findAllUser.do\u0026#34;,\u0026#34;pageNum=1\u0026#34;).then(response =\u0026gt; { this.users = response.data.list; }).catch(function (e){ alert(e); }) } }) 异步请求方式三：并发请求 # 格式 # method1(){ return axios.get(\u0026#34;http://ip:port/project?key=value\u0026#34;); }, method2(){ return axios.get(\u0026#34;http://ip:port/project?key=value\u0026#34;); } axios.all([this.method1(),this.method2()]).then(axios.spread(function (response1,response2){ alert(response1.data); alert(response2.data); })) 使用 # new Vue({ el:\u0026#34;div\u0026#34;, data:{ users:null, byid:null, }, methods:{ findAll(){ return axios.get(\u0026#34;http://172.16.2.67:8080/findAllUser.do?pageNum=1\u0026#34;); }, findByid(){ return axios.get(\u0026#34;http://172.16.2.67:8080/findByUid.do?uid=1\u0026#34;); } }, created(){ //此处的axios.spread()里面函数必须使用lambda表达式，不然无法获取this.users axios.all([this.findAll(),this.findByid()]).then(axios.spread((response1,response2) =\u0026gt; { this.users = response1.data.list; this.byid = response2.data; })) } }) 文件上传 # \u0026lt;div id=\u0026#34;app\u0026#34;\u0026gt; \u0026lt;input id=\u0026#34;inputElement\u0026#34; name=\u0026#34;file\u0026#34; type=\u0026#34;file\u0026#34; accept=\u0026#34;image/png, image/gif, image/jpeg\u0026#34; /\u0026gt; \u0026lt;button @click=\u0026#34;upload\u0026#34;\u0026gt;上传\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script\u0026gt; new Vue({ el:\u0026#34;#app\u0026#34;, data() { return { file: null }; }, methods: { upload() { let inputElement = document.getElementById(\u0026#34;inputElement\u0026#34;); //获取input-file中的file对象，只上传一个文件，索引就是0 let file = inputElement.files[0]; // 创建一个form对象 let param = new FormData(); // 通过append向form对象添加数据 param.append(\u0026#34;file\u0026#34;, file); // 添加form表单中其他字段数据 param.append(\u0026#34;key\u0026#34;, \u0026#34;value\u0026#34;); // FormData私有类对象，访问不到，可以通过get判断值是否传进去 console.log(param.get(\u0026#34;file\u0026#34;)); //配置多段请求，设置上传头 let config = { headers: { \u0026#34;Content-Type\u0026#34;: \u0026#34;multipart/form-data\u0026#34; } }; axios.post(\u0026#34;http://192.168.31.253:8080/upload\u0026#34;, param, config); } } }); \u0026lt;/script\u0026gt; Vuecli中使用axios # 1、添加axios到项目中\ncnpm install axios 2、在主配置文件main.js中引入axios\nimport axios from \u0026#39;axios\u0026#39; 3、在Vue原型中挂载axios（全局都可以使用）\nVue.prototype.$axios = axios 4、在其他位置使用axios\nthis.$axios.post().then 封装Axios # 日常业务中我们通常会对axios进行封装来使用，这么做的好处是：我们可以在每次请求前对请求做处理还可以对错误进行统一处理，从而避免每次发请求都要做重复的工作。比如：我们每次发送请求前都需要对请求的url做处理，就可以用到axios请求拦截；对后端返回的错误或网络错误通过响应拦截进行统一处理等等。\nfilter.js，用于对Axios的请求和响应进行拦截，并对Axios进行配置\nimport Axios from \u0026#39;axios\u0026#39;; let baseURL = \u0026#39;\u0026#39;; if (process.env.NODE_ENV == \u0026#39;development\u0026#39;) { baseURL = \u0026#39;/\u0026#39;; } else if (process.env.NODE_ENV == \u0026#39;production\u0026#39;) { baseURL = \u0026#39;/\u0026#39;; } let _axios = Axios.create({ withCredentials: true, // 当前请求为跨域类型时是否在请求中协带cookie baseURL, timeout: 30000, // 设置超时时间 }); _axios.interceptors.request.use( config =\u0026gt; { // 此处进行请求拦截 return config; }, err =\u0026gt; { return Promise.reject(err); } ); _axios.interceptors.response.use( resp =\u0026gt; { // 此处进行响应拦截 return resp }, err =\u0026gt; { if (err \u0026amp;\u0026amp; err.response) { // 此处对err.response.status响应码进行判断 } return Promise.resolve(err.response) } ); export default _axios; requester.js，对各种请求方法进行封装\nimport axios from \u0026#39;@/api/filter.js\u0026#39;; // 封装get方法 export function get(url,params={}){ return new Promise((resolve,reject) =\u0026gt; { axios.get(url,{ params:params }).then(response =\u0026gt; { resolve(response.data); }).catch(err =\u0026gt; { reject(err) }) }) } //封装get路径传参方法 export function getUrl(url,params={}){ return new Promise((resolve,reject) =\u0026gt; { axios.get(url+params).then(response =\u0026gt; { resolve(response.data); }).catch(err =\u0026gt; { reject(err) }) }) } // 封装post请求 export function post(url, params ,way=0) { return new Promise((resolve, reject) =\u0026gt; { axios.post(url,params,{ headers: { \u0026#39;Content-Type\u0026#39;:way == 0 ? \u0026#39;application/json;charset=UTF-8\u0026#39; : \u0026#39;application/x-www-form-urlencoded;charset=UTF-8\u0026#39; } }).then(res =\u0026gt; { resolve(res.data) }).catch(err =\u0026gt; { reject(err) }) }) } // 封装post请求文件下载 export function postDownLoad(url, params,type=1) { return new Promise((resolve, reject) =\u0026gt; { axios.post(url,params,{responseType: type == 0?\u0026#39;blob\u0026#39;:\u0026#39;arraybuffer\u0026#39;},{ headers: { \u0026#39;Content-Type\u0026#39;:\u0026#39;application/json;charset=UTF-8\u0026#39; } }).then(res =\u0026gt; { resolve(res) }).catch(err =\u0026gt; { reject(err) }) }) } // 封装post请求文件上传 export function postUpload(url, params) { return new Promise((resolve, reject) =\u0026gt; { axios.post(url,params,{ headers: { \u0026#39;Content-Type\u0026#39;:\u0026#39;multipart/form-data;charset=utf-8\u0026#39; } }).then(res =\u0026gt; { resolve(res.data) }).catch(err =\u0026gt; { reject(err) }) }) } ","date":"2024-12-03","externalUrl":null,"permalink":"/posts/bafd68f1/b2321cb9/f8290b84/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eAxios \n    \u003cdiv id=\"axios\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#axios\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eAjax要么就原生的JS写，要么就用Jquery的封装来写。要用封装，就必须要导入jquery.js。\u003c/p\u003e","title":"5、axios","type":"posts"},{"content":" 日志系统 # 日志文件 # 日志文件是用于记录系统操作事件的文件集合，可分为事件日志和消息日志。具有处理历史数据、诊断问题的追踪以及理解系统的活动等重要作用。 在计算机中，日志文件是记录在操作系统或其他软件运行中发生的事件或在通信软件的不同用户之间的消息的文件。记录是保持日志的行为。在最简单的情况下，消息被写入单个日志文件。 许多操作系统，软件框架和程序包括日志系统。广泛使用的日志记录标准是在因特网工程任务组（IETF）RFC5424中定义的syslog。 syslog标准使专用的标准化子系统能够生成，过滤，记录和分析日志消息 调试日志 # 软件开发中，我们经常需要去调试程序，做一些信息，状态的输出便于我们查询程序的运行状况。为了让我们能够更加灵活和方便的控制这些调试的信息，所以我们需要专业的日志技术。java中寻找bug会需要重现。调试也就是debug 可以在程序运行中暂停程序运行，可以查看程序在运行中的情况。日志主要是为了更方便的去重现问题。\n系统日志 # 系统日志是记录系统中硬件、软件和系统问题的信息，同时还可以监视系统中发生的事件。用户可以通过它来检查错误发生的原因，或者寻找受到攻击时攻击者留下的痕迹。系统日志包括系统日志、应用程序日志和安全日志。\nJava日志框架 # 一般有两种日志相关的框架，日志框架和日志门面，区别在于：每一种日志框架都有自己单独的API,要使用对应的框架就要使用对应的API，这就大大的增加了应用程序代码对于日志框架的耦合性。我们使用了日志门面技术之后，对于应用程序来说，无论底层的日志框架如何改变，应用程序不需要修改任何代码，就可以直接上线了。\n也就是可以理解为，日志门面是对日志框架技术整合的框架。\n日志框架 # 常用的日志框架有如下：\nJUL（java.util.logging）：Java原生的日志框架 Log4j：Apache的一个开源项目 Logback：由Log4j作者的另一个开源项目，旨在替代Log4j Log4j2：Log4j官方的第二个版本，各个方面都是与Logback及其相似 日志门面 # 常用的日志门面如下：\nJCL：JCL（Jakarta Commons Logging），是Apache提供的一个通用日志API。 SLF4j：简单日志门面(Simple Logging Facade For Java) SLF4J主要是为了给Java日志访问提供一套标准、规范的API框架，其主要意义在于提供接口，具体的实现可以交由其他日志框架，例如log4j和logback等。 日志框架 # JUL # 是java原生的日志框架，使用时不需要另外引用第三方的类库，相对其他的框架使用方便，学习简单，主要是使用在小型应用中。\n主要组件 # Logger：被称为记录器，应用程序通过获取Logger对象，调用用其API来发布日志信息。Logger通常被认为是访问日志系统的入口程序。 Handler：处理器，每个Logger都会关联一个或者是一组Handler，Logger会将日志交给关联的Handler去做处理，由Handler负责将日志做记录。Handler具体实现了日志的输出位置，比如可以输出到控制台或者是文件中等等。 Filter：过滤器，根据需要定制哪些信息会被记录，哪些信息会被略过。 Formatter：格式化组件，它负责对日志中的数据和信息进行转换和格式化，所以它决定了我们输出日志最终的形式。 Level：日志的输出级别，每条日志消息都有一个关联的级别。我们根据输出级别的设置，用来展现最终所呈现的日志信息。根据不同的需求，去设置不同的级别。 SEVERE：错误级别信息，程序出现了严重的错误，造成了程序的终止 — 最高级的日志级别 WARNING: 警告信息，记录程序的一些问题，但是这些信息不会造成程序的终止 INFO : （默认级别）消息记录，记录的比如说数据库的连接信息，IO的传递信息和网络的通讯信息等等 CONFIG : 配置信息 FINE : 详细信息（少） FINER : 详细信息（中） FINEST : 详细信息 （多） — 最低级的日志级别 两个特殊的级别 OFF：可用来关闭日志记录 ALL：启用所有消息的日志记录 简单使用 # // 创建Logger实例 Logger logger = Logger.getLogger(\u0026#34;top.ygang.demo_mvn\u0026#34;); // 配置，如下配置都不是必须的，有默认的 // 关闭按照父Logger的默认设置进行配置 logger.setUseParentHandlers(false); // 获取日志控制台处理实例 ConsoleHandler handler = new ConsoleHandler(); // 创建日志格式化组件实例 SimpleFormatter formatter = new SimpleFormatter(); handler.setFormatter(formatter); // 在Logger中添加处理器（可以添加多个处理器同时处理） logger.addHandler(handler); // 设置日志的处理级别 logger.setLevel(Level.ALL); handler.setLevel(Level.ALL); // 打印所有级别的日志 logger.severe(\u0026#34;severe log...\u0026#34;); logger.warning(\u0026#34;warning log...\u0026#34;); logger.info(\u0026#34;info log...\u0026#34;); logger.config(\u0026#34;config log...\u0026#34;); logger.fine(\u0026#34;fine log...\u0026#34;); logger.finer(\u0026#34;finer log...\u0026#34;); logger.finest(\u0026#34;finest log...\u0026#34;); // 也可以这样打印 logger.log(Level.INFO,\u0026#34;info log...\u0026#34;); // 十一月 09, 2023 8:27:50 下午 top.ygang.demo_mvn.Main main // 严重: severe log... // 十一月 09, 2023 8:27:50 下午 top.ygang.demo_mvn.Main main // 警告: warning log... // 十一月 09, 2023 8:27:50 下午 top.ygang.demo_mvn.Main main // 信息: info log... // 十一月 09, 2023 8:27:50 下午 top.ygang.demo_mvn.Main main // 配置: config log... // 十一月 09, 2023 8:27:50 下午 top.ygang.demo_mvn.Main main // 详细: fine log... // 十一月 09, 2023 8:27:50 下午 top.ygang.demo_mvn.Main main // 较详细: finer log... // 十一月 09, 2023 8:27:50 下午 top.ygang.demo_mvn.Main main // 非常详细: finest log... // 十一月 09, 2023 8:27:50 下午 top.ygang.demo_mvn.Main main // 信息: info log... 字符串模板 # // 创建Logger实例 Logger logger = Logger.getLogger(\u0026#34;top.ygang.demo_mvn\u0026#34;); // {}中填写对应index logger.log(Level.INFO,\u0026#34;name:{0},age:{1}\u0026#34;,new Object[]{\u0026#34;lucy\u0026#34;,18}); FileHandler # FileHandler可以将日志输出到磁盘中，FileHandler默认使用的是XMLFormatter，以XML的形式输出日志，可以自己替换\nLogger logger = Logger.getLogger(\u0026#34;top.ygang.demo_mvn\u0026#34;); FileHandler fileHandler = new FileHandler(\u0026#34;/Users/yanggang/develop/test.log\u0026#34;); logger.addHandler(fileHandler); logger.info(\u0026#34;test file log...\u0026#34;); 父子关系 # JUL中的Logger之间是可以存在父子关系的，父Logger所做的设置可以同时作用于子Logger，子Logger可以使用logger.setUseParentHandlers(false);来关闭继承父Logger配置。所有的Logger的父Logger是RootLogger，名称是\u0026quot;\u0026quot;空字符串，JUL在初始化时会创建一个顶层RootLogger作为所有Logger的父Logger。集成关系一般等同于路径的父子关系。\nLogger logger1 = Logger.getLogger(\u0026#34;top.ygang\u0026#34;); Logger logger2 = Logger.getLogger(\u0026#34;top.ygang.demo_mvn\u0026#34;); System.out.println(\u0026#34;logger1:\u0026#34; + logger1); System.out.println(\u0026#34;logger2:\u0026#34; + logger2); System.out.println(\u0026#34;logger1\u0026#39;s parent:\u0026#34; + logger1.getParent() + \u0026#34;,name:\u0026#34; + logger1.getParent().getName()); System.out.println(\u0026#34;logger2\u0026#39;s parent:\u0026#34; + logger2.getParent() + \u0026#34;,name:\u0026#34; + logger2.getParent().getName()); // logger1:java.util.logging.Logger@45ee12a7 // logger2:java.util.logging.Logger@330bedb4 // logger1\u0026#39;s parent:java.util.logging.LogManager$RootLogger@2503dbd3,name: // logger2\u0026#39;s parent:java.util.logging.Logger@45ee12a7,name:top.ygang 配置文件 # 默认使用的配置文件位于jre_path/lib/logging.properties\n# RootLogger的日志级别 .level= ALL # 启用的Handler,多个可以使用逗号分割 handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler # 文件处理器属性设置 # 输出日志文件的路径 java.util.logging.FileHandler.pattern = %h/java%u.log # 输出日志文件的限制(50000字节) java.util.logging.FileHandler.limit = 50000 # 设置日志文件的数量 java.util.logging.FileHandler.count = 1 # 输出日志的格式 # 默认是以XML的方式进行的输出 java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter # 控制台处理器属性设置 # 控制台输出默认的级别 java.util.logging.ConsoleHandler.level = ALL # 控制台默认输出的格式 java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter # 也可以将日志级别设定到具体的某个包下 top.ygang.level = SEVERE 可以根据自己的需要修改后，进行加载\n// 加载配置文件 InputStream inputStream = Main.class.getClassLoader().getResourceAsStream(\u0026#34;logging.properties\u0026#34;); LogManager logManager = LogManager.getLogManager(); logManager.readConfiguration(inputStream); Logger logger = Logger.getLogger(\u0026#34;top.ygang\u0026#34;); logger.fine(\u0026#34;fine log...\u0026#34;); Log4j # Log4j是Apache的一个开源项目，通过使用Log4j，我们可以控制日志信息输送的目的地是控制台、文件、GUI组件，甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等；我们也可以控制每一条日志的输出格式；通过定义每一条日志信息的级别，我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是，这些可以通过一个配置文件来灵活地进行配置，而不需要修改应用的代码。\n依赖 # \u0026lt;!-- https://mvnrepository.com/artifact/log4j/log4j --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;log4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;log4j\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.17\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 配置文件 # 在ClASSPATH下建立配置文件log4j.properties，Maven项目就是resources目录下\nlog4j.rootLogger = INFO, FILE, CONSOLE log4j.appender.FILE=org.apache.log4j.FileAppender log4j.appender.FILE.File=log.log log4j.appender.FILE.ImmediateFlush=true log4j.appender.FILE.Threshold = DEBUG log4j.appender.FILE.Append=true log4j.appender.FILE.layout=org.apache.log4j.PatternLayout log4j.appender.FILE.layout.conversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss:SSS} (%c\\:%L)-(%t) : %m%n log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Target=System.out log4j.appender.CONSOLE.ImmediateFlush=true log4j.appender.CONSOLE.Threshold = DEBUG log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.encoding=UTF-8 log4j.appender.CONSOLE.layout.conversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss:SSS} (%c\\:%L)-(%t) : %m%n 三大组件 # 在实际应用中，要使Log4j在系统中运行须事先设定配置文件。配置文件事实上也就是对Logger、Appender及Layout进行相应设定。\nLogger（记录器）：日志类别和级别； Appenders（输出源）：日志要输出的地方； Layouts（布局）：日志要以何种形式输出； Logger # 固定语法格式：log4j.rootLogger = [ level ] , appenderName, appenderName, …\nlog4j.rootLogger = INFO, FILE, CONSOLE level Loggers记录器组件在系统中被分为五个级别：DEBUG（调试信息）、INFO（信息）、WARN（警告）、ERROR（错误）和FATAL（致命错误）。这五个级别是有顺序的，DEBUG \u0026lt; INFO \u0026lt; WARN \u0026lt; ERROR \u0026lt; FATAL，分别用来指定这条日志信息的重要程度。 与此同时Log4j有一个自定的规则：只输出级别不低于设定级别（大于等于）的日志信息，假设Loggers记录器级别设定为INFO，则级别大于等于INFO的四个等级INFO,WARN,ERROR,FATAL级别的日志信息都会被输出，而级别比其低的DEBUG不会输出。 appenderName 指的是日志信息输出目的地，在此可以指定多个输出目的地 Appenders # log4j默认定义了几种输出目的\norg.apache.log4j.ConsoleAppender（控制台） org.apache.log4j.FileAppender（文件） org.apache.log4j.DailyRollingFileAppender（每天产生一个日志文件） org.apache.log4j.RollingFileAppender（文件大小到达指定尺寸的时候产生一个新的文件） org.apache.log4j.WriterAppender（将日志信息以流格式发送到任意指定的地方） ConsoleAppender # 将日志信息输出到控制台中\nlog4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender # 将日志信息使用System.out.println输出到控制台 log4j.appender.CONSOLE.Target=System.out # 表示所有信息都会被立即输出，设为false则不输出，默认值是true。 log4j.appender.CONSOLE.ImmediateFlush=true # 指定日志输出的最低级别，默认为DEBUG log4j.appender.CONSOLE.Threshold=DEBUG # 设置日志输出的编码 log4j.appender.CONSOLE.encoding=UTF-8 FileAppender # 将日志信息输出到对应的磁盘文件中\nlog4j.appender.FILE = org.apache.log4j.FileAppender # 指定日志输出的最低级别，默认为DEBUG log4j.appender.FILE.Threshold = DEBUG # 日志输出的路径 log4j.appender.FILE.File=e:/mylog.log # 设置日志输出的编码 log4j.appender.FILE.Encoding=UTF-8 # 将新增日志追加到文件中,默认为true,false为覆盖 log4j.appender.FILE.Append=false # # 表示所有信息都会被立即输出，设为false则不输出，默认值是true。 log4j.appender.FILE.ImmediateFlush=true # 是否使用缓冲，当缓存满了后才输出到磁盘文件中，默认为false log4j.appender.FILE.BufferedIO=true # 缓冲区大小 log4j.appender.FILE.BufferSize=8192 DailyRollingFileAppender # 将输出的日志信息，固定周期产生一个日志文件\nlog4j.appender.DRFILE = org.apache.log4j.DailyRollingFileAppender # 日志输出的路径 log4j.appender.DRFILE.File=e:/log.out # 表示所有信息都会被立即输出，设为false则不输出，默认值是true。 log4j.appender.DRFILE.ImmediateFlush=true # 指定日志输出的最低级别，默认为DEBUG log4j.appender.DRFILE.Threshold=DEBUG # 将新增日志追加到文件中,默认为true,false为覆盖 log4j.appender.DRFILE.Append=true # 标识每天产生一个新的日志文件，当然也可以指定按月、周、时、分 log4j.appender.DRFILE.DatePattern=\u0026#39;.\u0026#39;yyyy-MM-dd # \u0026#39;.\u0026#39;yyyy-MM: 每月 # \u0026#39;.\u0026#39;yyyy-ww: 每周 # \u0026#39;.\u0026#39;yyyy-MM-dd: 每天 # \u0026#39;.\u0026#39;yyyy-MM-dd-a: 每天两次 # \u0026#39;.\u0026#39;yyyy-MM-dd-HH: 每小时 # \u0026#39;.\u0026#39;yyyy-MM-dd-HH-mm: 每分钟 RollingFileAppender # 文件大小到达指定尺寸的时候产生一个新的文件\nlog4j.appender.RFILE=org.apache.log4j.RollingFileAppender # 指定日志输出的最低级别，默认为DEBUG log4j.appender.RFILE.Threshold=DEBUG # 日志输出的路径 log4j.appender.RFILE.File=e:/mylog.log # 设置日志输出的编码 log4j.appender.RFILE.encoding=UTF-8 # 将新增日志追加到文件中,默认为true,false为覆盖 log4j.appender.RFILE.Append=false # 表示所有信息都会被立即输出，设为false则不输出，默认值是true。 log4j.appender.RFILE.ImmediateFlush=true # 指定日志文件切割大小，默认10MB，单位KB/MB/GB # 当日志文件达到指定大小后，将当前日志文件内容剪切到新的日志文件中 # 新的文件默认以“原文件名.1”、“原文件名.2”的形式命名 log4j.appender.RFILE.MaxFileSize=100KB # 产生的切割文件最大数量，如果第二个文件超过了指定大小，那么第一个文件将会被删除 log4j.appender.RFILE.MaxBackupIndex=2 Layout # 配置日志信息的格式Layout，其语法为：log4j.appender.appenderName.layout = className\n其中，appenderName就是上面所讲的Appender的名称，Appender必须与Layout相互绑定\nclassName：是处理日志格式的类，也必须是全限定类名，log4j提供了如下几个类 org.apache.log4j.HTMLLayout（以HTML表格形式布局） org.apache.log4j.PatternLayout（可以灵活地自定义布局模式） org.apache.log4j.SimpleLayout（包含日志信息的级别和信息字符串） org.apache.log4j.TTCCLayout（包含日志产生的时间、线程、类别等等信息） HTMLLayout # 以html表格页面的形式输出日志\nlog4j.appender.FILE.layout=org.apache.log4j.HTMLLayout # 输出java文件名称和行号，默认值false log4j.appender.FILE.layout.LocationInfo=true # html页面的标题 log4j.appender.FILE.layout.Title=My Log SimpleLayout # 简单模式输出日志，只包含日志等级、信息\nlog4j.appender.FILE.layout=org.apache.log4j.SimpleLayout TTCCLayout # 包含日志产生的时间、线程、类别等等信息\nlog4j.appender.FILE.layout=org.apache.log4j.TTCCLayout PatternLayout # 自定义日志输出格式\nlog4j.appender.FILE.layout = org.apache.log4j.PatternLayout # 指定怎样格式化的日志 log4j.appender.FILE.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss:SSS} (%c\\:%L)-(%t) : %m%n # 具体的格式化说明： # %p：输出日志信息的优先级，即DEBUG，INFO，WARN，ERROR，FATAL。 # %d：输出日志时间点的日期或时间，默认格式为ISO8601，也可以在其后指定格式，如：%d{yyyy/MM/dd HH:mm:ss,SSS}。 # %r：输出自应用程序启动到输出该log信息耗费的毫秒数。 # %t：输出产生该日志事件的线程名。 # %l：输出日志事件的发生位置，相当于%c.%M(%F:%L)的组合，包括类全名、方法、文件名以及在代码中的行数。例如：test.TestLog4j.main(TestLog4j.java:10)。 # %c：输出日志信息所属的类目，通常就是所在类的全名。 # %M：输出产生日志信息的方法名。 # %F：输出日志消息产生时所在的文件名称。 # %L:：输出代码中的行号。 # %m:：输出代码中指定的具体日志信息。 # %n：输出一个回车换行符，Windows平台为\u0026#34;\\r\\n\u0026#34;，Unix平台为\u0026#34;\\n\u0026#34;。 # %x：输出和当前线程相关联的NDC(嵌套诊断环境)，尤其用到像java servlets这样的多客户多线程的应用中。 # %%：输出一个\u0026#34;%\u0026#34;字符。 可以在%与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式。如： %5c ：最小宽度是5，小于5，默认的情况下右对齐 %-5c：最小宽度是5，小于5，-号指定左对齐,会有空格 %.5c ：最大宽度是5，大于5，就会将左边多出的字符截掉，小于5不会有空格 %20.30c：小于20补空格，并且右对齐，大于30字符，就从左边交远销出的字符截掉 简单使用 # public class Start { public static final Logger log = Logger.getLogger(Start.class); public static void main(String[] args) { log.debug(\u0026#34;Debug Message!\u0026#34;); log.info(\u0026#34;Info Message!\u0026#34;); log.warn(\u0026#34;Warn Message!\u0026#34;); log.error(\u0026#34;Error Message!\u0026#34;); log.fatal(\u0026#34;Fatal Message!\u0026#34;); } } 日志门面 # Slf4j # slf4j-api是Simple Logging Facade for Java (SLF4J)框架的核心组件，它定义了SLF4J框架的API和日志交互接口，是所有SLF4J实现的基础。\nSLF4J旨在为不同的日志框架提供统一的抽象层，使得应用程序可以在运行时选择最适合的日志框架，而不必修改应用程序代码。SLF4J的API包括日志级别、日志信息的格式化、动态跟踪的绑定机制等，可以让开发者按自己的需求选择不同的日志框架来实现日志功能。并且，在JAVA EE中，SLF4J API是规范化的，可以方便地在web应用程序中使用。\n官方网站： https://www.slf4j.org/\n日志级别 # trace：日志追踪信息，一般不会使用，在日志里边也不会打印出来，最低的一个日志级别。 debug：日志详细信息，一般放于程序的某个关键点的地方，用于打印一个变量值或者一个方法返回的信息之类的信息 info：日志的关键信息，一般处理业务逻辑的时候使用，默认打印级别 warn：日志警告信息，不会影响程序的运行，但是值得注意 error：日志错误信息，必须解决的时候使用此级别打印日志 简单使用 # 使用时和JUL、Log4j类似，仍然是先获取Logger实例，这里有两种获取方法：一种是通过LoggerFactory获取，另一种是通过Lombok插件的@Slf4j注解获取，其原理是在编译期给类添加private static final Logger log = LoggerFactory.getLogger(Xxx.class)\n@Slf4j public class Main { // public static final Logger log = LoggerFactory.getLogger(Main.class); public static void main(String[] args) throws IOException { // slf4j支持使用{}作为占位符，然后后面参数可以填充 log.trace(\u0026#34;{} log...\u0026#34;,\u0026#34;trace\u0026#34;); log.debug(\u0026#34;{} log...\u0026#34;,\u0026#34;debug\u0026#34;); log.info(\u0026#34;{} log...\u0026#34;,\u0026#34;info\u0026#34;); log.warn(\u0026#34;{} log...\u0026#34;,\u0026#34;warn\u0026#34;); log.error(\u0026#34;{} log...\u0026#34;,\u0026#34;error\u0026#34;); } } 具体实现 # slf4j 仅仅是一个日志门面，需要接上具体的日志实现才能完成日志的打印，倘若仅提供slf4j-api，并没有给出具体的日志实现，程序运行时会出现警告，并且没有日志输出。\n例如SpringBoot中默认使用的日志实现是Logback\nslf4j-nop # 为了避免slf4j-api的警告，可以手动指定不输出任何日志。方式是，在maven配置中指定使用slf4j-nop日志后端实现。\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0.5\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-nop\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0.5\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; slf4j-simple # slf4j自己也提供了功能较为简单的实现（slf4j-simple），但是一般很少用到。\n\u0026lt;!-- slf4j-api --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.7.30\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- slf4j自带的简单日志实现 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-simple\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.7.30\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 配置文件，classpath下创建simplelogger.properties\norg.slf4j.simpleLogger.defaultLogLevel=DEBUG org.slf4j.simpleLogger.showDateTime=true org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z org.slf4j.simpleLogger.showThreadName=true org.slf4j.simpleLogger.showLogName=true org.slf4j.simpleLogger.showShortLogName=false JUL # JUL的日志等级与slf4j的日志等级存在较大的差异，在配置logging.properties时要注意。\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0.5\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-jdk14\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0.5\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; log4j # 早期版本\n\u0026lt;!-- slf4j-api --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.7.31\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- log4j的slf4j实现 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-log4j12\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.7.31\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 新版本\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0.5\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-reload4j\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0.5\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; log4j2 # 早期版本\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.7.31\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.logging.log4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;log4j-slf4j-impl\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.19.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 新版本\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0.5\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.logging.log4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;log4j-slf4j2-impl\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.19.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.logging.log4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;log4j-core\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.19.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; logback # logback是slf4j推荐的日志实现，毕竟出自同一家公司。\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-api\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0.5\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;ch.qos.logback\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;logback-classic\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.4.5\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; ","date":"2024-11-29","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/0f3856f3/a04bfb7d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e日志系统 \n    \u003cdiv id=\"日志系统\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%97%a5%e5%bf%97%e7%b3%bb%e7%bb%9f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e日志文件 \n    \u003cdiv id=\"日志文件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%97%a5%e5%bf%97%e6%96%87%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e日志文件是用于记录系统操作事件的文件集合，可分为事件日志和消息日志。具有处理历史数据、诊断问题的追踪以及理解系统的活动等重要作用。\u003c/li\u003e\n\u003cli\u003e在计算机中，日志文件是记录在操作系统或其他软件运行中发生的事件或在通信软件的不同用户之间的消息的文件。记录是保持日志的行为。在最简单的情况下，消息被写入单个日志文件。\u003c/li\u003e\n\u003cli\u003e许多操作系统，软件框架和程序包括日志系统。广泛使用的日志记录标准是在因特网工程任务组（IETF）RFC5424中定义的syslog。 syslog标准使专用的标准化子系统能够生成，过滤，记录和分析日志消息\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch4 class=\"relative group\"\u003e调试日志 \n    \u003cdiv id=\"调试日志\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%b0%83%e8%af%95%e6%97%a5%e5%bf%97\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cp\u003e软件开发中，我们经常需要去调试程序，做一些信息，状态的输出便于我们查询程序的运行状况。为了让我们能够更加灵活和方便的控制这些调试的信息，所以我们需要专业的日志技术。java中寻找bug会需要重现。调试也就是debug 可以在程序运行中暂停程序运行，可以查看程序在运行中的情况。日志主要是为了更方便的去重现问题。\u003c/p\u003e","title":"5、日志系统","type":"posts"},{"content":" 常用命令 # 目录操作 # ls # 查看当前文件夹下的内容\n-a 显示全部的文件，包括隐藏文件（开头为 . 的文件）也一起罗列出来 -A 显示全部的文件，连同隐藏文件，但不包括 . 与 .. 这两个目录 -d 只列出当前目录本身，也就是 . -f ls 默认会以文件名排序，使用 -f 选项会直接列出结果，而不进行排序。 -F 在文件或目录名后加上文件类型的指示符号，例如，* 代表可运行文件，/ 代表目录，= 代表 socket 文件，| 代表 FIFO 文件。 -h 以人们易读的方式显示文件或目录大小，如 1KB、234MB、2GB 等。 -i 显示 inode 节点信息 -l 使用长格式列出文件和目录信息。 -n 以 UID 和 GID 分别代替文件用户名和群组名显示出来。 -r 将排序结果反向输出，比如，若原本文件名由小到大，反向则为由大到小 -R 连同子目录内容一起列出来，等於将该目录下的所有文件都显示出来。 -S 以文件容量大小排序，而不是以文件名排序 -t 以时间排序，而不是以文件名排序 --color=never never表示不依据文件特性给予颜色显示，always代表显示，auto代表自动 --full-time 以完整时间模式 （包含年、月、日、时、分）输出 mkdir # 创建目录\n# 创建单层目录 mkdir [dir] # 创建多层目录 mkdir [/dir1/dir2/dir3] pwd # 查看当前所在全路径\ncd # 进入目录\n# 进入指定目录 cd [path] # 进入跟路径 cd / # 进入用户目录 cd ~ # 进入上层目录 cd .. # 切换上一次的工作目录 cd - rmdir # 删除目录，需要确保目录为空\nrmdir [dir] 文件操作 # touch # 如果文件不存在，则创建文件。如果文件存在，则不作变化。\nrm # 删除文件\n# 删除文件 rm [file] -r 删除目录，可以递归删除 -f 跳过确认 cat # 查看文件内容\ncat [options] [file] -n 由1开始对所有输出的行数编号 -b 和-n相似，只不过对于空白行不编号 -s 遇到有连续两行以上的空白行，就代换为一行的空白行 -v 使用^和M-符号，除了 LFD 和 TAB 之外。 -E 在每行结束处显示$ -T 将 TAB 字符显示为^I less # 它可以显示文件的全部内容，并且可以进行搜索、滚动、翻页等操作，比 cat 命令更加强大和灵活。\nless [options] [file] -N 显示行号。 -n 不显示行号。 -i 忽略大小写。 -F 一次性显示整个文件，不进行分页。 -f 强制显示文件名。 -q 静默模式，不显示任何提示信息。 # 进入less后命令如下 q：退出less 上下箭头：向上、向下滚动 空格：向下翻页 b：向上翻页 g：跳到文件开头 G：跳到文件结尾 /：搜索 head # 显示文件的开头部分，默认情况下显示文件的前10行\nhead [options] [file] -n 指定要显示的行数。例如，-n 20表示显示前20行。 -c 指定要显示的字节数。例如，-c 100表示显示前100个字节。 -q 不显示文件名头部信息。 -v 总是显示文件名头部信息。 tail # 用于显示文件的尾部内容，默认情况下显示文件的后10行\nhead [options] [file] -n 指定要显示的行数。例如，-n 20表示显示后20行。 -c 指定要显示的字节数。例如，-c 100表示显示最后100个字节。 -q 不显示文件名尾部信息。 -v 总是显示文件名尾部信息。 -f 该选项用于监视文件变化，在文件内容增长时自动显示新增的内容，常用于查看日志文件。 -F 类似于-f选项，但在文件被切换或重命名时，tail会尝试重新打开文件。 移动复制 # mv # 移动文件或目录\nmv [options] [old] [new] -b 为每个已存在的目标文件创建备份； -f 覆盖前不询问； -i 覆盖前询问； -n 不覆盖已存在文件，如果您指定了-i、-f、-n 中的多个，仅最后一个生效； -S 替换常用的备份文件后缀； -t 将所有参数指定的源文件或目录移动至指定目录； -T 将目标文件视作普通文件处理； -u 只在源文件文件比目标文件新或目标文件不存在时才进行移动； -v 详细显示进行的步骤； cp # 复制文件或目录\n# sources: 要复制的源文件或目录的路径。可以指定一个或多个源路径，多个源路径之间用空格分隔 cp [options] [sources...] [dist] -a 此选项通常在复制目录时使用，它保留链接、文件属性，并复制目录下的所有内容。其作用等于dpR参数组合。 -b 为每个已存在的目标文件创建备份。 -d 复制时保留链接。这里所说的链接相当于 Windows 系统中的快捷方式。 -f 覆盖已经存在的目标文件而不给出提示。 -p 除复制文件的内容外，还把修改时间和访问权限也复制到新文件中 -P 不跟随源文件中的符号链接 -i 交互式地询问是否覆盖现有文件。如果目标文件已经存在，cp将询问是否覆盖它。 -r 递归地复制目录及其内容。如果要复制目录，请使用此选项。 -u 仅复制源文件中更新的文件。如果源文件比目标文件新，或目标文件不存在，则复制源文件。 -v 显示详细输出，列出已复制的每个文件。 查找 # 按照文件属性查找（文件名、创建时间、大小等）：find 属性 文件名：find /范围 -name '文件全名'，find /范围 -name '*.后缀' 按照文件内容查找（区分大小写） grep '查找内容' 需要查找的文件 -i 不区分大小写 -v 排除符合条件的 -n打印行号 -r递归查找范围中的所有文件 管道命令 # 可以一次性运行多个Linux命令，第一个命令的结果，作为后一个命令的参数\ncat 1.txt | grep 'how'\n其他命令 # 文件权限 # 声明格式 # 第一位：-/d（-为文件，d为文件夹）第二位到第十位：权限描述，每三个一组 第一组：所属用户的权限 第二组：所属组的权限 第三组：其他用户的权限 权限分类 # 文件夹 r：可查看文件夹内容 w：可以在文件夹中创建、删除、修改文件和子文件夹 x：可以进入文件夹 文件 r：可以查看文件内容 w：可以编辑文件 x：可以执行该可执行文件 修改权限 # 方式一： 通过权限赋予对象的字母：所属用户：u，所属组：g，其他用户：o 增加权限：chmod u+w 文件名 更改权限：chmod u=rw 文件名 删除权限：chmod u-x 文件名 方式二： 通过权限数值：r：4；w：2；x：1 chmod 权限数值之和 文件名 用户操作 # 1、创建新用户 # useradd 用户名\n2、更改用户密码 # passwd 用户名\n3、删除用户 # userdel -r 用户名\n网络配置 # 1、查看ip地址 # ip addr\n2、修改网络配置 # vi /etc/sysconfig/network-scripts/ifcfg-ens33\n3、重启网络服务 # service network restart\n4、停止网络服务 # service network stop\n5、开启网络服务 # service network start\n6、查看网络服务状态 # service network status\n7、ping查看网络是否连通 # ping www.baidu.com\n8、防火墙 # #关闭防火墙 systemctl stop firewalld #开启防火墙 systemctl start firewalld #开机自启防火墙 systemctl enable firewalld #停止开机自启防火墙 systemctl disable firewalld # 查看白名单列表 firewall-cmd --zone=public --list-ports # 添加白名单端口 firewall-cmd --zone=public --add-port=80/tcp --permanent # 移除白名单端口 firewall-cmd --zone=public --remove-port=80/tcp --permanent # ****修改后需要重启防火墙**** firewall-cmd --reload # 查看防火墙状态，是否是running firewall-cmd --state # 列出支持的zone firewall-cmd --get-zones # 列出支持的服务，在列表中的服务是放行的 firewall-cmd --get-services # 永久生效，没有此参数重启后失效 --permanent 进程管理 # 1、查看进程 # 查看当前用户的进程：ps 查看系统的所有进程：-e 查看进程的详细信息：-f 2、结束进程 # 准备结束进程：kill PID 立即结束进程：-9 软件安装 # 1、安装方式 # rpm # 安装包格式为：mysql.rpm\n缺陷：一个软件通常存在多个依赖的rpm，安装过于繁琐\nyum # 只需要一个命令，就可以自动化安装\n缺陷：安装的软件版本，依赖于yum源\n命令： # 查看yum源中的所有软件：yum list\n安装软件：yum install 软件名 自动应答，例如安装java环境：yum -y install java-1.8.0* 编译 # 有的软件的tar.gz压缩包，解压后，需要C语言环境进行编译\n2、需要安装的软件 # jdk # yum list | grep java 查看yum源中带有java的安装包 yum -y install java-1.8.0* 安装jdk yum安装后，自动配置jdk环境变量 tomcat # 官网下载tomcat：https://tomcat.apache.org/download-90.cgi\n拷贝到linux中\n解压tomcat.tar.gz\n进入bin目录运行，startup.sh，命令：./startup.sh\n进行访问，如果无法访问，关闭防火墙或添加白名单\n成功访问tomcat管理页面后，配置tomcat管理员，在conf/tomcat-users.xml中配置以下代码： \u0026lt;role rolename=\u0026#34;manager-gui\u0026#34;/\u0026gt; \u0026lt;user username=\u0026#34;tomcat\u0026#34; password=\u0026#34;123\u0026#34; roles=\u0026#34;manager-gui\u0026#34;/\u0026gt; 如果访问登录页面中manager App后，登录页面没有弹出，需要在webapps/manager/Meta-inf/context.xml添加以下代码（如果配置文件已经有相同标签需要删除）： \u0026lt;Valve className=\u0026#34;org.apache.catalina.valves.RemoteAddrValve\u0026#34; allow=\u0026#34;127\\.\\d+\\.\\d+\\.\\d+|::1|0:0:0:0:0:0:0:1|\\d+\\.\\d+\\.\\d+\\.\\d\u0026#34; /\u0026gt; MySQL # MySQL5.7 # #centos7的yum源中没有mysql安装包,先在linux中安装wget的命令 yum install wget #再使用wget获取网络上的mysql安装包 wget https://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm #将mysql安装包放置到本地仓库中 rpm -ivh mysql57-community-release-el7-9.noarch.rpm rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 #进入仓库运行安装mysql的代码 cd /etc/yum.repos.d/ yum -y install mysql-server MySQL8 # # 安装yum库 yum -y install https://repo.mysql.com//mysql80-community-release-el7-7.noarch.rpm # 更新yum库 yum makecache # 检查yum库是否安装 yum repolist enabled | grep \u0026#34;mysql.*-community.*\u0026#34; # 安装mysql yum install -y mysql-community-server --nogpgcheck 配置 # #启动mysql服务 systemctl start mysqld.service #查看mysql是否启动 service mysqld status #查看临时密码 grep \u0026#39;temporary password\u0026#39; /var/log/mysqld.log #登录mysql mysql -uroot -p #设置密码策略为低，长度6位 set global validate_password_policy=LOW; set global validate_password_length=6; #设置密码 ALTER USER \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;123456\u0026#39;; #设置远程访问权限 update mysql.user set host=\u0026#39;%\u0026#39; where user=\u0026#39;root\u0026#39;; flush privileges; #给防火墙加白名单，放开3306端口或者关闭防火墙。 firewall-cmd --zone=public --add-port=3306/tcp --permanent python3.6 # yum install python36 -y //安装python3.6 yum install python36-pip -y //安装pip3 python3 --version //验证 配置软连接，python-\u0026gt;python3\ncd /usr/bin //进入这个目录，执行python命令的话，系统会在这里面找 ll | grep -n \u0026#39;python\u0026#39; //查询所有python相关的 rm -rf python //删除原来的python -\u0026gt; python2 ln -s ./python3.6 ./python //将python指向到python3.6 配置yum，因为yum使用的是python2，所以需要修改\nvi /usr/bin/yum vi /usr/libexec/urlgrabber-ext-down nginx # 配置nginx数据源\nvim /etc/yum.repos.d/nginx.repo 写入如下配置，将OSRELEASE替换为centos版本，6或7\n[nginx] name=nginx repo baseurl=http://nginx.org/packages/centos/OSRELEASE/$basearch/ gpgcheck=0 enabled=1 yum进行安装\nyum install nginx -y 修改niginx配置\nvim /etc/nginx/nginx.conf 查看nginx是否启动\nsystemctl status nginx.service redis # yum install epel-release -y yum update -y yum -y install redis systemctl start redis # 配置文件在/etc/redis.conf 运行jar # 使用nohup java -jar XXX.jar \u0026amp;\n可以保持项目运行在后台，窗口关闭不会导致项目停止，不挂断运行命令,当账户退出或终端关闭时,程序仍然运行\n可以通过ps -aux | grep \u0026quot;java -jar XXX.jar\u0026quot;查看运行的进程\n使用kill PID号//任务的PID号杀死进程\n","date":"2024-11-20","externalUrl":null,"permalink":"/posts/26654e7b/1a145064/a72e532f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e常用命令 \n    \u003cdiv id=\"常用命令\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b8%b8%e7%94%a8%e5%91%bd%e4%bb%a4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e目录操作 \n    \u003cdiv id=\"目录操作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%9b%ae%e5%bd%95%e6%93%8d%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003els \n    \u003cdiv id=\"ls\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#ls\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cp\u003e查看当前文件夹下的内容\u003c/p\u003e","title":"5、Linux常用命令","type":"posts"},{"content":" 数组基础 # 声明 # datatype[] arrayName; datatype 用于指定被存储在数组中的元素的类型。 [] 指定数组的秩（维度）。秩指定数组的大小。 arrayName 指定数组的名称。 初始化 # 声明一个数组不会在内存中初始化数组。当初始化数组变量时，可以赋值给数组。\n数组是一个引用类型，所以需要使用 new 关键字来创建数组的实例。\ndouble[] balance = new double[10]; 赋值 # 使用索引号赋值给一个单独的数组元素\ndouble[] balance = new double[10]; balance[0] = 4500.0; 在声明数组的同时给数组赋值\ndouble[] balance = { 2340.0, 4523.69, 3421.0}; 可以创建并初始化一个数组\nint [] marks = new int[5] { 99, 98, 92, 97, 95}; 上述情况下，也可以省略数组的大小\nint [] marks = new int[] { 99, 98, 92, 97, 95}; 数组长度 # int[] arr = {1,2,3}; Console.WriteLine(arr.Length); Array类 # Array 类是 C# 中所有数组的基类，它是在 System 命名空间中定义。Array 类提供了各种用于数组的属性和方法。\n属性 # 序号 属性 \u0026amp; 描述 1 IsFixedSize 获取一个值，该值指示数组是否带有固定大小。 2 IsReadOnly 获取一个值，该值指示数组是否只读。 3 Length 获取一个 32 位整数，该值表示所有维度的数组中的元素总数。 4 LongLength 获取一个 64 位整数，该值表示所有维度的数组中的元素总数。 5 Rank 获取数组的秩（维度）。 方法 # 序号 方法 \u0026amp; 描述 1 Clear 根据元素的类型，设置数组中某个范围的元素为零、为 false 或者为 null。 2 Copy(Array, Array, Int32) 从数组的第一个元素开始复制某个范围的元素到另一个数组的第一个元素位置。长度由一个 32 位整数指定。 3 CopyTo(Array, Int32) 从当前的一维数组中复制所有的元素到一个指定的一维数组的指定索引位置。索引由一个 32 位整数指定。 4 GetLength 获取一个 32 位整数，该值表示指定维度的数组中的元素总数。 5 GetLongLength 获取一个 64 位整数，该值表示指定维度的数组中的元素总数。 6 GetLowerBound 获取数组中指定维度的下界。 7 GetType 获取当前实例的类型。从对象（Object）继承。 8 GetUpperBound 获取数组中指定维度的上界。 9 GetValue(Int32) 获取一维数组中指定位置的值。索引由一个 32 位整数指定。 10 IndexOf(Array, Object) 搜索指定的对象，返回整个一维数组中第一次出现的索引。 11 Reverse(Array) 逆转整个一维数组中元素的顺序。 12 SetValue(Object, Int32) 给一维数组中指定位置的元素设置值。索引由一个 32 位整数指定。 13 Sort(Array) 使用数组的每个元素的 IComparable 实现来排序整个一维数组中的元素。 14 ToString 返回一个表示当前对象的字符串。从对象（Object）继承。 ","date":"2024-09-11","externalUrl":null,"permalink":"/posts/5ab2821f/fc047b40/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e数组基础 \n    \u003cdiv id=\"数组基础\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%95%b0%e7%bb%84%e5%9f%ba%e7%a1%80\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e声明 \n    \u003cdiv id=\"声明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%a3%b0%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-cs\" data-lang=\"cs\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edatatype\u003c/span\u003e\u003cspan class=\"p\"\u003e[]\u003c/span\u003e \u003cspan class=\"n\"\u003earrayName\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e\u003ccode\u003edatatype\u003c/code\u003e 用于指定被存储在数组中的元素的类型。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e[]\u003c/code\u003e 指定数组的秩（维度）。秩指定数组的大小。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003earrayName\u003c/code\u003e 指定数组的名称。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003e初始化 \n    \u003cdiv id=\"初始化\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%9d%e5%a7%8b%e5%8c%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e声明一个数组不会在内存中初始化数组。当初始化数组变量时，可以赋值给数组。\u003c/p\u003e","title":"5、数组","type":"posts"},{"content":"引擎通过模块向开发者暴露功能接口，模块以 ECMAScript 模块形式存在。\n功能 # 模块 'cc' 提供了所有引擎功能的访问。模块 'cc' 的内容是动态的，其内容和 项目设置 中的 功能裁剪 设置有关。\n引擎日志输出 # import { log } from \u0026#39;cc\u0026#39;; log(\u0026#39;Hello world!\u0026#39;); ","date":"2024-08-29","externalUrl":null,"permalink":"/posts/69064821/92082869/c2bb4c60/","section":"文章","summary":"\u003cp\u003e引擎通过模块向开发者暴露功能接口，模块以 \u003cstrong\u003eECMAScript\u003c/strong\u003e 模块形式存在。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e功能 \n    \u003cdiv id=\"功能\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8a%9f%e8%83%bd\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e模块 \u003ccode\u003e'cc'\u003c/code\u003e 提供了所有引擎功能的访问。模块 \u003ccode\u003e'cc'\u003c/code\u003e 的内容是动态的，其内容和 \u003cstrong\u003e项目设置\u003c/strong\u003e 中的 \u003cstrong\u003e功能裁剪\u003c/strong\u003e 设置有关。\u003c/p\u003e","title":"5、模块规范","type":"posts"},{"content":" XML # 可扩展标记语言（Extensive Markup Language），标签中的元素名是可以自己随意写，可拓展是相对于html来说\n标记语言：由一对尖括号括起来\u0026lt;内容\u0026gt;，就称为标记，标签；代码都是由标签组成，就称为标记语言\n作用 # 用来当做配置文件 xml的配置文件和properties的配置文件的选用： 如果配置的是单项数据，使用properties 如果配置的是多项数据，使用xml 语法 # 文件后缀 # .xml\n文档声明 # version 是版本的意思， encoding 是编码集\n\u0026lt;?xml version=\u0026#39;1.0\u0026#39; encoding=\u0026#39;utf-8\u0026#39;?\u0026gt; 注释 # \u0026lt;!-- 注释 --\u0026gt; 标签 # 1、xml文件中有且只有一个根标签\n2、标签中可以定义属性，在给属性赋值的时候，值要用引号括起来(单双都可)\n3、标签名区分大小写\n4、标签的闭合\n\u0026lt;aaa\u0026gt;\u0026lt;/aaa\u0026gt; 有头有尾 \u0026lt;bbb/\u0026gt; 自闭和 5、标签名的命名规则\n可以由数字，字母，一些符号来组成 开头不能是数字和标点符号 标签名中不能有空格 如：\u0026lt;aa a\u0026gt;\u0026lt;/aa a\u0026gt; 标签名不能是xml或者XML 如：\u0026lt;xml\u0026gt;\u0026lt;/xml\u0026gt; XML约束 # DTD约束 # 文档类型定义\n内部引入 # \u0026lt;!DOCTYPE books[ \u0026lt;!ELEMENT books (book+)\u0026gt; \u0026lt;!ELEMENT book (name,price)\u0026gt; \u0026lt;!ELEMENT name (#PCDATA)\u0026gt; \u0026lt;!ELEMENT price (#PCDATA)\u0026gt; ]\u0026gt; \u0026lt;books\u0026gt; \u0026lt;book\u0026gt; \u0026lt;name\u0026gt;三国\u0026lt;/name\u0026gt; \u0026lt;price\u0026gt;386\u0026lt;/price\u0026gt; \u0026lt;/book\u0026gt; \u0026lt;book\u0026gt; \u0026lt;name\u0026gt;水浒\u0026lt;/name\u0026gt; \u0026lt;price\u0026gt;400\u0026lt;/price\u0026gt; \u0026lt;/book\u0026gt; \u0026lt;/books\u0026gt; 外部引入(本地) # dtd文件：books.dtd\n\u0026lt;!ELEMENT books (book+)\u0026gt; \u0026lt;!ELEMENT book (name,price)\u0026gt; \u0026lt;!ELEMENT name (#PCDATA)\u0026gt; \u0026lt;!ELEMENT price (#PCDATA)\u0026gt; 引入\n\u0026lt;!DOCTYPE books SYSTEM \u0026#34;books.dtd\u0026#34;\u0026gt; 外部引入(网络) # \u0026lt;!DOCTYPE books PUBLIC \u0026#34;DTD名称\u0026#34; \u0026#34;DTD文档的URL\u0026#34;\u0026gt; XML解析 # DOM解析 # 就是指先将xml文件一次性的加载进内存中，在内存中形成一个树状结构（dom树）\n优点：我们可以通过dom方式的解析，对xml文件中的数据进行增删改查\n缺点：如果树太大了，非常占内存空间\nSAX解析 # Simple APIs for XML（简单应用程序接口）\n基于事件处理的，逐行扫描，逐行加载。\n优点：逐行扫描，读取一行，加载一行，加载完就扔了，不占用内存空间\n缺点：执行过程不可逆，不能对数据进行增删改操作，只能进行查询操作，不能回头了\nJDOM解析 # DOM4j解析 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.dom4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;dom4j\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; public static void main(String[] args) throws DocumentException { SAXReader saxReader = new SAXReader(); //解析XML文件、获取树对象 Document doc = saxReader.read(new File(\u0026#34;java20210519/src/study/books.xml\u0026#34;)); //如果是xml格式字符串，使用DocumentHelper.parseText()进行解析 //Document doc = DocumentHelper.parseText(xmlStr); //获取根标签对象 Element root = doc.getRootElement(); //获取根元素下的子元素对象集合 List\u0026lt;Element\u0026gt; list = root.elements(); for (Element element : list){ //获取该元素下的子元素name Element name = element.element(\u0026#34;name\u0026#34;); //获取该元素下的子元素price Element price = element.element(\u0026#34;price\u0026#34;); //获取name、price元素的文本信息 System.out.println(\u0026#34;名称：\u0026#34; + name.getText() + \u0026#34;，价格：\u0026#34; + price.getText()); } } 常用API # Element对象 // 获取所有的子标签 List\u0026lt;Element\u0026gt; elements(); // 获取元素的名字 String getName(); // 获取标签内的文本内容 String getText(); // 根据标签名获取指定第一个标签对象 Element element(String name); // 根据属性名获取属性值 String attributeValue(String name); // 返回xml字符串 String asXML(); ","date":"2024-08-27","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/2a92d21e/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eXML \n    \u003cdiv id=\"xml\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#xml\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e可扩展标记语言（\u003cstrong\u003eExtensive Markup Language\u003c/strong\u003e），标签中的元素名是可以自己随意写，可拓展是相对于html来说\u003c/p\u003e","title":"5、XML","type":"posts"},{"content":"Spring封装了一个异步方法的实现，使得方法可以在单独的线程中异步执行，提高系统的并发能力和响应性能。\n相关注解 # 注解 说明 @Async 将方法标记为异步执行的方法，在方法上添加@Async注解后，方法的调用将会在一个单独的线程中异步执行。 @EnableAsync 在Spring配置类上使用该注解来启用异步处理功能 注意事项 # 异步方法不可以使用static修饰 异步类必须使用@Component注解（或其他注解注入Spring容器），否则导致Spring无法扫描到异步类 异步方法不能与调用异步方法在同一个类中 类中需要使用@Autowired或@Resource等注解自动注入，不能自己手动new对象 必须使用@EnableAsync注解开启异步 注意：如果报错Null return value from advice does not match primitive return type for，需要将异步方法的返回值改为包装类，因为框架内部做了AOP处理，不管是cglib代理还是jdk代理，你的返回值都必须是包装类。\n代码示例 # 1、在启动类添加注解@EnableAsync用于异步执行注解，并且使用spring默认线程池；也可以使用配置类开启，并且自定义线程池\n@Configuration @EnableAsync public class AsyncThreadPoolConfig { /** * 默认情况下，在创建了线程池后，线程池中的线程数为0，当有任务来之后，就会创建一个线程去执行任务， * 当线程池中的线程数目达到corePoolSize后，就会把到达的任务放到缓存队列当中； * 当队列满了，就继续创建线程，当线程数量大于等于maxPoolSize后，开始使用拒绝策略拒绝 */ /** 核心线程数（默认线程数） */ private static final int corePoolSize = 20; /** 最大线程数 */ private static final int maxPoolSize = 100; /** 允许线程空闲时间（单位：默认为秒） */ private static final int keepAliveTime = 10; /** 缓冲队列大小 */ private static final int queueCapacity = 200; /** 线程池名前缀 */ private static final String threadNamePrefix = \u0026#34;Async-Service-\u0026#34;; @Bean(\u0026#34;taskExecutor\u0026#34;) // 默认Spring会找这个名字的线程池 public ThreadPoolTaskExecutor taskExecutor(){ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(corePoolSize); executor.setMaxPoolSize(maxPoolSize); executor.setQueueCapacity(queueCapacity); executor.setKeepAliveSeconds(keepAliveTime); executor.setThreadNamePrefix(threadNamePrefix); // 线程池对拒绝任务的处理策略 // CallerRunsPolicy：由调用线程（提交任务的线程）处理该任务 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 初始化 executor.initialize(); return executor; } } 2、在需要异步执行的方法上添加注解@Async()，value为使用的线程池，不写则为默认线程池byName = taskExcutor\n@Service public class TranTest2Service { Logger log = LoggerFactory.getLogger(TranTest2Service.class); // 使用自定义线程池 @Async(\u0026#34;taskExecutor\u0026#34;) public void sendMessage1() throws InterruptedException { // business } @Async(\u0026#34;taskExecutor\u0026#34;) public void sendMessage2() throws InterruptedException { // business } } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/c70ae83e/6df7419c/","section":"文章","summary":"\u003cp\u003eSpring封装了一个异步方法的实现，使得方法可以在单独的线程中异步执行，提高系统的并发能力和响应性能。\u003c/p\u003e","title":"5、Async","type":"posts"},{"content":" DOM # 文档对象模型（Document Object Model）\n在网页被加载时，浏览器会创建页面文档对象模型，形成DOM树\nDOM树 # Document：树对象，文档对象 Element：元素对象，标签对象 Attribute：属性对象 Text：文本对象 Comment：注释对象 Node：节点对象 获取标签对象的相关方法 # 方法 作用 备注 getElementById() 根据标签的id属性值来获取标签对象 一般id属性是用来获取标签对象 getElementsByName() 根据标签的name属性值来获取标签对象们 一般name属性是用来给后台获取数据用的 getElementsByTagName() 根据标签名来获取标签对象们 getElementsByClassName() 根据标签的class属性值来获取标签对象们 一般class属性是用来进行样式设置的，使用类选择器 如果直接在script标签中执行获取标签对象的方法，页面没有加载，获取的对象为null\n//页面加载完成，再执行函数 window.onload = function(){} 创建元素的相关方法 # 方法 方法 createElement() 创建元素节点 createAttribute(name) 创建拥有指定名称的属性节点，并返回新的 Attribute 对象 createTextNode() 创建文本节点 createComment() 创建注释节点 注意：创建出来的对象，都是游离状态,需要放置在父元素对象上\n父元素对象.appendChild(创建的子元素对象); //删除子元素对象 父元素对象.removeChild(需要删除的子元素对象); 对元素对象内容进行操作 # 操作的是元素对象，不可以操作元素对象数组，如果需要批量操作，只能写循环进行\n获取修改标签内容 # 标签对象.innerHTML = \u0026#34;字符串\u0026#34;; 标签对象.innerText = \u0026#34;字符串\u0026#34;; //区别 innerHTML，获取到的是HTML语句，用于处理标签，会解析字符串中的标签内容 innerText，获取到的是内容，用于处理文本，不会解析字符串中的标签内容 获取修改标签属性 # 标签对象.属性名 = \u0026#34;属性值\u0026#34;; 获取修改标签样式 # //在标签上添加样式(样式多的话，不推荐) 标签对象.style.样式名 = \u0026#34;值\u0026#34;; //给元素对象添加类属性,在style标签中添加类样式 标签对象.className = \u0026#34;类名\u0026#34; //只可以加一个类名 标签对象.classList.add(\u0026#34;类名1\u0026#34;,\u0026#34;类名2\u0026#34;,...); //可以添加多类名 //删除多类名元素对象的一个类名 标签对象.classList.remove(\u0026#34;类名\u0026#34;); 事件 # 事件的绑定方式 # 1、在标签元素上添加onXXX属性的方式绑定（xxx代表事件名称）onXXX的值为js代码，缺点是耦合度高\n2、获取标签元素的对象，通过标签.onXXX = function(){}，绑定事件\n3、通过dom元素对象的addEventListener(\u0026quot;事件名称\u0026quot;,function(){}，传播特性)方法绑定事件（事件名称不需要on前缀），推荐\nJS中的事件 # 点击事件： onclick：鼠标点击某个对象 ondblclick：鼠标双击某个对象 焦点事件： onblur：元素失去焦点 onfocus：元素获得焦点 blur()：一调用，就会失去焦点 focus()：一调用，就会获取焦点 键盘相关事件： onkeydown：某个键盘的键被按下 onkeypress：某个键盘的键被按下或按住，某个键盘按键被按下并松开。 onkeyup：某个键盘的键被松开 加载事件： onload：某个页面或图像被完成加载 onunload：用户离开某个页面 鼠标相关事件： onmousedown：某个鼠标按键被按下 onmousemove：鼠标被移动 onmouseout：鼠标从某元素移开 onmouseover：鼠标被移到某元素之上 onmouseup：某个鼠标按键被松开 表单相关事件： onchange：用户改变域的内容 支持该事件的 HTML 标签：\u0026lt;input type=\u0026quot;text\u0026quot;\u0026gt;, \u0026lt;select\u0026gt;, \u0026lt;textarea\u0026gt; onselect：文本被选定 支持该事件的 HTML 标签：\u0026lt;input type=\u0026quot;text\u0026quot;\u0026gt;, \u0026lt;textarea\u0026gt; onsubmit 提交按钮被点击 事件的传播特性 # addEventListener(\u0026ldquo;事件名称\u0026rdquo;,function(){}，传播特性)：默认的传播方式是false，冒泡\n事件的冒泡 # 如果点击最内层的红色元素的话，点击事件也会传播到外层的蓝色元素，称为事件的冒泡\n事件的捕获 # addEventListener可以改变事件的传播方式，第三个参数为true\n如果点击最外层的蓝色元素的话，点击事件会传播到内层的红色元素，称为事件的捕获\n事件的委托 # 将一个元素A中的子元素的点击事件交给元素A处理，称为事件的委托\n例如，接收表格中的按钮的点击事件\n//变量e用于接收浏览器传入的点击目标对象信息 table.onclick = function(e){ //获取到事件的触发元素对象 var t = e.target; //获取触发元素对象的名称 var butName = t.innerText; //筛选按钮类型 switch(butName){ case \u0026#34;购买\u0026#34;: //获取购买按钮的父元素对象 var td = but.parentElement; //获取td对象的父元素对象 var tr = td.parentElement; //获取tr的td子元素 var tds = tr.children; alert(\u0026#34;购买：\u0026#34; + tds[0].innerText); break; case \u0026#34;加入购物车\u0026#34;: break; case \u0026#34;查看详情\u0026#34;: break; } } 正则表达式 # 格式 # //正则表达式格式 /正则表达式主体/修饰符(可选) //正则表达式验证 var regExp = /正则表达式/; regExp.test(\u0026#34;校验的字符串\u0026#34;); 规则 # 方括号用于查找某个范围内的字符：\n表达式 描述 [abc] 查找方括号之间的任何字符。 [0-9] 查找任何从 0 至 9 的数字。 (x|y) 查找任何以 | 分隔的选项。 n{x} n恰好出现x次 n{x,} n最少出现x次 n{x,y} n最少出现x次，最多y次 元字符是拥有特殊含义的字符：\n元字符 描述 \\d 查找数字。 \\s 查找空白字符。 \\w 匹配数字、字母、下划线 \\W 匹配任意不是数字、字母、下划线 . 匹配除换行符以外的任意单个字符 ^ 行的开头 $ 行的结尾 \\uxxxx 查找以十六进制数 xxxx 规定的 Unicode 字符。 量词:\n量词 描述 n+ 匹配任何包含至少一个 n 的字符串。 n* 匹配任何包含零个或多个 n 的字符串。 n? 匹配任何包含零个或一个 n 的字符串。 修饰符可以在全局搜索中不区分大小写:\n修饰符 描述 i 执行对大小写不敏感的匹配。 g 执行全局匹配（查找所有匹配而非在找到第一个匹配后停止）。 m 执行多行匹配。 使用String类型的match方法，可以匹配正则表达式\nvar a = \u0026#34;abcABC\u0026#34;; var r = a.match(/b/gi); 使用String类型的replace方法，可以替换字符串中的字符\nvar a = \u0026#34;sb,fuck\u0026#34;; var r = a.replace(/(sb)|(fuck)/gi,\u0026#34;*\u0026#34;); 使用String类型的search方法，可以查询匹配的类型首次开始位置索引\nvar a = \u0026#34;abc,efg,123,hig\u0026#34;; var r = a.search(/\\d/g); 使用String类型的split方法，可以按照特定规则分割字符串\nvar a = \u0026#34;abc12def34\u0026#34;; var r = a.split(/\\d+/); 汉字的Unicode编码：[\\u4e00-\\u9fa5]\n在表单标签相关属性：\npattern：相关文本规则，正则表达式不需要//\nrequired：值为required，值不可以为空\nBOM # 浏览器对象模型（Browser Object Model）尚无正式标准\nWindow对象 # 所有浏览器都支持 window 对象。它表示浏览器窗口。\n所有 JavaScript 全局对象、函数以及变量均自动成为 window 对象的成员。\n全局变量是 window 对象的属性。\n全局函数是 window 对象的方法。\nWindow 常用属性、方法 # window.innerHeight - 浏览器窗口的内部高度(包括滚动条)\nwindow.innerWidth - 浏览器窗口的内部宽度(包括滚动条)\nwindow.open(\u0026quot;新窗口地址\u0026quot;,\u0026quot;新窗口名称\u0026quot;,\u0026quot;新窗口其余属性设置\u0026quot;) - 打开新窗口\nwindow.close() - 关闭当前窗口\nwindow.moveTo() - 移动当前窗口\nwindow.resizeTo() - 调整当前窗口的尺寸\nScreen对象 # window.screen 对象包含有关用户屏幕的信息。\nScreen常用属性、方法 # screen.availWidth - 可用的屏幕宽度\nscreen.availHeight - 可用的屏幕高度\nscreen.colorDepth - 色彩深度\nscreen.pixelDepth - 色彩分辨率\nLocation对象 # window.location 对象用于获得当前页面的地址 (URL)，并把浏览器本窗口重定向到新的页面。\nLocation常用属性、方法 # location.hostname - 返回 web 主机的域名\nlocation.pathname - 返回当前页面的路径和文件名\nlocation.port - 返回 web 主机的端口 （80 或 443）\nlocation.protocol - 返回所使用的 web 协议（http: 或 https:）\nlocation.href 返回当前页面的 URL\nlocation.href = 'URL' - 可以重定向到新的页面\nlocation.assign() 方法加载新的文档\nhistory对象 # window.history 对象包含浏览器的历史。\nhistory常用属性、方法 # history.go()：加载 history 列表中的某个具体页面,go方法中可以传递参数，如果是正数，就是下某页，如果是负数，就是上某页\nhistory.length：返回浏览器历史列表中的 URL 数量\nhistory.back() - 与在浏览器点击后退按钮相同\nhistory.forward() - 与在浏览器中点击向前按钮相同\n计时事件 # setInterval()：按照指定的周期（以毫秒计）来调用函数或计算表达式\n第一个参数：时间到了要执行的方法 第二个参数：周期（毫秒） 如果不关闭，它会一直执行下去 clearInterval()：取消由setInterval()设置的timeout\nsetTimeout()：在指定的毫秒数后调用函数或计算表达式\n第一个参数：时间到了要执行的方法 第二个参数：过多长时间执行该方法（毫秒） 执行完一次之后，就不再执行 clearTimeout()：取消由 setTimeout()方法设置的timeout\n弹窗 # 可以在 JavaScript 中创建三种消息框：警告框、确认框、提示框。\n警告框 # alert(\u0026quot;警告信息\u0026quot;);\n确认框 # confirm(\u0026quot;提示信息\u0026quot;);\n返回值为boolean类型，选择确定返回true，选择取消返回false\n提示框 # 当提示框出现后，用户需要输入某个值，然后点击确认或取消按钮才能继续操纵。\n如果用户点击确认，那么返回值为输入的值。如果用户点击取消，那么返回值为 null。\nwindow.prompt(\u0026quot;提示信息\u0026quot;,\u0026quot;默认值\u0026quot;);\nCookie对象 # Cookie 用于存储 web 页面的用户信息，最大容量一般为4KB。\nCookie 是一些数据, 存储于你电脑上的文本文件中。\n当 web 服务器向浏览器发送 web 页面时，在连接关闭后，服务端不会记录用户的信息。\n创建Cookie # JavaScript 可以使用 document.cookie 属性来创建 、读取、及删除 cookie。\ndocument.cookie=\u0026#34;key=value\u0026#34;; 添加过期时间 # 以 UTC 或 GMT 时间，默认情况下，cookie 在浏览器关闭时删除\ndocument.cookie=\u0026#34;key=value; expires=Thu, 18 Dec 2043 12:00:00 GMT\u0026#34;; 可以使用 path 参数告诉浏览器 cookie 的路径。默认情况下，cookie 属于当前页面。\ndocument.cookie=\u0026#34;key=value; expires=Thu, 18 Dec 2043 12:00:00 GMT; path=/\u0026#34;; 读取 Cookie # document.cookie 将以字符串的方式返回所有的 cookie，类型格式： key1=value;key2 =value; key3=value;\nvar x = document.cookie; 删除 Cookie # 只需要设置 expires 参数为以前的时间即可\ndocument.cookie = \u0026#34;key=; expires=Thu, 01 Jan 1970 00:00:00 GMT\u0026#34;; 删除时不必指定 cookie 的值\nWebStorage # 存储内容大小一般为5MB（不同浏览器可能会变化）\n通过Window.sessionStorage或Window.localStorage属性可以实现本地存储机制，二者的api一样\n// 添加一个item，如果key存在则会替换value sessionStorage.setItem(\u0026#34;key\u0026#34;,\u0026#34;value\u0026#34;); // 根据key获取一个value，如果不存在返回null sessionStorage.getItem(\u0026#34;key\u0026#34;); // 根据key删除 sessionStorage.removeItem(\u0026#34;key\u0026#34;); // 清空 sessionStorage.clear(); sessionStorage会随着窗口关闭而消失；localStorage不会随着浏览器关闭消失\n","date":"2024-01-19","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/0d08345a/1403736b/744a9b5e/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eDOM \n    \u003cdiv id=\"dom\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#dom\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e文档对象模型（Document Object Model）\u003c/p\u003e","title":"5、WebAPI","type":"posts"},{"content":" 定义函数 # function sayHello(){ return \u0026#39;Hello\u0026#39;; } var sayHello = function(){ return \u0026#39;Hello\u0026#39;; }; 函数体内部的语句在执行时，一旦执行到return时，函数就执行完毕，并将结果返回。因此，函数内部通过条件判断和循环可以实现非常复杂的逻辑。\n如果没有return语句，函数执行完毕后也会返回结果，只是结果为undefined。\n两种定义方式的区别：\n方式一，是定义一个函数sayHello 方式二，是定义一个匿名函数，然后赋值给变量sayHello，所以再赋值结束后，要加; 函数的传参 # function sayHello(name){ console.log(\u0026#39;Hello \u0026#39; + name); } //正常传参 sayHello(\u0026#39;lucy\u0026#39;); //多传参数，不影响，函数只会用第一个 sayHello(\u0026#39;tom\u0026#39;,\u0026#39;james\u0026#39;); //少传参数，函数会使用undefined sayHello(); // Hello lucy // Hello tom // Hello undefined arguments # JavaScript还有一个免费赠送的关键字arguments，它只在函数内部起作用，并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array\nfunction sayHello(name){ for(let i = 0;i \u0026lt; arguments.length;i++){ console.log(arguments[i]) } } sayHello(\u0026#39;lucy\u0026#39;,\u0026#39;tom\u0026#39;) // lucy // tom rest # 由于JavaScript函数允许接收任意个参数，于是我们就不得不用arguments来获取所有参数\n为了获取除了已定义参数a、b之外的参数，我们不得不用arguments，并且循环要从索引2开始以便排除前两个参数，这种写法很别扭，只是为了获得额外的rest参数，有没有更好的方法？ES6标准引入了rest参数\nfunction test(a,b,...rest){ console.log(rest); } test(1,2,3,4,5,6) // [ 3, 4, 5, 6 ] 名字空间 # 全局变量会绑定到window，nodejs中是global上，不同的JavaScript文件如果使用了相同的全局变量，或者定义了相同名字的顶层函数，都会造成命名冲突，并且很难被发现。\n减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中\n许多著名的JavaScript库都是这么干的：jQuery，YUI，underscore等等\nvar $ = {} msg = \u0026#39;Hello\u0026#39; function say(){ console.log(\u0026#39;Hello\u0026#39;) } $.msg = msg $.say = say 解构赋值 # 从ES6开始，JavaScript引入了解构赋值，可以同时对一组变量进行赋值，就是把一个数组的元素分别赋值给几个变量\nvar [a,b,c] = [10,20,30] console.log(a) console.log(b) console.log(c) // 10 // 20 // 30 对象方法 # 在一个对象中绑定函数，称为这个对象的方法\nvar lucy = { name:\u0026#39;lucy\u0026#39;, sayHello: function(){ console.log(\u0026#39;Hello,I\\\u0026#39;m \u0026#39; + this.name) } } lucy.sayHello() // Hello,I\u0026#39;m lucy 在一个方法内部，this是一个特殊变量，它始终指向当前对象\n但是如果在方法外调用，this的指向就不确定了，在web中this指向window，在nodejs中this指向global\napply # 我们可以使用apply来指定函数中this的指向\nvar lucy = { name:\u0026#39;lucy\u0026#39;, } function sayHello(){ console.log(\u0026#39;Hello,I\\\u0026#39;m \u0026#39; + this.name) } sayHello() //使用apply sayHello.apply(lucy,[]) // Hello,I\u0026#39;m undefined // Hello,I\u0026#39;m lucy call # call和apply的功能一样，只不过在传参的方式不一样，apply是把参数装在数组里面，call是直接传\nvar lucy = { name:\u0026#39;lucy\u0026#39;, } function sayHello(a,b){ console.log(\u0026#39;Hello,I\\\u0026#39;m \u0026#39; + this.name) } sayHello(1,2) //使用apply sayHello.call(lucy,1,2) // Hello,I\u0026#39;m undefined // Hello,I\u0026#39;m lucy 闭包 # 函数可以作为返回值进行返回，而返回的这个函数可以在后续执行，这种行为成为闭包，和python中的闭包类似\n注意：闭包的函数里面不要引入任何后续可能发生变化的值，例如循环的值\nfunction func1(){ return function func2(){ return \u0026#39;你好\u0026#39; } } f = func1() console.log(f()) 箭头函数 # x =\u0026gt; x * x //相当于 function(x){ return x * x; } //如果参数不是一个就需要使用括号把参数括住，如果函数体不是一行，就需要使用大括号 (x,y) =\u0026gt; { x += 1; y += 1; return } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/0d08345a/1403736b/50243d25/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e定义函数 \n    \u003cdiv id=\"定义函数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%9a%e4%b9%89%e5%87%bd%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e \u003cspan class=\"nx\"\u003esayHello\u003c/span\u003e\u003cspan class=\"p\"\u003e(){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;Hello\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003esayHello\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kd\"\u003efunction\u003c/span\u003e\u003cspan class=\"p\"\u003e(){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;Hello\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e};\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e函数体内部的语句在执行时，一旦执行到\u003ccode\u003ereturn\u003c/code\u003e时，函数就执行完毕，并将结果返回。因此，函数内部通过条件判断和循环可以实现非常复杂的逻辑。\u003c/p\u003e","title":"5、函数","type":"posts"},{"content":" 模板 # 建立通用模具，提高通用性\nC++另一种编程思想称为泛型编程 ，主要利用的技术就是模板 C++提供两种模板机制：函数模板和类模板 函数模板 # 建立一个通用函数，其函数返回值类型和形参类型可以不具体制定，用一个虚拟的类型来代表。\n函数模板语法 # //函数声明或定义 template\u0026lt;typename T\u0026gt; template --- 声明创建模板 typename --- 表面其后面的符号是一种数据类型，可以用class代替 T --- 通用的数据类型，名称可以替换，通常为大写字母 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; //两个整形交换 void changeInt(int\u0026amp; a, int\u0026amp; b) { int temp = a; a = b; b = temp; } //两个浮点型交换 void changeDouble(double\u0026amp; a, double\u0026amp; b) { double temp = a; a = b; b = temp;\t} //声明一个模板，告诉编译器，后面代码如果使用T，不要报错，T是一个泛型 template \u0026lt;typename T\u0026gt; //使用模板，可以提高交换函数的通用性 void change(T\u0026amp; a, T\u0026amp; b) { T temp = a; a = b; b = temp; } int main() { int a = 10; int b = 20; //此处为自动类型推断，编译器自动推断类型 change(a, b); cout \u0026lt;\u0026lt; \u0026#34;a = \u0026#34; \u0026lt;\u0026lt; a \u0026lt;\u0026lt; \u0026#34;，b = \u0026#34; \u0026lt;\u0026lt; b \u0026lt;\u0026lt; endl; //a = 20，b = 10 double c = 5.2; double d = 20.5; //此处指定类型，告诉编译器此处的T是double类型 change\u0026lt;double\u0026gt;(c, d); cout \u0026lt;\u0026lt; \u0026#34;c = \u0026#34; \u0026lt;\u0026lt; c \u0026lt;\u0026lt; \u0026#34;，d = \u0026#34; \u0026lt;\u0026lt; d \u0026lt;\u0026lt; endl; //c = 20.5，d = 5.2 return 0; } 模板使用案例 # #include\u0026lt;iostream\u0026gt; using namespace std; //使用选择排序，对多种类型的数组进行选择排序，从小到大 //声明模板 template\u0026lt;class T\u0026gt; //选择排序 void chooseSort(T \u0026amp; arr,int len) { for (int i = 0; i \u0026lt; len; i++) { int maxIndex = i; for (int j = i + 1; j \u0026lt; len; j++) { if (arr[maxIndex] \u0026lt; arr[j]) { maxIndex = j; } } if (maxIndex != i) { int temp = arr[i]; arr[i] = arr[maxIndex]; arr[maxIndex] = temp; } } } //声明模板 template\u0026lt;class T\u0026gt; //遍历数组 void showArr(T \u0026amp; arr, int len) { for (int i = 0; i \u0026lt; len; i++) { cout \u0026lt;\u0026lt; \u0026#34;第\u0026#34; \u0026lt;\u0026lt; i \u0026lt;\u0026lt; \u0026#34;个元素：\u0026#34; \u0026lt;\u0026lt; arr[i] \u0026lt;\u0026lt; endl; } } int main() { //int数组 int intArr[] = { 9,66,50,2,35,46,12,57 }; //int数组长度 int intArrLen = sizeof(intArr) / sizeof(intArr[0]); //进行排序 chooseSort(intArr, intArrLen); //遍历数组 showArr(intArr, intArrLen); return 0; } 普通函数和函数模板的区别 # 普通函数调用时可以发生自动类型转换（隐式类型转换） 函数模板调用时，如果利用自动类型推导，不会发生隐式类型转换 如果利用显示指定类型的方式，可以发生隐式类型转换 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; //普通函数 int add(int a, int b) { return a + b; } //自动类型推导的函数模板 template\u0026lt;class T\u0026gt; T add1(T a, T b) { return a + b; } int main() { int a = 10; char b = \u0026#39;a\u0026#39;; //普通函数，可以发生隐式类型转换，在可以满足自动类型提升的情况下 //例如char-\u0026gt;int cout \u0026lt;\u0026lt; add(a, b) \u0026lt;\u0026lt; endl; //107 //自动类型推导的函数模板，不可以发生隐式类型转换，必须两个参数为同一类型 //cout \u0026lt;\u0026lt; add1(a, b) \u0026lt;\u0026lt; endl; //报错 //显式指定类型的函数模板，可以发生隐式类型转换 cout \u0026lt;\u0026lt; add1\u0026lt;int\u0026gt;(a, b) \u0026lt;\u0026lt; endl;//107 return 0; } 普通函数与函数模板的调用规则 # 如果函数模板和普通函数都可以实现，优先调用普通函数 可以通过空模板参数列表来强制调用函数模板 函数模板也可以发生重载 如果函数模板可以产生更好的匹配,优先调用函数模板 实际开发中，既然提供了函数模板，最好就不要提供普通函数，否则容易出现二义性\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; //普通函数 void func(int a,int b) { cout \u0026lt;\u0026lt; \u0026#34;普通函数\u0026#34; \u0026lt;\u0026lt; endl; } //声明定义模板函数 template\u0026lt;class T\u0026gt; void func(T a, T b) { cout \u0026lt;\u0026lt; \u0026#34;模板函数\u0026#34; \u0026lt;\u0026lt; endl; } //3、函数模板也可以发生重载 template\u0026lt;class T\u0026gt; void func(T a) { cout \u0026lt;\u0026lt; \u0026#34;重载的模板函数\u0026#34; \u0026lt;\u0026lt; endl; } int main() { int a = 10; int b = 20; //1、如果函数模板和普通函数都可以实现，优先调用普通函数 func(a, b); //2、可以通过空模板参数列表来强制调用函数模板 func\u0026lt;\u0026gt;(a, b); char c = \u0026#39;a\u0026#39;; char d = \u0026#39;b\u0026#39;; //3、如果函数模板可以产生更好的匹配,优先调用函数模板 func(c, d); return 0; } 模板的局限性 # 例如：\ntemplate\u0026lt;class T\u0026gt; void f(T a, T b){ a = b; } 在上述代码中提供的赋值操作，如果传入的a和b是一个数组，就无法实现了\n再例如：\ntemplate\u0026lt;class T\u0026gt; void f(T a, T b){ if(a \u0026gt; b) { ... } } 在上述代码中，如果T的数据类型传入的是像Person这样的自定义数据类型，也无法正常运行\n因此C++为了解决这种问题，提供模板的重载，可以为这些特定的类型提供具体化的模板\n例子：\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; class Person { public: string name; int age; Person(string name, int age) { this-\u0026gt;name = name; this-\u0026gt;age = age; } }; template\u0026lt;typename T\u0026gt; bool equal(T\u0026amp; a, T\u0026amp; b) { if (a == b) { return true; } else { return false; } } //解决方法：2、利用具体化的Person来实现 template\u0026lt;\u0026gt; bool equal(Person\u0026amp; a, Person\u0026amp; b) { if (a.name == b.name \u0026amp;\u0026amp; a.age == b.age) { return true; } else { return false; } } int main() { int a = 10; int b = 20; //对于内置数据类型，可以进行比较 cout \u0026lt;\u0026lt; equal(a, b) \u0026lt;\u0026lt; endl; //0 Person p1(\u0026#34;lucy\u0026#34;, 18); Person p2(\u0026#34;tom\u0026#34;, 19); //此时，运行时会报错，解决方法：1、运算符重载，但是太繁琐 cout \u0026lt;\u0026lt; equal(p1, p2) \u0026lt;\u0026lt; endl; return 0; } 类模板 # 建立一个通用类，类中的成员数据类型可以不具体制定，用一个虚拟的类型来代表。\n类模板语法 # 类模板和函数模板语法相似，在声明模板template后面加类，此类称为类模板\ntemplate\u0026lt;class T\u0026gt; class MyClass{ } 例子：\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; //声明一个姓名的类型，和一个年龄的类型 template\u0026lt;class NameType,class AgeType\u0026gt; class Person { public: Person(NameType name,AgeType age) { this-\u0026gt;name = name; this-\u0026gt;age = age; } //声明Person属性 NameType name; AgeType age; }; int main() { //使用类模板 Person\u0026lt;string, int\u0026gt; p1(\u0026#34;lucy\u0026#34;, 18); cout \u0026lt;\u0026lt; \u0026#34;姓名：\u0026#34; \u0026lt;\u0026lt; p1.name \u0026lt;\u0026lt; \u0026#34;，年龄：\u0026#34; \u0026lt;\u0026lt; p1.age \u0026lt;\u0026lt; endl; return 0; } 类模板与函数模板区别 # 类模板与函数模板区别主要有两点：\n类模板没有自动类型推导的使用方式 类模板在模板参数列表中可以有默认参数 例子：\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; //类模板的参数列表，可以有默认参数 template\u0026lt;class NameType = string,class AgeType = int\u0026gt; class Person { public: Person(NameType name,AgeType age) { this-\u0026gt;name = name; this-\u0026gt;age = age; } NameType name; AgeType age; }; int main() { //使用类模板，如果有默认的参数，可以不指定类型 Person\u0026lt;\u0026gt; p1(\u0026#34;lucy\u0026#34;, 18); cout \u0026lt;\u0026lt; \u0026#34;姓名：\u0026#34; \u0026lt;\u0026lt; p1.name \u0026lt;\u0026lt; \u0026#34;，年龄：\u0026#34; \u0026lt;\u0026lt; p1.age \u0026lt;\u0026lt; endl; return 0; } 类模板中成员函数创建时机 # 类模板中成员函数和普通类中成员函数创建时机是有区别的：\n普通类中的成员函数一开始就可以创建 类模板中的成员函数在调用时才创建 类模板对象做函数参数 # 一共有三种传入方式：\n指定传入的类型 \u0026mdash; 直接显示对象的数据类型 参数模板化 \u0026mdash; 将对象中的参数变为模板进行传递 整个类模板化 \u0026mdash; 将这个对象类型 模板化进行传递 例子：\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; template\u0026lt;class NameType,class AgeType\u0026gt; class Person { public: Person(NameType name,AgeType age) { this-\u0026gt;name = name; this-\u0026gt;age = age; } NameType name; AgeType age; void showPerson() { cout \u0026lt;\u0026lt; \u0026#34;姓名：\u0026#34; \u0026lt;\u0026lt; this-\u0026gt;name \u0026lt;\u0026lt; \u0026#34;，年龄：\u0026#34; \u0026lt;\u0026lt; this-\u0026gt;age \u0026lt;\u0026lt; endl; } }; //1、指定传入类型 void func1(Person\u0026lt;string,int\u0026gt;\u0026amp; p) { p.showPerson(); } //2、参数模板化 template\u0026lt;class T1,class T2\u0026gt; void func2(Person\u0026lt;T1,T2\u0026gt;\u0026amp; p) { p.showPerson(); //查看传入的T1和T2类型 cout \u0026lt;\u0026lt; \u0026#34;T1：\u0026#34; \u0026lt;\u0026lt; typeid(T1).name() \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;T2：\u0026#34; \u0026lt;\u0026lt; typeid(T2).name() \u0026lt;\u0026lt; endl; } //3、将整个类模板化 template\u0026lt;class T\u0026gt; void func3(T\u0026amp; p) { p.showPerson(); } int main() { Person\u0026lt;string, int\u0026gt; p(\u0026#34;lucy\u0026#34;, 18); func1(p); //指定传入类型 func2\u0026lt;string, int\u0026gt;(p); //自动类型推导 func2(p); //自动类型推导 func3(p); //指定传入类型 func3\u0026lt;Person\u0026lt;string, int\u0026gt;\u0026gt;(p); return 0; } 类模板与继承 # 当类模板碰到继承时，需要注意一下几点： 当子类继承的父类是一个类模板时，子类在声明的时候，要指定出父类中T的类型 如果不指定，编译器无法给子类分配内存 如果想灵活指定出父类中T的类型，子类也需变为类模板 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; template\u0026lt;class T\u0026gt; class Base { T m; }; //class son :public Base {}; //错误，必须要指定父类中T的数据类型 //1、指定父类中T的数据类型 class Son1 :public Base\u0026lt;int\u0026gt; { }; //2、灵活的指定父类中T的类型，子类也需要是一个类模板 template\u0026lt;class T\u0026gt; class son2 :public Base\u0026lt;T\u0026gt; { }; 类模板成员函数类外实现 # #include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; template\u0026lt;class T1,class T2\u0026gt; class Person { public: T1 name; T2 age; //构造函数 Person(T1 name, T2 age); //成员函数 void showPerson(); }; //类外实现有参构造函数 template\u0026lt;class T1,class T2\u0026gt; Person\u0026lt;T1, T2\u0026gt;::Person(T1 name, T2 age) { this-\u0026gt;name = name; this-\u0026gt;age = age; } //类外实现成员函数,即使成员函数没有使用模板，也要声明 template\u0026lt;class T1, class T2\u0026gt; void Person\u0026lt;T1, T2\u0026gt;::showPerson() { cout \u0026lt;\u0026lt; \u0026#34;name:\u0026#34; \u0026lt;\u0026lt; this-\u0026gt;name \u0026lt;\u0026lt; \u0026#34;，age:\u0026#34; \u0026lt;\u0026lt; this-\u0026gt;age \u0026lt;\u0026lt; endl; } int main() { Person\u0026lt;string, int\u0026gt; p(\u0026#34;lucy\u0026#34;, 18); p.showPerson(); return 0; } 类模板的分文件编写 # 问题： 类模板中成员函数创建时机是在调用阶段，导致分文件编写时链接不到 解决： 解决方式1：直接包含.cpp源文件 解决方式2：将声明和实现写到同一个文件中，并更改后缀名为.hpp，hpp是约定的名称，并不是强制 解决方法1，不推荐 # person.h文件：\n#pragma once #include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; template\u0026lt;class T1,class T2\u0026gt; class Person { public: T1 name; T2 age; Person(T1 name, T2 age); void showPerson(); }; person.cpp文件：\n#include\u0026#34;person.h\u0026#34; template\u0026lt;class T1,class T2\u0026gt; Person\u0026lt;T1,T2\u0026gt;::Person(T1 name,T2 age) { this-\u0026gt;name = name; this-\u0026gt;age = age; } template\u0026lt;class T1,class T2\u0026gt; void Person\u0026lt;T1, T2\u0026gt;::showPerson() { cout \u0026lt;\u0026lt; \u0026#34;name:\u0026#34; \u0026lt;\u0026lt; this-\u0026gt;name \u0026lt;\u0026lt; \u0026#34;,age:\u0026#34; \u0026lt;\u0026lt; this-\u0026gt;age \u0026lt;\u0026lt; endl; } main.cpp文件：\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; //如果这样引入头文件，可以正常编译，但是，执行报错 //原因是：类模板中成员的创建时机是在运行时，编译器无法连接到cpp文件 //#include\u0026#34;person.h\u0026#34; //解决方法1，不推荐 //编译器可以通过cpp文件中的include连接.h文件 #include\u0026#34;person.cpp\u0026#34; using namespace std; int main() { Person\u0026lt;string, int\u0026gt; p(\u0026#34;lucy\u0026#34;, 18); p.showPerson(); return 0; } 解决方法2 # 将.h和.cpp的内容写在一起，文件后缀为.hpp\nperson.hpp文件：\n#pragma once #include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; //类模板的声明 template\u0026lt;class T1,class T2\u0026gt; class Person { public: T1 name; T2 age; Person(T1 name, T2 age); void showPerson(); }; //类模板成员函数的实现 template\u0026lt;class T1, class T2\u0026gt; Person\u0026lt;T1, T2\u0026gt;::Person(T1 name, T2 age) { this-\u0026gt;name = name; this-\u0026gt;age = age; } template\u0026lt;class T1, class T2\u0026gt; void Person\u0026lt;T1, T2\u0026gt;::showPerson() { cout \u0026lt;\u0026lt; \u0026#34;name:\u0026#34; \u0026lt;\u0026lt; this-\u0026gt;name \u0026lt;\u0026lt; \u0026#34;,age:\u0026#34; \u0026lt;\u0026lt; this-\u0026gt;age \u0026lt;\u0026lt; endl; } main.cpp文件：\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; #include\u0026#34;person.hpp\u0026#34; using namespace std; int main() { Person\u0026lt;string, int\u0026gt; p(\u0026#34;lucy\u0026#34;, 18); p.showPerson(); return 0; } 类模板和友元 # 全局函数类内实现 - 直接在类内声明友元即可\n全局函数类外实现 - 需要提前让编译器知道全局函数的存在\n建议全局函数做类内实现，用法简单，而且编译器可以直接识别\n全局函数类内实现：\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; template\u0026lt;class T1,class T2\u0026gt; class Person { //全局函数类内实现 friend void printPerson(Person\u0026lt;T1,T2\u0026gt; p) { cout \u0026lt;\u0026lt; p.name \u0026lt;\u0026lt; p.age \u0026lt;\u0026lt; endl; } public: Person(T1 name, T2 age) { this-\u0026gt;name = name; this-\u0026gt;age = age; } private: T1 name; T2 age; }; int main() { Person\u0026lt;string, int\u0026gt; p(\u0026#34;lucy\u0026#34;, 17); printPerson(p); return 0; } 全局函数类外实现：\n#include\u0026lt;iostream\u0026gt; #include\u0026lt;string\u0026gt; using namespace std; //需要让编译器提前知道有Person类的存在 template\u0026lt;class T1, class T2\u0026gt; class Person; //实现 template\u0026lt;class T1, class T2\u0026gt; void printPerson(Person\u0026lt;T1, T2\u0026gt; p) { cout \u0026lt;\u0026lt; p.name \u0026lt;\u0026lt; p.age \u0026lt;\u0026lt; endl; } template\u0026lt;class T1,class T2\u0026gt; class Person { //全局函数类外实现 //1、需要添加一个空模板的参数列表，如果不加，会报错：1 个无法解析的外部命令 friend void printPerson\u0026lt;\u0026gt;(Person\u0026lt;T1, T2\u0026gt; p); public: Person(T1 name, T2 age) { this-\u0026gt;name = name; this-\u0026gt;age = age; } private: T1 name; T2 age; }; int main() { Person\u0026lt;string, int\u0026gt; p(\u0026#34;lucy\u0026#34;, 17); printPerson(p); return 0; } 模板使用案例 # 实现一个通用的数组类，要求如下：\n可以对内置数据类型以及自定义数据类型的数据进行存储 将数组中的数据存储到堆区 构造函数中可以传入数组的容量 提供对应的拷贝构造函数以及operator=防止浅拷贝问题 提供尾插法和尾删法对数组中的数据进行增加和删除 可以通过下标的方式访问数组中的元素 可以获取数组中当前元素个数和数组的容量 MyArray.hpp文件：\n#pragma once #include\u0026lt;iostream\u0026gt; using namespace std; //编写通用的数组类 template\u0026lt;typename T\u0026gt; class MyArray{ private: //数组 T * arr; //数组的容量 int capacity; //数组的大小 int size; public: //有参构造，指定初始容量 MyArray(int capacity){ cout \u0026lt;\u0026lt; \u0026#34;有参构造\u0026#34; \u0026lt;\u0026lt; endl; this-\u0026gt;capacity = capacity; this-\u0026gt;arr = new T[capacity]; } //析构函数，手动释放 ~MyArray(){ cout \u0026lt;\u0026lt; \u0026#34;析构\u0026#34; \u0026lt;\u0026lt; endl; if(this-\u0026gt;arr != NULL){ delete[] this-\u0026gt;arr; this-\u0026gt;arr = NULL; //置空数组指针，防止野指针 } } //拷贝构造，深拷贝 MyArray(const MyArray\u0026amp; arr){ //获取容量和大小 this-\u0026gt;size = arr.size; this-\u0026gt;capacity = arr.capacity; //堆区new一个新的数组，容量和拷贝的数组一样 this-\u0026gt;arr = new T[arr.capacity]; //将所有的数据都拷贝过来 for(int i = 0;i \u0026lt; this-\u0026gt;size;i++){ this-\u0026gt;arr[i] = arr[i]; } } //重载=，防止直接=赋值出现浅拷贝问题 MyArray\u0026amp; operator=(const MyArray\u0026amp; arr){ //判断当前对象数组是否在堆区已经有数据，如果有，先释放 if(this-\u0026gt;arr != NULL){ delete[] this-\u0026gt;arr; this-\u0026gt;arr = NULL; this-\u0026gt;capacity = 0; this-\u0026gt;size = 0; } //进行深拷贝 this-\u0026gt;size = arr.size; this-\u0026gt;capacity = arr.capacity; this-\u0026gt;arr = new T[arr.capacity]; for(int i = 0;i \u0026lt; this-\u0026gt;size;i++){ this-\u0026gt;arr[i] = arr[i]; } } //尾插法，新增元素 void push(const T\u0026amp; value){ //判断容量是否等于大小 if(this-\u0026gt;size == this-\u0026gt;capacity){ return; } this-\u0026gt;arr[this-\u0026gt;size] = value; this-\u0026gt;size++; } //尾删法，删除元素 void remove(){ delete arr[this-\u0026gt;size - 1]; arr[this-\u0026gt;size - 1] = NULL; this-\u0026gt;size--; } //支持下标访问元素，重载[] T\u0026amp; operator[](int index){ return this-\u0026gt;arr[index]; } //获取容量 int getCapacity(){ return this-\u0026gt;capacity; } //获取大小 int getSize(){ return this-\u0026gt;size; } }; main.cpp文件：\n```cp #include\u0026lt;iostream\u0026gt; #include\u0026#34;MyArray.hpp\u0026#34; using namespace std; int main(){ MyArray\u0026lt;int\u0026gt; array(10); array.push(521); array.push(13); cout \u0026lt;\u0026lt; array[0] \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; array[1] \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;数组的容量：\u0026#34; \u0026lt;\u0026lt; array.getCapacity() \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;数组的大小：\u0026#34; \u0026lt;\u0026lt; array.getSize() \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/d8245fe1/f50d4583/7cedbb3a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e模板 \n    \u003cdiv id=\"模板\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%a8%a1%e6%9d%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e建立通用模具，提高通用性\u003c/p\u003e","title":"5、泛型编程","type":"posts"},{"content":" 选择结构 # if-else if-else # 和java的用法一样\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { int age = 0; cout \u0026lt;\u0026lt; \u0026#34;请输入年龄\u0026#34; \u0026lt;\u0026lt; endl; cin \u0026gt;\u0026gt; age; if (age \u0026lt; 18) { cout \u0026lt;\u0026lt; \u0026#34;未成年\u0026#34; \u0026lt;\u0026lt; endl; } else if (age \u0026lt; 45) { cout \u0026lt;\u0026lt; \u0026#34;青壮年\u0026#34; \u0026lt;\u0026lt; endl; } else { cout \u0026lt;\u0026lt; \u0026#34;注意养生\u0026#34; \u0026lt;\u0026lt; endl; } system(\u0026#34;pause\u0026#34;); return 0; } 三目运算符 # 作用： 通过三目运算符实现简单的判断\n语法：表达式1 ? 表达式2 ：表达式3\n和java一样，表达式1，需要是bool类型\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; int main() { int age = 24; string msg = (age \u0026lt; 18) ? \u0026#34;未成年\u0026#34; : \u0026#34;成年\u0026#34;; cout \u0026lt;\u0026lt; msg \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } switch-case # 作用：执行多条件分支语句\n和java的一样，只不过，cpp的switch后面只支持字符型和整形\n如果case后面有多行代码，那么需要使用大括号括住\n语法：\nswitch(表达式){ case 结果1：执行语句;break; case 结果2：执行语句;break; ... default:执行语句;break; } 例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; int main() { int day = 1; string msg = \u0026#34;\u0026#34;; switch (day) { case 1: msg = \u0026#34;星期一\u0026#34;; break; case 2: msg = \u0026#34;星期二\u0026#34;; break; default: msg = \u0026#34;懒得写那么多了\u0026#34;; break; } cout \u0026lt;\u0026lt; msg \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 循环结构 # while # 作用：满足循环条件，执行循环语句\n语法： while(循环条件){ 循环语句 }\n解释：只要循环条件的结果为真，就执行循环语句\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { int i = 0; while (i \u0026lt; 10) { cout \u0026lt;\u0026lt; i \u0026lt;\u0026lt; endl; i++; } system(\u0026#34;pause\u0026#34;); return 0; } do-while # 作用： 满足循环条件，执行循环语句\n语法： do{ 循环语句 } while(循环条件);\n注意：与while的区别在于do\u0026hellip;while会先执行一次循环语句，再判断循环条件\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { int i = 0; do { cout \u0026lt;\u0026lt; i \u0026lt;\u0026lt; endl; i++; } while (i \u0026lt; 10); system(\u0026#34;pause\u0026#34;); return 0; } for # 作用： 满足循环条件，执行循环语句\n语法： for(起始表达式;条件表达式;末尾循环体) { 循环语句; }\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { for (int i = 0; i \u0026lt; 10; i++) { cout \u0026lt;\u0026lt; i \u0026lt;\u0026lt; endl; } system(\u0026#34;pause\u0026#34;); return 0; } 控制关键字 # break # 作用: 用于跳出选择结构或者循环结构\nbreak使用的时机：\n出现在switch条件语句中，作用是终止case并跳出switch 出现在循环语句中，作用是跳出当前的循环语句 出现在嵌套循环中，跳出最近的内层循环语句 continue # 作用：在循环语句中，跳过本次循环中余下尚未执行的语句，继续执行下一次循环\ngoto # 作用：可以无条件跳转语句\n语法： goto 标记;\n解释：如果标记的名称存在，执行到goto语句时，会跳转到标记的位置\n注意：在程序中不建议使用goto语句，以免造成程序流程混乱\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { int i = 0; goto flag; i++; flag: cout \u0026lt;\u0026lt; i \u0026lt;\u0026lt; endl; //0 system(\u0026#34;pause\u0026#34;); return 0; } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/d8245fe1/09e70634/a7495c4c/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e选择结构 \n    \u003cdiv id=\"选择结构\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%80%89%e6%8b%a9%e7%bb%93%e6%9e%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eif-else if-else \n    \u003cdiv id=\"if-else-if-else\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#if-else-if-else\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e和java的用法一样\u003c/p\u003e","title":"5、程序流程控制","type":"posts"},{"content":" 实现网络通信需要解决的两个问题 # 如何准确地定位网络上一台或多台主机；并且定位主机上的特定的应用？ 找到主机后如何可靠高效地进行数据传输？ 网络通信的两个要素 # IP和端口号，解决上面问题1 网络通信协议，例如TCP/IP参考模型（应用层、传输层、网络层、物理+数据链路层），解决上面问题2 通信要素一：IP和端口号 # IP # IP：唯一的标识 Internet 上的计算机（通信实体） 在Java中使用InetAddress类代表IP IP分类：IPv4和 IPv6 域名: www.baidu.com，www.mi.com，www.sina.com，www.jd.com 域名解析：域名容易记忆，当在连接网络时输入一个主机的域名后，域名服务器（DNS）负责将域名转化成IP地址，这样才能和主机建立连接。 本地回路地址：127.0.0.1对应着localhost InetAddress类 # 此类的一个对象就代表着一个具体的IP地址，没有公共的构造方法，需要使用以下静态方法实例化 public static InetAddress getLocalHost() public static InetAddress getByNames(String host) 常用方法 public String getHostAddress()：返回IP地址字符串 public String getHostName()：返回此IP地址主机名 public boolean isReachable(int timeout)：测试是否可以连通此地址 //1.获取本机地址ip对象 InetAddress ip1 = InetAddress.getLocalHost(); System.out.println(ip1.getHostName());//获取主机名字 System.out.println(ip1.getHostAddress());//获取ip地址 //2.获取域名ip对象 InetAddress ip2 = InetAddress.getByName(\u0026#34;www.baidu.com\u0026#34;); System.out.println(ip2.getHostName());//获取域名 System.out.println(ip2.getHostAddress());//获取域名的ip地址 //3.获取公网对象 InetAddress ip3 = InetAddress.getByName(\u0026#34;112.80.248.76\u0026#34;); System.out.println(ip3.getHostName());//获取公网名字 System.out.println(ip3.getHostAddress());//获取公网ip地址 //判断网络是否能连接通信 ping 5s之前测试是否能通过 System.out.println(ip3.isReachable(5000));//通过会返回true 端口号 # 正在计算机上运行的进程。\n要求：不同的进程不同的端口号\n范围：被规定为一个 16 位的整数0~65535。\n端口号与IP地址的组合得出一个网络套接字：Socket\n通信要素二：网络通信协议 # 分层模型 # TCP和UDP # TCP协议 使用TCP协议前，必须先建立TCP连接，形成数据传输通道 传输前，采用三次握手方式，点对点通信，可靠 TCP协议进行通信的两个进程称为：客户端、服务端 在连接中可进行大数据量传输 传输完毕，需要释放建立的连接，效率低 使用场景：看重数据完整性，许多高级协议都是建立在TCP协议之上的，例如HTTP、SMTP等。 UDP协议 将数据源IP、目的地IP、端口封装成数据报，不需要建立连接 每个数据报大小限制在64K内 发送时不管对方是否准备好，接收方收到也不确认，不可靠 可以广播发送 发送数据结束无需释放资源，效率高 使用场景：传输的数据通常是能容忍丢失的，例如一些语音视频通信的应用会选择UDP 协议。 TCP # TCP三次握手和四次挥手 # TCP的三次握手和四次挥手实质就是TCP通信的连接和断开。\n三次握手：为了对每次发送的数据量进行跟踪与协商，确保数据段的发送和接收同步，根据所接收到的数据量而确认数据发送、接收完毕后何时撤消联系，并建立虚连接。 四次挥手：即终止TCP连接，就是指断开一个TCP连接时，需要客户端和服务端总共发送4个包以确认连接的断开。 三次握手 # 第1次握手：客户端发送一个带有SYN（synchronize）标志的数据包给服务端； 客户端发送建立TCP连接的请求报文，其中报文中包含seq序列号，是由发送端随机生成的，并且将报文中的SYN字段置为1，表示需要建立TCP连接。（SYN=1，seq=x，x为随机生成数值） 第2次握手：服务端接收成功后，回传一个带有SYN/ACK标志的数据包传递确认信息，表示我收到了； 服务端回复客户端发送的TCP连接请求报文，其中包含seq序列号，是由回复端随机生成的，并且将SYN置为1，而且会产生ACK字段，ACK字段数值是在客户端发送过来的序列号seq的基础上加1进行回复，以便客户端收到信息时，知晓自己的TCP建立请求已得到验证。（SYN=1，ACK=x+1，seq=y，y为随机生成数值）这里的ack加1可以理解为是确认和谁建立连接 第3次握手：客户端再回传一个带有ACK标志的数据包，表示我知道了，握手结束。 客户端收到服务端发送的TCP建立验证请求后，会使自己的序列号加1表示，并且再次回复ACK验证请求，在服务端发过来的seq上加1进行回复。（SYN=1，ACK=y+1，seq=x+1） SYN标志位数置1，表示建立TCP连接；ACK标志表示验证字段。\n四次挥手 # 由于TCP连接是全双工的，因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动，一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭，而另一方执行被动关闭。\n第1次挥手：客户端发送一个FIN，用来关闭客户端到服务端的数据传送，客户端进入FIN_WAIT_1状态； 客户端发送断开TCP连接请求的报文，其中报文中包含seq序列号，是由发送端随机生成的，并且还将报文中的FIN字段置为1，表示需要断开TCP连接。（FIN=1，seq=x，x由客户端随机生成） 第2次挥手：服务端收到FIN后，发送一个ACK给客户端，确认序号为收到序号+1（与SYN相同，一个FIN占用一个序号），服务端进入CLOSE_WAIT状态； 服务端会回复客户端发送的TCP断开请求报文，其包含seq序列号，是由回复端随机生成的，而且会产生ACK字段，ACK字段数值是在客户端发过来的seq序列号基础上加1进行回复，以便客户端收到信息时，知晓自己的TCP断开请求已经得到验证。（FIN=1，ACK=x+1，seq=y，y由服务端随机生成） 第3次挥手：服务端发送一个FIN，用来关闭服务端到客户端的数据传送，服务端进入LAST_ACK状态； 服务端在回复完客户端的TCP断开请求后，不会马上进行TCP连接的断开，服务端会先确保断开前，所有传输到A的数据是否已经传输完毕，一旦确认传输数据完毕，就会将回复报文的FIN字段置1，并且产生随机seq序列号。（FIN=1，ACK=x+1，seq=z，z由服务端随机生成） 第4次挥手：客户端收到FIN后，客户端进入TIME_WAIT状态，接着发送一个ACK给Server，确认序号为收到序号+1，服务端进入CLOSED状态，完成四次挥手。 客户端收到服务端的TCP断开请求后，会回复服务端的断开请求，包含随机生成的seq字段和ACK字段，ACK字段会在服务端的TCP断开请求的seq基础上加1，从而完成服务端请求的验证回复。（FIN=1，ACK=z+1，seq=h，h为客户端随机生成） 至此TCP断开的4次挥手过程完毕 其中：FIN标志位数置1，表示断开TCP连接。\nServerSocket # 构造方法 ServerSocket(int port) ：创建绑定到特定端口的服务器套接字 ServerSocket(int port, int backlog)：利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号 ServerSocket(int port, int backlog, InetAddress address)：使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器 ServerSocket()：创建非绑定服务器套接字。 常用方法 getLocalPort()：返回此套接字在其上侦听的端口 accept()：侦听并接受到此套接字的连接 setSoTimeout(int timeout)：指定超时值 bind(SocketAddress host, int backlog)：将 ServerSocket 绑定到特定地址 Socket # 客户端要获取一个 Socket 对象通过实例化 ，而服务器端获得一个 Socket 对象是通过accept()方法的返回值\n构造方法（Socket 构造方法执行完成后，并不只实例化一个 Socket 对象，其会尝试连接到指定的服务器和端口） Socket(String host, int port) ：创建一个流套接字并将其连接到指定主机上的指定端口号 Socket(InetAddress host, int port)：创建一个流套接字并将其连接到指定 IP 地址的指定端口号 Socket(String host, int port, InetAddress localAddress, int localPort)：创建一个指定主机和端口的套接字并将其连接到指定远程主机上的指定远程端口 Socket(InetAddress host, int port, InetAddress localAddress, int localPort)：创建一个指定ip和端口的套接字并将其连接到指定远程地址上的指定远程端口 Socket()：通过系统默认类型的 SocketImpl 创建未连接套接字 常用方法 connect(SocketAddress host, int timeout)：将此套接字连接到服务器，并指定一个超时值 getInetAddress()：返回套接字连接的地址 getPort()：返回此套接字连接到的远程端口 getLocalPort()：返回此套接字绑定到的本地端口 getRemoteSocketAddress()：返回此套接字连接的端点的地址，如果未连接则返回 null getInputStream()：返回此套接字的输入流 getOutputStream()：返回此套接字的输出流 close()：关闭此套接字 TCP Demo # 服务端 # public static void main(String[] args) throws Exception{ //1、创建服务端管道 ServerSocket serverSocket = new ServerSocket(8080); //2、阻塞等待客户端连接 Socket accept = serverSocket.accept(); System.out.println(\u0026#34;有客户端连接：\u0026#34; + accept.getRemoteSocketAddress()); //3、从管道获取输入流，接收客户端消息 InputStream inputStream = accept.getInputStream(); //4、升级输入流为缓冲流 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); //5、逐行读取数据 String s; while ((s = bufferedReader.readLine()) != null){ System.out.println(\u0026#34;客户端说：\u0026#34; + s); } bufferedReader.close(); } 客户端 # public static void main(String[] args) throws Exception{ //1、创建客户端管道 Socket socket = new Socket(\u0026#34;127.0.0.1\u0026#34;,8080); //2、从管道获取输出流，发送消息给服务端 OutputStream outputStream = socket.getOutputStream(); //3、升级输出流为打印流 PrintStream printStream = new PrintStream(outputStream); //4、发送消息 printStream.println(\u0026#34;你好呀，服务端！！！\u0026#34;); printStream.flush(); printStream.close(); } UDP # DatagramSocket # 构造方法 DatagramSocket()：绑定到本地地址和一个随机的端口号 DatagramSocket(int port)：绑定本地地址和一个特定的端口号 DatagramSocket(int port, InetAddress iad)：绑定到特定的端口号及指定地址 DatagramSocket(SocketAddress sad)：绑定指定地址和随机端口号 常用方法 close()：关闭套接字 recevie(DatagramPacket dp)：接受数据报 send(DatagramPacket dp)：发送数据报 DatagramPacket # java.net包中的 DatagramPacket 类用来表示数据报包，数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由，也可能按不同的顺序到达。\n接收包，（用来接收数据） DatagramPacket(byte[] buf, int length)：用来接受长度为length的buf数据(即数据存于字节数组buf中) 发送包（封装数据、目的ip、目的端口，用来发送数据） DatagramPacket(byte[] buf, int length, InetAddress address, int port)：将length长的buf数据发送到指定的地址的端口号处 DatagramPacket(byte[] buf, int length, SocketAddress address)：将length长的buf数据发送到指定的套接字地址处 UDP Demo # 服务端 # public static void main(String[] args) throws Exception{ //1、创建UDP对象，自定义端口 DatagramSocket datagramSocket = new DatagramSocket(8080); //2、创建容器接收数据 byte[] bytes = new byte[1024]; //3、创建接收数据包对象，将数据接收到bytes中 DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length); while (true){ //4、接收数据 datagramSocket.receive(datagramPacket); SocketAddress address = datagramPacket.getSocketAddress(); //5、获取接收数据的长度 int length = datagramPacket.getLength(); System.out.println(\u0026#34;收到数据：\u0026#34; + new String(bytes,0,length) + \u0026#34;，来自：\u0026#34; + address); } } 客户端 # public static void main(String[] args) throws Exception{ //1、创建UDP对象 DatagramSocket datagramSocket = new DatagramSocket(); //发送五次数据 for (int i = 0; i \u0026lt; 5; i++) { String s = \u0026#34;来自客户端的数据，第\u0026#34; + i + \u0026#34;次\u0026#34;; byte[] bytes = s.getBytes(StandardCharsets.UTF_8); //2、创建发送数据包对象 DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length, InetAddress.getByName(\u0026#34;127.0.0.1\u0026#34;),8080); //3、发送数据 datagramSocket.send(datagramPacket); } //4、关闭资源 datagramSocket.close(); } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/fbeea4f4/ea5074ef/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e实现网络通信需要解决的两个问题 \n    \u003cdiv id=\"实现网络通信需要解决的两个问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%9e%e7%8e%b0%e7%bd%91%e7%bb%9c%e9%80%9a%e4%bf%a1%e9%9c%80%e8%a6%81%e8%a7%a3%e5%86%b3%e7%9a%84%e4%b8%a4%e4%b8%aa%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e如何准确地定位网络上一台或多台主机；并且定位主机上的特定的应用？\u003c/li\u003e\n\u003cli\u003e找到主机后如何可靠高效地进行数据传输？\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003ch2 class=\"relative group\"\u003e网络通信的两个要素 \n    \u003cdiv id=\"网络通信的两个要素\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%bd%91%e7%bb%9c%e9%80%9a%e4%bf%a1%e7%9a%84%e4%b8%a4%e4%b8%aa%e8%a6%81%e7%b4%a0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003eIP和端口号，解决上面问题\u003ccode\u003e1\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e网络通信协议，例如TCP/IP参考模型（应用层、传输层、网络层、物理+数据链路层），解决上面问题\u003ccode\u003e2\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003ch3 class=\"relative group\"\u003e通信要素一：IP和端口号 \n    \u003cdiv id=\"通信要素一ip和端口号\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%80%9a%e4%bf%a1%e8%a6%81%e7%b4%a0%e4%b8%80ip%e5%92%8c%e7%ab%af%e5%8f%a3%e5%8f%b7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003eIP \n    \u003cdiv id=\"ip\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#ip\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003eIP：唯一的标识 Internet 上的计算机（通信实体）\u003c/li\u003e\n\u003cli\u003e在Java中使用\u003ccode\u003eInetAddress\u003c/code\u003e类代表IP\u003c/li\u003e\n\u003cli\u003eIP分类：IPv4和 IPv6\u003c/li\u003e\n\u003cli\u003e域名:  \u003ccode\u003ewww.baidu.com\u003c/code\u003e，\u003ccode\u003ewww.mi.com\u003c/code\u003e，\u003ccode\u003ewww.sina.com\u003c/code\u003e，\u003ccode\u003ewww.jd.com\u003c/code\u003e\n\u003cul\u003e\n\u003cli\u003e域名解析：域名容易记忆，当在连接网络时输入一个主机的域名后，域名服务器（DNS）负责将域名转化成IP地址，这样才能和主机建立连接。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e本地回路地址：\u003ccode\u003e127.0.0.1\u003c/code\u003e对应着\u003ccode\u003elocalhost\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch5 class=\"relative group\"\u003eInetAddress类 \n    \u003cdiv id=\"inetaddress类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#inetaddress%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h5\u003e\n\u003cul\u003e\n\u003cli\u003e此类的一个对象就代表着一个具体的IP地址，没有公共的构造方法，需要使用以下静态方法实例化\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003epublic static InetAddress getLocalHost()\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003epublic static InetAddress getByNames(String host)\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e常用方法\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003epublic String getHostAddress()\u003c/code\u003e：返回IP地址字符串\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003epublic String getHostName()\u003c/code\u003e：返回此IP地址主机名\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003epublic boolean isReachable(int timeout)\u003c/code\u003e：测试是否可以连通此地址\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//1.获取本机地址ip对象\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eInetAddress\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eip1\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInetAddress\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetLocalHost\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eip1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetHostName\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取主机名字\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eip1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetHostAddress\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取ip地址\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//2.获取域名ip对象\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eInetAddress\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eip2\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInetAddress\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetByName\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;www.baidu.com\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eip2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetHostName\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取域名\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eip2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetHostAddress\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取域名的ip地址\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//3.获取公网对象\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eInetAddress\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eip3\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInetAddress\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetByName\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;112.80.248.76\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eip3\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetHostName\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取公网名字\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eip3\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetHostAddress\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取公网ip地址\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//判断网络是否能连接通信 ping 5s之前测试是否能通过\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eip3\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eisReachable\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e5000\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"c1\"\u003e//通过会返回true\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e端口号 \n    \u003cdiv id=\"端口号\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%ab%af%e5%8f%a3%e5%8f%b7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e正在计算机上运行的进程。\u003c/p\u003e","title":"5、网络编程","type":"posts"},{"content":" 面向对象内容的三条主线 # 1.Java类及类的成员：属性、方法、构造器；代码块、内部类\n2.面向对象的三大特征：封装性、继承性、多态性、(抽象性)\n3.其它关键字：this、super、static、final、abstract、interface、package、import等\n面向对象的思想概述 # Java语言的基本元素： 类和对象 # 类 # 类的设计原则 # 尽量要将数据（成员变量）设为私有 要对数据（成员变量）初始化 不要在类中使用过多的基本类型 不是所有的属性都需要独立的访问器和更改器 格式要统一 将职责过多的类进行分解 类名与方法名应体现它们的功能（望文生义） UML类图 # 类的结构一：属性 # 属性（成员变量）vs局部变量 # 相同点\n1、定义变量的格式是一样的：数据类型 变量名 = 变量值\n2、先声明，后使用\n3、变量都有其对应的作用域\n不同点\n1、在类中声明位置的不同\n属性：直接定义在类的一对{}内\n局部变量：声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量\n2、关于权限修饰符的不同\n属性：可以在声明属性时，指明其权限，使用权限修饰符\n常用的权限修饰符：private（私有）、pubilc、缺省、protected\n局部变量：不可以使用权限修饰符\n3、默认初始化值的情况\n属性：类的属性，根据其类型，都有默认初始化值\n整形（byte、short、int、long）：0\n浮点型（double、float）：0.0\n字符型（char）：0（或\\u0000）\n布尔型（boolean）：false\n引用数据类型：null\n局部变量：没有默认初始化值\n意味着，调用局部变量之前，一定要显示赋值\n特别的，形参在调用时，赋值即可\n4、在内存中加载的位置不同\n属性：加载在堆空间中\n局部变量：加载在栈空间\n类的结构二：方法 # 方法：描述类应该具有的功能，比如：\nMath类：sqrt(),random()\u0026hellip;\nScanner类：nextXxx()\u0026hellip;\nArrays类：sort(),binarySearch(),toString(),equals()\u0026hellip;\n方法的分类 # 方法的声明格式 # public static void main (String[] args){ // 方法体 } //权限修饰符：private \\缺省 \\protected \\pubilc ----\u0026gt; 封装性 //修饰符：static \\ final \\ abstract \\native 可以用来修饰方法 //返回值类型： 无返回值 / 有返回值 --\u0026gt; return //方法名：需要满足标识符命名的规则、规范；\u0026#34;见名知意\u0026#34; //形参列表：重载 vs 重写；参数的值传递机制；体现对象的多态性 //方法体：来体现方法的功能 return关键字的使用 # 使用范围：使用在方法体中\n作用：\n1、结束方法\n2、针对于有返回值类型的方法，使用return 数据方式，返回需要的数据\n注意点：return关键字后面不可以声明执行语句\n注意 # 方法的使用中，可以调用当前类的属性和方法。\n方法中不可以定义方法。\n方法的重载 # 定义：在同一个类中，允许存在一个以上的同名方法，只要它们的参数个数或者参数类型不同即可。\n总结：\u0026ldquo;两同一不同\u0026rdquo;：类、相同方法名，相同\n​ 参数列表：参数个数、参数类型，参数顺序，不同\n方法的重载和方法的权限修饰符、返回值类型、形参变量名、方法体都没关系！\n可变个数的形参（JDK5.0新增） # 声明格式 # 方法名(参数的类型名 \u0026hellip;参数名)\n例如：public void st(String ... a);\n1、可变参数：方法参数部分指定类型的参数个数是可变多个：0个，1个或多个\n2、可变个数形参的方法与同名的方法之间，彼此构成重载\n3、可变参数方法的使用与方法参数部分使用数组是一致的\n4、方法的参数部分有可变形参，需要放在形参声明的最后\n5、在一个方法的形参位置，最多只能声明一个可变个数形参\n方法参数的值传递机制 # 方法，必须由其所在类或对象调用才有意义。若方法含有参数：\n形参：方法声明时的参数\n实参：方法调用时实际传给形参的参数值\nJava里方法的参数传递方式只有一种：值传递。 即将实际参数值的副本 （复制品）传入方法内，而参数本身不受影响。\n形参是基本数据类型：将实参基本数据类型变量的数据值传递给形参\n形参是引用数据类型：将实参引用数据类型变量的地址值传递给形参\n方法的递归 # 递归方法：一个方法体内调用它自身。\n方法递归包含了一种隐式的循环，它会重复执行某段代码，但这种重复执行无须循环控制。\n递归一定要向已知方向递归，否则这种递归就变成了无穷递归，类似于死循环。\n类的结构三：构造器 # 构造器（或构造方法）：Constructor\n构造器的作用 # 1.创建对象\n2.初始化对象的信息\n说明 # 如果没显式的定义类的构造器的话，则系统默认提供一个空参的构造器\n定义构造器的格式：权限修饰符 类名(形参列表){}\n一个类中定义的多个构造器，彼此构成重载\n一旦我们显式的定义了类的构造器之后，该类就不再提供默认的空参构造器\n一个类中，至少会有一个构造器。\n创建对象的执行顺序 # 1、给属性开辟空间，给属性赋默认值\n2、如果声明属性的时候给属性赋初始值，那么接下来会给属性赋初始值\n3、如果类中有代码块，会执行代码块（每创建一次对象都会执行一次）\n4、调用构造方法\n类的结构四：代码块 # 代码块的作用 # 用来初始化类、对象的信息\n代码块要是使用修饰符，只能使用static\n对象 # 没有完全相同的两个对象，对象具有唯一性（内存）！\n类和对象的使用（面向对象思想落地的实现） # 1、创建类、设计类的成员\n2、创建类的对象new Constuctor()\n3、通过对象.属性或对象.方法()调用对象的结构\n如果创建了一个类的多个对象，对于类中定义的属性，每个对象都拥有各自的一套副本，且互不干扰。意味着：如果修改一个对象的属性A，则不影响另一个对象的属性A的值。\n对象的产生的内存解析 # 对象数组的内存解析 # 面向对象特征一：封装性 # 概念 # 隐藏复杂，暴露简单\n隐藏对象的属性和实现细节,仅对外提供公共访问方式,将类的某些信息隐藏在类的内部，不允许外部程序直接访问，而是通过该类提供的方法来对隐藏的信息进行操作和访问。\n好处 # （1）只能通过规定的方法访问数据，可以有效的保护数据\n（2）隐藏类的实现细节，方便修改和实现。\n封装的实现步骤 # （1）修改属性的可见性设为private\n（2）创建getter/setter方法（用于属性的读写）（通过这两种方法对数据进行获取和设定，对象通过调用这两种发方法实现对数据的读写）\n（3）在getter/setter方法中加入属性控制语句（对属性值的合法性进行判断）（不是必须要加）\n四种访问权限修饰符 # 权限从小到大顺序为：private \u0026lt; 缺省 \u0026lt; protected \u0026lt; public\n可见范围 # 修饰符 类内部 同一个包 不同包的子类 同一个工程 private yes 缺省（default） yes yes protected yes yes yes public yes yes yes yes 4种权限都可以用来修饰类的内部结构：属性、方法、构造器、内部类\n修饰类的话，只能使用：缺省、public\nJava中关于对象的名词 # Bean 应用在Spring上，所有被Spring管理的类对象就可以将其称作为Bean。 它不仅仅可以包括对象的属性以及get、set方法，还可以有具体的业务逻辑。 Entity，同DO（Data Object） 实体，即指数据库表对应到实体类的映射，不包含业务逻辑方法，属性和数据表字段对应。 POJO（Plain Ordinary Java Object） 普通java对象，除了属性和get、set方法外不包含具体的业务逻辑方法，和Entity区别在于没有和数据表中字段一一对应。 Model model的字段要大于entity的字段，model主要用作前端页面数据展示，属性，字段，类型都可以有改变，但entity的属性则必须与数据表字段对应。 Query 数据查询对象，各层接收上层的查询请求。额外规定：【强制】超过2个参数的查询封装，禁止使用Map类来传输。 VO（View Object） 显示层对象，通常是Web向模板渲染引擎层传输的对象，即控制层向前端传输的对象。 关键字：this # 概念 # this理解为：当前对象 或 当前正在创建的对象\n在类的方法中，我们可以使用this.属性或this.方法的方式，调用当前对象属性或方法。但是，通常情况下，我们都择省略this.。\n特殊情况下，如果方法的形参和类的属性同名时，我们必须显式的使用\u0026quot;this.变量\u0026quot;的方式，表明此变量是属性，而非形参。\nthis调用构造器 # 我们在类的构造器中，可以显式的使用this(形参列表)方式，调用本类中指定的其他构造器\n构造器中不能通过this(形参列表)方式调用自己\n如果一个类中有n个构造器，则最多有 n - 1构造器中使用了this(形参列表)\n规定：this(形参列表)必须声明在当前构造器的首行\n构造器内部，最多只能声明一个this(形参列表)，用来调用其他的构造器\n关键字：final # 作用范围 # 可以用来修饰：类、方法、变量\n说明 # final 用来修饰一个类：此类不能被其他类所继承。\n​ 比如：String类、System类、StringBuffer类\nfinal 用来修饰方法：表明此方法不可以被重写\n​ 比如：Object类中getClass();\nfinal 用来修饰变量：此时的“变量”就称为是一个常量\n​ 1. final修饰属性：可以考虑赋值的位置：显式初始化、代码块中初始化、构造器中初始化，但是只可以三选一，final修饰的属性不可以提供setter方法！\n​ 2. final修饰局部变量：尤其是使用final修饰形参时，表明此形参是一个常量。当我们调用此方法时，给常量形参赋一个实参。一旦赋值以后，就只能在方法体内使用此形参，但不能进行重新赋值。\nstatic final 用来修饰属性：全局常量\n包（package） # 概念 # 为了更好的实现项目中类的管理，提供包的概念 使用package声明类或接口所属的包，必须声明在源文件的首行 包，属于标识符，遵循标识符的命名规则、规范xxxyyyzzz、“见名知意” 每.一次，就代表一层文件目录。 JDK中的主要包 # import的使用： # import：导入\n在源文件中显式的使用import结构导入指定包下的类、接口 声明在包的声明和类的声明之间 如果需要导入多个结构，则并列写出即可 可以使用xxx.*的方式，表示可以导入xxx包下的所有结构，但是如果使用的是xxx子包下的结构，则仍需要显式导入 如果使用的类或接口是java.lang（java的默认导入包）包下定义的，则可以省略import结构 如果使用的类或接口是本包下定义的，则可以省略import结构 如果在源文件中，使用了不同包下的同名的类，则必须至少一个类需要以全类名的方式显示。 import static：导入指定类或接口中的静态结构：属性或方法。 出血模型 # 定义：\n简单来说，失血模型就是一个类，只有私有属性以及属性的getter/setter的纯数据类，所有业务逻辑都由Business Object来完成。\n优势：\n实现了业务与数据的完全分离，降低了代码之间的耦合\n单一职责 # 一个类，只有一个引起它变化的原因。应该只有一个职责。每一个职责都是变化的一个轴线，如果一个类有一个以上的职责，这些职责就耦合在了一起。这会导致脆弱的设计。当一个职责发生变化时，可能会影响其它的职责。另外，多个职责耦合在一起，会影响复用性。例如：要实现逻辑和界面的分离\n","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/f742d77f/014aa577/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e面向对象内容的三条主线 \n    \u003cdiv id=\"面向对象内容的三条主线\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%9d%a2%e5%90%91%e5%af%b9%e8%b1%a1%e5%86%85%e5%ae%b9%e7%9a%84%e4%b8%89%e6%9d%a1%e4%b8%bb%e7%ba%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e1.Java类及类的成员：属性、方法、构造器；代码块、内部类\u003c/strong\u003e\u003c/p\u003e","title":"5、面向对象编程（上）","type":"posts"},{"content":" 预处理命令 # 已经多次使用过#include命令。使用库函数之前，应该用#include引入对应的头文件。这种以#号开头的命令称为预处理命令。\n为什么用预处理 # C语言源文件要经过编译、链接才能生成可执行程序：\n1、编译（Compile）会将源文件（.c文件）转换为目标文件。对于 VC/VS，目标文件后缀为.obj；对于GCC，目标文件后缀为.o。\n编译是针对单个源文件的，一次编译操作只能编译一个源文件，如果程序中有多个源文件，就需要多次编译操作。\n2、链接（Link）是针对多个文件的，它会将编译生成的多个目标文件以及系统中的库、组件等合并成一个可执行程序。\n在实际开发中，有时候在编译之前还需要对源文件进行简单的处理。例如，我们希望自己的程序在 Windows 和 Linux 下都能够运行，那么就要在 Windows 下使用 VS 编译一遍，然后在 Linux 下使用 GCC 编译一遍。但是现在有个问题，程序中要实现的某个功能在 VS 和 GCC 下使用的函数不同，（假设 VS 下使用 a()，GCC 下使用 b()），VS 下的函数在 GCC 下不能编译通过，GCC 下的函数在 VS 下也不能编译通过，怎么办呢？\n这就需要在编译之前先对源文件进行处理：如果检测到是 VS，就保留 a() 删除 b()；如果检测到是 GCC，就保留 b() 删除 a()。\n这些在编译之前对源文件进行简单加工的过程，就称为预处理（即预先处理、提前处理）。\n预处理主要是处理以#开头的命令，例如#include \u0026lt;stdio.h\u0026gt;等。预处理命令要放在所有函数之外，而且一般都放在源文件的前面。\n预处理是C语言的一个重要功能，由预处理程序完成。当对一个源文件进行编译时，系统将自动调用预处理程序对源程序中的预处理部分作处理，处理完毕自动进入对源程序的编译。\n编译器会将预处理的结果保存到和源文件同名的.i文件中，例如 main.c 的预处理结果在 main.i 中。和.c一样，.i也是文本文件，可以用编辑器打开直接查看内容。\nC语言提供了多种预处理功能，如宏定义、文件包含、条件编译等，合理地使用它们会使编写的程序便于阅读、修改、移植和调试，也有利于模块化程序设计。\n使用案例 # 要求：开发一个C语言程序，让它暂停 5 秒以后再输出内容，并且要求跨平台，在 Windows 和 Linux 下都能运行\nWindows 平台下的暂停函数的原型是void Sleep(DWORD dwMilliseconds)（注意 S 是大写的），参数的单位是“毫秒”，位于 \u0026lt;windows.h\u0026gt; 头文件。 Linux 平台下暂停函数的原型是unsigned int sleep (unsigned int seconds)，参数的单位是“秒”，位于 \u0026lt;unistd.h\u0026gt; 头文件。 那么就可以通过预处理命令，判断操作系统，然后引入不同的头文件，并且对逻辑也进行预处理判断\n#include \u0026lt;stdio.h\u0026gt; //不同的平台下引入不同的头文件 #if _WIN32 //识别windows平台 #include \u0026lt;windows.h\u0026gt; #elif __linux__ //识别linux平台 #include \u0026lt;unistd.h\u0026gt; #endif int main() { //不同的平台下调用不同的函数 #if _WIN32 //识别windows平台 Sleep(5000); #elif __linux__ //识别linux平台 sleep(5); #endif puts(\u0026#34;Hello World\u0026#34;); return 0; } 常用预处理命令 # 命令 说明 #define 定义宏 #include 包含一个源代码文件，的作用相当于把stdio.h文件中的所有内容都输入该行所在的位置 #undef 取消已定义的宏 #ifdef 如果宏已经定义，则返回真 #ifndef 如果宏没有定义，则返回真 #if 如果给定条件为真，则编译下面代码 #else #if 的替代方案 #elif 如果前面的 #if 给定条件不为真，当前条件为真，则编译下面代码 #endif 结束一个 #if……#else 条件编译块 #error 当遇到标准错误时，输出错误消息 #pragma 使用标准化方法，向编译器发布特殊的命令到编译器中 #include # #include叫做文件包含命令，用来引入对应的头文件（.h文件）。#include 也是C语言预处理命令的一种。\n#include 的处理过程很简单，就是将头文件的内容插入到该命令所在的位置，从而把头文件和当前源文件连接成一个源文件，这与复制粘贴的效果相同。\n两种用法 # #include \u0026lt;stdHeader.h\u0026gt; #include \u0026#34;myHeader.h\u0026#34; 使用尖括号\u0026lt; \u0026gt;，编译器会到系统路径下查找头文件，不会在当前路径下查找 而使用双引号\u0026quot; \u0026quot;，编译器首先在当前目录下查找头文件，如果没有找到，再到系统路径下查找。 注意事项 # 一个 #include 命令只能包含一个头文件，多个头文件需要多个 #include 命令。 同一个头文件可以被多次引入，多次引入的效果和一次引入的效果相同，因为头文件在代码层面有防止重复引入的机制。 文件包含允许嵌套，也就是说在一个被包含的文件中又可以包含另一个文件。 #define # #define 叫做宏定义命令，它也是C语言预处理命令的一种。所谓宏定义，就是用一个标识符来表示一个字符串（这个字符串可以是数字、表达式、if 语句、函数等），如果在后面的代码中出现了该标识符，那么就全部替换成指定的字符串。\n定义格式 # #define NUM 100 #define M (n*n+3*n) //如果定义表达式，那么左右的括号不能省略 注意事项 # 宏定义是用宏名来表示一个字符串，在宏展开时又以该字符串取代宏名，这只是一种简单粗暴的替换。字符串中可以含任何字符，它可以是常数、表达式、if 语句、函数等，预处理程序对它不作任何检查，如有错误，只能在编译已被宏展开后的源程序时发现。 宏定义不是说明或语句，在行末不必加分号，如加上分号则连分号也一起替换。 宏定义必须写在函数之外，其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令。 带参数的宏 # C语言允许宏带有参数。在宏定义中的参数称为“形式参数”，在宏调用中的参数称为“实际参数”，这点和函数有些类似。\n对带参数的宏，在展开过程中不仅要进行字符串替换，还要用实参去替换形参。\n语法：#define 宏名(形参列表) 字符串\n#include \u0026lt;stdio.h\u0026gt; #define MAX(a,b) (a\u0026gt;b) ? a : b int main(){ int x , y, max; printf(\u0026#34;input two numbers: \u0026#34;); scanf(\u0026#34;%d %d\u0026#34;, \u0026amp;x, \u0026amp;y); max = MAX(x, y); printf(\u0026#34;max=%d\\n\u0026#34;, max); return 0; } 预定义宏 # 宏 描述 __DATE__ 当前日期，一个以 \u0026ldquo;MMM DD YYYY\u0026rdquo; 格式表示的字符常量。 __TIME__ 当前时间，一个以 \u0026ldquo;HH:MM:SS\u0026rdquo; 格式表示的字符常量。 __FILE__ 这会包含当前文件名，一个字符串常量。 __LINE__ 这会包含当前行号，一个十进制常量。 __STDC__ 当编译器以 ANSI 标准编译时，则定义为 1。 ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/f571508d/8c7e092d/d168457d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e预处理命令 \n    \u003cdiv id=\"预处理命令\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%a2%84%e5%a4%84%e7%90%86%e5%91%bd%e4%bb%a4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e已经多次使用过\u003ccode\u003e#include\u003c/code\u003e命令。使用库函数之前，应该用\u003ccode\u003e#include\u003c/code\u003e引入对应的头文件。这种以\u003ccode\u003e#\u003c/code\u003e号开头的命令称为预处理命令。\u003c/p\u003e","title":"5、预处理器","type":"posts"},{"content":" 会话技术 # HTTP是一种无状态协议，每当用户发出请求时，服务器就会做出响应，客户端与服务器之间的联系是离散的、非连续的，服务器无法知道连接是否来自于同一个客户端。会话技术就是为了解决这个问题，通俗来讲，为了证明你就是你\n客户端会话技术：Cookie\n服务器端会话技术：Session\nCookie # Cookie 由服务器创建，由客户端存储 Cookie由服务端响应给客户端，在Cookie的有效生命周期中，浏览器在访问同一服务器（同一域名，不区分端口号）的所有资源的时候，会携带Cookie 如果cookie的名字相同的话，虽然可以写到浏览器，但是到了浏览器就值就被覆盖了 一个服务器可以创建多个Cookie ，所以客户端可以有多个Cookie的域名属性指向同一服务器 每个Cookie所存放的的数据不可以超过 4 KB 每个域名创建的Cookie不得超过20个 浏览器用户可以设定不使用Cookie 常用方法 # // 创建Cookie对象 Cookie cookie = new Cookie(\u0026#34;name\u0026#34;, \u0026#34;tom\u0026#34;); // 添加Cookie response.addCookie(cookie); // 获取Cookie Cookie[] cookies = request.getCookies(); // 遍历容器 for (Cookie cookie : cookies) { System.out.println(cookie.getName() + \u0026#34;=\u0026#34; + cookie.getValue()); } Cookie对象的生命周期 # 会话跟踪的发起端是服务器\n默认情况下浏览器关闭就销毁，浏览器获得服务器响应后获得Cookie，只要不关闭就存在\n设置Cookie对象生命时长 # cookie.setMaxAge(seconds); 方法参数 # 负数：默认，如果为负数的话，浏览器关闭，cookie就消失了，因为cookie存储在浏览器的缓存中 正数：以秒为单位，不管浏览器关闭不关闭，只看时间有没有到，时间到了，就消失了，消失前cookie存储在本地磁盘 0：当cookie从服务器写到浏览器的时候，马上消失，也可以用来销毁已经存在的cookie cookie的携带范围 # 1、默认情况下，访问当前项目下（同一域名）的任何资源，都会携带cookie\n2、设置携带范围\ncookie.setPath(String path); //只有访问这一个资源的时候，才会携带cookie setPath(\u0026#34;具体的资源路径\u0026#34;) //只要是该服务器下所部署的项目，访问资源的时候都可以携带cookie setPath(\u0026#34;/\u0026#34;) Session # Session是一个域对象，代表服务器和浏览器的一次会话过程，这个过程是连续的，也可以是时断时续的，一次会话过程中，可能出现多次的请求和响应 每一个Session对象都有一个Session ID，可以通过session.getId()获取 Session 的数据是存储在服务器上面的，数据无法构造但如果能够获取某个登录用户的 Session ID 通过伪造 Session ID 的请求, 可以仿造用户身份。 Session的实现是依赖于Cookie的 Tomcat下Session和Cookie的使用流程 # 客户端首次像服务器发起请求，经过服务端判断不存在key为JSESSIONID的Cookie信息 服务端创建Session并且将Session ID写入Cookie，例如：Set-Cookie:JSESSIONID=7809798C607C086C57C65C576C858787 客户端将Cookie保存到本地 客户端再次发送请求，服务端判断存在key为JSESSIONID的Cookie，取出ID，根据ID获取Session 常用方法 # //根据请求的Cookie获取Session对象，如果不存在则创建新的Session HttpSession session = request.getSession(); // 根据请求的Cookie获取Session对象，如果不存在则返回null HttpSession session = request.getSession(false); // 向session域对象中存储数据 session.setAttribute(\u0026#34;key\u0026#34;, object); // 从session域对象中获取数据 Object obj = session.getAttribute(\u0026#34;key\u0026#34;); // 从session中删除数据 session.removeAttribute(\u0026#34;key\u0026#34;); 生命周期 # 如果浏览器关闭，服务器没有关闭 # 默认浏览器关闭，此Cookie消失\n如果浏览器关闭了之后，在浏览器缓存中存储的JSESSIONID=XXXOOO的数据，就会消失，下次再去访问服务器使，就不会携带JSESSIONID的cookie\n如果想要关闭浏览器下一次访问还携带JSESSIONID的cookie的话，可以通过设置Cookie的生命周期来实现\nCookie cookie = new Cookie(\u0026#34;JSESSIONID\u0026#34;, session.getId()); cookie.setMaxAge(seconds); response.addCookie(cookie); 如果浏览器不关闭，服务器关闭的话 # 因为浏览器没有关闭，所以JSESSIONID的cookie没有消失，一直存在\n我们如果非正常关闭服务器，session就消失了，当我们再次开启服务器，通过浏览器访问，cookie会携带，JSESSIONID也有，但是却不配不到session的ID值，所以在服务器端重新创建一个session对象，并且重新生成一个session的id值\n我们如果正常关闭服务器，session也会消失，但是它会进行钝化（序列化，将session中数据存储到本地磁盘），当我们再次开启服务器，session会被活化（激活，反序列化，将本地磁盘的数据还原成java中的对象），通过浏览器访问，cookie会携带，JSESSIONID也有，ID值也匹配了，数据自然就获取到了，不会创建新的session对象\nSession的钝化和活化 # 钝化 当服务器正常关闭时，还存活着的Session（在设置时间内没有销毁）会随着服务器的关闭被以文件SESSIONS.ser的形式存储在tomcat/work目录下。 活化 当服务器再次正常开启时，服务器会找到之前的SESSIONS.ser文件，从中恢复之前保存起来的Sessio 对象，这个过程叫做Session的活化。 注意事项 想要随着Session 被钝化、活化的对象它的类必须实现Serializable接口，还有要注意的是只有在服务器正常关闭的条件下，还未超时的Session才会被钝化成文件。当Session 超时、调用invalidate()方法或者服务器在非正常情况下关闭时，Session 都不会被钝化，因此也就不存在活化。 在被钝化成SESSIONS.ser文件时，不会因为超过Session过期时间而消失，这个文件会一直存在，等到下一次服务器开启时消失。 当多个Session 被钝化时，这些被钝化的Session都被保存在同一个文件中，并不会为每个Session都建立一个文件。 Session对象的销毁方式 # 1、关闭服务器\n2、调用session.invalidate();方法\nSession的生命时长 # Session的生命时长，默认30分钟，Tomcat会开启一个后台线程每隔段时间检查Session的有效性,并删除过期的Session。\nint getMaxlnactivelnterval() ，获取Session过期时间，返回值以秒为单位。\n可以调用session.setMaxInactiveInterval(seconds)方法进行设置，参数以秒为单位，值为零或负数,则表示会话将永远不会超时。 或使用web.xml配置：参数以分钟为单位，值为零或负数,则表示会话将永远不会超时。 \u0026lt;session-config\u0026gt; \u0026lt;session-timeout\u0026gt;minutes\u0026lt;/session-timeout\u0026gt; \u0026lt;/session-config\u0026gt; ","date":"2024-01-04","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/fb007bea/e2e81182/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e会话技术 \n    \u003cdiv id=\"会话技术\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bc%9a%e8%af%9d%e6%8a%80%e6%9c%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eHTTP是一种无状态协议，每当用户发出请求时，服务器就会做出响应，客户端与服务器之间的联系是离散的、非连续的，服务器无法知道连接是否来自于同一个客户端。会话技术就是为了解决这个问题，通俗来讲，\u003cstrong\u003e为了证明你就是你\u003c/strong\u003e\u003c/p\u003e","title":"5、会话技术","type":"posts"},{"content":" 中文分词器 # 分词，一定是建立在创建“倒排索引”之前。ES将字符串划分为2种类型：Keyword和Text\nES不会针对Keyword进行分词，它只会针对Text进行分词\nES常见的中文分词器：ik_max_word，ik_smart\nik_max_word：细粒度的分词 # 举例：doc {desc: \u0026quot;中华人民共和国国歌\u0026quot;} 结果：中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、共和、人、国歌 ik_smart：粗粒度的分词 # 举例：doc {desc: \u0026quot;中华人民共和国国歌\u0026quot;}\n中华人民共和国 国歌 ES的中文分词安装 # 方式一：在线安装（不推荐） # 进入容器后，执行在线安装命令 #进入容器 docker exec -it es /bin/bash #执行命令进行安装 ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v6.8.2/elasticsearch-analysis-ik-6.8.2.zip 方式二：离线安装 # 进行下载：https://github.com/medcl/elasticsearch-analysis-ik/releases\n1、上传zip文件到服务器 # 2、重新启动elk容器，需要将插件目录映射出来 # docker run -dit --name elk \\ -p 5601:5601 \\ -p 9200:9200 \\ -p 5044:5044 \\ -v /root/elk/elk-data:/var/lib/elasticsearch \\ -v /root/elk/elasticsearch/plugins:/opt/elasticsearch/plugins \\ --privileged=true \\ sebp/elk:700 3、将zip文件解压到映射出来的/root/elk/elasticsearch/plugins目录中 # 如果没有安装unzip，需要进行安装\n#下载解压工具 yum install unzip #解压 unzip elasticsearch-analysis-ik-7.0.0.zip 4、重启elk容器 # docker restart elk docker logs -f elk 5、Kibana中测试中文分词器 # GET /_analyze { \u0026#34;text\u0026#34;: \u0026#34;提醒广大群众，如您是从黑龙江省有疫情发生地区及国内中高风险地区返（来）吉人员，或是与以上无症状感染者有接触的人员，必须主动向当地社区（村屯）报告，同时配合进行管控与核酸检测。为减少疫情传播风险，大家要不聚集、讲卫生、戴口罩、保持安全社交距离，一旦出现发热、咳嗽等急性呼吸道症状，请佩戴医用口罩及时到当地定点医疗机构发热门诊就诊\u0026#34;, \u0026#34;analyzer\u0026#34;: \u0026#34;ik_max_word\u0026#34; } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/d1774b5b/74546b43/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e中文分词器 \n    \u003cdiv id=\"中文分词器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%ad%e6%96%87%e5%88%86%e8%af%8d%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e分词，一定是建立在创建“倒排索引”之前。ES将字符串划分为2种类型：Keyword和Text\u003c/p\u003e","title":"5、Analysis分词器","type":"posts"},{"content":" 授权 # 新用户信息增改 # 1.创建用户: # 指定ip：192.118.1.1的用户登录 create user \u0026#39;用户名\u0026#39;@\u0026#39;192.118.1.1\u0026#39; identified by \u0026#39;密码\u0026#39;; # 指定ip：192.118.1.开头的用户登录 create user \u0026#39;用户名\u0026#39;@\u0026#39;192.118.1.%\u0026#39; identified by \u0026#39;密码\u0026#39;; # 指定任何ip的用户登录 create user \u0026#39;用户名\u0026#39;@\u0026#39;%\u0026#39; identified by \u0026#39;密码\u0026#39;; 2.删除用户 drop user \u0026#39;用户名\u0026#39;@\u0026#39;IP地址\u0026#39;; 3.修改用户 rename user \u0026#39;用户名\u0026#39;@\u0026#39;IP地址\u0026#39; to \u0026#39;新用户名\u0026#39;@\u0026#39;IP地址\u0026#39;; 4.修改密码 set password for \u0026#39;用户名\u0026#39;@\u0026#39;IP地址\u0026#39;=Password(\u0026#39;新密码\u0026#39;); 用户权限管理 # #查看用户权限 show grants for \u0026#39;用户名\u0026#39;@\u0026#39;IP地址\u0026#39; 1、授权 #授权用户仅对某文件有查询、插入和更新的操作 grant select,insert,update on 文件名 to \u0026#39;用户名\u0026#39;@\u0026#39;IP地址\u0026#39;; #授权所有的权限，除了grant这个命令，这个命令是root才有的。用户对db1下的t1文件有任意操作 grant all privileges on db1.t1 to \u0026#39;用户名\u0026#39;@\u0026#39;IP地址\u0026#39;; #授权用户可以对db1数据库中的所有文件执行任何操作 grant all privileges on db1.* to \u0026#39;用户名\u0026#39;@\u0026#39;IP地址\u0026#39;; #授权用户可以对所有数据库中文件有任何操作 grant all privileges on *.* to \u0026#39;用户名\u0026#39;@\u0026#39;IP地址\u0026#39;; 2、取消权限 # 取消用户对db1的t1文件的任意操作 revoke all on db1.t1 from \u0026#39;用户名\u0026#39;@\u0026#39;IP地址\u0026#39;; # 取消来自远程服务器的mjj用户对数据库db1的所有表的所有权限 revoke all on db1.* from \u0026#39;用户名\u0026#39;@\u0026#39;IP地址\u0026#39;; # 取消来自远程服务器的mjj用户所有数据库的所有的表的权限 revoke all privileges on *.* from \u0026#39;用户名\u0026#39;@\u0026#39;IP地址\u0026#39;; ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/a6ece75f/8b824d9b/5d290da9/195ee0b2/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e授权 \n    \u003cdiv id=\"授权\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%88%e6%9d%83\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e新用户信息增改 \n    \u003cdiv id=\"新用户信息增改\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b0%e7%94%a8%e6%88%b7%e4%bf%a1%e6%81%af%e5%a2%9e%e6%94%b9\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"err\"\u003e创建用户\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e指定\u003c/span\u003e\u003cspan class=\"n\"\u003eip\u003c/span\u003e\u003cspan class=\"err\"\u003e：\u003c/span\u003e\u003cspan class=\"mi\"\u003e192\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"mi\"\u003e118\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"err\"\u003e的用户登录\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ecreate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003euser\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;用户名\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;192.118.1.1\u0026#39;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eidentified\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eby\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;密码\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e指定\u003c/span\u003e\u003cspan class=\"n\"\u003eip\u003c/span\u003e\u003cspan class=\"err\"\u003e：\u003c/span\u003e\u003cspan class=\"mi\"\u003e192\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"mi\"\u003e118\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"err\"\u003e开头的用户登录\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ecreate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003euser\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;用户名\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;192.118.1.%\u0026#39;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eidentified\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eby\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;密码\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e指定任何\u003c/span\u003e\u003cspan class=\"n\"\u003eip的用户登录\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ecreate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003euser\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;用户名\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;%\u0026#39;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eidentified\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eby\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;密码\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"err\"\u003e删除用户\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003edrop\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003euser\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;用户名\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;IP地址\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"err\"\u003e修改用户\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003erename\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003euser\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;用户名\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;IP地址\u0026#39;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eto\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;新用户名\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;IP地址\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"err\"\u003e修改密码\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eset\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epassword\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;用户名\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;IP地址\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003ePassword\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;新密码\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e用户权限管理 \n    \u003cdiv id=\"用户权限管理\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%94%a8%e6%88%b7%e6%9d%83%e9%99%90%e7%ae%a1%e7%90%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e查看用户权限\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eshow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003egrants\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;用户名\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;IP地址\u0026#39;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"err\"\u003e、授权\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e授权用户仅对某文件有查询、插入和更新的操作\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003egrant\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eselect\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"k\"\u003einsert\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"k\"\u003eupdate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eon\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e文件名\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eto\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;用户名\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;IP地址\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e授权所有的权限，除了\u003c/span\u003e\u003cspan class=\"n\"\u003egrant这个命令\u003c/span\u003e\u003cspan class=\"err\"\u003e，这个命令是\u003c/span\u003e\u003cspan class=\"n\"\u003eroot才有的\u003c/span\u003e\u003cspan class=\"err\"\u003e。用户对\u003c/span\u003e\u003cspan class=\"n\"\u003edb1下的t1文件有任意操作\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003egrant\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eall\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eprivileges\u003c/span\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"k\"\u003eon\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edb1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003et1\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eto\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;用户名\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;IP地址\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e授权用户可以对\u003c/span\u003e\u003cspan class=\"n\"\u003edb1数据库中的所有文件执行任何操作\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003egrant\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eall\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eprivileges\u003c/span\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"k\"\u003eon\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edb1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eto\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;用户名\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;IP地址\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e授权用户可以对所有数据库中文件有任何操作\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003egrant\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eall\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eprivileges\u003c/span\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"k\"\u003eon\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"k\"\u003eto\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;用户名\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;IP地址\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"err\"\u003e、取消权限\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e取消用户对\u003c/span\u003e\u003cspan class=\"n\"\u003edb1的t1文件的任意操作\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003erevoke\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eall\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eon\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edb1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003et1\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003efrom\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;用户名\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;IP地址\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e取消来自远程服务器的\u003c/span\u003e\u003cspan class=\"n\"\u003emjj用户对数据库db1的所有表的所有权限\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003erevoke\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eall\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eon\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edb1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003efrom\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;用户名\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;IP地址\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e取消来自远程服务器的\u003c/span\u003e\u003cspan class=\"n\"\u003emjj用户所有数据库的所有的表的权限\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003erevoke\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eall\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eprivileges\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eon\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003efrom\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;用户名\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e@\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;IP地址\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"5、DCL数据控制语句","type":"posts"},{"content":"goquery是用 Go 语言编写的一个类似于 jQuery 的库。它基于 HTML 解析库net/html和 CSS 库cascadia，提供与 jQuery 相近的接口。Go 著名的爬虫框架colly就是基于 goquery 的。\n安装 # go get -u github.com/PuerkitoBio/goquery 由于 net/html 要求使用 UTF-8 编码，goquery 也是如此。我们需要保证传给 goquery 的 HTML 源字符串是 UTF-8 编码的。现在已经很少有非UTF-8 编码的网页了。在早些时候，国内很多网站都是使用 GB2312 或 GBK 编码。如果我们遇到了非 UTF-8 编码的网页该怎么办呢？可以使用iconv-go将字符串的编码转为 UTF-8。\n使用 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/PuerkitoBio/goquery\u0026#34; \u0026#34;log\u0026#34; \u0026#34;strings\u0026#34; ) func main() { html := `\u0026lt;html\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1 id=\u0026#34;title\u0026#34;\u0026gt;Test\u0026lt;/h1\u0026gt; \u0026lt;p class=\u0026#34;content1\u0026#34;\u0026gt;t1\u0026lt;/p\u0026gt; \u0026lt;p class=\u0026#34;content2\u0026#34;\u0026gt;t2\u0026lt;/p\u0026gt; \u0026lt;p class=\u0026#34;content3\u0026#34;\u0026gt;t3\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; ` dom, err := goquery.NewDocumentFromReader(strings.NewReader(html)) if err != nil { log.Fatalln(err) } dom.Find(\u0026#34;p\u0026#34;).Each(func(i int, selection *goquery.Selection) { fmt.Println(i, selection.Text()) }) } /* 0 t1 1 t2 2 t3 */ NewDocumentFromReader() 返回了一个*Document和error。Document代表一个将要被操作的HTML文档。\nFind()是获取当前匹配元素集中每个元素的子代，参数是x选择器 ，它返回一个包含这些匹配元素的新选择对象。在例子中我们使用的是元素选择器P，它会帮我们匹配出所有的p标签 。\nEach() 是迭代器，它会循环遍历选择的节点，它的参数是一个匿名函数，匿名函数拥有2个参数，一个是元素的索引位置，还有一个就是选择的结果集匹配到的内容都在它的里面。\nText() 则是获取匹配元素集中的文本内容。\n选择器 # 上面的例子中，我们使用了元素选择器，goquery跟jquery一样都支持很多选择器，我们简单的介绍下常用的选择器：\n基于HTML Element 元素的选择器\n就是基于a,p等这些HTML的基本元素进行选择。\n使用方法 ：使用语法为 dom.Find(\u0026quot;p\u0026quot;)，匹配文档中所有的p标签。\nID 选择器 # ID选择器是我们使用最频繁的，假如我们有2个p元素，其实我们只需要其中的一个，那么我们只需要给这个标记一个唯一的id即可，这样我们就可以使用id选择器，精确定位了。\n使用方法 ：id选择器以#开头，紧跟着元素id的值，使用语法为dom.Find(\u0026quot;#title\u0026quot;) ，匹配文档中所有的 id=title的内容\n如果多个标签的ID都是title，我们可以指定某一个标签,如dom.Find(\u0026quot;p#title\u0026quot;)\nClass选择器 # 类选择跟ID选择器一样都是使用很频繁的，我们可以通过类选择器快速筛选到需要的内容。\n使用方法 ： id选择器以.开头，紧跟着元素class的值，使用语法为dom.Find(\u0026quot;.content1\u0026quot;)，匹配文档中所有的 id=title的元素。\n类选择权器跟ID选择器一样,也可以指定某一个标签dom.Find(\u0026quot;div.content1\u0026quot;)\n属性选择器 # 一个HTML元素都有自己的属性以及属性值，所以我们也可以通过属性和值筛选元素。\n使用方法 ：我们可以通过元素的属性和属性值来筛选数据，使用语法为dom.Find(\u0026quot;p[class=content1]，匹配文档中所有的 p标签的class属性是content1的元素。\n当然我们这里以class属性为例，还可以用其他属性，比如href等很多，自定义属性也是可以的。\n刚刚我们使用的是完全相等的匹配方式，属性选择器还要很多匹配方式。\n选择器 说明 Find(“div[my]“) 筛选含有my属性的div元素 Find(“div[my=zh]“) 筛选my属性为zh的div元素 Find(“div[my!=zh]“) 筛选my属性不等于zh的div元素 Find(“div[my|=zh]“) 筛选my属性为zh或者zh-开头的div元素 Find(“div[my*=zh]“) 筛选my属性包含zh这个字符串的div元素 Find(“div[my~=zh]“) 筛选my属性包含zh这个单词的div元素，单词以空格分开的 Find(“div[my$=zh]“) 筛选my属性以zh结尾的div元素，区分大小写 Find(“div[my^=zh]“) 筛选my属性以zh开头的div元素，区分大小写 parent \u0026gt; child选择器 # 筛选出某个元素下的子元素。\n使用方法：使用\u0026gt;符号连接，使用语法 dom.Find(\u0026quot;div\u0026gt;p\u0026quot;) ， 筛选div标签下的p标签\nelement + next 相邻选择器 # 如果要筛选的元素没有规律，但是该元素的上一个元素有规律，我们就可以使用这种下一个相邻选择器来进行选择。\n\u0026lt;div\u0026gt; \u0026lt;p my=\u0026#34;a\u0026#34;\u0026gt;a\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;b\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;c\u0026lt;/p\u0026gt; \u0026lt;div\u0026gt; 我想筛选出b所在的标签\n使用方法：dom.Find(\u0026quot;p[my=a]+p\u0026quot;)筛选出p标签属性my的值为a的相邻p标签。\nelement~next 兄弟选择器 # 有时候我们需要筛选同一父元素下，不相邻的标签，可以使用兄弟选择器\n比如我想筛选出 b 和c 所在标签\n使用方法：dom.Find(\u0026quot;p[my=a]~p\u0026quot;)，筛选出p标签属性my的值为a的兄弟p标签。\n常用方法 # // 类似函数的位置操作 Find(selection) *Selection //根据选择器查找节点集 Eq(index int) *Selection //根据索引获取某个节点集 First() *Selection //获取第一个子节点集 Last() *Selection //获取最后一个子节点集 Next() *Selection //获取下一个兄弟节点集 NextAll() *Selection //获取后面所有兄弟节点集 Prev() *Selection //前一个兄弟节点集 Get(index int) *html.Node //根据索引获取一个节点 Index() int //返回选择对象中第一个元素的位置 Slice(start, end int) *Selection //根据起始位置获取子节点集 // 循环遍历选择的节点 Each(f func(int, *Selection)) *Selection //遍历 EachWithBreak(f func(int, *Selection) bool) *Selection //可中断遍历 Map(f func(int, *Selection) string) (result []string) //返回字符串数组 // 检测或获取节点属性值 Attr(), RemoveAttr(), SetAttr() //获取，移除，设置属性的值 AddClass(), HasClass(), RemoveClass(), ToggleClass() Html() //获取该节点的html Length() //返回该Selection的元素个数 Text() //获取该节点的文本值 在文档树之间来回跳转（常用的查找节点方法） Children() //返回selection中各个节点下的孩子节点 Contents() //获取当前节点下的所有节点 Find() //查找获取当前匹配的元素 Next() //下一个元素 Prev() //上一个元素 ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/29ede47b/303d63c9/","section":"文章","summary":"\u003cp\u003egoquery是用 Go 语言编写的一个类似于 jQuery 的库。它基于 HTML 解析库net/html和 CSS 库cascadia，提供与 jQuery 相近的接口。Go 著名的爬虫框架colly就是基于 goquery 的。\u003c/p\u003e","title":"5、goquery","type":"posts"},{"content":" Mybatis二级缓存存在的问题 # 1、Mybatis自带的二级缓存是对当前容器而言的，存储在一个map集合对象中，如果容器重启，那么会导致缓存丢失\n2、Mybatis自带的二级缓存不支持分布式以及集群\nPerpetualCache # 这个类就是mybatis二级缓存使用的类\n//对应表的mapper标签 \u0026lt;mapper namespace=\u0026#34;top.ygang.mybatiscache.mapper.BookMapper\u0026#34; \u0026gt; //PerpetualCache就是Mybatis的二级缓存的源码 //自带的二级缓存就是存在这个map集合中 private final Map\u0026lt;Object, Object\u0026gt; cache = new HashMap(); //当使用二级缓存时，这个对象创建，会将namespace的值注入 public PerpetualCache(String id) { this.id = id; //这个id就是你的namespace的命名空间 } public void putObject(Object key, Object value) { this.cache.put(key, value); //key就是你当前的SQL和SQL的参数 value就是当前SQL执行后的结果 } public void clear() { this.cache.clear(); } //当你在操作表执行insert update delete的时候，都会引发clear方法的调用，把map清空了。 //你做delete update insert的时候，会清空当前命名空间下的所有缓存。为了避免脏读。 使用Redis作为Mybatis二级缓存容器 # 优点 # 可以实现分布式，以及集群，解决了mybatis二级缓存在启动容器时丢失\n实现方式 # 1、使用RedisTempalte（推荐） # RedisTempalte是Spring容器已经给我们提供好的一个类。受Spring容器的管理。对象也是Spring生的。不用来管Redis的连接和关闭。因为他采用了AOP的机制。\n1）添加依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-data-redis\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.2.6.RELEASE\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 2）配置redis # spring: redis: host: localhost port: 6379 3）写好dao以及entity，并在mapper.xml文件中开启二级缓存，type自定缓存 # entity\n@Data @NoArgsConstructor @AllArgsConstructor @ToString //实体类必须实现序列化接口 public class Student implements Serializable { private Integer sno; private String sname; private String ssex; private Integer sage; } dao\npublic interface StudentMapper { public Student findBySno(Integer sno); } \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; ?\u0026gt; \u0026lt;!DOCTYPE mapper PUBLIC \u0026#34;-//mybatis.org//DTD Mapper 3.0//EN\u0026#34; \u0026#34;http://mybatis.org/dtd/mybatis-3-mapper.dtd\u0026#34;\u0026gt; \u0026lt;mapper namespace=\u0026#34;top.ygang.cache0625.dao.StudentMapper\u0026#34;\u0026gt; \u0026lt;!--开启二级缓存，type属性为自定义缓存类的全类名--\u0026gt; \u0026lt;cache type=\u0026#34;top.ygang.cache0625.cache.MyCache\u0026#34;\u0026gt;\u0026lt;/cache\u0026gt; \u0026lt;resultMap id=\u0026#34;findResult\u0026#34; type=\u0026#34;top.ygang.cache0625.entity.Student\u0026#34;\u0026gt; \u0026lt;result property=\u0026#34;sno\u0026#34; column=\u0026#34;sno\u0026#34;\u0026gt;\u0026lt;/result\u0026gt; \u0026lt;result property=\u0026#34;sname\u0026#34; column=\u0026#34;sname\u0026#34;\u0026gt;\u0026lt;/result\u0026gt; \u0026lt;result property=\u0026#34;ssex\u0026#34; column=\u0026#34;ssex\u0026#34;\u0026gt;\u0026lt;/result\u0026gt; \u0026lt;result property=\u0026#34;sage\u0026#34; column=\u0026#34;sage\u0026#34;\u0026gt;\u0026lt;/result\u0026gt; \u0026lt;/resultMap\u0026gt; \u0026lt;select id=\u0026#34;findBySno\u0026#34; resultMap=\u0026#34;findResult\u0026#34;\u0026gt; select `sno`, `sname`, `ssex`, `sage` from `stu`.`student` where sno=#{sno} \u0026lt;/select\u0026gt; \u0026lt;/mapper\u0026gt; 4）创建一个工具类，用来获取ApplicationContext对象 # 由于自定义缓存类MybatisCache是由Mybatis实例，所以无法在里面使用@Autowired进行创建RedisTemplate实例\n注意：要保证该类比MybatisCache先被加载\n@Component //ApplicationContextAware是Spring容器提供的接口类 //这个接口类的方法setApplicationContext，每次在项目启动的时候，会自动调用 public class MyApplicationContext implements ApplicationContextAware { public static ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context = applicationContext; } } 5）创建自定义缓存类，实现Cache接口，重写相关方法 # public class MyBatisCache implements Cache { private RedisTemplate redisTemplate; private String id; private ReadWriteLock readWriteLock; public MyBatisCache(String id){ //获取namespace的值 this.id = id; //获取RedisTemplate对象 redisTemplate = (RedisTemplate) MyApplicationContext.context.getBean(\u0026#34;redisTemplate\u0026#34;); //创建读写锁 readWriteLock = new ReentrantReadWriteLock(); } @Override public String getId() { return id; } @Override public void putObject(Object key, Object value) { readWriteLock.writeLock().lock(); try { redisTemplate.opsForHash().put(id,key,value); //设置过期时间，10分钟 redisTemplate.expire(key,10L, TimeUnit.MINUTES); }catch (Exception e){ e.printStackTrace(); }finally { readWriteLock.writeLock().unlock(); } } @Override public Object getObject(Object key) { readWriteLock.readLock().lock(); Object o = null; try { o = redisTemplate.opsForHash().get(id,key); }catch (Exception e){ e.printStackTrace(); }finally { readWriteLock.readLock().unlock(); } return o; } @Override public Object removeObject(Object key) { readWriteLock.writeLock().lock(); Object o = null; try { o = redisTemplate.opsForHash().delete(id,key); }catch (Exception e){ e.printStackTrace(); }finally { readWriteLock.writeLock().unlock(); } return o; } @Override public void clear() { readWriteLock.writeLock().lock(); try { redisTemplate.delete(id); }catch (Exception e){ e.printStackTrace(); }finally { readWriteLock.writeLock().unlock(); } } @Override public int getSize() { return Math.toIntExact(redisTemplate.opsForHash().size(id)); } @Override public ReadWriteLock getReadWriteLock() { return this.readWriteLock; } } 6）进行测试，会将查询结果缓存在redis数据库中 # 2、利用对象流\u0026amp;字节数组流存入redis # 其余步骤与方式一相同，但不需要获取ApplicationContext对象，需要写IOUTil工具类\n1）IOUTil工具类 # public class IOUtil { public static byte[] getByte(Object o){ ByteArrayOutputStream byteArrayOutputStream = null; ObjectOutputStream objectOutputStream = null; byte[] byteArray = null; try { byteArrayOutputStream = new ByteArrayOutputStream(); objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(o); byteArray = byteArrayOutputStream.toByteArray(); objectOutputStream.flush(); byteArrayOutputStream.flush(); objectOutputStream.close(); byteArrayOutputStream.close(); }catch (Exception e){ e.printStackTrace(); } return byteArray; } public static Object getObject(byte[] bytes){ ByteArrayInputStream byteArrayInputStream = null; ObjectInputStream objectInputStream = null; Object o = null; try { byteArrayInputStream = new ByteArrayInputStream(bytes); objectInputStream = new ObjectInputStream(byteArrayInputStream); o = objectInputStream.readObject(); objectInputStream.close(); byteArrayInputStream.close(); }catch (Exception e){ e.printStackTrace(); } return o; } } 2）自定义缓存类 # public class MyCache1 implements Cache { private Jedis jedis = new Jedis(); private String id; private byte[] idByte; public MyCache1(String id){ this.id = id; idByte = IOUtil.getByte(id); } @Override public String getId() { return id; } @Override public void putObject(Object key, Object value) { byte[] keyByte = IOUtil.getByte(key); byte[] valueByte = IOUtil.getByte(value); jedis.hset(idByte,keyByte,valueByte); } @Override public Object getObject(Object key) { byte[] keyByte = IOUtil.getByte(key); byte[] valueByte = jedis.hget(idByte, keyByte); if(valueByte == null){ return null; } Object object = IOUtil.getObject(valueByte); return object; } @Override public Object removeObject(Object key) { byte[] keyByte = IOUtil.getByte(key); Long num = jedis.hdel(idByte, keyByte); return num == 0?false:true; } @Override public void clear() { jedis.del(idByte); } @Override public int getSize() { Map\u0026lt;byte[], byte[]\u0026gt; map = jedis.hgetAll(idByte); return map.size(); } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/caadfbf9/dc2888db/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eMybatis二级缓存存在的问题 \n    \u003cdiv id=\"mybatis二级缓存存在的问题\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#mybatis%e4%ba%8c%e7%ba%a7%e7%bc%93%e5%ad%98%e5%ad%98%e5%9c%a8%e7%9a%84%e9%97%ae%e9%a2%98\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e1、Mybatis自带的二级缓存是对当前容器而言的，存储在一个map集合对象中，如果容器重启，那么会导致缓存丢失\u003c/p\u003e","title":"5、MybatisCache\u0026Redis","type":"posts"},{"content":" 分布式存储 # 1、分布式存储是一种数据存储技术，通过网络使用企业中的每台机器上的磁盘空间，并将这些分散的存储资源构成一个虚拟的存储设备，数据分散的存储在企业的各个角落。\n2、分布式存储系统，是将数据分散存储在多台独立的设备上。传统的网络存储系统采用集中的存储服务器存放所有数据，存储服务器成为系统性能的瓶颈，也是可靠性和安全性的焦点，不能满足大规模存储应用的需要。分布式网络存储系统采用可扩展的系统结构，利用多台存储服务器分担存储负荷，利用位置服务器定位存储信息，它不但提高了系统的可靠性、可用性和存取效率，还易于扩展。\nMinIO Quickstart Guide # MinIO 是在 GNU Affero 通用公共许可证 v3.0 下发布的高性能对象存储。 它是与 Amazon S3 云存储服务兼容的 API。 使用 MinIO 为机器学习、分析和应用程序数据工作负载构建高性能基础架构。\n对于 Kubernetes 环境，请使用 MinIO Kubernetes Operator。\n对象存储实现 # 一、系统搭建 # 1、Linux # 使用以下命令在运行 64 位 Intel/AMD 架构的 Linux 主机上运行独立的 MinIO 服务器。将/data 替换为您希望 MinIO 存储数据的驱动器或目录的路径。\nwget http://dl.minio.org.cn/server/minio/release/linux-amd64/minio chmod +x minio ./minio server /data 将/data 替换为您希望 MinIO 存储数据的驱动器或目录的路径。\n2、window # 要在 64 位 Windows 主机上运行 MinIO，请从以下 URL 下载 MinIO 可执行文件：\nhttp://dl.minio.org.cn/server/minio/release/windows-amd64/minio.exe 使用以下命令在 Windows 主机上运行独立的 MinIO 服务器。 将“D:\\”替换为您希望 MinIO 存储数据的驱动器或目录的路径。 您必须将终端或 powershell 目录更改为 minio.exe 可执行文件的位置，或将该目录的路径添加到系统 $PATH 中\nminio.exe server D:\\ MinIO 部署开始使用默认的 root 凭据 minioadmin:minioadmin。您可以使用 MinIO 控制台测试部署，这是一个内置在 MinIO 服务器中的基于 Web 的嵌入式对象浏览器。将主机上运行的 Web 浏览器指向 http://127.0.0.1:9000 并使用 root 凭据登录。您可以使用浏览器来创建桶、上传对象以及浏览 MinIO 服务器的内容。\n二、java开发 # 中文参考API：http://docs.minio.org.cn/docs/\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;io.minio\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;minio\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;8.2.1\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 需要有存储服务的三个参数才能连接到该服务\n参数 说明 Endpoint 对象存储服务的URL Access Key Access key就像用户ID，可以唯一标识你的账户。 Secret Key Secret key是你账户的密码。 MinIO和springboot整合 # 1、配置文件 # #minio配置 minio: url: http://localhost:9000/ username: minioadmin password: minioadmin 2、工具类 # @Component @Slf4j public class MinioUtil { @Value(\u0026#34;${minio.url}\u0026#34;) private String url; @Value(\u0026#34;${minio.username}\u0026#34;) private String username; @Value(\u0026#34;${minio.password}\u0026#34;) private String password; private MinioClient minioClient; @Bean private void init(){ minioClient = MinioClient.builder().endpoint(url).credentials(username,password).build(); log.info(\u0026#34;###### 初始化minio客户端完成，server：\u0026#34; + url + \u0026#34; ######\u0026#34;); } /** * 查看桶是否已经存在 * @param bucketName * @return */ public boolean bucketExist(String bucketName){ try { BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder().bucket(bucketName).build(); return minioClient.bucketExists(bucketExistsArgs); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 创建桶 * @param bucketName */ public void createBucket(String bucketName){ try { MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(bucketName).build(); minioClient.makeBucket(makeBucketArgs); }catch (Exception e){ e.printStackTrace(); } } /** * 上传文件，使用上传文件的文件名 * @param multipartFile * @param bucketName * @param path 文件在桶中的路径，开头不要加/，末尾加/ */ public String uploadFile(MultipartFile multipartFile, String bucketName, String path){ try { String objectFile = path + multipartFile.getOriginalFilename(); PutObjectArgs putObjectArgs = PutObjectArgs .builder() .bucket(bucketName) .stream(multipartFile.getInputStream(), multipartFile.getSize(), -1) .object(objectFile) .contentType(multipartFile.getContentType()) .build(); minioClient.putObject(putObjectArgs); return \u0026#34;/\u0026#34; + bucketName + \u0026#34;/\u0026#34; + objectFile; }catch (Exception e){ e.printStackTrace(); return null; } } /** * 上传文件，使用自定义的文件名 * @param multipartFile * @param bucketName * @param path 文件在桶中的路径，开头不要加/，末尾加/ * @param fileName 自定义文件名，不用写后缀 */ public String uploadFile(MultipartFile multipartFile, String bucketName, String path ,String fileName){ try { String originalFilename = multipartFile.getOriginalFilename(); String fileTyle = originalFilename.substring(originalFilename.lastIndexOf(\u0026#34;.\u0026#34;),originalFilename.length()); String objectFile = path + fileName + fileTyle; PutObjectArgs putObjectArgs = PutObjectArgs .builder() .bucket(bucketName) .stream(multipartFile.getInputStream(), multipartFile.getSize(), -1) .object(objectFile) .contentType(multipartFile.getContentType()) .build(); minioClient.putObject(putObjectArgs); return \u0026#34;/\u0026#34; + bucketName + \u0026#34;/\u0026#34; + objectFile; }catch (Exception e){ e.printStackTrace(); return null; } } /** * 获取上传文件的访问url * @param bucketName * @param fileName * @return */ public String getFileUrl(String bucketName, String fileName) { try { GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs. builder() .method(Method.GET) .bucket(bucketName) .object(fileName) .build(); minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs); }catch (Exception e) { e.printStackTrace(); } return null; } /** * 删除文件 * @param buketName * @param objectName 此处为文件在桶中的全路径名，开头不要加/ */ public void removeFile(String buketName, String objectName){ try { RemoveObjectArgs removeObjectArgs = RemoveObjectArgs .builder() .bucket(buketName) .object(objectName) .build(); minioClient.removeObject(removeObjectArgs); }catch (Exception e){ e.printStackTrace(); } } } InputStream转MutipartFile # 由于我们工具类使用的是MultipartFile进行文件上传，所以需要有inputStream转MultipartFile\npublic class MultipartFileUtil { /** * inputStream转MultipartFile * @param inputStream * @param fileName * @return */ public static MultipartFile getMultipartFile(InputStream inputStream, String fileName) { FileItem fileItem = createFileItem(inputStream, fileName); //CommonsMultipartFile是feign对multipartFile的封装，但是要FileItem类对象 return new CommonsMultipartFile(fileItem); } /** * FileItem类对象创建 * @param inputStream * @param fileName * @return FileItem */ private static FileItem createFileItem(InputStream inputStream, String fileName) { FileItemFactory factory = new DiskFileItemFactory(16, null); String textFieldName = \u0026#34;file\u0026#34;; FileItem item = factory.createItem(textFieldName, MediaType.MULTIPART_FORM_DATA_VALUE, true, fileName); int bytesRead = 0; byte[] buffer = new byte[8192]; OutputStream os = null; //使用输出流输出输入流的字节 try { os = item.getOutputStream(); while ((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) { os.write(buffer, 0, bytesRead); } inputStream.close(); } catch (Exception e) { throw new IllegalArgumentException(\u0026#34;文件上传失败\u0026#34;); } finally { if (os != null) { try { os.close(); } catch (Exception e) { e.printStackTrace(); } } if (inputStream != null) { try { inputStream.close(); } catch (Exception e) { e.printStackTrace(); } } } return item; } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/e7266f96/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e分布式存储 \n    \u003cdiv id=\"分布式存储\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%86%e5%b8%83%e5%bc%8f%e5%ad%98%e5%82%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e1、分布式存储是一种数据存储技术，通过网络使用企业中的每台机器上的磁盘空间，并将这些分散的存储资源构成一个虚拟的存储设备，数据分散的存储在企业的各个角落。\u003c/p\u003e","title":"5、分布式存储Minio","type":"posts"},{"content":" SpringMVC的拦截器 # SpringMVC的拦截器，类似于Servlet中的过滤器Filter，都是用来对处理器进行预处理和后处理。开发者可以通过拦截器自定义一些功能。区别在于过滤器可以过滤所有请求（动态请求，静态请求），但是拦截器只能拦截动态请求（用于控制层）\n拦截器跟AOP一样，底层都是动态代理模式\n拦截器常见的使用的场景 # 权限检查：例如用户登陆权限，登陆验证拦截 日志记录：可以记录请求信息的日治 性能监控：可以用来计算请求处理前后的时间查来计算请求响应完成所消耗的时间 2种实现方式 # 实现HandlerInterceptor接口 # org.springframework.web.servlet.HandlerInterceptor\n实现HandlerInterceptor接口\npublic class ImpInterceptor implements HandlerInterceptor{ /** * 收到请求后，在执行controller方法之前 * return：true代表放行，fasle代表不需要放行 * arg2：被拦截的控制器对象（当前的controller对象） */ @Override public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception { System.out.println(\u0026#34;执行controller方法前执行，下一个责任链对象是：\u0026#34; + arg2); return true; } /** * controller方法执行完毕，响应之前 * arg2:当前的控制器对象（controller对象） * arg3:当前执行的controller方法中的ModelAndView对象，可以获得要转发的地址和传递的参数 */ @Override public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)throws Exception { System.out.println(\u0026#34;执行完controller方法后，响应之前执行，当前责任链对象是：\u0026#34; + arg2); } /** * 响应之后 * arg2:当前的控制器对象（controller对象） * arg3:当前controller方法中发生的异常对象 */ @Override public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)throws Exception { System.out.println(\u0026#34;执行完controller方法后，响应之后执行，当前责任链对象是：\u0026#34; + arg2 + \u0026#34;,当前的异常对象是：\u0026#34; + arg3); } } 继承HandlerInterceptorAdapter（推荐） # org.springframework.web.servlet.handler.HandlerInterceptorAdapter\n是HandlerInterceptor的实现类，所以，此方式，可以按照也无需求进行选择方法，不需要三个方法都写\n继承HandlerInterceptorAdapter\npublic class ExetInterceptor extends HandlerInterceptorAdapter{ /** * 收到请求后，在执行controller方法前执行该方法 * return：true代表放行，执行下一个责任链对象的方法（controller方法），fasle代表不需要放行 * arg2：下一个责任链对象（当前的controller对象） */ @Override public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception { System.out.println(\u0026#34;执行controller方法前执行，下一个责任链对象是：\u0026#34; + arg2); return true; } /** * arg2:当前的责任链对象（controller对象） * arg3:当前执行的controller方法中的ModelAndView对象，可以获得要转发的地址和传递的参数 */ @Override public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)throws Exception { System.out.println(\u0026#34;执行完controller方法后，响应之前执行，当前责任链对象是：\u0026#34; + arg2); } /** * arg2:当前的责任链对象（controller对象） * arg3:当前controller方法中发生的异常对象 */ @Override public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)throws Exception { System.out.println(\u0026#34;执行完controller方法后，响应之后执行，当前责任链对象是：\u0026#34; + arg2 + \u0026#34;,当前的异常对象是：\u0026#34; + arg3); } } 配置拦截器 # 基于配置文件 # 基于Spring核心配置文件，对拦截器进行配置\n\u0026lt;!--配置拦截器--\u0026gt; \u0026lt;mvc:interceptors\u0026gt; \u0026lt;!--拦截所有的请求--\u0026gt; \u0026lt;mvc:interceptor\u0026gt; \u0026lt;mvc:mapping path=\u0026#34;/**\u0026#34;/\u0026gt; \u0026lt;bean class=\u0026#34;top.ygang.interceptor.ExetInterceptor1\u0026#34;/\u0026gt; \u0026lt;/mvc:interceptor\u0026gt; \u0026lt;!--拦截sys下的所有请求--\u0026gt; \u0026lt;mvc:interceptor\u0026gt; \u0026lt;mvc:mapping path=\u0026#34;/sys/*\u0026#34;/\u0026gt; \u0026lt;bean class=\u0026#34;top.ygang.interceptor.ExetInterceptor2\u0026#34;/\u0026gt; \u0026lt;/mvc:interceptor\u0026gt; \u0026lt;!--拦截sys/login单个请求--\u0026gt; \u0026lt;mvc:interceptor\u0026gt; \u0026lt;mvc:mapping path=\u0026#34;/sys/login\u0026#34;/\u0026gt; \u0026lt;bean class=\u0026#34;top.ygang.interceptor.ExetInterceptor3\u0026#34;/\u0026gt; \u0026lt;/mvc:interceptor\u0026gt; \u0026lt;/mvc:interceptors\u0026gt; 当多个拦截器拦截同一个路径的时候，拦截顺序与配置文件中拦截器声明顺序保持一致\n基于配置类 # 在不使用Spring配置文件的情况下，基于实现WebMvcConfigurer接口的addInterceptors方法对拦截器进行配置\n@Configuration @ComponentScan(\u0026#34;top.ygang.sm_demo\u0026#34;) @EnableWebMvc public class SpringMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new ExetInterceptor()) .addPathPatterns(\u0026#34;/**\u0026#34;) .excludePathPatterns(\u0026#34;/login\u0026#34;) .order(1); } } 当多个拦截器拦截同一个路径的时候，拦截顺序与按照order()参数从小到大的允许进行排序拦截，order默认值为0\n过滤器与拦截器的区别（面试题） # 拦截器和过滤器同时使用的执行顺序：过滤前 - 拦截前 - Action处理 - 拦截后 - 过滤后\n拦截器 过滤器 只能拦截动态请求（控制层的方法） 能过滤所有的请求 由Spring MVC框架提供 由servlet api提供 原理是动态代理模式，AOP的思想 原理是回调的机制 运行在Spring容器中 运行在Tomcat容器中 实现登录拦截 # @Override public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object o) throws Exception { HttpSession session = req.getSession(); Object ob = session.getAttribute(\u0026#34;nowLogin\u0026#34;); String uri = req.getRequestURI(); if(uri.endsWith(\u0026#34;/login.do\u0026#34;)){ return true; }else{ if(ob == null){ return false; }else{ return true; } } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/617a1910/43a9be61/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eSpringMVC的拦截器 \n    \u003cdiv id=\"springmvc的拦截器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#springmvc%e7%9a%84%e6%8b%a6%e6%88%aa%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eSpringMVC的拦截器，类似于Servlet中的过滤器Filter，都是用来对处理器进行预处理和后处理。开发者可以通过拦截器自定义一些功能。区别在于过滤器可以过滤所有请求（动态请求，静态请求），但是\u003cstrong\u003e拦截器只能拦截动态请求（用于控制层）\u003c/strong\u003e\u003c/p\u003e","title":"5、拦截器","type":"posts"},{"content":" 服务雪崩 # 在微服务架构中通常会有多个服务层调用，基础服务的故障可能会导致级联故障，进而造成整个系统不可用的情况，这种现象被称为服务雪崩效应\n服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用，并将不可用逐渐放大的过程\n如果下图所示：A作为服务提供者，B为A的服务消费者，C和D是B的服务消费者。A不可用引起了B的不可用，并将不可用像滚雪球一样放大到C和D时，雪崩效应就形成了\n现实通常是更糟糕 当一切正常时，请求看起来是这样的 当其中有一个系统有延迟时，它可能阻塞整个用户请求 在高流量的情况下，一个后端依赖项的延迟可能导致所有服务器上的所有资源在数秒内饱和（PS：意味着后续再有请求将无法立即提供服务） Hystrix # Hystrix是NetFlex公司提供的一套用于分布式系统的延迟和容错的开源库。在分布式系统里，许多依赖不可避免的调用失败，比如超时、异常等，Hystrix能够保证在一个依赖出问题的情况下，不会导致整个服务失败，避免级联故障，以提高分布式系统的弹性\nHystrix的作用：服务降级、服务熔断、服务限流\n服务降级 为了保证“核心业务”一切正常，我们可能会让一些“非核心业务”不提供它的功能，让它服务降级，直接提醒用户：“服务器当前繁忙，请稍后再试”，不让客户端等待并立刻返回一个友好提示，fallback 哪些情况会触发降级的情况 程序运行异常（没有处理异常try-catch）、超时、服务熔断触发服务降级、线程池/信号量打满也会导致服务降级 服务熔断 类比保险丝达到最大服务访问后，直接拒绝访问，拉闸限电，然后调用服务降级的方法并返回友好提示，就是保险丝：服务的降级 -\u0026gt; 进而熔断 -\u0026gt; 恢复调用链路 服务限流 秒杀高并发等操作，严禁一窝蜂的过来拥挤， 大家排队，一秒钟N个，有序进行 一般来讲：先配置服务限流，然后再配置服务降级，进而服务熔断\n目的：都是为了保证在高并发的情况，系统永远是可用的\n熔断器的3个状态 # Closed\n关闭状态（断路器关闭），所有请求都正常访问。代理类维护了最近调用失败的次数，如果某次调用失败，则使失败次数加1。如果最近失败次数超过了在给定时间内允许失败的阈值，则代理类切换到断开(Open)状态。此时代理开启了一个超时时钟，当该时钟超过了该时间，则切换到半断开（Half-Open）状态。该超时时间的设定是给了系统一次机会来修正导致调用失败的错误 OPEN\n打开状态（断路器打开），所有请求都会被降级。Hystix会对请求情况计数，当一定时间内失败请求百分比达到阈值，则触发熔断，断路器会打开。默认失败比例的阈值是50%，请求次数最少不低于20次，默认是10S Half Open\n半开状态，open状态不是永久的，打开后会进入休眠时间（默认是5S）。随后断路器会自动进入半开状态。此时会释放1次请求通过，若这个请求是健康的，则会关闭断路器，否则继续保持OPEN状态，再次进行5秒休眠计时 Hystrix使用 # RestTemplate和Hystrix整合 # 1、添加依赖 # \u0026lt;!-- 导入熔断器的启动器 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-netflix-hystrix\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、服务消费者启动类上，添加注解@EnableCircuitBreaker开启熔断器 # @SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker public class OrdersApplication { public static void main(String[] args) { SpringApplication.run(OrdersApplication.class,args); } @Bean @LoadBalanced public RestTemplate getRestTemplate(){ return new RestTemplate(); } } 3、在消费者控制层调用RestTemplate方法上（也可以是提供者被调用方法上添加），添加注解@HystrixCommand，并声明熔断降级备选方法 # @RestController @RequestMapping(\u0026#34;/orders\u0026#34;) public class OrdersController { //如果该方法或服务提供者发生异常，那么会执行熔断降级方法 @HystrixCommand(defaultFallback = \u0026#34;errorResult\u0026#34;) @GetMapping(\u0026#34;/findAllProduct\u0026#34;) public ResultVO findAllProduct(Integer num){ System.out.println(1 / num); ResultVO resultVO = restTemplate.getForObject(\u0026#34;http://star-product/product/findAllProduct\u0026#34;,ResultVO.class); return resultVO; } //熔断降级方法 public ResultVO errorResult(){ return ResultVO.fail(\u0026#34;降级处理！\u0026#34;); } } 4、观察效果 # OpenFeign和Hystrix整合（常用） # 使用前提 # 项目中需要导入feign依赖，以及在启动类上开启feign，不需要额外导入Hystrix依赖，因为OpenFeign自带\n1、消费者配置文件中打开feign中的hystrix，默认没有打开 # feign: hystrix: enabled: true 2、服务消费者启动类上，添加注解@EnableCircuitBreaker开启熔断器 # @SpringBootApplication @EnableEurekaClient @EnableFeignClients @EnableCircuitBreaker public class OrdersApplication { public static void main(String[] args) { SpringApplication.run(OrdersApplication.class,args); } } 3、声明feign接口，以及调用的提供者方法，注解声明降级处理类 # //fallback声明了用于降级处理类 @FeignClient(value = \u0026#34;star-product\u0026#34;,fallback = ProductServerFeignImpl.class) public interface ProductServerFeign { @GetMapping(\u0026#34;/product/findAllByNum/{num}\u0026#34;) ResultVO findAllByNum(@PathVariable(\u0026#34;num\u0026#34;) Integer num); } 4、在feign客户端（接口）的实现类声明降级的处理，并被spring容器管理 # 该类中重写的方法，即为对应feign接口中方法在运行出现熔断降级时，执行的方法\n//需要被spring容器管理 @Component public class ProductServerFeignImpl implements ProductServerFeign { public ResultVO findAllByNum(Integer num) { return ResultVO.fail(\u0026#34;触发降级处理！\u0026#34;); } } 5、测试 # 5.1、提供者控制层 # @RestController @RequestMapping(\u0026#34;/product\u0026#34;) public class ProductController { @Autowired private ProductService productService; //如果参数为1，则引发异常；如果参数为2，则引发超时 @GetMapping(\u0026#34;/findAllByNum/{num}\u0026#34;) public ResultVO findAllByNum(@PathVariable(\u0026#34;num\u0026#34;) Integer num) throws InterruptedException { if(num == 1){ System.out.println(1/0); } if(num == 2){ Thread.sleep(2000); } List\u0026lt;Product\u0026gt; list = productService.findAllProduct(); return ResultVO.success(\u0026#34;查询所有商品成功\u0026#34;,list); } } Hystrix配置 # 全局配置 # 在消费者的application.yml中，进行配置 # 避坑1：此时的timeoutInMilliseconds需要大于ribbon的默认读取时间（1秒）和默认连接时间（1秒），否则，ribbon没有来得及重试，就会熔断\n避坑2：如果程序的读取速度过慢，例如，自己手动设置线程睡眠超过1秒，就需要修改ribbon的默认读取时间\nhystrix: command: default: circuitBreaker: requestVolumeThreshold: 20 #10S范围内，熔断器至少接收20个请求，才会执行熔断判断逻辑，默认20个 sleepWindowInMilliseconds: 5000 #熔断以后，5S内，熔断器的状态都出于半开状态，默认5秒 errorThresholdPercentage: 50 #10S范围内，超过50%的请求出问题，服务开始熔断，默认50% execution: isolation: thread: timeoutInMilliseconds: 1000 #超过1秒钟，微服务调用将超时，默认1秒 局部配置 # 注解配置 # @HystrixCommand(fallbackMethod=\u0026#34;fallback\u0026#34;, commandProperties = { @HystrixProperty(name = \u0026#34;execution.isolation.thread.timeoutInMilliseconds\u0026#34;, value = \u0026#34;1000\u0026#34; ) } ) 针对服务配置 # 针对服务级别的话，直接配置service-id，如下：\nhystrix: command: service-id: execution: isolation: thread: timeoutInMilliseconds: 3000 Hystrix DashBoard # 微服务配置了Hystrix熔断规则，Hystrix-DashBoard可以帮我们分析熔断的情况 该技术，主要用来帮助项目经理，统计/分析熔断数据 创建DashBoard的微服务 # 1、导入依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-actuator\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-netflix-hystrix-dashboard\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、编写application.yml配置文件 # server: port: 7070 spring: application: name: hystrix-dashBoard 3、编写启动类，添加注解@EnableHystrixDashboard # @SpringBootApplication @EnableHystrixDashboard public class DashBoardApplication { public static void main(String[] args) { SpringApplication.run(DashBoardApplication.class,args); } } 4、启动，访问http://localhost:7070/hystrix # DashBoard监控微服务 # 如果是OpenFeign和Hystrix整合，没有加入Hystrix依赖的话，那么需要在该微服务上，添加依赖\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-netflix-hystrix\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 并且在该项目的启动类上，添加@EnableCircuitBreaker\n1、在需要监控的微服务中，添加依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-actuator\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、修改启动类，添加Servlet的支持 # Spring Cloud使用G及其之前的版本不用写配置类,升级到H以后还有写个配置信息 在启动类添加下面的代码 /** * 此配置是为了服务监控而配置，与服务器容器本身无关，springcloud升级后的坑 * ServletRegistrationBean因为springboot的默认路径不是/hystrix.stream * 只要在自己的项目里配置下面的servlet就可以了 */ @Bean public ServletRegistrationBean getServlet() { HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet); registrationBean.setLoadOnStartup(1); registrationBean.addUrlMappings(\u0026#34;/hystrix.stream\u0026#34;); registrationBean.setName(\u0026#34;HystrixMetricsStreamServlet\u0026#34;); return registrationBean; } 3、在DashBoard微服务的application.yml中，添加配置 # hystrix: dashboard: proxy-stream-allow-list: \u0026#34;localhost\u0026#34; 4、使用被监控微服务的地址+/hystrix.stream，可以查看效果 # 例如：http://localhost:8090/hystrix.stream\n可以查看出，在被监控微服务调用时，在向dashboard发送消息\n测试效果 # 监控中各项图标的意义 # 编号 解释 1 圆点：微服务的健康状态，有绿色，黄色，橙色，红色，健康状态依次降低 2 线条：流量变化 3 请求的方法 4 成功请求（绿色） 5 短路请求（蓝色） 6 坏请求（青色） 7 超时请求（黄色） 8 被拒绝的请求（紫色） 9 失败请求（红色） 10 最近10秒钟内请求错误的百分比 11 请求频率 12 熔断器状态 13 数据延迟统计 14 线程池 Sentinel（Alibaba） # 客户端弹性模式 # 客户端弹性模式的重点是，当远程服务表现不佳或发生错误的时候，保护调用该远程服务的客户端，让该客户端能够“快速失败”，而不消耗诸如数据库连接和线程池之类的宝贵资源，从而避免客户端崩溃。常见的客户端弹性模式有：隔离、超时、限流、熔断、降级。\n隔离 将系统按照一定的原则划分为若干个服务模块，各个模块之间相对独立。当有故障发生时，能将问题隔离在某个模块内部，而不扩散风险，不波及其他模块，不影响整体的系统服务。常见的隔离方式有：线程池隔离。 超时 在上游服务调用下游服务的时候，设置一个最大响应时间，如果调用远程服务所花费的时间超过这个时间，就断开请求，释放掉线程。 限流QOS 限流就是限制系统的输入和输出流量，以达到保护系统的目的。为了保证系统的稳定运行，一旦达到需要限制的阈值，就需要限制流量。 熔断 在互联网系统中，当下游服务因访问压力过大而导致响应变慢或失败时，上游服务为了保护整体系统的可用性，可以暂时切断对下游服务的调用。这种局部牺牲，保全整体的措施就叫做熔断。 降级 降级就是为服务提供一个后备方案，一旦下游服务无法正常调用，就是用后备方案 Sentinel安装与配置 # 1、下载并运行sentinel # 1.1、下载 # https://github.com/alibaba/Sentinel/releases\n1.2、运行 # 把sentinel拷贝项目同级目录，在cmd中运行sentinel项目\n账号密码默认为sentinel\njava -Dserver.port=9090 -Dcsp.sentinel.dashboard.server=localhost:9090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.1.jar 2、服务添加依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-alibaba-sentinel\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 3、修改服务配置文件 # spring: cloud: sentinel: transport: port: 9999 # 与控制台交流的端口，随意指定一个未使用的端口即可 dashboard: localhost:9090 # 指定控制台服务的地址 4、启动nacos、被监听的服务、sentinel控制台 # 通过localhost:9090访问sentinel控制台\n需要访问上游服务的控制层方法，才可以在sentinel控制台中显示\nSentinel应用 # 一、流量控制 # 1、QPS每秒访问数 # 可以控制指定资源路径的每秒访问数\n2、线程 # 可以控制指定资源路径的并发线程数\n3、流控的模式 # 直接：对接口的访问达到限流条件时，开启限流。（默认值）\n关联：当关联的资源达到限流条件时，开启限流。\n例如，在资源路径test1中设置关联test2，如果test2没有超过自身的阈值，那么test1可以一直访问，但是，如果test2超过了自身的阈值，那么test1无法访问 链路：当从某个接口过来的资源达到限流条件时，开启限流。\n对于同一service资源，被控制层的两个方法调用时，可以通过在该service方法上添加@SentinelResource(\u0026ldquo;资源名称\u0026rdquo;)的注解，并且，在簇点链路中设置该资源流控为链路，来控制该资源限流\n链路流控时需要添加配置和代码\nspring: application: name: star-orders cloud: nacos: config: server-addr: localhost:8848 file-extension: yml sentinel: transport: port: 9999 # 与控制台交流的端口，随意指定一个未使用的端口即可 dashboard: localhost:8080 # 指定控制台服务的地址 filter: enabled: false # 关闭sentinel的CommonFilter实例化 @Configuration public class RootConfig { @Bean public FilterRegistrationBean sentinelFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new CommonFilter()); registration.addUrlPatterns(\u0026#34;/*\u0026#34;); // 入口资源关闭聚合 registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, \u0026#34;false\u0026#34;); registration.setName(\u0026#34;sentinelFilter\u0026#34;); registration.setOrder(1); return registration; } } 4、流控效果 # 快速失败：直接失败，抛出异常，不做任何额外的处理，是最简单的效果 Warm up： Warm Up（RuleConstant.CONTROL_BEHAVIOR_WARM_UP）方式，即预热/冷启动方式。当系统长期处于低水位的情况下，当流量突然增加时，直接把系统拉升到高水位可能瞬间把系统压垮。通过\u0026quot;冷启动\u0026quot;，让通过的流量缓慢增加，在一定时间内逐渐增加到阈值上限，给冷系统一个预热的时间，避免冷系统被压垮。 排队等待：让请求以均匀的速度通过，单机阈值为每秒通过数量，其余的排队等待；它同时还允许设置一个超时时间，当请求超过超时时间还未处理时，则丢弃该请求。 二、降级规则 # 1、超时进行降级 # 访问超时，会进行降级\n2、异常比例 # 3、异常数 # 在一定时间内，异常数量到达阈值则进行降级\n三、热点规则 # 热点规则可以对控制层的参数进行流量控制，如果该参数达到阈值，就会进行熔断\n1、参数访问流量的控制 # 在需要添加热点规则的控制层方法上添加注解添加@SentinelResource(\u0026ldquo;资源名称\u0026rdquo;)\n对该资源进行添加热点规则\n2、特定参数值访问流量的控制排除 # 在热点控制规则生成后，进行编辑，编辑例外的参数值\n四、授权规则 # 很多时候，我们需要根据调用来源来判断该次请求是否允许放行，这时候可以使用 Sentinel 的来源访问控制的功能。来源访问控制根据资源的请求来源（origin）限制资源是否通过：\n若配置白名单，则只有请求来源位于白名单内时才可通过 若配置黑名单，则请求来源位于黑名单时不通过，其余的请求通过 此规则需要自定义来源名称 1、在微服务中添加自定义来源处理规则 # @Component public class MyRequestOriginParser implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest httpServletRequest) { String comefrom = httpServletRequest.getParameter(\u0026#34;comefrom\u0026#34;); return comefrom; } } 2、对资源路径进行授权规则的设置 # 五、系统规则 # 系统保护规则是从应用级别的入口流量进行控制，从单台机器的总体 Load、RT（平均响应时间）、入口 QPS 、CPU使用率和线程数五个维度监控应用数据，让系统尽可能保持在最大吞吐量的情况下，还能保证系统整体的稳定性。\n系统保护规则是应用整体维度的，而不是资源维度的，并且仅对入口流量 (进入应用的流量) 生效。\nLoad（仅对 Linux/Unix-like 机器生效）：当系统 load1 （load1指1分钟之内的平均负责）超过阈值，且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。\nRT：当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护，单位是毫秒。\n线程数：当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。\n入口 QPS：当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。\nCPU使用率：当单台机器上所有入口流量的 CPU使用率达到阈值即触发系统保护。\n六、规则触发后，设置统一显示结果 # 以下的类，会自动拦截所有Sentinel为了保护资源而抛出的异常，我们可以根据异常的具体类型，来向客户端响应具体的信息\n@Component public class MyUrlBlockHandler implements UrlBlockHandler { @Override public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException e) throws IOException { response.setContentType(\u0026#34;application/json;charset=utf-8\u0026#34;); ResultVO r = null; //BlockException 异常接口,包含Sentinel的五个异常 //1、 FlowException 限流异常 //2、 DegradeException 降级异常 //3、 ParamFlowException 参数限流异常 //4、 AuthorityException 授权异常 //5、 SystemBlockException 系统负载异常 if (e instanceof FlowException) { r = new ResultVO(-1, \u0026#34;接口被限流了...\u0026#34;); } else if (e instanceof DegradeException) { r = new ResultVO(-2, \u0026#34;接口被降级了...\u0026#34;); } response.getWriter().write(JSON.toJSONString(r)); } } 对于使用注解@SentinelResource声明的资源，可以使用自定义方法进行处理\n//blockHandler声明的方法需要有参数为BlockException，用于接收抛出的规则的异常 //fallbackHandler，用于其他异常 @SentinelResource(value = \u0026#34;test\u0026#34;,blockHandler = \u0026#34;paramFlowHadler\u0026#34;,fallback = \u0026#34;fallbackHandler\u0026#34;) 七、规则持久化 # sentinel中每次重启服务时，对应服务的规则都需要重新配置，一旦项目进入生产环境，规则变动不大，而且配置会有很多，这时就要使用规则持久化。\n1、编写一个持久化工具类 # public class FilePersistence implements InitFunc { @Override public void init() throws Exception { // TIPS: 如果你对这个路径不喜欢，可修改为你喜欢的路径 String ruleDir = System.getProperty(\u0026#34;user.home\u0026#34;) + \u0026#34;/sentinel/rules\u0026#34;; System.out.println(ruleDir); String flowRulePath = ruleDir + \u0026#34;/flow-rule.json\u0026#34;; String degradeRulePath = ruleDir + \u0026#34;/degrade-rule.json\u0026#34;; String systemRulePath = ruleDir + \u0026#34;/system-rule.json\u0026#34;; String authorityRulePath = ruleDir + \u0026#34;/authority-rule.json\u0026#34;; String paramFlowRulePath = ruleDir + \u0026#34;/param-flow-rule.json\u0026#34;; this.mkdirIfNotExits(ruleDir); this.createFileIfNotExits(flowRulePath); this.createFileIfNotExits(degradeRulePath); this.createFileIfNotExits(systemRulePath); this.createFileIfNotExits(authorityRulePath); this.createFileIfNotExits(paramFlowRulePath); // 流控规则 ReadableDataSource\u0026lt;String, List\u0026lt;FlowRule\u0026gt;\u0026gt; flowRuleRDS = new FileRefreshableDataSource\u0026lt;\u0026gt;( flowRulePath, flowRuleListParser ); // 将可读数据源注册至FlowRuleManager // 这样当规则文件发生变化时，就会更新规则到内存 FlowRuleManager.register2Property(flowRuleRDS.getProperty()); WritableDataSource\u0026lt;List\u0026lt;FlowRule\u0026gt;\u0026gt; flowRuleWDS = new FileWritableDataSource\u0026lt;\u0026gt;( flowRulePath, this::encodeJson ); // 将可写数据源注册至transport模块的WritableDataSourceRegistry中 // 这样收到控制台推送的规则时，Sentinel会先更新到内存，然后将规则写入到文件中 WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS); // 降级规则 ReadableDataSource\u0026lt;String, List\u0026lt;DegradeRule\u0026gt;\u0026gt; degradeRuleRDS = new FileRefreshableDataSource\u0026lt;\u0026gt;( degradeRulePath, degradeRuleListParser ); DegradeRuleManager.register2Property(degradeRuleRDS.getProperty()); WritableDataSource\u0026lt;List\u0026lt;DegradeRule\u0026gt;\u0026gt; degradeRuleWDS = new FileWritableDataSource\u0026lt;\u0026gt;( degradeRulePath, this::encodeJson ); WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS); // 系统规则 ReadableDataSource\u0026lt;String, List\u0026lt;SystemRule\u0026gt;\u0026gt; systemRuleRDS = new FileRefreshableDataSource\u0026lt;\u0026gt;( systemRulePath, systemRuleListParser ); SystemRuleManager.register2Property(systemRuleRDS.getProperty()); WritableDataSource\u0026lt;List\u0026lt;SystemRule\u0026gt;\u0026gt; systemRuleWDS = new FileWritableDataSource\u0026lt;\u0026gt;( systemRulePath, this::encodeJson ); WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS); // 授权规则 ReadableDataSource\u0026lt;String, List\u0026lt;AuthorityRule\u0026gt;\u0026gt; authorityRuleRDS = new FileRefreshableDataSource\u0026lt;\u0026gt;( authorityRulePath, authorityRuleListParser ); AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty()); WritableDataSource\u0026lt;List\u0026lt;AuthorityRule\u0026gt;\u0026gt; authorityRuleWDS = new FileWritableDataSource\u0026lt;\u0026gt;( authorityRulePath, this::encodeJson ); WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS); // 热点参数规则 ReadableDataSource\u0026lt;String, List\u0026lt;ParamFlowRule\u0026gt;\u0026gt; paramFlowRuleRDS = new FileRefreshableDataSource\u0026lt;\u0026gt;( paramFlowRulePath, paramFlowRuleListParser ); ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty()); WritableDataSource\u0026lt;List\u0026lt;ParamFlowRule\u0026gt;\u0026gt; paramFlowRuleWDS = new FileWritableDataSource\u0026lt;\u0026gt;( paramFlowRulePath, this::encodeJson ); ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS); } private Converter\u0026lt;String, List\u0026lt;FlowRule\u0026gt;\u0026gt; flowRuleListParser = source -\u0026gt; JSON.parseObject( source, new TypeReference\u0026lt;List\u0026lt;FlowRule\u0026gt;\u0026gt;() { } ); private Converter\u0026lt;String, List\u0026lt;DegradeRule\u0026gt;\u0026gt; degradeRuleListParser = source -\u0026gt; JSON.parseObject( source, new TypeReference\u0026lt;List\u0026lt;DegradeRule\u0026gt;\u0026gt;() { } ); private Converter\u0026lt;String, List\u0026lt;SystemRule\u0026gt;\u0026gt; systemRuleListParser = source -\u0026gt; JSON.parseObject( source, new TypeReference\u0026lt;List\u0026lt;SystemRule\u0026gt;\u0026gt;() { } ); private Converter\u0026lt;String, List\u0026lt;AuthorityRule\u0026gt;\u0026gt; authorityRuleListParser = source -\u0026gt; JSON.parseObject( source, new TypeReference\u0026lt;List\u0026lt;AuthorityRule\u0026gt;\u0026gt;() { } ); private Converter\u0026lt;String, List\u0026lt;ParamFlowRule\u0026gt;\u0026gt; paramFlowRuleListParser = source -\u0026gt; JSON.parseObject( source, new TypeReference\u0026lt;List\u0026lt;ParamFlowRule\u0026gt;\u0026gt;() { } ); private void mkdirIfNotExits(String filePath) throws IOException { File file = new File(filePath); if (!file.exists()) { file.mkdirs(); } } private void createFileIfNotExits(String filePath) throws IOException { File file = new File(filePath); if (!file.exists()) { file.createNewFile(); } } private \u0026lt;T\u0026gt; String encodeJson(T t) { return JSON.toJSONString(t); } } 2、在配置目录新建一个文件 # 在resouces下创建META-INF/services目录\n并在该目录下新建文件，文件名为com.alibaba.csp.sentinel.init.InitFunc，\n文件内容为配置类的全类名top.ygang.config.FilePersistence\n八、Feign和sentinel的组合 # 1、添加依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-alibaba-sentinel\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、yml配置激活feign和sentinel组合 # feign: sentinel: enabled: true 3、编写fallback类 # 有两种，分别fallback和fallbackFactory，两种都可以实现\nfallback # //自定义类实现feign接口 @Service public class ProductFallback implements IProductService { @Override public ResultVO findAllProduct() { return ResultVO.success(\u0026#34;使用ProductFallback的备用方法\u0026#34;); } } 对应的feign接口中需要添加注解信息\n@FeignClient( value = \u0026#34;star-product\u0026#34;, fallback = ProductFallback.class ) fallbackfactory # //自定义类实现FallbackFactory接口，泛型为feign接口 @Service public class ProductFallbackFactory implements FallbackFactory\u0026lt;IProductService\u0026gt; { @Override public IProductService create(Throwable throwable) { System.out.println(\u0026#34;ProductFallbackFactory.create:\u0026#34;+throwable); return new IProductService() { @Override public ResultVO findAllProduct() { return ResultVO.success(\u0026#34;这是ProductFallbackFactory中的备用方法！！\u0026#34;); } }; } } 对应的feign接口中需要添加注解信息\n@FeignClient( value = \u0026#34;star-product\u0026#34;, fallbackFactory = ProductFallbackFactory.class ) ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/fd17f7dc/c7cff96d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e服务雪崩 \n    \u003cdiv id=\"服务雪崩\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9c%8d%e5%8a%a1%e9%9b%aa%e5%b4%a9\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e在微服务架构中通常会有多个服务层调用，基础服务的故障可能会导致级联故障，进而造成整个系统不可用的情况，这种现象被称为服务雪崩效应\u003c/p\u003e","title":"5、服务熔断降级","type":"posts"},{"content":"字符串是编程时涉及到的最多的一种数据结构，对字符串进行操作的需求几乎无处不在。比如判断一个字符串是否是合法的Email地址，虽然可以编程提取@前后的子串，再分别判断是否是单词和域名，但这样做不但麻烦，而且代码难以复用。\n正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则，凡是符合规则的字符串，我们就认为它“匹配”了，否则，该字符串就是不合法的。\n所以我们判断一个字符串是否是合法的Email的方法是：\n创建一个匹配Email的正则表达式； 用该正则表达式去匹配用户的输入来判断是否合法。 语法规则 # re模块 # Python提供re模块，包含所有正则表达式的功能。\n由于Python的字符串本身也用\\转义，所以要特别注意：\ns = \u0026#39;ABC\\\\-001\u0026#39; # Python的字符串 # 对应的正则表达式字符串变成： # \u0026#39;ABC\\-001\u0026#39; 因此我们强烈建议使用Python的r前缀，就不用考虑转义的问题了\ns = r\u0026#39;ABC\\-001\u0026#39; # Python的字符串 # 对应的正则表达式字符串不变： # \u0026#39;ABC\\-001\u0026#39; match() # re.match(pattern, string[, flags])：这个方法将会从 string（我们要匹配的字符串）的开头开始，尝试匹配 pattern，一直向后匹配，如果遇到无法匹配的字符，立即返回 None，如果匹配未结束已经到达 string 的末尾，也会返回 None。两个结果均表示匹配失败，否则匹配 pattern 成功，同时匹配终止，不再对 string 向后匹配。\nimport re msg = \u0026#39;929880282@qq.com\u0026#39; #匹配邮箱 r = re.compile(r\u0026#39;[A-Za-z0-9]+@\\w+.com\u0026#39;) result = re.match(r,msg) print(type(result)) print(result) if result: print(\u0026#39;邮箱匹配成功\u0026#39;) else: print(\u0026#39;邮箱匹配失败\u0026#39;) \u0026#39;\u0026#39;\u0026#39; \u0026lt;class \u0026#39;_sre.SRE_Match\u0026#39;\u0026gt; \u0026lt;_sre.SRE_Match object; span=(0, 16), match=\u0026#39;929880282@qq.com\u0026#39;\u0026gt; 邮箱匹配成功 \u0026#39;\u0026#39;\u0026#39; search() # re.search(pattern, string[, flags])：search 方法与 match 方法极其类似，区别在于 match () 函数只检测 re 是不是在 string 的开始位置匹配，search () 会扫描整个 string 查找匹配，match（）只有在 0 位置匹配成功的话才有返回，如果不是开始位置匹配成功的话，match () 就返回 None。同样，search 方法的返回对象同样 match () 返回对象的方法和属性\nimport re #虽然邮箱的前三位无法匹配，但是从第四位开始符合pattern msg = \u0026#39;...929880282@qq.com\u0026#39; #匹配邮箱 r = re.compile(r\u0026#39;[A-Za-z0-9]+@\\w+.com\u0026#39;) result = re.search(r,msg) print(type(result)) print(result) if result: print(\u0026#39;邮箱匹配成功\u0026#39;) else: print(\u0026#39;邮箱匹配失败\u0026#39;) \u0026#39;\u0026#39;\u0026#39; \u0026lt;class \u0026#39;_sre.SRE_Match\u0026#39;\u0026gt; \u0026lt;_sre.SRE_Match object; span=(3, 19), match=\u0026#39;929880282@qq.com\u0026#39;\u0026gt; 邮箱匹配成功 \u0026#39;\u0026#39;\u0026#39; split() # re.split(pattern, string[, maxsplit])：按照能够匹配的子串将 string 分割后返回列表。maxsplit 用于指定最大分割次数，不指定将全部分割。\nimport re msg = \u0026#39;abc1efg2hig3klm\u0026#39; #按照数字进行分隔 result = re.split(r\u0026#39;\\d\u0026#39;,msg) print(type(result)) print(result) \u0026#39;\u0026#39;\u0026#39; \u0026lt;class \u0026#39;list\u0026#39;\u0026gt; [\u0026#39;abc\u0026#39;, \u0026#39;efg\u0026#39;, \u0026#39;hig\u0026#39;, \u0026#39;klm\u0026#39;] \u0026#39;\u0026#39;\u0026#39; findall() # re.findall(pattern, string[, flags])：搜索 string，以列表形式返回全部能匹配的子串\nimport re msg = \u0026#39;anis5soid8nosnsaai9nso\u0026#39; #匹配两个字母中夹一个数字 pattern = r\u0026#39;[a-z]\\d[a-z]\u0026#39; result = re.findall(pattern=pattern,string=msg) print(type(result)) print(result) \u0026#39;\u0026#39;\u0026#39; \u0026lt;class \u0026#39;list\u0026#39;\u0026gt; [\u0026#39;s5s\u0026#39;, \u0026#39;d8n\u0026#39;, \u0026#39;i9n\u0026#39;] \u0026#39;\u0026#39;\u0026#39; ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/1b37041b/339aff44/0903f3ca/","section":"文章","summary":"\u003cp\u003e字符串是编程时涉及到的最多的一种数据结构，对字符串进行操作的需求几乎无处不在。比如判断一个字符串是否是合法的Email地址，虽然可以编程提取\u003ccode\u003e@\u003c/code\u003e前后的子串，再分别判断是否是单词和域名，但这样做不但麻烦，而且代码难以复用。\u003c/p\u003e","title":"5、正则表达式","type":"posts"},{"content":" 正则表达式 # 正则表达式定义了字符串的模式。 正则表达式可以用来搜索、编辑或处理文本。 正则表达式并不仅限于某一种语言，但是在每种语言中有细微的差别。 语法 # regex包 # java.util.regex包主要包括以下三个类：\nPattern类： pattern对象是一个正则表达式的编译表示。Pattern类没有公共构造方法。要创建一个Pattern对象，你必须首先调用其公共静态方法compile()，它返回一个Pattern对象。该方法接受一个正则表达式作为它的第一个参数。 Matcher类： Matcher对象是对输入字符串进行解释和匹配操作的引擎。与Pattern类一样，Matcher也没有公共构造方法。你需要调用Pattern对象的matcher()方法来获得一个 Matcher 对象。 PatternSyntaxException异常类： PatternSyntaxException是一个非强制异常类，它表示一个正则表达式模式中的语法错误。 java.util.regex.Pattern # Pattern对象是一个正则表达式的编译表示。Pattern类没有公共构造方法。要创建一个Pattern对象，你必须首先调用其公共静态方法compile()，它返回一个Pattern对象。该方法接受一个正则表达式作为它的第一个参数。\n// 编译一个正则表达式 Pattern pattern = Pattern.compile(\u0026#34;\\\\d+\u0026#34;); java.util.regex.Matcher # Matcher对象是对输入字符串进行解释和匹配操作的引擎。与Pattern类一样，Matcher也没有公共构造方法。你需要调用Pattern对象的matcher(CharSequence s)方法来获得一个 Matcher 对象。\n// 创建字符串匹配引擎 Matcher matcher = pattern.matcher(\u0026#34;1324\u0026#34;); Matcher可以理解为匹配的迭代器， 执行上述代码后，相当于有一个匹配指针在字符序列的首位，并没有开始匹配，只有在调用matches()或find()方法后，指针才会后移进行匹配。 其中matches()方法调用后，指针会直接移动到字符序列的末尾，此时如果再调用find()方法，就无法找到匹配项了。 其中find()方法调用后，指针会向后逐个匹配子字符串，如果找到就停下。 捕获组 # 捕获组是把多个字符当成一个单独单元进行处理的方法，它通过对括号()内的字符分组来创建。\n例如表达式A(B(C))中，一共由三个分组ABC、BC、C\nPattern pattern = Pattern.compile(\u0026#34;A(B(C))\u0026#34;); String s = \u0026#34;ABC\u0026#34;; Matcher matcher = pattern.matcher(s); // 如果要调用group()查看分组结果，必须调用matches()或find()进行匹配，否则会有异常 System.out.println(matcher.matches()); System.out.println(matcher.groupCount()); // 2 System.out.println(matcher.group(0)); // ABC System.out.println(matcher.group(1)); // BC System.out.println(matcher.group(2)); // C 注意 # 如果调用group()方法查看分组结果，必须是调用了matches()方法或者find()方法，并且已经匹配到了，否则会抛出异常throw new IllegalStateException(\u0026quot;No match found\u0026quot;); 有一个特殊的group：group(0)，表示了正则表达式此时的匹配结果，例如matches()方法调用后如果匹配成功，则表示整个字符串；find()方法调用后，如果匹配到子字符串，则表示这个子字符串。并且group(0)不计入groupCount()的统计结果。 引用组 # 例如，要匹配例如123-123的字符串，-前面和后面的数字不固定，但是一定相同那么就可以用引用组\n// \\1表示引用group(1)的字符串 Pattern pattern = Pattern.compile(\u0026#34;(\\\\d+)-\\\\1\u0026#34;); String s1 = \u0026#34;1234-1234\u0026#34;; String s2 = \u0026#34;66-661\u0026#34;; Matcher matcher = pattern.matcher(s1); System.out.println(matcher.matches()); // true matcher.reset(s2); System.out.println(matcher.matches()); // false 常用方法 # matches() # 判断整个字符串是否匹配上模式，匹配上则返回true，否则false。\nfind() # 作用是发现字符串中所有与pattern匹配的子字符串，并且每次调用find()，都会指向下一个匹配到的字符串，并返回是否有匹配的子字符串\nPattern pattern = Pattern.compile(\u0026#34;\\\\d+\u0026#34;); String s = \u0026#34;123abc456\u0026#34;; Matcher matcher = pattern.matcher(s); while (matcher.find()){ System.out.println(\u0026#34;匹配的字符串开始位置：\u0026#34; + matcher.start()); System.out.println(\u0026#34;匹配的字符串结束位置：\u0026#34; + matcher.end()); System.out.println(\u0026#34;匹配的字符串：\u0026#34; + matcher.group()); } start()、end() # 如果匹配成功，start()会返回此次匹配的开始位置；end()会返回此次匹配的结束位置，即最后一个字符的下标加一。\n如果没有调用过matches()、find()或者未找到匹配的字符串，此时的start、end都是-1，如果调用start()、end()方法，会抛出异常throw new IllegalStateException(\u0026quot;No match available\u0026quot;);\nreset() # 用reset(CharSequence s)方法可以给现有的Matcher对象配上个新的CharSequence。\n如果不给参数，reset()会把Matcher设到当前字符串的开始处。\ngroup() # 有一个特殊的group：group(0)，表示了正则表达式此时的匹配结果，例如matches()方法调用后如果匹配成功，则表示整个字符串；find()方法调用后，如果匹配到子字符串，则表示这个子字符串。并且group(0)不计入groupCount()的统计结果。\n其中matcher.group() == matcher.group(0)\n常用的正则表达式 # 中文字符 # [\\u4e00-\\u9fa5]+ 身份证号 # 中国的身份证为15位或18位\n\\d{17}[\\d|X]|\\d{15} IP # ((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?) 日期 # \\d{4}[年|\\-|\\.]\\d{1,2}[月|\\-|\\.]\\d{1,2}日? 邮箱 # \\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)* URL # ((http)|(https))://([\\w-]+\\.)+[\\w-]+(/[\\w-./?%\u0026amp;=]*)?$ 密码 # // 表示至少一位数字、至少一位大写字母、至少一个非单词字符，然后8-16位非空字符 ^(?=.*[0-9])(?=.*[A-Z])(?=.*\\W)[\\S]{8,16}$ ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/409de243/0903f3ca/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e正则表达式 \n    \u003cdiv id=\"正则表达式\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%ad%a3%e5%88%99%e8%a1%a8%e8%be%be%e5%bc%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e正则表达式定义了字符串的模式。\u003c/li\u003e\n\u003cli\u003e正则表达式可以用来搜索、编辑或处理文本。\u003c/li\u003e\n\u003cli\u003e正则表达式并不仅限于某一种语言，但是在每种语言中有细微的差别。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003e语法 \n    \u003cdiv id=\"语法\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%af%ad%e6%b3%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20230525103855839\"\n    data-zoom-src=\"/posts/3ab7256e/2caf48b8/409de243/0903f3ca/image/image-20230525103855839.png\"\n    src=\"/posts/3ab7256e/2caf48b8/409de243/0903f3ca/image/image-20230525103855839.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"5、正则表达式","type":"posts"},{"content":"运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C 语言内置了丰富的运算符，并提供了以下类型的运算符：\n算术运算符 关系运算符 逻辑运算符 位运算符 赋值运算符 杂项运算符 算数运算符 # 假设变量 A 的值为 10，变量 B 的值为 20\n运算符 描述 实例 + 把两个操作数相加 A + B 将得到 30 - 从第一个操作数中减去第二个操作数 A - B 将得到 -10 * 把两个操作数相乘 A * B 将得到 200 / 分子除以分母 B / A 将得到 2 % 取模运算符，整除后的余数 B % A 将得到 0 ++ 自增运算符，整数值增加 1 A++ 将得到 11 \u0026ndash; 自减运算符，整数值减少 1 A\u0026ndash; 将得到 9 c的++和\u0026ndash;也有前后之分，和java一样\n关系运算符 # 假设变量 A 的值为 10，变量 B 的值为 20\n运算符 描述 实例 == 检查两个操作数的值是否相等，如果相等则条件为真。 (A == B) 不为真。 != 检查两个操作数的值是否相等，如果不相等则条件为真。 (A != B) 为真。 \u0026gt; 检查左操作数的值是否大于右操作数的值，如果是则条件为真。 (A \u0026gt; B) 不为真。 \u0026lt; 检查左操作数的值是否小于右操作数的值，如果是则条件为真。 (A \u0026lt; B) 为真。 \u0026gt;= 检查左操作数的值是否大于或等于右操作数的值，如果是则条件为真。 (A \u0026gt;= B) 不为真。 \u0026lt;= 检查左操作数的值是否小于或等于右操作数的值，如果是则条件为真。 (A \u0026lt;= B) 为真 逻辑运算符 # 假设变量 A 的值为 1，变量 B 的值为 0\n运算符 描述 实例 \u0026amp;\u0026amp; 称为逻辑与运算符。如果两个操作数都非零，则条件为真。 (A \u0026amp;\u0026amp; B) 为假。 || 称为逻辑或运算符。如果两个操作数中有任意一个非零，则条件为真。 (A || B) 为真。 ! 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 !(A \u0026amp;\u0026amp; B) 为真。 位运算符 # 运算符 描述 实例 \u0026amp; 按位与操作，按二进制位进行“与”运算。运算规则： 0\u0026amp;0=0; 0\u0026amp;1=0; 1\u0026amp;0=0; 1\u0026amp;1=1; (A \u0026amp; B) 将得到 12，即为 0000 1100 | 按位或运算符，按二进制位进行“或”运算。运算规则：`0 0=0; 0 ^ 异或运算符，按二进制位进行“异或”运算。运算规则：0^0=0; 0^1=1; 1^0=1; 1^1=0 (A ^ B) 将得到 49，即为 0011 0001 ~ 取反运算符，按二进制位进行“取反”运算。运算规则：~1=0; ~0=1; (~A ) 将得到 -61，即为 1100 0011，2 的补码形式，带符号的二进制数。 \u0026laquo; 二进制左移运算符。左操作数的值向左移动右操作数指定的位数（左边的二进制位丢弃，右边补0）。 A \u0026laquo; 2 将得到 240，即为 1111 0000 \u0026raquo; 二进制右移运算符。左操作数的值向右移动右操作数指定的位数（正数左补0，负数左补1，右边丢弃）。 A \u0026raquo; 2 将得到 15，即为 0000 1111 赋值运算符 # 运算符 描述 实例 = 简单的赋值运算符，把右边操作数的值赋给左边操作数 C = A + B 将把 A + B 的值赋给 C += 加且赋值运算符，把右边操作数加上左边操作数的结果赋值给左边操作数 C += A 相当于 C = C + A -= 减且赋值运算符，把左边操作数减去右边操作数的结果赋值给左边操作数 C -= A 相当于 C = C - A *= 乘且赋值运算符，把右边操作数乘以左边操作数的结果赋值给左边操作数 C *= A 相当于 C = C * A /= 除且赋值运算符，把左边操作数除以右边操作数的结果赋值给左边操作数 C /= A 相当于 C = C / A %= 求模且赋值运算符，求两个操作数的模赋值给左边操作数 C %= A 相当于 C = C % A \u0026laquo;= 左移且赋值运算符 C \u0026laquo;= 2 等同于 C = C \u0026laquo; 2 \u0026raquo;= 右移且赋值运算符 C \u0026raquo;= 2 等同于 C = C \u0026raquo; 2 \u0026amp;= 按位与且赋值运算符 C \u0026amp;= 2 等同于 C = C \u0026amp; 2 ^= 按位异或且赋值运算符 C ^= 2 等同于 C = C ^ 2 |= 按位或且赋值运算符 C |= 2 等同于 C = C | 2 杂项运算符 # 运算符 描述 实例 sizeof() 返回变量的大小。 sizeof(a) 将返回 4，其中 a 是整数。 \u0026amp; 返回变量的地址。 \u0026amp;a; 将给出变量的实际地址。 * 指向一个变量。 *a; 将指向一个变量。 ? : 条件表达式（三元） 如果条件为真 ? 则值为 X : 否则值为 Y 运算符优先级 # 类别 运算符 结合性 后缀 () [] -\u0026gt; . ++ - - 从左到右 一元 + - ! ~ ++ - - (type)* \u0026amp; sizeof 从右到左 乘除 * / % 从左到右 加减 + - 从左到右 移位 \u0026laquo; \u0026raquo; 从左到右 关系 \u0026lt; \u0026lt;= \u0026gt; \u0026gt;= 从左到右 相等 == != 从左到右 位与 AND \u0026amp; 从左到右 位异或 XOR ^ 从左到右 位或 OR | 从左到右 逻辑与 AND \u0026amp;\u0026amp; 从左到右 逻辑或 OR || 从左到右 条件 ?: 从右到左 赋值 = += -= *= /= %=\u0026raquo;= \u0026laquo;= \u0026amp;= ^= |= 从右到左 逗号 , 从左到右 ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/f571508d/fa8095d0/72c6fd50/","section":"文章","summary":"\u003cp\u003e运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C 语言内置了丰富的运算符，并提供了以下类型的运算符：\u003c/p\u003e","title":"5、运算符","type":"posts"},{"content":" 反射的基本概念 # 在Go编程中，反射（Reflection）是一项强大而复杂的特性，它允许程序在运行时检查和修改自己的结构和行为。虽然在日常编程中我们可能会尽量避免过度使用反射（因为它会带来复杂性和性能损失），但了解反射机制对于理解许多高级库和框架的工作原理至关重要。\n反射是程序在运行时访问、检查和修改其自身结构的能力。在Go中，反射主要通过reflect包提供支持，它使我们能够：\n在运行时检查变量的类型信息 在运行时检查和修改变量的值 在运行时调用函数和方法 在运行时创建新的类型和值 反射在某种程度上打破了Go的静态类型系统，为程序提供了更高的灵活性，但同时也带来了复杂性和潜在的运行时错误。\n反射三大定律 # 理解Go反射的核心是掌握Rob Pike提出的\u0026quot;反射三大法则\u0026quot;：\n从接口值到反射对象：反射是从接口值到反射对象的转换 从反射对象到接口值：反射是从反射对象到接口值的转换 要修改反射对象，其值必须可设置（可寻址） 为什么需要反射 # 在某些场景下，反射是非常有用甚至是必不可少的：\n序列化和反序列化：将结构体转换为JSON、XML等格式，或反过来 ORM（对象关系映射）：将数据库记录映射到结构体 依赖注入：在运行时动态创建和连接对象 类型安全的容器：创建能够处理任意类型的通用容器或函数 插件系统和动态加载：在运行时加载和使用组件 元编程：程序生成或修改其自身的代码 反射的缺点 # 性能损失：反射操作比直接代码慢 代码复杂性：反射代码通常更难理解和维护 安全性问题：反射可能绕过类型系统的保护，导致运行时错误 编译时检查缺失：编译器无法捕获反射相关的许多错误 reflect包 # Go的反射机制主要通过reflect包中的两个核心类型来实现：\nType：表示Go类型的接口，提供了获取类型名称、种类、方法集等信息的能力 Value：表示Go值的接口，提供了获取和设置值、调用方法等功能 这两个类型是Go反射机制的基础，几乎所有反射操作都围绕它们展开。\nType和Kind # Type表示具体的类型，而Kind表示类型的基础类别。例如：\n*User和*Admin是不同的Type，但它们的Kind都是Ptr []int和[]string是不同的Type，但它们的Kind都是Slice Go的reflect包中定义了27种基本Kind：\ntype Kind uint const ( Invalid Kind = iota Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 Uintptr Float32 Float64 Complex64 Complex128 Array Chan Func Interface Map Pointer Slice String Struct UnsafePointer ) const Ptr = Pointer Value类型 # Value类型表示一个Go值，它提供了访问和修改值的方法：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;reflect\u0026#34; ) func main() { // 通过ValueOf获取值的反射表示 var x int = 100 v := reflect.ValueOf(x) fmt.Println(\u0026#34;值:\u0026#34;, v.Interface()) // 输出: 100 fmt.Println(\u0026#34;类型:\u0026#34;, v.Type()) // 输出: int fmt.Println(\u0026#34;种类:\u0026#34;, v.Kind()) // 输出: int fmt.Println(\u0026#34;是否可寻址:\u0026#34;, v.CanAddr()) // 输出: false (传值方式) fmt.Println(\u0026#34;是否可设置:\u0026#34;, v.CanSet()) // 输出: false (传值方式) // 创建可设置的Value y := 200 pv := reflect.ValueOf(\u0026amp;y) // 获取y的地址的反射值 ev := pv.Elem() // 获取指针指向的Value fmt.Println(\u0026#34;是否可寻址:\u0026#34;, ev.CanAddr()) // 输出: true fmt.Println(\u0026#34;是否可设置:\u0026#34;, ev.CanSet()) // 输出: true // 修改值 if ev.CanSet() { ev.SetInt(300) fmt.Println(\u0026#34;修改后的y值:\u0026#34;, y) // 输出: 300 } // 结构体值 type Person struct { Name string Age int } p := Person{\u0026#34;李四\u0026#34;, 25} pv = reflect.ValueOf(\u0026amp;p) ev = pv.Elem() // 获取并修改字段值 nameField := ev.FieldByName(\u0026#34;Name\u0026#34;) if nameField.CanSet() { nameField.SetString(\u0026#34;王五\u0026#34;) } ageField := ev.FieldByName(\u0026#34;Age\u0026#34;) if ageField.CanSet() { ageField.SetInt(30) } fmt.Printf(\u0026#34;修改后的person: %+v\\n\u0026#34;, p) // 输出: {Name:王五 Age:30} } Value类型常用方法：\nInterface()：将Value转换回接口值 Type()：返回Value的类型 Kind()：返回Value的种类 CanAddr()：报告Value是否可寻址 CanSet()：报告Value是否可设置 Elem()：返回指针指向的Value Field(i int)：返回结构体的第i个字段的Value FieldByName(name string)：通过名称返回结构体字段的Value Method(i int)：返回类型的第i个方法的Value IsValid()：检查通过反射获得的反射对象是否有效。 从接口值到反射对象 # 反射的第一法则是\u0026quot;从接口值到反射对象\u0026quot;。在Go中，这通过两个基本函数实现：\nreflect.TypeOf()：返回一个reflect.Type，表示值的类型 reflect.ValueOf()：返回一个reflect.Value，包含值的实际数据 当我们传递一个值给reflect.TypeOf()或reflect.ValueOf()时，该值会被转换为一个空接口interface{}，然后反射系统从这个接口值中提取类型和值信息。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;reflect\u0026#34; ) func main() { // 基本类型 i := 42 s := \u0026#34;Hello, reflection!\u0026#34; // 获取类型信息 iType := reflect.TypeOf(i) sType := reflect.TypeOf(s) fmt.Printf(\u0026#34;Type of i: %v\\n\u0026#34;, iType) // int fmt.Printf(\u0026#34;Type of s: %v\\n\u0026#34;, sType) // string // 获取种类信息 fmt.Printf(\u0026#34;Kind of i: %v\\n\u0026#34;, iType.Kind()) // int fmt.Printf(\u0026#34;Kind of s: %v\\n\u0026#34;, sType.Kind()) // string // 获取值信息 iValue := reflect.ValueOf(i) sValue := reflect.ValueOf(s) fmt.Printf(\u0026#34;Value of i: %v\\n\u0026#34;, iValue) // 42 fmt.Printf(\u0026#34;Value of s: %v\\n\u0026#34;, sValue) // Hello, reflection! // 获取值的实际内容 fmt.Printf(\u0026#34;Value of i (int): %v\\n\u0026#34;, iValue.Int()) // 42 fmt.Printf(\u0026#34;Value of s (string): %v\\n\u0026#34;, sValue.String()) // Hello, reflection! } 从反射对象到接口值 # 反射的第二法则是\u0026quot;从反射对象到接口值\u0026quot;。我们可以使用Interface()方法将reflect.Value转换回接口值：\n这个过程是从接口值到反射对象的逆过程，将反射值转换回Go值。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;reflect\u0026#34; ) func main() { i := 42 v := reflect.ValueOf(i) // 从反射值到接口值 iface := v.Interface() // 使用类型断言获取具体类型的值 i2 := iface.(int) fmt.Println(i2) // 输出: 42 } 修改反射对象的值 # 反射的第三法则是\u0026quot;要修改反射对象，其值必须可设置（可寻址）\u0026quot;。简单来说，只有当原始值可以被修改时，其对应的反射值才能被修改。\n通过值传递给reflect.ValueOf()得到的反射值是不可设置的 通过指针传递然后使用Elem()方法得到的反射值是可设置的 在修改反射值之前，始终检查它是否可设置（使用CanSet()方法） package main import ( \u0026#34;fmt\u0026#34; \u0026#34;reflect\u0026#34; ) func main() { // 示例1: 无法修改不可设置的值 x := 42 v := reflect.ValueOf(x) // 这会导致panic // v.SetInt(21) // panic: reflect.Value.SetInt using unaddressable value // 示例2: 修改可设置的值 y := 42 p := reflect.ValueOf(\u0026amp;y) // 获取y的地址的反射值 v = p.Elem() // 获取指针指向的值 fmt.Println(\u0026#34;CanSet:\u0026#34;, v.CanSet()) // true v.SetInt(21) fmt.Println(y) // 输出: 21 } 使用反射检查类型 # 反射最常见的用途之一是检查变量类型的各个方面。特别是对于复杂的结构体类型，反射提供了强大的功能来检查其字段、方法和标签。\n检查结构体类型 # 结构体是Go中最常用的复合类型之一。通过反射，我们可以检查结构体的字段、标签和方法：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;reflect\u0026#34; ) type User struct { ID int `json:\u0026#34;id\u0026#34; validate:\u0026#34;required\u0026#34;` Name string `json:\u0026#34;name\u0026#34; validate:\u0026#34;required,min=3\u0026#34;` Email string `json:\u0026#34;email\u0026#34; validate:\u0026#34;required,email\u0026#34;` Age int `json:\u0026#34;age\u0026#34; validate:\u0026#34;gte=0,lte=130\u0026#34;` CreatedAt string `json:\u0026#34;created_at\u0026#34; validate:\u0026#34;-\u0026#34;` } func (u User) GetFullInfo() string { return fmt.Sprintf(\u0026#34;User %s (ID: %d, Email: %s)\u0026#34;, u.Name, u.ID, u.Email) } func (u *User) UpdateName(newName string) { u.Name = newName } func inspectStruct(v interface{}) { t := reflect.TypeOf(v) // 如果是指针，获取其指向的元素类型 if t.Kind() == reflect.Ptr { t = t.Elem() } // 确保是结构体 if t.Kind() != reflect.Struct { fmt.Println(\u0026#34;不是结构体类型\u0026#34;) return } fmt.Printf(\u0026#34;类型名称: %s\\n\u0026#34;, t.Name()) fmt.Printf(\u0026#34;字段数量: %d\\n\u0026#34;, t.NumField()) fmt.Println(\u0026#34;字段列表:\u0026#34;) for i := 0; i \u0026lt; t.NumField(); i++ { field := t.Field(i) fmt.Printf(\u0026#34; %d: %s (%s)\\n\u0026#34;, i, field.Name, field.Type) // 打印字段标签 if tag := field.Tag; tag != \u0026#34;\u0026#34; { fmt.Printf(\u0026#34; 标签: %s\\n\u0026#34;, tag) fmt.Printf(\u0026#34; json标签: %s\\n\u0026#34;, tag.Get(\u0026#34;json\u0026#34;)) fmt.Printf(\u0026#34; validate标签: %s\\n\u0026#34;, tag.Get(\u0026#34;validate\u0026#34;)) } } // 检查方法 fmt.Println(\u0026#34;方法列表:\u0026#34;) // 值接收者方法 vt := reflect.TypeOf(v) for i := 0; i \u0026lt; t.NumMethod(); i++ { method := t.Method(i) fmt.Printf(\u0026#34; 值接收者方法 %d: %s\\n\u0026#34;, i, method.Name) fmt.Printf(\u0026#34; 类型: %s\\n\u0026#34;, method.Type) } // 指针接收者方法 if vt.Kind() != reflect.Ptr { vt = reflect.PtrTo(t) } for i := 0; i \u0026lt; vt.NumMethod(); i++ { method := vt.Method(i) fmt.Printf(\u0026#34; 指针接收者方法 %d: %s\\n\u0026#34;, i, method.Name) fmt.Printf(\u0026#34; 类型: %s\\n\u0026#34;, method.Type) } } func main() { user := User{ ID: 1, Name: \u0026#34;张三\u0026#34;, Email: \u0026#34;zhangsan@example.com\u0026#34;, Age: 30, CreatedAt: \u0026#34;2023-01-01\u0026#34;, } inspectStruct(user) } 检查接口实现 # 反射还可以用来检查一个类型是否实现了特定接口：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;os\u0026#34; \u0026#34;reflect\u0026#34; ) // 检查类型是否实现接口 func implementsInterface(v interface{}, interfaceType reflect.Type) bool { vType := reflect.TypeOf(v) return vType.Implements(interfaceType) } // 获取接口类型 func getInterfaceType(interfaceValue interface{}) reflect.Type { return reflect.TypeOf(interfaceValue).Elem() } func main() { // 检查*os.File是否实现io.Reader接口 file, _ := os.Open(\u0026#34;example.txt\u0026#34;) defer file.Close() var reader io.Reader readerType := getInterfaceType(\u0026amp;reader) fmt.Printf(\u0026#34;*os.File是否实现io.Reader: %v\\n\u0026#34;, implementsInterface(file, readerType)) // 检查string是否实现io.Reader var s string = \u0026#34;hello\u0026#34; fmt.Printf(\u0026#34;string是否实现io.Reader: %v\\n\u0026#34;, implementsInterface(s, readerType)) } 使用反射修改值 # 除了检查类型和值外，反射还允许我们在运行时修改变量的值，只要这些值是可寻址和可设置的。\n修改基本类型值 # var x int = 100 v := reflect.ValueOf(\u0026amp;x) // 获取指针的反射值 e := v.Elem() // 获取指针指向的值 fmt.Println(\u0026#34;旧值:\u0026#34;, x) e.SetInt(200) fmt.Println(\u0026#34;新值:\u0026#34;, x) 修改结构体字段 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;reflect\u0026#34; ) type Product struct { ID int Name string Price float64 Quantity int Tags []string Metadata map[string]string } func main() { p := Product{ ID: 1, Name: \u0026#34;笔记本电脑\u0026#34;, Price: 5999.99, Quantity: 10, Tags: []string{\u0026#34;电子\u0026#34;, \u0026#34;计算机\u0026#34;}, Metadata: map[string]string{\u0026#34;brand\u0026#34;: \u0026#34;品牌A\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;银色\u0026#34;}, } fmt.Printf(\u0026#34;原始产品: %+v\\n\u0026#34;, p) // 获取可设置的反射值 v := reflect.ValueOf(\u0026amp;p).Elem() // 修改基本类型字段 if idField := v.FieldByName(\u0026#34;ID\u0026#34;); idField.IsValid() \u0026amp;\u0026amp; idField.CanSet() { idField.SetInt(2) } if nameField := v.FieldByName(\u0026#34;Name\u0026#34;); nameField.IsValid() \u0026amp;\u0026amp; nameField.CanSet() { nameField.SetString(\u0026#34;高性能服务器\u0026#34;) } if priceField := v.FieldByName(\u0026#34;Price\u0026#34;); priceField.IsValid() \u0026amp;\u0026amp; priceField.CanSet() { priceField.SetFloat(19999.99) } // 修改切片字段 if tagsField := v.FieldByName(\u0026#34;Tags\u0026#34;); tagsField.IsValid() \u0026amp;\u0026amp; tagsField.CanSet() { // 创建一个新的切片 newTags := reflect.MakeSlice(tagsField.Type(), 3, 3) newTags.Index(0).SetString(\u0026#34;服务器\u0026#34;) newTags.Index(1).SetString(\u0026#34;企业级\u0026#34;) newTags.Index(2).SetString(\u0026#34;高性能\u0026#34;) // 设置整个切片 tagsField.Set(newTags) } // 修改映射字段 if metaField := v.FieldByName(\u0026#34;Metadata\u0026#34;); metaField.IsValid() \u0026amp;\u0026amp; metaField.CanSet() { // 创建一个新的映射 newMeta := reflect.MakeMap(metaField.Type()) newMeta.SetMapIndex(reflect.ValueOf(\u0026#34;brand\u0026#34;), reflect.ValueOf(\u0026#34;品牌B\u0026#34;)) newMeta.SetMapIndex(reflect.ValueOf(\u0026#34;color\u0026#34;), reflect.ValueOf(\u0026#34;黑色\u0026#34;)) newMeta.SetMapIndex(reflect.ValueOf(\u0026#34;series\u0026#34;), reflect.ValueOf(\u0026#34;专业系列\u0026#34;)) // 设置整个映射 metaField.Set(newMeta) } fmt.Printf(\u0026#34;修改后的产品: %+v\\n\u0026#34;, p) } 创建新的值 # 反射提供了创建新值的能力，这在需要动态创建对象时非常有用：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;reflect\u0026#34; ) func main() { // 创建基本类型值 intType := reflect.TypeOf(0) intValue := reflect.New(intType).Elem() // 创建int类型的可设置Value intValue.SetInt(42) fmt.Println(\u0026#34;创建的int值:\u0026#34;, intValue.Interface()) // 创建切片 sliceType := reflect.TypeOf([]string{}) sliceValue := reflect.MakeSlice(sliceType, 3, 5) // 长度3，容量5 // 设置切片元素 sliceValue.Index(0).SetString(\u0026#34;Go\u0026#34;) sliceValue.Index(1).SetString(\u0026#34;Java\u0026#34;) sliceValue.Index(2).SetString(\u0026#34;Python\u0026#34;) fmt.Println(\u0026#34;创建的切片:\u0026#34;, sliceValue.Interface()) // 创建映射 mapType := reflect.TypeOf(map[string]int{}) mapValue := reflect.MakeMap(mapType) // 添加键值对 mapValue.SetMapIndex(reflect.ValueOf(\u0026#34;one\u0026#34;), reflect.ValueOf(1)) mapValue.SetMapIndex(reflect.ValueOf(\u0026#34;two\u0026#34;), reflect.ValueOf(2)) mapValue.SetMapIndex(reflect.ValueOf(\u0026#34;three\u0026#34;), reflect.ValueOf(3)) fmt.Println(\u0026#34;创建的映射:\u0026#34;, mapValue.Interface()) // 创建结构体 type Person struct { Name string Age int } personType := reflect.TypeOf(Person{}) personPtr := reflect.New(personType) // 创建*Person类型的Value personValue := personPtr.Elem() // 获取Person类型的Value personValue.FieldByName(\u0026#34;Name\u0026#34;).SetString(\u0026#34;张三\u0026#34;) personValue.FieldByName(\u0026#34;Age\u0026#34;).SetInt(30) fmt.Println(\u0026#34;创建的结构体:\u0026#34;, personValue.Interface()) // 将反射值转换为具体类型 person := personPtr.Interface().(*Person) fmt.Printf(\u0026#34;转换后的Person: %+v\\n\u0026#34;, *person) } 使用反射调用函数和方法 # 反射的另一个强大功能是能够在运行时动态调用函数和方法。\n调用函数 # 通过反射调用函数需要准备好函数和参数的反射值：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;reflect\u0026#34; ) func add(a, b int) int { return a + b } func concat(strs ...string) string { result := \u0026#34;\u0026#34; for _, s := range strs { result += s } return result } func divide(a, b float64) (float64, error) { if b == 0 { return 0, fmt.Errorf(\u0026#34;除数不能为零\u0026#34;) } return a / b, nil } func callFunction() { // 获取函数的反射值 addFunc := reflect.ValueOf(add) // 准备参数 args := []reflect.Value{ reflect.ValueOf(10), reflect.ValueOf(20), } // 调用函数 results := addFunc.Call(args) // 处理返回值 if len(results) \u0026gt; 0 { fmt.Println(\u0026#34;add(10, 20) =\u0026#34;, results[0].Interface()) } // 调用可变参数函数 concatFunc := reflect.ValueOf(concat) concatArgs := []reflect.Value{ reflect.ValueOf(\u0026#34;Hello\u0026#34;), reflect.ValueOf(\u0026#34;, \u0026#34;), reflect.ValueOf(\u0026#34;World\u0026#34;), reflect.ValueOf(\u0026#34;!\u0026#34;), } concatResults := concatFunc.Call(concatArgs) if len(concatResults) \u0026gt; 0 { fmt.Println(\u0026#34;concat结果:\u0026#34;, concatResults[0].Interface()) } // 调用返回多个值的函数 divideFunc := reflect.ValueOf(divide) divideArgs := []reflect.Value{ reflect.ValueOf(10.0), reflect.ValueOf(2.0), } divideResults := divideFunc.Call(divideArgs) if len(divideResults) \u0026gt; 0 { fmt.Printf(\u0026#34;除法结果: %.2f\\n\u0026#34;, divideResults[0].Interface()) // 检查错误 if len(divideResults) \u0026gt; 1 \u0026amp;\u0026amp; !divideResults[1].IsNil() { fmt.Println(\u0026#34;错误:\u0026#34;, divideResults[1].Interface()) } } } func main() { callFunction() } 调用方法 # 调用结构体的方法与调用函数类似，但需要先获取方法的反射值：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;reflect\u0026#34; ) type Calculator struct { Brand string } func (c Calculator) Add(a, b int) int { return a + b } func (c Calculator) Multiply(a, b int) int { return a * b } func (c *Calculator) SetBrand(brand string) { c.Brand = brand } func (c Calculator) GetInfo() string { return fmt.Sprintf(\u0026#34;Calculator [%s]\u0026#34;, c.Brand) } func callMethod() { // 创建计算器实例 calc := Calculator{Brand: \u0026#34;科学计算器\u0026#34;} fmt.Println(\u0026#34;原始计算器:\u0026#34;, calc) // 获取值接收者方法 addMethod := reflect.ValueOf(calc).MethodByName(\u0026#34;Add\u0026#34;) if addMethod.IsValid() { args := []reflect.Value{ reflect.ValueOf(10), reflect.ValueOf(20), } result := addMethod.Call(args) fmt.Println(\u0026#34;Add方法结果:\u0026#34;, result[0].Interface()) } // 获取指针接收者方法 - 需要传递指针的反射值 calcPtr := reflect.ValueOf(\u0026amp;calc) setBrandMethod := calcPtr.MethodByName(\u0026#34;SetBrand\u0026#34;) if setBrandMethod.IsValid() { args := []reflect.Value{ reflect.ValueOf(\u0026#34;高级科学计算器\u0026#34;), } setBrandMethod.Call(args) fmt.Println(\u0026#34;修改后的计算器:\u0026#34;, calc) } // 动态选择要调用的方法 methodName := \u0026#34;Multiply\u0026#34; method := reflect.ValueOf(calc).MethodByName(methodName) if method.IsValid() { args := []reflect.Value{ reflect.ValueOf(5), reflect.ValueOf(7), } result := method.Call(args) fmt.Printf(\u0026#34;%s方法结果: %v\\n\u0026#34;, methodName, result[0].Interface()) } else { fmt.Printf(\u0026#34;方法%s不存在\\n\u0026#34;, methodName) } } func main() { callMethod() } 反射的实际应用 # JSON序列化和反序列化 # Go标准库中的encoding/json包使用反射来实现JSON的序列化（Marshal）和反序列化（Unmarshal）。这使得任何符合特定规则的Go结构体都可以自动转换为JSON，而无需为每种结构编写专门的编码/解码代码。\npackage main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; ) type Person struct { Name string `json:\u0026#34;name\u0026#34;` Age int `json:\u0026#34;age\u0026#34;` Email string `json:\u0026#34;email,omitempty\u0026#34;` Addresses []string `json:\u0026#34;addresses\u0026#34;` private string // 不会被序列化 } func main() { person := Person{ Name: \u0026#34;张三\u0026#34;, Age: 30, Addresses: []string{\u0026#34;北京市\u0026#34;, \u0026#34;上海市\u0026#34;}, private: \u0026#34;私有字段\u0026#34;, } // 序列化为JSON data, err := json.Marshal(person) if err != nil { fmt.Println(\u0026#34;序列化错误:\u0026#34;, err) return } fmt.Println(\u0026#34;JSON:\u0026#34;, string(data)) // 反序列化 var decodedPerson Person err = json.Unmarshal(data, \u0026amp;decodedPerson) if err != nil { fmt.Println(\u0026#34;反序列化错误:\u0026#34;, err) return } fmt.Printf(\u0026#34;反序列化结果: %+v\\n\u0026#34;, decodedPerson) } 结构体验证 # 许多验证库，如validator，使用反射来根据结构体标签验证字段值：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;reflect\u0026#34; \u0026#34;regexp\u0026#34; \u0026#34;strconv\u0026#34; \u0026#34;strings\u0026#34; ) // 验证器接口 type Validator interface { Validate(val interface{}) bool Message() string } // 电子邮件验证器 type EmailValidator struct{} func (v EmailValidator) Validate(val interface{}) bool { str, ok := val.(string) if !ok { return false } return regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$`).MatchString(str) } func (v EmailValidator) Message() string { return \u0026#34;必须是有效的电子邮件地址\u0026#34; } // 长度验证器 type LengthValidator struct { Min int Max int } func (v LengthValidator) Validate(val interface{}) bool { str, ok := val.(string) if !ok { return false } length := len(str) if v.Min \u0026gt; 0 \u0026amp;\u0026amp; length \u0026lt; v.Min { return false } if v.Max \u0026gt; 0 \u0026amp;\u0026amp; length \u0026gt; v.Max { return false } return true } func (v LengthValidator) Message() string { if v.Min \u0026gt; 0 \u0026amp;\u0026amp; v.Max \u0026gt; 0 { return fmt.Sprintf(\u0026#34;长度必须在%d到%d之间\u0026#34;, v.Min, v.Max) } if v.Min \u0026gt; 0 { return fmt.Sprintf(\u0026#34;长度必须大于等于%d\u0026#34;, v.Min) } return fmt.Sprintf(\u0026#34;长度必须小于等于%d\u0026#34;, v.Max) } // 验证结构体 func ValidateStruct(obj interface{}) []string { var errors []string v := reflect.ValueOf(obj) // 处理指针 if v.Kind() == reflect.Ptr { v = v.Elem() } // 确保是结构体 if v.Kind() != reflect.Struct { return []string{\u0026#34;只能验证结构体\u0026#34;} } t := v.Type() // 遍历所有字段 for i := 0; i \u0026lt; t.NumField(); i++ { field := t.Field(i) // 检查validate标签 validateTag := field.Tag.Get(\u0026#34;validate\u0026#34;) if validateTag == \u0026#34;\u0026#34; { continue } fieldValue := v.Field(i) // 解析验证规则 validations := strings.Split(validateTag, \u0026#34;,\u0026#34;) for _, validation := range validations { var validator Validator if validation == \u0026#34;email\u0026#34; { validator = EmailValidator{} } else if strings.HasPrefix(validation, \u0026#34;length=\u0026#34;) { params := strings.TrimPrefix(validation, \u0026#34;length=\u0026#34;) parts := strings.Split(params, \u0026#34;:\u0026#34;) min, _ := strconv.Atoi(parts[0]) max := 0 if len(parts) \u0026gt; 1 { max, _ = strconv.Atoi(parts[1]) } validator = LengthValidator{Min: min, Max: max} } else { continue } // 验证字段值 if !validator.Validate(fieldValue.Interface()) { errors = append(errors, fmt.Sprintf(\u0026#34;字段 %s: %s\u0026#34;, field.Name, validator.Message())) } } } return errors } func main() { type User struct { Username string `validate:\u0026#34;length=3:20\u0026#34;` Email string `validate:\u0026#34;email\u0026#34;` Bio string `validate:\u0026#34;length=0:100\u0026#34;` } user := User{ Username: \u0026#34;a\u0026#34;, Email: \u0026#34;invalid-email\u0026#34;, Bio: strings.Repeat(\u0026#34;很长的简介 \u0026#34;, 30), } errors := ValidateStruct(user) if len(errors) \u0026gt; 0 { fmt.Println(\u0026#34;验证错误:\u0026#34;) for _, err := range errors { fmt.Println(\u0026#34;- \u0026#34; + err) } } else { fmt.Println(\u0026#34;验证通过\u0026#34;) } } 依赖注入 # 反射可用于实现依赖注入系统，在运行时动态装配对象：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;reflect\u0026#34; ) // 服务接口 type Logger interface { Log(message string) } type UserRepository interface { FindUser(id int) string } // 服务实现 type ConsoleLogger struct{} func (l *ConsoleLogger) Log(message string) { fmt.Printf(\u0026#34;[LOG] %s\\n\u0026#34;, message) } type DatabaseUserRepository struct { Logger Logger `inject:\u0026#34;logger\u0026#34;` } func (r *DatabaseUserRepository) FindUser(id int) string { r.Logger.Log(fmt.Sprintf(\u0026#34;查找用户ID: %d\u0026#34;, id)) return fmt.Sprintf(\u0026#34;用户_%d\u0026#34;, id) } // 控制器 type UserController struct { Repository UserRepository `inject:\u0026#34;userRepository\u0026#34;` Logger Logger `inject:\u0026#34;logger\u0026#34;` } func (c *UserController) GetUser(id int) { c.Logger.Log(fmt.Sprintf(\u0026#34;控制器处理GetUser请求，ID: %d\u0026#34;, id)) user := c.Repository.FindUser(id) fmt.Printf(\u0026#34;找到用户: %s\\n\u0026#34;, user) } // 简单的依赖注入容器 type Container struct { services map[string]interface{} } func NewContainer() *Container { return \u0026amp;Container{ services: make(map[string]interface{}), } } func (c *Container) Register(name string, service interface{}) { c.services[name] = service } func (c *Container) Resolve(target interface{}) error { v := reflect.ValueOf(target) if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { return fmt.Errorf(\u0026#34;目标必须是结构体指针\u0026#34;) } e := v.Elem() t := e.Type() for i := 0; i \u0026lt; t.NumField(); i++ { field := t.Field(i) injectTag := field.Tag.Get(\u0026#34;inject\u0026#34;) if injectTag == \u0026#34;\u0026#34; { continue } service, exists := c.services[injectTag] if !exists { return fmt.Errorf(\u0026#34;服务不存在: %s\u0026#34;, injectTag) } sv := reflect.ValueOf(service) fieldValue := e.Field(i) if !sv.Type().AssignableTo(fieldValue.Type()) { return fmt.Errorf( \u0026#34;类型不匹配: 服务 %s 类型为 %s, 但字段 %s 类型为 %s\u0026#34;, injectTag, sv.Type(), field.Name, fieldValue.Type(), ) } fieldValue.Set(sv) // 递归解析依赖 if sv.Kind() == reflect.Ptr \u0026amp;\u0026amp; sv.Elem().Kind() == reflect.Struct { c.Resolve(service) } } return nil } func main() { // 创建容器 container := NewContainer() // 注册服务 logger := \u0026amp;ConsoleLogger{} repository := \u0026amp;DatabaseUserRepository{} controller := \u0026amp;UserController{} container.Register(\u0026#34;logger\u0026#34;, logger) container.Register(\u0026#34;userRepository\u0026#34;, repository) // 解析依赖 if err := container.Resolve(repository); err != nil { fmt.Printf(\u0026#34;解析仓库依赖错误: %v\\n\u0026#34;, err) return } if err := container.Resolve(controller); err != nil { fmt.Printf(\u0026#34;解析控制器依赖错误: %v\\n\u0026#34;, err) return } // 使用控制器 controller.GetUser(42) } StructTag 结构体标签 # 通过 reflect.Type 获取结构体成员信息 reflect.StructField 结构中的 Tag 被称为结构体标签（StructTag）。结构体标签是对结构体字段的额外信息标签。\nJSON、BSON 等格式进行序列化及对象关系映射（Object Relational Mapping，简称 ORM）系统都会用到结构体标签，这些系统使用标签设定字段在处理时应该具备的特殊属性和可能发生的行为。这些信息都是静态的，无须实例化结构体，可以通过反射获取到。\nStructTag具有两个方法\nfunc (tag StructTag) Get(key string) string //根据 Tag 中的键获取对应的值 func (tag StructTag) Lookup(key string) (value string, ok bool) //根据 Tag 中的键，查询值是否存在 type Stu struct { name string `json:\u0026#34;name\u0026#34; yaml:\u0026#34;n\u0026#34;` age int `json:\u0026#34;age\u0026#34; yaml:\u0026#34;a\u0026#34;` } s := Stu{name: \u0026#34;lucy\u0026#34;, age: 18} typ := reflect.TypeOf(s) tag1 := typ.Field(0).Tag tag2 := typ.Field(1).Tag //name字段Tag为,json:name,yaml:n fmt.Printf(\u0026#34;name字段Tag为,json:%s,yaml:%s\\n\u0026#34;, tag1.Get(\u0026#34;json\u0026#34;), tag1.Get(\u0026#34;yaml\u0026#34;)) //age字段Tag为,json:age,yaml:a fmt.Printf(\u0026#34;age字段Tag为,json:%s,yaml:%s\\n\u0026#34;, tag2.Get(\u0026#34;json\u0026#34;), tag2.Get(\u0026#34;yaml\u0026#34;)) 常见结构体标签 # JSON\n`json:\u0026#34;fieldname,omitempty\u0026#34;` // omitempty表示值为零值时忽略 DB\n`db:\u0026#34;column_name\u0026#34;` 表单\n`form:\u0026#34;field_name\u0026#34;` 验证\n`validate:\u0026#34;required,min=3,max=50\u0026#34;` ","date":"2025-06-27","externalUrl":null,"permalink":"/posts/2d3150b2/0e0cf2f4/66945220/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e反射的基本概念 \n    \u003cdiv id=\"反射的基本概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8f%8d%e5%b0%84%e7%9a%84%e5%9f%ba%e6%9c%ac%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在Go编程中，反射（Reflection）是一项强大而复杂的特性，它允许程序在运行时检查和修改自己的结构和行为。虽然在日常编程中我们可能会尽量避免过度使用反射（因为它会带来复杂性和性能损失），但了解反射机制对于理解许多高级库和框架的工作原理至关重要。\u003c/p\u003e","title":"6、反射","type":"posts"},{"content":"Gomail 是发送电子邮件的简单高效的包。它已经过充分测试和记录。Gomail 只能使用 SMTP 服务器发送电子邮件。但是该 API 灵活，可以轻松实现使用本地 Postfix，API 等发送电子邮件的其他方法。\ngomail 包含的特性\n附件 嵌入图片 HTML 和文本模板 特殊字符的自动编码 SSL 和 TLS 使用相同的 SMTP 连接发送多封电子邮件 安装 # go get -u gopkg.in/gomail.v2 使用 # package main import ( \u0026#34;crypto/tls\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;gopkg.in/gomail.v2\u0026#34; ) type EmailConf struct { From string Host string Port int UserName string PassWord string } var EmailSuper = EmailConf{ From: \u0026#34;测试邮箱\u0026#34;, Host: \u0026#34;smtp.exmail.qq.com\u0026#34;, Port: 465, UserName: \u0026#34;hello@qq.com\u0026#34;, PassWord: \u0026#34;123456789\u0026#34;, } func SendMail(ec EmailConf, subject, body string, to, cc, bcc, attaches []string) error { m := gomail.NewMessage() m.SetHeader(\u0026#34;From\u0026#34;, m.FormatAddress(ec.UserName, ec.From)) // 发送者 m.SetHeader(\u0026#34;To\u0026#34;, to...) // 接受者 m.SetHeader(\u0026#34;Cc\u0026#34;, cc...) // 抄送 m.SetHeader(\u0026#34;Bcc\u0026#34;, bcc...) // 暗送 m.SetHeader(\u0026#34;Subject\u0026#34;, subject) // 邮件标题 m.SetBody(\u0026#34;text/html\u0026#34;, body) // 发送邮件内容,text/plain为纯文本 for _, att := range attaches { // 添加附件 m.Attach(att) } /* 实例化邮件发送器 第一个参数为服务器地址，第二个为端口号，第三个为发送者邮箱号 第四个如果是qq邮箱为授权玛而其他邮箱是密码 */ d := gomail.NewDialer(ec.Host, ec.Port, ec.UserName, ec.PassWord) // 关闭SSL协议认证 d.TLSConfig = \u0026amp;tls.Config{InsecureSkipVerify: true} if err := d.DialAndSend(m); err != nil { return err } return nil } func main() { to := []string{\u0026#34;123456789@qq.com\u0026#34;} attaches := []string{\u0026#34;./go.mod\u0026#34;} err := SendMail(EmailSuper, \u0026#34;nihao\u0026#34;, \u0026#34;hello golang!\u0026#34;, to, nil, nil, attaches) if err != nil { fmt.Println(err) } } ","date":"2025-05-19","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/29ede47b/bc48dbae/","section":"文章","summary":"\u003cp\u003eGomail 是发送电子邮件的简单高效的包。它已经过充分测试和记录。Gomail 只能使用 SMTP 服务器发送电子邮件。但是该 API 灵活，可以轻松实现使用本地 Postfix，API 等发送电子邮件的其他方法。\u003c/p\u003e","title":"6、gomail","type":"posts"},{"content":"gin支持加载HTML模板, 然后根据模板参数进行配置并返回相应的数据，本质上就是字符串替换\n基本使用 # package main import ( \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;net/http\u0026#34; ) func main() { router := gin.Default() // 加载所有模板文件 router.LoadHTMLGlob(\u0026#34;template/*\u0026#34;) router.GET(\u0026#34;/index\u0026#34;, func(context *gin.Context) { // 渲染模板文件，第二个参数为模板在上面template文件夹中的路径，第三个参数为模板参数 context.HTML(http.StatusOK, \u0026#34;index.html\u0026#34;, \u0026#34;hello\u0026#34;) }) router.Run(\u0026#34;:8080\u0026#34;) } 静态文件 # 例如jpg、js这些静态文件，可以指定路径前缀和路径\nrouter.Static(\u0026#34;static\u0026#34;,\u0026#34;./static\u0026#34;) ","date":"2025-05-19","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/1bd46fa5/711ebfb3/","section":"文章","summary":"\u003cp\u003egin支持加载HTML模板, 然后根据模板参数进行配置并返回相应的数据，本质上就是字符串替换\u003c/p\u003e","title":"6、模板引擎","type":"posts"},{"content":"Go语言内置的flag包实现了命令行参数的解析，flag包使得开发命令行工具更为简单。\nArgs # 用于简单的获取命令行参数\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) func main() { // args为[]string args := os.Args for i, arg := range args { fmt.Printf(\u0026#34;arg[%d] = %s\\n\u0026#34;, i, arg) } } .\\main.exe abc def arg[0] = .\\main.exe arg[1] = abc arg[2] = def flag包基本使用 # flag包支持的命令行参数类型有bool、int、int64、uint、uint64、float float64、string、duration。\n定义参数并解析 # 有两种方法接受参数\nfunc (f *FlagSet) [Type](name string, value [Type], usage string) *[Type] ：方法返回接收参数的指针 Type：要接收参数的类型 name：参数名 value：默认值 usage：使用说明 func [Type]Var(p *[Type], name string, value [Type], usage string)：方法将参数接收到p中 p：接受参数的指针 Type：要接收参数的类型 name：参数名 value：默认值 usage：使用说明 注意：定义好参数后，需要使用flag.Parse()对命令行参数进行解析\n程序可以使用-h，查看参数列表\npackage main import ( \u0026#34;flag\u0026#34; \u0026#34;fmt\u0026#34; ) type Stu struct { id string name string age int } func (s *Stu) String() (str string) { return fmt.Sprintf(\u0026#34;编号：%s；姓名：%s；年龄：%d\u0026#34;, s.id, s.name, s.age) } func main() { s := new(Stu) // 定义参数 flag.StringVar(\u0026amp;s.id, \u0026#34;id\u0026#34;, \u0026#34;A123456\u0026#34;, \u0026#34;学生编号\u0026#34;) flag.StringVar(\u0026amp;s.name, \u0026#34;name\u0026#34;, \u0026#34;lucy\u0026#34;, \u0026#34;学生姓名\u0026#34;) flag.IntVar(\u0026amp;s.age, \u0026#34;age\u0026#34;, 18, \u0026#34;学生年龄\u0026#34;) // 执行解析 flag.Parse() fmt.Println(s) } 支持的命令行参数格式有以下几种：\n-flag xxx -flag=xxx --flag xxx --flag=xxx 注意：等号左右不可以有空格；布尔类型的参数必须使用等号的方式-flag=false/--flag=false指定。\nFlag解析在第一个非flag参数（单个-不是flag参数）之前停止，或者在终止符--之后停止。\n","date":"2025-05-13","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/12d682f6/b3e5ff32/","section":"文章","summary":"\u003cp\u003eGo语言内置的flag包实现了命令行参数的解析，flag包使得开发命令行工具更为简单。\u003c/p\u003e","title":"6、flag","type":"posts"},{"content":" 指针 # Go语言为程序员提供了控制数据结构指针的能力，但是，并不能进行指针运算，Go语言允许你控制特定集合的数据结构、分配的数量以及内存访问模式。\n指针（pointer）在Go语言中可以被拆分为两个核心概念：\n类型指针，允许对这个指针类型的数据进行修改，传递数据可以直接使用指针，而无须拷贝数据，类型指针不能进行偏移和运算。 切片，由指向起始元素的原始指针、元素数量和容量组成。 受益于这样的约束和拆分，Go语言的指针类型变量即拥有指针高效访问的特点，又不会发生指针偏移，从而避免了非法修改关键性数据的问题。同时，垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。\n切片比原始指针具备更强大的特性，而且更为安全。切片在发生越界时，运行时会报出宕机，并打出堆栈，而原始指针只会崩溃。\n指针也是一种变量，只不过是一个是一种特殊的变量，指针变量对应的值是指针所指向的内存地址。\n声明 # 一个指针变量可以指向任何一个值的内存地址，它所指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节，占用字节的大小与所指向的值的大小无关。当一个指针被定义后没有分配到任何变量时，它的默认值为 nil。指针变量通常缩写为 ptr。\nvar ptr *type = \u0026amp;v //指针变量也可以进行自动类型推导 ptr := \u0026amp;v \u0026amp;为取址符，用于获取变量v的地址 ptr为指针变量，类型为*type v为被取址变量，类型为type package main import ( \u0026#34;fmt\u0026#34; ) func main() { a := 10 s := \u0026#34;hello\u0026#34; var ptr1 *int = \u0026amp;a ptr2 := \u0026amp;s fmt.Printf(\u0026#34;%p\\n\u0026#34;, ptr1) //0xc000014098 fmt.Printf(\u0026#34;%p\\n\u0026#34;, ptr2) //0xc000088220 } 通过指针获取指向的值（解引用） # 当使用\u0026amp;操作符对普通变量进行取地址操作并得到变量的指针后，可以对指针使用*操作符，也就是指针取值\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { a := 10 ptr := \u0026amp;a fmt.Printf(\u0026#34;%p\\n\u0026#34;, ptr) //0xc000014098 fmt.Printf(\u0026#34;%d\\n\u0026#34;, *ptr) //10 } 使用指针修改值 # package main import ( \u0026#34;fmt\u0026#34; ) func main() { a := 10 b := 20 changeByPoint(\u0026amp;a, \u0026amp;b) fmt.Println(a, b) // 20 10 } func changeByPoint(a, b *int) { c := *a *a = *b *b = c } 使用指针获取命令行参数 # package main import ( \u0026#34;flag\u0026#34; \u0026#34;fmt\u0026#34; ) func main() { //定义命令行参数 //方法三个参数分别为：参数名称、默认值、用法，返回值为*string na := flag.String(\u0026#34;name\u0026#34;, \u0026#34;lucy\u0026#34;, \u0026#34;your name\u0026#34;) //解析命令行参数 flag.Parse() fmt.Println(*na) } new # 由于在Go中，引用数据类型使用前必须手动分配内存空间，指针也是引用数据类型，指针在未分配数据内存空间时为nil，所以必须使用new方法进行分配后，才可以操作指针。\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { var a *int fmt.Println(a, a == nil) // 未分配内存的指针，无法操作 *a = 10 fmt.Println(a) } /* \u0026lt;nil\u0026gt; true panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x10420ac0c] */ new使用方法\nvar ptr *type = new(type) ptr := new(type) new() 函数可以创建一个对应类型的指针，创建过程会分配内存，被创建的指针指向对应类型的默认值（零值）。\npackage main import ( \u0026#34;fmt\u0026#34; ) func main() { // 声明*int类型的变量，并分配内存 var a *int = new(int) fmt.Println(a, a == nil) fmt.Println(*a) *a = 10 fmt.Println(*a) } /* 0x1400000e0b8 false 0 10 */ 内存分析 # 栈 # 栈（Stack）是一种拥有特殊规则的线性表数据结构。\n栈只允许从线性表的同一端放入和取出数据，按照后进先出（LIFO，Last In First Out）的顺序，如下图所示\n往栈中放入元素的过程叫做入栈。入栈会增加栈的元素数量，最后放入的元素总是位于栈的顶部，最先放入的元素总是位于栈的底部。\n从栈中取出元素时，只能从栈顶部取出。取出元素后，栈的元素数量会变少。最先放入的元素总是最后被取出，最后放入的元素总是最先被取出。不允许从栈底获取数据，也不允许对栈成员（除了栈顶部的成员）进行任何查看和修改操作。\n变量和栈的关系：\nfunc calc(a, b int) int { var c int c = a * b var x int x = c * 10 return x } 上面的代码在没有任何优化的情况下，会进行变量 c 和 x 的分配过程。Go语言默认情况下会将 c 和 x 分配在栈上，这两个变量在 calc() 函数退出时就不再使用，函数结束时，保存 c 和 x 的栈内存再出栈释放内存，整个分配内存的过程通过栈的分配和回收都会非常迅速。\n堆 # 堆（heap）在内存分配中类似于往一个房间里摆放各种家具，家具的尺寸有大有小，分配内存时，需要找一块足够装下家具的空间再摆放家具。经过反复摆放和腾空家具后，房间里的空间会变得乱七八糟，此时再往这个空间里摆放家具会发现虽然有足够的空间，但各个空间分布在不同的区域，没有一段连续的空间来摆放家具。此时，内存分配器就需要对这些空间进行调整优化\n堆分配内存和栈分配内存相比，堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢，而且会形成内存碎片。\n变量逃逸分析（Escape Analysis） # Go语言中的变量逃逸指的是编译器自动将一个变量从局部（函数内部）变量变为堆上分配的全局变量的情况。\n堆和栈各有优缺点，该怎么在编程中处理这个问题呢？在 C/C++语言中，需要开发者自己学习如何进行内存分配，选用怎样的内存分配方式来适应不同的算法需求。比如，函数局部变量尽量使用栈；全局变量、结构体成员使用堆分配等。\nGo语言将这个过程整合到了编译器中，命名为“变量逃逸分析”。通过编译器分析代码的特征和代码的生命周期，决定应该使用堆还是栈来进行内存分配。\n逃逸分析 # go run -gcflags \u0026#34;-m -l\u0026#34; main.go -gcflags 参数是编译参数。其中 -m 表示进行内存分配分析，-l 表示避免程序内联，也就是避免进行程序优化。\n常见逃逸 # 函数返回指针 # 当函数返回一个局部变量的指针时，编译器将不得不将该变量分配到堆上，以便在函数返回后仍然可以访问它。\npackage main import \u0026#34;fmt\u0026#34; func dummy() *int { a := 10 return \u0026amp;a } func main() { b := dummy() fmt.Println(*b) } go run -gcflags \u0026#34;-m -l\u0026#34; main.go # command-line-arguments ./main.go:6:2: moved to heap: a ./main.go:12:13: ... argument does not escape ./main.go:12:14: *b escapes to heap # 变量b逃逸到堆 10 闭包 # 如果一个局部变量被一个闭包函数引用，那么编译器可能会将该变量逃逸到堆上，以确保闭包可以继续访问它，即使函数返回了。\npackage main import \u0026#34;fmt\u0026#34; func dummy() func() { a := 10 return func() { fmt.Println(a) } } func main() { dummy()() } go run -gcflags \u0026#34;-m -l\u0026#34; main.go # command-line-arguments ./main.go:7:9: func literal escapes to heap ./main.go:8:14: ... argument does not escape ./main.go:8:15: a escapes to heap # 变量a逃逸到堆 10 长寿命变量 # 如果一个局部变量的生命周期比函数的生命周期长，编译器可能会将它逃逸到堆上。这通常发生在使用go关键字启动的 goroutine 中，因为 goroutine 可能会在函数返回后继续执行。\npackage main import \u0026#34;fmt\u0026#34; func dummy() { a := 10 go func() { fmt.Println(a) }() } func main() { dummy() } go run -gcflags \u0026#34;-m -l\u0026#34; main.go # command-line-arguments ./main.go:7:5: func literal escapes to heap ./main.go:8:14: ... argument does not escape ./main.go:8:15: a escapes to heap # 变量a逃逸到堆 逃逸分析怎么完成的 # Go逃逸分析最基本的原则是**：如果一个函数返回对一个变量的引用，那么它就会发生逃逸。**\n简单来说，编译器会分析代码的特征和代码生命周期，Go中的变量只有在编译器可以证明在函数返回后不会再被引用的，才分配到栈上，其他情况下都是分配到堆上。\nGo语言里没有一个关键字或者函数可以直接让变量被编译器分配到堆上，相反，编译器通过分析代码来决定将变量分配到何处。\n对一个变量取地址，可能会被分配到堆上。但是编译器进行逃逸分析后，如果考察到在函数返回后，此变量不会被引用，那么还是会被分配到栈上。\n简单来说，编译器会根据变量是否被外部引用来决定是否逃逸：\n如果函数外部没有引用，则优先放到栈中； 如果函数外部存在引用，则必定放到堆中； 针对第一条，可能放到堆上的情形：定义了一个很大的数组，需要申请的内存过大，超过了栈的存储能力。\n避免逃逸 # 变量逃逸的主要影响是性能。堆分配和垃圾收集通常比栈上分配更昂贵，所以尽量避免变量逃逸是一种优化 Go 代码的方式。在编写 Go 代码时，可以使用以下技巧来减少变量逃逸：\n避免返回指针：如果可能的话，避免从函数返回局部变量的指针。相反，尽量返回局部变量的值。 限制闭包的作用范围：尽量将闭包函数内部引用的变量限制在局部范围，以减少逃逸。 减小对象生命周期：确保局部变量的生命周期不超出需要的范围。如果一个变量只在函数内部使用，那么应该在函数内部定义它，而不是将它逃逸到堆上。 ","date":"2025-05-12","externalUrl":null,"permalink":"/posts/2d3150b2/02a0817f/f3f335ef/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e指针 \n    \u003cdiv id=\"指针\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8c%87%e9%92%88\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eGo语言为程序员提供了控制数据结构指针的能力，但是，\u003cstrong\u003e并不能进行指针运算\u003c/strong\u003e，Go语言允许你控制特定集合的数据结构、分配的数量以及内存访问模式。\u003c/p\u003e","title":"6、指针","type":"posts"},{"content":" FS 简介 # Node.js 的文件系统模块（fs 模块）提供了丰富的 API，用于读取、写入、删除文件以及执行其他文件系统操作。\nfs 模块既支持同步方法也支持异步方法，使得开发者可以根据具体需求选择合适的方式来处理文件操作。\nNode.js 文件系统（fs 模块）模块中的方法均有异步和同步版本，例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。\n异步的方法函数最后一个参数为回调函数，回调函数的第一个参数包含了错误信息(error)。\n建议大家使用异步方法，比起同步，异步方法性能更高，速度更快，而且没有阻塞。\n导入 # var fs = require(\u0026#34;fs\u0026#34;) Demo # var fs = require(\u0026#34;fs\u0026#34;); // 异步读取 fs.readFile(\u0026#39;input.txt\u0026#39;, function (err, data) { if (err) { return console.error(err); } console.log(\u0026#34;异步读取: \u0026#34; + data.toString()); }); // 同步读取 var data = fs.readFileSync(\u0026#39;example.txt\u0026#39;); console.log(\u0026#34;同步读取: \u0026#34; + data.toString()); console.log(\u0026#34;程序执行完毕。\u0026#34;); 文件操作 # 文件写入 # 方法 说明 writeFile 异步写入 writeFileSync 同步写入 appendFile 异步追加写入 appendFileSync 同步追加写入 createWriteStream 流式写入 writeFile 异步写入 # fs.writeFile(file, data[, options], callback)\n参数说明：\nfile 文件名 data 待写入的数据 options 选项设置 （可选） callback 写入回调 fs.writeFile(\u0026#39;example.txt\u0026#39;, \u0026#39;Hello, World!\u0026#39;, (err) =\u0026gt; { if (err) { console.error(\u0026#39;Error writing file:\u0026#39;, err); return; } console.log(\u0026#39;File written successfully\u0026#39;); }); writeFileSync 同步写入 # fs.writeFileSync(file, data[, options])\n参数与 fs.writeFile 大体一致，只是没有 callback 参数\ntry { fs.writeFileSync(\u0026#39;example.txt\u0026#39;, \u0026#39;Hello, World!\u0026#39;); console.log(\u0026#39;File written successfully\u0026#39;); } catch (err) { console.error(\u0026#39;Error writing file:\u0026#39;, err); } appendFile 和 appendFileSync 追加写入 # appendFile 作用是在文件尾部追加内容，appendFile 语法与 writeFile 语法完全相同\n异步\nfs.appendFile(\u0026#39;example.txt\u0026#39;, \u0026#39;\\nAppending some text\u0026#39;, (err) =\u0026gt; { if (err) { console.error(\u0026#39;Error appending to file:\u0026#39;, err); return; } console.log(\u0026#39;Text appended successfully\u0026#39;); }); 同步\ntry { fs.appendFileSync(\u0026#39;example.txt\u0026#39;, \u0026#39;\\nAppending some text\u0026#39;); console.log(\u0026#39;Text appended successfully\u0026#39;); } catch (err) { console.error(\u0026#39;Error appending to file:\u0026#39;, err); } createWriteStream 流式写入 # fs.createWriteStream(path[, options])\n参数说明：\npath 文件路径 options 选项配置（ 可选 ） const fs = require(\u0026#39;fs\u0026#39;); const writableStream = fs.createWriteStream(\u0026#39;output.txt\u0026#39;); writableStream.write(\u0026#39;Hello, \u0026#39;); writableStream.write(\u0026#39;World!\\n\u0026#39;); writableStream.end(); // 完成写入操作。此时可以关闭流或进行其他操作。 writableStream.on(\u0026#39;finish\u0026#39;, () =\u0026gt; { console.log(\u0026#39;All writes are now complete.\u0026#39;); }); 文件读取 # 方法 说明 readFile 异步读取 readFileSync 同步读取 createReadStream 流式读取 readFile 异步读取 # fs.readFile(path[, options], callback)\n参数说明：\npath 文件路径 options 选项配置 callback 回调函数 //导入 fs 模块 const fs = require(\u0026#39;fs\u0026#39;); fs.readFile(\u0026#39;./input.txt\u0026#39;, (err, data) =\u0026gt; { if(err) throw err; console.log(data); }); fs.readFile(\u0026#39;./input.txt\u0026#39;, \u0026#39;utf-8\u0026#39;,(err, data) =\u0026gt; { if(err) throw err; console.log(data); }); readFileSync 同步读取 # fs.readFileSync(path[, options])\n参数说明：\npath 文件路径 options 选项配置 let data = fs.readFileSync(\u0026#39;./input.txt\u0026#39;); let data2 = fs.readFileSync(\u0026#39;./input.txt\u0026#39;, \u0026#39;utf-8\u0026#39;); createReadStream 流式读取 # fs.createReadStream(path[, options])\n参数说明：\npath 文件路径 options 选项配置（ 可选 ） const fs = require(\u0026#39;fs\u0026#39;); const readableStream = fs.createReadStream(\u0026#39;large_file.txt\u0026#39;, \u0026#39;utf8\u0026#39;); readableStream.on(\u0026#39;data\u0026#39;, (chunk) =\u0026gt; { console.log(\u0026#39;Received chunk:\u0026#39;, chunk); }); readableStream.on(\u0026#39;end\u0026#39;, () =\u0026gt; { console.log(\u0026#39;No more data.\u0026#39;); }); 删除文件 # 异步\nfs.unlink(\u0026#39;example.txt\u0026#39;, (err) =\u0026gt; { if (err) { console.error(\u0026#39;Error deleting file:\u0026#39;, err); return; } console.log(\u0026#39;File deleted successfully\u0026#39;); }); 同步\ntry { fs.unlinkSync(\u0026#39;example.txt\u0026#39;); console.log(\u0026#39;File deleted successfully\u0026#39;); } catch (err) { console.error(\u0026#39;Error deleting file:\u0026#39;, err); } 目录操作 # 借助 Node.js 的能力，我们可以对文件夹进行创建 、 读取 、 删除 等操作\n方法 说明 mkdir / mkdirSync 创建文件夹 readdir / readdirSync 读取文件夹 rmdir / rmdirSync 删除文件夹 创建目录 # 异步\nfs.mkdir(\u0026#39;new_directory\u0026#39;, (err) =\u0026gt; { if (err) { console.error(\u0026#39;Error creating directory:\u0026#39;, err); return; } console.log(\u0026#39;Directory created successfully\u0026#39;); }); 同步\ntry { fs.mkdirSync(\u0026#39;new_directory\u0026#39;); console.log(\u0026#39;Directory created successfully\u0026#39;); } catch (err) { console.error(\u0026#39;Error creating directory:\u0026#39;, err); } 读取目录内容 # 异步\nfs.readdir(\u0026#39;new_directory\u0026#39;, (err, files) =\u0026gt; { if (err) { console.error(\u0026#39;Error reading directory:\u0026#39;, err); return; } console.log(\u0026#39;Directory contents:\u0026#39;, files); }); 同步\ntry { const files = fs.readdirSync(\u0026#39;new_directory\u0026#39;); console.log(\u0026#39;Directory contents:\u0026#39;, files); } catch (err) { console.error(\u0026#39;Error reading directory:\u0026#39;, err); } 删除目录 # //异步删除文件夹 fs.rmdir(\u0026#39;new_directory\u0026#39;, err =\u0026gt; { if(err) throw err; console.log(\u0026#39;删除成功\u0026#39;); }); //异步递归删除文件夹 fs.rmdir(\u0026#39;new_directory\u0026#39;, {recursive: true}, err =\u0026gt; { if(err) { console.log(err); } console.log(\u0026#39;递归删除\u0026#39;) }); //同步递归删除文件夹 fs.rmdirSync(\u0026#39;new_directory\u0026#39;, {recursive: true}) 通用方法 # 检查文件或目录 # 异步\nfs.access(\u0026#39;example.txt\u0026#39;, fs.constants.F_OK, (err) =\u0026gt; { if (err) { console.log(\u0026#39;File does not exist\u0026#39;); } else { console.log(\u0026#39;File exists\u0026#39;); } }); 同步\ntry { fs.accessSync(\u0026#39;example.txt\u0026#39;, fs.constants.F_OK); console.log(\u0026#39;File exists\u0026#39;); } catch (err) { console.log(\u0026#39;File does not exist\u0026#39;); } fs.constants.F_OK：检查文件或目录是否存在。 fs.constants.R_OK：检查文件或目录是否可读。 fs.constants.W_OK：检查文件或目录是否可写。 fs.constants.X_OK：检查文件或目录是否可执行。 查看资源信息（状态） # 方法 说明 stat 异步查看资源信息 statSync 同步查看资源信息 异步\nfs.stat(\u0026#39;example.txt\u0026#39;, (err, stats) =\u0026gt; { if (err) { console.error(\u0026#39;Error getting stats:\u0026#39;, err); return; } // 查看资源是否为文件 console.log(\u0026#39;Is file?\u0026#39;, stats.isFile()); // 查看资源是否为目录 console.log(\u0026#39;Is directory?\u0026#39;, stats.isDirectory()); // 资源的大小 console.log(\u0026#39;Size:\u0026#39;, stats.size); }); 同步\ntry { const stats = fs.statSync(\u0026#39;example.txt\u0026#39;); console.log(\u0026#39;Is file?\u0026#39;, stats.isFile()); console.log(\u0026#39;Is directory?\u0026#39;, stats.isDirectory()); console.log(\u0026#39;Size:\u0026#39;, stats.size); } catch (err) { console.error(\u0026#39;Error getting stats:\u0026#39;, err); } 文件移动与重命名 # 在 Node.js 中，我们可以使用 rename 或 renameSync 来移动或重命名文件或文件夹\nfs.rename(oldPath, newPath, callback)\nfs.renameSync(oldPath, newPath)\n参数说明：\noldPath 文件当前的路径 newPath 文件新的路径 callback 操作后的回调 异步\nfs.rename(\u0026#39;./file.txt\u0026#39;, \u0026#39;./path/new_file.txt\u0026#39;, (err) =\u0026gt;{ if(err) throw err; console.log(\u0026#39;移动完成\u0026#39;) }); 同步\nfs.renameSync(\u0026#39;./file.txt\u0026#39;, \u0026#39;./path/new_file.txt\u0026#39;); fs.open打开文件 # 以下为在异步模式下打开文件的语法格式：fs.open(path, flags[, mode], callback)\n参数使用说明如下：\npath：文件的路径。 flags：文件打开的行为。具体值详见下文。 mode：设置文件模式(权限)，文件创建默认权限为 0666(可读，可写)。 callback：回调函数，带有两个参数如：callback(err, fd)。 var fs = require(\u0026#39;fs\u0026#39;) fs.open(\u0026#39;package.json\u0026#39;,\u0026#39;r\u0026#39;,(err,fd) =\u0026gt; { if (err) { console.error(err) } }) flags 参数可以是以下值：\nFlag 描述 r 以只读模式打开文件。文件必须存在。如果文件不存在，会抛出异常 r+ 以读写模式打开文件。文件必须存在。 rs 以同步方式只读打开文件。阻塞操作，但在某些操作系统上可能会提供更好的稳定性。 rs+ 以同步方式读写打开文件。阻塞操作，但在某些操作系统上可能会提供更好的稳定性。 w 以只写模式打开文件。如果文件不存在则创建文件，如果文件存在则截断文件。 wx 类似于 \u0026lsquo;w\u0026rsquo;，但如果路径存在，则失败。 w+ 以读写模式打开文件。如果文件不存在则创建文件，如果文件存在则截断文件。 wx+ 类似于 \u0026lsquo;w+\u0026rsquo;，但如果路径存在，则失败。 a 以追加模式打开文件。如果文件不存在则创建文件。 ax 类似于 \u0026lsquo;a\u0026rsquo;，但如果路径存在，则失败。 a+ 以读取和追加模式打开文件。如果文件不存在则创建文件。 ax+ 类似于 \u0026lsquo;a+\u0026rsquo;，但如果路径存在，则失败。 读取 # fs.read(fd[, options], callback) fs.read(fd, buffer, offset, length, position, callback) fs.read(fd, buffer[, options], callback) 参数使用说明如下：\nfd：通过 fs.open() 方法返回的文件描述符。 buffer：数据写入的缓冲区。 offset：缓冲区写入的写入偏移量。 length：要从文件中读取的字节数。 position：文件读取的起始位置，如果 position 的值为 null，则会从当前文件指针的位置读取。 callback：回调函数，有三个参数err, bytesRead, buffer，err 为错误信息， bytesRead 表示读取的字节数，buffer 为缓冲区对象。 var fs = require(\u0026#34;fs\u0026#34;); var buf = new Buffer.alloc(1024); console.log(\u0026#34;准备打开已存在的文件！\u0026#34;); fs.open(\u0026#39;input.txt\u0026#39;, \u0026#39;r+\u0026#39;, function(err, fd) { if (err) { return console.error(err); } console.log(\u0026#34;文件打开成功！\u0026#34;); console.log(\u0026#34;准备读取文件：\u0026#34;); fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){ if (err){ console.log(err); } console.log(bytes + \u0026#34; 字节被读取\u0026#34;); // 仅输出读取的字节 if(bytes \u0026gt; 0){ console.log(buf.slice(0, bytes).toString()); } }); }); 写入 # fs.write(fd, buffer[, options], callback)\nvar fs = require(\u0026#39;fs\u0026#39;) fs.open(\u0026#39;input.txt\u0026#39;,\u0026#39;w\u0026#39;,(err,fd) =\u0026gt; { if (err) { console.error(err) } fs.write(fd,Buffer.from(\u0026#39;hello world\u0026#39;),(err,written) =\u0026gt; { console.log(written + \u0026#34; 字节被写入\u0026#34;) fs.close(fd) }) }) 注意：fs.close() 用于关闭由 fs.open 打开的文件描述符，如果文件描述符未正确关闭，会导致资源泄漏，最终耗尽可用的文件描述符，使程序无法打开新的文件或网络连接。另外我们一直说Buffer叫缓冲区，它并不是真正的磁盘，我们只有在关闭文件转换才会写入，这样能够避免文件丢失。\n监视文件 # 对文件进行监视，并且在监视到文件被修改时执行处理\nfs.watchFile(filename, [options], listener)\nfilename,：完整路径及文件名；\noptions：persistent:true表示持续监视，不退出程序；interval 单位毫秒，表示每隔多少毫秒监视一次文件\nlistener：文件发生变化时回调，有两个参数：curr为一个fs.Stat对象表示被修改后文件，prev为一个fs.Stat对象表示修改前对象\nfs.watchFile(__dirname + \u0026#39;/test.txt\u0026#39;, {interval: 20}, function (curr, prev) { if(Date.parse(prev.ctime) == 0) { console.log(\u0026#39;文件被创建!\u0026#39;); } else if(Date.parse(curr.ctime) == 0) { console.log(\u0026#39;文件被删除!\u0026#39;) } else if(Date.parse(curr.mtime) != Date.parse(prev.mtime)) { console.log(\u0026#39;文件有修改\u0026#39;); } }); fs.watchFile(__dirname + \u0026#39;/test.txt\u0026#39;, function (curr, prev) { console.log(\u0026#39;这是第二个watch,监视到文件有修改\u0026#39;); }); 取消监视器 # fs.unwatchFile(filename, [listener])\nfilename：完整路径及文件名； listener： 要取消的监听器事件，如果不指定，则取消所有监听处理事件 var listener = function (curr, prev) { console.log(\u0026#39;我是监视函数\u0026#39;) } fs.unwatchFile(__dirname + \u0026#39;/test.txt\u0026#39;, listener); 监视目录或文件 # fs.watch(filename, [options], [listener]);\nvar fsWatcher = fs.watch(__dirname + \u0026#39;/test\u0026#39;, function (event, filename) { //console.log(event) }); fsWatcher.on(\u0026#39;change\u0026#39;, function (event, filename) { console.log(filename + \u0026#39; 发生变化\u0026#39;) }); //30秒后关闭监视 setTimeout(function () { console.log(\u0026#39;关闭\u0026#39;) fsWatcher.close(function (err) { if(err) { console.error(err) } console.log(\u0026#39;关闭watch\u0026#39;) }); }, 30000); ","date":"2025-04-27","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/65df2b3b/c6d3351f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eFS 简介 \n    \u003cdiv id=\"fs-简介\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#fs-%e7%ae%80%e4%bb%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eNode.js 的文件系统模块（fs 模块）提供了丰富的 API，用于读取、写入、删除文件以及执行其他文件系统操作。\u003c/p\u003e","title":"6、文件系统","type":"posts"},{"content":"在数据分析和处理领域，Excel文件是常见的数据存储格式之一。Pandas库提供了强大的功能来读取、处理和写入Excel文件。本文将详细介绍如何使用Pandas操作Excel文件，包括读取、数据清洗、数据操作和写入等步骤。\n安装 # 首先，确保你已经安装了Pandas库以及用于读写Excel文件的库（如 openpyxl 或 xlrd）。你可以使用以下命令进行安装：\npip install pandas openpyxl 读取 Excel # 基本用法 # import pandas as pd # 读取Excel文件 df = pd.read_excel(\u0026#39;data.xlsx\u0026#39;) print(df.head()) read_excel()函数说明\npandas.read_excel(io, sheet_name=0, *, header=0, names=None, index_col=None, usecols=None, dtype=None, engine=None, converters=None, true_values=None, false_values=None, skiprows=None, nrows=None, na_values=None, keep_default_na=True, na_filter=True, verbose=False, parse_dates=False, date_parser=\u0026lt;no_default\u0026gt;, date_format=None, thousands=None, decimal=\u0026#39;.\u0026#39;, comment=None, skipfooter=0, storage_options=None, dtype_backend=\u0026lt;no_default\u0026gt;, engine_kwargs=None) 参数说明：\nio：这是必需的参数，指定了要读取的 Excel 文件的路径或文件对象。 sheet_name=0：指定要读取的工作表名称或索引。默认为0，即第一个工作表。 header=0：指定用作列名的行。默认为0，即第一行。 names=None：用于指定列名的列表。如果提供，将覆盖文件中的列名。 index_col=None：指定用作行索引的列。可以是列的名称或数字。 usecols=None：指定要读取的列。可以是列名的列表或列索引的列表。 dtype=None：指定列的数据类型。可以是字典格式，键为列名，值为数据类型。 engine=None：指定解析引擎。默认为None，pandas 会自动选择。 converters=None：用于转换数据的函数字典。 true_values=None：指定应该被视为布尔值True的值。 false_values=None：指定应该被视为布尔值False的值。 skiprows=None：指定要跳过的行数或要跳过的行的列表。 nrows=None：指定要读取的行数。 na_values=None：指定应该被视为缺失值的值。 keep_default_na=True：指定是否要将默认的缺失值（例如NaN）解析为NA。 na_filter=True：指定是否要将数据转换为NA。 verbose=False：指定是否要输出详细的进度信息。 parse_dates=False：指定是否要解析日期。 date_parser=\u0026lt;no_default\u0026gt;：用于解析日期的函数。 date_format=None：指定日期的格式。 thousands=None：指定千位分隔符。 decimal='.'：指定小数点字符。 comment=None：指定注释字符。 skipfooter=0：指定要跳过的文件末尾的行数。 storage_options=None：用于云存储的参数字典。 dtype_backend=\u0026lt;no_default\u0026gt;：指定数据类型后端。 engine_kwargs=None：传递给引擎的额外参数字典。 指定工作表 # 如果Excel文件包含多个工作表，可以使用 sheet_name 参数指定要读取的工作表\n# 读取名为 \u0026#39;Sheet1\u0026#39; 的工作表 df = pd.read_excel(\u0026#39;data.xlsx\u0026#39;, sheet_name=\u0026#39;Sheet1\u0026#39;) print(df.head()) 指定单元格范围 # 可以使用 usecols 参数指定要读取的列范围，使用 skiprows 和 nrows 参数指定要跳过的行和读取的行数\n# 读取第1到第3列，跳过前2行，读取10行 df = pd.read_excel(\u0026#39;data.xlsx\u0026#39;, usecols=\u0026#34;A:C\u0026#34;, skiprows=2, nrows=10) print(df.head()) 数据检查与预处理 # 查看数据的基本信息 # 使用 head()、tail()、info() 和 describe() 函数可以查看数据的基本信息：\nprint(df.head()) # 显示前5行 print(df.tail()) # 显示后5行 print(df.info()) # 显示数据类型和缺失值信息 print(df.describe()) # 显示统计信息 数据类型检查与转换 # 可以使用 dtypes 属性查看数据类型，并使用 astype() 函数进行类型转换\nprint(df.dtypes) df[\u0026#39;Column1\u0026#39;] = df[\u0026#39;Column1\u0026#39;].astype(\u0026#39;int\u0026#39;) 检查缺失值 # 使用 isnull() 和 sum() 函数检查缺失值\nprint(df.isnull().sum()) 处理缺失值 # 可以使用 fillna() 函数填充缺失值，或使用 dropna() 函数删除包含缺失值的行或列\n# 填充缺失值 df.fillna(0, inplace=True) # 删除包含缺失值的行 df.dropna(inplace=True) 数据清洗与转换 # 重命名列 # 使用 rename() 函数重命名列\ndf.rename(columns={\u0026#39;OldName\u0026#39;: \u0026#39;NewName\u0026#39;}, inplace=True) 删除重复数据 # 使用 drop_duplicates() 函数删除重复数据\ndf.drop_duplicates(inplace=True) 数据替换 # 使用 replace() 函数进行数据替换\ndf[\u0026#39;Column1\u0026#39;].replace(10, 20, inplace=True) 数据排序 # 使用 sort_values() 函数进行数据排序：\ndf.sort_values(by=\u0026#39;Column1\u0026#39;, ascending=False, inplace=True) 数据分组与聚合 # 使用 groupby() 和 agg() 函数进行数据分组与聚合\ngrouped = df.groupby(\u0026#39;Category\u0026#39;) result = grouped[\u0026#39;Value\u0026#39;].agg([\u0026#39;mean\u0026#39;, \u0026#39;sum\u0026#39;]) print(result) 数据选择与过滤 # 按标签选择 # 使用 loc 按标签选择数据\nsubset = df.loc[df[\u0026#39;Column1\u0026#39;] \u0026gt; 10] print(subset) 按位置选择 # subset = df.iloc[0:5, 1:3] print(subset) 布尔索引 # 使用布尔索引进行数据过滤\nsubset = df[df[\u0026#39;Column1\u0026#39;] \u0026gt; 10] print(subset) 多条件过滤 # subset = df[(df[\u0026#39;Column1\u0026#39;] \u0026gt; 10) \u0026amp; (df[\u0026#39;Column2\u0026#39;] \u0026lt; 20)] print(subset) 数据操作 # 添加、删除行、列 # 使用 insert() 函数添加行、列，使用 drop() 函数删除列\ndf.insert(1, \u0026#39;NewColumn\u0026#39;, [1, 2, 3, 4, 5]) # 删除行 df.drop(1,axis=0) # 删除列 df.drop(columns=[\u0026#39;OldColumn\u0026#39;], inplace=True) drop函数详解 # df.drop(labels = None, axis = 0, index = None, columns = None, level = None, inplace = False,errors = \u0026#39;raise\u0026#39;) 参数解释：\n**labels：**要删除的行或列的标签。如果要删除行，就传入行的标签；如果要删除列，就传入列的标签。就像你想丢掉房间里的一件衣服，labels就告诉你要丢掉哪一件。 **axis：**指定删除的方向，axis=0表示删除行，axis=1表示删除列。默认是axis=0，如果想清理掉无用的行，就设置成0；如果想去掉某些列，就设置成1。 **level：**当数据有多重索引时，可以指定要删除的层级，通常我们在处理简单的DataFrame时不太需要关心这个参数，它就像是多层文件夹中指定删除哪一层。 **inplace：**如果设置为True，原DataFrame将被直接修改；如果是False（默认值），则会返回一个新的DataFrame，原始数据保持不变。就像你在整理文件夹时，如果选择“保存”，文件会直接被更新；如果选择“另存为”，原文件不变，保存一个新版本。 **errors：**如果为\u0026rsquo;raise\u0026rsquo;（默认），当你试图删除的标签不存在时会抛出错误；如果设置为\u0026rsquo;ignore\u0026rsquo;，则忽略这些错误。这个参数就像是你在丢东西时，如果放错了地方，有人会提醒你“这件衣服不存在”；如果设置成忽略，你就不会收到任何提醒。 数据框合并 # 使用 concat()、merge() 和 join() 函数进行数据框合并\n# 使用 concat() 合并 df1 = pd.DataFrame({\u0026#39;A\u0026#39;: [1, 2], \u0026#39;B\u0026#39;: [3, 4]}) df2 = pd.DataFrame({\u0026#39;A\u0026#39;: [5, 6], \u0026#39;B\u0026#39;: [7, 8]}) result = pd.concat([df1, df2], ignore_index=True) # 使用 merge() 合并 df1 = pd.DataFrame({\u0026#39;key\u0026#39;: [\u0026#39;K0\u0026#39;, \u0026#39;K1\u0026#39;], \u0026#39;A\u0026#39;: [1, 2]}) df2 = pd.DataFrame({\u0026#39;key\u0026#39;: [\u0026#39;K0\u0026#39;, \u0026#39;K1\u0026#39;], \u0026#39;B\u0026#39;: [3, 4]}) result = pd.merge(df1, df2, on=\u0026#39;key\u0026#39;) # 使用 join() 合并 df1 = pd.DataFrame({\u0026#39;A\u0026#39;: [1, 2]}, index=[\u0026#39;K0\u0026#39;, \u0026#39;K1\u0026#39;]) df2 = pd.DataFrame({\u0026#39;B\u0026#39;: [3, 4]}, index=[\u0026#39;K0\u0026#39;, \u0026#39;K1\u0026#39;]) result = df1.join(df2) 数据透视表 # 使用 pivot_table() 函数创建数据透视表：\npivot_table = df.pivot_table(values=\u0026#39;Value\u0026#39;, index=\u0026#39;Category\u0026#39;, columns=\u0026#39;Year\u0026#39;, aggfunc=\u0026#39;mean\u0026#39;) print(pivot_table) 交叉表 # 使用 crosstab() 函数创建交叉表：\ncrosstab = pd.crosstab(df[\u0026#39;Category\u0026#39;], df[\u0026#39;Year\u0026#39;]) print(crosstab) 写入Excel文件 # 基本用法 # 使用 pd.DataFrame.to_excel() 函数将DataFrame写入Excel文件\ndf.to_excel(\u0026#39;output.xlsx\u0026#39;, index=False) to_excel()函数说明\nDataFrame.to_excel(excel_writer, *, sheet_name=\u0026#39;Sheet1\u0026#39;, na_rep=\u0026#39;\u0026#39;, float_format=None, columns=None, header=True, index=True, index_label=None, startrow=0, startcol=0, engine=None, merge_cells=True, inf_rep=\u0026#39;inf\u0026#39;, freeze_panes=None, storage_options=None, engine_kwargs=None) 参数说明：\nexcel_writer：这是必需的参数，指定了要写入的 Excel 文件路径或文件对象。 sheet_name='Sheet1'：指定写入的工作表名称，默认为 'Sheet1'。 na_rep=''：指定在 Excel 文件中表示缺失值（NaN）的字符串，默认为空字符串。 float_format=None：指定浮点数的格式。如果为 None，则使用 Excel 的默认格式。 columns=None：指定要写入的列。如果为 None，则写入所有列。 header=True：指定是否写入列名作为第一行。如果为 False，则不写入列名。 index=True：指定是否写入索引作为第一列。如果为 False，则不写入索引。 index_label=None：指定索引列的标签。如果为 None，则不写入索引标签。 startrow=0：指定开始写入的行号，默认从第0行开始。 startcol=0：指定开始写入的列号，默认从第0列开始。 engine=None：指定写入 Excel 文件时使用的引擎，默认为 None，pandas 会自动选择。 merge_cells=True：指定是否合并单元格。如果为 True，则合并具有相同值的单元格。 inf_rep='inf'：指定在 Excel 文件中表示无穷大值的字符串，默认为 'inf'。 freeze_panes=None：指定冻结窗格的位置。如果为 None，则不冻结窗格。 storage_options=None：用于云存储的参数字典。 engine_kwargs=None：传递给引擎的额外参数字典。 指定工作表名称 # 可以使用 sheet_name 参数指定工作表名称\ndf.to_excel(\u0026#39;output.xlsx\u0026#39;, sheet_name=\u0026#39;Sheet1\u0026#39;, index=False) 指定单元格位置 # df.to_excel(\u0026#39;output.xlsx\u0026#39;, startrow=1, startcol=2, index=False) 处理多个工作表 # 使用 ExcelWriter 类处理多个工作表\nwith pd.ExcelWriter(\u0026#39;output.xlsx\u0026#39;) as writer: df1.to_excel(writer, sheet_name=\u0026#39;Sheet1\u0026#39;, index=False) df2.to_excel(writer, sheet_name=\u0026#39;Sheet2\u0026#39;, index=False) 设置样式和格式 # 可以使用 openpyxl 库设置单元格样式和格式\nfrom openpyxl import Workbook from openpyxl.styles import Font with pd.ExcelWriter(\u0026#39;output.xlsx\u0026#39;, engine=\u0026#39;openpyxl\u0026#39;) as writer: df.to_excel(writer, sheet_name=\u0026#39;Sheet1\u0026#39;, index=False) workbook = writer.book worksheet = workbook[\u0026#39;Sheet1\u0026#39;] for cell in worksheet[\u0026#39;A\u0026#39;] + worksheet[\u0026#39;B\u0026#39;]: cell.font = Font(bold=True) ","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/46b8de4c/efe9ca01/","section":"文章","summary":"\u003cp\u003e在数据分析和处理领域，Excel文件是常见的数据存储格式之一。Pandas库提供了强大的功能来读取、处理和写入Excel文件。本文将详细介绍如何使用Pandas操作Excel文件，包括读取、数据清洗、数据操作和写入等步骤。\u003c/p\u003e","title":"6、pandas-excel","type":"posts"},{"content":"复选和单选的用法一样，和Button类似\nimport tkinter as tk root = tk.Tk() cb1 = tk.Checkbutton(root,text=\u0026#39;Java\u0026#39;) cb2 = tk.Checkbutton(root,text=\u0026#39;Python\u0026#39;) cb1.pack() cb2.pack() root.mainloop() ","date":"2025-04-15","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/a879fe06/ad8679f1/","section":"文章","summary":"\u003cp\u003e复选和单选的用法一样，和Button类似\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003etkinter\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nn\"\u003etk\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eTk\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ecb1\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eCheckbutton\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003etext\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;Java\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ecb2\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eCheckbutton\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003etext\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;Python\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ecb1\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epack\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ecb2\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epack\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emainloop\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20250414165822042\"\n    data-zoom-src=\"/posts/1b37041b/cf2af7cb/a879fe06/ad8679f1/image/image-20250414165822042.png\"\n    src=\"/posts/1b37041b/cf2af7cb/a879fe06/ad8679f1/image/image-20250414165822042.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"6、复选、单选框","type":"posts"},{"content":"在 Blender 中材质编辑器，也就是着色器编辑器\n","date":"2025-04-12","externalUrl":null,"permalink":"/posts/69064821/f1464f3a/c4740025/","section":"文章","summary":"\u003cp\u003e在 Blender 中材质编辑器，也就是着色器编辑器\u003c/p\u003e","title":"6、材质","type":"posts"},{"content":" 命名空间 # A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。\n命名空间(Namespace)是从名称到对象的映射，大部分的命名空间都是通过 Python 字典来实现的。\n命名空间提供了在项目中避免名字冲突的一种方法。各个命名空间是独立的，没有任何关系的，所以一个命名空间中不能有重名，但不同的命名空间是可以重名而没有任何影响。\n一般有三种命名空间：\n内置名称（built-in names）， Python 语言内置的名称，比如函数名 abs、char 和异常名称 BaseException、Exception 等等。 全局名称（global names），模块中定义的名称，记录了模块的变量，包括函数、类、其它导入的模块、模块级的变量和常量。 局部名称（local names），函数中定义的名称，记录了函数的变量，包括函数的参数和局部定义的变量。（类中定义的也是） python在查询名称时，是从内到外查询\n#此处的var1是全局名称 var1 = 10 def test(): #此处的var2是局部名称 var1 = 20 #abs是内置名称 abs(-1) 命名空间的生命周期 # 命名空间的生命周期取决于对象的作用域，如果对象执行完成，则该命名空间的生命周期就结束。\n因此，我们无法从外部命名空间访问内部命名空间的对象。\n作用域 # 变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python 的作用域一共有4种，有四种作用域\nL（Local）：最内层，包含局部变量，比如一个函数/方法内部。 E（Enclosing）：包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数，一个函数（或类） A 里面又包含了一个函数 B ，那么对于 B 中的名称来说 A 中的作用域就为 nonlocal。 G（Global）：当前脚本的最外层，比如当前模块的全局变量。 B（Built-in）： 包含了内建的变量/关键字等，最后被搜索。 全局变量和局部变量 # total = 0 def add(arg1,arg2): total = arg1 + arg2 print(\u0026#39;局部变量total-\u0026gt;%s\u0026#39; % total) add(5,6) print(\u0026#39;全局变量total-\u0026gt;%s\u0026#39; % total) \u0026#39;\u0026#39;\u0026#39; 局部变量total-\u0026gt;11 全局变量total-\u0026gt;0 \u0026#39;\u0026#39;\u0026#39; global # 如果要在函数内修改（赋值）全局变量total，那么需要使用global\nnum = 10 def show(): # 声明全局作用域变量 num global num print(\u0026#39;函数内使用全局变量：%s\u0026#39; % num) num = 100 show() print(\u0026#39;全局变量：%s\u0026#39; % num) \u0026#39;\u0026#39;\u0026#39; 函数内使用全局变量：10 全局变量：100 \u0026#39;\u0026#39;\u0026#39; nonlocal # 要修改嵌套作用域（enclosing 作用域，外层非全局作用域）中的变量则需要 nonlocal 关键字\nnum = 10 def func1(): num = 20 def func2(): nonlocal num print(num) func2() func1() \u0026#39;\u0026#39;\u0026#39; 20 \u0026#39;\u0026#39;\u0026#39; ","date":"2025-04-09","externalUrl":null,"permalink":"/posts/1b37041b/aae761a6/8beb1514/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e命名空间 \n    \u003cdiv id=\"命名空间\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%91%bd%e5%90%8d%e7%a9%ba%e9%97%b4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eA namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。\u003c/code\u003e\u003c/p\u003e","title":"6、命名空间和作用域","type":"posts"},{"content":" 什么是路由 # 路由的本质就是一些key、value组成的映射关系，每一个key都会指向一个value，多个路由（route）需要通过一个路由器（router）进行管理。\nvue中的路由，目的就是实现单页面应用（SPA - single page web application），是vue的一个插件库。\n搭建路由环境 # 1、安装vue-router\n注意：vue-router@4只能在vue3中使用，vue-router@3可以在vue2中使用\ncnpm i vue-router@3 2、src下新建router/index.js，Vue使用VueRouter，并且配置router实例\n// 该文件用于创建整个应用的route import VueRouter from \u0026#34;vue-router\u0026#34; import Vue from \u0026#39;vue\u0026#39; //使用VueRouter Vue.use(VueRouter); //创建并暴露一个VueRouter实例 export default new VueRouter({ routes: [] }) 3、main.js中引入router，并配置到Vue实例\nimport Vue from \u0026#39;vue\u0026#39; import App from \u0026#39;./App.vue\u0026#39; Vue.config.productionTip = false //引入router import router from \u0026#39;./router\u0026#39; new Vue({ render: h =\u0026gt; h(App), //配置router到Vue实例 router }).$mount(\u0026#34;#app\u0026#34;) 简单使用 # router/index.js中引入要切换的组件，并配置路径\nimport VueRouter from \u0026#34;vue-router\u0026#34; import Vue from \u0026#39;vue\u0026#39; Vue.use(VueRouter); import Home from \u0026#39;../view/Home.vue\u0026#39; import About from \u0026#39;../view/About.vue\u0026#39; export default new VueRouter({ routes: [ //重定向根路径/到/home { path: \u0026#34;/\u0026#34;, redirect: \u0026#34;/home\u0026#34; }, { path: \u0026#34;/home\u0026#34;, component: Home }, { path: \u0026#34;/about\u0026#34;, component: About } ] }) 两个组件Home.vue和About.vue\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; is About \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;About\u0026#34;, } \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;div\u0026gt; is Home \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;Home\u0026#34;, } \u0026lt;/script\u0026gt; App.vue中创建了两个路由链接和路由展示标签\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!-- roter-link相当于a标签，只不过href需要换成to，值就是roter中配置的path --\u0026gt; \u0026lt;router-link to=\u0026#34;/home\u0026#34; active-class=\u0026#34;ac\u0026#34;\u0026gt;Home\u0026lt;/router-link\u0026gt;/ \u0026lt;!-- replace：本次的浏览记录会替换掉上次的 --\u0026gt; \u0026lt;router-link replace to=\u0026#34;/about\u0026#34; active-class=\u0026#34;ac\u0026#34;\u0026gt;About\u0026lt;/router-link\u0026gt;\u0026lt;br\u0026gt; \u0026lt;!-- 用于展示路由的标签 --\u0026gt; \u0026lt;router-view/\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;App\u0026#34; } \u0026lt;/script\u0026gt; \u0026lt;style scoped\u0026gt; /* 选中样式 */ .ac{ color: red; } \u0026lt;/style\u0026gt; 注意点 # 1、开发中，一般组件和路由组件通常放在src下不同的文件夹中，易于区分。一般情况下，一般组件放在components文件夹下，路由组件放在views文件夹下\n2、组件在切换的时候才会进行创建挂载、切换到其他组件后，当前组件将被销毁\n3、在使用了vue-route插件后，所有的组件实例对象上，都会多两个属性$route（保存当前路由信息）、$router（全局路由实例）\n嵌套路由 # router/index.js\nimport VueRouter from \u0026#34;vue-router\u0026#34; import Vue from \u0026#39;vue\u0026#39; Vue.use(VueRouter); import Home from \u0026#39;../pages/Home.vue\u0026#39; import About from \u0026#39;../pages/About.vue\u0026#39; import Ab1 from \u0026#39;../pages/Ab1.vue\u0026#39; import Ab2 from \u0026#39;../pages/Ab2.vue\u0026#39; export default new VueRouter({ routes: [ { path: \u0026#34;/home\u0026#34;, component: Home }, { path: \u0026#34;/about\u0026#34;, component: About, //使用children属性声明子路由 children:[ { // 注意：对于子路由，路由不需要加/ path: \u0026#34;ab1\u0026#34;, component: Ab1 }, { path: \u0026#34;ab2\u0026#34;, component: Ab2 } ] } ] }) About.vue进行子路由的跳转和展示\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; is About\u0026lt;br\u0026gt; \u0026lt;router-link to=\u0026#34;/about/ab1\u0026#34;\u0026gt;ab1\u0026lt;/router-link\u0026gt;/ \u0026lt;router-link to=\u0026#34;/about/ab2\u0026#34;\u0026gt;ab2\u0026lt;/router-link\u0026gt; \u0026lt;router-view/\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;About\u0026#34;, } \u0026lt;/script\u0026gt; App.vue\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;router-link to=\u0026#34;/home\u0026#34; active-class=\u0026#34;ac\u0026#34;\u0026gt;Home\u0026lt;/router-link\u0026gt;/ \u0026lt;router-link to=\u0026#34;/about\u0026#34; active-class=\u0026#34;ac\u0026#34;\u0026gt;About\u0026lt;/router-link\u0026gt;\u0026lt;br\u0026gt; \u0026lt;router-view/\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;App\u0026#34;, } \u0026lt;/script\u0026gt; \u0026lt;style scoped\u0026gt; /* 选中样式 */ .ac{ color: red; } \u0026lt;/style\u0026gt; 命名路由 # 命名路由用来简化路由跳转时的完整路径（只是简化编码，并不简化地址栏显示）\nimport VueRouter from \u0026#34;vue-router\u0026#34; import Vue from \u0026#39;vue\u0026#39; Vue.use(VueRouter); import Home from \u0026#39;../pages/Home.vue\u0026#39; import About from \u0026#39;../pages/About.vue\u0026#39; import Info from \u0026#39;../pages/Info.vue\u0026#39; export default new VueRouter({ routes: [ { path: \u0026#34;/home\u0026#34;, component: Home }, { path: \u0026#34;/about\u0026#34;, component: About, children:[ {\t//配置命名路由 name: \u0026#34;abInfo\u0026#34;, path: \u0026#34;info\u0026#34;, component: Info } ] } ] }) 使用命名路由\n\u0026lt;!-- 跳转的时候，就可以直接写路由的name值 --\u0026gt; \u0026lt;router-link to=\u0026#34;abInfo\u0026#34;\u0026gt;info\u0026lt;/router-link\u0026gt;\u0026lt;br\u0026gt; 路由传参 # query # 可以对路由组件进行传参\n方式一：拼接路径字符串 # 这种方法适合传少量的参数\nAbout.vue，通过路径给子路由传参\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; 学生信息\u0026lt;br\u0026gt; \u0026lt;!-- 利用路由跳转路径传参 --\u0026gt; \u0026lt;router-link :to=\u0026#34;\u0026#39;/about/info?id=\u0026#39; + s.id + \u0026#39;\u0026amp;name=\u0026#39; + s.name + \u0026#39;\u0026amp;age=\u0026#39; + s.age\u0026#34; v-for=\u0026#34;s in stu\u0026#34; :key=\u0026#34;s.id\u0026#34;\u0026gt; {{s.name}} \u0026lt;br/\u0026gt; \u0026lt;/router-link\u0026gt; \u0026lt;router-view/\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;About\u0026#34;, data() { return { stu: [ {id: \u0026#34;001\u0026#34;,name: \u0026#34;lucy\u0026#34;,age: \u0026#34;18\u0026#34;}, {id: \u0026#34;002\u0026#34;,name: \u0026#34;tom\u0026#34;,age: \u0026#34;22\u0026#34;}, {id: \u0026#34;003\u0026#34;,name: \u0026#34;lily\u0026#34;,age: \u0026#34;16\u0026#34;}, {id: \u0026#34;004\u0026#34;,name: \u0026#34;james\u0026#34;,age: \u0026#34;23\u0026#34;}, {id: \u0026#34;005\u0026#34;,name: \u0026#34;marry\u0026#34;,age: \u0026#34;17\u0026#34;}, ], }; }, } \u0026lt;/script\u0026gt; Info.vue，通过this.$route.query接收路径中的参数\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!-- 通过this.$route.query可以获取路径中拼接的参数 --\u0026gt; 编号：{{$route.query.id}}\u0026lt;br/\u0026gt; 姓名：{{$route.query.name}}\u0026lt;br/\u0026gt; 年龄：{{$route.query.age}}\u0026lt;br/\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;Ab1\u0026#34;, } \u0026lt;/script\u0026gt; 方式二：to的对象写法 # 这种方法可以传任何的对象\nAbout.vue\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; 学生信息\u0026lt;br\u0026gt; \u0026lt;!-- 使用对象进行传递参数 --\u0026gt; \u0026lt;router-link :to=\u0026#34;{path:\u0026#39;/about/info\u0026#39;,query:{stu:s}}\u0026#34; v-for=\u0026#34;s in stu\u0026#34; :key=\u0026#34;s.id\u0026#34;\u0026gt; {{s.name}} \u0026lt;br/\u0026gt; \u0026lt;/router-link\u0026gt; \u0026lt;router-view/\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;About\u0026#34;, data() { return { stu: [ {id: \u0026#34;001\u0026#34;,name: \u0026#34;lucy\u0026#34;,age: \u0026#34;18\u0026#34;}, {id: \u0026#34;002\u0026#34;,name: \u0026#34;tom\u0026#34;,age: \u0026#34;22\u0026#34;}, {id: \u0026#34;003\u0026#34;,name: \u0026#34;lily\u0026#34;,age: \u0026#34;16\u0026#34;}, {id: \u0026#34;004\u0026#34;,name: \u0026#34;james\u0026#34;,age: \u0026#34;23\u0026#34;}, {id: \u0026#34;005\u0026#34;,name: \u0026#34;marry\u0026#34;,age: \u0026#34;17\u0026#34;}, ], }; }, } \u0026lt;/script\u0026gt; Info.vue\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!-- 通过this.$route.query可以获取query中的参数 --\u0026gt; 编号：{{$route.query.stu.id}}\u0026lt;br/\u0026gt; 姓名：{{$route.query.stu.name}}\u0026lt;br/\u0026gt; 年龄：{{$route.query.stu.age}}\u0026lt;br/\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;Ab1\u0026#34;, } \u0026lt;/script\u0026gt; params # 可以在路径直接传参，不需要k1=val\u0026amp;k2=val\n注意：这种方法，需要在路由配置中声明占位参数\n方式一：拼接路径字符串 # index.js，声明路由路径的占位参数\nexport default new VueRouter({ routes: [ { path: \u0026#34;/home\u0026#34;, component: Home }, { path: \u0026#34;/about\u0026#34;, component: About, children:[ { // 声明占位参数 path: \u0026#34;info/:id/:name/:age\u0026#34;, component: Info } ] } ] }) About.vue\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; 学生信息\u0026lt;br\u0026gt; \u0026lt;!-- 使用拼接字符串进行传递参数 --\u0026gt; \u0026lt;router-link :to=\u0026#34;\u0026#39;/about/info/\u0026#39; + s.id + \u0026#39;/\u0026#39; + s.name + \u0026#39;/\u0026#39; + s.age\u0026#34; v-for=\u0026#34;s in stu\u0026#34; :key=\u0026#34;s.id\u0026#34;\u0026gt; {{s.name}} \u0026lt;br/\u0026gt; \u0026lt;/router-link\u0026gt; \u0026lt;router-view/\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;About\u0026#34;, data() { return { stu: [ {id: \u0026#34;001\u0026#34;,name: \u0026#34;lucy\u0026#34;,age: \u0026#34;18\u0026#34;}, {id: \u0026#34;002\u0026#34;,name: \u0026#34;tom\u0026#34;,age: \u0026#34;22\u0026#34;}, {id: \u0026#34;003\u0026#34;,name: \u0026#34;lily\u0026#34;,age: \u0026#34;16\u0026#34;}, {id: \u0026#34;004\u0026#34;,name: \u0026#34;james\u0026#34;,age: \u0026#34;23\u0026#34;}, {id: \u0026#34;005\u0026#34;,name: \u0026#34;marry\u0026#34;,age: \u0026#34;17\u0026#34;}, ], }; }, } \u0026lt;/script\u0026gt; Info.vue\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!-- 通过this.$route.params可以获取路径中拼接的参数 --\u0026gt; 编号：{{$route.params.id}}\u0026lt;br/\u0026gt; 姓名：{{$route.params.name}}\u0026lt;br/\u0026gt; 年龄：{{$route.params.age}}\u0026lt;br/\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;Ab1\u0026#34;, } \u0026lt;/script\u0026gt; 方式二：to的对象写法 # 注意：此处必须使用命名路由，不可以使用path\nindex.js，声明命名路由和占位参数\nexport default new VueRouter({ routes: [ { path: \u0026#34;/home\u0026#34;, component: Home }, { path: \u0026#34;/about\u0026#34;, component: About, children:[ { name: \u0026#34;abInfo\u0026#34;, path: \u0026#34;info/:id/:name/:age\u0026#34;, component: Info } ] } ] }) About.vue\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; 学生信息\u0026lt;br\u0026gt; \u0026lt;!-- 使用对象进行传递参数 --\u0026gt; \u0026lt;router-link :to=\u0026#34;{name:\u0026#39;abInfo\u0026#39;,params: {id:s.id,name:s.name,age:s.age}}\u0026#34; v-for=\u0026#34;s in stu\u0026#34; :key=\u0026#34;s.id\u0026#34;\u0026gt; {{s.name}} \u0026lt;br/\u0026gt; \u0026lt;/router-link\u0026gt; \u0026lt;router-view/\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;About\u0026#34;, data() { return { stu: [ {id: \u0026#34;001\u0026#34;,name: \u0026#34;lucy\u0026#34;,age: \u0026#34;18\u0026#34;}, {id: \u0026#34;002\u0026#34;,name: \u0026#34;tom\u0026#34;,age: \u0026#34;22\u0026#34;}, {id: \u0026#34;003\u0026#34;,name: \u0026#34;lily\u0026#34;,age: \u0026#34;16\u0026#34;}, {id: \u0026#34;004\u0026#34;,name: \u0026#34;james\u0026#34;,age: \u0026#34;23\u0026#34;}, {id: \u0026#34;005\u0026#34;,name: \u0026#34;marry\u0026#34;,age: \u0026#34;17\u0026#34;}, ], }; }, } \u0026lt;/script\u0026gt; Info.vue\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!-- 通过this.$route.params可以获取路径中拼接的参数 --\u0026gt; 编号：{{$route.params.id}}\u0026lt;br/\u0026gt; 姓名：{{$route.params.name}}\u0026lt;br/\u0026gt; 年龄：{{$route.params.age}}\u0026lt;br/\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;Ab1\u0026#34;, } \u0026lt;/script\u0026gt; props # 可以让路由更加简便的接收参数\nexport default new VueRouter({ routes: [ { path: \u0026#34;/home\u0026#34;, component: Home }, { path: \u0026#34;/about\u0026#34;, component: About, children:[ { name: \u0026#34;abInfo\u0026#34;, path: \u0026#34;info/:id/:name/:age\u0026#34;, component: Info, //1、对象写法，该对象中的所有属性，可以作为组件Info的data使用 //props: {a:1,b:2} //2、布尔写法，如果为ture，就会将路由参数params所有值，以props形式传给Info组件 //props: ture //3、函数写法，参数是$route，返回值对象中的所有属性，可以作为组件Info的data使用 props(route){ return {id:\u0026#34;001\u0026#34;,name:\u0026#34;lucy\u0026#34;} } } ] } ] }) 编程式路由跳转 # 除了使用router-link标签实现路由跳转，还可以使用js代码完成跳转。由于所有的组件实例对象都有一个$router属性，就可以通过这个属性来操作跳转。\n//push、replace传参和在router-link中to属性值一样 //1、直接传入路径 this.$router.push(\u0026#34;/home\u0026#34;); this.$router.replace(\u0026#34;/about\u0026#34;); //2、传入对象 this.$router.push({ path:\u0026#39;/about/info\u0026#39;, query:{stu:s} }); this.$router.replace({ name:\u0026#39;abInfo\u0026#39;, params: { id:s.id, name:s.name, age:s.age } }); //back可以后退一步 this.$router.back() //forward可以前进一步 this.$router.forward() //go可以跳转历史，前进指定的步数 //后退两步 this.$router.go(-2) 避免路由重复控制台报错 # 在router的index.js中添加，避免路由重复push导致控制台爆红Avoided redundant navigation to current location\nconst originalPush = VueRouter.prototype.push VueRouter.prototype.push = function push(location) { return originalPush.call(this, location).catch(err =\u0026gt; err) } 缓存路由组件 # 由于组件在切换的时候，老的组件会被销毁，组件中的数据会丢失；但是在实际开发中，例如表单数据，希望在切换组件后，再回来还能保持，那么就需要使用缓存路由组件\n\u0026lt;!-- 使用该标签，默认情况下，router-view中展示的所有组件都会被销毁 --\u0026gt; \u0026lt;!-- 也可以使用include属性，指定需要缓存的组件name --\u0026gt; \u0026lt;!-- 对于多个组件，可以使用逗号分隔，也可以使用v-bind --\u0026gt; \u0026lt;keep-alive include=\u0026#34;Home\u0026#34;\u0026gt; \u0026lt;router-view/\u0026gt; \u0026lt;/keep-alive\u0026gt; 路由生命周期 # 路由组件除了有普通组件的生命周期钩子外，还有自己独有的钩子\nactivated # 路由组件被激活（显示）\ndeactivated # 路由组件失活（切换到其他组件）\n路由守卫 # 注意：在实际开发中，路由守卫进行校验，如果都是用name、path进行校验，那么不容易维护，而且逻辑混乱，这个时候，我们可以例如route的meta属性（路由元信息），添加一个boolean类型的参数，来判断该路由是否需要校验。\n全局前置 # 在router/index.js中对router对象进行配置，在路由的切换前进行回调（路径不发生变化），一般用于对路由切换的合法性进行验证\nrouter.beforeEach(function(to,from,next){})，beforeEach接收一个回调函数，这个函数有三个参数\nto：想要切换到的目标route对象\nfrom：来源的route对象\nnext：是一个函数，如果调用的话，说明放行（进行切换）\n全局后置 # 在router/index.js中对router对象进行配置，在路由的切换后进行回调（路径已经变化），一般用于组件切换后的渲染，例如title的变化\nrouter.afterEach(function(to,from){})，afterEach接收一个回调函数，这个函数有两个参数\nto：想要切换到的目标route对象\nfrom：来源的route对象\n独享 # 在route配置项中进行配置，只对当前的route生效\n独享路由守卫只有前置beforeEnter(function(to,from,next){})，参数使用和全局前置一样，没有后置\n组件内 # 直接声明在路由组件的配置项中，注意：beforeRouteEnter中，不能获取组件实例对象this\nbeforeRouteEnter(to,from,next){}，通过路由规则进入当前组件时进行调用\nbeforeRouteLeave(to,from,next){}，通过路由规则离开当前组件时进行调用\nhash模式和history模式 # hash模式 hash模式的路径一般格式为http://localhost:8080/#/search 其中#后面的路径都为hash路径，这一部分路径不会传给服务器 优点：兼容性一般比history好 history模式 history模式的路径格式一般为http://localhost:8080/search 请求时，全路径都会做为资源路径发送给服务器 优点： 切换模式 vue-router中默认为hash模式 可以通过router的mode选项进行切换 注意：如果使用history模式，可能会导致路由刷新404，这是因为浏览器向服务器发送了请求，而服务器没有这个资源，解决方式，例如nginx，添加如下配置\n# 解决vue路由模式为history时刷新返回404的问题 location / { root /usr/share/nginx/html; index /index.html; // 找不到文件则尝试直接返回index.html，而vue路由会监控地址栏的俩变化，因此还是在当前页刷新的效果 try_files $uri $uri/ /index.html; } 路由懒加载 # import Vue from \u0026#39;vue\u0026#39; import VueRouter from \u0026#39;vue-router\u0026#39; Vue.use(VueRouter) const routes = [ { path: \u0026#39;/\u0026#39;, name: \u0026#39;home\u0026#39;, component: () =\u0026gt; import(\u0026#39;../views/Home.vue\u0026#39;) //进行l }, { path: \u0026#39;/about\u0026#39;, name: \u0026#39;about\u0026#39;, component: () =\u0026gt; import(\u0026#39;../views/About.vue\u0026#39;) } ] const router = new VueRouter({ routes }) export default router ","date":"2025-03-03","externalUrl":null,"permalink":"/posts/bafd68f1/b2321cb9/27adf556/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是路由 \n    \u003cdiv id=\"什么是路由\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af%e8%b7%af%e7%94%b1\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e路由的本质就是一些\u003ccode\u003ekey\u003c/code\u003e、\u003ccode\u003evalue\u003c/code\u003e组成的映射关系，每一个\u003ccode\u003ekey\u003c/code\u003e都会指向一个value，多个路由（route）需要通过一个路由器（router）进行管理。\u003c/p\u003e","title":"6、路由","type":"posts"},{"content":" HttpClient # HttpClient是客户端的http通信实现库，这个类库的作用是接收和发送http报文，使用这个类库，它相比传统的 HttpURLConnection，增加了易用性和灵活性，我们对于http的操作会变得简单一些。\nHttpClient是Apache Jakarta Common下的子项目，用来提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包，并且它支持HTTP协议最新的版本和建议。HttpClient已经应用在很多的项目中，比如Apache Jakarta上很著名的另外两个开源项目Cactus和HTMLUnit都使用了HttpClient。\nHttpClient 支持了在 HTTP / 1.1 规范中定义的所有 HTTP 方法：GET，HEAD，POST，PUT，DELETE，TRACE 和 OPTIONS。对于每个方法类型，都有一个特定的类来支持：HttpGet， HttpHead，HttpPost， HttpPut，HttpDelete， HttpTrace，和 HttpOptions。\n官网地址：https://hc.apache.org/\nmaven仓库：https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.apache.httpcomponents\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;httpclient\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;4.5.13\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; HttpClient使用步骤 # 创建一个 HttpClient 对象 CloseableHttpClient httpClient = HttpClients.createDefault(); 官方推荐使用CloseableHttpClient 创建一个 HttpRequest 对象 HttpGet httpGet = new HttpGet(\u0026quot;https://www.ygang.top\u0026quot;); 使用 HttpClient 来执行 HttpRequest请求，得到对方的 HttpResponse HttpResponse response = HttpClient.execute(httpGet); 处理 HttpResponse HttpEntity resEntity = response.getEntity(); 关闭此次请求连接 response.close(); httpClient.close(); HttpEntity # Entity 是HttpClient中的一个特别的概念，所有的Entity都实现了HttpEntity 接口，输入是一个Entity，输出也是一个Entity 。这和HttpURLConnection的流有些不同，但是基本理念是相通的。HttpClient提供给我们一个工具类 EntityUtils，可以很方便的操作Entity。\nDemo # Get # String urlTest = \u0026#34;https://www.ygang.top\u0026#34;; // 1、创建httpclient CloseableHttpClient httpclient = HttpClients.createDefault(); // 2、创建HttpGet HttpGet httpGetTest1 = new HttpGet(urlTest); // 3、请求执行，获取响应 CloseableHttpResponse response = httpclient.execute(httpGetTest1); System.out.println(response); // 4、获取响应实体 HttpEntity entityTest = response.getEntity(); System.out.println(EntityUtils.toString(entityTest,\u0026#34;utf-8\u0026#34;)); // 5、关闭资源 response.close(); httpclient.close(); URIBuilder # HttpGet httpget = new HttpGet(\u0026#34;http://www.google.com/search?h1=1234\u0026amp;h2=5678\u0026#34;); // 使用URLBuilder来简化URI的创建 URI uri = null; try { uri = new URIBuilder() .setScheme(\u0026#34;http\u0026#34;) .setHost(\u0026#34;www.google.com\u0026#34;) .setPath(\u0026#34;/search\u0026#34;) .setParameter(\u0026#34;h1\u0026#34;, \u0026#34;1234\u0026#34;) .setParameter(\u0026#34;h2\u0026#34;, \u0026#34;5678\u0026#34;) .build(); } catch (URISyntaxException e) { e.printStackTrace(); } HttpGet httpget = new HttpGet(uri); Post # String url = \u0026#34;https://www.ygang.top\u0026#34;; String jsonString = \u0026#34;{\\\u0026#34;name\\\u0026#34;:\\\u0026#34;tom\\\u0026#34;}\u0026#34;; // 1、创建HttpClient CloseableHttpClient httpClient = HttpClients.createDefault(); // 2、创建HttpPost HttpPost httpPost = new HttpPost(url); // 3、设置请求头 httpPost.setHeader(\u0026#34;x-appid\u0026#34;,\u0026#34;test\u0026#34;); httpPost.setHeader(\u0026#34;Content-type\u0026#34;,\u0026#34;application/json\u0026#34;); // 4、设置请求体 StringEntity requestEntity = new StringEntity(jsonString, StandardCharsets.UTF_8); httpPost.setEntity(requestEntity); // 5、执行请求 CloseableHttpResponse response = httpClient.execute(httpPost); // 6、解析响应数据 HttpEntity responseEntity = response.getEntity(); String result = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8); System.out.println(result); // 7、关闭资源 response.close(); httpClient.close(); ","date":"2025-01-16","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/0f3856f3/7f9aa377/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eHttpClient \n    \u003cdiv id=\"httpclient\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#httpclient\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eHttpClient是客户端的http通信实现库，这个类库的作用是接收和发送http报文，使用这个类库，它相比传统的 HttpURLConnection，增加了易用性和灵活性，我们对于http的操作会变得简单一些。\u003c/p\u003e","title":"6、HttpClient","type":"posts"},{"content":"在 C# 中，您可以使用字符数组来表示字符串，但是，更常见的做法是使用 string 关键字来声明一个字符串变量。string 关键字是 System.String 类的别名。\n创建字符串 # 您可以使用以下方法之一来创建 string 对象：\n通过给 String 变量指定一个字符串 通过使用 String 类构造函数 通过使用字符串串联运算符（ + ） 通过检索属性或调用一个返回字符串的方法 通过格式化方法来转换一个值或对象为它的字符串表示形式 using System; namespace StringApplication { class Program { static void Main(string[] args) { //字符串，字符串连接 string fname, lname; fname = \u0026#34;Rowan\u0026#34;; lname = \u0026#34;Atkinson\u0026#34;; string fullname = fname + lname; Console.WriteLine(\u0026#34;Full Name: {0}\u0026#34;, fullname); // Full Name: RowanAtkinson //通过使用 string 构造函数 char[] letters = { \u0026#39;H\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;l\u0026#39;, \u0026#39;l\u0026#39;,\u0026#39;o\u0026#39; }; string greetings = new string(letters); Console.WriteLine(\u0026#34;Greetings: {0}\u0026#34;, greetings); // Greetings: Hello //方法返回字符串 string[] sarray = { \u0026#34;Hello\u0026#34;, \u0026#34;From\u0026#34;, \u0026#34;Tutorials\u0026#34;, \u0026#34;Point\u0026#34; }; string message = String.Join(\u0026#34; \u0026#34;, sarray); Console.WriteLine(\u0026#34;Message: {0}\u0026#34;, message); // Message: Hello From Tutorials Point //用于转化值的格式化方法 DateTime waiting = new DateTime(2012, 10, 10, 17, 58, 1); string chat = String.Format(\u0026#34;Message sent at {0:t} on {0:D}\u0026#34;, waiting); Console.WriteLine(\u0026#34;Message: {0}\u0026#34;, chat); // Message: Message sent at 17:58 on Wednesday, 10 October 2012 Console.ReadKey() ; } } } 属性 # 序号 属性名称 \u0026amp; 描述 1 Chars 在当前 String 对象中获取 Char 对象的指定位置。 2 Length 在当前的 String 对象中获取字符数。 方法 # 序号 方法名称 \u0026amp; 描述 1 public static int Compare( string strA, string strB ) 比较两个指定的 string 对象，并返回一个表示它们在排列顺序中相对位置的整数。该方法区分大小写。 2 public static int Compare( string strA, string strB, bool ignoreCase ) 比较两个指定的 string 对象，并返回一个表示它们在排列顺序中相对位置的整数。但是，如果布尔参数为真时，该方法不区分大小写。 3 public static string Concat( string str0, string str1 ) 连接两个 string 对象。 4 public static string Concat( string str0, string str1, string str2 ) 连接三个 string 对象。 5 public static string Concat( string str0, string str1, string str2, string str3 ) 连接四个 string 对象。 6 public bool Contains( string value ) 返回一个表示指定 string 对象是否出现在字符串中的值。 7 public static string Copy( string str ) 创建一个与指定字符串具有相同值的新的 String 对象。 8 public void CopyTo( int sourceIndex, char[] destination, int destinationIndex, int count ) 从 string 对象的指定位置开始复制指定数量的字符到 Unicode 字符数组中的指定位置。 9 public bool EndsWith( string value ) 判断 string 对象的结尾是否匹配指定的字符串。 10 public bool Equals( string value ) 判断当前的 string 对象是否与指定的 string 对象具有相同的值。 11 public static bool Equals( string a, string b ) 判断两个指定的 string 对象是否具有相同的值。 12 public static string Format( string format, Object arg0 ) 把指定字符串中一个或多个格式项替换为指定对象的字符串表示形式。 13 public int IndexOf( char value ) 返回指定 Unicode 字符在当前字符串中第一次出现的索引，索引从 0 开始。 14 public int IndexOf( string value ) 返回指定字符串在该实例中第一次出现的索引，索引从 0 开始。 15 public int IndexOf( char value, int startIndex ) 返回指定 Unicode 字符从该字符串中指定字符位置开始搜索第一次出现的索引，索引从 0 开始。 16 public int IndexOf( string value, int startIndex ) 返回指定字符串从该实例中指定字符位置开始搜索第一次出现的索引，索引从 0 开始。 17 public int IndexOfAny( char[] anyOf ) 返回某一个指定的 Unicode 字符数组中任意字符在该实例中第一次出现的索引，索引从 0 开始。 18 public int IndexOfAny( char[] anyOf, int startIndex ) 返回某一个指定的 Unicode 字符数组中任意字符从该实例中指定字符位置开始搜索第一次出现的索引，索引从 0 开始。 19 public string Insert( int startIndex, string value ) 返回一个新的字符串，其中，指定的字符串被插入在当前 string 对象的指定索引位置。 20 public static bool IsNullOrEmpty( string value ) 指示指定的字符串是否为 null 或者是否为一个空的字符串。 21 public static string Join( string separator, string[] value ) 连接一个字符串数组中的所有元素，使用指定的分隔符分隔每个元素。 22 public static string Join( string separator, string[] value, int startIndex, int count ) 连接一个字符串数组中的指定位置开始的指定元素，使用指定的分隔符分隔每个元素。 23 public int LastIndexOf( char value ) 返回指定 Unicode 字符在当前 string 对象中最后一次出现的索引位置，索引从 0 开始。 24 public int LastIndexOf( string value ) 返回指定字符串在当前 string 对象中最后一次出现的索引位置，索引从 0 开始。 25 public string Remove( int startIndex ) 移除当前实例中的所有字符，从指定位置开始，一直到最后一个位置为止，并返回字符串。 26 public string Remove( int startIndex, int count ) 从当前字符串的指定位置开始移除指定数量的字符，并返回字符串。 27 public string Replace( char oldChar, char newChar ) 把当前 string 对象中，所有指定的 Unicode 字符替换为另一个指定的 Unicode 字符，并返回新的字符串。 28 public string Replace( string oldValue, string newValue ) 把当前 string 对象中，所有指定的字符串替换为另一个指定的字符串，并返回新的字符串。 29 public string[] Split( params char[] separator ) 返回一个字符串数组，包含当前的 string 对象中的子字符串，子字符串是使用指定的 Unicode 字符数组中的元素进行分隔的。 30 public string[] Split( char[] separator, int count ) 返回一个字符串数组，包含当前的 string 对象中的子字符串，子字符串是使用指定的 Unicode 字符数组中的元素进行分隔的。int 参数指定要返回的子字符串的最大数目。 31 public bool StartsWith( string value ) 判断字符串实例的开头是否匹配指定的字符串。 32 public char[] ToCharArray() 返回一个带有当前 string 对象中所有字符的 Unicode 字符数组。 33 public char[] ToCharArray( int startIndex, int length ) 返回一个带有当前 string 对象中所有字符的 Unicode 字符数组，从指定的索引开始，直到指定的长度为止。 34 public string ToLower() 把字符串转换为小写并返回。 35 public string ToUpper() 把字符串转换为大写并返回。 36 public string Trim() 移除当前 String 对象中的所有前导空白字符和后置空白字符。 ","date":"2024-09-11","externalUrl":null,"permalink":"/posts/5ab2821f/51590480/","section":"文章","summary":"\u003cp\u003e在 C# 中，您可以使用字符数组来表示字符串，但是，更常见的做法是使用 \u003cstrong\u003estring\u003c/strong\u003e 关键字来声明一个字符串变量。string 关键字是 \u003cstrong\u003eSystem.String\u003c/strong\u003e 类的别名。\u003c/p\u003e","title":"6、字符串","type":"posts"},{"content":"Cocos Creator 的物理系统提供了高效的组件化工作流程和便捷的使用方法。目前支持刚体、碰撞组件、触发和碰撞事件、物理材质、射线检测等等特性。\n物理引擎 # 物理引擎主要包含以下四种：\nbuiltin builtin 内置物理引擎 仅有碰撞检测 的功能。相对于其它的物理引擎，它没有复杂的物理模拟计算（比如反弹、惯性等），如果您的项目不需要这一部分的物理模拟，那么建议使用 builtin，使游戏的包体更小。 cannon 是一个开源的物理引擎，使用 JavaScript 开发并实现了比较全面的物理模拟功能。cannon.js 模块大小约为 141KB。 当选择的物理引擎为 cannon.js 时，需要在节点上添加 刚体组件 才能进行物理模拟。然后再根据需求添加 碰撞组件，该节点就会增加相应的碰撞体，用于检测是否与其它碰撞体产生碰撞。 bullet（ammo.js） ammo.js 模块较大（约 1.5MB），但它具有完善的物理功能，以及更佳的性能，未来我们也将在此投入更多工作。 PhysX 是由英伟达公司开发的开源实时商业物理引擎，它具有完善的功能特性和极高的稳定性，同时也兼具极佳的性能表现。 但由于 PhysX 目前的包体过于庞大（约 5MB）以及自身的一些限制，导致部分平台无法得到良好支持，包括： 各类有包体限制的小游戏平台 安卓 x86 设备 可以在（项目 - 项目设置 - 功能裁剪）中设置项目使用的物理引擎，若不需要用到任何物理相关的组件和接口，可以取消物理系统选项的勾选，使游戏的包体更小。\n主要针对各类小游戏平台和原生平台，并对使用 Bullet 和 PhysX 物理时的性能进行了对比：\n在原生和抖音小游戏平台上，使用 PhysX 物理可以得到更加良好的性能。 在各类小游戏平台上，使用 Bullet 物理可以得到更加良好的性能。 物理系统配置 # 物理系统模块（PhysicsSystem）用于管理整个物理系统，负责同步物理元素、触发物理事件和调度物理世界的迭代。\n有两种办法可以配置物理系统，一种是在编辑器中配置，另一种是通过代码配置。\n通过物理配置面板 # 通过（项目设置 - 物理配置）对物理系统进行相关配置。\n属性 说明 Gravity X 重力矢量，设置 x 分量上的重力值 Gravity Y 重力矢量，设置 y 分量上的重力值 Gravity Z 重力矢量，设置 z 分量上的重力值 AllowSleep 是否允许系统进入休眠状态，默认值 true SleepThreshold 进入休眠的默认速度临界值，默认值 0.1，最小值 0 AutoSimulation 是否开启自动模拟, 默认值 true FixedTimeStep 每步模拟消耗的固定时间，默认值 1/60，最小值 0 MaxSubSteps 每步模拟的最大子步数，默认值 1，最小值 0 Friction 摩擦系数，默认值 0.5 RollingFriction 滚动摩擦系数，默认值 0.1 SpinningFriction 自旋摩擦系数，默认值 0.1 Restitution 弹性系数，默认值 0.1 CollisionMatrix 碰撞矩阵，仅用于初始化 程序化配置 # 程序化配置目前可以通过直接访问 PhysicsSystem.instance 对物理系统进行配置。部分代码示例如下：\nimport { _decorator, Component, Node, Vec3, PhysicsSystem } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; @ccclass(\u0026#39;Example\u0026#39;) export class Example extends Component { start () { PhysicsSystem.instance.enable = true; PhysicsSystem.instance.gravity = new Vec3(0, -10, 0); PhysicsSystem.instance.allowSleep = false; } } 碰撞矩阵 # 碰撞矩阵是分组和掩码功能的进一步封装，它用于初始化物理元素的分组和掩码。\n碰撞矩阵默认情况下只有一个 DEFAULT 分组，新建分组默认不与其它组碰撞。\n点击 + 按钮可以新增分组。新增分组的 Index 和 Name 均为必填。\nIndex 代表的是碰撞分组值， 最高支持 32 位，即数值范围为 [0, 31)。分组值不可重复。 Name 代表的是碰撞分组名。此处在这里设置的名字只是为了用户进行碰撞分组配置方便，无法通过代码获取，代码能获取到的只有分组值。 如上图就是飞机大战的碰撞矩阵\nindex 1的意思就是：self_plane：玩家飞机可以与enemy_plane、enemy_bullet发生碰撞\n配置完成碰撞矩阵之后，就可以对需要产生碰撞的对象添加 刚体（RigidBody） 组件，设置碰撞分组 Group。\n通常，在游戏开发中，需要在碰撞发生前设置好可碰撞分组，在碰撞发生时处理相关的逻辑。在 Cocos Creator 中，所有的碰撞数据获取到的是数值，这样不利于开发过程中的判断。因此，可以通过定义分组对象或者枚举的形式，清晰的知道每一串数字的意义。\n// 常量 export class Constant { public static CollisionType = { self_plane: 1 \u0026lt;\u0026lt; 1, enemy_plane: 1 \u0026lt;\u0026lt; 2, self_bullet: 1 \u0026lt;\u0026lt; 3, enemy_bullet: 1 \u0026lt;\u0026lt; 4 } } 掩码 # 可以理解为，可以与当前组件发生碰撞的组件实际二进制值，根据上图的配置，Cocos Creator 会将数据解析为以下值：\nDEFAULT：Index 值为 0，分组实际值为 1\u0026lt;\u0026lt;0=1，二进制值为 0000 0001；掩码值实际值为 1\u0026lt;\u0026lt;0=1，二进制值为 0000 0001。 self_plane：Index 值为 1，分组实际值为 1\u0026lt;\u0026lt;1=2，二进制为 0000 0010；掩码值实际值为 (1\u0026lt;\u0026lt;3)+(1\u0026lt;\u0026lt;4)=24，二进制值为 0001 1000。 enemy_bullet：Index 值为 4，分组实际值为 1\u0026lt;\u0026lt;4=16，二进制为 0001 0000；掩码值实际值为 1\u0026lt;\u0026lt;1=2，二进制值为 0000 0010。 通过程序设置 # // 获取分组 const rigid = this.node.getComponent(RigidBody); // 设置掩码，等价于 rigid.setGroup(1 \u0026lt;\u0026lt; 1) 或 rigid.setGroup(1) rigid.setGroup(Constant.CollisionType.self_plane); // 获取分组，二进制值 const group = rigid.getGroup(); // 如果当前分组并未在碰撞矩阵中定义，也可以动态添加 const group = 1 \u0026lt;\u0026lt; 7; const rigid = this.getComponent(RigidBody); rigid.addGroup(group); rigid.removeGroup(group); // 设置和获取掩码 const rigid = this.getComponent(RigidBody); const mask = (1 \u0026lt;\u0026lt; 0) + (1 \u0026lt;\u0026lt; 1); // 等价于 1 \u0026lt;\u0026lt; 0 | 1 \u0026lt;\u0026lt; 1 rigid.setMask(mask); rigid.getMask(); 物理组件 # 碰撞组件 # 碰撞组件可用于定义需要进行物理碰撞的物体形状，不同的几何形状拥有不同的属性。碰撞体通常分为以下几种：\n基础碰撞体。常见的包含 盒（BoxCollider)、球（SphereCollider)、圆柱（CylinderCollider)、圆锥（ConeCollider)、胶囊（CapsuleCollider) 碰撞体。 复合碰撞体。可以通过在一个节点身上添加一个或多个基础碰撞体，简易模拟游戏对象形状，同时保持较低的性能开销。 网格碰撞体（MeshCollider)。根据物体网格信息生成碰撞体，完全的贴合网格。 单纯形碰撞体（SimplexCollider)。提供点、线、三角面、四面体碰撞。 平面碰撞体（PlaneCollider)。可以代表无限平面或半空间。这个形状只能用于静态的、非移动的物体。 地形碰撞体（TerrainCollider)。一种用于凹地形的特殊支持。 添加碰撞体 # 例如盒碰撞器组件，新建一个 3D 对象 Cube，在 资源管理器 中点击左上角的 + 创建按钮，然后选择 创建 -\u0026gt; 3D 对象 -\u0026gt; Cube 立方体。在右侧的 属性检查器 面板下方点击 添加组件 按钮，选择 Physics -\u0026gt; BoxCollider 添加一个碰撞器组件。\n也可以使用代码添加\nimport { BoxCollider } from \u0026#39;cc\u0026#39; const boxCollider = this.node.addComponent(BoxCollider); 属性 说明 Attached 碰撞器所绑定的刚体 Material 碰撞器所使用的物理材质，未设置时使用引擎默认的物理材质 IsTrigger 是否为触发器，触发器不会产生物理反馈 刚体组件 # 刚体是组成物理世界的基本对象，它可以使游戏对象的运动方式受物理控制。例如：刚体可以使游戏对象受重力影响做自由下落，也可以在力和扭矩的作用下，让游戏对象模拟真实世界的物理现象。\n什么情况下需要添加刚体 # 配置碰撞分组并让其生效。 物体需要具备运动学或动力学行为 添加刚体 # 点击 属性检查器 下方的 添加组件 -\u0026gt; Physics -\u0026gt; RigidBody，即可添加刚体组件到节点上。\n也可以使用代码进行添加\nimport { RigidBody } from \u0026#39;cc\u0026#39; // 添加刚体 const rigidbody = this.node.addComponent(RigidBody); // 获取刚体 const rigidBody = this.node.getComponent(RigidBody); 属性 说明 Group 刚体分组 Type 刚体类型。 DYNAMIC：动力学 STATIC：静态 KINEMATIC：运动学 刚体具有以下属性：\nGroup：刚体分组 Type：刚体类型 STATIC：静态刚体。可以是手动设置刚体类型的游戏对象，也可以是具有碰撞体而没有刚体的游戏对象。如果一个节点默认只添加了碰撞器而没有添加刚体，那么这个节点可以认为默认使用的是 静态刚体。静态刚体在大多数情况下用于一些始终停留在一个地方，不会轻易移动的游戏物体，例如：建筑物。若物体需要持续运动，应设置为 KINEMATIC 类型。静态刚体与其他物体发生碰撞时，不会产生物理行为，因此，也不会移动。 DYNAMIC：动力学刚体。刚体碰撞完全由物理引擎模拟，可以通过 力的作用 运动物体（需要保证质量大于 0）。例如：斯诺克游戏击球后，母球滚动与其他球撞击； KINEMATIC：运动学刚体。具有碰撞体和运动刚体，可以直接通过移动刚体对象的变换属性，但不会像动力学刚体一样响应力和碰撞，通常用于表达电梯这类平台运动的物体。它与静态刚体类似，不同的地方在于移动的运动刚体会对其他对象施加摩擦力，并在接触时唤醒其他刚体。 控制刚体 # 针对不同的类型，让刚体运动的方式不同：\n对于静态刚体（STATIC），应当尽可能保持物体静止，但仍然可以通过变换（位置、旋转等）来改变物体的位置。 对于运动学刚体（KINEMATIC），应当通过改变变换（位置、旋转等）使其运动。 对于动力学（DYNAMIC）刚体，需要改变其速度，有以下几种方式： 1、通过重力 # 刚体组件提供了 UseGravity 属性，需要使用重力时候，需将 UseGravity 属性设置为 true。\n2、通过施加力 # 刚体组件提供了 applyForce 接口，根据牛顿第二定律，可对刚体某点上施加力来改变物体的原有状态。\nimport { math } from \u0026#39;cc\u0026#39; rigidBody.applyForce(new math.Vec3(200, 0, 0)); 3、通过施加扭矩 # 刚体组件提供了 applyTorque 接口，可用于改变刚体的角速度。\nrigidBody.applyTorque(new math.Vec3(200, 0, 0)); 4、通过施加冲量 # 刚体组件提供了 applyImpulse 接口，施加冲量到刚体上的一个点，根据动量定理，将立即改变刚体的线性速度。 如果冲量施加到的点在力方向上的延长线不过刚体的质心，那么将产生一个非零扭矩并影响刚体的角速度。\nrigidBody.applyImpulse(new math.Vec3(5, 0, 0)); 5、通过改变速度 # 刚体组件提供了 setLinearVelocity 接口，可用于改变线性速度。\nrigidBody.setLinearVelocity(new math.Vec3(5, 0, 0)); 刚体组件提供了 setAngularVelocity 接口，可用于改变旋转速度。\nrigidBody.setAngularVelocity(new math.Vec3(5, 0, 0)); 限制刚体的运动 # 1、通过休眠 # 休眠刚体时，会将刚体所有的力和速度清空，使刚体停下来。\nif (rigidBody.isAwake) { rigidBody.sleep(); } 唤醒刚体时，刚体的力和速度将会恢复。\nif (rigidBody.isSleeping) { rigidBody.wakeUp(); } 注意：执行部分接口，例如施加力或冲量、改变速度、分组和掩码会尝试唤醒刚体。\n2、通过阻尼 # 刚体组件提供了 linearDamping 线性阻尼和 angularDamping 旋转阻尼属性，可以通过 linearDamping 和 angularDamping 方法对其获取或设置。\n阻尼参数的范围建议在 0 到 1 之间，0 意味着没有阻尼，1 意味着满阻尼。\nif (rigidBody) { rigidBody.linearDamping = 0.5; let linearDamping = rigidBody.linearDamping; rigidBody.angularDamping = 0.5; let angularDamping = rigidBody.angularDamping; } 恒力组件 # 恒力组件是一个工具组件，依赖于刚体组件，每帧都会对一个刚体施加给定的力和扭矩。\n属性 说明 force 在世界坐标系中，对刚体施加的力 localForce 在本地坐标系中，对刚体施加的力 torque 在世界坐标系中，对刚体施加的扭转力 localTorque 在本地坐标系中，对刚体施加的扭转力 约束 # 在物理引擎中，约束 用于模拟物体间的连接情况，如连杆、绳子、弹簧或者布娃娃等。\n约束依赖刚体组件，若节点无刚体组件，则添加约束时，引擎会自动添加刚体组件。\n物理事件 # 触发器与碰撞器 # 碰撞组件属性 IsTrigger 决定了组件为触发器还是碰撞器。将 IsTrigger 设置为 true 时，组件为触发器。触发器只用于碰撞检测和触发事件，会被物理引擎忽略。默认设置 false，组件为碰撞器，可以结合刚体产生碰撞效果。\n两者的区别如下：\n触发器不会与其它触发器或者碰撞器做更精细的检测。 碰撞器与碰撞器会做更精细的检测，并会产生碰撞数据，如碰撞点、法线等。 触发事件和碰撞事件区别 # 触发事件由触发器生成，碰撞事件根据碰撞数据生成。 触发事件可以由触发器和另一个触发器/碰撞器产生。 碰撞事件需要由两个碰撞器产生，并且至少有一个是动力学刚体。 触发事件 # 事件 说明 onTriggerEnter 触发开始时触发该事件 onTriggerStay 触发保持时会频发触发该事件 onTriggerExit 触发结束时触发该事件 接收到触发事件的前提是两者都必须带有碰撞组件，并且至少有一个是触发器类型。当使用物理引擎为非 builtin 物理引擎时，还需要确保至少有一个物体带有的是非静态刚体（只有碰撞组件没有刚体组件的对象，视为持有静态刚体的对象），而 builtin 物理引擎则没有这个限制。\n监听触发事件 # // 此处的节点添加了 BoxCollider 组件 import { BoxCollider, ITriggerEvent } from \u0026#39;cc\u0026#39; public start () { let collider = this.node.getComponent(BoxCollider); collider.on(\u0026#39;onTriggerStay\u0026#39;, this.onTriggerStay, this); } private onTriggerStay (event: ITriggerEvent) { console.log(event.type, event); } 碰撞事件 # 碰撞事件根据碰撞数据生成，静态类型的刚体之间不会产生碰撞数据。\n事件 说明 onCollisionEnter 碰撞开始时触发 onCollisionStay 碰撞保持时不断的触发 onCollisionExit 碰撞结束时触发 接收到碰撞事件的前提是两者都必须带有碰撞组件、至少有一个是非静态刚体并且使用的是非 builtin 的物理引擎。\n监听碰撞事件 # import { Collider, ICollisionEvent } from \u0026#39;cc\u0026#39; public start () { let collider = this.node.getComponent(Collider); // 监听触发事件 collider.on(\u0026#39;onCollisionStay\u0026#39;, this.onCollision, this); } private onCollision (event: ICollisionEvent) { console.log(event.type, event); // 获取另一个组件的碰撞分组，为二进制 const otherGroup = event.otherCollider.getGroup(); } 物理材质 # 物理材质是一种资源，它记录了物体的物理属性，这些信息用来计算碰撞物体受到的摩擦力和弹力等。\n创建物理材质 # 在编辑器内创建 # 在 属性检查器 内右键任意空白处或点击 + 号间都可以创建物理材质：\n通过代码创建 # 也可通过代码实例化物理材质：\nimport { PhysicsMaterial } from \u0026#39;cc\u0026#39;; let newPMtl = new PhysicsMaterial(); newPMtl.friction = 0.1; newPMtl.rollingFriction = 0.1; newPMtl.spinningFriction = 0.1; newPMtl.restitution = 0.5; 属性 # 属性 属性说明 Friction 摩擦系数 RollingFriction 滚动摩擦系数 SpinningFriction 自旋摩擦系数 Restitution 回弹系数 使用 # 目前物理材质以碰撞体为单位进行设置，每个 Collider 都具有一个 Material 的属性（不设置时， Collider 将会引用物理系统中的默认物理材质）。\n应用到 Collider 同样也分编辑器操作和代码操作两种方式。\n编辑器内操作，只需要将资源拖入到 cc.PhysicMaterial 属性框中即可，如下图所示：\n在代码中操作：\nimport { Collider } from \u0026#39;cc\u0026#39;; let collider = this.node.getComponent(Collider); if (collider) { collider.material = newPMtl; collider.material.rollingFriction = 0.1; } ","date":"2024-09-04","externalUrl":null,"permalink":"/posts/69064821/92082869/bc467973/","section":"文章","summary":"\u003cp\u003eCocos Creator 的物理系统提供了高效的组件化工作流程和便捷的使用方法。目前支持刚体、碰撞组件、触发和碰撞事件、物理材质、射线检测等等特性。\u003c/p\u003e","title":"6、物理系统","type":"posts"},{"content":" 基础查询 # 语法： # select 列名1, ... ,列名n from 表名 where 条件 -- 1、条件 group by 列名 -- 2、分组 having 条件 -- 3、条件 order by 列名 -- 4、排序 limit 开始，条数 -- 5、分页 关于列的操作 # 1、查询所有列所有行 # select * from 表名 2、查询指定列 # #多列列名使用，隔开 select 列名 from 表名 3、给列起别名 # select 列名 [as] 别名,列名n [as] 别名n 4、列查询并进行算术运算 # #列和固定数值 select 列名+\\-\\*\\/\\%数值 from 表名 #列和列 select 列名+\\-\\*\\/\\%列名 from 表名 5、多列进行合并为一列查询 # select concat(列名1,列名2,...)合并后列名 from 表名 6、查询过程增加常量列 # select * ,\u0026#39;常量\u0026#39; 列名 from 表名 条件查询（where） # 1、比较运算符作为处理条件 # （=,\u0026gt;,\u0026lt;,\u0026gt;=,\u0026lt;=,!=,\u0026lt;\u0026gt;,如果是null，需要写为is,is not )\nselect * from 表名 where 列名=值 2、多条件 # 1）同时满足（and） # select * from 表名 where 列名1=值 and 列名2=值 2）或者（or） # #不同列 select * from 表名 where 列名1=值 or 列名2=值 #同一列的不同值 select * from 表名 where 列名 in (值1,值2,...) select * from 表名 where 列名 not in (值1,值2,...) 3）模糊查询（like） # #查询l开头的名字，任意长度 select * from 表名 where 列名 like \u0026#39;l%\u0026#39; #查询l开头的名字，固定长度 select * from 表名 where 列名 like \u0026#39;l__\u0026#39; #查询包含o的名字 select * from 表名 where 列名 like \u0026#39;%o%\u0026#39; 4）上下界（between） # select * from 表名 where 列名 between 下界值 and 上界值 排序查询（order by） # 升序 # select * from 表名 order by 列名 select * from 表名 order by 列名 asc 降序 # select * from 表名 order by 列名 desc 排序相等行，在进行排序 # select * from 表名 order by 列名1 desc,列名2 desc 分页查询（limit） # #起始位置默认为0，查询不包括起始位置 select * from 表名 limit 起始位置,每页条目数 公式 # #假如要显示的页数为page，每一页条目数为size select 查询列表 from 表 limit (page-1)*size,size; 聚合函数 # 关于某一列进行操作，和行没有关系，查询结果为一行\ncount() 返回结果集中行的数目 max() 返回结果集中所有值的最大值 min() 返回结果集中所有值的最小值 sum() 返回结果集中所有值的总和 avg() 返回结果集中所有值的平均值 用法 # 聚合函数null值不参与运算，如果希望null值也参与那么需要**ifnull()**函数处理\nselect 函数名(列名) from 表名 分组查询（group by） # select 分组列名,聚合函数 from 表名 group by 分组列名 #一般和聚合函数一起使用 #如，查询student表中的男女两个分组中的最大年龄和最小年龄 select ssex,max(sage),min(sage) from student group by ssex 条件筛选（having） # group by后不可以使用where进行筛选，需要使用having关键字\n如果能用where筛选数据的话，绝不使用having\nwhere和having的区别？ # 都是进行条件判断的关键字 where条件判断是在分组前判断，having条件判断是在分组后判断 where关键字要写在group by前面，having关键字要写在group by后面 where条件里不能写聚合函数，having条件里可以写聚合函数 #如，查询年龄大于11的各个年龄段的人数 select sage,count(sage) from student group by sage having sage \u0026gt; 11 #可以使用where就不要用having select sage,count(sage) from student where sage \u0026gt; 11 group by sage 联合查询（union） # 要求多条查询语句的查询列数必须一致 要求多条查询语句的查询的各列类型、顺序最好一致 union 去重，union all包含重复项 1. 将2个表的数据进行拼接，针对拼接后的重复数据 去重显示 select 列名 from 表名1 where 条件 union select 列名 from 表名2 where 条件 2. 将2个表的数据进行拼接，针对拼接后的重复数据 不去重显示 select 列名 from 表名1 where 条件 union all select 列名 from 表名2 where 条件 多表联合查询 # #sql92 select * from 表1,表2; #sql99 select * from 表1 cross join 表2; 笛卡尔积组合，形成数据没有价值\n内连接（inner join） # select 列名 from A表 inner join B表 on 关联条件; 外连接 # 左外连接（left） # select 列名 from A表 left join B表 on 关联条件; 右外连接（right） # select 列名 from A表 right join B表 on 关联条件; 自连接 # #查询1号课程成大于2号课程的学生id select * from sc s1 inner join sc s2 on s1.sno=s2.sno where s1.cno=1 and s2.cno=2 and s1.score\u0026gt;s2.score 去重（distinct） # select distinct 列名 from 表名; 子查询 # 嵌套查询，就是指一个sql语句里面还有一条sql语句\n将查询的结果（可以作为值，也可以作为表），再次被sql语句使用\n结果为一个值 # #查询李华老师所带课程 select * from course where tno = ( select tno from teacher where tname = \u0026#39;李华\u0026#39; ) 结果为一个表（多个值） # #查询李华老师所带学生的信息 -- 1 获取李华老师的编号 select tno from teacher where tname = \u0026#39;李华\u0026#39; -- 2 根据老师的编号获取所带学科的编号 select cno from course where cno in ( select tno from teacher where tname = \u0026#39;李华\u0026#39; ) -- 3 根据学科编号获取学生的编号 select sno from sc where cno in ( select cno from course where cno in ( select tno from teacher where tname = \u0026#39;李华\u0026#39; ) ) -- 4 根据学生编号，获取学生的信息 select * from student where sno in ( select sno from sc where cno in ( select cno from course where cno in ( select tno from teacher where tname = \u0026#39;李华\u0026#39; ) ) ) any（满足该字段任何一个值，‘或’） # #查询学生表中，年龄大于张三或tony的学生信息 select * from student where sage \u0026gt; any( select sage from student where sname = \u0026#39;张三\u0026#39; or sname = \u0026#39;tony\u0026#39; ) all（满足该字段所有的值，‘且’） # #查询学生表中，年龄大于张三并且大于tony的学生信息 select * from student where sage \u0026gt; all( select sage from student where sname = \u0026#39;张三\u0026#39; or sname = \u0026#39;tony\u0026#39; ) exists和in的区别 # 扩展 # 按照条件显示不同信息（case、when、then、else、end） # #查询student表中，按照年龄显示是否成年 select *, case when sage \u0026gt;= 18 then \u0026#39;成年\u0026#39; else \u0026#39;未成年\u0026#39; end from student 开窗函数 # 现有数据表如下，字段分别为：主键id、姓名、城市、年龄、工资\n要查询每个城市中最高工资，我们可以使用group by\nselect fcity,max(fsalary) from t_person GROUP BY fcity 但是如果查询每个程序中工资最高的一个人，那么就不能使用group by了，而是要使用开窗函数\n开窗函数使用格式为：聚合函数 over()，其中over()可以写查询条件\n理解：partition by实际上是对fcity进行分区，每一个分区相互独立，前面的聚合函数row_number()是对每个分区结果进行聚合计算\nSELECT * from ( select fname, fcity, fsalary, ROW_NUMBER() over(PARTITION by fcity order by fsalary desc) fs from t_person ) b where b.fs = 1 ","date":"2024-05-09","externalUrl":null,"permalink":"/posts/a6ece75f/8b824d9b/5d290da9/73c86049/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e基础查询 \n    \u003cdiv id=\"基础查询\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%9f%ba%e7%a1%80%e6%9f%a5%e8%af%a2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e语法： \n    \u003cdiv id=\"语法\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%af%ad%e6%b3%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eselect\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e列名\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e...\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"err\"\u003e列名\u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003efrom\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e表名\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ewhere\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e条件\u003c/span\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e-- 1、条件\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003egroup\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eby\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e列名\u003c/span\u003e\u003cspan class=\"w\"\u003e     \u003c/span\u003e\u003cspan class=\"c1\"\u003e-- 2、分组\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003ehaving\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e条件\u003c/span\u003e\u003cspan class=\"w\"\u003e       \u003c/span\u003e\u003cspan class=\"c1\"\u003e-- 3、条件\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eorder\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eby\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e列名\u003c/span\u003e\u003cspan class=\"w\"\u003e     \u003c/span\u003e\u003cspan class=\"c1\"\u003e-- 4、排序\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003elimit\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e开始，条数\u003c/span\u003e\u003cspan class=\"w\"\u003e   \u003c/span\u003e\u003cspan class=\"c1\"\u003e-- 5、分页\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e关于列的操作 \n    \u003cdiv id=\"关于列的操作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%85%b3%e4%ba%8e%e5%88%97%e7%9a%84%e6%93%8d%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003e1、查询所有列所有行 \n    \u003cdiv id=\"1查询所有列所有行\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1%e6%9f%a5%e8%af%a2%e6%89%80%e6%9c%89%e5%88%97%e6%89%80%e6%9c%89%e8%a1%8c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eselect\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003efrom\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e表名\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e2、查询指定列 \n    \u003cdiv id=\"2查询指定列\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#2%e6%9f%a5%e8%af%a2%e6%8c%87%e5%ae%9a%e5%88%97\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e多列列名使用，隔开\u003c/span\u003e\u003cspan class=\"w\"\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eselect\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e列名\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003efrom\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e表名\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e3、给列起别名 \n    \u003cdiv id=\"3给列起别名\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#3%e7%bb%99%e5%88%97%e8%b5%b7%e5%88%ab%e5%90%8d\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eselect\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e列名\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"k\"\u003eas\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e别名\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"err\"\u003e列名\u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"k\"\u003eas\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e别名\u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e4、列查询并进行算术运算 \n    \u003cdiv id=\"4列查询并进行算术运算\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#4%e5%88%97%e6%9f%a5%e8%af%a2%e5%b9%b6%e8%bf%9b%e8%a1%8c%e7%ae%97%e6%9c%af%e8%bf%90%e7%ae%97\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e列和固定数值\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eselect\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e列名\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"o\"\u003e%\u003c/span\u003e\u003cspan class=\"err\"\u003e数值\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003efrom\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e表名\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"err\"\u003e列和列\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eselect\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e列名\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"o\"\u003e%\u003c/span\u003e\u003cspan class=\"err\"\u003e列名\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003efrom\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e表名\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e5、多列进行合并为一列查询 \n    \u003cdiv id=\"5多列进行合并为一列查询\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#5%e5%a4%9a%e5%88%97%e8%bf%9b%e8%a1%8c%e5%90%88%e5%b9%b6%e4%b8%ba%e4%b8%80%e5%88%97%e6%9f%a5%e8%af%a2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eselect\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003econcat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"err\"\u003e列名\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"err\"\u003e列名\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,...)\u003c/span\u003e\u003cspan class=\"err\"\u003e合并后列名\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003efrom\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e表名\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e6、查询过程增加常量列 \n    \u003cdiv id=\"6查询过程增加常量列\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#6%e6%9f%a5%e8%af%a2%e8%bf%87%e7%a8%8b%e5%a2%9e%e5%8a%a0%e5%b8%b8%e9%87%8f%e5%88%97\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eselect\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;常量\u0026#39;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e列名\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003efrom\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"err\"\u003e表名\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e条件查询（where） \n    \u003cdiv id=\"条件查询where\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9d%a1%e4%bb%b6%e6%9f%a5%e8%af%a2where\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e1、比较运算符作为处理条件 \n    \u003cdiv id=\"1比较运算符作为处理条件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1%e6%af%94%e8%be%83%e8%bf%90%e7%ae%97%e7%ac%a6%e4%bd%9c%e4%b8%ba%e5%a4%84%e7%90%86%e6%9d%a1%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e（\u003ccode\u003e=\u003c/code\u003e,\u003ccode\u003e\u0026gt;\u003c/code\u003e,\u003ccode\u003e\u0026lt;\u003c/code\u003e,\u003ccode\u003e\u0026gt;=\u003c/code\u003e,\u003ccode\u003e\u0026lt;=\u003c/code\u003e,\u003ccode\u003e!=\u003c/code\u003e,\u003ccode\u003e\u0026lt;\u0026gt;\u003c/code\u003e,如果是\u003ccode\u003enull\u003c/code\u003e，需要写为\u003ccode\u003eis\u003c/code\u003e,\u003ccode\u003eis not\u003c/code\u003e )\u003c/p\u003e","title":"6、DQL数据查询语句","type":"posts"},{"content":" 分布式事务 # 分布式事务概念 # 本地事务：单体应用对应的单个数据库的事务 事务的目的：保证整个业务流程，要么统一成功，要么统一失败 通过单体应用中的事务管理器（TransactionManagement），可以保证事务的完整性 分布式事务 现在，我们用的是微服务。微服务的特点：一个微服务对应一个数据库 分布式程序，或微服务程序是相互独立的模块，都是远程调用，无法继续使用本地事务控制 产生的背景 # 系统越来越大，数据量越来越多，就不可避免的需要做数据库的分库，分片，分表\n不同的数据定位在不同的数据库中，不同的数据库又对应着不同的微服务，这就变成了跨库操作，以前的本地事务@Transactional就没用了\n不同的数据库对应的不同的微服务，每个微服务又有了它自己的业务逻辑，并且微服务之间又是相互独立的，此时：微服务相互调用过程中，就很难做到保证整个业务链条过程中，业务保持完整性\n不同的数据库，还有可能分配在不同的机房中，甚至有可能在不同的网络中，进一步加深了咱们分布式事务控制难度\n分布式事务解决方案 # 全局事务（2PC） # 2 Prepared Commit\n全局事务基于DTP模型实现。DTP是由X/Open组织提出的一种分布式事务模型\u0026ndash;X/Open Distributed Transcation Processing Reference Model。它规定，要实现分布式事务的话，就需要三种角色：\nAP：Application 应用系统（参与者） TM：Transaction Manager 事务管理器（全局事务管理者） RM：Resource Manager 资源管理器（本地事务管理者，即数据库，也可以是其他资源管理器， 比如消息队列，文件系统） 阶段一：表决阶段，所有参与者都预提交（只执行sql，但不提交）事务，并将能都成功的信息反馈发给协调者。\n事务协调者(事务管理器)给每个参与者(资源管理器)发送 Prepare 消息，每个参与者要么直接返回失败(如权限验证失败)，要么在本地执行事务，写本地的 redo 和 undo 日志，但不提交，到达一种“万事俱备，只欠东风”的状态。\n阶段二：执行阶段，协调者根据所有参与者的反馈，通知所有参与者，步调一致地执行提交或者回滚。\n如果协调者收到了参与者的失败消息或者超时，直接给每个参与者发送回滚(Rollback)消息；否则，发送提交(Commit)消息；参与者根据协调者的指令执行提交或者回滚操作，释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)\n优缺点 # 优点 提高了数据一致性的概率，实现成本较低 缺点 单点问题：事务协调者宕机 同步阻塞：延迟了提交时间，加长了资源阻塞时间 数据不一致：在第二阶段提交时，依然存在commit结果未知的情况，有可能导致数据不一致 数据不一致（脑裂问题） 在二阶段提交的阶段二中，当协调者向参与者发送 commit 请求之后，发生了局部网络异常或者在发送 commit 请求过程中协调者发生了故障，导致只有一部分参与者接受到了commit 请求。于是整个分布式系统便出现了数据部一致性的现象(脑裂现象)。 TCC两阶段补偿性方案 # 数据库的事务，为逻辑业务处理\nTCC，Try Confirm Cancel，它属于补偿型事务。顾名思义，TCC实现分布式事务一共有三个步骤：\nTry：尝试待执行的业务\n这个过程并未执行业务，只是完成所有业务的一致性检查，并预留好执行所需的全部资源\nConfirm：确认执行业务\n确认执行业务操作，不做任何业务检查，只使用Try阶段预留的业务资源。通常情况下，采用TCC则认为Confirm阶段是不会出错的。即：只要Try成功，Confirm一定成功。若Confirm阶段真的出错了，需引入重试机制或人工处理。\nCancel：取消待执行的业务\n取消Try阶段预留的业务资源。通常情况下，采用TCC则认为Cancel阶段也是一定会成功的。若Cancel阶段真的出错了，需引入重试机制或人工处理。\nTCC两阶段提交与XA两阶段提交的区别是： XA是资源层面的分布式事务，强一致性，在两阶段提交的过程中，一直会持有资源的锁 TCC是业务层面的分布式事务，最终一致性，不会一直持有资源的锁 TCC事务的优缺点 优点：执行完每个阶段就commit，不会长时间占有资源。提高了整个系统的并发量。 缺点：TCC的Try、Confirm和Cancel操作功能需业务提供，开发成本高。 分布式事务中间件 # Seata # 官网地址：http://seata.io/zh-cn/\nSeata是由阿里中间件团队发起的开源项目 Fescar，后更名为Seata，它是一个是开源的分布式事务框架。致力于在微服务架构下提供高性能和简单易用的分布式事务服务。（AT模式是阿里首推模式，阿里云上有商用版本的GTS[Global Transaction service全局事务服务]）。\n它通过对本地关系数据库的分支事务的协调来驱动完成全局事务，是工作在应用层的中间件。主要优点是性能较好，且不长时间占用连接资源，它以高效并且对业务0侵入的方式解决微服务场景下面临的分布式事务问题，它目前提供AT模式(即2PC)及TCC模式的分布式事务解决方案。\n发展史 # 2014 - 阿里中间件团队发布txc（taobao transaction constructor）在阿里内部提供分布式事务服务； 2016 - txc经过改造和升级，变成了gts（global transaction service）在阿里云作为服务对外开放，也成为当时唯一一款对外的服务； 2019 - 阿里经过txc和gts的技术积累，决定开源（Apache开源协议）。并且，在github上发起了一个项目叫做fescar（fast easy commit and rollback）开始拥有了社区群体； 2019 - fescar被重命名为了seata（simple extensiable autonomous transaction architecture），项目迁移到了新的github地址。\n全局事务 # AT 模式是一种无侵入的分布式事务解决方案。在 AT 模式下，用户只需关注自己的“业务 SQL”，用户的 “业务 SQL” 作为一阶段，Seata 框架会自动生成事务的二阶段提交和回滚操作。\nSeata把一个全局事务，看做是由一组分支事务组成的一个大的事务（分支事务可以直接认为就是本地事务）\nSeata 是一款开源的分布式事务解决方案，致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式，为用户打造一站式的分布式解决方案。\nSeata把一个分布式事务理解成一个包含了若干分支事务的全局事务。全局事务的职责是协调其下管辖的分支事务达成一致，要么一起成功提交，要么一起失败回滚。此外，通常分支事务本身就是一个关系数据库的本地事务，下图是全局事务与分支事务的关系图：\nSeata中的三个组件 # Transaction Coordinator (TC)： 事务协调器，维护全局事务的运行状态，负责协调并驱动全局事务的提交或回滚。\nTransaction Manager (TM)： 控制全局事务的边界，负责开启一个全局事务，并最终发起全局提交或全局回滚的决议。\nResource Manager (RM)： 控制分支事务，负责分支注册、状态汇报，并接收事务协调器的指令，驱动分支（本地）事务的提交和回滚。\nSeata管理分布式事务的典型流程 # TM创建全局事务，告知TC，TC生成全局唯一的XId 在TM中调用分支事务RM，将xid传给RM。 RM通过xid注册的TC中，告知TC他是哪一个全局事务的分支事务。 TM告知TC，可以进行提交或者回滚了。 由TC通知TM对应的RM进行提交或回滚。 Seata安装与启动 # 1、下载seata # 下载地址：https://github.com/seata/seata/releases\n2、修改conf/registry.conf # 将原来的模板全部删除，粘贴下面的\nregistry块用以控制将TC注册在哪里（注册中心），config块用以控制将TC的配置文件交由谁来管理（配置中心）\nregistry { type = \u0026#34;nacos\u0026#34; nacos { serverAddr = \u0026#34;localhost\u0026#34; namespace = \u0026#34;\u0026#34; cluster = \u0026#34;default\u0026#34; } } config { type = \u0026#34;nacos\u0026#34; nacos { serverAddr = \u0026#34;localhost\u0026#34; namespace = \u0026#34;\u0026#34; cluster = \u0026#34;default\u0026#34; } } 3、修改conf/nacos-config.txt # 定义某个事务组，属于哪个TC集群\nservice.vgroup_mapping.micro-product=default service.vgroup_mapping.micro-orders=default 其中，micro-product和micro-orders是事务分组的名字，其后的取值“default”是TC集群的名字\n4、将seata的配置文件存入配置中心 # 确保nacos已经运行\n在conf目录下，执行nacos-config.sh脚本，该脚本能把seata TC所需要的配置存入nacos配置中心\n进入nacos控制台，查看配置列表，可以看到很多Group为SEATA_GROUP的配置\n5、启动seata服务（TC） # bin目录下\n默认占用的端口是8091，可以在启动seata-server时，通过-p指定端口\nseata-server.bat -p 8091 -m file 启动后，在nacos的服务列表下面可以看到一个名为serverAddr的服务\n微服务中使用Seata # 1、在需要进行事务管理的微服务中添加依赖 # 如果只加依赖，不进行下面的配置，启动会报错\n依赖中封装了（TM、RM）\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-alibaba-seata\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、每个微服务添加初始化数据表 # 初始化数据表：在每个微服务的项目数据库中加入一张undo_log表，这是Seata记录事务日志要用到的表\n-- the table to store seata xid data -- 0.7.0+ add context -- you must to init this sql for you business databese. the seata server not need it. -- 此脚本必须初始化在你当前的业务数据库中，用于AT 模式XID记录。与server端无关（注：业务数据库） -- 注意此处0.3.0+ 增加唯一索引 ux_undo_log drop table `undo_log`; CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 3、在需要进行事务管理的微服务添加配置类 # 在star-orders和star-product的config包中，创建以下配置类，在其中配置代理数据源，该代理数据源主要添加了日志记录功能，TC通过该日志才能够进行回滚！也就是说这一步必不可少！！\n//普通数据源 @Configuration public class DataSourceProxyConfig { @Bean @ConfigurationProperties(prefix = \u0026#34;spring.datasource\u0026#34;) //读取yml中的配置 public DriverManagerDataSource dataSource(){ return new DriverManagerDataSource(); } // 必须在代理数据源上添加@Primary，保证在自动装配时优先注入代理数据源 @Primary @Bean public DataSourceProxy dataSource(DriverManagerDataSource driverManagerDataSource) { return new DataSourceProxy(driverManagerDataSource); } } //druid数据源 @Configuration public class DataSourceProxyConfig { @Bean @ConfigurationProperties(prefix = \u0026#34;spring.datasource\u0026#34;) //读取yml中的配置 public DruidDataSource druidDataSource() { return new DruidDataSource(); } // 必须在代理数据源上添加@Primary，保证在自动装配时优先注入代理数据源 @Primary @Bean public DataSourceProxy dataSource(DruidDataSource druidDataSource) { return new DataSourceProxy(druidDataSource); } } 4、修改微服务启动类 # 启动类上去掉默认的数据库链接，否则容易造成死循环\n@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) 5、微服务添加seata的配置文件registry.conf # 用于告诉应用中的TM，去哪里找TC\nregistry { type = \u0026#34;nacos\u0026#34; nacos { serverAddr = \u0026#34;localhost\u0026#34; namespace = \u0026#34;\u0026#34; } } config { type = \u0026#34;nacos\u0026#34; nacos { serverAddr = \u0026#34;localhost\u0026#34; namespace = \u0026#34;\u0026#34; } } 6、修改微服务的配置文件application.yml # 用于告诉应用中的TM，去哪里找TC\n要与nacos-config.txt中的service.vgroup_mapping.micro-orders=default一致\nspring: cloud: alibaba: seata: tx-service-group: micro-orders 7、在微服务控制层方法，开启全局事务 # 添加注解@GlobalTransactional\n@GetMapping(\u0026#34;/addOrderProduct/{uid}\u0026#34;) @GlobalTransactional public ResultVO addOrderProduct(@PathVariable Integer uid){ } 测试时，记得关闭micro-orders中的ProductService的后备方法\nCAP理论 # C：Consistency \u0026ndash; 一致性，在分布式系统完成某写操作后任何读操作，都应该获取到该写操作写入的那个最新的值。相当于要求分布式系统中的各节点时时刻刻保持数据的一致性。\nA：Availability \u0026ndash; 可用性， 一直可以正常的做读写操作。简单而言就是客户端一直可以正常访问并得到系统的正常响应。用户角度来看就是不会出现系统操作失败或者访问超时等问题。\nP：Partition Tolerance \u0026ndash; 分区容错性，指的分布式系统中的某个节点或者网络分区出现了故障的时候，整个系统仍然能对外提供满足一致性和可用性的服务。也就是说部分故障不影响整体使用。\nCAP三者不可兼得，该如何取舍 # (1) CA: 优先保证一致性和可用性，放弃分区容错。 这也意味着放弃系统的扩展性，系统不再是分布式的，有违设计的初衷。\n(2) CP: 优先保证一致性和分区容错性，放弃可用性。在数据一致性要求比较高的场合(譬如:zookeeper,Hbase) 是比较常见的做法，一旦发生网络故障或者消息丢失，就会牺牲用户体验，等恢复之后用户才逐渐能访问。\n(3) AP: 优先保证可用性和分区容错性，放弃一致性。NoSQL中的Cassandra 就是这种架构。跟CP一样，放弃一致性不是说一致性就不保证了，而是逐渐的变得一致。\n分布式锁 # 分布式锁的特点 # 互斥性。在任意时刻，只有一个客户端能持有锁 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁，也能保证后续其他客户端能加锁。 具有容错性。只要大部分的Redis节点正常运行，客户端就可以加锁和解锁。 解铃还须系铃人。加锁和解锁必须是同一个客户端，其他客户端无法对已经加锁的数据进行解锁操作 Redission # 可以用Redission来实现分布式锁 Redission是专门针对Redis分布式锁，提供的一套相关技术 分布式锁的实现原理 # Redis加锁的命令：SET lock_key random_value NX PX 5000\nlock_key：需要加锁的某一个key random_value：给key设置一个默认值，因为在Redis中key-value，任意值都可以 NX：加锁命令，生效的地方：有且仅当key不存在，才可以上锁成功 PX 5000：设定锁的到期时间，单位是毫秒 使用Reids，自己进行实现 # @PostMapping(\u0026#34;/shopAndReduceStoreRedis\u0026#34;) public ResultVO shopAndReduceStore(){ ResultVO resultVO = null; //time为锁的释放时间，如果达到3秒，那么会自动释放锁 Long time = 3000L; //设置效果相当于redis命令setnx lock lock，如果当前的key存在，那么无法复制和创建，返回false Boolean addLockSuccess = redisTemplate.opsForValue().setIfAbsent(\u0026#34;lock\u0026#34;, \u0026#34;lock\u0026#34;,time,TimeUnit.SECONDS); //判断是否成功添加锁，如果没有，则循环，直到成功添加锁 while (!addLockSuccess){ addLockSuccess = redisTemplate.opsForValue().setIfAbsent(\u0026#34;lock\u0026#34;, \u0026#34;lock\u0026#34;,time,TimeUnit.SECONDS); //等待一会儿再尝试 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } //守护线程，对主线程进行续命 Thread thread = new Thread(new Runnable() { @Override public void run() { sleep(time/2); redisTemplate.expire(\u0026#34;lock\u0026#34;,time/2,TimeUnit.SECONDS); } }); thread.setDaemon(true); thread.start(); try { Integer store = (Integer) redisTemplate.opsForValue().get(\u0026#34;store\u0026#34;); if (store == 0){ resultVO = ResultVO.fail(\u0026#34;库存不足\u0026#34;); }else { redisTemplate.opsForValue().set(\u0026#34;store\u0026#34;,store--); resultVO = ResultVO.success(\u0026#34;购买成功\u0026#34;); } }catch (Exception e){ e.printStackTrace(); resultVO = ResultVO.fail(\u0026#34;购买异常!\u0026#34;); }finally { //释放锁 redisTemplate.delete(\u0026#34;lock\u0026#34;); return resultVO; } } 使用Redission实现 # 1、导入依赖 # \u0026lt;!-- 导入Redisson分布锁技术 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.redisson\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;redisson\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.12.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 2、编写Redission的配置类 # /** * Redisson配置类 * 主要提供：Redis分布式锁的功能 */ @Configuration public class RedissonConfiguration { /** * 根据配置信息，产生一个Redisson的客户端对象 * @return */ @Bean public RedissonClient redissonClient(){ Config config = new Config(); config.useSingleServer().setAddress(\u0026#34;redis://localhost:6379\u0026#34;); return Redisson.create(config); } } 3、代码中具体使用 # @Resource private RedissonClient redissonClient; @PostMapping(\u0026#34;/shopAndReduceStore\u0026#34;) public ResultVO shopAndReduceStore(){ RLock lock = null; ResultVO resultVO = null; try { //获取锁对象 lock = redissonClient.getLock(\u0026#34;lock\u0026#34;); //加锁，30秒释放锁,防止死锁出现 lock.lock(30L, TimeUnit.SECONDS); Integer store = (Integer) redisTemplate.opsForValue().get(\u0026#34;store\u0026#34;); if (store == 0){ resultVO = ResultVO.fail(\u0026#34;库存不足\u0026#34;); }else { redisTemplate.opsForValue().set(\u0026#34;store\u0026#34;,store--); resultVO = ResultVO.success(\u0026#34;购买成功\u0026#34;); } }catch (Exception e){ e.printStackTrace(); resultVO = ResultVO.fail(\u0026#34;购买异常!\u0026#34;); }finally { //释放锁 lock.unlock(); return resultVO; } } ","date":"2024-04-03","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/e8fd0d06/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e分布式事务 \n    \u003cdiv id=\"分布式事务\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%86%e5%b8%83%e5%bc%8f%e4%ba%8b%e5%8a%a1\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e分布式事务概念 \n    \u003cdiv id=\"分布式事务概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%86%e5%b8%83%e5%bc%8f%e4%ba%8b%e5%8a%a1%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e本地事务：单体应用对应的单个数据库的事务\n\u003cul\u003e\n\u003cli\u003e事务的目的：保证整个业务流程，要么统一成功，要么统一失败\u003c/li\u003e\n\u003cli\u003e通过单体应用中的事务管理器（TransactionManagement），可以保证事务的完整性\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e分布式事务\n\u003cul\u003e\n\u003cli\u003e现在，我们用的是微服务。微服务的特点：一个微服务对应一个数据库\u003c/li\u003e\n\u003cli\u003e分布式程序，或微服务程序是相互独立的模块，都是远程调用，无法继续使用本地事务控制\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"img\"\n    data-zoom-src=\"/posts/3ab7256e/675117b0/e8fd0d06/image/202109181403510.png\"\n    src=\"/posts/3ab7256e/675117b0/e8fd0d06/image/202109181403510.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"6、分布式事务","type":"posts"},{"content":"简单来说，Beautiful Soup 是 python 的一个库，最主要的功能是从网页抓取数据。官方解释如下：\nBeautiful Soup 提供一些简单的、python 式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱，通过解析文档为用户提供需要抓取的数据，因为简单，所以不需要多少代码就可以写出一个完整的应用程序。 Beautiful Soup 自动将输入文档转换为 Unicode 编码，输出文档转换为 utf-8 编码。你不需要考虑编码方式，除非文档没有指定一个编码方式，这时，Beautiful Soup 就不能自动识别编码方式了。然后，你仅仅需要说明一下原始编码方式就可以了。 Beautiful Soup 已成为和 lxml、html6lib 一样出色的 python 解释器，为用户灵活地提供不同的解析策略或强劲的速度。\n安装 # Beautiful Soup 3 目前已经停止开发，推荐在现在的项目中使用 Beautiful Soup 4，不过它已经被移植到 BS4 了，也就是说导入时我们需要 import bs4\nBeautiful Soup 支持 Python 标准库中的 HTML 解析器，还支持一些第三方的解析器，例如lxml，如果我们不安装它，则 Python 会使用 Python 默认的解析器，lxml 解析器更加强大，速度更快，推荐安装。\npip install lxml beautifulsoup4 优缺点 # html.parser（python默认解析器）：\nPython 的内置标准库 执行速度适中 文档容错能力强 Python 2.7.3 or 3.2.2前的版本中文档容错能力差 lxml（第三方解析器）：\n速度快 文档容错能力强 需要安装 C 语言库 html5lib（第三方解析器）：\n最好的容错性 以浏览器的方式解析文档 生成 HTML5 格式的文档 速度慢 不依赖外部扩展 基本使用 # from bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; # 创建对象，也可以打开本地的html文件：soup = BeautifulSoup(open(\u0026#39;./text.html\u0026#39;)) # 可以指定解析器，如果不指定，默认使用当前系统最好的那个，例如lxml soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) #格式化并输出 print(soup.prettify()) 四大对象种类 # Tag # from bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) #获取第一个出现的title标签 print(type(soup.title)) print(soup.title) #获取第一个出现的p标签 print(soup.p) \u0026#39;\u0026#39;\u0026#39; \u0026lt;class \u0026#39;bs4.element.Tag\u0026#39;\u0026gt; \u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt; \u0026lt;p align=\u0026#34;center\u0026#34; id=\u0026#34;firstpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026#39;\u0026#39;\u0026#39; Tag，它有两个重要的属性，是 name 和 attrs\nfrom bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) #soup 对象本身比较特殊，它的 name 即为 [document] print(soup.name) pTag = soup.p #获取标签的名字 print(pTag.name) print(pTag.attrs) \u0026#39;\u0026#39;\u0026#39; [document] p {\u0026#39;id\u0026#39;: \u0026#39;firstpara\u0026#39;, \u0026#39;align\u0026#39;: \u0026#39;center\u0026#39;} \u0026#39;\u0026#39;\u0026#39; 获取属性值的方法\nfrom bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) print(soup.p[\u0026#39;id\u0026#39;]) print(soup.p.get(\u0026#39;id\u0026#39;)) \u0026#39;\u0026#39;\u0026#39; firstpara firstpara \u0026#39;\u0026#39;\u0026#39; 删除tag\nsoup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) #删除tag，并返回 print(soup.extract()) \u0026#39;\u0026#39;\u0026#39; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026#39;\u0026#39;\u0026#39; NavigableString # 可以通过标签的string属性获取标签内部的文字，这个属性是NavigableString类型\nfrom bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) titleTag = soup.title print(type(titleTag.string)) print(titleTag.string) \u0026#39;\u0026#39;\u0026#39; \u0026lt;class \u0026#39;bs4.element.NavigableString\u0026#39;\u0026gt; Page title \u0026#39;\u0026#39;\u0026#39; 注意：例如上面的p标签，是无法使用string来取出内容的，需要使用text属性或者使用strings属性（返回一个迭代器），官方文档中介绍\n1，当tag 包含了多个子节点，tag 就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None。 2，text 返回的是标签的所有字符串连接成的字符串\nfrom bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) pTag = soup.p print(type(pTag.text)) print(pTag.text) \u0026#39;\u0026#39;\u0026#39; \u0026lt;class \u0026#39;str\u0026#39;\u0026gt; This is paragraph one. \u0026#39;\u0026#39;\u0026#39; from bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) p = soup.p print(p.strings) for s in p.strings: print(s) \u0026#39;\u0026#39;\u0026#39; \u0026lt;generator object _all_strings at 0x0000020E0DD9A570\u0026gt; This is paragraph one . \u0026#39;\u0026#39;\u0026#39; BeautifulSoup # BeautifulSoup 对象表示的是一个文档的全部内容，也是一个节点，name是[document]\nComment # Comment 对象是一个特殊类型的 NavigableString 对象，其实输出的内容仍然不包括注释符号，但是如果不好好处理它，可能会对我们的文本处理造成意想不到的麻烦\n节点 # 直接子节点 # tag 的contents属性可以将 tag 的直接子节点以列表list的方式输出\nfrom bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) tags = soup.html.contents print(type(tags)) print(tags) \u0026#39;\u0026#39;\u0026#39; \u0026lt;class \u0026#39;list\u0026#39;\u0026gt; [\u0026#39;\\n\u0026#39;, \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt;, \u0026#39;\\n\u0026#39;, \u0026lt;body\u0026gt; \u0026lt;p align=\u0026#34;center\u0026#34; id=\u0026#34;firstpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p align=\u0026#34;blah\u0026#34; id=\u0026#34;secondpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt;, \u0026#39;\\n\u0026#39;] \u0026#39;\u0026#39;\u0026#39; tag还有一个children属性，返回的是一个list迭代器，需要遍历和contents效果一样\n所有子节点 # tag的descendants属性，返回该tag的所有子节点（直接子节点和孙节点，以此递归），和children一样，返回的是一个list迭代器\nfrom bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) tags = soup.descendants print(type(tags)) for tag in tags: print(tag) \u0026#39;\u0026#39;\u0026#39; \u0026lt;class \u0026#39;generator\u0026#39;\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p align=\u0026#34;center\u0026#34; id=\u0026#34;firstpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p align=\u0026#34;blah\u0026#34; id=\u0026#34;secondpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt; Page title \u0026lt;body\u0026gt; \u0026lt;p align=\u0026#34;center\u0026#34; id=\u0026#34;firstpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p align=\u0026#34;blah\u0026#34; id=\u0026#34;secondpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;p align=\u0026#34;center\u0026#34; id=\u0026#34;firstpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt; one . \u0026lt;p align=\u0026#34;blah\u0026#34; id=\u0026#34;secondpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt; two . \u0026#39;\u0026#39;\u0026#39; 直接父节点 # tag的parent属性，可以得到该节点的直接父节点\nfrom bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) p = soup.p print(p.parent.name) print(p.parent) \u0026#39;\u0026#39;\u0026#39; body \u0026lt;body\u0026gt; \u0026lt;p align=\u0026#34;center\u0026#34; id=\u0026#34;firstpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p align=\u0026#34;blah\u0026#34; id=\u0026#34;secondpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026#39;\u0026#39;\u0026#39; 所有父节点 # tag的parents属性可以递归得到元素的所有父辈节点，该属性是一个迭代器\nfrom bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) p = soup.p print(p.parents) for p in p.parents: print(p.name) \u0026#39;\u0026#39;\u0026#39; \u0026lt;generator object parents at 0x000001C57839A570\u0026gt; body html [document] \u0026#39;\u0026#39;\u0026#39; 相邻兄弟节点 # 兄弟节点可以理解为和本节点处在统一级的节点，next_sibling 属性获取了该节点的下一个兄弟节点，previous_sibling 属性获取了该节点的上一个兄弟节点，如果节点不存在，则返回 None ，注意：实际文档中的 tag 的 .next_sibling 和 .previous_sibling 属性通常是字符串或空白，因为空白或者换行也可以被视作一个节点，所以得到的结果可能是空白或者换行\nfrom bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) p = soup.p #由于此处的p标签前后有换行，所以使用了调用了两次该属性 print(p.next_sibling.next_sibling) print(p.previous_sibling.previous_sibling) \u0026#39;\u0026#39;\u0026#39; \u0026lt;p align=\u0026#34;blah\u0026#34; id=\u0026#34;secondpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; None \u0026#39;\u0026#39;\u0026#39; 全部兄弟节点 # 通过 next_siblings 和 previous_siblings 属性可以对当前节点的兄弟节点迭代输出\nfrom bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) p = soup.p for pre in p.previous_siblings: print(pre) for next in p.next_siblings: print(next) \u0026#39;\u0026#39;\u0026#39; \u0026lt;p align=\u0026#34;blah\u0026#34; id=\u0026#34;secondpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026#39;\u0026#39;\u0026#39; 相邻前后节点 # tag的next_element和previous_element属性可以获得该节点相邻前后的节点，不一定是相同等级的兄弟节点\nfrom bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) p = soup.p print(p.previous_element) print(p.next_element) \u0026#39;\u0026#39;\u0026#39; This is paragraph \u0026#39;\u0026#39;\u0026#39; 所有前后节点 # 通过 next_elements 和 previous_elements 的迭代器就可以向前或向后访问文档的解析内容，就好像文档正在被解析一样\nfrom bs4 import BeautifulSoup html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) p = soup.p for pre in p.previous_elements: print(pre) for next in p.next_elements: print(next) 搜索文档树 # find_all( name , attrs , recursive , text , **kwargs ) #搜索当前 tag 的所有 tag 子节点，返回符合要求的结果list find( name , attrs , recursive , text , **kwargs ) #搜索当前 tag 的所有 tag 子节点，返回第一个符合要求的节点 find_parents() | find_parent() #find_all () 和 find () 只搜索当前节点的所有子节点，孙子节点等. find_parents () 和 find_parent () 用来搜索当前节点的父辈节点，搜索方法与普通 tag 的搜索方法相同，搜索文档搜索文档包含的内容 find_next_siblings() | find_next_sibling() #这 2 个方法通过 .next_siblings 属性对当 tag 的所有后面解析的兄弟 tag 节点进行迭代，find_next_siblings () 方法返回所有符合条件的后面的兄弟节点，find_next_sibling () 只返回符合条件的后面的第一个 tag 节点 find_previous_siblings() | find_previous_sibling() #这 2 个方法通过 .previous_siblings 属性对当前 tag 的前面解析的兄弟 tag 节点进行迭代，find_previous_siblings () 方法返回所有符合条件的前面的兄弟节点，find_previous_sibling () 方法返回第一个符合条件的前面的兄弟节点 find_all_next() | find_next() #这 2 个方法通过 .next_elements 属性对当前 tag 的之后的 tag 和字符串进行迭代，find_all_next() 方法返回所有符合条件的节点，find_next () 方法返回第一个符合条件的节点 find_all_previous() | find_previous () #这 2 个方法通过 .previous_elements 属性对当前节点前面的 tag 和字符串进行迭代，find_all_previous () 方法返回所有符合条件的节点，find_previous () 方法返回第一个符合条件的节点 传参 # name # name参数选择： 字符串：匹配name为这个字符串的tag 正则：匹配name符合改正则的tag 列表：匹配name和列表中任意元素匹配的tag 方法：这个方法只有一个参数tag，如果方法返回True则认为匹配 from bs4 import BeautifulSoup import re html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) print(soup.find_all(name=\u0026#39;b\u0026#39;)) \u0026#39;\u0026#39;\u0026#39; [\u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;, \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;] \u0026#39;\u0026#39;\u0026#39; from bs4 import BeautifulSoup import re html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) print(soup.find_all(name=re.compile(r\u0026#39;b|title\u0026#39;))) \u0026#39;\u0026#39;\u0026#39; [\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;, \u0026lt;body\u0026gt; \u0026lt;p align=\u0026#34;center\u0026#34; id=\u0026#34;firstpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p align=\u0026#34;blah\u0026#34; id=\u0026#34;secondpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt;, \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;, \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;] \u0026#39;\u0026#39;\u0026#39; from bs4 import BeautifulSoup import re html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) def findTag(tag): if(tag.name == \u0026#39;title\u0026#39; or tag.name == \u0026#39;b\u0026#39;): return True else: return False print(soup.find_all(name=findTag)) \u0026#39;\u0026#39;\u0026#39; [\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;, \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;, \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;] \u0026#39;\u0026#39;\u0026#39; keyword 参数 # 如果一个指定名字的参数不是搜索内置的参数名，搜索时会把该参数当作指定名字 tag 的属性来搜索，如果包含一个名字为 id 的参数，Beautiful Soup 会搜索每个 tag 的”id” 属性\n如果想用 class 过滤，不过 class 是 python 的关键词，这怎么办？加个下划线就可以，即\u0026rsquo;class_=value\u0026rsquo;\n也可以将一个参数字典传入attrs参数进行筛选\nfrom bs4 import BeautifulSoup import re html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) #查询有属性为align=center的tag print(soup.find_all(align=\u0026#39;center\u0026#39;)) print(\u0026#39;********************************\u0026#39;) attrs = { \u0026#39;id\u0026#39;:\u0026#39;secondpara\u0026#39;, \u0026#39;align\u0026#39;:\u0026#39;blah\u0026#39; } #查询属性为字典中属性的tag print(soup.find_all(attrs=attrs)) \u0026#39;\u0026#39;\u0026#39; [\u0026lt;p align=\u0026#34;center\u0026#34; id=\u0026#34;firstpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt;] ******************************** [\u0026lt;p align=\u0026#34;blah\u0026#34; id=\u0026#34;secondpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt;] \u0026#39;\u0026#39;\u0026#39; CSS选择器 # 我们在写 CSS 时，标签名不加任何修饰，类名前加点，id 名前加 #，在这里我们也可以利用类似的方法来筛选元素，用到的方法是 soup.select()，返回类型是list\nfrom bs4 import BeautifulSoup import re html = \u0026#34;\u0026#34;\u0026#34; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;Page title\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026#34;firstpara\u0026#34; align=\u0026#34;center\u0026#34; class=\u0026#34;myp\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026#34;secondpara\u0026#34; align=\u0026#34;blah\u0026#34; class=\u0026#34;myp\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026#34;\u0026#34;\u0026#34; soup = BeautifulSoup(html,\u0026#39;lxml\u0026#39;) #查询id为secondpara的tag print(soup.select(\u0026#39;#secondpara\u0026#39;)) print(\u0026#34;****************************************\u0026#34;) #查询class为myp的tag print(soup.select(\u0026#39;.myp\u0026#39;)) \u0026#39;\u0026#39;\u0026#39; [\u0026lt;p align=\u0026#34;blah\u0026#34; class=\u0026#34;myp\u0026#34; id=\u0026#34;secondpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt;] **************************************** [\u0026lt;p align=\u0026#34;center\u0026#34; class=\u0026#34;myp\u0026#34; id=\u0026#34;firstpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;one\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt;, \u0026lt;p align=\u0026#34;blah\u0026#34; class=\u0026#34;myp\u0026#34; id=\u0026#34;secondpara\u0026#34;\u0026gt;This is paragraph \u0026lt;b\u0026gt;two\u0026lt;/b\u0026gt;.\u0026lt;/p\u0026gt;] \u0026#39;\u0026#39;\u0026#39; ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/1b37041b/339aff44/f23b1be9/","section":"文章","summary":"\u003cp\u003e简单来说，Beautiful Soup 是 python 的一个库，最主要的功能是从网页抓取数据。官方解释如下：\u003c/p\u003e","title":"6、beautifulsoup","type":"posts"},{"content":" WebSocket # WebSocket是HTML5提供的一种新的网络通信协议。它实现了服务端与客户端的全双工通信，建立在传输层TCP协议之上，即浏览器与服务端需要先建立TCP协议，再发送WebSocket连接建立请求。\n为什么需要 WebSocket？ # 因为 HTTP 协议有一个缺陷：通信只能由客户端发起，HTTP 协议做不到服务器主动向客户端推送信息\n比如说我们想要获取一个实时的新闻信息，在每次更新新闻信息后，我们都需要刷新页面才能获取到最新的信息，只有再次发起客户端请求，服务器端才会返回结果。但是服务器端不能做到推送消息给客户端，当然我们可以使用轮询，查看服务器有没有新的消息，比如 \u0026ldquo;聊天室\u0026rdquo; 这样的，但是轮询效率是非常低的，因此WebSocket就这样产生了。\n建立连接的过程 # 客户端发送请求信息，服务端接收到这个请求并返回响应信息。\n当连接建立后，客户端发送http请求时，通过Upgrade:webSocket Connection:Upgrade 告知服务器需要建立的是WebSocket连接，并且还会传递WebSocket版本号、协议的字版本号、原始地址、主机地址， WebSocket相互通信的Header很小，大概只有2Bytes。\n实现 # 原生实现 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.java-websocket\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;Java-WebSocket\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.5.5\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 服务端 # 实现WebSocketServer抽象类\npublic class WsServer extends WebSocketServer { public WsServer(int port){ super(new InetSocketAddress(port)); } public WsServer(InetSocketAddress address){ super(address); } // 建立连接 @Override public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) { broadcast(webSocket.getRemoteSocketAddress().getAddress().getHostAddress() + \u0026#34; 连接服务器！\u0026#34;); // 获取客户端连接的路径 // 例如客户端使用ws://ip:port/test连接，获取值为/test broadcast(\u0026#34;连接的路径： \u0026#34; + clientHandshake.getResourceDescriptor()); } // 连接关闭 @Override public void onClose(WebSocket webSocket, int code, String reason, boolean remote) { broadcast(webSocket + \u0026#34; 与服务器断开连接！\u0026#34;); System.out.println(webSocket + \u0026#34; 与服务器断开连接！\u0026#34;); } // 收到消息 @Override public void onMessage(WebSocket webSocket, String message) { // 广播消息，所有连接都会收到 broadcast(message); // 只给当前连接发送消息 // webSocket.send(message); System.out.println(webSocket + \u0026#34;: \u0026#34; + message); } // 连接异常 @Override public void onError(WebSocket webSocket, Exception ex) { ex.printStackTrace(); } // 服务启动 @Override public void onStart() { System.out.println(\u0026#34;WsServer 启动成功!\u0026#34;); setConnectionLostTimeout(0); setConnectionLostTimeout(100); } } 客户端 # public class Client { public static void main(String[] args) throws Exception{ // 创建客户端实例 WebSocketClient webSocketClient = new WebSocketClient(new URI(\u0026#34;ws://localhost:8887/\u0026#34;), new Draft_6455()) { //连接服务端时触发 @Override public void onOpen(ServerHandshake handshakedata) { System.out.println(\u0026#34;与服务器连接成功！\u0026#34;); } //收到服务端消息时触发 @Override public void onMessage(String message) { System.out.println(\u0026#34;收到消息：\u0026#34; + message); } //和服务端断开连接时触发 @Override public void onClose(int code, String reason, boolean remote) { System.out.println(\u0026#34;退出连接！\u0026#34;); } //连接异常时触发 @Override public void onError(Exception ex) { ex.printStackTrace(); } }; // 建立连接 webSocketClient.connect(); BufferedReader sysin = new BufferedReader(new InputStreamReader(System.in)); while (true) { String in = sysin.readLine(); // 发送消息 webSocketClient.send(in); if (in.equals(\u0026#34;exit\u0026#34;)) { webSocketClient.close(); break; } } } } SpringBoot整合 # 服务端 # 1、添加依赖\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-websocket\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、配置类\n@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } @Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); // 设置最大Text大小 container.setMaxTextMessageBufferSize(512000); // 设置最大Binary大小 container.setMaxBinaryMessageBufferSize(512000); // 设置空闲连接超时ms container.setMaxSessionIdleTimeout(5000L); return container; } } 3、ws服务端逻辑\n@Component @ServerEndpoint(\u0026#34;/test/{id}\u0026#34;) public class WsServer { private static final Logger log = LoggerFactory.getLogger(WsServer.class); private static final ConcurrentHashMap\u0026lt;String,Session\u0026gt; webSockets = new ConcurrentHashMap\u0026lt;\u0026gt;(); @OnOpen public void onOpen(Session session, @PathParam(\u0026#34;id\u0026#34;) String id){ webSockets.put(id,session); log.info(\u0026#34;新的客户端id : {} ,URI: {} 连接服务器!\u0026#34;,id,session.getRequestURI()); } @OnClose public void onClose(Session session, @PathParam(\u0026#34;id\u0026#34;) String id){ webSockets.remove(id); log.info(\u0026#34;客户端id : {} 与服务器断开连接!\u0026#34;,id); } @OnMessage public void onMessage(Session session, @PathParam(\u0026#34;id\u0026#34;) String id, String message){ log.info(\u0026#34;收到客户端 id : {} 发送的消息 : {}\u0026#34;,id,message); } @OnError public void onError(Session session, @PathParam(\u0026#34;id\u0026#34;) String id,Throwable error){ log.info(\u0026#34;客户端id : {} ,发生异常 : {}\u0026#34;,id,error); } /** * 发送消息给指定客户端 * @param message * @param id */ public void send(String message,String ... id){ try { for (String i : id){ webSockets.get(i).getBasicRemote().sendText(message); } }catch (Exception e){ e.printStackTrace(); } } } 4、启动类\n@SpringBootApplication public class Main implements CommandLineRunner { @Autowired private WsServer wsServer; public static void main(String[] args) throws Exception { SpringApplication.run(Main.class); } @Override public void run(String... args) throws Exception { Scanner scanner = new Scanner(System.in); while (true){ System.out.println(\u0026#34;输入目标客户端id : \u0026#34;); String ids = scanner.next(); System.out.println(\u0026#34;输入要发送的消息 : \u0026#34;); String msg = scanner.next(); wsServer.send(msg,ids.split(\u0026#34;,\u0026#34;)); } } } 前端客户端实现 # \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;WebSocket\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;input id=\u0026#34;text\u0026#34; type=\u0026#34;text\u0026#34;/\u0026gt; \u0026lt;button onclick=\u0026#34;send()\u0026#34;\u0026gt;发送消息\u0026lt;/button\u0026gt; \u0026lt;hr/\u0026gt; \u0026lt;button onclick=\u0026#34;closeWebSocket()\u0026#34;\u0026gt;关闭WebSockeet连接\u0026lt;/button\u0026gt; \u0026lt;hr/\u0026gt; \u0026lt;div id=\u0026#34;message\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; \u0026lt;script type=\u0026#34;text/javascript\u0026#34;\u0026gt; var webSocket = null; //判断当前浏览器是否支持WebSocket if (\u0026#39;WebSocket\u0026#39; in window){ webSocket = new WebSocket(\u0026#39;ws://localhost:8090/webSocket\u0026#39;); } else{ alert(\u0026#34;当前浏览器不支持WebSocket\u0026#34;); } //连接发生错误的回调方法 webSocket.onerror = function () { setMessageInnerHTML(\u0026#34;WebSocket连接发生错误！\u0026#34;); } webSocket.onopen = function () { setMessageInnerHTML(\u0026#34;WebSocket连接成功！\u0026#34;) } webSocket.onmessage = function (event) { setMessageInnerHTML(event.data); } webSocket.onclose = function () { setMessageInnerHTML(\u0026#34;WebSocket连接关闭\u0026#34;); } window.onbeforeunload = function () { closeWebSocket(); } function closeWebSocket() { webSocket.close(); } function send() { var message = document.getElementById(\u0026#39;text\u0026#39;).value; webSocket.send(message); } //将消息显示在网页上 function setMessageInnerHTML(innerHTML) { document.getElementById(\u0026#39;message\u0026#39;).innerHTML += innerHTML + \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; } \u0026lt;/script\u0026gt; ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/4f5b7444/10075ef2/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eWebSocket \n    \u003cdiv id=\"websocket\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#websocket\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eWebSocket是HTML5提供的一种新的网络通信协议。它实现了服务端与客户端的全双工通信，建立在传输层TCP协议之上，即浏览器与服务端需要先建立TCP协议，再发送WebSocket连接建立请求。\u003c/p\u003e","title":"6、WebSokect","type":"posts"},{"content":" 泛型的概念 # 所谓泛型，就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用（例如，继承或实现这个接口，用这个类型声明变量、创建对象时确定（即传入实际的类型参数，也称为类型实参）。\nJDK1.5引入\n泛型的定义中，不可以使用基本数据类型，可以使用对应的包装类进行替换\n可避免的问题 # 1、类型不安全，导入数据存入混乱，添加泛型，在编译时就会进行类型检查，保证了数据安全\n2、避免了强制类型转换时，出现异常ClassCastException 伪泛型 # Java中的泛型是伪泛型，泛型技术实际上是Java语言的一颗语法糖，Java语言中的泛型实现方法称为类型擦除，基于这种方法实现的泛型被称为伪泛型。\nJava的泛型只在程序源码中存在，在编译后的字节码文件中，就已经被替换为原来的原始类型（Raw Type，也称为裸类型）了，并且在相应的地方插入了强制转型代码，因此对于运行期的Java语言来说，ArrayList\u0026lt;int\u0026gt;与ArrayList\u0026lt;String\u0026gt;就是同一个类ArrayList。\n所以以下代码，不会造成任何异常：\nList\u0026lt;Integer\u0026gt; list1 = new ArrayList\u0026lt;\u0026gt;(); List list2 = new ArrayList\u0026lt;\u0026gt;(); list2.add(\u0026#34;hello\u0026#34;); list1 = list2; System.out.println(list1); 泛型的使用 # List\u0026lt;String\u0026gt; li = new ArrayList\u0026lt;String\u0026gt;();\n1、在实例化集合类时，可以指明具体的泛型类型\n2、指明完泛型以后，在集合类或接口中凡是定义类或接口时，内部结构（比如：方法、构造器、属性等）使用到类的泛型的位置，都指定为实例化的泛型类型。\n3、如果实例化时，没指明泛型的类型。默认类型为java.lang.Object类型。\n4、泛型可以嵌套使用\n5、JDK7新特性，类型推断，可以写成List\u0026lt;String\u0026gt; li = new ArrayList\u0026lt;\u0026gt;();\n泛型类 # 修饰符 class 类名\u0026lt;泛型形参1，泛型形参2，......\u0026gt; { 在类中使用泛型形参对类型进行占位; } 泛型形参名称，一般由单个大写字母组成，常用的：E、T、V、N、K 约定的含义为 E - Element (在集合中使用，因为集合中存放的是元素) T - Type（Java 类） K - Key（键） V - Value（值） N - Number（数值类型） 如果泛型类在实例化的时候没有指明泛型的类型，那么默认的类型就是Object类型，如果实例化的类是带有泛型的，那么建议在实例化的时候指明类的泛型\n泛型不同的两个引用不可以互相赋值 在类、接口上声明的泛型，在本类或本接口中即代表某种类型，可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型，但在静态方法中不能使用类的泛型。 异常类不能是泛型的 如果子类在继承带泛型的父类时，指明了泛型类型。则实例化子类对象时，不再需要指明泛型。 子类除了指定或保留父类的泛型，还可以增加自己的泛型 泛型接口 # 修饰符 interface 接口名\u0026lt;泛型形参\u0026gt; { 在接口中使用泛型形参对类型进行占位; } 泛型方法 # 泛型方法是指在方法中出现了泛型的结构，泛型参数与类的泛型参数没任何关系和类是不是泛型类也没有关系。\n可以声明为静态的。原因：泛型参数是在调用方法时确定的。并非在实例化类时确定。\npublic \u0026lt;E\u0026gt; List\u0026lt;E\u0026gt; 方法名(E[] arr){ 方法体; } 使用泛型的注意事项 # 类A是类B的父类，G\u0026lt;A\u0026gt;和G\u0026lt;B\u0026gt;不具备子父类关系，属于并列关系\n实例化后，操作原来泛型位置的结构必须与指定的泛型类型一致\n泛型不同的引用不能相互赋值\n未指定泛型类型的除外，例如ArrayList的实例可以赋值给ArrayList\u0026lt;String\u0026gt; 泛型如果不指定，将被擦除，泛型对应的类型均按照Object处理，但是不等价于泛型\u0026lt;Object\u0026gt;\n如果泛型结构是一个接口或者抽象类，则不可以创建泛型类的对象\n泛型的指定不能使用基本数据类型，可以使用包装类替换\n在类或接口上声明的泛型，可以作为此类非静态属性类型、非静态方法的参数、返回值类型，但是在静态方法中不能使用类的泛型\n异常类不能是泛型的\n不能使用new E[10]，但是可以使用E[] e = (E[])new Object[10]来代替\n父类有泛型，子类可以选择保留泛型也可以指定泛型类型\n子类除了指定或保留父类的泛型，还可以增加自己的泛型\n通配符 # Java泛型通配符遵循：PECS(Producer Extends Consumer Super)原则\n协变性、逆变性、不变性 # 若类A是类B的子类，则记作A ≦ B。设有类型变换f()\n协变性 允许将一个更具体的类型（子类型）当作一个更一般的类型（父类型）的替代品使用 当A ≦ B时，有f(A)≦ f(B)，则称变换f()具有协变性 Java中数组具有协变性，例如：Object[] a = new Integer[10] Java中上限统配符声明的泛型具有协变性：例如：\u0026lt;? extends T\u0026gt; 逆变性 逆变性允许将一个泛型类型参数替代为其超类型 当A ≦ B时，有f(B)≦ f(A)，则称变换f()具有逆变性 Java中下限统配符声明的泛型具有协变性：例如：\u0026lt;? super T\u0026gt; 不变性 表示泛型类型参数既不是协变的也不是逆变的，即不能被替代为其超类型或子类型 当A ≦ B时，f(A)与f(B)无关，则称变换f()具有不变性 Java中泛型具有不变性，例如：List\u0026lt;Object\u0026gt; a = new ArrayList()，List\u0026lt;Integer\u0026gt; b = new ArrayList()，此时如果a = b会编译错误 理解 # 假设Orange类是Fruit类的子类，以集合类List\u0026lt;T\u0026gt;为例：\n协变(covariance)：满足条件诸如List\u0026lt;Orange\u0026gt;是List\u0026lt;? extends Fruit\u0026gt;的子类型时，称为协变。 逆变(covariance)：满足条件诸如List\u0026lt;Fruit\u0026gt;是List\u0026lt;? super Orange\u0026gt;的子类型时，称为逆变。 不变(invariance)：表示List\u0026lt;Orange\u0026gt;和List\u0026lt;Fruit\u0026gt;不存在型变关系。 子类(subclass)和子类型(subtype)不是同一个概念。\n里氏代换原则 # 里氏替换原则（Liskov Substitution Principle, LSP），由Barbara Liskov于1987年提出\n定义：所有引用基类（父类）的地方必须能透明地使用其子类的对象 核心思想：如果将一个父类对象替换成它的子类对象后，该程序不会发生异常。这也是该原则希望达到的一种理想状态。 通俗理解：子类可以扩展父类的功能，但不能改变父类原有的功能。也就是说：子类继承父类时，除添加新的方法完成新增功能外，尽量不要重写父类的方法。 含义 子类完全拥有父类的方法，且具体子类必须实现父类的抽象方法。 子类中可以增加自己的方法。 当子类覆盖或实现父类的方法时，方法的形参要比父类方法的更为宽松。 当子类覆盖或实现父类的方法时，方法的返回值要比父类更严格。 // 含义中最后两句话的理解 class Super{ Number method(Number n){} } class Sub{ @Override Integer method(Object n){} } // 此时如果将父类引用指向子类对象（多态） Super s = new Sub(); // 由于子类所重写的方法，形参比父类更宽松，返回值比父类更严格，程序并不会发生异常 Number n = s.method(1); 限制的通配符 # 由于无限通配符、上限通配符，元素无法确认最小类型，所以都不允许add()，其实就是都不允许调用定义了最小类型无法确认的形参的方法。\n无限通配符（只读） # 语法：\u0026lt;?\u0026gt;，例如List\u0026lt;?\u0026gt;，表示了List中元素类型未知\nList\u0026lt;?\u0026gt; list1 = new ArrayList\u0026lt;\u0026gt;(); List\u0026lt;Integer\u0026gt; list2 = new ArrayList\u0026lt;\u0026gt;(); list1 = list2; //success，由于list1的泛型为Object及其子类 list1.add(1); //error，使用无限通配符不允许调用参数为泛型的方法 上限通配符(只读) # 语法：\u0026lt;? extends T\u0026gt;，例如List\u0026lt;? extends T\u0026gt;，表示了List中元素类型转换的上界\nList\u0026lt;? extends Number\u0026gt; list1 = new ArrayList\u0026lt;\u0026gt;(); List\u0026lt;Integer\u0026gt; list2 = new ArrayList\u0026lt;\u0026gt;(); list1 = list2; //success，由于list1的泛型为Number及其子类 list1.add(1); //error，使用上限通配符不允许调用参数为泛型的方法 下限通配符（只写） # 语法：\u0026lt;? super T\u0026gt;，例如List\u0026lt;? super T\u0026gt;，表示了List中元素类型转换的下界\nList\u0026lt;? super Number\u0026gt; list1 = new ArrayList\u0026lt;\u0026gt;(); List\u0026lt;Number\u0026gt; list2 = new ArrayList\u0026lt;\u0026gt;(); List\u0026lt;Object\u0026gt; list3 = new ArrayList\u0026lt;\u0026gt;(); list1 = list2;//success，由于list1的泛型为Number及其父类 list1 = list3;//success，由于list1的泛型为Number及其父类 list1.add(1); //success ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/fbeea4f4/afe70010/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e泛型的概念 \n    \u003cdiv id=\"泛型的概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%b3%9b%e5%9e%8b%e7%9a%84%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e所谓泛型，就是\u003cstrong\u003e允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型\u003c/strong\u003e。这个类型参数将在使用（例如，继承或实现这个接口，用这个类型声明变量、创建对象时确定（即传入实际的类型参数，也称为类型实参）。\u003c/p\u003e","title":"6、泛型","type":"posts"},{"content":" 关键字：static（静态） # 作用范围 # 可以用来修饰的结构：主要用来修饰类的内部结构\n属性、方法、代码块、内部类\nstatic修饰属性 # 静态变量（或类变量）\n静态属性 vs 非静态属性 # 属性，是否使用static修饰，又分为：静态属性 vs 非静态属性(实例变量)\n实例变量：我们创建了类的多个对象，每个对象都独立的拥一套类中的非静态属性。当修改其中一个对象中的非静态属性时，不会导致其他对象中同样的属性值的修改。\n静态变量：我们创建了类的多个对象，多个对象共享同一个静态变量。当通过某一个对象修改静态变量时，会导致其他对象调用此静态变量时，是修改过了的。\nstatic修饰属性的其他说明 # 静态变量随着类的加载而加载。可以通过类.静态变量的方式进行调用\n静态变量的加载要早于对象的创建。\n由于类只会加载一次，则静态变量在内存中也只会存在一份：存在方法区的静态域中\n内存解析 # static修饰方法 # 随着类的加载而加载，可以通过类.静态方法的方式进行调用\n静态方法中，只能调用静态的方法或属性\n非静态方法中，既可以调用非静态的方法或属性，也可以调用静态的方法或属性\nstatic修饰代码块 # 静态代码块 # 内部可以输出语句\n随着类的加载而执行,而且只执行一次\n作用：初始化类的信息\n如果一个类中定义了多个静态代码块，则按照声明的先后顺序执行\n静态代码块的执行要优先于非静态代码块的执行\n静态代码块内只能调用静态的属性、静态的方法，不能调用非静态的结构\n非静态代码块 # 内部可以输出语句\n随着对象的创建而执行\n每创建一个对象，就执行一次非静态代码块\n作用：可以在创建对象时，对对象的属性等进行初始化\n如果一个类中定义了多个非静态代码块，则按照声明的先后顺序执行\n非静态代码块内可以调用静态的属性、静态的方法，或非静态的属性、非静态的方法\nstatic的注意点 # 在静态的方法内，不能使用this关键字、super关键字\n什么时候使用static # 属性：\n属性是可以被多个对象所共享的，不会随着对象的不同而不同的。\n类中的常量也常常声明为static\n方法：\n操作静态属性的方法，通常设置为static的\n工具类中的方法，习惯上声明为static的。 比如：Math、Arrays、Collections\nmain方法 # 作为程序的入口出现\nmain()方法也是一个普通的静态方法\nmain()方法可以作为我们与控制台交互的方式。\n面向对象特征二：继承性 # extends：延展、扩展\n格式 # class A extends B{}\nA：子类、派生类、subclass\nB：父类、超类、基类、superclass\n子类继承父类以后有哪些不同？ # 体现：一旦子类A继承父类B以后，子类A中就获取了父类B中声明的所有的属性和方法。\n特别的，父类中声明为private的属性或方法，子类继承父类以后，仍然认为获取了父类中私有的结构。只因为封装性的影响，使得子类不能直接调用父类的结构而已。\n子类继承父类以后，还可以声明自己特有的属性或方法：实现功能的拓展。\n子类和父类的关系，不同于子集和集合的关系。\nJava中继承性的说明 # 一个类可以被多个子类继承。\nJava中类的单继承性：一个类只能有一个父类\n子父类是相对的概念。\n子类直接继承的父类，称为：直接父类。间接继承的父类称为：间接父类\n子类继承父类以后，就获取了直接父类以及所间接父类中声明的属性和方法\n方法重写 # override 或 overwrite\n子类继承父类以后，可以对父类中同名同参数的方法，进行覆盖操作\n重写以后，当创建子类对象以后，通过子类对象调用子父类中的同名同参数的方法时，实际执行的是子类重写父类的方法。\n例如\nclass Circle{ public double findArea(){}//求面积 } class Cylinder extends Circle{ public double findArea(){}//求表面积 } // -------------------------------------- class Account{ public boolean withdraw(double amt){} } class CheckAccount extends Account{ public boolean withdraw(double amt){} } 重写的规则 # 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{ //方法体 } 约定俗称：子类中的叫重写的方法，父类中的叫被重写的方法\n参数\n子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同 权限修饰符\n子类重写的方法的权限修饰符大于父类被重写的方法的权限修饰符 特殊情况：子类不能重写父类中声明为private、static、final的方法 返回值类型：\n父类被重写的方法的返回值类型是void，则子类重写的方法的返回值类型只能是void 父类被重写的方法的返回值类型是A类型，则子类重写的方法的返回值类型可以是A类或A类的子类 父类被重写的方法的返回值类型是基本数据类型(比如：double)，则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double) 异常\n子类重写的方法抛出的异常类型小于父类被重写的方法抛出的异常类型 super关键字 # super 关键字可以理解为：父类的\n可以用来调用的结构：属性、方法、构造器\nsuper调用属性、方法 # 我们可以在子类的方法或构造器中。通过使用super.属性或super.方法的方式，显式的调用父类中声明的属性或方法。但是，通常情况下，我们习惯省略super.\n特殊情况：当子类和父类中定义了同名的属性时，我们要想在子类中调用父类中声明的属性，则必须显式的使用super.属性的方式，表明调用的是父类中声明的属性。\n特殊情况：当子类重写了父类中的方法以后，我们想在子类的方法中调用父类中被重写的方法时，则必须显式的使用super.方法的方式，表明调用的是父类中被重写的方法。\nsuper调用构造器 # 我们可以在子类的构造器中显式的使用\u0026quot;super(形参列表)\u0026ldquo;的方式，调用父类中声明的指定的构造器\nsuper(形参列表)的使用，必须声明在子类构造器的首行！\n我们在类的构造器中，针对于this(形参列表)或super(形参列表)只能二选一，不能同时出现\n在构造器的首行，没显式的声明this(形参列表)或super(形参列表)，则默认调用的是父类中空参的构造器：super()\n在类的多个构造器中，至少一个类的构造器中使用了super(形参列表)，调用父类中的构造器\n类、对象的初始化顺序 # 加载父类（给父类的静态属性开辟空间、赋默认值、赋初始值，执行静态块）\n加载子类（给子类的静态属性开辟空间、赋默认值、赋初始值，执行静态块）\n以下是在创建子类对象的时候才会发生：\n对父类中定义的非静态属性分配空间，赋默认值，做初始化（执行父类中定义的初始化块，执行父类的构造方法），注意：默认执行的是父类的无参数的构造方法，如果在子类中使用super显示的调用了父类的其他构造方法，无参的就不会再次被调用\n对子类中定义的非静态属性进行初始化（执行子类中定义的初始化块，执行子类中定义的构造方法，创建子类对象）\n关键字：abstract（抽象的） # 可以用来修饰：类、方法\n不可以和private、static、final、一起使用，不可以修饰构造器\nabstract修饰类：抽象类 # 此类不能实例化\n抽象类中一定有构造器，便于子类实例化时调用\n开发中，都会提供抽象类的子类，让子类对象实例化，完成相关的操作，抽象的使用前提：继承性\nabstract修饰方法：抽象方法 # 抽象方法只有方法的声明，没方法体\n包含抽象方法的类，一定是一个抽象类。反之，抽象类中可以没有抽象方法的。\n若子类重写了父类中的所的抽象方法后，此子类方可实例化\n若子类没重写父类中的所的抽象方法，则此子类也是一个抽象类，需要使用abstract修饰\n面向对象特征三：多态性 # 可以理解为一个事物的多种形态。\n对象的多态性：父类的引用指向子类的对象（或子类的对象赋给父类的引用）\n多态性的使用：虚拟方法调用 # 有了对象的多态性以后，我们在编译期，只能调用父类中声明的方法，但在运行期，我们实际执行的是子类重写父类的方法。\n总结：编译，看左边；运行，看右边。\n对象的多态性，只适用于方法，不适用于属性（编译和运行都看左边）\n向上转型 # 多态性的体现\n向下转型 # 为什么使用向下转型 # 有了对象的多态性以后，内存中实际上是加载了子类特有的属性和方法的，但是由于变量声明为父类类型，导致编译时，只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。如何才能调用子类特的属性和方法？使用向下转型。 如何实现向下转型 # 使用强制类型转换符：() 使用时的注意点 # 使用强转时，可能出现ClassCastException的异常。\n为了避免在向下转型时出现ClassCastException的异常，我们在向下转型之前，先进行instanceof的判断，一旦返回true，就进行向下转型。如果返回false，不进行向下转型。\ninstanceof 的使用 # a instanceof A : 判断对象a是否是类A的实例。如果是，返回true；如果不是，返回false。\n类B是类A的父类：a instanceof A返回true,a instanceof B也返回true\n","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/f742d77f/66d6513f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e关键字：static（静态） \n    \u003cdiv id=\"关键字static静态\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%85%b3%e9%94%ae%e5%ad%97static%e9%9d%99%e6%80%81\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e作用范围 \n    \u003cdiv id=\"作用范围\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bd%9c%e7%94%a8%e8%8c%83%e5%9b%b4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e可以用来修饰的结构：主要用来修饰类的内部结构\u003c/p\u003e","title":"6、面向对象编程（中）","type":"posts"},{"content":" Filter # 过滤器，是JavaWeb三大组件之一，是一个驻留在服务端的Web组件，可以截取用户端和资源之间的请求和响应信息，并对信息进行过滤 过滤器可以把对web资源的请求和响应拦截下来，做一些处理后再交给下一个过滤器或客户端。从而实现一些特殊的功能。 过滤器一般完成一些通用操作，比如：自动登录、权限控制、统一编码格式设置、敏感字符过滤等。 工作流程 # 生命周期 # 当服务器启动的时候，Filter对象会被创建，并且调用init()方法 当我们通过浏览器请求Servlet的时候，Servlet对象会被创建，并且调用init()方法，然后调用过滤器的doFilter()方法对请求过滤，然后再调用servlet的service()方法，然后再调用过滤器的doFilter()方法对响应过滤 当我们多次访问Servlet的时候，只会重复的调用filter的doFilter()方法和Servlet的service()方法 当我们正常的关闭服务器的时候，会先执行Servlet的destroy()方法，然后再执行Filter的destroy()方法 过滤器的实现 # 1、实现javax.servlet.Filter接口 # package top.ygang.filters; import javax.servlet.*; import java.io.IOException; public class MyFilter implements Filter { @Override public void init(FilterConfig arg0) throws ServletException { // 初始化方法，接受一个 FilterConfig 的参数，该参数是对Filter的配置，在服务器启动时自动调用 // 一般不用实现该方法 } @Override public void destroy() { // 服务器关闭时调用，释放Filter占用的资源 // 一般不用实现该方法 } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException { // 过滤方法，对 Resquest 与 Response 对象进行处理，之后交给其他Filter或服务器端 // 如果符合过滤规则则通过chain.doFilter(request,response);进行放行 } } 2、配置过滤器拦截规则 # url-pattern的配置（配置拦截路径） # 具体的资源路径：/xxx /servletDemo1，/demo1.jsp 根据路径进行拦截：/xxx/* /user/*，/goods/* 针对后缀名进行配置：*.jsp 拦截所有的配置：/* 方式一：通过web.xml文件 # \u0026lt;filter\u0026gt; \u0026lt;!-- 配置过滤器名称，一般为Filter实现类类名 --\u0026gt; \u0026lt;filter-name\u0026gt;MyFilter\u0026lt;/filter-name\u0026gt; \u0026lt;!-- 配置过滤器实现类全类名 --\u0026gt; \u0026lt;filter-class\u0026gt;top.ygang.filters.MyFilter\u0026lt;/filter-class\u0026gt; \u0026lt;/filter\u0026gt; \u0026lt;!-- 指定过滤器规则 --\u0026gt; \u0026lt;filter-mapping\u0026gt; \u0026lt;!-- 规则所属的过滤器 --\u0026gt; \u0026lt;filter-name\u0026gt;MyFilter\u0026lt;/filter-name\u0026gt; \u0026lt;!-- 过滤器拦截路径 --\u0026gt; \u0026lt;filter-pattern\u0026gt;/*\u0026lt;/filter-pattern\u0026gt; \u0026lt;/filter-mapping\u0026gt; 方式二：通过注解@WebFilter # @WebFilter({\u0026#34;/login.jsp\u0026#34;,\u0026#34;/auth.jsp\u0026#34;}) 配置多个过滤器（过滤器链） # 过滤相同资源时执行的先后顺序 # 配置文件：谁在配置文件的上面谁先执行 注解：根据类名的字符串的字典顺序（字母a-z）进行排序由小到大 使用举例 # 过滤字符，防止中文乱码 # 如果是tomcat8之前的版本，该方法只可以解决post请求的乱码处理\n@WebFilter(\u0026#34;/*\u0026#34;) public class CharacterFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding(\u0026#34;utf-8\u0026#34;); chain.doFilter(request,response); } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/fb007bea/6ff711f8/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eFilter \n    \u003cdiv id=\"filter\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#filter\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e过滤器，是JavaWeb三大组件之一，是一个驻留在服务端的Web组件，可以\u003cstrong\u003e截取用户端和资源之间的请求和响应信息，并对信息进行过滤\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003e过滤器可以把对web资源的请求和响应\u003ccode\u003e拦截\u003c/code\u003e下来，做一些处理后再交给下一个过滤器或客户端。从而实现一些特殊的功能。\u003c/li\u003e\n\u003cli\u003e过滤器一般完成一些\u003ccode\u003e通用操作\u003c/code\u003e，比如：自动登录、权限控制、统一编码格式设置、敏感字符过滤等。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003e工作流程 \n    \u003cdiv id=\"工作流程\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b7%a5%e4%bd%9c%e6%b5%81%e7%a8%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20230529134558112\"\n    data-zoom-src=\"/posts/3ab7256e/98c7af00/fb007bea/6ff711f8/image/image-20230529134558112.png\"\n    src=\"/posts/3ab7256e/98c7af00/fb007bea/6ff711f8/image/image-20230529134558112.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"6、Filter","type":"posts"},{"content":"在Spring框架中，提供了一些用于调度任务的注解，用于实现定时任务的执行。\n相关注解 # 注解 说明 @EnableScheduling 在Spring配置类上添加@EnableScheduling注解来启用定时任务功能 @Scheduled 用于标记定时任务的方法，并配置任务的触发时间表达式或固定频率。 注意事项 # 定时任务方法必须是无参方法，可以是void返回类型或具有返回值。 时间表达式可以使用cron表达式或固定频率进行配置，例如cron = \u0026quot;0 0 8 * * ?\u0026quot;表示每天早上8点触发，fixedRate = 5000表示每隔5秒触发一次。 定时任务的方法默认是串行执行的，如果任务执行时间较长，可能会影响后续任务的触发。可以通过配置线程池或使用异步方法（也就是可以和@Async搭配使用）来并发执行定时任务。 定时任务应避免使用阻塞操作，例如长时间的I/O操作或线程休眠等，以免影响整个应用程序的性能和响应性。 代码示例 # 配置类开启定时任务\n@Configuration @ComponentScan(\u0026#34;top.ygang\u0026#34;) @EnableScheduling public class SpringConfiguration { } 定义任务方法\n@Component public class MyTask { @Scheduled(cron = \u0026#34;0 0 8 * * ?\u0026#34;) // 每天早上8点触发 public void executeTask() { // 定时任务执行的方法体 } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/c70ae83e/69060ec1/","section":"文章","summary":"\u003cp\u003e在Spring框架中，提供了一些用于调度任务的注解，用于实现定时任务的执行。\u003c/p\u003e","title":"6、Schedule","type":"posts"},{"content":" 关于Spring Cache # Spring Cache是作用在方法上的，其核心思想是这样的：当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中，等到下次利用同样的参数来调用该方法时将不再执行该方法，而是直接从缓存中获取结果进行返回。所以在使用Spring Cache的时候我们要保证我们缓存的方法对于相同的方法参数要有相同的返回结果。\n如果是颗粒度要求不高的项目，那么可以使用Spring Cache\n使用Spring Cache需要我们做两方面的事 # 1、声明某些方法使用缓存\n2、配置Spring对Cache的支持\n和Spring对事务管理的支持一样，Spring对Cache的支持也有基于注解和基于XML配置两种方式。 使用方式一：基于Spring自带的缓存工具 # 1、在启动类上添加注解\n@EnableCaching 2、在需要缓存结果的方法上，添加注解@Cacheable(\u0026quot;value\u0026quot;)\n@Cacheable(\u0026#34;cache1\u0026#34;)//Cache是发生在cache1上的 @GetMapping(\u0026#34;/find\u0026#34;) public User find() { System.out.println(\u0026#34;into find\u0026#34;); User user = new User(1,\u0026#34;zfs\u0026#34;,\u0026#34;ljh\u0026#34;); return user; } 此方法，如果项目重启，那么缓存就会丢失\n使用方式二：基于Redis进行缓存（推荐） # 1、需要在spring配置yml文件中添加节点\nspring: cache: type: redis 2、在maven配置文件pom.xml中，引入spring-redis依赖，这个时候，缓存就成功地切换到Redis上了\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-data-redis\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 关键注解 # @Cacheable # 此注解，可以标记在方法上，也可以标记在类上，当标记在一个方法上时表示该方法是支持缓存的，当标记在一个类上时则表示该类所有的方法都是支持缓存的。\n@Cacheable(\u0026#34;cache1\u0026#34;)//Cache是发生在cache1上的 public User find(Integer id) { return null; } key属性 # 这个时候，保存在Redis数据库中的key，格式为\nvalue::key\n@Cacheable(value=\u0026#34;users\u0026#34;, key=\u0026#34;#id\u0026#34;) public Userinfo find(Integer id) { return new Userinfo(id,\u0026#34;刘德华\u0026#34;,\u0026#34;张曼玉\u0026#34;); } @Cacheable(value=\u0026#34;users\u0026#34;) public List findAll() { return new ArrayList(); } @Cacheable(value=\u0026#34;users\u0026#34;, key=\u0026#34;#p0\u0026#34;)//代表方法的第一个形参 public Userinfo find(Integer id) { return new Userinfo(id,\u0026#34;刘德华\u0026#34;,\u0026#34;张曼玉\u0026#34;); } @Cacheable(value=\u0026#34;users\u0026#34;, key=\u0026#34;#info.uid\u0026#34;) public Userinfo find(Userinfo info) { return new Userinfo(info.getUid(),\u0026#34;刘德华\u0026#34;,\u0026#34;张曼玉\u0026#34;); } @Cacheable(value=\u0026#34;users\u0026#34;, key=\u0026#34;#p0.uid\u0026#34;) public Userinfo find(Userinfo info) { return new Userinfo(info.getUid(),\u0026#34;刘德华\u0026#34;,\u0026#34;张曼玉\u0026#34;); } condition属性 # 此时，只有在condition值为true的情况下，再会执行缓存\n@Cacheable(value=\u0026#34;users\u0026#34;, key=\u0026#34;#p0.uid\u0026#34;,condition=\u0026#34;#info.uid%2==0\u0026#34;) public Userinfo find(Userinfo info) { return new Userinfo(info.getUid(),\u0026#34;刘德华\u0026#34;,\u0026#34;张曼玉\u0026#34;); } @CachePut # 此注解标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果，而是每次都会执行该方法，并将执行结果以键值对的形式存入指定的缓存中，因此建议使用在数据库的修改方法上\n@CachePut(value=\u0026#34;users\u0026#34;, key=\u0026#34;#p0.uid\u0026#34;) public Userinfo updata(Userinfo info) { return new Userinfo(info.getUid(),\u0026#34;刘德华\u0026#34;,\u0026#34;张曼玉\u0026#34;); } @CacheEvict # 此注解是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作，因此，建议使用在数据库的删除方法上\n@CacheEvict(value=\u0026#34;users\u0026#34;, key=\u0026#34;#p0.uid\u0026#34;) public Userinfo delete(Userinfo info) { return new Userinfo(info.getUid(),\u0026#34;刘德华\u0026#34;,\u0026#34;张曼玉\u0026#34;); } allEntries属性 # 由于，findAll方法和findById方法，所产生的的缓存，可能key会不同，所以，只使用CacheEvict注解，可能会导致，findAll的缓存无法清除，所以，需要加上allEntries属性，那么就会清除所有的以value=\u0026ldquo;users\u0026quot;所产生的注解\n@CacheEvict(value=\u0026#34;users\u0026#34;, key=\u0026#34;#p0.uid\u0026#34;,allEntries=true) public Userinfo delete(Userinfo info) { return new Userinfo(info.getUid(),\u0026#34;刘德华\u0026#34;,\u0026#34;张曼玉\u0026#34;); } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/caadfbf9/938d95e1/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e关于Spring Cache \n    \u003cdiv id=\"关于spring-cache\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%85%b3%e4%ba%8espring-cache\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eSpring Cache是作用在方法上的，其核心思想是这样的：当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中，等到下次利用同样的参数来调用该方法时将不再执行该方法，而是直接从缓存中获取结果进行返回。所以在使用Spring Cache的时候我们要保证我们缓存的方法对于相同的方法参数要有相同的返回结果。\u003c/p\u003e","title":"6、SpringCache\u0026Redis","type":"posts"},{"content":" java.util.UUID # 什么是UUID # UUID是国际标准化组织（ISO）提出的一个概念。这个数值可以通过一定的算法计算出来。为了提高效率，常用的UUID可缩短至16位。UUID用来识别属性类型，在所有空间和时间上被视为唯一的标识。一般来说，可以保证这个值是真正唯一的任何地方产生的任意一个UUID都不会有相同的值。UUID是基于当前时间、计数器（counter）和硬件标识（通常为无线网卡的MAC地址）等数据计算生成的。\nUUID是由一组32位数的16进制数字所构成，UUID的标准型式包含32个16进制数字，以连字号分为五段，形式为8-4-4-4-12的32个字符，如\n550e8400-e29b-41d4-a716-446655440000\nxxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx是UUID的格式，其中有两个字母M和N M的意思是表示UUID的版本，不同的版本生成UUID的方式不同 M = 1，版本1，UUID根据时间和设备MAC地址生成 M = 2，版本2，UUID根据标识符（通常是组或用户 ID）、时间和节点 ID生成的 M = 3，版本3，UUID通过散列（MD5 作为散列算法）名字空间（namespace）标识符和名称生成的 M = 4，版本4，UUID使用随机性或为随机性生成（Java默认） M = 5，版本5，与版本3相同，只不过算法换成了SHA1 N是变体的意思：为了能兼容过去的 UUID，以及应对未来的变化，因此有了变体（Variants）这一概念，值只会是8,9,a,b其中一个 使用方法 # randomUUID() # 采用版本4，利用随机性生成\nUUID uuid = UUID.randomUUID(); System.out.println(uuid.toString()); // bddaf695-f393-498f-9230-73c399988c9c nameUUIDFromBytes() # 采用版本3，利用名称生成，只要有用户的唯一性信息。就能保证此用户的uuid的唯一性，例如（身份证号等）\nUUID uuid = UUID.nameUUIDFromBytes(\u0026#34;12345678\u0026#34;.getBytes()); System.out.println(uuid.toString()); // 25d55ad2-83aa-300a-b464-c76d713c07ad ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/409de243/4a2d728d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003ejava.util.UUID \n    \u003cdiv id=\"javautiluuid\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#javautiluuid\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e什么是UUID \n    \u003cdiv id=\"什么是uuid\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%afuuid\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003eUUID是国际标准化组织（ISO）提出的一个概念。这个数值可以通过一定的算法计算出来。为了提高效率，常用的UUID可缩短至16位。UUID用来识别属性类型，在所有空间和时间上被视为唯一的标识。一般来说，可以保证这个值是真正唯一的任何地方产生的任意一个UUID都不会有相同的值。UUID是基于当前时间、计数器（counter）和硬件标识（通常为无线网卡的MAC地址）等数据计算生成的。\u003c/p\u003e","title":"6、UUID","type":"posts"},{"content":" 创建线程 # 由于线程是操作系统直接支持的执行单元，因此，高级语言通常都内置多线程的支持，Python也不例外，并且，Python的线程是真正的Posix Thread，而不是模拟出来的线程。\nPython的标准库提供了两个模块：_thread和threading，_thread是低级模块，threading是高级模块，对_thread进行了封装。绝大多数情况下，我们只需要使用threading这个高级模块。\n创建线程的语法：\nt = threading.Thread()\n常用参数：\nname：线程名，如果不起名字Python就自动给线程命名为Thread-1，Thread-2\ntarget：线程执行的函数\nargs：传递给线程函数的参数,必须是个tuple元组类型\nimport time, threading def printCount(sec): print(\u0026#39;线程 %s 正在运行...\u0026#39; % threading.current_thread().name) n = 0 while n \u0026lt; 5: n += 1 print(\u0026#39;线程 %s \u0026gt;\u0026gt;\u0026gt; %s\u0026#39; % (threading.current_thread().name,n)) # 推迟线程的调用sec秒 time.sleep(sec) print(\u0026#39;线程 %s 结束运行...\u0026#39; % threading.current_thread().name) t1 = threading.Thread(name=\u0026#39;printConut1\u0026#39;,target=printCount,args=(1,)) t2 = threading.Thread(name=\u0026#39;printConut2\u0026#39;,target=printCount,args=(3,)) t1.start() t2.start() threading # threading.currentThread(): 返回当前的线程变量。 threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前，不包括启动前和终止后的线程。 threading.activeCount(): 返回正在运行的线程数量，与len(threading.enumerate())有相同的结果。 Thread # **run():**用以表示线程活动的方法。 **start():**启动线程活动。 join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。 isAlive(): 返回线程是否活动的。 getName(): 返回线程名。 setName(): 设置线程名。 Lock # 如果多个线程共同对某个数据修改，则可能出现不可预料的结果，为了保证数据的正确性，需要对多个线程进行同步。\nimport threading count = 0 # 每次调用先+8、后-8，结果应该是0 def addCount(): global count count = count + 8 count = count - 8 def threadRun(): for i in range(2000000): addCount() t1 = threading.Thread(target=threadRun) t2 = threading.Thread(target=threadRun) t1.start() t2.start() t1.join() t2.join() #此时的count不是0 print(count) 使用同步锁，由于锁只有一个，无论多少线程，同一时刻最多只有一个线程持有该锁，所以，不会造成修改的冲突。创建一个锁就是通过threading.Lock()来实现\n当多个线程同时执行lock.acquire()时，只有一个线程能成功地获取锁，然后继续执行代码，其他线程就继续等待直到获得锁为止。\n获得锁的线程用完后一定要释放锁，否则那些苦苦等待锁的线程将永远等待下去，成为死线程。所以我们用try...finally来确保锁一定会被释放。\nthreading.Lock().acquire()：获得锁\nthreading.Lock().release()：释放锁\nimport threading lock = threading.Lock() count = 0 # 每次调用先+8、后-8，结果应该是0 def addCount(): #加锁 lock.acquire() global count try: count = count + 8 count = count - 8 finally: #释放锁 lock.release() def threadRun(): for i in range(2000000): addCount() t1 = threading.Thread(target=threadRun) t2 = threading.Thread(target=threadRun) t1.start() t2.start() t1.join() t2.join() #此时的count不是0 print(count) ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/1b37041b/a64b122d/fc2f0203/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e创建线程 \n    \u003cdiv id=\"创建线程\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%9b%e5%bb%ba%e7%ba%bf%e7%a8%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e由于线程是操作系统直接支持的执行单元，因此，高级语言通常都内置多线程的支持，Python也不例外，并且，Python的线程是真正的Posix Thread，而不是模拟出来的线程。\u003c/p\u003e","title":"6、多线程","type":"posts"},{"content":" 头文件 # 头文件是扩展名为 .h 的文件，包含了 C 函数声明和宏定义，被多个源文件中引用共享。有两种类型的头文件：程序员编写的头文件和编译器自带的头文件。\n在程序中要使用头文件，需要使用 C 预处理指令 #include 来引用它。stdio.h 头文件，它是编译器自带的头文件。\n引用头文件相当于复制头文件的内容，但是我们不会直接在源文件中复制头文件的内容，因为这么做很容易出错，特别在程序是由多个源文件组成的时候。\nA simple practice in C 或 C++ 程序中，建议把所有的常量、宏、系统全局变量和函数原型写在头文件中，在需要的时候随时引用这些头文件。\n分文件编程 # test.h\n#define SIZE 200 int sum(int a,int b); int max(int a,int b); test.c\nint sum(int a,int b){ return a + b; } int max(int a,int b){ return a \u0026gt; b ? a : b; } main.c\n#include\u0026lt;stdio.h\u0026gt; #include\u0026#34;test.h\u0026#34; int main(){ int a = 10; int b = 20; printf(\u0026#34;the num max is %d\\n\u0026#34;,max(a,b)); printf(\u0026#34;total of a and b is %d\\n\u0026#34;,sum(a,b)); return 0; } 编译执行\ngcc .\\main.c .\\test.c -o main.exe .\\main.exe the num max is 20 total of a and b is 30 ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/f571508d/8c7e092d/2c00068f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e头文件 \n    \u003cdiv id=\"头文件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%a4%b4%e6%96%87%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e头文件是扩展名为 \u003cstrong\u003e.h\u003c/strong\u003e 的文件，包含了 C 函数声明和宏定义，被多个源文件中引用共享。有两种类型的头文件：程序员编写的头文件和编译器自带的头文件。\u003c/p\u003e","title":"6、头文件","type":"posts"},{"content":" 常用的抓取数据的姿势 # 姿势1：HTMLParser 姿势2：HttpClient 姿势3：Jsoup Jsoup # 1、添加依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.jsoup\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jsoup\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.13.1\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 2、代码 # @Test public void test5() throws Exception { String url = \u0026#34;https://kns.cnki.net/kcms/detail/detail.aspx?dbcode=CJFD\u0026amp;dbname=CJFDAUTODAY\u0026amp;filename=SAHG202109022\u0026amp;uniplatform=NZKPT\u0026#34;; //解析资源路径,设置超时时间30秒 //此方法生成一个Document对象，可以按照前端方式进行操作 Document doc = Jsoup.parse(new URL(url), 30000); //获取标题 Element element = doc.getElementsByClass(\u0026#34;wx-tit\u0026#34;).get(0); String title = element.child(0).text(); System.out.println(\u0026#34;标题：\u0026#34; + title); //获取摘要 Element chDivSummary = doc.getElementById(\u0026#34;ChDivSummary\u0026#34;); String digest = chDivSummary.text(); System.out.println(\u0026#34;摘要：\u0026#34; + digest); //获取关键字 Element keywords = doc.getElementsByClass(\u0026#34;keywords\u0026#34;).get(0); int num = keywords.childNodeSize(); String keyword = \u0026#34;\u0026#34;; for (int i = 0; i \u0026lt; num; i++) { keyword += keywords.child(i).text(); } System.out.println(\u0026#34;关键字：\u0026#34; + keyword); } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/d1774b5b/35ae091c/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e常用的抓取数据的姿势 \n    \u003cdiv id=\"常用的抓取数据的姿势\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b8%b8%e7%94%a8%e7%9a%84%e6%8a%93%e5%8f%96%e6%95%b0%e6%8d%ae%e7%9a%84%e5%a7%bf%e5%8a%bf\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e姿势1：HTMLParser\u003c/li\u003e\n\u003cli\u003e姿势2：HttpClient\u003c/li\u003e\n\u003cli\u003e姿势3：Jsoup\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eJsoup \n    \u003cdiv id=\"jsoup\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jsoup\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e1、添加依赖 \n    \u003cdiv id=\"1添加依赖\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1%e6%b7%bb%e5%8a%a0%e4%be%9d%e8%b5%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.jsoup\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003ejsoup\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e1.13.1\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e2、代码 \n    \u003cdiv id=\"2代码\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#2%e4%bb%a3%e7%a0%81\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Test\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003etest5\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003ethrows\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;https://kns.cnki.net/kcms/detail/detail.aspx?dbcode=CJFD\u0026amp;dbname=CJFDAUTODAY\u0026amp;filename=SAHG202109022\u0026amp;uniplatform=NZKPT\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//解析资源路径,设置超时时间30秒\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//此方法生成一个Document对象，可以按照前端方式进行操作\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eDocument\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edoc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eJsoup\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eparse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e30000\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取标题\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eElement\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eelement\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edoc\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetElementsByClass\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;wx-tit\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eelement\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003echild\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"na\"\u003etext\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;标题：\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取摘要\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eElement\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003echDivSummary\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edoc\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetElementById\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;ChDivSummary\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edigest\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003echDivSummary\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003etext\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;摘要：\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edigest\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//获取关键字\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eElement\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ekeywords\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edoc\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetElementsByClass\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;keywords\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enum\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ekeywords\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003echildNodeSize\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ekeyword\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enum\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003ekeyword\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ekeywords\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003echild\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"na\"\u003etext\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;关键字：\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ekeyword\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"6、抓取数据","type":"posts"},{"content":"数组是放在连续的内存空间中，相同数据类型的一个集合\n一维数组 # 一维数组定义的三种方式：\n数据类型 数组名[ 数组长度 ]; 数据类型 数组名[ 数组长度 ] = { 值1，值2 ...}; 数据类型 数组名[ ] = { 值1，值2 ...}; 方式一例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { int a[2]; a[0] = 1; a[1] = 2; //获取数组长度 int arrayLength = sizeof(a) / sizeof(a[0]); for (int i = 0; i \u0026lt; arrayLength; i++) { cout \u0026lt;\u0026lt; a[i] \u0026lt;\u0026lt; endl; } system(\u0026#34;pause\u0026#34;); return 0; } 方式二例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; int main() { string a[2] = { \u0026#34;lucy\u0026#34;,\u0026#34;tom\u0026#34; }; for (int i = 0; i \u0026lt; sizeof(a) / sizeof(a[0]); i++) { cout \u0026lt;\u0026lt; a[i] \u0026lt;\u0026lt; endl; } system(\u0026#34;pause\u0026#34;); return 0; } 方式三例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; int main() { string a[] = { \u0026#34;lucy\u0026#34;,\u0026#34;tom\u0026#34; }; for (int i = 0; i \u0026lt; sizeof(a) / sizeof(a[0]); i++) { cout \u0026lt;\u0026lt; a[i] \u0026lt;\u0026lt; endl; } system(\u0026#34;pause\u0026#34;); return 0; } 数组名的用途 # 数组名是一个常量，不可以重新赋值\n获取数组元素个数 # 由于数组里面的所有元素的数据类型是一样的，所以，每一个元素的内存占用长度也是一样的，可以使用sizeof()计算出数组在内存中占用的总长度，然后除以单个元素的内存长度\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { int a[5]; int arrayLength = sizeof(a) / sizeof(a[0]); cout \u0026lt;\u0026lt; a[1] \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;数组a的元素个数是：\u0026#34; \u0026lt;\u0026lt; arrayLength \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 获取数组在内存中的首地址 # #include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; int main() { string a[] = { \u0026#34;lucy\u0026#34;,\u0026#34;tom\u0026#34; }; //获取数组在内存的首地址值:十六进制 cout \u0026lt;\u0026lt; \u0026#34;首地址：\u0026#34; \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; //首地址：008FFB5C //获取数组在内存的首地址值:十进制 cout \u0026lt;\u0026lt; \u0026#34;首地址：\u0026#34; \u0026lt;\u0026lt; (int)a \u0026lt;\u0026lt; endl; //首地址：5503028 cout \u0026lt;\u0026lt; \u0026amp;a[0] \u0026lt;\u0026lt; endl; //008FFB5C，由此可见，数组变量a，保存的就是第一个元素的地址 system(\u0026#34;pause\u0026#34;); return 0; } 获取数组中单个元素的地址值 # \u0026amp;符号，为cpp中的取址符，加在元素或变量前面可以用来取地址值\n#include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; int main() { int a = 10; //取变量a在内存中十六进制地址 cout \u0026lt;\u0026lt; \u0026amp;a \u0026lt;\u0026lt; endl; //00F5FB84 system(\u0026#34;pause\u0026#34;); return 0; } #include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; int main() { string a[] = { \u0026#34;lucy\u0026#34;,\u0026#34;tom\u0026#34; }; //获取数组第一个元素在内存的地址值:十进制 cout \u0026lt;\u0026lt; \u0026#34;地址：\u0026#34; \u0026lt;\u0026lt; (int)\u0026amp;a[0] \u0026lt;\u0026lt; endl; //地址：5503152 //获取数组第二个元素在内存的地址值:十进制 cout \u0026lt;\u0026lt; \u0026#34;地址：\u0026#34; \u0026lt;\u0026lt; (int)\u0026amp;a[1] \u0026lt;\u0026lt; endl; //地址：5503180 system(\u0026#34;pause\u0026#34;); return 0; } 二维数组 # 二维数组定义的四种方式：\n数据类型 数组名[ 行数 ][ 列数 ]; 数据类型 数组名[ 行数 ][ 列数 ] = { {数据1，数据2 } ，{数据3，数据4 } }; 数据类型 数组名[ 行数 ][ 列数 ] = { 数据1，数据2，数据3，数据4}; 数据类型 数组名[][ 列数 ] = { 数据1，数据2，数据3，数据4}; ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/d8245fe1/09e70634/41ce178e/","section":"文章","summary":"\u003cp\u003e数组是放在连续的内存空间中，相同数据类型的一个集合\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e一维数组 \n    \u003cdiv id=\"一维数组\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%80%e7%bb%b4%e6%95%b0%e7%bb%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e一维数组定义的三种方式：\u003c/p\u003e","title":"6、数组","type":"posts"},{"content":" 微服务网关 # 有一些问题：不同的微服务一般会有不同的网络地址，客户端在访问这些微服务时必须记住几十甚至几百个地址，这对于客户端方来说太复杂也难以维护。\n如果让客户端直接与各个微服务通讯，可能会有很多问题：\n客户端会请求多个不同的服务，需要维护不同的请求地址，增加开发难度 加大身份认证的难度，每个微服务需要独立认证 因此，我们需要一个微服务网关，介于客户端与服务器之间的中间层，所有的外部请求都会先经过微服务网关。客户端只需要与网关交互，只知道一个网关地址即可，这样简化了开发还有以下优点：\n易于监控 易于认证 减少了客户端与各个微服务之间的交互次数 什么是微服务网关 # API网关是一个服务器，是系统对外的唯一入口\nAPI网关封装了系统内部架构，为每个客户端提供一个统一定制的API（应用编程接口）\nAPI网关方式的核心要点是，所有的客户端和消费端都通过统一的网关接入微服务，在网关层处理所有的非业务功能\n通常，网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务\n微服务网关产品 # Kong\n基于Nginx+Lua开发，性能高，稳定，有多个可用的插件(限流、鉴权等等)可以开箱即用。 问题：只支持Http协议；二次开发，自由扩展困难；提供管理API，缺乏更易用的管控、配置方式 Zuul\nNetflix开源，功能丰富，使用JAVA开发，易于二次开发；需要运行在web容器中，如Tomcat 问题：缺乏管控，无法动态配置；依赖组件较多；处理Http请求依赖的是Web容器，性能不如Nginx Traefik\nGo语言开发；轻量易用；提供大多数的功能：服务路由，负载均衡等等；提供WebUI 问题：二进制文件部署，二次开发难度大；UI更多的是监控，缺乏配置、管理能力 Spring Cloud Gateway\nSpringCloud提供的网关服务 Nginx + lua实现 使用Nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用 问题：自注册的问题和网关本身的扩展性 Zuul网关技术 # Zuul是Netflix开源的微服务网关，它可以和Eureka、Ribbon、Hystrix等组件配合使用，Zuul组件的核心是一系列的过滤器。Spring Cloud对Zuul进行了整合和增强\nZuul的最主要作用：路由分配，过滤器（身份认证，铭感词处理……）\n当然，这个技术，跟Eureka，Hystrix-DashBoard一样又是项目经理的东西\n配一次，一般就不需要再做多次的修正\n网关程序，一般都是一个独立的微服务程序\n配置方法 # Zuul实际上，它是可以和Eureka配合起来使用的 Zuul支持与Eureka整合开发，根据ServiceID自动的从注册中心中获取服务地址并转发请求，这样做的好处不仅可以通过单个端点来访问应用的所有服务，而且在添加或移除服务实例的时候不用修改Zuul的路由配置 1、创建网关微服务，添加依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-netflix-zuul\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- 导入Eureka的客户端依赖包 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-netflix-eureka-client\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、启动类添加eureka客户端、启动网关服务、springboot启动类注解 # @SpringCloudApplication @EnableZuulProxy public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class,args); } } 3、配置application.yml文件，添加其他微服务路由 # #配置端口 server: port: 10000 #配置项目名称 spring: application: name: zuul #配置eureka注册路径 eureka: client: service-url: defaultZone: http://localhost:9000/eureka #配置路由 zuul: routes: #路由ID/名称（可以随便写） micro-base-server: #访问路径（不能随便写，它需要对应微服务中Controller发布的接口） path: /auction/** /market/** #在eureka中注册的微服务名称 serverId: micro-base-server micro-user-server: path: /feign/** /rent/** serverId: micro-user-server 4、进行访问 # url格式：网关微服务ip:网关微服务端口/路由ID/对应微服务Controller路径\n5、路由配置简化 # zuul: routes: #eureka中微服务id: 访问路径 micro-base-server: /auction/**,/market/** micro-user-server: /feign/**,/rent/** 过滤器 # Zuul网关过滤器 # Zuul它包含了两个核心功能：对请求的路由和过滤\n其中路由功能负责将外部请求转发到具体的微服务实例上，是实现外部访问统一入口的基础；而过滤器功能则负责对请求的处理过程进行干预，是实现请求校验、服务聚合等功能的基础。其实，路由功能在真正运行时，它的路由映射和请求转发同样也由几个不同的过滤器完成的。所以，过滤器可以说是Zuul实现API网关功能最为核心的部件，每一个进入Zuul的HTTP请求都会经过一系列的过滤器处理链得到请求响应并返回给客户端\nZuul中的过滤器跟我们之前使用的javax.servlet.Filter不一样，javax.servlet.Filter只有一种类型，可以通过配置urlPatterns来拦截对应的请求\nZuul中的过滤器总共有4种类型，且每种类型都有对应的使用场景\n过滤的结构图 # PRE：这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等 ROUTING：这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求，并使用Apache HttpClient或Netfilx Ribbon请求微服务 POST：这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header：收集统计信息和指标、将响应从微服务发送给客户端等 ERROR：在其他阶段发生错误时执行该过滤器 PRE过滤器范例 # Pre过滤器的适用场景：身份认证，脱敏处理，编码集的统一设置等 编写过滤器继承ZuulFilter，并且添加@Component注解 //需要被spring容器管理 @Component public class MyZuulFilter extends ZuulFilter { /** * 用来指定过滤器的类型 * pre：路由之前 * routing：路由之时 * post： 路由之后 * error：发送错误调用 */ public String filterType() { return \u0026#34;pre\u0026#34;; } /** * 过滤器的顺序 * @return 返回的Int越小，越先执行 * 每个过滤器，默认都有一个中间值5 */ public int filterOrder() { return 0; } /** * 用来判断哪些路径，是需要做过滤处理的 * @return true 需要做过滤（就是需要执行run()） false 不需要做过滤(就是不需要执行run()） */ public boolean shouldFilter() { return true; } /** * 具体的过滤器规则 */ public Object run() throws ZuulException { RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); System.out.println(\u0026#34;进入路由的过滤器 \u0026#34;+request.getServletPath()); //true：成功响应，false：拒绝响应 context.setSendZuulResponse(true); context.setResponseStatusCode(200); return null; } } Gateway（SpringCloud） # Gateway的特点 # 优点 性能强劲，是第一代网关Zuul的1.6倍 功能强大，内置了很多使用的功能，例如转发、监控、限流等 设计优雅，容易扩展 缺点 其实现依赖于Netty与WebFlux，不是传统的Servlet编程模型，学习成本高 不能将其部署在tomcat、jetty等servlet容器中，只能打成jar包执行 需要Spring Boot 2.0以上的版本，才支持 简单使用 # 1、新建modules，添加依赖 # 不要添加spring-boot-starter-web，因为gateway使用的是webFlux，和springboot的web场景启动器会有冲突\n\u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;top.ygang\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;star-common\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-gateway\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba.csp\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;sentinel-spring-cloud-gateway-adapter\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; 2、添加启动类 # @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class,args); } } 3、编写配置文件 # server: port: 80 spring: application: name: gateway cloud: gateway: # 自定义配置路由规则 routes: # 路由标识，必须唯一，默认是UUID - id: student-sys # 路由的目标地址 uri: http://localhost:8080 # 路由的优先级，数字越小代表路由的优先级越高 order: 1 # 断言 predicates: # 路径断言，当请求路径匹配Path时，就会路由到uri - Path=/student-sys/** # 过滤器 filters: # 转发之前，去掉1层路径 - StripPrefix=1 4、进行访问 # 访问地址格式：http://gateway的ip:gateway的端口/请求路径Path/Controller路径\nGateway和nacos结合 # 在简单使用的基础上，将Gateway添加到nacos上，通过微服务名称在nacos上查找微服务，一边实现访问微服务集群，实现负载均衡\n1、添加依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-alibaba-nacos-discovery\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、启动类添加注解 # @SpringBootApplication @EnableDiscoveryClient public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class,args); } } 3、修改配置文件 # server: port: 80 spring: application: name: gateway cloud: nacos: discovery: # 将服务注册到nacos server-addr: http://localhost:8848 gateway: discovery: locator: # 从nacos中获取服务信息，自动生成routes，开启会导致断言失效 enabled: false # 自定义配置路由规则 routes: # 路由标识，必须唯一，默认是UUID - id: student-sys # 路由的目标地址，lb(loadblance)表示使用负载均衡，其后是微服务的标识 uri: lb://student-system # 路由的优先级，数字越小代表路由的优先级越高 order: 1 # 断言 predicates: # 路径断言，当请求路径匹配Path时，就会路由到uri - Path=/student-sys/** # 过滤器 filters: # 转发之前，去掉1层路径 - StripPrefix=1 nacos配置的精简版 # server: port: 80 spring: application: name: gateway cloud: nacos: discovery: # 将服务注册到nacos server-addr: http://localhost:8848 gateway: discovery: locator: # 让gateway从nacos中获取服务信息，自动生成routes，开启会导致断言失效 enabled: true 精简版中没有关于路由的配置routes，不需要自定义配置路由规则 uri就等于 lb://微服务名称 path就等于 /微服务名称/** 精简版是不常用的，因为只实现了路由功能。 Gateway核心架构 # 路由（Route）是Gateway中的组件之一，用来选择某一个具体的微服务。路由有以下的相关配置：\nid，路由标识符，区别于其他Route uri，路由选择的目标uri，即客户的请求最终被转发到的微服务 order，用于多个Route之间的排序，数值越小优先级越高 predicate，断言的作用是进行条件判断，只有断言都返回真，才会真正地执行路由 filter，过滤器，用于修改请求和响应信息 大致的执行流程如下：\nGateway Client向Gateway Server发送请求 请求首先会被HttpWebHandlerAdapter进行提取，进而组装成网关上下文 网关上下文被传递到DispatcherHandler，DispatcherHandler负责先找到能处理该请求的映射器处理器,这里它找到的映射器处理器是RoutePredicateHandlerMapping（没有分发给其他的HandlerMapping，是因为这个请求已经被封装进网关上下文了，该请求不是一般的请求，只能由特定的HandlerMapping处理，也就是RoutePredicateHandlerMapping） 路由断言处理映射器（RoutePredicateHandlerMapping）主要用于路由的查找，如何找呢？就是根据断言来找。 如果断言成功，由FilerWebHandler创建过滤器链并调用 请求会依次经过PreFilter、微服务、PostFilter的方法，最终返回响应 predicates断言 # Predicate(断言)用于进行条件判断，只有断言都返回真，才会执行真正的路由。\n内置路由断言工厂 # Spring Cloud Gateway包括许多内置的断言工厂，所有这些断言都与HTTP请求的不同属性匹配。具体如下：\n基于Datetime类型的断言工厂\nAfterRoutePredicateFactory：接收一个日期参数，判断请求日期是否晚于指定日期\nBeforeRoutePredicateFactory：接收一个日期参数，判断请求日期是否早于指定日期\nBetweenRoutePredicateFactory：接收两个日期参数，判断请求日期是否介于指定时间段之内\n-After=2020-12-31T23:59:59.999+08:00[Asia/Shanghai]\n基于远程地址的断言工厂\nRemoteAddrRoutePredicateFactory：接收一个IP地址段，判断请求主机地址是否在地址段中\n-RemoteAddr=192.168.1.1/24\n基于Cookie的断言工厂\nCookieRoutePredicateFactory：接收两个参数，cookie名字和一个正则表达式。判断请求Cookie是否具有给定名称且值与正则表达式匹配\n-Cookie=foo，\\w*\n基于Header的断言工厂\nHeaderRoutePredicateFactory：接收两个参数，标题名和正则表达式。判断请求Header是否具有给定名称且值与正则表达式匹配\n-Header=X-Request-Id，\\d+\n基于Method请求方法的断言工厂\nMethodRoutePredicateFactory：接收一个参数，判断请求类型是否与指定的类型匹配\n-Method=Get\n基于Path请求路径的断言工厂\nPathRoutePredicateFactory：接收一个参数，判断请求的URI部分是否满足路径规则\n-Path=/foo/bar\n基于Host的断言工厂\nHostRoutePredicateFactory：接收一个参数，该参数是主机名模式。判断请求的Host是否满足匹配规则。 基于Query请求参数的断言工厂\nQueryRoutePredicateFactory：接收两个参数，请求参数和正则表达式，判断请求参数是否具有指定的名称且值与正则表达式匹配 基于路由权重的断言工厂\nWeightRoutePredicateFactory：接收一个【组名，权重】，然后对同一个组内的路由按照权重转发 自定义断言工厂 # 1、编写一个断言工厂的类\n类名必须以RoutePredicateFactory为结束\n继承AbstractRoutePredicateFactory\n@Component public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory\u0026lt;AgeRoutePredicateFactory.Config\u0026gt; { public AgeRoutePredicateFactory(){ super(AgeRoutePredicateFactory.Config.class); } // 读取配置文件中的参数，并封装到Config内部类的对象中 @Override public List\u0026lt;String\u0026gt; shortcutFieldOrder() { // 该集合中元素的顺序，必须与配置文件中参数的顺序一致 return Arrays.asList(\u0026#34;minAge\u0026#34;, \u0026#34;maxAge\u0026#34;); } @Override public Predicate\u0026lt;ServerWebExchange\u0026gt; apply(AgeRoutePredicateFactory.Config config) { return new Predicate\u0026lt;ServerWebExchange\u0026gt;(){ @Override public boolean test(ServerWebExchange serverWebExchange) { //通过路由获得传递的参数 String age = serverWebExchange.getRequest().getQueryParams().getFirst(\u0026#34;age\u0026#34;); //判断是否有值 if(!StringUtils.isEmpty(age)){ //转型 Integer a = Integer.parseInt(age); return a\u0026gt;=config.getMinAge()\u0026amp;\u0026amp;a\u0026lt;=config.getMaxAge(); } return false; } }; } public static class Config { private Integer minAge; private Integer maxAge; public Integer getMinAge() { return minAge; } public void setMinAge(Integer minAge) { this.minAge = minAge; } public Integer getMaxAge() { return maxAge; } public void setMaxAge(Integer maxAge) { this.maxAge = maxAge; } } } 2、在配置文件中配置断言\n配置后，在启动gateway时，会对自定义断言工厂进行加载\n访问时，如果没有传age参数或age不符合配置中的范围，那么会404\nfilters过滤器 # 过滤器的描述 # 过滤器的作用：在请求的处理过程中，对请求和响应进行加工。\n在Gateway中，Filter的生命周期主要有两个阶段：\u0026ldquo;pre\u0026quot;和\u0026quot;post\u0026rdquo;\nPRE：这种过滤器在请求被路由之前调用。我们可以利用这种过滤器实现身份验证、记录调试信息等。 POST：这种过滤器在请求被路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息等等。 Gateway的Filter从作用范围可分为两种：GatewayFilter与GloalFilter。\nGatewayFilter：应用到单个路由或者一个分组的路由上 GlobalFilter：应用到所有的路由上 局部过滤器 # 内置局部过滤器 # 过滤器工厂 作用 参数 AddRequestHeader 为原始请求添加Header Header的名称和对应的值 AddRequestParameter 为原始请求添加请求参数 参数名和对应的值 AddResponseHeader 为原始响应添加Header Header的名称和对应的值 DedupeResponseHeader 剔除响应头中重复的值 需要去重的Header名称及去重策略 Hystrix 为路由引入Hystrix的断路器保护 HystrixCommand的名称 FallbackHeaders 为FallbackUrl的请求头中添加具体的异常信息 Header的名称 PrefixPath 为原始请求路径添加前缀 前缀路径 PreserveHostHeader 为请求添加一个preserveHostHeader=true的属性，\n路由过滤器会检查该属性以决定是否要发送原始的Host 无 RequestRateLimiter 用于对请求限流，限流算法为令牌桶 keyResolver、rateLimiter、statusCode、\ndenyEmptyKey、emptyKeyStatus RedirectTo 将原始请求重定向到指定的URL http状态码及重定向的url RemoveRequestHeader 为原始请求删除某个Header Header名称 RemoveResponseHeader 为原始响应删除某个Header Header名称 RewritePath 重写原始的请求路径 原始路径正则表达式以及重写后的路径 RewriteResponseHeader 重写原始响应中的某个Header Header名称，值的正则表达式，重写后的值 SaveSession 在转发请求之前，强制执行WebSession::save操作 无 secureHeaders 为原始响应添加一系列响应头，这些响应头可以保证安全 无，支持修改这些安全响应头的值 SetPath 修改原始的请求路径 修改后的路径 SetResponseHeader 修改原始响应中，某个Header的值 Header名称，修改后的值 SetStatus 修改原始响应的状态码 HTTP状态码，而已是数字，也可以是字符串 StripPrefix 用于截断原始请求的路径 使用数字表示要截断的路径的数量 Retry 针对不同的响应进行重试 retries、statuses、methods、series RequestSize 设置允许接收的请求的最大大小，如果请求包大小超过设置的\n值，则会返回413 Payload Too Large 请求包大小，单位为字节，默认值为5M ModifyRequestBody 在转发请求之前修改原始请求体内容 修改后的请求体内容 ModifyResponseBody 修改原始响应体的内容 修改后的响应体内容 内置局部过滤器的使用 # server: port: 9009 spring: application: name: star-gateway cloud: nacos: discovery: server-addr: localhost:8848 gateway: discovery: locator: enabled: false routes: - id: product_route uri: lb://star-product order: 1 predicates: - Path=/product-serv/** filters: - StripPrefix=1 - SetStatus=202\t#修改 response的状态码 请求后，相应的状态码变为202\n自定义局部过滤器 # 1、编写一个过滤器类\n类名必须以GatewayFilterFactory为结尾\n@Component public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory\u0026lt;LogGatewayFilterFactory.Config\u0026gt; { public LogGatewayFilterFactory(){ super(LogGatewayFilterFactory.Config.class); } @Override public GatewayFilter apply(Config config) { return new GatewayFilter() { @Override public Mono\u0026lt;Void\u0026gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) { if(config.isConsolelog()){ System.out.println(\u0026#34;控制台日志功能开启！！\u0026#34;); } if(config.isCachelog()){ System.out.println(\u0026#34;缓存日志功能开启！！\u0026#34;); } return chain.filter(exchange); } }; } @Override public List\u0026lt;String\u0026gt; shortcutFieldOrder() { return Arrays.asList(\u0026#34;consoleLog\u0026#34;, \u0026#34;cacheLog\u0026#34;); } public static class Config { private boolean consolelog; private boolean cachelog; public boolean isConsolelog() { return consolelog; } public void setConsolelog(boolean consolelog) { this.consolelog = consolelog; } public boolean isCachelog() { return cachelog; } public void setCachelog(boolean cachelog) { this.cachelog = cachelog; } } } 2、在配置文件中添加过滤器\n全局过滤器 # 全局过滤器不需要再配置文件中进行配置\n内置全局过滤器 # 全局过滤器 作用 ForwardRoutingFilter 用于本地forward，也就是将请求在Gateway服务内进行转发，而不是转发到下游服务 LoadBalancerClientFilter 整合Ribbon实现负载均衡 NettyRoutinFilter 使用Netty的 HttpClient 转发http、https请求 NettyWriteResponseFilter 将代理响应写回网关的客户端侧 RouteToRequestUrlFilter 将从request里获取的原始url转换成Gateway进行请求转发时所使用的url WebsocketRoutingFilter 使用Spring Web Socket将转发 Websocket 请求 GatewayMetricsFilter 整合监控相关，提供监控指标 自定义全局过滤器 # 1、编写过滤器类，实现GlobalFilter和Ordered接口，重写方法\n@Component public class AuthGlobalFilter implements GlobalFilter, Ordered { @Override public Mono\u0026lt;Void\u0026gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getQueryParams().getFirst(\u0026#34;token\u0026#34;); if(!\u0026#34;test\u0026#34;.equals(token)){ System.out.println(\u0026#34;未认证\u0026#34;); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder() { return 0; } } 2、进行访问，只有参数token为test时，才可以正常访问\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/fd17f7dc/5ef2080a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e微服务网关 \n    \u003cdiv id=\"微服务网关\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%be%ae%e6%9c%8d%e5%8a%a1%e7%bd%91%e5%85%b3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e有一些问题：不同的微服务一般会有不同的网络地址，客户端在访问这些微服务时必须记住几十甚至几百个地址，这对于客户端方来说太复杂也难以维护。\u003c/p\u003e","title":"6、服务网关","type":"posts"},{"content":" 分支语句 # if-else # #include\u0026lt;stdio.h\u0026gt; int main(){ int age = 19; if(age \u0026gt;= 18){ printf(\u0026#34;已经成年\u0026#34;); }else if(age \u0026gt;= 15){ printf(\u0026#34;青少年\u0026#34;); }else{ printf(\u0026#34;青年\u0026#34;); } return 0; } switch-case # switch 语句中的 expression 必须是一个整型或枚举类型，或者是一个 class 类型，其中 class 有一个单一的转换函数将其转换为整型或枚举类型。 在一个 switch 中可以有任意数量的 case 语句。每个 case 后跟一个要比较的值和一个冒号。 case 的 constant-expression 必须与 switch 中的变量具有相同的数据类型，且必须是一个常量或字面量。 当被测试的变量等于 case 中的常量时，case 后跟的语句将被执行，直到遇到 break 语句为止。 当遇到 break 语句时，switch 终止，控制流将跳转到 switch 语句后的下一行。 不是每一个 case 都需要包含 break。如果 case 语句不包含 break，控制流将会 继续 后续的 case，直到遇到 break 为止。 一个 switch 语句可以有一个可选的 default case，出现在 switch 的结尾。default case 可用于在上面所有 case 都不为真时执行一个任务。default case 中的 break 语句不是必需的。 #include\u0026lt;stdio.h\u0026gt; int main(){ int mon = 1; switch(mon){ case 1: printf(\u0026#34;春天\u0026#34;); break; case 2: printf(\u0026#34;夏天\u0026#34;); break; case 3: printf(\u0026#34;秋天\u0026#34;); break; default: printf(\u0026#34;冬天\u0026#34;); break; } return 0; } 循环语句 # while # #include\u0026lt;stdio.h\u0026gt; int main(){ int i = 0; while (i \u0026lt; 10){ printf(\u0026#34;循环第%d次，此时i为：%d\\n\u0026#34;,i + 1,i); i++; } return 0; } do-while # #include\u0026lt;stdio.h\u0026gt; int main(){ int i = 0; do{ printf(\u0026#34;循环第%d次，此时i为：%d\\n\u0026#34;,i + 1,i); i++; } while (i \u0026lt; 10); return 0; } for # #include\u0026lt;stdio.h\u0026gt; int main(){ for(int i = 0;i \u0026lt; 10;i++){ printf(\u0026#34;循环第%d次，此时i为：%d\\n\u0026#34;,i + 1,i); } return 0; } 循环控制关键字 # break # #include\u0026lt;stdio.h\u0026gt; int main(){ int count = 0; while(1){ printf(\u0026#34;此时的count为%d\\n\u0026#34;,count); if(count == 9){ break; } count++; } return 0; } continue # #include\u0026lt;stdio.h\u0026gt; int main(){ for(int i = 0;i \u0026lt; 100;i++){ if(i % 2 == 0){ continue; } printf(\u0026#34;偶数：%d\\n\u0026#34;,i); } return 0; } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/f571508d/fa8095d0/d50ae9cf/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e分支语句 \n    \u003cdiv id=\"分支语句\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%86%e6%94%af%e8%af%ad%e5%8f%a5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eif-else \n    \u003cdiv id=\"if-else\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#if-else\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#include\u003c/span\u003e\u003cspan class=\"cpf\"\u003e\u0026lt;stdio.h\u0026gt;\u003c/span\u003e\u003cspan class=\"cp\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003eage\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e19\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eage\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e \u003cspan class=\"mi\"\u003e18\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nf\"\u003eprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;已经成年\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eage\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e \u003cspan class=\"mi\"\u003e15\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nf\"\u003eprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;青少年\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nf\"\u003eprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;青年\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eswitch-case \n    \u003cdiv id=\"switch-case\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#switch-case\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eswitch\u003c/strong\u003e 语句中的 \u003cstrong\u003eexpression\u003c/strong\u003e 必须是一个整型或枚举类型，或者是一个 class 类型，其中 class 有一个单一的转换函数将其转换为整型或枚举类型。\u003c/li\u003e\n\u003cli\u003e在一个 switch 中可以有任意数量的 case 语句。每个 case 后跟一个要比较的值和一个冒号。\u003c/li\u003e\n\u003cli\u003ecase 的 \u003cstrong\u003econstant-expression\u003c/strong\u003e 必须与 switch 中的变量具有相同的数据类型，且必须是一个常量或字面量。\u003c/li\u003e\n\u003cli\u003e当被测试的变量等于 case 中的常量时，case 后跟的语句将被执行，直到遇到 \u003cstrong\u003ebreak\u003c/strong\u003e 语句为止。\u003c/li\u003e\n\u003cli\u003e当遇到 \u003cstrong\u003ebreak\u003c/strong\u003e 语句时，switch 终止，控制流将跳转到 switch 语句后的下一行。\u003c/li\u003e\n\u003cli\u003e不是每一个 case 都需要包含 \u003cstrong\u003ebreak\u003c/strong\u003e。如果 case 语句不包含 \u003cstrong\u003ebreak\u003c/strong\u003e，控制流将会 \u003cstrong\u003e继续\u003c/strong\u003e 后续的 case，直到遇到 break 为止。\u003c/li\u003e\n\u003cli\u003e一个 \u003cstrong\u003eswitch\u003c/strong\u003e 语句可以有一个可选的 \u003cstrong\u003edefault\u003c/strong\u003e case，出现在 switch 的结尾。default case 可用于在上面所有 case 都不为真时执行一个任务。default case 中的 \u003cstrong\u003ebreak\u003c/strong\u003e 语句不是必需的。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#include\u003c/span\u003e\u003cspan class=\"cpf\"\u003e\u0026lt;stdio.h\u0026gt;\u003c/span\u003e\u003cspan class=\"cp\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003emon\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"k\"\u003eswitch\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emon\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"nf\"\u003eprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;春天\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003ebreak\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"nf\"\u003eprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;夏天\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003ebreak\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"nf\"\u003eprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;秋天\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003ebreak\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003edefault\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"nf\"\u003eprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;冬天\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003ebreak\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e循环语句 \n    \u003cdiv id=\"循环语句\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%be%aa%e7%8e%af%e8%af%ad%e5%8f%a5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003ewhile \n    \u003cdiv id=\"while\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#while\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#include\u003c/span\u003e\u003cspan class=\"cpf\"\u003e\u0026lt;stdio.h\u0026gt;\u003c/span\u003e\u003cspan class=\"cp\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"k\"\u003ewhile\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nf\"\u003eprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;循环第%d次，此时i为：%d\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003edo-while \n    \u003cdiv id=\"do-while\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#do-while\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#include\u003c/span\u003e\u003cspan class=\"cpf\"\u003e\u0026lt;stdio.h\u0026gt;\u003c/span\u003e\u003cspan class=\"cp\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"k\"\u003edo\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nf\"\u003eprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;循环第%d次，此时i为：%d\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003ewhile\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003efor \n    \u003cdiv id=\"for\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#for\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#include\u003c/span\u003e\u003cspan class=\"cpf\"\u003e\u0026lt;stdio.h\u0026gt;\u003c/span\u003e\u003cspan class=\"cp\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nf\"\u003eprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;循环第%d次，此时i为：%d\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e循环控制关键字 \n    \u003cdiv id=\"循环控制关键字\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%be%aa%e7%8e%af%e6%8e%a7%e5%88%b6%e5%85%b3%e9%94%ae%e5%ad%97\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003ebreak \n    \u003cdiv id=\"break\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#break\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e#include\u0026lt;stdio.h\u0026gt;\n\nint main(){\n   int count = 0;\n   while(1){\n      printf(\u0026#34;此时的count为%d\\n\u0026#34;,count);\n      if(count == 9){\n         break;\n      }\n      count++;\n   }\n   return 0;\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch4 class=\"relative group\"\u003econtinue \n    \u003cdiv id=\"continue\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#continue\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#include\u003c/span\u003e\u003cspan class=\"cpf\"\u003e\u0026lt;stdio.h\u0026gt;\u003c/span\u003e\u003cspan class=\"cp\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e100\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003econtinue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nf\"\u003eprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;偶数：%d\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"6、流程控制","type":"posts"},{"content":" 技术选型 # Gin：Web 框架 Gorm：ORM 框架 Go-Redis：Redis 框架 Logrus：日志框架 Wire：依赖注入管理 # Gin go get -u github.com/gin-gonic/gin # Go-Redis go get -u github.com/redis/go-redis/v9 # Gorm go get -u gorm.io/gorm # 数据库驱动 go get -u gorm.io/driver/mysql # Gorm-Gen go get -u gorm.io/gen # Logrus go get -u github.com/sirupsen/logrus # logrus-formatter go get -u github.com/antonfisher/nested-logrus-formatter # Wire-Cli go install github.com/google/wire/cmd/wire@latest # Wire go get -u github.com/google/wire 项目结构 # ├── cmd # cmd │ ├── gorm_gen.go # gorm代码生成 │ ├── main.go # main.go │ └── wire # wire │ ├── wire.go # 依赖注入管理 │ └── wire_gen.go # wire生成 ├── config # 配置目录 │ └── application.yml # 应用配置文件 ├── go.mod # go.mod ├── go.sum # go.sum ├── internal # 内部包 │ ├── config # 应用配置 │ │ ├── config.go # 配置文件读取 │ │ └── time.go # 自定义时间序列化 │ ├── dao # dao │ │ ├── gen.go # gorm生成 │ │ └── sys_user.gen.go # gorm生成 │ ├── database # 持久化层 │ │ ├── mysql.go # MySQL │ │ ├── provider_set.go # Wire ProviderSets 分组管理 │ │ └── redis.go # Redis │ ├── dto # DTO │ │ └── response.go # 控制层统一响应结构 │ ├── engine # 管理GIN引擎及路由 │ │ ├── api_v1 # V1版本路由 │ │ │ └── routes.go # 统一管理路由 │ │ ├── engine.go # GIN引擎管理 │ │ └── middleware # GIN中间件 │ │ └── error.go # 全局异常处理中间件 │ ├── handler # 控制层处理器 │ │ ├── provider_set.go # Wire ProviderSets 分组管理 │ │ └── sys_user.go # sys_user 处理器 │ ├── model # 模型 │ │ └── sys_user.gen.go # sys_user 模型 │ └── service # 业务层 │ ├── provider_set.go # Wire ProviderSets 分组管理 │ └── sys_user.go # sys_user 业务层 使用方式 # 管理路由 # 所有的路由均在engine/api_v1下管理\n生成数据库model以及dao # 在cmd/gorm_gen.go中配置数据库连接信息，并运行\nWire生成依赖注入 # 在cmd/wire目录中执行wire命令\n模板代码仓库地址 # Gitee：https://gitee.com/gradyyoung/gin-template Github：https://github.com/gradyyoung/gin-template ","date":"2025-06-26","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/1bd46fa5/5781d607/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e技术选型 \n    \u003cdiv id=\"技术选型\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8a%80%e6%9c%af%e9%80%89%e5%9e%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eGin：Web 框架\u003c/li\u003e\n\u003cli\u003eGorm：ORM 框架\u003c/li\u003e\n\u003cli\u003eGo-Redis：Redis 框架\u003c/li\u003e\n\u003cli\u003eLogrus：日志框架\u003c/li\u003e\n\u003cli\u003eWire：依赖注入管理\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Gin\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ego get -u github.com/gin-gonic/gin\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Go-Redis\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ego get -u github.com/redis/go-redis/v9\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Gorm\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ego get -u gorm.io/gorm\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 数据库驱动\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ego get -u gorm.io/driver/mysql\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Gorm-Gen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ego get -u gorm.io/gen\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Logrus\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ego get -u github.com/sirupsen/logrus\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# logrus-formatter\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ego get -u github.com/antonfisher/nested-logrus-formatter\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Wire-Cli\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ego install github.com/google/wire/cmd/wire@latest\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Wire\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ego get -u github.com/google/wire\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e项目结构 \n    \u003cdiv id=\"项目结构\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%a1%b9%e7%9b%ae%e7%bb%93%e6%9e%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-txt\" data-lang=\"txt\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e├── cmd                         # cmd\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── gorm_gen.go             # gorm代码生成\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── main.go                 # main.go\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   └── wire                    # wire\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│       ├── wire.go             # 依赖注入管理\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│       └── wire_gen.go         # wire生成\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e├── config                      # 配置目录\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   └── application.yml         # 应用配置文件\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e├── go.mod                      # go.mod\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e├── go.sum                      # go.sum\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e├── internal                    # 内部包\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── config                  # 应用配置\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │   ├── config.go           # 配置文件读取\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │   └── time.go             # 自定义时间序列化\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── dao                     # dao\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │   ├── gen.go              # gorm生成\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │   └── sys_user.gen.go     # gorm生成\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── database                # 持久化层\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │   ├── mysql.go            # MySQL\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │   ├── provider_set.go     # Wire ProviderSets 分组管理\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │   └── redis.go            # Redis\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── dto                     # DTO\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │   └── response.go         # 控制层统一响应结构\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── engine                  # 管理GIN引擎及路由\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │   ├── api_v1              # V1版本路由\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │   │   └── routes.go       # 统一管理路由\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │   ├── engine.go           # GIN引擎管理\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │   └── middleware          # GIN中间件\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │       └── error.go        # 全局异常处理中间件\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── handler                 # 控制层处理器\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │   ├── provider_set.go     # Wire ProviderSets 分组管理\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │   └── sys_user.go         # sys_user 处理器\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── model                   # 模型\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   │   └── sys_user.gen.go     # sys_user 模型\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   └── service                 # 业务层\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│       ├── provider_set.go     # Wire ProviderSets 分组管理\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│       └── sys_user.go         # sys_user 业务层\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e使用方式 \n    \u003cdiv id=\"使用方式\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bd%bf%e7%94%a8%e6%96%b9%e5%bc%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e管理路由 \n    \u003cdiv id=\"管理路由\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%ae%a1%e7%90%86%e8%b7%af%e7%94%b1\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e所有的路由均在\u003ccode\u003eengine/api_v1\u003c/code\u003e下管理\u003c/p\u003e","title":"7、项目实战","type":"posts"},{"content":"Go语言标准库提供了基础的日志功能，通过log包可以快速实现日志记录。标准库log包的主要特点：\n简单易用：API简洁，容易上手 支持基本格式化：时间、文件名、行号等 支持日志级别：Print、Fatal、Panic 可自定义输出目标：文件、网络等 支持前缀：可为日志添加前缀信息 然而，标准库日志包存在一些局限性：\n不支持结构化日志 日志级别有限 没有内置的日志轮转功能 缺乏日志过滤和高级格式化选项 简单使用 # logger会打印每条日志信息的日期、时间，默认输出到系统的标准错误。\nFatal系列函数会在写入日志信息后调用os.Exit(1)。\nPanic系列函数会在写入日志信息后panic。\npackage main import \u0026#34;log\u0026#34; func main() { log.Println(\u0026#34;this is a simple log\u0026#34;) log.Fatalln(\u0026#34;this is a fatal log\u0026#34;) log.Panicln(\u0026#34;this is a panic log\u0026#34;) } 配置logger # 默认情况下的logger只会提供日志的时间信息，但是很多情况下我们希望得到更多信息，比如记录该日志的文件名和行号等。log标准库中为我们提供了定制这些设置的方法。\nlog标准库中的Flags函数会返回标准logger的输出配置，而SetFlags函数用来设置标准logger的输出配置。\nfunc Flags() int func SetFlags(flag int) flag选项 # 控制输出日志信息的细节，不能控制输出的顺序和格式。\nconst ( Ldate = 1 \u0026lt;\u0026lt; iota // 日期：2009/01/23 Ltime // 时间：01:23:23 Lmicroseconds // 微秒级别的时间：01:23:23.123123（用于增强Ltime位） Llongfile // 文件全路径名+行号： /a/b/c/d.go:23 Lshortfile // 文件名+行号：d.go:23（会覆盖掉Llongfile） LUTC // 使用UTC时间 LstdFlags = Ldate | Ltime // 标准logger的初始值 ) package main import \u0026#34;log\u0026#34; func main() { log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate) log.Println(\u0026#34;this is a simple log\u0026#34;) log.Fatalln(\u0026#34;this is a fatal log\u0026#34;) log.Panicln(\u0026#34;this is a panic log\u0026#34;) } //2022/12/02 11:15:17.686667 E:/go-project/nginx-log-parse/cmd/main.go:7: this is a simple log //2022/12/02 11:15:17.695173 E:/go-project/nginx-log-parse/cmd/main.go:8: this is a fatal log 配置日志前缀 # func Prefix() string func SetPrefix(prefix string) package main import \u0026#34;log\u0026#34; func main() { log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate) log.SetPrefix(\u0026#34;[SIMPLE]\u0026#34;) log.Println(\u0026#34;this is a simple log\u0026#34;) log.SetPrefix(\u0026#34;[FATAL]\u0026#34;) log.Fatalln(\u0026#34;this is a fatal log\u0026#34;) log.SetPrefix(\u0026#34;[PANIC]\u0026#34;) log.Panicln(\u0026#34;this is a panic log\u0026#34;) } //[SIMPLE]2022/12/02 11:17:05.758862 E:/go-project/nginx-log-parse/cmd/main.go:8: this is a simple log //[FATAL]2022/12/02 11:17:05.767646 E:/go-project/nginx-log-parse/cmd/main.go:10: this is a fatal log 配置日志输出位置 # func SetOutput(w io.Writer) 创建logger # func New(out io.Writer, prefix string, flag int) *Logger 输出到文件 # package main import ( \u0026#34;log\u0026#34; \u0026#34;os\u0026#34; ) func main() { // 默认日志输出到标准错误 log.Println(\u0026#34;这是一条标准日志消息\u0026#34;) // 创建日志文件 f, err := os.OpenFile(\u0026#34;app.log\u0026#34;, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Fatal(err) } defer f.Close() // 配置日志输出到文件 logger := log.New(f, \u0026#34;APP: \u0026#34;, log.Ldate|log.Ltime|log.Lshortfile) // 使用配置好的logger记录日志 logger.Println(\u0026#34;应用启动成功\u0026#34;) logger.Printf(\u0026#34;配置加载完成: %s\u0026#34;, \u0026#34;config.json\u0026#34;) // 记录致命错误并终止程序 if err != nil { logger.Fatalf(\u0026#34;无法连接数据库: %v\u0026#34;, err) } } 自定义logger # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;os\u0026#34; \u0026#34;runtime\u0026#34; \u0026#34;strings\u0026#34; \u0026#34;time\u0026#34; ) // CustomLogger 自定义日志格式 type CustomLogger struct { logger *log.Logger } const ( LevelDebug = \u0026#34;DEBUG\u0026#34; LevelInfo = \u0026#34;INFO\u0026#34; LevelWarning = \u0026#34;WARNING\u0026#34; LevelError = \u0026#34;ERROR\u0026#34; LevelFatal = \u0026#34;FATAL\u0026#34; ) // NewCustomLogger 初始化自定义日志 func NewCustomLogger() *CustomLogger { return \u0026amp;CustomLogger{ logger: log.New(os.Stdout, \u0026#34;\u0026#34;, 0), // 输出到标准输出，不带前缀 } } // 自定义日志格式 func (l *CustomLogger) formatLog(level, message string) string { // 获取当前时间 now := time.Now().Format(\u0026#34;2006-01-02 15:04:05.000\u0026#34;) // 获取调用栈信息 pc, file, line, ok := runtime.Caller(3) // 跳过三层调用栈 caller := \u0026#34;unknown\u0026#34; if ok { funcName := runtime.FuncForPC(pc).Name() caller = fmt.Sprintf(\u0026#34;%s:%d\u0026#34;, file, line) if index := strings.LastIndex(funcName, \u0026#34;.\u0026#34;); index != -1 { caller = fmt.Sprintf(\u0026#34;%s.%s:%d\u0026#34;, funcName[:index], funcName[index+1:], line) } } // 构建日志格式 return fmt.Sprintf(\u0026#34;%s [%s] %s - %s\u0026#34;, now, level, caller, message) } // Log 日志输出函数 func (l *CustomLogger) Log(level, message string) { logLine := l.formatLog(level, message) l.logger.Println(logLine) } func (l *CustomLogger) Debug(message string) { l.Log(LevelDebug, message) } func (l *CustomLogger) Debugf(format string, v ...any) { message := fmt.Sprintf(format, v...) l.Log(LevelDebug, message) } func (l *CustomLogger) Info(message string) { l.Log(LevelInfo, message) } func (l *CustomLogger) Infof(format string, v ...any) { message := fmt.Sprintf(format, v...) l.Log(LevelInfo, message) } func (l *CustomLogger) Warning(message string) { l.Log(LevelWarning, message) } func (l *CustomLogger) Warningf(format string, v ...any) { message := fmt.Sprintf(format, v...) l.Log(LevelWarning, message) } func (l *CustomLogger) Error(message string) { l.Log(LevelError, message) } func (l *CustomLogger) Errorf(format string, v ...any) { message := fmt.Sprintf(format, v...) l.Log(LevelError, message) } func (l *CustomLogger) Fatal(message string) { l.Log(LevelFatal, message) } func (l *CustomLogger) Fatalf(format string, v ...any) { message := fmt.Sprintf(format, v...) l.Log(LevelFatal, message) } ","date":"2025-05-19","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/12d682f6/7dbe13c3/","section":"文章","summary":"\u003cp\u003eGo语言标准库提供了基础的日志功能，通过\u003ccode\u003elog\u003c/code\u003e包可以快速实现日志记录。标准库\u003ccode\u003elog\u003c/code\u003e包的主要特点：\u003c/p\u003e","title":"7、log","type":"posts"},{"content":"在 Go 中操作 Excel 文件，最常用的库是 excelize，它支持读取、写入和修改 Excel 文件。\n官网：https://xuri.me/excelize/zh-hans/\n安装 # go get github.com/xuri/excelize/v2 读写操作 # 创建并写入 Excel # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/xuri/excelize/v2\u0026#34; ) func main() { file := excelize.NewFile() sheet := \u0026#34;Sheet1\u0026#34; file.SetCellValue(sheet, \u0026#34;A1\u0026#34;, \u0026#34;A1Value\u0026#34;) file.SetCellValue(sheet, \u0026#34;B1\u0026#34;, \u0026#34;B1Value\u0026#34;) file.SetCellValue(sheet, \u0026#34;C1\u0026#34;, \u0026#34;C1Value\u0026#34;) file.SetCellValue(sheet, \u0026#34;D1\u0026#34;, \u0026#34;D1Value\u0026#34;) if err := file.SaveAs(\u0026#34;test.xlsx\u0026#34;); err != nil { fmt.Println(err) } } 读取 Excel # excelize提供了两个函数获取文件File对象\nfunc OpenFile(filename string, opt ...Options) (*File, error) func OpenReader(r io.Reader, opt ...Options) (*File, error) 方式一 # func (f *File) GetRows(sheet string, opts ...Options) ([][]string, error)\n直接读取Row和Col的数据二维切片，然后进行读取操作\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/xuri/excelize/v2\u0026#34; ) func main() { file, err := excelize.OpenFile(\u0026#34;test.xlsx\u0026#34;) defer file.Close() if err != nil { fmt.Println(err) return } rows, err := file.GetRows(\u0026#34;Sheet1\u0026#34;) if err != nil { fmt.Println(err) return } for i, row := range rows { fmt.Printf(\u0026#34;Row %d is %v\u0026#34;, i, row) } } 方式二 # 使用Rows函数，返回Rows的对象进行操作, 函数原型如下：\nfunc (f *File) Rows(sheet string) (*Rows, error)\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/xuri/excelize/v2\u0026#34; ) func main() { file, err := excelize.OpenFile(\u0026#34;test.xlsx\u0026#34;) defer file.Close() if err != nil { fmt.Println(err) return } rows, err := file.Rows(\u0026#34;Sheet1\u0026#34;) for rows.Next() { columns, _ := rows.Columns() fmt.Println(columns) } } 更新单元格的值 # 在一个现有 Excel 文件中更新某个单元格值：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/xuri/excelize/v2\u0026#34; ) func main() { file, err := excelize.OpenFile(\u0026#34;test.xlsx\u0026#34;) defer file.Close() if err != nil { fmt.Println(err) return } if err = file.SetCellValue(\u0026#34;Sheet1\u0026#34;, \u0026#34;B1\u0026#34;, \u0026#34;B1UpdateValue\u0026#34;); err != nil { fmt.Println(err) } file.Save() } 读取单元格的值 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/xuri/excelize/v2\u0026#34; ) func main() { file, err := excelize.OpenFile(\u0026#34;test.xlsx\u0026#34;) defer file.Close() if err != nil { fmt.Println(err) return } value, err := file.GetCellValue(\u0026#34;Sheet1\u0026#34;, \u0026#34;A1\u0026#34;) if err != nil { fmt.Println(err) } fmt.Printf(\u0026#34;Sheet1 A1 value is %s\u0026#34;, value) } 合并单元格 # func (f *File) MergeCell(sheet, topLeftCell, bottomRightCell string) error 工作表 # // 根据sheet的名称获取sheet的索引 func (f *File) GetSheetIndex(name string) int // 根据sheet的索引获取sheet的名称 func (f *File) GetSheetName(index int) (name string) // 获取所有sheet的名称列表 func (f *File) GetSheetList() (list []string) // 获取所有sheet的索引对应的名称集合 func (f *File) GetSheetMap() map[int]string 添加新的工作表 # 如果你想在现有 Excel 文件中添加一个新的工作表，可以使用 NewSheet 方法：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/xuri/excelize/v2\u0026#34; ) func main() { file, err := excelize.OpenFile(\u0026#34;test.xlsx\u0026#34;) defer file.Close() if err != nil { fmt.Println(err) return } // 创建新的工作表 sheet, err := file.NewSheet(\u0026#34;Sheet2\u0026#34;) // 写入数据到新工作表 file.SetCellValue(\u0026#34;Sheet2\u0026#34;, \u0026#34;A1\u0026#34;, \u0026#34;Sheet2A1Value\u0026#34;) // 设置 \u0026#34;Sheet2\u0026#34; 为默认工作表，可选 file.SetActiveSheet(sheet) file.Save() } 删除工作表 # func (f *File) DeleteSheet(sheet string) error 样式设置 # 创建样式 # func (f *File) NewStyle(style *Style) (int, error) Style 结构体 # type Style struct { Border []Border Fill Fill Font *Font Alignment *Alignment Protection *Protection NumFmt int DecimalPlaces *int CustomNumFmt *string NegRed bool } Boder 结构体 # type Border struct { Type string Color string Style int } Type：边线方向 left 左边 right 正确的 top 顶端 bottom 底部 diagonalDown：左上到右下 diagonalUP：左下到右上 Color：颜色 Style：边线类型 Sytle可选值参考：https://xuri.me/excelize/zh-hans/style.html#border\ndiagonalDown和diagonalUp同时设置，如果二者样式不同，则diagonalDown会被diagonalUp的样式覆盖。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/xuri/excelize/v2\u0026#34; ) func main() { f := excelize.NewFile() styleId, err := f.NewStyle(\u0026amp;excelize.Style{ Border: []excelize.Border{ {Type: \u0026#34;left\u0026#34;, Color: \u0026#34;000000\u0026#34;, Style: 1}, {Type: \u0026#34;top\u0026#34;, Color: \u0026#34;000000\u0026#34;, Style: 2}, {Type: \u0026#34;bottom\u0026#34;, Color: \u0026#34;000000\u0026#34;, Style: 3}, {Type: \u0026#34;right\u0026#34;, Color: \u0026#34;000000\u0026#34;, Style: 4}, {Type: \u0026#34;diagonalDown\u0026#34;, Color: \u0026#34;000000\u0026#34;, Style: 5}, {Type: \u0026#34;diagonalUp\u0026#34;, Color: \u0026#34;A020F0\u0026#34;, Style: 6}, }, }) if err != nil { fmt.Println(err) } err = f.SetCellStyle(\u0026#34;Sheet1\u0026#34;, \u0026#34;B4\u0026#34;, \u0026#34;D2\u0026#34;, styleId) if err = f.SaveAs(\u0026#34;test.xlsx\u0026#34;); err != nil { fmt.Println(err) } } Fill 结构体 # type Fill struct { Type string Pattern int Color []string Shading int } Type gradient：渐变 pattern：填充图 Shading（Type为gradient时生效） 1：横向填充 2：纵向填充 3：对角线向下填充 4：对角线向上填充 5：从内向外填充 Pattern（Type为pattern时生效） 值从1~18。 1 表示纯色填充 Color Type为gradient时，Color 有两个值，Pattern不生效 切片只有一个成员时，Shading 不生效。 Pattern 可选值参考：https://xuri.me/excelize/zh-hans/style.html#shading\n渐变填充\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/xuri/excelize/v2\u0026#34; ) func main() { f := excelize.NewFile() styleId, err := f.NewStyle(\u0026amp;excelize.Style{ Border: []excelize.Border{ {Type: \u0026#34;left\u0026#34;, Color: \u0026#34;000000\u0026#34;, Style: 2}, {Type: \u0026#34;top\u0026#34;, Color: \u0026#34;000000\u0026#34;, Style: 2}, {Type: \u0026#34;bottom\u0026#34;, Color: \u0026#34;000000\u0026#34;, Style: 2}, {Type: \u0026#34;right\u0026#34;, Color: \u0026#34;000000\u0026#34;, Style: 2}, }, Fill: excelize.Fill{ Type: \u0026#34;gradient\u0026#34;, Color: []string{\u0026#34;FFFF00\u0026#34;, \u0026#34;00FF00\u0026#34;}, Shading: 1, }, }) if err != nil { fmt.Println(err) } err = f.SetCellStyle(\u0026#34;Sheet1\u0026#34;, \u0026#34;B4\u0026#34;, \u0026#34;D2\u0026#34;, styleId) if err = f.SaveAs(\u0026#34;test.xlsx\u0026#34;); err != nil { fmt.Println(err) } } 纯色填充\nstyle, err := f.NewStyle(\u0026amp;excelize.Style{ Fill: excelize.Fill{Type: \u0026#34;pattern\u0026#34;, Color: []string{\u0026#34;FF0000\u0026#34;}, Pattern: 1}, }) Font 结构体 # type Font struct { Bold bool Italic bool Underline string Family string Size float64 Strike bool Color string ColorIndexed int ColorTheme *int ColorTint float64 VertAlign string } Bold：是否粗体 Italic：是否斜体 Underline：下划线 single ：单线 double：双线 Family：字体样式 Size：字体大小 Color：字体颜色 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/xuri/excelize/v2\u0026#34; ) func main() { f := excelize.NewFile() styleId, err := f.NewStyle(\u0026amp;excelize.Style{ Font: \u0026amp;excelize.Font{ Bold: true, Italic: true, Family: \u0026#34;Times New Roman\u0026#34;, Size: 36, Color: \u0026#34;微软雅黑\u0026#34;, }, }) if err != nil { fmt.Println(err) } f.SetCellStyle(\u0026#34;Sheet1\u0026#34;, \u0026#34;B4\u0026#34;, \u0026#34;B4\u0026#34;, styleId) f.SetCellValue(\u0026#34;Sheet1\u0026#34;,\u0026#34;B4\u0026#34;,\u0026#34;LiuBei\u0026#34;) if err = f.SaveAs(\u0026#34;test.xlsx\u0026#34;); err != nil { fmt.Println(err) } } Alignment 结构体 # type Alignment struct { Horizontal string Indent int JustifyLastLine bool ReadingOrder uint64 RelativeIndent int ShrinkToFit bool TextRotation int Vertical string WrapText bool } Horizontal：水平对齐 right 正确的 left 左边 center 中心 Indent：缩进 JustifyLastLine：两端对齐 ReadingOrder：文字方向 RelativeIndent：相对缩进 ShrinkToFit：缩小字体 TextRotation：文字旋转 Vertical：垂直对齐 top 顶端 bottom 底部 center 中心 WrapText：自动换行 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/xuri/excelize/v2\u0026#34; ) func main() { f := excelize.NewFile() styleId, err := f.NewStyle(\u0026amp;excelize.Style{ Alignment: \u0026amp;excelize.Alignment{ Horizontal: \u0026#34;center\u0026#34;, Indent: 1, JustifyLastLine: true, ReadingOrder: 2, RelativeIndent: 1, ShrinkToFit: true, TextRotation: 30, Vertical: \u0026#34;top\u0026#34;, WrapText: true, }, }) if err != nil { fmt.Println(err) } f.SetCellStyle(\u0026#34;Sheet1\u0026#34;, \u0026#34;B4\u0026#34;, \u0026#34;B4\u0026#34;, styleId) f.SetCellValue(\u0026#34;Sheet1\u0026#34;,\u0026#34;B4\u0026#34;,\u0026#34;LiuBei\u0026#34;) if err = f.SaveAs(\u0026#34;test.xlsx\u0026#34;); err != nil { fmt.Println(err) } } CustomNumFmt自定义格式 # import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/xuri/excelize/v2\u0026#34; \u0026#34;time\u0026#34; ) func main() { f := excelize.NewFile() numFmt := \u0026#34;yyyy\\\u0026#34;年\\\u0026#34;m\\\u0026#34;月\\\u0026#34;d\\\u0026#34;日\\\u0026#34;\u0026#34; styleId, err := f.NewStyle(\u0026amp;excelize.Style{ CustomNumFmt: \u0026amp;numFmt, }) if err != nil { fmt.Println(err) } f.SetCellStyle(\u0026#34;Sheet1\u0026#34;, \u0026#34;B4\u0026#34;, \u0026#34;B4\u0026#34;, styleId) f.SetCellValue(\u0026#34;Sheet1\u0026#34;,\u0026#34;B4\u0026#34;,time.Now()) if err = f.SaveAs(\u0026#34;test.xlsx\u0026#34;); err != nil { fmt.Println(err) } } 单元格使用样式 # func (f *File) SetCellStyle(sheet string, hCell string, vCell string, styleID int) error 列使用样式 # func (f *File) SetColStyle(sheet, columns string, styleID int) error 行使用样式 # func (f *File) SetRowStyle(sheet string, start int, end int, styleID int) error ","date":"2025-05-13","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/29ede47b/e5625e11/","section":"文章","summary":"\u003cp\u003e在 Go 中操作 Excel 文件，最常用的库是 excelize，它支持读取、写入和修改 Excel 文件。\u003c/p\u003e","title":"7、excelize","type":"posts"},{"content":" 数组 # 声明 # 数组是一个由固定长度的特定类型元素组成的序列，属于值类型（所以传参数组的时候一般使用指针，避免值类型传参复制导致内存消耗），不可以与nil比较，一个数组可以由零个或多个元素组成。因为数组的长度是固定的，所以在Go语言中很少直接使用数组。\nvar variable [length]Type // 声明 var arr [3]int // 声明并赋值 var arr [3]int = [3]int{1,2,3} // 自动类型推导 arr := [3]int{1,2,3} // 自动类型推导并自动计算长度 arr := [...]int{1,2,3} // 初始化并指定索引的值 arr := [5]string{2: \u0026#34;lucy\u0026#34;, 4: \u0026#34;tom\u0026#34;} 数组变量名：数组声明及使用时的变量名。 元素数量：数组的元素数量，可以是一个表达式，但最终通过编译期计算的结果必须是整型数值，元素数量不能含有到运行时才能确认大小的数值。 Type：可以是任意基本类型，包括数组本身，类型为数组本身时，可以实现多维数组。 数组的每个元素都可以通过索引下标来访问，索引下标的范围是从 0 开始到数组长度减 1 的位置，内置函数 len() 可以返回数组中元素的个数。\n//main包 package main import ( \u0026#34;fmt\u0026#34; ) func main() { var arr [3]int fmt.Println(len(arr)) //数组长度：3 fmt.Println(arr[0]) //数组索引为0的元素值为：0 } 比较数组是否相等 # 如果两个数组类型相同（包括数组的长度，数组中元素的类型）的情况下，我们可以直接通过较运算符（==和!=）来判断两个数组是否相等，只有当两个数组的所有元素都是相等的时候数组才是相等的，不能比较两个类型、长度不同的数组，否则程序将无法完成编译。\n//main包 package main import ( \u0026#34;fmt\u0026#34; ) func main() { a1 := [...]int{1, 2, 3} a2 := [...]int{1, 2, 3} a3 := [...]int{2, 3, 4} a4 := [...]int{1, 2, 3, 4} a5 := [...]int{1, 2, 3} fmt.Println(a1 == a2) // true fmt.Println(a1 == a3) // false fmt.Println(a1 == a5) // false,顺序不同也不相等 fmt.Println(a1 == a4) // 编译错误,[3]int不能和[4]int进行比较 } 遍历数组 # //main包 package main import \u0026#34;fmt\u0026#34; func main() { names := [...]string{\u0026#34;lucy\u0026#34;, \u0026#34;tom\u0026#34;, \u0026#34;lily\u0026#34;} //使用for，根据数据长度遍历 for i := 0; i \u0026lt; len(names); i++ { fmt.Printf(\u0026#34;第%d个姓名为：%s\\n\u0026#34;, i, names[i]) } //使用for range for index, value := range names { fmt.Printf(\u0026#34;第%d个姓名为：%s\\n\u0026#34;, index, value) } } 多维数组 # var array_name [size1][size2] array_type //声明 var ab [2][3]int //声明并赋值 var ab [2][3]int = [2][3]int{{1, 2, 3}, {4, 5, 6}} //自动类型推导 ab := [2][3]int{{1, 2, 3}, {4, 5, 6}} //初始化并指定索引的值 ab := [5][3]int{2:{1, 2, 3}, 4:{1:66}} //获取指定索引的值 a := ab[0][2] 注意：多维数组只有最外层可以使用[...]来表示数组长度，内层数组不可以使用[...]来表示数组长度\n切片Slice # 切片（slice）本质就是对一个底层数组的一个连续片段的引用（封装），所以切片是一个引用类型（因此更类似于 C/C++中的数组类型，或者Python中的 list 类型），这个片段可以是整个数组，也可以是由起始和终止索引标识的一些项的子集，需要注意的是，终止索引标识的项不包括在切片内，也就是切片不含结束索引的元素。切片一般用于快速地操作一块数据集合。\n容量和长度的区别 # 切片的长度（通过len(s)获取）就是它所包含的元素个数，这些元素是会被初始化的。\n切片的容量（通过cap(s)获取）是从它的第一个元素开始数，到其底层数组元素末尾的个数。\n也就是说，对于s := make([]int, 3, 6) ，该切片创建了一个能够容纳6个元素（容量）的数组。同时，因为长度length被设置成了3，所以，Go仅仅初始化前3个元素。因为slice的元素是[]int类型，所以前3个元素用int的零值0来初始化。剩余的元素空间只被分配，但没有使用。\n创建切片 # 从数组或切片生成新的切片 # 切片默认指向一段连续内存区域，可以是数组，也可以是切片本身。\ns := target[开始位置 : 结束位置] target：表示目标切片对象； 开始位置：对应目标切片对象的索引； 结束位置：对应目标切片的结束索引（不包含结束索引元素）。 package main import \u0026#34;fmt\u0026#34; func main() { a := [5]int{1, 2, 3, 4, 5} slice := a[1:3] fmt.Println(slice) // [2 3] fmt.Println(len(slice), cap(slice)) // 2 4 a[1] = 10 fmt.Println(slice) // [10 3] } 从数组或切片生成新的切片拥有如下特性：\n取出的元素数量为：结束位置 - 开始位置； 取出元素不包含结束位置对应的元素，切片最后一个元素使用 slice[len(slice)-1] 获取； 当缺省开始位置时，表示从连续区域开头到结束位置，例如target[:2]等价于target[0:2]； 当缺省结束位置时，表示从开始位置到整个连续区域末尾，例如target[2:]等价于target[2:len(target)]； 两者同时缺省时，与target本身等效，例如target[:]等价于target[0:len(target)]； 两者同时为 0 时，等效于空切片，一般用于切片复位。 如果是从数组生成的切片，那么切片容量等于切片第一个元素到底层数组末尾的长度 由于切片是引用数据类型，修改切片的底层数组元素，切片的元素也会随之改变 直接声明新的切片 # 注意：[]type声明的是切片，是引用类型；而[...]type或[length]type声明的是数组，是值类型\nvar name []Type //声明一个切片，此时slice没有分配内存，是nil，len和cap都是0 var slice []string //声明切片并赋值 var slice []string = []string{\u0026#34;lucy\u0026#34;, \u0026#34;tom\u0026#34;, \u0026#34;lily\u0026#34;} //自动类型推导 slice := []string{\u0026#34;lucy\u0026#34;, \u0026#34;tom\u0026#34;, \u0026#34;lily\u0026#34;} //声明一个空切片，此时slice已经分配内存，不是nil slice := []string make # make( []Type, len, cap ) Type 是指切片的元素类型 len 指的是初始化多少个元素 cap 为预分配的元素数量，这个值设定后不影响 size，只是能提前分配空间，降低多次分配空间造成的性能问题，超出原cap(slice)限制，就会重新分配底层数组，即便原数组并未填满。 其中len \u0026lt;= cap package main import \u0026#34;fmt\u0026#34; func main() { a := make([]int, 2) // len = 2;cap = 2 b := make([]int, 2, 10) // len = 2;cap = 10 fmt.Println(len(a), len(b)) // 2 2 //cap函数可以获取切片的容量 fmt.Println(cap(a), cap(b)) // 2 10 } 注意：使用 make() 函数生成的切片一定发生了内存分配操作，但给定开始与结束位置（包括切片复位）的切片只是将新的切片结构指向已经分配好的内存区域，设定开始与结束位置，不会发生内存分配操作，也就是说如果修改切片元素，那么相当于修改被切片的数组或切片的元素。\npackage main import \u0026#34;fmt\u0026#34; func main() { a := [...]int{1, 2, 3, 4, 5, 6, 7} slice := a[1:6] slice[1] = 100 fmt.Println(a) //[1 2 100 4 5 6 7] } append # Go语言的内建函数append()可以为切片动态添加元素，切片的扩容也只能通过append()方法进行\n注意：append()函数的第一个参数必须为slice；而第二位参数为可变参数，如果是切片则需要使用...进行解包，如果是数组就需要先转成切片再解包，例如append(a,arr[:]...)\npackage main import \u0026#34;fmt\u0026#34; func main() { var a []int //在切片尾部添加 //在切片a的基础上，追加一个元素，并返回新的切片 a = append(a, 1) //在切片a的基础上，追加三个元素，并返回新的切片 a = append(a, 2, 3, 4) //在切片a的基础上，追加一个切片（这个切片需要使用省略号...进行解包），并返回新的切片 a = append(a, []int{5, 6, 7}...) //在切片头部添加 //在切片a的前面加一个元素0，并返回新的切片 a = append([]int{0}, a...) //在切片a的前面加一个切片，并返回新的切片 a = append([]int{-3, -2, -1}, a...) fmt.Println(a) // [-3 -2 -1 0 1 2 3 4 5 6 7] } 切片的扩容机制 # 注意：在使用append()函数为切片动态添加元素时，如果空间不足以容纳足够多的元素，切片就会进行扩容，此时新切片的长度会发生改变。\n如果新容量大于当前容量的两倍，则直接使用新容量 如果当前容量小于256，则新容量为当前容量的2倍 如果当前容量大于或等于256，则新容量 = 当前容量 + (当前容量 / 4)，即增加25% 计算出的容量会被舍入到较大的值以对齐内存 每次扩容时，创建新的数组，并将之前数组内容copy过来，所以发生扩容的时候slice的内存地址会发生变化。\n扩容需要创建新数组并复制数据，这是一个较为昂贵的操作。因此，如果预知切片可能增长到的大小，最好使用make函数预分配足够的容量。\npackage main import \u0026#34;fmt\u0026#34; func main() { s := []int{100, 200, 300, 400} fmt.Println(len(s), cap(s)) // 4 4 s = append(s, 500) fmt.Println(len(s), cap(s)) // 5 8 } 因为 append 函数返回新切片的特性，所以切片也支持链式操作，我们可以将多个 append 操作组合起来，实现在切片中间插入元素\n// 在第i个位置插入x a = append(a[:i], append(x, a[i:]...)...) package main import \u0026#34;fmt\u0026#34; func main() { slice := []int{1, 2, 3} insert(\u0026amp;slice, 1, 100, 200) fmt.Println(arr) // [1 100 200 2 3] } // 在切片slice的第index位插入num func insert(slice *[]int, index int, num ...int) { *slice = append((*slice)[:index], append(num, (*slice)[index:]...)...) } copy # Go语言的内置函数 copy() 可以将一个切片复制到另一个切片中，如果加入的两个数组切片不一样大，就会按照其中len()较小的那个数组切片的元素个数进行复制。\ncopy( destSlice, srcSlice ) 将 srcSlice 复制到 destSlice 返回值表示实际发生复制的元素个数 注意：目标切片必须和原切片类型一致\npackage main import \u0026#34;fmt\u0026#34; func main() { srcSlice := []int{100, 200, 300, 400} targetSlice := make([]int, 2, 5) i := copy(targetSlice, srcSlice) fmt.Println(i, targetSlice) // 2 [100 200] } 删除元素 # Go语言并没有对删除切片元素提供专用的语法或者接口，需要使用切片本身的特性来删除元素，根据要删除元素的位置有三种情况，分别是从开头位置删除、从中间位置删除和从尾部删除，其中删除切片尾部的元素速度最快。\n开头位置 # a = a[n:] //从开头位置删除n个元素 package main import \u0026#34;fmt\u0026#34; func main() { a := []int{1, 2, 3, 4, 5} //从开头位置删除2个元素 a = a[2:] fmt.Println(a) //[3 4 5] } 中间位置 # a = append(a[:n], a[m:]...) //从索引为n的位置，删除count=m-n个元素，即m=count+n package main import \u0026#34;fmt\u0026#34; func main() { a := []int{1, 2, 3, 4, 5} //从索引为1的位置开始删除2个元素 a = append(a[:1], a[2:]...) fmt.Println(a) //[1 3 4 5] } 尾部位置 # a = a[:len(a)-n] //删除尾部n个元素 package main import \u0026#34;fmt\u0026#34; func main() { a := []int{1, 2, 3, 4, 5} //删除尾部的2个元素 a = a[:len(a)-2] fmt.Println(a) //[1 2 3] } 注意：连续容器的元素删除无论在任何语言中，都要将删除点前后的元素移动到新的位置，随着元素的增加，这个过程将会变得极为耗时，因此，当业务需要大量、频繁地从一个切片中删除元素时，如果对性能要求较高的话，就需要考虑更换其他的容器了（如双链表等能快速从删除点删除元素）。\n清空切片 # slice := []int{1, 2, 3, 4, 5} // 方法1：保留底层数组 slice = slice[:0] // 方法2：完全清空，允许垃圾回收底层数组 slice = nil 排序 # Golang中除了自己手写排序算法实现排序外，还提供了sort包\n升序排序\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;sort\u0026#34; ) func main() { s1 := []int{4, 1, 6, 2, 10} sort.Ints(s1) fmt.Println(s1) // [1 2 4 6 10] s2 := []float64{3.1, 1.1, 2.5, 10.10} sort.Float64s(s2) fmt.Println(s2) // [1.1 2.5 3.1 10.1] s3 := []string{\u0026#34;a\u0026#34;, \u0026#34;c\u0026#34;, \u0026#34;f\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;e\u0026#34;} sort.Strings(s3) fmt.Println(s3) // [a b c e f] } 降序排序\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;sort\u0026#34; ) func main() { s1 := []int{4, 1, 6, 2, 10} sort.Sort(sort.Reverse(sort.IntSlice(s1))) fmt.Println(s1) // [1 2 4 6 10] s2 := []float64{3.1, 1.1, 2.5, 10.10} sort.Sort(sort.Reverse(sort.Float64Slice(s2))) fmt.Println(s2) // [10.1 3.1 2.5 1.1] s3 := []string{\u0026#34;a\u0026#34;, \u0026#34;c\u0026#34;, \u0026#34;f\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;e\u0026#34;} sort.Sort(sort.Reverse(sort.StringSlice(s3))) fmt.Println(s3) // [f e c b a] } map # Go语言中 map 是一种特殊的数据结构，一种元素对（Pair）的无序集合，Pair 对应一个 key（索引）和一个 value（值），所以这个结构也称为关联数组或字典，这是一种能够快速寻找值的理想结构，给定 key，就可以迅速找到对应的 value。\n声明 # var mapName map[keyType]valueType // [keyType]和valueType中间可以有空格 // 声明一个map var m map[int]string // 声明map并初始化 var m map[int]string = map[int]string{1: \u0026#34;lucy\u0026#34;, 2: \u0026#34;tom\u0026#34;} // 自动类型推导 m := map[int]string{1: \u0026#34;lucy\u0026#34;, 2: \u0026#34;tom\u0026#34;} // 换行初始化时最后一个元素后要加逗号 m := map[int]string{ 1: \u0026#34;lucy\u0026#34;, 2: \u0026#34;tom\u0026#34;, 3: \u0026#34;lily\u0026#34;, } // 使用make进行初始化 m := make(map[int]string) // 使用make进行初始化，并且初始容量100 m := make(map[int]string, 100) 注意： map 是可以动态增长（每次容量+1）的，未初始化的 map 的值是 nil（声明的map必须初始化才可以进行添加元素），使用函数len()可以获取 map 中 Pair（键值对）的数目。 出于性能的考虑，对于大的 map 或者会快速扩张的 map，即使只是大概知道容量，也最好先标明。 不能使用 new() 来构造 map，如果错误的使用 new() 分配了一个引用对象，会获得一个空引用的指针 package main import \u0026#34;fmt\u0026#34; func main() { a := make(map[int]string) // 添加元素 a[1] = \u0026#34;lucy\u0026#34; a[2] = \u0026#34;tom\u0026#34; a[3] = \u0026#34;lily\u0026#34; // 如果key已存在，那么会修改value a[1] = \u0026#34;james\u0026#34; // 获取值和是否存在，存在返回value,true val, exist := a[1] fmt.Println(val, exist) // james true // 使用一个变量接收，value value := a[1] fmt.Println(value) // james // 遍历map for k, v := range a { println(k, v) } } 删除 # 使用 delete() 内建函数从 map 中删除一组键值对，delete() 函数的格式如下：\ndelete(map, key) package main import \u0026#34;fmt\u0026#34; func main() { a := map[string]string{ \u0026#34;A1\u0026#34;: \u0026#34;tom\u0026#34;, \u0026#34;A2\u0026#34;: \u0026#34;lily\u0026#34;, \u0026#34;A3\u0026#34;: \u0026#34;james\u0026#34;, } // 删除编号为A2的键值对 delete(a, \u0026#34;A2\u0026#34;) fmt.Println(a) // map[A1:tom A3:james] } 切片与map组合 # package main import \u0026#34;fmt\u0026#34; func main() { // map切片：切片中元素为map a1 := []map[string]string{ { \u0026#34;id\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;tom\u0026#34;, }, { \u0026#34;id\u0026#34;: \u0026#34;2\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;lucy\u0026#34;, }, } fmt.Println(a1) // [map[id:1 name:tom] map[id:2 name:lucy]] // 切片作为map的value a2 := map[string][]string{ \u0026#34;A1\u0026#34;: {\u0026#34;tom\u0026#34;, \u0026#34;lucy\u0026#34;}, \u0026#34;A2\u0026#34;: {\u0026#34;lily\u0026#34;, \u0026#34;james\u0026#34;}, } fmt.Println(a2) // map[A1:[tom lucy] A2:[lily james]] } sync.Map # Go 语言中map如果在并发读的情况下是线程安全的，如果是在并发写的情况下，则是线程不安全的。Go 1.9引入了 sync.Map 是并发写安全的。\nsync.Map是一个结构体，所以无须初始化，直接声明即可使用，获取使用new()。\n声明 # // 方式一 var smap sync.Map // 方式二 smap = new(sync.Map) 常用API # Store()添加元素 # //对一个Map添加any类型的key和value func (m *Map) Store(key, value interface{}) Load()获取元素 # //根据key获取value,ok表示是否存在 func (m *Map) Load(key interface{}) (value interface{}, ok bool) Delete()删除元素 # //根据key删除 func (m *Map) Delete(key interface{}) LoadAndDelete()删除并返回 # //根据key删除，如果存在，就返回value,true;如果不存在返回nil,false func (m *Map) LoadAndDelete(key any) (value any, loaded bool) Range()遍历元素 # //接受一个参数为any类型的key和value、返回值为bool类型的函数 //如果返回true表示继续遍历，如果false表示停止遍历 func (m *Map) Range(f func(key, value interface{}) bool) a := new(sync.Map) a.Store(\u0026#34;lucy\u0026#34;, \u0026#34;stu001\u0026#34;) a.Store(\u0026#34;tom\u0026#34;, \u0026#34;stu002\u0026#34;) a.Store(\u0026#34;lily\u0026#34;, \u0026#34;stu003\u0026#34;) a.Range(func(key, value any) bool { fmt.Printf(\u0026#34;name:%s,id:%s\\n\u0026#34;, key, value) return true }) LoadOrStore()获取或添加 # //这个函数就是如果key存在那么返回value,true;如果key不存在就添加该key-value func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) list # 列表是一种非连续的存储容器，由多个节点组成，节点通过一些变量记录彼此之间的关系，列表有多种实现方法，如单链表、双链表等。\n在Go语言中，列表使用 container/list 包来实现，内部的实现原理是双链表，列表能够高效地进行任意位置的元素插入和删除操作。\n该包下有两个结构体，Element（代表双向链表中的节点）和List（代表双向链表）\n初始化 # list 的初始化有两种方法：分别是使用 list.New() 函数（func New() *List）和 var 关键字声明，两种方法的初始化效果都是一致的。\nli := list.New() var li list.List 列表与切片和 map 不同的是，列表并没有具体元素类型的限制，因此，列表的元素可以是任意类型，但是最好放一样的类型\nElement # 成员 描述 Value 在节点中存储的值 func (e *Element) Next() *Element 返回该元素的下一个元素指针，如果没有则返回nil func (e *Element) Prev() *Element 返回该元素的前一个元素指针，如果没有则返回nil List # 成员 描述 func (l *List) Back() *Element 获取list l的最后一个元素 func (l *List) Front() *Element 获取list l的第一个元素 func (l *List) Init() *List list l初始化或者清空 func (l *List) InsertAfter(v interface{}, mark *Element) *Element 在list l中元素mark之后插入一个值为v的元素，并返回该元素，如果mark不是list中元素，则list不改变 func (l *List) InsertBefore(v interface{}, mark *Element) *Element 在list l中元素mark之前插入一个值为v的元素，并返回该元素，如果mark不是list中元素，则list不改变。 func (l *List) Len() int 获取list l的长度 func (l *List) MoveAfter(e, mark *Element) 将元素e移动到元素mark之后，如果元素e或者mark不属于list l，或者e==mark，则list l不改变 func (l *List) MoveBefore(e, mark *Element) 将元素e移动到元素mark之前，如果元素e或者mark不属于list l，或者e==mark，则list l不改变 func (l *List) MoveToBack(e *Element) 将元素e移动到list l的末尾，如果e不属于list l，则list不改变 func (l *List) MoveToFront(e *Element) 将元素e移动到list l的首部，如果e不属于list l，则list不改变 func (l *List) PushBack(v interface{}) *Element 在list l的末尾插入值为v的元素，并返回该元素 func (l *List) PushBackList(other *List) /在list l的尾部插入另外一个list，其中l和other可以相等 func (l *List) PushFront(v interface{}) *Element 在list l的首部插入值为v的元素，并返回该元素 func (l *List) PushFrontList(other *List) 在list l的首部插入另外一个list，其中l和other可以相等 unc (l *List) Remove(e *Element) interface{} 如果元素e属于list l，将其从list中删除，并返回元素e的值 //main包 package main import ( \u0026#34;container/list\u0026#34; \u0026#34;fmt\u0026#34; ) func main() { li := list.New() li.PushBack(\u0026#34;lucy\u0026#34;) li.PushBack(\u0026#34;tom\u0026#34;) li.PushBack(\u0026#34;lily\u0026#34;) fmt.Printf(\u0026#34;列表的长度：%d\\n\u0026#34;, li.Len()) //遍历li for e := li.Front(); e != nil; e = e.Next() { fmt.Println(e.Value) } /* 列表的长度：3 lucy tom lily */ } for range # range 关键字在 go 语言中是相当常用好用的语法糖，可以用在 for 循环中迭代 array、slice、map、channel、字符串所有涉及到遍历输出的东西。\n注意：range迭代出来的value(v)只是每个元素的副本，也就是说range每迭代一次，就会出现一次拷贝，所以，修改v并不会对原有数据进行修改！\n如果不需要k或v，建议使用匿名变量_，这样减少赋值所带来的损耗\n字符串 # k：该字符开始位置的字节索引\nv：单个字符输出的是ASCII码，实际类型是 rune 类型\npackage main import \u0026#34;fmt\u0026#34; func main() { a := \u0026#34;你好呀\u0026#34; for k, v := range a { fmt.Printf(\u0026#34;第%d个字节后是:%c\\n\u0026#34;, k, v) } /* 第0个字符是:你 第3个字符是:好 第6个字符是:呀 */ } 数组 # k：元素的索引\nv：元素的值\npackage main import \u0026#34;fmt\u0026#34; func main() { a := [...]string{\u0026#34;lucy\u0026#34;, \u0026#34;lily\u0026#34;, \u0026#34;tom\u0026#34;} for k, v := range a { fmt.Printf(\u0026#34;第%d个姓名是:%s\\n\u0026#34;, k, v) } /* 第0个姓名是:lucy 第1个姓名是:lily 第2个姓名是:tom */ } 切片 # k：元素的索引\nv：元素的值\npackage main import \u0026#34;fmt\u0026#34; func main() { a := []string{\u0026#34;lucy\u0026#34;, \u0026#34;lily\u0026#34;, \u0026#34;tom\u0026#34;} for k, v := range a { fmt.Printf(\u0026#34;第%d个姓名是:%s\\n\u0026#34;, k, v) } /* 第0个姓名是:lucy 第1个姓名是:lily 第2个姓名是:tom */ } map # k：Pair的键（key）\nv：Pair的值（value）\n//main包 package main import \u0026#34;fmt\u0026#34; func main() { a := map[string]string{\u0026#34;A1\u0026#34;: \u0026#34;tom\u0026#34;, \u0026#34;A2\u0026#34;: \u0026#34;lily\u0026#34;, \u0026#34;A3\u0026#34;: \u0026#34;james\u0026#34;} for k, v := range a { fmt.Printf(\u0026#34;编号：%s，姓名：%s\\n\u0026#34;, k, v) } /* 编号：A1，姓名：tom 编号：A2，姓名：lily 编号：A3，姓名：james */ } channel # v：从管道中接收的数据\nch := make(chan string) for v := range ch { fmt.Println(\u0026#34;accept ok:\u0026#34;, v) } make和new的区别 # 相同点：二者都是用来分配内存的\nnew\n函数原型：func new(Type) *Type 用来分配内存，它的第一个参数是一个类型，不是一个值，它的返回值是一个指向新分配类型零值的指针 make\n函数原型：func make(t Type, size ...IntegerType) Type 用来为slice，map 或 chan 类型分配内存和初始化（不是零值）一个对象(注意：只能用在这三种类型上)。跟 new 类似，第一个参数也是一个类型，跟 new 不同的是，make 返回类型的引用而不是指针，而返回值也依赖于具体传入的类型 对于这三种类型，size参数的意义不一样 对于slice，第一个size表示长度，第二个size表示容量，且容量不能小于长度。如果省略第二个size，默认容量等于长度。 对于map，会根据size大小分配资源，以足够存储size个元素。如果省略size，会默认分配一个小的起始size。 对于chan，size表示缓冲区容量。如果省略size，channel为无缓冲channel。 一般情况下，我们不经常使用new，但是make非常常用，因为slice、map、chan必须初始化才可以进行操作，二者内存都是分配在堆上\n","date":"2025-05-12","externalUrl":null,"permalink":"/posts/2d3150b2/02a0817f/f4c735df/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e数组 \n    \u003cdiv id=\"数组\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%95%b0%e7%bb%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e声明 \n    \u003cdiv id=\"声明\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%a3%b0%e6%98%8e\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e数组是一个由\u003cstrong\u003e固定长度\u003c/strong\u003e的特定类型元素组成的序列，属于\u003cstrong\u003e值类型\u003c/strong\u003e（所以传参数组的时候一般使用指针，避免值类型传参复制导致内存消耗），不可以与\u003ccode\u003enil\u003c/code\u003e比较，一个数组可以由零个或多个元素组成。因为数组的长度是固定的，所以在Go语言中很少直接使用数组。\u003c/p\u003e","title":"7、容器","type":"posts"},{"content":"Node.js 的 http 模块提供了创建HTTP 服务器和客户端的功能。\nHTTP 服务器 # 创建方法 # 使用createServer 创建 # const http = require(\u0026#39;http\u0026#39;); // 创建服务对象 const server = http.createServer((req, res) =\u0026gt; { res.writeHead(200, { \u0026#39;Content-Type\u0026#39;: \u0026#39;text/plain\u0026#39; }); res.end(\u0026#39;Hello World\\n\u0026#39;); }); // 开启监听 3000 端口 server.listen(3000, \u0026#39;127.0.0.1\u0026#39;, () =\u0026gt; { console.log(\u0026#39;服务器运行在 http://127.0.0.1:3000/\u0026#39;); }); 使用 Server 类创建 # const http = require(\u0026#39;http\u0026#39;); const server = new http.Server((req, res) =\u0026gt; { // 设置统一响应头 res.writeHead(200, { \u0026#39;Content-Type\u0026#39;: \u0026#39;text/plain\u0026#39; }); // 设置统一响应体 res.end(\u0026#39;Hello World\\n\u0026#39;); }); server.listen(3001); 处理不同请求方法 # const http = require(\u0026#39;http\u0026#39;); // 2. 使用 Server 类 const server = new http.Server((req, res) =\u0026gt; { res.writeHead(200, { \u0026#39;Content-Type\u0026#39;: \u0026#39;text/plain\u0026#39; }); res.end(\u0026#39;Hello World\\n\u0026#39;); }); server.listen(3001); // 处理不同请求方法 const server = http.createServer((req, res) =\u0026gt; { switch (req.method) { case \u0026#39;GET\u0026#39;: handleGet(req, res); break; case \u0026#39;POST\u0026#39;: handlePost(req, res); break; default: res.writeHead(405); res.end(\u0026#39;Method Not Allowed\u0026#39;); } }); // get 方法处理函数 function handleGet(req, res) { res.writeHead(200, { \u0026#39;Content-Type\u0026#39;: \u0026#39;application/json\u0026#39; }); res.end(JSON.stringify({ message: \u0026#39;GET request handled\u0026#39; })); } // post 方法处理函数 function handlePost(req, res) { let body = \u0026#39;\u0026#39;; req.on(\u0026#39;data\u0026#39;, chunk =\u0026gt; { body += chunk.toString(); }); req.on(\u0026#39;end\u0026#39;, () =\u0026gt; { try { const data = JSON.parse(body); res.writeHead(200, { \u0026#39;Content-Type\u0026#39;: \u0026#39;application/json\u0026#39; }); res.end(JSON.stringify({ received: data })); } catch (e) { res.writeHead(400); res.end(\u0026#39;Invalid JSON\u0026#39;); } }); } 服务端事件 # // 1. 连接事件 server.on(\u0026#39;connection\u0026#39;, (socket) =\u0026gt; { console.log(\u0026#39;新连接建立\u0026#39;); socket.on(\u0026#39;data\u0026#39;, (chunk) =\u0026gt; { console.log(\u0026#39;收到数据:\u0026#39;, chunk.toString()); }); socket.on(\u0026#39;end\u0026#39;, () =\u0026gt; { console.log(\u0026#39;连接结束\u0026#39;); }); socket.on(\u0026#39;error\u0026#39;, (err) =\u0026gt; { console.error(\u0026#39;Socket 错误:\u0026#39;, err); }); }); // 2. 请求事件 server.on(\u0026#39;request\u0026#39;, (req, res) =\u0026gt; { console.log(\u0026#39;收到请求:\u0026#39;, req.method, req.url); }); // 3. 错误事件 server.on(\u0026#39;error\u0026#39;, (err) =\u0026gt; { console.error(\u0026#39;服务器错误:\u0026#39;, err); }); // 4. 关闭事件 server.on(\u0026#39;close\u0026#39;, () =\u0026gt; { console.log(\u0026#39;服务器关闭\u0026#39;); }); // 5. 监听事件 server.on(\u0026#39;listening\u0026#39;, () =\u0026gt; { console.log(\u0026#39;服务器开始监听\u0026#39;); }); 服务端常用方法 # // 1. 启动服务器 server.listen(3000, \u0026#39;127.0.0.1\u0026#39;, () =\u0026gt; { console.log(\u0026#39;服务器运行在 http://127.0.0.1:3000/\u0026#39;); }); // 2. 关闭服务器 server.close(() =\u0026gt; { console.log(\u0026#39;服务器已关闭\u0026#39;); }); // 3. 获取服务器地址信息 const address = server.address(); console.log(\u0026#39;服务器地址:\u0026#39;, address); // { address: \u0026#39;127.0.0.1\u0026#39;, family: \u0026#39;IPv4\u0026#39;, port: 3000 } // 4. 设置超时 server.setTimeout(60000); // 60 秒 server.on(\u0026#39;timeout\u0026#39;, (socket) =\u0026gt; { console.log(\u0026#39;连接超时\u0026#39;); }); // 5. 获取最大头信息大小 const maxHeaderSize = server.maxHeaderSize; console.log(\u0026#39;最大头信息大小:\u0026#39;, maxHeaderSize); // 6. 设置最大头信息大小 server.maxHeaderSize = 16384; // 16KB 回调函数 # 请求对象 (http.IncomingMessage) # 即我们在创建 server 时回调函数的第一个参数\n// 1. 请求属性 server.on(\u0026#39;request\u0026#39;, (req, res) =\u0026gt; { console.log(\u0026#39;请求方法:\u0026#39;, req.method); console.log(\u0026#39;请求 URL:\u0026#39;, req.url); console.log(\u0026#39;请求头:\u0026#39;, req.headers); console.log(\u0026#39;HTTP 版本:\u0026#39;, req.httpVersion); console.log(\u0026#39;HTTP 版本号:\u0026#39;, req.httpVersionMajor, req.httpVersionMinor); console.log(\u0026#39;原始 URL:\u0026#39;, req.rawHeaders); console.log(\u0026#39;原始尾随头信息:\u0026#39;, req.rawTrailers); console.log(\u0026#39;尾随头信息:\u0026#39;, req.trailers); console.log(\u0026#39;Socket:\u0026#39;, req.socket); console.log(\u0026#39;连接:\u0026#39;, req.connection); }); // 2. 请求事件 server.on(\u0026#39;request\u0026#39;, (req, res) =\u0026gt; { // 数据事件 req.on(\u0026#39;data\u0026#39;, (chunk) =\u0026gt; { console.log(\u0026#39;收到数据块:\u0026#39;, chunk); }); // 结束事件 req.on(\u0026#39;end\u0026#39;, () =\u0026gt; { console.log(\u0026#39;请求数据接收完成\u0026#39;); }); // 错误事件 req.on(\u0026#39;error\u0026#39;, (err) =\u0026gt; { console.error(\u0026#39;请求错误:\u0026#39;, err); }); // 关闭事件 req.on(\u0026#39;close\u0026#39;, () =\u0026gt; { console.log(\u0026#39;请求关闭\u0026#39;); }); // 中止事件 req.on(\u0026#39;aborted\u0026#39;, () =\u0026gt; { console.log(\u0026#39;请求中止\u0026#39;); }); }); // 3. 请求方法 server.on(\u0026#39;request\u0026#39;, (req, res) =\u0026gt; { // 销毁请求 req.destroy(); // 暂停请求 req.pause(); // 恢复请求 req.resume(); }); 响应对象 (http.ServerResponse) # 即我们在创建 server 时回调函数的第二个参数\n// 1. 响应属性 server.on(\u0026#39;request\u0026#39;, (req, res) =\u0026gt; { console.log(\u0026#39;响应头已发送:\u0026#39;, res.headersSent); console.log(\u0026#39;响应完成:\u0026#39;, res.finished); console.log(\u0026#39;响应发送中:\u0026#39;, res.sending); console.log(\u0026#39;响应状态码:\u0026#39;, res.statusCode); console.log(\u0026#39;响应状态消息:\u0026#39;, res.statusMessage); console.log(\u0026#39;Socket:\u0026#39;, res.socket); }); // 2. 响应方法 server.on(\u0026#39;request\u0026#39;, (req, res) =\u0026gt; { // 设置响应头 res.setHeader(\u0026#39;Content-Type\u0026#39;, \u0026#39;text/plain\u0026#39;); res.setHeader(\u0026#39;X-Custom-Header\u0026#39;, \u0026#39;value\u0026#39;); // 获取响应头 const contentType = res.getHeader(\u0026#39;Content-Type\u0026#39;); console.log(\u0026#39;Content-Type:\u0026#39;, contentType); // 移除响应头 res.removeHeader(\u0026#39;X-Custom-Header\u0026#39;); // 写入响应头 res.writeHead(200, { \u0026#39;Content-Type\u0026#39;: \u0026#39;text/plain\u0026#39; }); // 写入响应体 res.write(\u0026#39;Hello \u0026#39;); res.write(\u0026#39;World\\n\u0026#39;); // 结束响应 res.end(); // 刷新响应头 res.flushHeaders(); }); // 3. 响应事件 server.on(\u0026#39;request\u0026#39;, (req, res) =\u0026gt; { // 完成事件 res.on(\u0026#39;finish\u0026#39;, () =\u0026gt; { console.log(\u0026#39;响应完成\u0026#39;); }); // 关闭事件 res.on(\u0026#39;close\u0026#39;, () =\u0026gt; { console.log(\u0026#39;响应关闭\u0026#39;); }); }); HTTP 客户端 # 发送请求 # GET # const getReq = http.get(\u0026#39;http://example.com\u0026#39;, (res) =\u0026gt; { console.log(\u0026#39;状态码:\u0026#39;, res.statusCode); console.log(\u0026#39;头信息:\u0026#39;, res.headers); let data = \u0026#39;\u0026#39;; res.on(\u0026#39;data\u0026#39;, chunk =\u0026gt; { data += chunk; }); res.on(\u0026#39;end\u0026#39;, () =\u0026gt; { console.log(\u0026#39;响应数据:\u0026#39;, data); }); }); getReq.on(\u0026#39;error\u0026#39;, (err) =\u0026gt; { console.error(\u0026#39;请求错误:\u0026#39;, err); }); POST # const postData = JSON.stringify({ key: \u0026#39;value\u0026#39; }); const options = { hostname: \u0026#39;example.com\u0026#39;, path: \u0026#39;/api/data\u0026#39;, method: \u0026#39;POST\u0026#39;, headers: { \u0026#39;Content-Type\u0026#39;: \u0026#39;application/json\u0026#39;, \u0026#39;Content-Length\u0026#39;: Buffer.byteLength(postData) } }; const postReq = http.request(options, (res) =\u0026gt; { console.log(\u0026#39;状态码:\u0026#39;, res.statusCode); let data = \u0026#39;\u0026#39;; res.on(\u0026#39;data\u0026#39;, chunk =\u0026gt; { data += chunk; }); res.on(\u0026#39;end\u0026#39;, () =\u0026gt; { console.log(\u0026#39;响应数据:\u0026#39;, data); }); }); postReq.on(\u0026#39;error\u0026#39;, (err) =\u0026gt; { console.error(\u0026#39;请求错误:\u0026#39;, err); }); postReq.write(postData); postReq.end(); 使用 Agent # const agent = new http.Agent({ keepAlive: true, keepAliveMsecs: 1000, maxSockets: 10 }); const reqWithAgent = http.request({ hostname: \u0026#39;example.com\u0026#39;, path: \u0026#39;/\u0026#39;, agent: agent }, (res) =\u0026gt; { // 处理响应 }); ","date":"2025-04-27","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/65df2b3b/a81fac8d/","section":"文章","summary":"\u003cp\u003eNode.js 的 http 模块提供了创建HTTP 服务器和客户端的功能。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003eHTTP 服务器 \n    \u003cdiv id=\"http-服务器\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#http-%e6%9c%8d%e5%8a%a1%e5%99%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e创建方法 \n    \u003cdiv id=\"创建方法\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%9b%e5%bb%ba%e6%96%b9%e6%b3%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003e使用createServer 创建 \n    \u003cdiv id=\"使用createserver-创建\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bd%bf%e7%94%a8createserver-%e5%88%9b%e5%bb%ba\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003erequire\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;http\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 创建服务对象\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eserver\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecreateServer\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ewriteHead\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e200\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;Content-Type\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;text/plain\u0026#39;\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;Hello World\\n\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 开启监听 3000 端口\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elisten\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e3000\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;127.0.0.1\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;服务器运行在 http://127.0.0.1:3000/\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch4 class=\"relative group\"\u003e使用 Server 类创建 \n    \u003cdiv id=\"使用-server-类创建\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bd%bf%e7%94%a8-server-%e7%b1%bb%e5%88%9b%e5%bb%ba\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003erequire\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;http\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eserver\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eServer\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"c1\"\u003e// 设置统一响应头\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e  \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ewriteHead\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e200\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;Content-Type\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;text/plain\u0026#39;\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"c1\"\u003e// 设置统一响应体\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e  \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;Hello World\\n\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elisten\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e3001\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e处理不同请求方法 \n    \u003cdiv id=\"处理不同请求方法\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%a4%84%e7%90%86%e4%b8%8d%e5%90%8c%e8%af%b7%e6%b1%82%e6%96%b9%e6%b3%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003erequire\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;http\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 2. 使用 Server 类\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eserver\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eServer\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ewriteHead\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e200\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;Content-Type\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;text/plain\u0026#39;\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;Hello World\\n\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elisten\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e3001\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 处理不同请求方法\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eserver\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecreateServer\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"k\"\u003eswitch\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003emethod\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;GET\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nx\"\u003ehandleGet\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ebreak\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;POST\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nx\"\u003ehandlePost\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"k\"\u003ebreak\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edefault\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ewriteHead\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e405\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;Method Not Allowed\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// get 方法处理函数\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e \u003cspan class=\"nx\"\u003ehandleGet\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ewriteHead\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e200\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;Content-Type\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;application/json\u0026#39;\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eJSON\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003estringify\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"nx\"\u003emessage\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;GET request handled\u0026#39;\u003c/span\u003e \u003cspan class=\"p\"\u003e}));\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// post 方法处理函数\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e \u003cspan class=\"nx\"\u003ehandlePost\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nx\"\u003ebody\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eon\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;data\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003echunk\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ebody\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"nx\"\u003echunk\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003etoString\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eon\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;end\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003etry\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003edata\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eJSON\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eparse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ewriteHead\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e200\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;Content-Type\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;application/json\u0026#39;\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eJSON\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003estringify\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"nx\"\u003ereceived\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003edata\u003c/span\u003e \u003cspan class=\"p\"\u003e}));\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003ecatch\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ewriteHead\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e400\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;Invalid JSON\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e服务端事件 \n    \u003cdiv id=\"服务端事件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9c%8d%e5%8a%a1%e7%ab%af%e4%ba%8b%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 1. 连接事件\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eon\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;connection\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003esocket\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;新连接建立\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003esocket\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eon\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;data\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003echunk\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;收到数据:\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003echunk\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003etoString\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003esocket\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eon\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;end\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;连接结束\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003esocket\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eon\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;error\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eerror\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;Socket 错误:\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 2. 请求事件\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eon\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;request\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;收到请求:\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003emethod\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 3. 错误事件\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eon\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;error\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eerror\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;服务器错误:\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 4. 关闭事件\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eon\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;close\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;服务器关闭\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 5. 监听事件\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eon\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;listening\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;服务器开始监听\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e服务端常用方法 \n    \u003cdiv id=\"服务端常用方法\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9c%8d%e5%8a%a1%e7%ab%af%e5%b8%b8%e7%94%a8%e6%96%b9%e6%b3%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 1. 启动服务器\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elisten\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e3000\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;127.0.0.1\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;服务器运行在 http://127.0.0.1:3000/\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 2. 关闭服务器\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e(()\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;服务器已关闭\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 3. 获取服务器地址信息\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eaddress\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eaddress\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;服务器地址:\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eaddress\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \u003cspan class=\"c1\"\u003e// { address: \u0026#39;127.0.0.1\u0026#39;, family: \u0026#39;IPv4\u0026#39;, port: 3000 }\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 4. 设置超时\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003esetTimeout\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e60000\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 60 秒\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eon\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;timeout\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003esocket\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;连接超时\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 5. 获取最大头信息大小\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003emaxHeaderSize\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003emaxHeaderSize\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;最大头信息大小:\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003emaxHeaderSize\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 6. 设置最大头信息大小\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003eserver\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003emaxHeaderSize\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e16384\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 16KB\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e回调函数 \n    \u003cdiv id=\"回调函数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%9b%9e%e8%b0%83%e5%87%bd%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003e请求对象 (http.IncomingMessage) \n    \u003cdiv id=\"请求对象-httpincomingmessage\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%af%b7%e6%b1%82%e5%af%b9%e8%b1%a1-httpincomingmessage\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cp\u003e即我们在创建 server 时回调函数的第一个参数\u003c/p\u003e","title":"7、http","type":"posts"},{"content":"charts 是个由百度开源的数据可视化，凭借着良好的交互性，精巧的图表设计，得到了众多开发者的认可。而 Python 是门富有表达力的语言，很适合用于数据处理. 当数据分析遇上数据可视化时pyecharts 诞生了。\n官方文档：https://gallery.pyecharts.org/#/README\n安装 # pip install pyecharts 使用 # 折线图 # from pyecharts.charts import Line # 构建折线图对象 line = Line() # x轴数据 line.add_xaxis([\u0026#39;张三\u0026#39;,\u0026#39;李四\u0026#39;,\u0026#39;王五\u0026#39;]) # y轴数据 line.add_yaxis(\u0026#39;score\u0026#39;,[80,90,70]) # 生成图表 line.render() ","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/46b8de4c/45b667ec/","section":"文章","summary":"\u003cp\u003echarts 是个由百度开源的数据可视化，凭借着良好的交互性，精巧的图表设计，得到了众多开发者的认可。而 Python 是门富有表达力的语言，很适合用于数据处理. 当数据分析遇上数据可视化时pyecharts 诞生了。\u003c/p\u003e","title":"7、pyecharts","type":"posts"},{"content":" 创建骨骼 # 内置骨骼：就是通过Shift + a的骨骼选项创建的单节骨骼 内置插件 Rigify 快捷键 # 编辑模式 # 挤出骨骼：E\n骨骼扭转：Ctrl + R\n清空扭转：Alt + R\n扭转对齐：Shift + N\n骨骼镜像 # 需要先名称重命名，再进行对称\n姿态模式 # 只有在姿态模式下才能做动画关键帧\n添加 IK：Shift + i（姿态模式）\n清空 IK 控制器：Ctrl + Alt + i\n蒙皮权重 # 给模型添加骨骼，必须先选择模型，再按住Shift选骨骼，然后再Ctrl + P，然后一般选择附带自动权重即可。\n骨骼的权重，其实是靠顶点组来完成的，可以在物体的数据面板中看到\n然后进入权重绘制模式，可以看到每一段骨骼影响的模型范围以及权重颜色\n在权重绘制面板中，可以按住左键刷权重，也可以按住Ctrl + 左键刷掉权重\n","date":"2025-04-18","externalUrl":null,"permalink":"/posts/69064821/f1464f3a/19289dae/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e创建骨骼 \n    \u003cdiv id=\"创建骨骼\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%9b%e5%bb%ba%e9%aa%a8%e9%aa%bc\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e内置骨骼：就是通过\u003ccode\u003eShift + a\u003c/code\u003e的骨骼选项创建的单节骨骼\u003c/li\u003e\n\u003cli\u003e内置插件\n\u003cul\u003e\n\u003cli\u003eRigify\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e快捷键 \n    \u003cdiv id=\"快捷键\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%bf%ab%e6%8d%b7%e9%94%ae\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e编辑模式 \n    \u003cdiv id=\"编辑模式\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%bc%96%e8%be%91%e6%a8%a1%e5%bc%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e挤出骨骼：\u003ccode\u003eE\u003c/code\u003e\u003c/p\u003e","title":"7、骨骼","type":"posts"},{"content":"Text 组件用于显示文本文档，包含纯文本或格式化文本（使用不同字体，嵌入图片，显示链接，甚至是带 CSS 格式的 HTML 等）。因此，它常常也被用于作为简单的文本编辑器和网页浏览器使用。\nimport tkinter as tk root = tk.Tk() # 创建了一个宽60字符、高10行的Text控件 text = tk.Text(root,width=60,height=30) text.pack() # 创建一个Button，每次点击给Text尾部添加一句话 btn = tk.Button(root,text=\u0026#39;click to add text\u0026#39;,command=lambda :text.insert(\u0026#39;end\u0026#39;,\u0026#39;hello\\n\u0026#39;)) btn.pack() root.mainloop() 属性 # 属性 含义 autoseparators 1. 指定实现“撤销”操作的时候是否自动插入一个“分隔符”（用于分隔操作记录）； 2. 默认值是 True background 1. 设置 Text 组件的背景颜色 ；2. 注意：通过使用 Tags 可以使 Text 组件中的文本支持多种背景颜色显示（请参考上方【Tags 用法】） bg 跟 background 一样 borderwidth 1. 设置 Entry 的边框宽度 ；2. 默认值是 1 像素 bd 跟 borderwidth 一样 cursor 1. 指定当鼠标在 Text 组件上飘过的时候的鼠标样式 ；2. 默认值由系统指定 exportselection 1. 指定选中的文本是否可以被复制到剪贴板 ；2. 默认值是 True ；3. 可以修改为 False 表示不允许复制文本 font 1. 设置 Text 组件中文本的默认字体 ；2. 注意：通过使用 Tags 可以使 Text 组件中的文本支持多种字体显示 foreground 1. 设置 Text 组件中文本的颜色 ；2. 注意：通过使用 Tags 可以使 Text 组件中的文本支持多种颜色显示 fg 跟 foreground 一样 height 1. 设置 Text 组件的高度； 2. 注意：单位是行数，不是像素噢 highlightbackground 1. 指定当 Text 组件没有获得焦点的时候高亮边框的颜色 ；2. 默认值由系统指定 highlightcolor 1. 指定当 Text 组件获得焦点的时候高亮边框的颜色 ；2. 默认值由系统指定 highlightthickness 1. 指定高亮边框的宽度 ；2. 默认值是 0 insertbackground 1. 设置插入光标的颜色 ；2. 默认是 BLACK（或 \u0026ldquo;black\u0026rdquo;） insertborderwidth 1. 设置插入光标的边框宽度 ；2. 默认值是 0 3；3.你得设置 insertwidth 选项为比较大的数值才能看出来 insertofftime 1. 该选项控制光标的闪烁频率（灭）； 2. 单位是毫秒 insertontime 1. 该选项控制光标的闪烁频率（亮）； 2. 单位是毫秒 insertwidth 1. 指定光标的宽度 ；2. 默认值是 2 像素 maxundo 1. 设置允许“撤销”操作的最大次数 ；2. 默认值是 0 ；3. 设置为 -1 表示不限制 padx 1. 指定水平方向上的额外间距（内容和边框间） ；2. 默认值是 1 pady 1. 指定垂直方向上的额外间距（内容和边框间） ；2. 默认值是 1 relief 1. 指定边框样式 ；2. 默认值是 \u0026ldquo;sunken\u0026rdquo; ；3. 其他可以选择的值是 \u0026ldquo;flat\u0026rdquo;，\u0026ldquo;raised\u0026rdquo;，\u0026ldquo;groove\u0026rdquo; 和 \u0026ldquo;ridge\u0026rdquo; selectbackground 1. 指定被选中文本的背景颜色 ；2. 默认值由系统指定 selectborderwidth 1. 指定被选中文本的边框宽度 ；2. 默认值是 0 selectforeground 1. 指定被选中文本的字体颜色 ；2. 默认值由系统指定 setgrid 1. 指定一个布尔类型的值，确定是否启用网格控制 ；2. 默认值是 False spacing1 1. 指定 Text 组件的文本块中每一行与上方的空白间隔； 2. 注意：自动换行不算 ；3. 默认值是 0 spacing2 1. 指定 Text 组件的文本块中自动换行的各行间的空白间隔 ；2. 注意：换行符（\u0026rsquo;\\n\u0026rsquo;）不算 ；3. 默认值是 0 spacing3 1. 指定 Text 组件的文本中每一行与下方的空白间隔 ；2. 注意：自动换行不算； 3. 默认值是 0 state 1. 默认情况下 Text 组件响应键盘和鼠标事件（\u0026ldquo;normal\u0026rdquo;） ；2. 如果将该选项的值设置为 \u0026ldquo;disabled\u0026rdquo;，那么上述响应就不会发生，并且你无法修改里边的内容 tabs 1. 定制 Tag 所描述的文本块中 Tab 按键的功能； 2. 默认 Tab 被定义为 8 个字符的宽度 ；3.你还可以定义多个制表位：tabs=(\u0026lsquo;3c\u0026rsquo;, \u0026lsquo;5c\u0026rsquo;, \u0026lsquo;12c\u0026rsquo;) 表示前 3 个 Tab 宽度分别为 3厘米，5厘米，12厘米，接着的 Tab 按照最后两个的差值计算，即：19厘米，26厘米，33厘米；4.你应该注意到了，它上边 \u0026lsquo;c\u0026rsquo; 的含义是“厘米”而不是“字符”，还可以选择的单位有 \u0026lsquo;i\u0026rsquo;（英寸），\u0026rsquo;m\u0026rsquo;（毫米）和 \u0026lsquo;p\u0026rsquo;（DPI，大约是 \u0026lsquo;1i\u0026rsquo; 等于 \u0026lsquo;72p\u0026rsquo;）；5.如果是一个整型值，则单位是像素 takefocus 1. 指定使用 Tab 键可以将焦点移动到 Text 组件中 ；2. 默认是开启的，可以将该选项设置为 False 避免焦点在此 Text 组件中 undo 1. 该选项设置为 True 开启“撤销”功能 ；2. 该选项设置为 False 关闭“撤销”功能 ；3. 默认值是 False width 1. 设置 Text 组件的宽度 ；2. 注意：单位是字符数，因此 Text 组件的实际宽度还取决于字体的大小 wrap 1. 设置当一行文本的长度超过 width 选项设置的宽度时，是否自动换行 ；2. 该选项的值可以是：\u0026ldquo;none\u0026rdquo;（不自动换行），\u0026ldquo;char\u0026rdquo;（按字符自动换行）和 \u0026ldquo;word\u0026rdquo;（按单词自动换行） xscrollcommand 1. 与 scrollbar（滚动条）组件相关联（水平方向） yscrollcommand 1. 与 scrollbar（滚动条）组件相关联（垂直方向） 加载控件或图片 # 在 Text 组件中插入对象，可以使用 window_create() 和 image_create() 方法来插入一个控件或者一个图片\nimport tkinter as tk from PIL import Image,ImageTk root = tk.Tk() # 创建Text text = tk.Text(root,width=30,height=10) text.pack() # 在 Text 中插入一个按钮控件 test_btn = tk.Button(text,text=\u0026#39;test_btn\u0026#39;) text.window_create(\u0026#39;insert\u0026#39;,window=test_btn) # 在 Text 中插入一张图片 # 这个方法传入 file 参数的话只支持 gif 图片 img1 = tk.PhotoImage(file=\u0026#39;./123.gif\u0026#39;) text.image_create(\u0026#39;insert\u0026#39;,image=img1) # 如果需要插入其他格式图片，则需要使用PIL库中的ImageTk方法进行转换 img2 = ImageTk.PhotoImage(Image.open(\u0026#39;./123.jpg\u0026#39;)) text.image_create(\u0026#39;insert\u0026#39;,image=img2) root.mainloop() 只读 # 将 state 选项从默认的 \u0026ldquo;normal\u0026rdquo; 修改为 \u0026ldquo;disabled\u0026rdquo;，使得 Text 组件中的内容为“只读”形式。不过需要注意的是，当你需要进行任何修改的时候，记得将 state 选项改回 \u0026ldquo;normal\u0026rdquo;，否则 insert() 和 delete() 方法都会失效。\ntext[\u0026#39;state\u0026#39;] = \u0026#39;disabled\u0026#39; 搜索 # 使用search()方法，可以可以搜索 Text 组件中的内容\nsearch()方法的 backwards 属性表示是否向后搜索\npos + '+1c'表示pos后一个字符，同理pos - '-1c'表示pos前一个字符\nimport tkinter as tk from PIL import Image,ImageTk root = tk.Tk() # 创建Text text = tk.Text(root,width=30,height=10) text.pack() text.insert(\u0026#39;insert\u0026#39;,\u0026#39;hello\\nworld\u0026#39;) lable = tk.Label(root) lable.pack() search_val = \u0026#39;\u0026#39; search_start = 1.0 pos = \u0026#39;\u0026#39; # 搜索函数 def search(): global search_val,search_start,pos # 获取搜索框内的内容 val = search_entry.get() # 判断搜索框内容如果变了的话就重新从头搜索 if search_val != val: search_start = 1.0 pos = None search_val = val # 在Text控件中搜索，返回索引 pos = text.search(val,index=search_start,stopindex=\u0026#39;end\u0026#39;) # 如果没有搜索到，则显示 not found，并退出函数 if not pos: lable[\u0026#39;text\u0026#39;] = \u0026#39;not found\u0026#39; search_val = \u0026#39;\u0026#39; return # 如果搜索到了，就显示索引 lable[\u0026#39;text\u0026#39;] = pos # 并且将下回搜索的开始位置设置到本次搜索结果后一个字符 search_start = pos + \u0026#39;+1c\u0026#39; # 搜索框与搜索按钮 search_entry = tk.Entry(root) search_entry.pack() search_btn = tk.Button(root,text=\u0026#39;Search\u0026#39;,command=search) search_btn.pack() root.mainloop() 恢复和撤销操作 # 通过设置 undo 选项为 True 可以开启 Text 组件的“撤销”功能。然后用 edit_undo() 方法实现“撤销”操作，用 edit_redo() 方法实现“恢复”操作。\n这是因为 Text 组件内部有一个栈专门用于记录内容的每次变动，所以每次“撤销”操作就是一次弹栈操作，“恢复”就是再次压栈。\nimport tkinter as tk root = tk.Tk() # 此处开启Text控件的撤销操作 text = tk.Text(root,undo=True) text.pack() undo_btn = tk.Button(root,text=\u0026#39;撤销\u0026#39;,command=lambda :text.edit_undo()) redo_btn = tk.Button(root,text=\u0026#39;恢复\u0026#39;,command=lambda :text.edit_redo()) undo_btn.pack() redo_btn.pack() root.mainloop() 默认情况下，每一次完整的操作将会放入栈中。但怎么样算是一次完整的操作呢？Tkinter 觉得每次焦点切换、用户按下 Enter 键、删除、插入操作的转换等之前的操作算是一次完整的操作。也就是说你连续输入I love Python的话，一次的“撤销”操作就会将所有的内容删除。\n如果不想这样，那么做法就是先将 autoseparators 选项设置为 False（因为这个选项是让 Tkinter 在认为一次完整的操作结束后自动插入“分隔符”），然后绑定键盘事件，每次有输入就用 edit_separator() 方法人为地插入一个“分隔符”：\nimport tkinter as tk root = tk.Tk() text = tk.Text(root, width=40, height=5, autoseparators=False, undo=True, maxundo=10) text.pack() def callback(event): text.edit_separator() text.bind(\u0026#39;\u0026lt;Key\u0026gt;\u0026#39;, callback) def move(): text.edit_undo() tk.Button(root, text = \u0026#34;撤销\u0026#34;, command = move).pack() root.mainloop() 常用方法 # 方法 功能 delete(起始位置，[,终止位置]) 删除指定区域文本，起始位置 index = 1.0（特别注意），也可以传入一个 window 对象或者一个 image 对象 get(起始位置，[,终止位置]) 获取指定区域文本 insert(位置，[,字符串]\u0026hellip;) 将文本插入到指定位置 see(位置) 在指定位置是否可见文本，返回布尔值 index(标记) 返回标记所在的行和列 mark_names() 返回所有标记名称 mark_set(标记，位置) 在指定位置设置标记 mark_unset(标记) 去除标记 indexes 索引 # Text 控件的操作，基本上都与索引有关，Tkinter 提供一系列不同的索引类型：\nline.column：（行.列），例如(1.0)代表第一行第一列 注意：行号以 1 开始，列号则以 0 开始 在需要指定索引的时候使用浮点值例如1.0代替也是可以的。 line.end：（某一行的末尾），例如(1,end)代表第一行末尾 insert：对应插入光标的位置。 current：对应与鼠标坐标最接近的位置。不过，如果你紧按鼠标任何一个按钮，它会直到你松开它才响应。 end：对应 Text 组件的文本缓冲区最后一个字符的下一个位置。 user-defined marks：是对 Text 组件中位置的命名。例如insert和 current就是两个预先命名好的 marks，参考后面的 marks user-defined tags：代表可以分配给 Text 组件的特殊事件绑定和风格，参考后面的 tags selection(sel.first，sel.last)：selection 是一个名为 sel的特殊 tag，表示当前被选中的范围 可以使用 sel.first 到 sel.last 来表示这个范围。如果没有选中的内容，那么 Tkinter 会抛出一个TclError 异常。 window coordinate(\u0026quot;@x,y\u0026quot;)：使用窗口坐标作为索引。 例如在一个事件绑定中，可以使用\u0026quot;@%d,%d\u0026quot; % (event.x, event.y)找到最接近鼠标位置的字符。 embedded object name(window，images)：用于指向在 Text 组件中嵌入的 window 和 image 对象。 expressions：用于修改任何格式的索引，用字符串的形式实现修改索引的表达式。 + count chars：将索引向前（-\u0026gt;）移动 count 个字符，可以越过换行符，但不能超过 END 的位置。 - count chars：将索引向后（\u0026lt;-）移动 count 个字符，可以越过换行符，但不能超过 1.0 的位置 + count lines：将索引向前（-\u0026gt;）移动 count 行，索引会尽量保持与移动前在同一列上，但如果移动后的那一行字符太少，将移动到该行的末尾 - count lines：将索引向后（\u0026lt;-）移动 count 行，索引会尽量保持与移动前在同一列上，但如果移动后的那一行字符太少，将移动到该行的末尾 linestart：将索引移动到当前索引所在行的起始位置，注意，使用该表达式前边必须有一个空格隔开 lineend：将索引移动到当前索引所在行的末尾，注意，使用该表达式前边必须有一个空格隔开 wordstart：将索引移动到当前索引指向的单词的开头，单词的定义是一系列字母、数字、下划线或任何非空白字符的组合，注意，使用该表达式前边必须有一个空格隔开 wordend：将索引移动到当前索引指向的单词的末尾，单词的定义是一系列字母、数字、下划线或任何非空白字符的组合，注意，使用该表达式前边必须有一个空格隔开 此外：只要结果不产生歧义，关键字可以被缩写，空格也可以省略。例如：+ 5 chars 可以简写成 +5c index() # index() 方法用于将所有支持的“索引”格式，即line.column（行、列）的格式\nimport tkinter as tk from PIL import Image,ImageTk root = tk.Tk() # 创建Text text = tk.Text(root,width=30,height=10) text.pack() text.insert(\u0026#39;insert\u0026#39;,\u0026#39;hello\\n\u0026#39;) lable = tk.Label(root,text=f\u0026#39;insert: {text.index(\u0026#39;insert\u0026#39;)},end: {text.index(\u0026#39;end\u0026#39;)}\u0026#39;) lable.pack() root.mainloop() 此时的2.0表示索引insert也就是光标在第2行第0列；3.0表示索引end也就是文档结束在第3行第0列\nMarks # Marks（标记）通常是嵌入到 Text 组件文本中的不可见对象。事实上 Marks 是指定字符间的位置，并跟随相应的字符一起移动。\ninsert 和 current 是 Tkinter 预定义的特殊 Marks，它们不能够被删除。\ninsert 用于指定当前插入光标的位置，Tkinter 会在该位置绘制一个闪烁的光标（因此并不是所有的 Marks 都不可见）。 current 用于指定与鼠标坐标最接近的位置。不过，如果你紧按鼠标任何一个按钮，它会直到你松开它才响应。 你还可以自定义任意数量的 Marks，Marks 的名字是由普通字符串组成，可以是除了空白字符外的任何字符（为了避免歧义，你应该起一个有意义的名字）。使用 mark_set() 方法创建和移动 Marks。\n如果你在一个 Mark 标记的位置之前插入或删除文本，那么 Mark 跟着一并移动。删除 Marks 你需要使用 mark_unset() 方法，删除 Mark 周围的文本并不会删除 Mark 本身。\nimport tkinter as tk root = tk.Tk() text = tk.Text(root) text.pack() text.insert(\u0026#34;1.0\u0026#34;,\u0026#34;HelloWorld\u0026#34;) text.mark_set(\u0026#34;my_mark\u0026#34;,\u0026#34;1.2\u0026#34;) # 在 e 和 l 中间插入一个 mark，命名为 my_mark text.insert(\u0026#34;my_mark\u0026#34;,\u0026#34;123\u0026#34;) # my_mark的位置插入123 root.mainloop() 默认插入内容到 Mark，是插入到它的左侧（就是说插入一个字符的话，Mark 向后移动了一个字符的位置）。那能不能插入到 Mark 的右侧呢？其实是可以的，通过 mark_gravity() 方法就可以实现。\ntext.mark_gravity(\u0026#34;my_mark\u0026#34;, \u0026#34;left\u0026#34;) #默认是 \u0026#34;right\u0026#34; Tags # gs（标签）通常用于改变 Text 组件中内容的样式和功能。你可以修改文本的字体、尺寸和颜色。另外，Tags 还允许你将文本、嵌入的组件和图片与键盘和鼠标等事件相关联。\nsel # 除了 user-defined tags（用户自定义的 Tags），还有一个预定义的特殊 Tag：sel用于表示对应的选中内容（如果有的话）。\n例如实现获取选中内容：\nimport tkinter as tk root = tk.Tk() text = tk.Text(root) text.pack() lable = tk.Label(root) lable.pack() def get_select(e): w = text.get(text.index(\u0026#39;sel.first\u0026#39;),text.index(\u0026#39;sel.last\u0026#39;)) lable[\u0026#39;text\u0026#39;] = w text.bind(\u0026#39;\u0026lt;ButtonRelease-1\u0026gt;\u0026#39;,get_select) root.mainloop() 自定义Tags # 你可以自定义任意数量的 Tags，Tags 的名字是由普通字符串组成，可以是除了空白字符外的任何字符。另外，任何文本内容都支持多个 Tags 描述，任何 Tag 也可以用于描述多个不同的文本内容。为指定文本添加 Tags 可以使用 tag_add() 方法：\nimport tkinter as tk root = tk.Tk() text = tk.Text(root) text.pack() text.insert(\u0026#39;1.0\u0026#39;,\u0026#39;HelloWorld\u0026#39;) text.tag_add(\u0026#39;my_tag_1\u0026#39;,\u0026#39;1.1\u0026#39;) # 设置my_tag_1到第一行第二个字符 text.tag_config(\u0026#39;my_tag_1\u0026#39;,background=\u0026#39;yellow\u0026#39;,foreground=\u0026#39;red\u0026#39;) # 设置my_tag_1字符背景颜色和字体颜色 text.tag_add(\u0026#39;my_tag_2\u0026#39;,\u0026#39;1.3\u0026#39;,\u0026#39;1.6\u0026#39;) # 设置my_tag_2到第一行的第（四-七）的字符 text.tag_config(\u0026#39;my_tag_2\u0026#39;,background=\u0026#39;black\u0026#39;,foreground=\u0026#39;white\u0026#39;) # 设置my_tag_1字符背景颜色和字体颜色 root.mainloop() 使用 tag_config() 方法可以设置 Tags 的样式，具体参数如下：\n选项 含义 background 1. 指定该 Tag 所描述的内容的背景颜色； 2. 注意：bg 并不是该选项的缩写，在这里 bg 被解释为 bgstipple 选项的缩写 bgstipple 1. 指定一个位图作为背景，并使用 background 选项指定的颜色填充； 2. 只有设置了 background 选项该选项才会生效； 3.默认的标准位图有：\u0026rsquo;error\u0026rsquo;, \u0026lsquo;gray75\u0026rsquo;, \u0026lsquo;gray50\u0026rsquo;, \u0026lsquo;gray25\u0026rsquo;, \u0026lsquo;gray12\u0026rsquo;, \u0026lsquo;hourglass\u0026rsquo;, \u0026lsquo;info\u0026rsquo;, \u0026lsquo;questhead\u0026rsquo;, \u0026lsquo;question\u0026rsquo; 和 \u0026lsquo;warning\u0026rsquo; borderwidth 1. 指定文本框的宽度 ；2. 默认值是 0 ；3. 只有设置了 relief 选项该选项才会生效 ；4. 注意：该选项不能使用 bd 缩写 fgstipple 1. 指定一个位图作为前景色 ；2. 默认的标准位图有：\u0026rsquo;error\u0026rsquo;, \u0026lsquo;gray75\u0026rsquo;, \u0026lsquo;gray50\u0026rsquo;, \u0026lsquo;gray25\u0026rsquo;, \u0026lsquo;gray12\u0026rsquo;, \u0026lsquo;hourglass\u0026rsquo;, \u0026lsquo;info\u0026rsquo;, \u0026lsquo;questhead\u0026rsquo;, \u0026lsquo;question\u0026rsquo; 和 \u0026lsquo;warning\u0026rsquo; font 指定该 Tag 所描述的内容使用的字体 foreground 1. 指定该 Tag 所描述的内容的前景色 ；2. 注意：fg 并不是该选项的缩写，在这里 fg 被解释为 fgstipple 选项的缩写 justify 1. 控制文本的对齐方式 ；2. 默认是 \u0026ldquo;left\u0026rdquo;（左对齐），还可以选择 \u0026ldquo;right\u0026rdquo;（右对齐）和 \u0026ldquo;center\u0026rdquo;（居中） ；3. 注意：需要将 Tag 指向该行的第一个字符，该选项才能生效 lmargin1 1. 设置 Tag 指向的文本块第一行的缩进； 2. 默认值是 0 ；3. 注意：需要将 Tag 指向该文本块的第一个字符或整个文本块，该选项才能生效 lmargin2 1. 设置 Tag 指向的文本块除了第一行其他行的缩进 ；2. 默认值是 0 ；3. 注意：需要将 Tag 指向整个文本块，该选项才能生效 offset 1. 设置 Tag 指向的文本相对于基线的偏移距离；2. 可以控制文本相对于基线是升高（正数值）或者降低（负数值）； 3. 默认值是 0 overstrike 1. 在 Tag 指定的文本范围画一条删除线 ；2. 默认值是 False relief 1. 指定 Tag 对应范围的文本的边框样式 ；2. 可以使用的值有：\u0026ldquo;sunken\u0026rdquo;, \u0026ldquo;raised\u0026rdquo;, \u0026ldquo;groove\u0026rdquo;, \u0026ldquo;rifge\u0026rdquo; 或 \u0026ldquo;flat\u0026rdquo; ；3. 默认值是 \u0026ldquo;flat\u0026rdquo;（没有边框） rmargin 1. 设置 Tag 指向的文本块右侧的缩进； 2. 默认值是 0 spacing1 1. 设置 Tag 所描述的文本块中每一行与上方的空白间隔； 2. 注意：自动换行不算 ；3. 默认值是 0 spacing2 1. 设置 Tag 所描述的文本块中自动换行的各行间的空白间隔 ；2. 注意：换行符（\u0026rsquo;\\n\u0026rsquo;）不算 ；3. 默认值是 0 spacing3 1. 设置 Tag 所描述的文本块中每一行与下方的空白间隔 ； 2. 注意：自动换行不算； 3. 默认值是 0 tabs 1. 定制 Tag 所描述的文本块中 Tab 按键的功能 ；2. 默认 Tab 被定义为 8 个字符的宽度 ；3.你还可以定义多个制表位：tabs=(\u0026lsquo;3c\u0026rsquo;, \u0026lsquo;5c\u0026rsquo;, \u0026lsquo;12c\u0026rsquo;) 表示前 3 个 Tab 宽度分别为 3厘米，5厘米，12厘米，接着的 Tab 按照最后两个的差值计算，即：19厘米，26厘米，33厘米 ；4.你应该注意到了，它上边 \u0026lsquo;c\u0026rsquo; 的含义是“厘米”而不是“字符”，还可以选择的单位有 \u0026lsquo;i\u0026rsquo;（英寸），\u0026rsquo;m\u0026rsquo;（毫米）和 \u0026lsquo;p\u0026rsquo;（DPI，大约是 \u0026lsquo;1i\u0026rsquo; 等于 \u0026lsquo;72p\u0026rsquo;）；5.如果是一个整型值，则单位是像素 underline 1. 该选项设置为 True 的话，则 Tag 所描述的范围内文本将被画上下划线 ；2. 默认值是 False wrap 1. 设置当一行文本的长度超过 width 选项设置的宽度时，是否自动换行； 2. 该选项的值可以是：\u0026ldquo;none\u0026rdquo;（不自动换行），\u0026ldquo;char\u0026rdquo;(默认)（按字符自动换行）和 \u0026ldquo;word\u0026rdquo;（按单词自动换行） Tags 还支持事件绑定，使用的是 tag_bind() 的方法。\n下边例子中我们将文本（\u0026ldquo;Python.com\u0026rdquo;）与鼠标事件进行绑定，当鼠标进入该文本段的时候，鼠标样式切换为 \u0026ldquo;arrow\u0026rdquo; 形态，离开文本段的时候切换回 \u0026ldquo;xterm\u0026rdquo; 形态。当触发鼠标“左键点击操作”事件的时候，使用默认浏览器打开Python的首页（https://www.python.org/）：\nimport tkinter as tk import webbrowser root = tk.Tk() text = tk.Text(root, width=40, height=5) text.pack() text.insert(\u0026#34;insert\u0026#34;, \u0026#34;I love Python.com!\u0026#34;) text.tag_add(\u0026#34;link\u0026#34;, \u0026#34;1.7\u0026#34;, \u0026#34;1.17\u0026#34;) text.tag_config(\u0026#34;link\u0026#34;, foreground = \u0026#34;blue\u0026#34;, underline = True) def show_arrow_cursor(event): text.config(cursor = \u0026#34;arrow\u0026#34;) def show_xterm_cursor(event): text.config(cursor = \u0026#34;xterm\u0026#34;) def click(event): webbrowser.open(\u0026#34;https://www.python.org/\u0026#34;) text.tag_bind(\u0026#34;link\u0026#34;, \u0026#34;\u0026lt;Enter\u0026gt;\u0026#34;, show_arrow_cursor) text.tag_bind(\u0026#34;link\u0026#34;, \u0026#34;\u0026lt;Leave\u0026gt;\u0026#34;, show_xterm_cursor) text.tag_bind(\u0026#34;link\u0026#34;, \u0026#34;\u0026lt;Button-1\u0026gt;\u0026#34;, click) root.mainloop() ","date":"2025-04-15","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/a879fe06/fef801bd/","section":"文章","summary":"\u003cp\u003eText 组件用于显示文本文档，包含纯文本或格式化文本（使用不同字体，嵌入图片，显示链接，甚至是带 CSS 格式的 HTML 等）。因此，它常常也被用于作为简单的文本编辑器和网页浏览器使用。\u003c/p\u003e","title":"7、文本Text","type":"posts"},{"content":" 结构体 # 在 C# 中，结构体是值类型数据结构，这样使得一个单一变量可以存储各种数据类型的相关数据。\n结构体是用来代表一个记录，假设您想跟踪图书馆中书的动态，您可能想跟踪每本书的以下属性：\nTitle Author Subject Book ID using System; using System.Text; struct Books { public string title; public string author; public string subject; public int book_id; }; public class testStructure { public static void Main(string[] args) { Books Book1; /* 声明 Book1，类型为 Books */ Books Book2; /* 声明 Book2，类型为 Books */ /* book 1 详述 */ Book1.title = \u0026#34;C Programming\u0026#34;; Book1.author = \u0026#34;Nuha Ali\u0026#34;; Book1.subject = \u0026#34;C Programming Tutorial\u0026#34;; Book1.book_id = 6495407; /* book 2 详述 */ Book2.title = \u0026#34;Telecom Billing\u0026#34;; Book2.author = \u0026#34;Zara Ali\u0026#34;; Book2.subject = \u0026#34;Telecom Billing Tutorial\u0026#34;; Book2.book_id = 6495700; /* 打印 Book1 信息 */ Console.WriteLine( \u0026#34;Book 1 title : {0}\u0026#34;, Book1.title); Console.WriteLine(\u0026#34;Book 1 author : {0}\u0026#34;, Book1.author); Console.WriteLine(\u0026#34;Book 1 subject : {0}\u0026#34;, Book1.subject); Console.WriteLine(\u0026#34;Book 1 book_id :{0}\u0026#34;, Book1.book_id); /* 打印 Book2 信息 */ Console.WriteLine(\u0026#34;Book 2 title : {0}\u0026#34;, Book2.title); Console.WriteLine(\u0026#34;Book 2 author : {0}\u0026#34;, Book2.author); Console.WriteLine(\u0026#34;Book 2 subject : {0}\u0026#34;, Book2.subject); Console.WriteLine(\u0026#34;Book 2 book_id : {0}\u0026#34;, Book2.book_id); Console.ReadKey(); } } 结构可带有方法、字段、索引、属性、运算符方法和事件，适用于表示轻量级数据的情况，如坐标、范围、日期、时间等。 结构可定义构造函数，但不能定义析构函数。但是，您不能为结构定义无参构造函数。无参构造函数(默认)是自动定义的，且不能被改变。 与类不同，结构不能继承其他的结构或类。 结构不能作为其他结构或类的基础结构。 结构可实现一个或多个接口。 结构成员不能指定为 abstract、virtual 或 protected。 当您使用 New 操作符创建一个结构对象时，会调用适当的构造函数来创建结构。与类不同，结构可以不使用 New 操作符即可被实例化。 如果不使用 New 操作符，只有在所有的字段都被初始化之后，字段才被赋值，对象才被使用。 结构变量通常分配在栈上，这使得它们的创建和销毁速度更快。但是，如果将结构用作类的字段，且这个类是引用类型，那么结构将存储在堆上。 结构默认情况下是可变的，这意味着你可以修改它们的字段。但是，如果结构定义为只读，那么它的字段将是不可变的。 类和结构体的区别 # 类和结构在设计和使用时有不同的考虑因素，类适合表示复杂的对象和行为，支持继承和多态性，而结构则更适合表示轻量级数据和值类型，以提高性能并避免引用的管理开销。\n类和结构有以下几个基本的不同点：\n值类型 vs 引用类型：\n结构是值类型（Value Type）： 结构是值类型，它们在栈上分配内存，而不是在堆上。当将结构实例传递给方法或赋值给另一个变量时，将复制整个结构的内容。 类是引用类型（Reference Type）： 类是引用类型，它们在堆上分配内存。当将类实例传递给方法或赋值给另一个变量时，实际上是传递引用（内存地址）而不是整个对象的副本。 继承和多态性：\n结构不能继承： 结构不能继承其他结构或类，也不能作为其他结构或类的基类。 类支持继承： 类支持继承和多态性，可以通过派生新类来扩展现有类的功能。 默认构造函数：\n结构不能有无参数的构造函数： 结构不能包含无参数的构造函数。每个结构都必须有至少一个有参数的构造函数。 类可以有无参数的构造函数： 类可以包含无参数的构造函数，如果没有提供构造函数，系统会提供默认的无参数构造函数。 赋值行为：\n类型为类的变量在赋值时存储的是引用，因此两个变量指向同一个对象。 结构变量在赋值时会复制整个结构，因此每个变量都有自己的独立副本。 传递方式：\n类型为类的对象在方法调用时通过引用传递，这意味着在方法中对对象所做的更改会影响到原始对象。 结构对象通常通过值传递，这意味着传递的是结构的副本，而不是原始结构对象本身。因此，在方法中对结构所做的更改不会影响到原始对象。 可空性：\n**结构体是值类型，不能直接设置为null：**因为 null 是引用类型的默认值，而不是值类型的默认值。如果你需要表示结构体变量的缺失或无效状态，可以使用 Nullable 或称为 T? 的可空类型。 类默认可为null： 类的实例默认可以为 null，因为它们是引用类型。 性能和内存分配：\n结构通常更轻量： 由于结构是值类型且在栈上分配内存，它们通常比类更轻量，适用于简单的数据表示。 类可能有更多开销： 由于类是引用类型，可能涉及更多的内存开销和管理。 ","date":"2024-09-11","externalUrl":null,"permalink":"/posts/5ab2821f/047d8aa1/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e结构体 \n    \u003cdiv id=\"结构体\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%bb%93%e6%9e%84%e4%bd%93\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在 C# 中，结构体是值类型数据结构，这样使得一个单一变量可以存储各种数据类型的相关数据。\u003c/p\u003e","title":"7、结构体","type":"posts"},{"content":"引擎中所有不拥有的 model 的渲染对象都为 2D 渲染对象。与 3D 对象不同，2D 对象本身不拥有 model 信息，其顶点信息是由 UITransform 组件的 Rect 信息持有并由引擎创建的，且本身没有厚度。由于引擎的设计要求，2D 渲染对象需要为 RenderRoot 节点（带有 RenderRoot2D 组件的节点）的子节点才能完成数据的收集操作。\n所以 2D 渲染对象的渲染要求有两点：\n自身带有 UITransform 组件 需要为带有 RenderRoot2D/Canvas 组件节点的子节点（也就是说，2D节点必须以RenderRoot2D/Canvas节点为Parent） 2D对象跟随3D对象 # 例如，常见的英雄联盟，人物是3D对象，而头上的血条是2D对象，这就需要让2D的血条可以跟随3D的人物移动。\nPlayer：人物根节点 RootNode：人物节点 BloodLabelParent：血条节点 BloodLabel：血条Label节点 要让BloodLabel跟随玩家Player移动，代码如下\n// 血条控制类 @ccclass(\u0026#39;BloodController\u0026#39;) export class BloodController extends Component { // 3D场景的主摄像机节点 @property(Node) private mainCamera: Node = null; @property(Node) private bloodLabelParent: Node = null; @property(Node) private bloodLabel: Node = null; start() { } update(deltaTime: number) { this.bloodLabelFollowPlayer(); } /** * 主角血量跟随主角移动 */ private bloodLabelFollowPlayer(){ // 该脚本组件挂载在Player节点上面，获取玩家的世界坐标 var pos = this.node.getWorldPosition() // Label在Player的头顶，所以增加了y轴的偏移量 Vec3.add(pos,pos,new Vec3(0,2.5,0)) // 接收坐标 var out = new Vec3(0,0,0) // 将三维坐标转换成bloodLabelParent所在二维场景的坐标 this.mainCamera.getComponent(CameraComponent).convertToUINode(pos,this.bloodLabelParent,out) // 设置坐标 this.bloodLabel.position = out } } 注意：使用convertToUINode的第二个参数，要使用最终Label的父节点，否则可能会闪。\n","date":"2024-09-06","externalUrl":null,"permalink":"/posts/69064821/92082869/73fc9d3e/","section":"文章","summary":"\u003cp\u003e引擎中所有不拥有的 model 的渲染对象都为 2D 渲染对象。与 3D 对象不同，2D 对象本身不拥有 model 信息，其顶点信息是由 UITransform 组件的 Rect 信息持有并由引擎创建的，且本身没有厚度。由于引擎的设计要求，2D 渲染对象需要为 RenderRoot 节点（带有 RenderRoot2D 组件的节点）的子节点才能完成数据的收集操作。\u003c/p\u003e","title":"7、2D对象","type":"posts"},{"content":" 事务 # 事务在逻辑上是一组操作，要么执行，要不都不执行。主要是针对数据库而言的，比如说 MySQL。事务是恢复和并发控制的基本单位。\n事务的特性 # 原子性（Atomicity）：事务是一个原子操作，由一系列动作组成。事务的原子性确保动作要么全部完成，要么完全不执行。\n一致性（Consistency）：一旦事务完成（不管成功还是失败），系统必须确保它所建模的业务处于一致的状态，而不会是部分完成部分失败。在现实中的数据不应该被破坏。\n比如转账：A用户和B用户的钱合计是800，在使用事务的前提下，A和B互相转账，不管最终转账多少次，A用户和B用户的钱合计只能是800。这就是事务的一致性。 隔离性（Isolation）：可能有许多事务会同时处理相同的数据，因此每个事务都应该与其他事务隔离开来，防止数据损坏。\n对于任意两个并发的事务T1和T2，在事务T1看来，T2要么在T1开始之前就已经结束，要么在T1结束之后才开始，这样每个事务都感觉不到有其他事务在并发地执行。 持久性（Durability）：一旦事务完成，无论发生什么系统错误，它的结果都不应该受到影响，这样就能从任何系统崩溃中恢复过来。通常情况下，事务的结果被写到持久化存储器中。\nSpring提供的事务处理方案 # 编程式事务管理 # 编程式事务管理是侵入性事务管理，使用TransactionTemplate或者直接使用PlatformTransactionManager，对于编程式事务管理，Spring推荐使用TransactionTemplate。\n声明式事务管理 # 声明式事务管理建立在AOP之上，事务是定义在切面中，Spring的事务默认会在方法执行完后进行提交，在方法执行过程中出现异常会进行回滚，其本质是对方法前后进行拦截，然后在目标方法开始之前创建或者加入一个事务，执行完目标方法之后根据执行的情况提交或者回滚。\n编程式事务每次实现都要单独实现，但业务量大功能复杂时，使用编程式事务无疑是痛苦的，而声明式事务不同，声明式事务属于无侵入式，不会影响业务逻辑的实现，只需要在配置文件中做相关的事务规则声明或者通过注解的方式，便可以将事务规则应用到业务逻辑中。\n显然声明式事务管理要优于编程式事务管理，这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别，而编程式事务管理是可以到代码块的，但是可以通过提取方法的方式完成声明式事务管理的配置。\n使用xml配置文件进行事务控制 # \u0026lt;!--需要开启tx的命名空间--\u0026gt; \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; standalone=\u0026#34;no\u0026#34;?\u0026gt; \u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:aop=\u0026#34;http://www.springframework.org/schema/aop\u0026#34; xmlns:tx=\u0026#34;http://www.springframework.org/schema/tx\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xmlns:context=\u0026#34;http://www.springframework.org/schema/context\u0026#34; xsi:schemaLocation=\u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd\u0026#34;\u0026gt; \u0026lt;!--定义事务管理者实例--\u0026gt; \u0026lt;bean id=\u0026#34;transactionManager\u0026#34; class=\u0026#34;org.springframework.jdbc.datasource.DataSourceTransactionManager\u0026#34;\u0026gt; \u0026lt;!--此处属性为数据源--\u0026gt; \u0026lt;property name=\u0026#34;dataSource\u0026#34; ref=\u0026#34;dataSource\u0026#34; /\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;!--定义了事务的通知，管理者为transactionManager--\u0026gt; \u0026lt;tx:advice id=\u0026#34;ttt\u0026#34; transaction-manager=\u0026#34;transactionManager\u0026#34;\u0026gt; \u0026lt;tx:attributes\u0026gt; \u0026lt;!--REQUIRED：表示该方法需要加事务，NOT_SUPPORTED：表示该方法不许要加事务--\u0026gt; \u0026lt;tx:method name=\u0026#34;addStu\u0026#34; propagation=\u0026#34;REQUIRED\u0026#34; /\u0026gt; \u0026lt;tx:method name=\u0026#34;sel*\u0026#34; propagation=\u0026#34;NOT_SUPPORTED\u0026#34; /\u0026gt; \u0026lt;/tx:attributes\u0026gt; \u0026lt;/tx:advice\u0026gt; \u0026lt;!--定义aop--\u0026gt; \u0026lt;aop:config\u0026gt; \u0026lt;!--定义切入点--\u0026gt; \u0026lt;aop:pointcut id=\u0026#34;aaa\u0026#34; expression=\u0026#34;execution(* com.aaa.biz.*.*(..))\u0026#34; /\u0026gt; \u0026lt;!--切入点关联事务通知--\u0026gt; \u0026lt;aop:advisor pointcut-ref=\u0026#34;aaa\u0026#34; advice-ref=\u0026#34;ttt\u0026#34; /\u0026gt; \u0026lt;/aop:config\u0026gt; \u0026lt;/beans\u0026gt; 使用注解方式进行事务控制 # 1、开启事务注解 # \u0026lt;bean id=\u0026#34;transactionManager\u0026#34; class=\u0026#34;org.springframework.jdbc.datasource.DataSourceTransactionManager\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;dataSource\u0026#34; ref=\u0026#34;dataSource\u0026#34; /\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;tx:annotation-driven transaction-manager=\u0026#34;transactionManager\u0026#34;/\u0026gt; 2、在需要的业务层，或方法上添加事务注解@Transactional() # @Transactional()的属性 # 事务的传播性 # @Transactional(propagation=Propagation.REQUIRED) 事务的隔离级别 # @Transactional(isolation = Isolation.READ_UNCOMMITTED) 只读 # //该属性用于设置当前事务是否为只读事务，设置为true表示只读，false则表示可读写，默认值为false。 @Transactional(readOnly=true) 事务的超时性 # //默认值-1 @Transactional(timeout=30) 回滚 # //指定单一异常类 @Transactional(rollbackFor=RuntimeException.class) //指定多个异常类 @Transactional(rollbackFor={RuntimeException.class, Exception.class}) //指定出现这个异常时，不回滚 @Transactional(noRollbackFor=RuntimeException.class) 事务的传播特性 # 事物的传播，就是在多个支持事务的方法互相调用之间，事务如何在这些方法之间传播\nPropagation枚举类 # org.springframework.transaction.annotation.Propagation\nSpring事务传播的类型的枚举类\n在Spring中对于事务的传播行为定义了七种类型分别是：REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED\n枚举类Propagation是为了结合**@Transactional注解使用而设计的，这个枚举里面定义的事务传播行为类型与TransactionDefinition中定义的事务传播行为类型是对应的，所以在使用@Transactional**注解时我们就要使用Propagation枚举类来指定传播行为类型，而不直接使用TransactionDefinition接口里定义的属性\n事务传播特性 # 注意：Spring中事务的默认实现使用的是AOP，也就是代理的方式，如果大家在使用代码测试时，同一个Service类中的方法相互调用需要使用注入的对象来调用，不要直接使用this.方法名来调用，this.方法名调用是对象内部方法调用，不会通过Spring代理，也就是事务不会起作用\nREQUIRED : 不管怎么样，一定要有事务，通常用于CUD\nSUPPORTS : 有事务，就支持事务；没有事务，就非事务方式执行\n","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/c70ae83e/c6e22db7/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e事务 \n    \u003cdiv id=\"事务\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%ba%8b%e5%8a%a1\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e事务在逻辑上是\u003cstrong\u003e一组操作\u003c/strong\u003e，要么执行，要不都不执行。主要是针对数据库而言的，比如说 MySQL。事务是恢复和并发控制的基本单位。\u003c/p\u003e","title":"7、Transaction","type":"posts"},{"content":" 概述 # 作用：将一段经常使用的代码封装起来，减少重复代码\n一个较大的程序，一般分为若干个程序块，每个模块实现特定的功能。\n函数的定义 # 类似于java的方法定义，只不过没有修饰符\n函数的定义一般主要有5个步骤：\n1、返回值类型\n2、函数名\n3、参数表列\n4、函数体语句\n5、return 表达式\n语法：\n返回值类型/void 函数名 （参数列表）{ 函数体语句 return表达式 } 返回值类型 ：一个函数可以返回一个值或void 函数名：给函数起个名称 参数列表：使用该函数时，传入的数据 函数体语句：花括号内的代码，函数内需要执行的语句 return表达式： 和返回值类型挂钩，函数执行完后，返回相应的数据 函数的定义和调用 # #include\u0026lt;iostream\u0026gt; using namespace std; /* 定义函数 功能：将a和b相加并返回 */ int add(int a,int b) { return a + b; } int main() { //调用函数 cout \u0026lt;\u0026lt; add(1, 2) \u0026lt;\u0026lt; endl; //3 system(\u0026#34;pause\u0026#34;); return 0; } 值传递 # 值传递，就是函数调用时实参将数值传入给形参，和java中不一样，函数传递的是值，而不是引用\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; /* 作用，在a前面添加“你好” */ string strEnable(string a) { a = \u0026#34;你好\u0026#34; + a; return a; } int main() { string name = \u0026#34;lucy\u0026#34;; string msg = strEnable(name); cout \u0026lt;\u0026lt; msg \u0026lt;\u0026lt; endl; // 你好lucy cout \u0026lt;\u0026lt; name \u0026lt;\u0026lt; endl; // lucy，入参的name没有发生改变 system(\u0026#34;pause\u0026#34;); return 0; } 函数的声明 # 告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。\n函数的声明可以多次，但是函数的定义只能有一次\n#include\u0026lt;iostream\u0026gt; using namespace std; int add(int a, int b); // 提前告诉编译器，函数add存在，不会再编译时报错 int add(int a, int b); int main() { cout \u0026lt;\u0026lt; add(1, 5) \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } int add(int a, int b) { return a + b; } 函数分文件编写 # 作用：让代码结构更加清晰\n函数分文件编写一般有4个步骤\n创建后缀名为.h的头文件 创建后缀名为.cpp的源文件 在.h头文件中写函数的声明 在.cpp源文件中写函数的定义 例子：\n1、声明头文件add.h\n#include\u0026lt;iostream\u0026gt; //声明函数 int add(int a,int b); 2、声明源文件add.cpp\n//声明关联哪一个头文件 #include\u0026#34;add.h\u0026#34; //定义函数 int add(int a, int b) { return a + b; } 3、在需要使用函数的源文件中引入\n#include\u0026lt;iostream\u0026gt; #include\u0026#34;add.h\u0026#34; //引入自己定义的头文件 using namespace std; int main() { //使用函数 cout \u0026lt;\u0026lt; add(1, 2) \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 函数的默认参数 # 在C++中，函数的形参列表中的形参是可以有默认值的。\n语法： 返回值类型 函数名 （参数= 默认值）{}\n例子：\n注意：默认参数必须在形参列表结尾\n#include\u0026lt;iostream\u0026gt; using namespace std; void test(int a,int b = 10,int c = 20) { int count = a + b + c; cout \u0026lt;\u0026lt; count \u0026lt;\u0026lt; endl; } int main() { test(30); // 60 test(30, 30); // 80 test(30, 30, 30); // 90 return 0; } 注意：如果函数声明的时候有默认参数，那么实现的时候不可以有默认参数\n#include\u0026lt;iostream\u0026gt; using namespace std; void test(int a, int b = 10, int c = 20); void test(int a, int b, int c) { int count = a + b + c; cout \u0026lt;\u0026lt; count \u0026lt;\u0026lt; endl; } int main() { test(30); // 60 test(30, 30); // 80 test(30, 30, 30); // 90 return 0; } 函数占位参数 # C++中函数的形参列表里可以有占位参数，用来做占位，调用函数时必须填补该位置\n语法： 返回值类型 函数名 (数据类型){}\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; void test(int a, int) { cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; } int main() { test(10, 10); return 0; } 占位参数也可以有默认值\n#include\u0026lt;iostream\u0026gt; using namespace std; void test(int a, int = 10) { cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; } int main() { test(10); return 0; } 函数的重载 # 作用：函数名可以相同，提高复用性\n函数重载满足条件：\n同一个作用域下 函数名称相同 函数参数列表不同：类型不同 或者 个数不同 或者 顺序不同 //函数重载需要函数都在同一个作用域下 void func(){ cout \u0026lt;\u0026lt; \u0026#34;func 的调用！\u0026#34; \u0026lt;\u0026lt; endl; } void func(int a){ cout \u0026lt;\u0026lt; \u0026#34;func (int a) 的调用！\u0026#34; \u0026lt;\u0026lt; endl; } void func(double a){ cout \u0026lt;\u0026lt; \u0026#34;func (double a)的调用！\u0026#34; \u0026lt;\u0026lt; endl; } void func(int a ,double b){ cout \u0026lt;\u0026lt; \u0026#34;func (int a ,double b) 的调用！\u0026#34; \u0026lt;\u0026lt; endl; } void func(double a ,int b){ cout \u0026lt;\u0026lt; \u0026#34;func (double a ,int b)的调用！\u0026#34; \u0026lt;\u0026lt; endl; } int main() { func(); func(10); func(3.14); func(10,3.14); func(3.14 , 10); system(\u0026#34;pause\u0026#34;); return 0; } 函数重载的注意事项 # 1、引用作为函数参数 # #include\u0026lt;iostream\u0026gt; using namespace std; void test(int \u0026amp; a) { cout \u0026lt;\u0026lt; \u0026#34;no-const\u0026#34; \u0026lt;\u0026lt; endl; } void test(const int \u0026amp; b) { cout \u0026lt;\u0026lt; \u0026#34;const\u0026#34; \u0026lt;\u0026lt; endl; } int main() { int a = 10; test(a); // no-const test(10); // const return 0; } 2、函数有默认参数 # #include\u0026lt;iostream\u0026gt; using namespace std; void test(int a) { cout \u0026lt;\u0026lt; \u0026#34;no-default\u0026#34; \u0026lt;\u0026lt; endl; } void test(int a,int b = 10) { cout \u0026lt;\u0026lt; \u0026#34;default\u0026#34; \u0026lt;\u0026lt; endl; } int main() { int a = 10; test(a); // 出现二义性，编译器会报错 return 0; } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/d8245fe1/09e70634/3078886e/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e概述 \n    \u003cdiv id=\"概述\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%a6%82%e8%bf%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e作用\u003c/strong\u003e：将一段经常使用的代码封装起来，减少重复代码\u003c/p\u003e","title":"7、函数","type":"posts"},{"content":" 函数 # 函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数，即主函数 main() ，所有简单的程序都可以定义其他额外的函数。\n您可以把代码划分到不同的函数中。如何划分代码到不同的函数中是由您来决定的，但在逻辑上，划分通常是根据每个函数执行一个特定的任务来进行的。\n函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。\nC 标准库提供了大量的程序可以调用的内置函数。例如，函数 strcat() 用来连接两个字符串，函数 memcpy() 用来复制内存到另一个位置。\n函数还有很多叫法，比如方法、子例程或程序，等等。\n库函数和自定义函数 # C语言在发布时已经为我们封装好了很多函数，它们被分门别类地放到了不同的头文件中（暂时先这样认为），使用函数时引入对应的头文件即可。这些函数都是专家编写的，执行效率极高，并且考虑到了各种边界情况。\nC语言自带的函数称为库函数（Library Function）。库（Library）是编程中的一个基本概念，可以简单地认为它是一系列函数的集合，在磁盘上往往是一个文件夹。C语言自带的库称为标准库（Sdandard Library），其他公司或个人开发的库称为第三方库（Third-Party Library）。\n库函数 # 标准C语言（ANSI C）共定义了15 个头文件，称为“C标准库”，所有的编译器都必须支持，如何正确并熟练的使用这些标准库，可以反映出一个程序员的水平。\n可根据需要查阅C语言函数手册，网址是http://www.cplusplus.com/reference/clibrary/\n合格程序员：\u0026lt;stdio.h\u0026gt;、\u0026lt;ctype.h\u0026gt;、\u0026lt;stdlib.h\u0026gt;、\u0026lt;string.h\u0026gt; 熟练程序员：\u0026lt;assert.h\u0026gt;、\u0026lt;limits.h\u0026gt;、\u0026lt;stddef.h\u0026gt;、\u0026lt;time.h\u0026gt; 优秀程序员：\u0026lt;float.h\u0026gt;、\u0026lt;math.h\u0026gt;、\u0026lt;error.h\u0026gt;、\u0026lt;locale.h\u0026gt;、\u0026lt;setjmp.h\u0026gt;、\u0026lt;signal.h\u0026gt;、\u0026lt;stdarg.h\u0026gt; main函数 # C 语言有两种可能的运行环境中，它们之间有一定差别：\n独立环境（freestanding）：在独立环境中，C 程序的运行没有操作系统的支持，因此，只具有最小部分的标准库能力。 在独立环境中，程序开始时所调用的第一个函数，其类型和名称是由正在运行的 C 语言实现版本所决定的。除非是在嵌入式系统上进行 C 程序开发，否则程序一般都运行在宿主环境中。 宿主环境（hosted）：在宿主环境中，C 程序会在操作系统的控制和支持下运行。可得到完整的标准库能力。 在宿主环境中编译的 C 程序必须定义一个名为 main 的函数，这是程序开始时调用的第一个函数。 main函数一般有三种定义方式\n//这两种定义方式都符合 C 语言标准 int main( void ) { /* … */ } int main( int argc, char *argv[ ] ) { /* … */ } //除此之外，许多 C 的实现版本还支持第三种、非标准语法的定义方式 int main( int argc, char *argv[ ], char *envp[ ] ) { /* … */ } 返回值 # main()函数都会把最终的执行状态以整数的方式传递给操作系统。返回值如果是 0 或 EXIT_SUCCESS，就表示程序执行过程一切顺利；任何非 0 的返回值，尤其是 EXIT_FAILURE，则表示程序执行时出现了某种问题。\n头文件 stdlib.h 中定义了上述的两个常量 EXIT_SUCCESS 和 EXIT_FAILURE。main()函数不一定要有 return 语句。如果程序运行到 main()函数块的右括号（}），那么就会自动向执行环境返回状态值 0。\nmain()函数结束等效于调用标准库函数 exit()，main()的返回值作为 exit()的参数。\n参数 # argc（全称为 argument count）的值为 0 或者为命令行中启动该程序的字符串的数量。程序本身的名称也算作该字符串，也要计算进去。 argv（全称为 arguments vector）是一个 char 指针数组，每个指针都独立的指向命令行中每个字符串： 数组中元素的个数，比 argc 的值多 1；最后一个元素 argv[argc] 是空指针。 如果 argc 大于 0，那么第一个字符串，argv[0]，就是程序本身的名称。如果运行环境不支持程序名称，那么 argv[0] 为空。 如果 argc 大于 1，从字符串 argv[1] 到 argv[argc-1] 包含该程序命令行参数。 envp（全称为 environment pointer）在非标准的、有 3 个参数的 main()函数版本中，是一个指针数组，每个指针都指向组成程序环境的一个字符串。通常，这个字符串的格式是名称=值。在标准 C 语言中，可以利用函数 getenv()获取得这些环境变量。 函数的定义 # return_type function_name( parameter list ){ body of the function } 返回类型：一个函数可以返回一个值。return_type 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值，在这种情况下，return_type 是关键字 void。 函数名称：这是函数的实际名称。函数名和参数列表一起构成了函数签名。 参数：参数就像是占位符。当函数被调用时，您向参数传递一个值，这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的，也就是说，函数可能不包含参数。 函数主体：函数主体包含一组定义函数执行任务的语句。 c语言的函数不可以嵌套定义 函数的声明 # 所谓声明（Declaration），就是告诉编译器我要使用这个函数，你现在没有找到它的定义不要紧，请不要报错，稍后我会把定义补上\nreturn_type function_name( parameter list ); //因为函数的参数名不是必须的，所以下面两种声明方式都可以 void max(int a,int b); void max(int,int); 函数声明给出了函数名、返回值类型、参数列表（重点是参数类型）等与该函数有关的信息，称为函数原型（Function Prototype）。函数原型的作用是告诉编译器与该函数有关的信息，让编译器知道函数的存在，以及存在的形式，即使函数暂时没有定义，编译器也知道如何使用它。\n有了函数声明，函数定义就可以出现在任何地方了，甚至是其他文件、静态链接库、动态链接库等。\n#include\u0026lt;stdio.h\u0026gt; //函数的声明 int max(int,int); int main(){ int num1 = 10; int num2 = 20; //调用函数 printf(\u0026#34;num1、num2最大值为：%d\u0026#34;,max(num1,num2)); } //函数的定义 int max(int a,int b){ return a \u0026gt; b? a : b; } 作用域规则 # 任何一种编程中，作用域是程序中定义的变量所存在的区域，超过该区域变量就不能被访问。C 语言中有三个地方可以声明变量：\n在函数或块内部的局部变量 在所有函数外部的全局变量 在形式参数的函数参数定义中 局部变量 # 在某个函数或块的内部声明的变量称为局部变量。它们只能被该函数或该代码块内部的语句使用。局部变量在函数外部是不可知的。\n#include\u0026lt;stdio.h\u0026gt; int main(){ //局部变量a和b，只可以在main函数中使用 int a = 10,b = 20; printf(\u0026#34;a:%d,b:%d\\n\u0026#34;,a,b); return 0; } 全局变量 # 全局变量是定义在函数外部，通常是在程序的顶部。全局变量在整个程序生命周期内都是有效的，作用域在整个程序，也就是所有的代码文件，包括源文件（.c文件）和头文件（.h文件），任意的函数内部能访问全局变量。\n#include\u0026lt;stdio.h\u0026gt; //局部变量a和b，作用域是整个程序 int a = 10,b = 20; int main(){ printf(\u0026#34;a:%d,b:%d\\n\u0026#34;,a,b); return 0; } 形参 # 函数的参数，形式参数，被当作该函数内的局部变量，它们会优先覆盖全局变量。\n#include\u0026lt;stdio.h\u0026gt; int a = 10; void test(int a){ printf(\u0026#34;%d\u0026#34;,a); } int main(){ test(50); //50 return 0; } 同名参数调用规则 # C语言规定，在同一个作用域中不能出现两个名字相同的变量，否则会产生命名冲突；但是在不同的作用域中，允许出现名字相同的变量，它们的作用范围不同，彼此之间不会产生冲突。\n不同函数内部可以出现同名的变量，不同函数是不同的局部作用域； 函数内部和外部可以出现同名的变量，函数内部是局部作用域，函数外部是全局作用域。 函数调用参数遵循就近原则，也就是形参\u0026gt;全局参数，局部参数\u0026gt;全局参数 参数默认值 # 当局部变量被定义时，系统不会对其初始化，必须自行对其初始化\n定义全局变量时，系统会自动对其初始化\n正确地初始化变量是一个良好的编程习惯，否则有时候程序可能会产生意想不到的结果，因为未初始化的变量会导致一些在内存位置中已经可用的垃圾值。\n全局参数默认值：\n数据类型 初始化默认值 int 0 char '' float 0 double 0 pointer NULL ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/f571508d/fa8095d0/f0c0f947/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e函数 \n    \u003cdiv id=\"函数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%87%bd%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数，即主函数 \u003cstrong\u003emain()\u003c/strong\u003e ，所有简单的程序都可以定义其他额外的函数。\u003c/p\u003e","title":"7、函数和作用域","type":"posts"},{"content":" interface接口 # 接口，实际上可以看做是一种规范\n说明 # 接口使用interface来定义：public interface MyInterface{} Java中，接口和类是并列的两个结构 如何定义接口：定义接口中的成员 JDK7及以前：只能定义全局常量和抽象方法 全局常量：默认是public static final的，但是书写时，可以省略不写 抽象方法：默认是public abstract的，省略不写 JDK8以后：除了定义全局常量和抽象方法之外，还可以定义静态方法（static）、默认方法（default） 静态方法：默认public static，public可以不写，实现类不可重写 默认方法：default，提供基本的方法实现，子类可根据需求选择是否重写 接口中不能定义构造器的！意味着接口不可以实例化 Java开发中，接口通过让类去实现implements的方式来使用 如果实现类覆盖了接口中的所抽象方法，则此实现类就可以实例化 如果实现类没覆盖接口中所的抽象方法，则此实现类仍为一个抽象类 Java类可以实现多个接口，弥补了Java单继承性的局限性 格式：class AA extends BB implements CC,DD,EE 接口与接口之间可以继承，而且可以多继承 接口的具体使用，体现多态性 public interface MyInterface { // 全局常量 String MSG = \u0026#34;hello interface\u0026#34;; // 抽象方法 void method1(); // 静态方法 static void method2(){ System.out.println(MSG); } // 默认方法 default void method3(){ System.out.println(MSG); } } Java8中关于接口的新规范 # 接口中定义的静态方法，只能通过接口来调用。\n通过实现类的对象，可以调用接口中的默认方法。如果实现类重写了接口中的默认方法，调用时，仍然调用的是重写以后的方法\n如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法，那么子类在没重写此方法的情况下，默认调用的是父类中的同名同参数的方法。类优先原则\n如果实现类实现了多个接口，而这多个接口中定义了同名同参数的默认方法，那么在实现类在没重写此方法的情况下会报错。接口冲突。这就需要我们必须在实现类中重写此方法\n如何在子类(或实现类)的方法中调用父类、接口中被重写的方法\npublic void myMethod(){ method3();//调用自己定义的重写的方法 super.method3();//调用的是父类中声明的 //调用接口中的默认方法 CompareA.super.method3(); CompareB.super.method3(); } 接口和抽象类的区别 # 接口中不能写实例属性，但是抽象类中可以写实例属性，如果说在父类中想定义一些实例属性体现所有子类通用的属性，那么只能选择使用抽象类，如果没有上述需求，接口和抽象类都可以，那么优先使用接口，因为接口会更灵活一些。 抽象类中可以写构造方法，接口没有构造器 接口和类之间的关系是实现关系，不一定满足is a的原则，但是抽象类是属于继承体系，需要满足is a的原则 接口和接口之间可以有继承关系，并且是多继承，类和类之间的继承关系是单继承 类的结构五：内部类 # 定义：Java中允许将一个类A声明在另一个类B中，则类A就是内部类，类B称为外部类\n内部类的分类 # 成员内部类（静态、非静态） # 一方面，作为外部类的成员：\n调用外部类的结构\n可以被static修饰\n可以被4种不同的权限修饰\n另一方面，作为一个类：\n内可以定义属性、方法、构造器等\n可以被final修饰，表示此类不能被继承。言外之意，不使用final，就可以被继承\n可以被abstract修饰\n非静态 # 可以访问外部类所有非静态成员和静态成员，不受权限修饰符影响\npublic class OuterClass { // 可以使用权限修饰符 private class InerClass{ } } // 实例化，由于属于外部类的非静态类成员，所以需要外部类的实例 OuterClass outerClass = new OuterClass(); InerClass inerClass = outerClass.new InerClass(); 静态 # 只能访问外部类的静态成员，不受权限修饰符影响\npublic class OuterClass { private static class InerClass{ } } // 实例化，属于外部类的静态成员，所以需要类名.来访问 OuterClass.InerClass inerClass = new OuterClass.InerClass(); 局部内部类(方法内、代码块内、构造器内) # 可以直接访问其所在的外部类的所有非静态的成员和静态的成员，不受权限修饰符影响\npublic class OuterClass { public void test(){ // 不可以使用权限修饰符 class InnerClass{ } // 实例化 InnerClass innerClass = new InnerClass(); } } 匿名内部类 # 匿名内部类是成员内部类的一种，只是一种对接口（或抽象类）实现的形式\npublic interface MyInterface { void func1(); void func2(); } public class OuterClass { public static void test(MyInterface i){ } public static void main(String[] args) { // 匿名内部类实现接口 test(new MyInterface(){ @Override public void func1() { } @Override public void func2() { } }); } } 字节码文件的区别 # 成员内部类和局部内部类，在编译以后，都会生成字节码文件。格式：\n成员内部类：外部类$内部类名.class\n局部内部类：外部类$数字编码 内部类名.class\n匿名内部类：外部类$数字编码.class 作用 # 可以无条件地访问外部类的所有成员 隐藏程序实现细节 可以实现多重继承（并不是一个内部类可以继承多个），可以声明多个内部类分别继承不同的父类 对于简单的接口实现进行优化 关键字：native # 使用native关键字说明这个方法是原生函数，也就是这个方法是用 C/C++等非 Java 语言实现的，并且被编译成了动态链接库DLL，由java去调用\n为什么要用native方法 # java使用起来非常方便，然而有些层次的任务用 java 实现起来不容易，或者我们对程序的效率很在意时，问题就来了\n例如：**有时java应用需要与java外面的环境交互。**这是本地方法存在的主要原因，你可以想想java需要与一些底层系统如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制： 它为我们提供了一个非常简洁的接口，而且我们无需去了解java应用之外的繁琐的细节\nnative声明的方法，对于调用者，可以当做和其他Java方法一样使用\n一个native method方法可以返回任何java类型，包括非基本类型，而且同样可以进行异常控制\nnative method的存在并不会对其他类调用这些本地方法产生任何影响，实际上调用这些方法的其他类甚至不知道它所调用的是一个本地方法。JVM将控制调用本地方法的所有细节\n如果一个含有本地方法的类被继承，子类会继承这个本地方法并且可以用java语言重写这个方法（如果需要的话）\n简单使用 # 1、编写java程序 # public class JniDemo { // native方法和abstract修饰的方法一样，只有签名 public static native void test(); static { // 加载JniDemo.dll,不写文件的后缀，程序会自动加上.dll System.loadLibrary(\u0026#34;JniDemo\u0026#34;); } public static void main(String[] args) { // 调用方法 test(); } } 2、编译并生成头文件 # # 编译，产生JniDemo.class javac -encoding utf-8 JniDemo.java #生成.h头文件 javah -jni JniDemo 打开生成的头文件JniDemo.h\nJniDemo.java文件中的test()方法已经变成了JNIEXPORT void JNICALL Java_JniDemo_test(JNIEnv *, jclass);，方法名是原来的包名_类名_方法名 。\n/* DO NOT EDIT THIS FILE - it is machine generated */ #include \u0026lt;jni.h\u0026gt; /* Header for class JniDemo */ #ifndef _Included_JniDemo #define _Included_JniDemo #ifdef __cplusplus extern \u0026#34;C\u0026#34; { #endif /* * Class: JniDemo * Method: test * Signature: ()V */ JNIEXPORT void JNICALL Java_JniDemo_test (JNIEnv *, jclass); #ifdef __cplusplus } #endif #endif 3、编写cpp文件 # #include \u0026lt;stdio.h\u0026gt; #include \u0026#34;JniDemo.h\u0026#34; JNIEXPORT void JNICALL Java_JniDemo_test (JNIEnv *, jclass){ printf(\u0026#34;Hello Jni From Cpp!\u0026#34;); } 4、编译生成动态链接库 # g++ -m64 -I\u0026#34;E:/Java/jdk1.8.0_301/include\u0026#34; -I\u0026#34;E:/Java/jdk1.8.0_301/include/win32\u0026#34; -shared -o JniDemo.dll JniDemo.cpp 5、运行Java程序 # java JniDemo Hello Jni From Cpp! ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/f742d77f/ee390caf/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003einterface接口 \n    \u003cdiv id=\"interface接口\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#interface%e6%8e%a5%e5%8f%a3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e接口，实际上可以看做是一种规范\u003c/strong\u003e\u003c/p\u003e","title":"7、面向对象编程（下）","type":"posts"},{"content":"SpringBoot项目启动时，通过命令行，传入运行参数的方法\n方法一 # //命令行 java -jar xx-1.1.2.RELEASE.jar aaa bbb cccc //通过main方法的参数获取 System.out.println(\u0026#34;java -jar xxx.jar aaa bbb cccc 方式传参：\u0026#34; + args[0] + \u0026#34;,\u0026#34; + args[1] + \u0026#34;,\u0026#34; + args[2]); 方法二 # //命令行 java -jar xx-1.1.2.RELEASE.jar --a1=aaa --b1=bbb --c1=ccc 或者 java -a1=aaa -b1=bbb -c1=ccc -jar xx-1.1.2.RELEASE.jar //springboot的写法，都可以通过@Value(\u0026#34;${a1}\u0026#34;） 获取 @Value(\u0026#34;${a1}\u0026#34;) private String a1; @Value(\u0026#34;${b1}\u0026#34;) private String b1; @Value(\u0026#34;${c1}\u0026#34;) private String c1; 方法三 # //命令行： java -jar xx-1.1.2.RELEASE.jar -Da1=aaa -Db1=bbb -Dc1=ccc //（参数放到后面System.getProperty(\u0026#34;aaa\u0026#34;,\u0026#34;1\u0026#34;); 方式获取不到） //正确写法： java -Da1=aaa -Db1=bbb -Dc1=ccc -jar xx-1.1.2.RELEASE.jar //获取方式： System.out.println(\u0026#34;java -Da1=aaa -Db1=bbb -Dc1=ccc -jar xxx.jar方式传参：\u0026#34;+ System.getProperty(\u0026#34;a1\u0026#34;)+\u0026#34;,\u0026#34;+System.getProperty(\u0026#34;b1\u0026#34;)+\u0026#34;,\u0026#34;+System.getProperty(\u0026#34;c1\u0026#34;)); ","date":"2024-01-04","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/4f5b7444/fad3d409/","section":"文章","summary":"\u003cp\u003eSpringBoot项目启动时，通过命令行，传入运行参数的方法\u003c/p\u003e","title":"7、命令行参数","type":"posts"},{"content":" FreeMarker # FreeMarker 是一款免费的模板引擎： 即一种基于模板和要改变的数据， 并用来生成输出文本(HTML网页，电子邮件，配置文件，源代码等)的通用工具。 它不是面向最终用户的，而是一个Java类库，是一款程序员可以嵌入他们所开发产品的组件，也可以通过模板来生成Word、pdf、Excel\n依赖 # 普通项目\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.freemarker\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;freemarker\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.3.23\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; SpringBoot项目\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-freemarker\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 语法 # 一般FreeMarker的模板文件后缀名为：.ftl，模板文件中有四种元素：\n文本，直接输出的部分 注释，即\u0026lt;#-- … --\u0026gt;格式不会输出 插值（Interpolation）：即${…}部分，将使用数据模型中的部分替代输出 FTL指令：FreeMarker指令，和HTML标记类似，名字前加#予以区分，不会输出 \u0026lt;#-- 注释 --\u0026gt; \u0026lt;body\u0026gt;${name}\u0026lt;/body\u0026gt; 常见指令 # assign # assign指令用于在页面上定义一个变量\n\u0026lt;#-- 定义变量 --\u0026gt; \u0026lt;#assign linkman=\u0026#34;周先生\u0026#34;\u0026gt; \u0026lt;#-- 使用变量 --\u0026gt; 联系人：${linkman} \u0026lt;#-- 定义变量 --\u0026gt; \u0026lt;#assign info={\u0026#34;mobile\u0026#34;:\u0026#34;13812345678\u0026#34;,\u0026#39;address\u0026#39;:\u0026#39;北京市昌平区\u0026#39;} \u0026gt; \u0026lt;#-- 使用变量 --\u0026gt; 电话：${info.mobile} 地址：${info.address} include # 模板嵌套，在指定位置引入指定模板\n\u0026lt;#include \u0026#34;head.ftl\u0026#34;/\u0026gt; if # 在模板文件中使用if指令进行判断\n\u0026lt;#if isSuccess=true\u0026gt; 你已通过实名认证 \u0026lt;#else\u0026gt; 你未通过实名认证 \u0026lt;/#if\u0026gt; list # 在模板文件中使用list指令进行遍历\n\u0026lt;#list goodsList as goods\u0026gt; 第${goods?index}个商品，商品名称：${goods.name}，价格：${goods.price}\u0026lt;br\u0026gt; \u0026lt;/#list\u0026gt; 特殊数据类型 # null # Freemarker不支持null值，会报错，可以对空值进行处理\n\u0026lt;#--不存在则什么都不显示--\u0026gt; ${abc!} \u0026lt;#--如果为空，则显示没有字符--\u0026gt; ${abc!\u0026#39;没有字符\u0026#39;} boolean # ${flag?string(\u0026#34;Yes\u0026#34;,\u0026#34;No\u0026#34;)} Date # \u0026lt;#--格式化--\u0026gt; ${date?string(\u0026#34;yyyy-MM-dd HH:mm:ss\u0026#34;)} \u0026lt;#--格式化，为null则不显示--\u0026gt; ${date?string(\u0026#34;yyyy-MM-dd HH:mm:ss\u0026#34;)!} 使用 # 生成Word # 将 word 中需要填充的数据用占位符${变量名}替换。 将该 word 另存为.xml的格式，并检查看格式是否有误（主要看占位符有没被分割开来）。 将后缀.xml改成.ftl后（可以不改） 再调用相关 API 即可生成 word 文档。 public static void generateWord(Map\u0026lt;String,Object\u0026gt; map) throws Exception { // 设置FreeMarker的版本和编码格式 Configuration configuration = new Configuration(Configuration.getVersion()); configuration.setDefaultEncoding(\u0026#34;UTF-8\u0026#34;); // 设置FreeMarker生成Word文档所需要的模板的 绝对路径 // configuration.setDirectoryForTemplateLoading(new File(\u0026#34;/Users/yanggang/Desktop/\u0026#34;)); // 设置FreeMarker生成Word文档所需要的模板 ClassPath下 configuration.setClassForTemplateLoading(XsReportGenerator.class, \u0026#34;/templates\u0026#34;); // 设置FreeMarker生成Word文档所需要的模板 Template tem = configuration.getTemplate(\u0026#34;模板.xml\u0026#34;, \u0026#34;UTF-8\u0026#34;); // 创建一个Word文档的输出流，生成到本地 Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(\u0026#34;/Users/yanggang/Desktop/生成结果.docx\u0026#34;)), StandardCharsets.UTF_8)); tem.process(map, out); out.flush(); out.close(); } 占位符被分割的问题 # 在我们编辑好Word模板，转成xml时，有的占位符会被分割，那么我们可以使用如下的程序对xml模板文件进行整理\n注意：使用下面程序整理xml模板文件时，不要格式化\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;cn.hutool\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;hutool-all\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;5.7.11\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; package top.ygang; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.file.FileAppender; import cn.hutool.core.io.file.FileReader; import java.util.List; /** * Author: yanggang * Date: 2023-12-11 16:41:51 */ public class Main { public static void main(String[] args) { //文件读取-FileReader //默认UTF-8编码，可以在构造中传入第二个参数作为编码 FileReader fileReader = new FileReader(\u0026#34;/Users/yanggang/Desktop/模板.xml\u0026#34;); //从文件中读取每一行数据 List\u0026lt;String\u0026gt; strings = fileReader.readLines(); //文件追加-FileAppender //destFile – 目标文件 //capacity – 当行数积累多少条时刷入到文件 //isNewLineMode – 追加内容是否为新行 FileAppender appender = new FileAppender(FileUtil.newFile(\u0026#34;/Users/yanggang/Desktop/模板new.xml\u0026#34;), 16, true); //遍历得到每一行数据 for (String string : strings) { //判断每一行数据中不包含\u0026#39;$\u0026#39;的数据先添加进新文件 if (!string.contains(\u0026#34;$\u0026#34;)) { appender.append(string); continue; } //如果一行数据中包含\u0026#39;$\u0026#39;变量符将替换为\u0026#39;#$\u0026#39; string = string.replaceAll(\u0026#34;\\\\$\u0026#34;, \u0026#34;#\\\\$\u0026#34;); //然后以\u0026#39;#\u0026#39;切割成每一行（数组）,这样一来\u0026#39;$\u0026#39;都将在每一行的开头 String[] ss = string.split(\u0026#34;#\u0026#34;); // 同一行的数据写到同一行,文件追加自动换行了(最后的完整数据) StringBuilder sb = new StringBuilder(); //遍历每一行（数组ss） for (int i = 0; i \u0026lt; ss.length; i++) { //暂存数据 String s1 = ss[i]; //将不是以\u0026#39;$\u0026#39;开头的行数据放进StringBuilder if (!s1.startsWith(\u0026#34;$\u0026#34;)) { sb.append(s1); continue; } //被分离的数据一般都是\u0026#39;${\u0026#39;这样被分开 //匹配以\u0026#39;$\u0026#39;开头的变量找到\u0026#39;}\u0026#39; 得到索引位置 int i1 = s1.lastIndexOf(\u0026#34;}\u0026#34;); //先切割得到这个完整体 String substr = s1.substring(0, i1 + 1); //把变量追加到StringBuilder sb.append(substr.replaceAll(\u0026#34;\u0026lt;[^\u0026gt;]+\u0026gt;\u0026#34;, \u0026#34;\u0026#34;)); //再将标签数据追加到StringBuilder包裹变量 sb.append(s1.substring(i1 + 1)); } appender.append(sb.toString()); } appender.flush(); appender.toString(); } } ","date":"2023-12-22","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/0f3856f3/1db79909/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eFreeMarker \n    \u003cdiv id=\"freemarker\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#freemarker\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eFreeMarker 是一款免费的模板引擎： 即一种基于模板和要改变的数据， 并用来生成输出文本(HTML网页，电子邮件，配置文件，源代码等)的通用工具。 它不是面向最终用户的，而是一个Java类库，是一款程序员可以嵌入他们所开发产品的组件，也可以通过模板来生成Word、pdf、Excel\u003c/p\u003e","title":"7、FreeMarker","type":"posts"},{"content":" Listener # 监听器，是JavaWeb三大组件之一，用于监听JavaWeb程序中的事件，例如创建、修改、删除Session、request、context等，并触发响应事件\n八种监听器 # 不同功能的监听器，需要实现不同的Listener接口，一个监听器也可以实现多个Listener接口，实现不同的功能\n监听器分类 监听器名称 作用 ServletContext监听 ServletContextListener 用于对ServletContext对象进行监听（创建、销毁） ServletContext监听 ServletContextAttributeListener 对ServletContext对象中属性的监听（增删改） Session监听 HttpSessionListener 对Session对象的整体状态的监听（创建、销毁） Session监听 HttpSessionAttributeListener 对Session对象中的属性监听（增删改） Session监听 HttpSessionBindingListener 监听对象于Session的绑定和解除 Session监听 HttpSessionActivationListener 对Session数据的钝化和活化的监听 Request监听 ServletRequestListener 对Request对象进行监听（创建、销毁） Request监听 ServletRequestAttributeListener 对Request对象中属性的监听（增删改） 使用 # 1、实现对应功能的接口 # package top.ygang.listener; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; public class MyListener implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent se) { } @Override public void sessionDestroyed(HttpSessionEvent se) { } } 2、配置Listener # 在web.xml文件中配置 # \u0026lt;listener\u0026gt; \u0026lt;listener-class\u0026gt;top.ygang.listener.MyListener\u0026lt;/listener-class\u0026gt; \u0026lt;/listener\u0026gt; 使用注解 # @WebListener 使用场景 # 监控网站登录人数 # @WebListener public class MyListener implements HttpSessionAttributeListener,HttpSessionListener{ //监控网站新增登录 @Override public void attributeAdded(HttpSessionBindingEvent se) { ServletContext servletContext = se.getSession().getServletContext(); Integer loginCount = (Integer) servletContext.getAttribute(\u0026#34;loginCount\u0026#34;); if (loginCount == null){ servletContext.setAttribute(\u0026#34;loginCount\u0026#34;,1); }else { servletContext.setAttribute(\u0026#34;loginCount\u0026#34;,loginCount + 1); } } //监控网站退出登录 @Override public void sessionDestroyed(HttpSessionEvent se) { ServletContext servletContext = se.getSession().getServletContext(); Integer loginCount = (Integer) servletContext.getAttribute(\u0026#34;loginCount\u0026#34;); servletContext.setAttribute(\u0026#34;loginCount\u0026#34;,loginCount - 1); } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/fb007bea/6bed6c4f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eListener \n    \u003cdiv id=\"listener\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#listener\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e监听器，是JavaWeb三大组件之一，用于监听JavaWeb程序中的事件，例如创建、修改、删除Session、request、context等，并触发响应事件\u003c/p\u003e","title":"7、Listener","type":"posts"},{"content":"Python中的random模块用于生成随机数。\nrandom.random() # 用于生成一个0到1的随机浮点数：0\u0026lt;= n \u0026lt; 1.0\nimport random a = random.random() print(a) \u0026#39;\u0026#39;\u0026#39; 0.8955144155478291 \u0026#39;\u0026#39;\u0026#39; random.uniform(a,b) # 用于生成一个指定范围内的随机浮点数，两个参数其中一个是上限，一个是下限。如果a \u0026gt; b，则生成的随机数n: b \u0026lt;= n \u0026lt;= a。如果 a \u0026lt;b， 则a \u0026lt;= n \u0026lt;= b。\nimport random a = random.uniform(2,9) print(a) \u0026#39;\u0026#39;\u0026#39; 5.810291283396123 \u0026#39;\u0026#39;\u0026#39; random.randint(a, b) # 用于生成一个指定范围内的整数。其中参数a是下限，参数b是上限，生成的随机数n: a \u0026lt;= n \u0026lt;= b\nimport random a = random.randint(2,9) print(a) \u0026#39;\u0026#39;\u0026#39; 5 \u0026#39;\u0026#39;\u0026#39; random.randrange([start], stop[, step]) # 从指定范围内，按指定基数递增的集合中 获取一个随机数。\nrandom.randrange(10, 30, 2)结果相当于从[10, 12, 14, 16, \u0026hellip; 26, 28]序列中获取一个随机数。\nrandom.randrange(10, 30, 2)在结果上与 random.choice(range(10, 30, 2) 等效。\nimport random a = random.randrange(1,10,2) print(a) \u0026#39;\u0026#39;\u0026#39; 5 \u0026#39;\u0026#39;\u0026#39; random.choice(sequence) # random.choice从序列中获取一个随机元素\n参数sequence表示一个有序类型。这里要说明 一下：sequence在python不是一种特定的类型，而是泛指一系列的类型。list, tuple, 字符串都属于sequence。\nimport random a = [1,2,3,4,5,6] #list b = (1,2,3,4,5,6) #tuple e = \u0026#34;123456\u0026#34; print(random.choice(a)) print(random.choice(b)) print(random.choice(e)) \u0026#39;\u0026#39;\u0026#39; 1 3 6 \u0026#39;\u0026#39;\u0026#39; random.shuffle(x[, random]) # 用于将一个列表中的元素打乱,即将列表内的元素随机排列。\nimport random a = [1,2,3,4,5,6] random.shuffle(a) print(a) \u0026#39;\u0026#39;\u0026#39; [6, 3, 1, 2, 4, 5] \u0026#39;\u0026#39;\u0026#39; andom.sample(sequence, k) # 从指定序列中随机获取指定长度的片断并随机排列。注意：sample函数不会修改原有序列。\nimport random a = [1,2,3,4,5,6] print(random.sample(a,4)) \u0026#39;\u0026#39;\u0026#39; [6, 5, 4, 3] \u0026#39;\u0026#39;\u0026#39; ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/1b37041b/9766939c/c4da0ecc/","section":"文章","summary":"\u003cp\u003ePython中的random模块用于生成随机数。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003erandom.random() \n    \u003cdiv id=\"randomrandom\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#randomrandom\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e用于生成一个0到1的随机浮点数：\u003ccode\u003e0\u0026lt;= n \u0026lt; 1.0\u003c/code\u003e\u003c/p\u003e","title":"7、random","type":"posts"},{"content":" 反射的理解 # Reflection（反射）被视为动态语言的关键，反射机制允许程序在**执行期（Runtime）**借助于Reflection API取得任何类的内部信息，并能直接操作任意对象的内部属性及方法。\n框架 = 反射 + 注解 + 设计模式\n反射机制能提供的功能 # 在运行时判断任意一个对象所属的类 在运行时构造任意一个类的对象 在运行时判断任意一个类所具有的成员变量和方法 在运行时获取泛型信息 在运行时调用任意一个对象的成员变量和方法 在运行时处理注解 生成动态代理 Class类的理解 # java.lang.Class\n类的加载及反射过程如下\n程序经过javac.exe命令编译以后，会生成一个或多个字节码文件(.class结尾)。\n接着我们使用java.exe命令对某个字节码文件进行解释运行。字节码文件被ClassLoader加载到JVM内存中，此过程就称为类的加载。加载到内存中的类，我们就称为运行时类（Runtime Class），此运行时类，就作为java.lang.Class的一个实例。\n换句话说，Class的实例只对应着加载到内存中的一个运行时类。\n加载到内存中的运行时类，会缓存一定的时间。在此时间之内，我们可以通过不同的方式来获取此运行时类。\n一个加载的类在 JVM 中只会有一个Class实例\n此时就可以通过Class类的实例方法获取Field、Method、Constructor等实例对象完成反射\n获取Class实例的几种方式 # 方式一：调用运行时类的属性：class\nClass clazz = ClassName.class; 方式二：通过运行时类的对象，调用getClass()方法\nPerson p = new Person(); Class clazz = p.getClass(); 方式三：调用Class的静态方法：forName(String classPath)，可能抛出ClassNotFoundException异常\n全类名：是类所属包名 + . + 类名，例如java.lang.String\nClass clazz = Class.forName(\u0026#34;全类名\u0026#34;); 方式四：使用类的加载器\nClassLoader cl = this.getClass().getClassLoader(); Class clazz = cl.loadClass(\u0026#34;全类名\u0026#34;); 创建类的对象的方式 # 方式一：构造方法，new Constructor()\n方式二：要创建Xxx类的对象，可以考虑：Xxx、Xxxs、XxxFactory、XxxBuilder类中查看是否有静态方法的存在。可以调用其静态方法，创建Xxx对象。\n方式三：通过反射\nClass实例可以是哪些结构的说明 # class：类\ninterface：接口\n[]：数组\nenum：枚举\nannotation：注解@interface\nprimitive type：基本数据类型\nvoid\n数组的Class实例中，只要类型和维度一样，那么两个Class实例相等\nint[] a = new int[10]; int[] b = new int[100]; a.class == b.class //结果为true Class类的常用方法 # static Class forName(String name)：根据全类名返回Class对象 Object newInstance()：调用默认无参构造，创建实例 getName()：获取Class所代表的结构全类名 get Class[] getInterfaces()：返回Class对象实现的所有接口的Class对象 ClassLoader getClassLoader()：返回该类的类加载器 Class getSuperclass()：返回Class对象的超类Class对象 Constuctor[] getConstrctors()：返回类的所有构造方法 Field[] getDeciaredFields()：返回所有属性 Method getMethod(String name,Class ... paramTypes)：返回该类中参数为Class ... paramTypes的方法 Package getPackage()：获取包信息 通过Class实例创建类实例 # Class\u0026lt;Person\u0026gt; clazz = Person.class; Person obj = clazz.newInstance(); newInstance()：调用此方法，创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。\n使用此方法，要求：\n运行时类必须提供空参的构造器 空参的构造器的访问权限足够。通常，设置为public。 没有无参的构造器就不能创建对象了吗？ # **不是！**只要在操作的时候明确的调用类中的构造器，并将参数传递进去之后，才可以实例化操作。\n通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定参数类型的构造器 通过Constructor实例化对象。 static class Apple{ public Apple(int a){ } } public static void main(String[] args) throws Exception { // 获取Class对象 Class\u0026lt;Apple\u0026gt; appleClass = Apple.class; // 根据参数获取有参构造 Constructor\u0026lt;Apple\u0026gt; declaredConstructor = appleClass.getDeclaredConstructor(int.class); // 设置可见性 declaredConstructor.setAccessible(true); // 创建实例 Apple apple = declaredConstructor.newInstance(1); } 获取类结构的方法 # 接口、父类 # Class\u0026lt;?\u0026gt;[] getInterfaces()：获取实现的全部接口 Class\u0026lt;? Super T\u0026gt; getSuperclass()：获取继承的父类 构造器 # Constructor\u0026lt;T\u0026gt;[] getConstructors()：获取有所public构造方法 Constructor\u0026lt;T\u0026gt;[] getDeclaredConstructors()：获取所有构造方法 Constructor类 # int getModifiers()：获取所有修饰符之和，例如public static，返回值为1 + 8 = 9 String getName()：获取方法名称 Class\u0026lt;?\u0026gt;[] getParameterTypes()：获取参数类型 修饰符 返回值 public 1 private 2 protected 4 static 8 final 16 synchronized 32 volatile 64 transient 128 native 256 interface 512 abstract 1024 strict 2048 方法 # Methos[] getDeclaredMethods()：获取所有方法 Methos[] getMethos()：获取所有public方法 Method类 # Class\u0026lt;?\u0026gt; getReturnType()：获取返回值类型对象 Class\u0026lt;?\u0026gt;[] getParameterTypes()：获取所有参数类型对象 int getModifiers()：获取修饰符 Class\u0026lt;?\u0026gt;[] getExceptionTypes()：获取抛出异常类对象 属性 # Field[] getFields()：获取所有public的属性 Field[] getDeclaredFields()：获取所有属性 Field类 # int getModifiers()：获取修饰符 Class\u0026lt;?\u0026gt; getType()：获取属性类型对象 String getName()：获取属性名称 注解 # \u0026lt;A extends Annotation\u0026gt; A getAnnotation(Class\u0026lt;A\u0026gt; annotationClass)：获取指定类型的注解 Annotation[] getAnnotations()：获取所有注解 泛型 # Type getGenericSuperclass()：获取父类泛型类型 static class MyList extends ArrayList\u0026lt;Number\u0026gt; { } public static void main(String[] args) { Class\u0026lt;MyList\u0026gt; myListClass = MyList.class; Class\u0026lt;? super MyList\u0026gt; superclass = myListClass.getSuperclass(); System.out.println(superclass.getName()); // java.util.ArrayList Type genericSuperclass = myListClass.getGenericSuperclass(); System.out.println(genericSuperclass.getTypeName()); // java.util.ArrayList\u0026lt;java.lang.Number\u0026gt; } 所在包 # Package getPackage()：获取所在包 调用类的指定结构 # public class Person{ private String name; public Person(String name){ this.name = name; } public String show(){ System.out.println(name); } } 关于setAccessible方法的使用 # Method、Field、Constructor对象都有setAccessible(boolean flag)方法 此方法可以启动和禁用访问安全检查（权限） 如果参数flag为true，则可以无视private等修饰符进行访问 调用方法 # Method getDeclaredMethod(String name,Class ... parameterTypes)：获取方法 Object invoke(Object obj,Object ... args)：执行方法 此方法的返回值就是调用方法的返回值，如没有返回值则为null 如果方法为非静态方法，则参数obj为null 如果方法权限不足，如private，则需要在调用invoke()之前，显式的调用setAccessible(true)设为可访问 //一、获取类的Class实例、并获得运行时类的实例 Class clazz = Person.class; Person p = (Person) clazz.newInstance(); //二、获取需要调用的方法，getDeclaredMethod(): //参数1 ：指明获取的方法的名称 参数2：指明获取的方法的形参列表 Method show = clazz.getDeclaredMethod(\u0026#34;show\u0026#34;, String.class); //三、保证当前方法可以访问 show.setAccessible(true); //四、调用方法的invoke() //参数1：方法的调用者 参数2：给方法形参赋值的实参 //invoke()的返回值即为对应类中调用的方法的返回值。 Object returnValue = show.invoke(p,\u0026#34;CHN\u0026#34;); 调用属性 # Class clazz = Person.class; //创建运行时类的对象 Person p = (Person) clazz.newInstance(); //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性 Field name = clazz.getDeclaredField(\u0026#34;name\u0026#34;); //2.保证当前属性是可访问的 name.setAccessible(true); //3.设置指定对象的此属性值 name.set(p,\u0026#34;Tom\u0026#34;); //4、获取指定对象的此属性值 System.out.println(name.get(p)); 调用指定的构造器 # Class clazz = Person.class; //1.获取指定的构造器 //getDeclaredConstructor():参数：指明构造器的参数列表 Constructor constructor = clazz.getDeclaredConstructor(String.class); //2.保证此构造器是可访问的 constructor.setAccessible(true); //3.调用此构造器创建运行时类的对象,此时才会进行类加载 Person per = (Person) constructor.newInstance(\u0026#34;Tom\u0026#34;); ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/fbeea4f4/c15c0ffe/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e反射的理解 \n    \u003cdiv id=\"反射的理解\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8f%8d%e5%b0%84%e7%9a%84%e7%90%86%e8%a7%a3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eReflection（反射）被视为动态语言的关键，反射机制允许程序在**执行期（Runtime）**借助于Reflection API取得任何类的内部信息，并能直接操作任意对象的内部属性及方法。\u003c/p\u003e","title":"7、反射机制","type":"posts"},{"content":" 插槽（slot） # 插槽的作用 # 组件的插槽，为了让我们封装的组件更具有扩展性，例如导航栏封装为一个nav-bar组件，但是不同的使用，组件的样式都不一样 可以让使用者决定组件内部的一些内容展示什么（对组件进行扩展） 使用 # 默认插槽（单个组件，一个插槽） # 子组件\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!-- 组件的固有内容 --\u0026gt; \u0026lt;h1\u0026gt;title\u0026lt;/h1\u0026gt; \u0026lt;!-- 声明插槽，默认将组件替换为组件使用者放入组件标签的所有内容--\u0026gt; \u0026lt;slot\u0026gt; \u0026lt;!-- 默认值，组件使用者未添加内容到组件标签中，就会展示默认值 --\u0026gt; \u0026lt;h2\u0026gt;defalut\u0026lt;/h2\u0026gt; \u0026lt;/slot\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;MyComp\u0026#34; } \u0026lt;/script\u0026gt; 父组件\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;MyComp\u0026gt; \u0026lt;!-- 组件标签内内容，将会替换组件的插槽slot标签 --\u0026gt; \u0026lt;h2\u0026gt;Hello World\u0026lt;/h2\u0026gt; \u0026lt;/MyComp\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import MyComp from \u0026#39;./components/MyComp.vue\u0026#39; export default { name: \u0026#39;App\u0026#39;, components: { MyComp } } \u0026lt;/script\u0026gt; 具名插槽（单个组件中，多个插槽） # 子组件\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!-- 组件的固有内容 --\u0026gt; \u0026lt;h1\u0026gt;title\u0026lt;/h1\u0026gt; \u0026lt;!-- 声明插槽1--\u0026gt; \u0026lt;slot name=\u0026#34;slot1\u0026#34;\u0026gt; \u0026lt;h2\u0026gt;defalut1\u0026lt;/h2\u0026gt; \u0026lt;/slot\u0026gt; \u0026lt;!-- 声明插槽2 --\u0026gt; \u0026lt;slot name=\u0026#34;slot2\u0026#34;\u0026gt; \u0026lt;h2\u0026gt;defalut2\u0026lt;/h2\u0026gt; \u0026lt;/slot\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;MyComp\u0026#34; } \u0026lt;/script\u0026gt; 父组件\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;MyComp\u0026gt; \u0026lt;!-- 替换slot1插槽,对于没有替换的插槽,还是使用默认值 --\u0026gt; \u0026lt;h2 slot=\u0026#34;slot1\u0026#34;\u0026gt;Hello World\u0026lt;/h2\u0026gt; \u0026lt;/MyComp\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import MyComp from \u0026#39;./components/MyComp.vue\u0026#39; export default { name: \u0026#39;App\u0026#39;, components: { MyComp } } \u0026lt;/script\u0026gt; 作用域插槽 # 父组件的模板的所有东西都会在父级作用域内编译，子组件的模板的所有东西都会在子级作用域内编译\n作用域插槽的作用：父组件替换插槽的标签，但是数据由子组件提供，也就是样式不同、数据相同\n子组件\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!-- 组件的固有内容 --\u0026gt; \u0026lt;h1\u0026gt;title\u0026lt;/h1\u0026gt; \u0026lt;!-- 声明插槽,并且将msg传给组件的使用者--\u0026gt; \u0026lt;slot :msg=\u0026#34;msg\u0026#34;\u0026gt; \u0026lt;h2\u0026gt;defalut1\u0026lt;/h2\u0026gt; \u0026lt;/slot\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;MyComp\u0026#34;, data() { return { msg: \u0026#39;Hello World\u0026#39;, }; }, } \u0026lt;/script\u0026gt; 父组件\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;MyComp\u0026gt; \u0026lt;!-- 组件的使用者,需要使用slot-scope接收子组件传来的数据--\u0026gt; \u0026lt;h2 slot-scope=\u0026#34;childData\u0026#34;\u0026gt;{{childData.msg}}\u0026lt;/h2\u0026gt; \u0026lt;/MyComp\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import MyComp from \u0026#39;./components/MyComp.vue\u0026#39; export default { name: \u0026#39;App\u0026#39;, components: { MyComp } } \u0026lt;/script\u0026gt; ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/bafd68f1/b2321cb9/ca6ed550/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e插槽（slot） \n    \u003cdiv id=\"插槽slot\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8f%92%e6%a7%bdslot\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch2 class=\"relative group\"\u003e插槽的作用 \n    \u003cdiv id=\"插槽的作用\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8f%92%e6%a7%bd%e7%9a%84%e4%bd%9c%e7%94%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e组件的插槽，为了让我们封装的组件更具有扩展性，例如导航栏封装为一个nav-bar组件，但是不同的使用，组件的样式都不一样\u003c/li\u003e\n\u003cli\u003e可以让使用者决定组件内部的一些内容展示什么（对组件进行扩展）\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e使用 \n    \u003cdiv id=\"使用\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bd%bf%e7%94%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20210803155425481\"\n    data-zoom-src=\"/posts/bafd68f1/b2321cb9/ca6ed550/image/202109181416870.png\"\n    src=\"/posts/bafd68f1/b2321cb9/ca6ed550/image/202109181416870.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"7、插槽","type":"posts"},{"content":" Config # Config叫配置中心，它的作用：帮助整个微服务系统，统一的管理配置文件\n目前，我们分离了很多的微服务，每个微服务都有对应的配置文件。开发环境下，我们每个微服务各自管理自己的配置文件，没有任何问题，但是在测试环境下，生产环境下等等。。。\n解决方案：Spring Cloud提供了一种远程配置中心的方案，例如使用github.com，gitee.com\n建立Config配置中心 # 远程配置中心 # 1、创建一个仓库 # 2、clone远程仓库 # 3、针对不同的微服务，创建不同的目录 # 4、将微服务的配置文件，放入到对应的目录中 # 命名格式为：服务ID-dev/pro/test.yml\n5、上传配置文件到git # 本地配置中心 # 配置中心也是一个微服务、所以需要重新写一个模块\n1、引入依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-config-server\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、编写启动类，添加注解，激活配置中心 # @EnableConfigServer @SpringBootApplication public class ConfigApplication { public static void main(String[] args) { SpringApplication.run(ConfigApplication.class,args); } } 3、编写配置中心微服务配置文件 # 配置文件的名称：bootstrap.yml（启动型配置文件） 当：bootstrap.yml，application.yml，application.properties同时存在时，bootstrap.yml先启动 server: port: 11000 spring: application: name: star-config cloud: config: server: git: #git地址 uri: https://gitee.com/yh-gh/star-config.git #登录GIT用户名,如果访问的git开源，不需要账户、密码 username: #登录GIT密码 password: #启动配置中心时，要求强制获取配置信息 force-pull: true #搜索配置文件的路由，{application}为占位符，搜索全部路径\tsearch-paths: \u0026#39;{application}\u0026#39; 4、启动配置中心微服务，访问配置文件 # 其余微服务集成配置中心，例如商品微服务 # 1、引入配置中心客户端依赖 # \u0026lt;!-- 引入配置服务客户端的依赖启动器 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-config-client\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 2、创建bootstrap.yml配置文件，删除之前的配置文件 # spring: cloud: config: #从git上的master分支获取内容 label: master #开发环境(dev/test/pro) profile: dev #配置中心的uri uri: http://localhost:11000 application: #微服务的名称 name: star-product 3、启动微服务 # 说明读取配置文件成功\n建立Nacos注册、配置中心 # 1、下载、解压、启动nacos服务器，并配置为注册中心 # 2、访问nacos配置中心界面 # 3、点击右边加号，添加配置 # 添加微服务的配置文件，例如clazz-system.yml # 4、微服务中，添加配置中心依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba.cloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-cloud-starter-alibaba-nacos-config\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 4、微服务中，删除之前的application.yml文件，创建bootstrap.yml文件，并编辑 # spring: application: # 配置应用的名称，用于获取配置 name: clazz-system cloud: nacos: discovery: server-addr: http://localhost:8848 config: # 配置nacos配置中心服务的地址 server-addr: http://localhost:8848 # 配置分组 group: clazz-system # 配置文件后缀，用于拼接配置配置文件名称 file-extension: yml # 配置自动刷新 refresh: true # 配置文件的前缀 prefix: clazz-system 5、启动微服务，读取配置文件 # 控制台出现配置信息，证明读取配置文件成功\nNacos动态刷新配置 # 1、在控制层添加注解@RefreshScope # @RestController @RequestMapping(\u0026#34;/student\u0026#34;) @RefreshScope public class StudentController {} 2、在配置中心修改配置文件并发布 # 3、观察控制台，发现配置文件已经实时刷新 # ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/fd17f7dc/81501b42/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eConfig \n    \u003cdiv id=\"config\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#config\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eConfig叫配置中心，它的作用：帮助整个微服务系统，统一的管理配置文件\u003c/p\u003e","title":"7、服务配置中心","type":"posts"},{"content":"自然排序：java.lang.Comparable\n定制排序：java.util.Comparator\nComparable接口 # 重写compareTo(obj)的规则：\n如果当前对象this大于形参对象obj，则返回正整数， 如果当前对象this小于形参对象obj，则返回负整数， 如果当前对象this等于形参对象obj，则返回零。 使用方法 # //必须在需要排序的类中，实现Comparable接口，并且重写方法compareTo public class Goods implements Comparable\u0026lt;Goods\u0026gt;{ private double goodsPrice; @Override public int compareTo(Goods o) { return Double.compare(this.goodsPrice,o.goodsPrice); } } Comparator接口 # 当元素的类型没实现java.lang.Comparable接口而又不方便修改代码，或者实现了java.lang.Comparable接口的排序规则不适合当前的操作，那么可以考虑使用 Comparator 的对象来排序\n重写compare(Object o1,Object o2)规则：\n比较o1和o2的大小： 如果方法返回正整数，则表示o1大于o2； 如果返回0，表示相等； 返回负整数，表示o1小于o2。 使用方法 # //在新的类里面，进行Comparator接口的实现并且重写compare方法 public class GoodsesIdUp implements Comparator\u0026lt;Goods\u0026gt; { @Override public int compare(Goods o1, Goods o2) { return Double.compare(o1.getGoodsPrice(),o2.getGoodsPrice()); } } 两种排序方式对比 # Comparable是比较的意思，而Comparator是比较器的意思； Comparable是通过重写compareTo方法实现排序的，而Comparator是通过重写compare方法实现排序的； Comparable必须由自定义类内部实现排序方法，而Comparator是外部定义并实现排序的。 ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/409de243/4f0fc875/","section":"文章","summary":"\u003cp\u003e自然排序：\u003ccode\u003ejava.lang.Comparable\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e定制排序：\u003ccode\u003ejava.util.Comparator\u003c/code\u003e\u003c/p\u003e","title":"7、比较器","type":"posts"},{"content":" 基本概念 # 计算机为了联网，就必须规定通信协议，早期的计算机网络，都是由各厂商自己规定一套协议，IBM、Apple和Microsoft都有各自的网络协议，互不兼容，这就好比一群人有的说英语，有的说中文，有的说德语，说同一种语言的人可以交流，不同的语言之间就不行了。\n为了把全世界的所有不同类型的计算机都连接起来，就必须规定一套全球通用的协议，为了实现互联网这个目标，互联网协议簇（Internet Protocol Suite）就是通用协议标准。Internet是由inter和net两个单词组合起来的，原意就是连接“网络”的网络，有了Internet，任何私有网络，只要支持这个协议，就可以联入互联网。\n因为互联网协议包含了上百种协议标准，但是最重要的两个协议是TCP和IP协议，所以，大家把互联网的协议简称TCP/IP协议。\n通信的时候，双方必须知道对方的标识，好比发邮件必须知道对方的邮件地址。互联网上每个计算机的唯一标识就是IP地址，类似123.123.123.123。如果一台计算机同时接入到两个或更多的网络，比如路由器，它就会有两个或多个IP地址，所以，IP地址对应的实际上是计算机的网络接口，通常是网卡。\nIP协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块，然后通过IP包发送出去。由于互联网链路复杂，两台计算机之间经常有多条线路，因此，路由器就负责决定如何把一个IP包转发出去。IP包的特点是按块发送，途径多个路由，但不保证能到达，也不保证顺序到达。\nIP地址实际上是一个32位整数（称为IPv4），以字符串表示的IP地址如192.168.0.1实际上是把32位整数按8位分组后的数字表示，目的是便于阅读。\nIPv6地址实际上是一个128位整数，它是目前使用的IPv4的升级版，以字符串表示类似于2001:0db8:85a3:0042:1000:8a2e:0370:7334。\nTCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接，保证数据包按顺序到达。TCP协议会通过握手建立连接，然后，对每个IP包编号，确保对方按顺序收到，如果包丢掉了，就自动重发。\n许多常用的更高级的协议都是建立在TCP协议基础上的，比如用于浏览器的HTTP协议、发送邮件的SMTP协议等。\n一个TCP报文除了包含要传输的数据外，还包含源IP地址和目标IP地址，源端口和目标端口。\n端口有什么作用？在两台计算机通信时，只发IP地址是不够的，因为同一台计算机上跑着多个网络程序。一个TCP报文来了之后，到底是交给浏览器还是QQ，就需要端口号来区分。每个网络程序都向操作系统申请唯一的端口号，这样，两个进程在两台计算机之间建立网络连接就需要各自的IP地址和各自的端口号。\n一个进程也可能同时与多个计算机建立链接，因此它会申请很多端口。\nSocket # python提供了两种Socket：\n第一个是 Socket，它提供了标准的 BSD Sockets API。\n第二个是 SocketServer， 它提供了服务器中心类，可以简化网络服务器的开发。\nSocket类型 # socket类型 描述 socket.AF_UNIX 只能够用于单一的Unix系统进程间通信 socket.AF_INET 服务器之间网络通信 socket.AF_INET6 IPv6 socket.SOCK_STREAM 流式socket , for TCP socket.SOCK_DGRAM 数据报式socket , for UDP socket.SOCK_RAW 原始套接字，普通的套接字无法处理ICMP、IGMP等网络报文，而SOCK_RAW可以；其次，SOCK_RAW也可以处理特殊的IPv4报文；此外，利用原始套接字，可以通过IP_HDRINCL套接字选项由用户构造IP头。 socket.SOCK_SEQPACKET 可靠的连续数据包服务 创建TCP Socket： s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 创建UDP Socket： s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) Socket函数 # 注意点:\n1）TCP发送数据时，已建立好TCP连接，所以不需要指定地址。UDP是面向无连接的，每次发送要指定是发给谁。\n2）服务端与客户端不能直接发送列表，元组，字典。需要字符串化repr(data)。\nsocket函数 描述 服务端socket函数 s.bind(address) 将套接字绑定到地址, 在AF_INET下,以元组（host,port）的形式表示地址，也可用于客户端 s.listen(backlog) 开始监听TCP传入连接。backlog指定在拒绝连接之前，操作系统可以挂起的最大连接数量。该值至少为1，大部分应用程序设为5就可以了。 s.accept() 接受TCP连接并返回（conn,address）,其中conn是新的套接字对象，可以用来接收和发送数据。address是连接客户端的地址。 客户端socket函数 s.connect(address) 连接到address处的套接字。一般address的格式为元组（hostname,port），如果连接出错，返回socket.error错误。 s.connect_ex(adddress) 功能与connect(address)相同，但是成功返回0，失败返回errno的值。 公共socket函数 s.recv(bufsize[,flag]) 接受TCP套接字的数据。数据以字符串形式返回，bufsize指定要接收的最大数据量。flag提供有关消息的其他信息，通常可以忽略。 s.send(string[,flag]) 发送TCP数据。将string中的数据发送到连接的套接字。返回值是要发送的字节数量，该数量可能小于string的字节大小。 s.sendall(string[,flag]) 完整发送TCP数据。将string中的数据发送到连接的套接字，但在返回之前会尝试发送所有数据。成功返回None，失败则抛出异常。 s.recvfrom(bufsize[.flag]) 接受UDP套接字的数据。与recv()类似，但返回值是（data,address）。其中data是包含接收数据的字符串，address是发送数据的套接字地址。 s.sendto(string[,flag],address) 发送UDP数据。将数据发送到套接字，address是形式为（ipaddr，port）的元组，指定远程地址。返回值是发送的字节数。 s.close() 关闭套接字。 s.getpeername() 返回连接套接字的远程地址。返回值通常是元组（ipaddr,port）。 s.getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port) s.setsockopt(level,optname,value) 设置给定套接字选项的值。 s.getsockopt(level,optname[.buflen]) 返回套接字选项的值。 s.settimeout(timeout) 设置套接字操作的超时期，timeout是一个浮点数，单位是秒。值为None表示没有超时期。一般，超时期应该在刚创建套接字时设置，因为它们可能用于连接的操作（如connect()） s.gettimeout() 返回当前超时期的值，单位是秒，如果没有设置超时期，则返回None。 s.fileno() 返回套接字的文件描述符。 s.setblocking(flag) 如果flag为0，则将套接字设为非阻塞模式，否则将套接字设为阻塞模式（默认值）。非阻塞模式下，如果调用recv()没有发现任何数据，或send()调用无法立即发送数据，那么将引起socket.error异常。 s.makefile() 创建一个与该套接字相关连的文件 TCP编程 # Socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络链接”，而打开一个Socket需要知道目标计算机的IP地址和端口号，再指定协议类型即可。\n大多数连接都是可靠的TCP连接。创建TCP连接时，主动发起连接的叫客户端，被动响应连接的叫服务器。\n访问网页服务 # 举个例子，当我们在浏览器中访问百度时，我们自己的计算机就是客户端，浏览器会主动向百度的服务器发起连接。如果一切顺利，百度的服务器接受了我们的连接，一个TCP连接就建立起来的，后面的通信就是发送网页内容了。\n作为服务器，提供什么样的服务，端口号就必须固定下来。由于我们想要访问网页，因此提供网页服务的服务器必须把端口号固定在80端口，因为80端口是Web服务的标准端口。其他服务都有对应的标准端口号，例如SMTP服务是25端口，FTP服务是21端口，等等。端口号小于1024的是Internet标准服务的端口（必须管理员权限才可以用），端口号大于1024的，可以任意使用。\nTCP连接创建的是双向通道，双方都可以同时给对方发数据。但是谁先发谁后发，怎么协调，要根据具体的协议来决定。例如，HTTP协议规定客户端必须先发请求给服务器，服务器收到后才发数据给客户端。\nfrom socket import socket import socket #创建一个基于TCP的socket #AF_INET指定使用IPv4协议，如果要用更先进的IPv6，就指定为AF_INET6。SOCK_STREAM指定使用面向流的TCP协议 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #链接百度服务器,参数是一个元组，(ip,port) s.connect((\u0026#39;www.baidu.com\u0026#39;,80)) #发送数据 s.send(b\u0026#39;GET / HTTP/1.1\\r\\nHost: www.baidu.com\\r\\nConnection: close\\r\\n\\r\\n\u0026#39;) #循环接收数据，每次接收1024字节 buffer = [] while True: b = s.recv(1024) if b: buffer.append(b) else: break data = b\u0026#39;\u0026#39;.join(buffer) #对响应的数据进行处理 #相应的数据中含有响应头和HTML页面 headers,html = data.split(b\u0026#39;\\r\\n\\r\\n\u0026#39;,1) print(headers.decode(\u0026#39;utf8\u0026#39;)) #将html页面保存到文件 with open(\u0026#39;./baidu.html\u0026#39;,\u0026#39;wb\u0026#39;) as f: f.write(html) \u0026#39;\u0026#39;\u0026#39; HTTP/1.1 200 OK Accept-Ranges: bytes Cache-Control: no-cache Content-Length: 9508 Content-Type: text/html Date: Tue, 22 Feb 2022 09:04:23 GMT P3p: CP=\u0026#34; OTI DSP COR IVA OUR IND COM \u0026#34; P3p: CP=\u0026#34; OTI DSP COR IVA OUR IND COM \u0026#34; Pragma: no-cache Server: BWS/1.1 Set-Cookie: BAIDUID=6420A429826E22B0B7F0BFB267607ACD:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com Set-Cookie: BIDUPSID=6420A429826E22B0B7F0BFB267607ACD; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com Set-Cookie: PSTM=1645520663; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com Set-Cookie: BAIDUID=6420A429826E22B07BD8F456F971C92E:FG=1; max-age=31536000; expires=Wed, 22-Feb-23 09:04:23 GMT; domain=.baidu.com; path=/; version=1; comment=bd Traceid: 164552066303888542827146785808572153771 Vary: Accept-Encoding X-Frame-Options: sameorigin X-Ua-Compatible: IE=Edge,chrome=1 Connection: close \u0026#39;\u0026#39;\u0026#39; 客户端和服务器 # 上面的访问baidu，我们就是一个客户端\n服务器 # 绑定监听的地址和端口。服务器可能有多块网卡，可以绑定到某一块网卡的IP地址上，也可以用0.0.0.0绑定到所有的网络地址，还可以用127.0.0.1绑定到本机地址。127.0.0.1是一个特殊的IP地址，表示本机地址，如果绑定到这个地址，客户端必须同时在本机运行才能连接，也就是说，外部的计算机无法连接进来。\n简单例子：\nserver.py，运行后会进入阻塞，等待连接\nimport socket #创建一个基于ip4、TCP的socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #绑定一个ip和端口,参数是一个元组(ip,port) s.bind((\u0026#39;127.0.0.1\u0026#39;,80)) #开始监听端口，参数为支持的最大连接数 s.listen(5) #处理连接，通常使用死循环 while True: #接收新的连接,此方法返回一个元组(socket,(ip,port)) sock,addr = s.accept() print(\u0026#39;socket:\u0026#39;,sock) print(\u0026#39;addr:\u0026#39;,addr) \u0026#39;\u0026#39;\u0026#39; socket: \u0026lt;socket.socket fd=568, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(\u0026#39;127.0.0.1\u0026#39;, 80), raddr=(\u0026#39;127.0.0.1\u0026#39;, 8080)\u0026gt; addr: (\u0026#39;127.0.0.1\u0026#39;, 8080) \u0026#39;\u0026#39;\u0026#39; client.py，运行后会发现server.py打印信息\nimport socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #客户端同样也可以绑定ip和端口 s.bind((\u0026#39;127.0.0.1\u0026#39;,8080)) s.connect((\u0026#39;127.0.0.1\u0026#39;,80)) 模拟聊天 # server.py\nimport socket import threading #创建一个基于ip4、TCP的socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #绑定一个ip和端口,参数是一个元组(ip,port) s.bind((\u0026#39;127.0.0.1\u0026#39;,80)) #开始监听端口，参数为支持的最大连接数 s.listen(5) #用于处理客户端连接的方法 def clientHandler(sock,addr): sock.send(b\u0026#39;Hello!\u0026#39;) while True: b = sock.recv(1024) print(\u0026#39;客户端%s发来消息：%s\u0026#39; % (addr,b.decode(\u0026#39;utf8\u0026#39;))) s = input(\u0026#39;请发送消息给客户端：\u0026#39;) sock.send(s.encode(\u0026#39;utf8\u0026#39;)) #处理连接，通常使用死循环 while True: #接收新的连接,此方法返回一个元组(socket,(ip,port)) sock,addr = s.accept() print(\u0026#39;新的客户端连接，ip：%s，port：%s\u0026#39; % addr) #创建新的线程用来处理客户端TCP连接 threading.Thread(name=addr,target=clientHandler,args=(sock,addr)).start() \u0026#39;\u0026#39;\u0026#39; 新的客户端连接，ip：127.0.0.1，port：8080 客户端(\u0026#39;127.0.0.1\u0026#39;, 8080)发来消息：你好，服务器 请发送消息给客户端：你好，客户端 \u0026#39;\u0026#39;\u0026#39; client.py\nimport socket import threading #创建一个基于ip4、TCP的socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #绑定一个ip和端口,参数是一个元组(ip,port) s.bind((\u0026#39;127.0.0.1\u0026#39;,80)) #开始监听端口，参数为支持的最大连接数 s.listen(5) #用于处理客户端连接的方法 def clientHandler(sock,addr): sock.send(b\u0026#39;Hello!\u0026#39;) while True: b = sock.recv(1024) print(\u0026#39;客户端%s发来消息：%s\u0026#39; % (addr,b.decode(\u0026#39;utf8\u0026#39;))) s = input(\u0026#39;请发送消息给客户端：\u0026#39;) sock.send(s.encode(\u0026#39;utf8\u0026#39;)) #处理连接，通常使用死循环 while True: #接收新的连接,此方法返回一个元组(socket,(ip,port)) sock,addr = s.accept() print(\u0026#39;新的客户端连接，ip：%s，port：%s\u0026#39; % addr) #创建新的线程用来处理客户端TCP连接 threading.Thread(name=addr,target=clientHandler,args=(sock,addr)).start() \u0026#39;\u0026#39;\u0026#39; 服务器发来消息：Hello! 请发送消息给服务器：你好，服务器 服务器发来消息：你好，客户端 请发送消息给服务器：exit \u0026#39;\u0026#39;\u0026#39; ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/1b37041b/a64b122d/9ee4721e/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e基本概念 \n    \u003cdiv id=\"基本概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%9f%ba%e6%9c%ac%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e计算机为了联网，就必须规定通信协议，早期的计算机网络，都是由各厂商自己规定一套协议，IBM、Apple和Microsoft都有各自的网络协议，互不兼容，这就好比一群人有的说英语，有的说中文，有的说德语，说同一种语言的人可以交流，不同的语言之间就不行了。\u003c/p\u003e","title":"7、网络编程","type":"posts"},{"content":" 网络编程 # 网络编程的本质是两个设备之间的数据交换，在计算机网络中，设备主要指计算机。数据传递本身没有多大的难度，不就是把一个设备中的数据发送给另外一个设备，然后接受另外一个设备反馈的数据。\n现在的网络编程基本上都是基于请求/响应方式的，也就是一个设备发送请求数据给另外一个设备，然后接收这个设备的反馈。\n在网络编程中，发起连接程序，也就是发送第一次请求的程序，被称作客户端(Client)，等待其他程序连接的程序被称作服务器(Server)。客户端程序可以在需要的时候启动，而服务器为了能够时刻相应连接，则需要一直启动。\n连接一旦建立以后，就客户端和服务器端就可以进行数据传递了，而且两者的身份是等价的。\n端口 # 为了在一台设备上可以运行多个程序，人为的设计了端口(Port)的概念，类似的例子是公司内部的分机号码。\n每个网络程序，无论是客户端还是服务器端，都对应一个或多个特定的端口号。由于 0-1024 之间多被操作系统占用，所以实际编程时一般采用1024 以后的端口号。\n使用端口号，可以找到一台设备上唯一的一个程序。所以如果需要和某台计算机建立连接的话，只需要知道IP地址或域名即可，但是如果想和该台计算机上的某个程序交换数据的话，还必须知道该程序使用的端口号。\n网络编程分类 # TCP TCP/IP(Transmission Control Protocol/Internet Protocol) 即传输控制协议/网际协议，是一种面向连接（连接导向）的、可靠的、基于字节流的传输层（Transport layer）通信协议，因为是面向连接的协议，数据像水流一样传输，会存在黏包问题。 传统BS架构的HTTP协议就是使用了TCP作为传输层协议 UDP UDP协议（User Datagram Protocol）中文名称是用户数据报协议，是OSI（Open System Interconnection，开放式系统互联）参考模型中一种无连接的传输层协议，不需要建立连接就能直接进行数据发送和接收，属于不可靠的、没有时序的通信，但是UDP协议的实时性比较好，通常用于视频直播相关领域。 Socket编程 # Socket是应用层与TCP/IP协议族通信的中间软件抽象层。在设计模式中，Socket其实就是一个门面模式，它把复杂的TCP/IP协议族隐藏在Socket后面，对用户来说只需要调用Socket规定的相关函数，让Socket去组织符合指定的协议数据然后进行通信。\ngolang中标准库提供了net包，用来进行Socket编程\nSocket又称套接字，应用程序通常通过Socket向网络发出请求或者应答网络请求 常用的Socket类型有两种：流式Socket（SOCK_STREAM）和数据报式Socket（SOCK_DGRAM），流式是一种面向连接的Socket，针对于面向连接的TCP服务应用；数据报式Socket是一种无连接的Socket，针对于无连接的UDP服务应用 TCP：比较靠谱，面向连接，比较慢 UDP：不是太靠谱，比较快 UDP # 服务端 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;net\u0026#34; ) const ( IP = \u0026#34;127.0.0.1\u0026#34; PORT = 8080 ) func main() { // 设置监听地址 addr, err := net.ResolveUDPAddr(\u0026#34;udp\u0026#34;, fmt.Sprintf(\u0026#34;%s:%d\u0026#34;, IP, PORT)) if err != nil { fmt.Println(err) } // 开启udp监听 conn, err := net.ListenUDP(\u0026#34;udp\u0026#34;, addr) if err != nil { fmt.Println(err) } defer conn.Close() for { data := make([]byte, 1024) _, clientAddr, err := conn.ReadFromUDP(data) if err != nil { fmt.Println(err) } if clientAddr != nil { fmt.Printf(\u0026#34;msg from client:%s,msg:%s\u0026#34;, clientAddr.IP.String(), string(data)) conn.WriteToUDP([]byte(\u0026#34;accept ok\u0026#34;), clientAddr) } } } 客户端 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;net\u0026#34; ) const ( IP = \u0026#34;127.0.0.1\u0026#34; PORT = 8080 ) func main() { // 连接udp server conn, err := net.Dial(\u0026#34;udp\u0026#34;, fmt.Sprintf(\u0026#34;%s:%d\u0026#34;, IP, PORT)) if err != nil { fmt.Println(err) } defer conn.Close() var msg string for { fmt.Scanln(\u0026amp;msg) if msg == \u0026#34;exit\u0026#34; { break } _, err = conn.Write([]byte(msg)) if err != nil { fmt.Println(err) } data := make([]byte, 1024) _, err := conn.Read(data) if err != nil { fmt.Println(err) } fmt.Println(string(data)) } } TCP # 服务端 # Go 语言TcpServer 教程的步骤可以总结为：定义通信的地址和端口、使用 Listen 函数监听 TCP 的地址和端口信息并得到连接信息、使用连接信息的 Accept 函数等待连接、每来一个连接得到一个连接，使用连接进行读写数据。\n一个TCP服务端可以同时连接很多个客户端。因为Go语言中创建多个goroutine实现并发非常方便和高效，所以我们可以每建立一次链接就创建一个goroutine去处理。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;net\u0026#34; ) const ( SERVER_IP = \u0026#34;127.0.0.1\u0026#34; SERVER_PORT = 8080 ) //处理tcp连接 func handler(conn net.Conn) { defer conn.Close() clientAddr := conn.RemoteAddr().String() fmt.Println(\u0026#34;新客户端连接:\u0026#34;, clientAddr) for { data := make([]byte, 1024) i, err := conn.Read(data) if err != nil { fmt.Println(\u0026#34;客户端断开连接:\u0026#34;, clientAddr) return } fmt.Println(string(data[:i])) conn.Write([]byte(\u0026#34;服务器成功接收\u0026#34;)) } } func main() { //开启监听 li, err := net.Listen(\u0026#34;tcp\u0026#34;, fmt.Sprintf(\u0026#34;%s:%d\u0026#34;, SERVER_IP, SERVER_PORT)) if err != nil { fmt.Println(err) } defer li.Close() for { //接收并建立链接 conn, err := li.Accept() if err != nil { fmt.Println(err) } go handler(conn) } } 客户端 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;net\u0026#34; ) const ( CLIENT_IP = \u0026#34;127.0.0.1\u0026#34; CLIENT_PORT = 8080 ) func main() { //连接服务端 conn, err := net.Dial(\u0026#34;tcp\u0026#34;, fmt.Sprintf(\u0026#34;%s:%d\u0026#34;, CLIENT_IP, CLIENT_PORT)) if err != nil { fmt.Println(err) } defer conn.Close() for { var msg string fmt.Scanln(\u0026amp;msg) if msg == \u0026#34;exit\u0026#34; { break } _, err := conn.Write([]byte(msg)) if err != nil { fmt.Println(err) } data := make([]byte, 1024) i, err := conn.Read(data) if err != nil { fmt.Println(err) } fmt.Println(string(data[:i])) } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/2d3150b2/0e0cf2f4/9ee4721e/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e网络编程 \n    \u003cdiv id=\"网络编程\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%bd%91%e7%bb%9c%e7%bc%96%e7%a8%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e网络编程的本质是两个设备之间的数据交换，在计算机网络中，设备主要指计算机。数据传递本身没有多大的难度，不就是把一个设备中的数据发送给另外一个设备，然后接受另外一个设备反馈的数据。\u003c/p\u003e","title":"7、网络编程","type":"posts"},{"content":" 为什么使用视图 # 多表的联合查询，最多也才3张表，如果面临更多的表，为了简化连表操作，可以使用MySQL中的视图\n好处 # 1、简化sql语句\n2、提高了sql的重用性\n3、保护基表的数据，提高了安全性\n创建视图 # create view 视图名 as 查询语句; 修改视图 # 方式一 # create or replace view 视图名 as 查询语句; 方式二 # alter view 视图名 as 查询语句 删除视图 # drop view 视图1，视图2,...; 查看视图 # desc 视图名; show create view 视图名; 视图和表的对比 # 关键字 是否占用物理空间 使用 视图 view 占用较小，只保存sql逻辑 一般用于查询 表 table 保存实际的数据 增删改查 ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/a6ece75f/8b824d9b/5d290da9/4b5e473b/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e为什么使用视图 \n    \u003cdiv id=\"为什么使用视图\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bd%bf%e7%94%a8%e8%a7%86%e5%9b%be\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e多表的联合查询，最多也才3张表，如果面临更多的表，为了简化连表操作，可以使用MySQL中的视图\u003c/p\u003e","title":"7、视图","type":"posts"},{"content":"math 包实现的就是数学函数计算。\n随机数 # //生成非负伪随机int值 rand.Int() //返回一个取值范围在[0,n)的伪随机int值，如果n\u0026lt;=0会panic rand.Intn(n) //返回一个有n个元素的，[0,n)范围内整数的伪随机排列的切片 rand.Perm(n) //生成取值范围[0.0, 1.0)的伪随机float64值 rand.Float64()) 三角函数 # //正弦函数，反正弦函数，双曲正弦，反双曲正弦 func Sin(x float64) float64 func Asin(x float64) float64 func Sinh(x float64) float64 func Asinh(x float64) float64 //余弦函数，反余弦函数，双曲余弦，反双曲余弦 func Cos(x float64) float64 func Acos(x float64) float64 func Cosh(x float64) float64 func Acosh(x float64) float64 //正切函数，反正切函数，双曲正切，反双曲正切 func Tan(x float64) float64 func Atan(x float64) float64 和 func Atan2(y, x float64) float64 func Tanh(x float64) float64 func Atanh(x float64) float64 幂次函数 # func Cbrt(x float64) float64 // 立方根函数 func Pow(x, y float64) float64 // x 的幂函数 func Pow10(e int) float64 // 10 根的幂函数 func Sqrt(x float64) float64 // 平方根 func Log(x float64) float64 // 对数函数 func Log10(x float64) float64 // 10 为底的对数函数 func Log2(x float64) float64 // 2 为底的对数函数 func Log1p(x float64) float64 // log(1 + x) func Logb(x float64) float64 // 相当于 log2(x) 的绝对值 func Ilogb(x float64) int // 相当于 log2(x) 的绝对值的整数部分 func Exp(x float64) float64 // 指数函数 func Exp2(x float64) float64 // 2 为底的指数函数 func Expm1(x float64) float64 // Exp(x) - 1 特殊函数 # func Inf(sign int) float64 // 正无穷 func IsInf(f float64, sign int) bool // 是否正无穷 func NaN() float64 // 无穷值 func IsNaN(f float64) (is bool) // 是否是无穷值 func Hypot(p, q float64) float64 // 计算直角三角形的斜边长 类型转化函数 # func Float32bits(f float32) uint32 // float32 和 unit32 的转换 func Float32frombits(b uint32) float32 // uint32 和 float32 的转换 func Float64bits(f float64) uint64 // float64 和 uint64 的转换 func Float64frombits(b uint64) float64 // uint64 和 float64 的转换 其他函数 # func Abs(x float64) float64 // 绝对值函数 func Ceil(x float64) float64 // 向上取整 func Floor(x float64) float64 // 向下取整 func Mod(x, y float64) float64 // 取模 func Modf(f float64) (int float64, frac float64) // 分解 f，以得到 f 的整数和小数部分 func Frexp(f float64) (frac float64, exp int) // 分解 f，得到 f 的位数和指数 func Max(x, y float64) float64 // 取大值 func Min(x, y float64) float64 // 取小值 func Dim(x, y float64) float64 // 复数的维数 func J0(x float64) float64 // 0 阶贝塞尔函数 func J1(x float64) float64 // 1 阶贝塞尔函数 func Jn(n int, x float64) float64 // n 阶贝塞尔函数 func Y0(x float64) float64 // 第二类贝塞尔函数 0 阶 func Y1(x float64) float64 // 第二类贝塞尔函数 1 阶 func Yn(n int, x float64) float64 // 第二类贝塞尔函数 n 阶 func Erf(x float64) float64 // 误差函数 func Erfc(x float64) float64 // 余补误差函数 func Copysign(x, y float64) float64 // 以 y 的符号返回 x 值 func Signbit(x float64) bool // 获取 x 的符号 func Gamma(x float64) float64 // 伽玛函数 func Lgamma(x float64) (lgamma float64, sign int) // 伽玛函数的自然对数 func Ldexp(frac float64, exp int) float64 // value 乘以 2 的 exp 次幂 func Nextafter(x, y float64) (r float64) // 返回参数 x 在参数 y 方向上可以表示的最接近的数值，若 x 等于 y，则返回 x func Nextafter32(x, y float32) (r float32) // 返回参数 x 在参数 y 方向上可以表示的最接近的数值，若 x 等于 y，则返回 x func Remainder(x, y float64) float64 // 取余运算 func Trunc(x float64) float64 // 截取函数 ","date":"2025-05-22","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/12d682f6/af699bb4/","section":"文章","summary":"\u003cp\u003emath 包实现的就是数学函数计算。\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e随机数 \n    \u003cdiv id=\"随机数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%9a%8f%e6%9c%ba%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//生成非负伪随机int值\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003erand\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eInt\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//返回一个取值范围在[0,n)的伪随机int值，如果n\u0026lt;=0会panic\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003erand\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eIntn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//返回一个有n个元素的，[0,n)范围内整数的伪随机排列的切片\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003erand\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePerm\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//生成取值范围[0.0, 1.0)的伪随机float64值\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003erand\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eFloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e三角函数 \n    \u003cdiv id=\"三角函数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%b8%89%e8%a7%92%e5%87%bd%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//正弦函数，反正弦函数，双曲正弦，反双曲正弦\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eSin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eAsin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eSinh\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eAsinh\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//余弦函数，反余弦函数，双曲余弦，反双曲余弦\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eCos\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eAcos\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eCosh\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eAcosh\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//正切函数，反正切函数，双曲正切，反双曲正切\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eTan\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eAtan\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"nx\"\u003e和\u003c/span\u003e \u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eAtan2\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eTanh\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eAtanh\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e幂次函数 \n    \u003cdiv id=\"幂次函数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b9%82%e6%ac%a1%e5%87%bd%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eCbrt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 立方根函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003ePow\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ey\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// x 的幂函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003ePow10\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ee\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 10 根的幂函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eSqrt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 平方根\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eLog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 对数函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eLog10\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 10 为底的对数函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eLog2\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// 2 为底的对数函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eLog1p\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// log(1 + x)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eLogb\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 相当于 log2(x) 的绝对值\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eIlogb\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 相当于 log2(x) 的绝对值的整数部分\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eExp\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 指数函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eExp2\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 2 为底的指数函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eExpm1\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// Exp(x) - 1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e特殊函数 \n    \u003cdiv id=\"特殊函数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%89%b9%e6%ae%8a%e5%87%bd%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eInf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003esign\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// 正无穷\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eIsInf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ef\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003esign\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003ebool\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 是否正无穷\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eNaN\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 无穷值\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eIsNaN\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ef\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eis\u003c/span\u003e \u003cspan class=\"kt\"\u003ebool\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 是否是无穷值\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eHypot\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eq\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 计算直角三角形的斜边长\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e类型转化函数 \n    \u003cdiv id=\"类型转化函数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%b1%bb%e5%9e%8b%e8%bd%ac%e5%8c%96%e5%87%bd%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eFloat32bits\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ef\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat32\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003euint32\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// float32 和 unit32 的转换\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eFloat32frombits\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eb\u003c/span\u003e \u003cspan class=\"kt\"\u003euint32\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat32\u003c/span\u003e \u003cspan class=\"c1\"\u003e// uint32 和 float32 的转换\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eFloat64bits\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ef\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003euint64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// float64 和 uint64 的转换\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eFloat64frombits\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eb\u003c/span\u003e \u003cspan class=\"kt\"\u003euint64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// uint64 和 float64 的转换\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e其他函数 \n    \u003cdiv id=\"其他函数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%85%b6%e4%bb%96%e5%87%bd%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eAbs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 绝对值函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eCeil\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// 向上取整\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eFloor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 向下取整\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eMod\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ey\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 取模\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eModf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ef\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003efrac\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 分解 f，以得到 f 的整数和小数部分\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eFrexp\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ef\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003efrac\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eexp\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 分解 f，得到 f 的位数和指数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eMax\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ey\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// 取大值\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eMin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ey\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// 取小值\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eDim\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ey\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 复数的维数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eJ0\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// 0 阶贝塞尔函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eJ1\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// 1 阶贝塞尔函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eJn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003en\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// n 阶贝塞尔函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eY0\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// 第二类贝塞尔函数 0 阶\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eY1\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// 第二类贝塞尔函数 1 阶\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eYn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003en\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 第二类贝塞尔函数 n 阶\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eErf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 误差函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eErfc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 余补误差函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eCopysign\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ey\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 以 y 的符号返回 x 值\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eSignbit\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003ebool\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 获取 x 的符号\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eGamma\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 伽玛函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eLgamma\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003elgamma\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003esign\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 伽玛函数的自然对数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eLdexp\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003efrac\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eexp\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// value 乘以 2 的 exp 次幂\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eNextafter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ey\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003er\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 返回参数 x 在参数 y 方向上可以表示的最接近的数值，若 x 等于 y，则返回 x\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eNextafter32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ey\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat32\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003er\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat32\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 返回参数 x 在参数 y 方向上可以表示的最接近的数值，若 x 等于 y，则返回 x\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eRemainder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ey\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 取余运算\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eTrunc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ex\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003efloat64\u003c/span\u003e \u003cspan class=\"c1\"\u003e// 截取函数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"8、math","type":"posts"},{"content":" 依赖注入与控制反转基础概念 # 在软件工程中，依赖注入（Dependency Injection, DI）和控制反转（Inversion of Control, IoC）是两个密切相关的设计原则，它们有助于创建松耦合、可测试且可维护的代码。虽然这些概念在Java等语言中更为普遍，但在Go语言中，它们同样重要且有多种实现方式。\n依赖注入 # 依赖注入是一种设计模式，它允许我们将组件所需的依赖项\u0026quot;注入\u0026quot;到组件中，而不是让组件自己创建或查找这些依赖项。通过这种方式，组件变得更加解耦，更容易测试和维护。\n简单来说，依赖注入主要解决的问题是：如何在不增加组件之间耦合度的前提下，让一个组件获得它所需的其他组件。\n传统的依赖管理方式可能如下：\n// 不使用依赖注入 type UserService struct { // UserService自己创建依赖 repo *UserRepository } func NewUserService() *UserService { return \u0026amp;UserService{ // 强耦合：服务直接依赖于具体的实现 repo: NewUserRepository(\u0026#34;mongodb://localhost:27017\u0026#34;), } } 使用依赖注入后：\n// 使用依赖注入 type UserService struct { repo UserRepository } func NewUserService(repo UserRepository) *UserService { return \u0026amp;UserService{ repo: repo, // 依赖通过参数注入 } } 依赖注入的类型 # 依赖注入通常有以下几种类型：\n构造函数注入：通过构造函数提供依赖项 方法注入：通过方法调用提供依赖项 属性注入：直接设置对象的公共属性 在Go语言中，由于没有类和对象的概念，依赖注入主要通过函数参数（类似于构造函数注入）和结构体字段（类似于属性注入）来实现。\n依赖注入的优势 # 依赖注入提供了以下几个主要优势：\n解耦：减少组件之间的直接依赖，使代码更模块化 可测试性：便于使用模拟（mock）对象进行单元测试 可维护性：清晰地表达依赖关系，使代码更易于理解和维护 灵活性：允许在运行时或配置时更改依赖关系 可重用性：组件更容易在不同上下文中重用 控制反转 # 控制反转是一个更广泛的概念，依赖注入是控制反转的一种实现方式。控制反转描述的是一种设计思想，即将对象的创建、管理和依赖关系的控制权从代码内部转移到外部。\n在传统编程中，我们的代码直接控制对象的创建和依赖关系。而在控制反转模式下，这些控制权被\u0026quot;反转\u0026quot;了，由外部容器或框架来管理对象的创建和依赖关系。\n控制反转的\u0026quot;反转\u0026quot;主要体现在两个方面：\n依赖对象的获取方向反转：传统方式是组件主动获取依赖，控制反转是被动接收依赖 依赖关系控制权反转：从组件内部转移到外部 Go语言中依赖注入的基本实现 # Go语言没有内置的依赖注入框架，但我们可以使用多种方式来实现依赖注入。Go的简洁设计使得依赖注入变得相对直观。\n通过函数参数进行依赖注入 # 最简单的依赖注入方式是通过函数参数。这种方式与构造函数注入类似，适合大多数场景：\n// 定义一个数据存储接口 type UserRepository interface { FindByID(id string) (*User, error) Save(user *User) error } // 定义一个服务，需要依赖数据存储 type UserService struct { repo UserRepository } // 通过函数参数注入依赖 func NewUserService(repo UserRepository) *UserService { return \u0026amp;UserService{ repo: repo, } } // 使用依赖处理业务逻辑 func (s *UserService) GetUser(id string) (*User, error) { return s.repo.FindByID(id) } 这种方式简单直观，是Go语言中最常见的依赖注入方式。调用者负责创建依赖并将其传递给服务：\n// 创建具体的存储实现 repo := NewMySQLUserRepository(db) // 将依赖注入到服务中 userService := NewUserService(repo) // 使用服务 user, err := userService.GetUser(\u0026#34;123\u0026#34;) 通过结构体字段进行依赖注入 # 除了在创建时注入依赖，我们也可以通过设置结构体的公开字段来注入依赖：\ntype AppConfig struct { DatabaseURL string APIKey string Debug bool } type Application struct { Config AppConfig // 通过结构体字段注入配置 UserRepo UserRepository AuthService AuthService } // 使用 app := Application{} app.Config = loadConfig() app.UserRepo = NewUserRepository(app.Config.DatabaseURL) app.AuthService = NewAuthService(app.UserRepo, app.Config.APIKey) 这种方式在某些简单场景下有用，但通常我们更倾向于使用函数参数方式，因为它可以确保对象在创建时就获得所有必要的依赖，避免了部分初始化的对象。\n使用接口实现依赖反转 # 依赖注入在与接口结合使用时效果最佳。通过定义接口，我们使高层组件依赖于抽象而非具体实现，这正是依赖反转原则（Dependency Inversion Principle）的核心思想。\n// 定义数据库接口 type Database interface { Query(query string) ([]Row, error) Execute(command string) error } // 定义使用数据库的服务 type ProductService struct { db Database } func NewProductService(db Database) *ProductService { return \u0026amp;ProductService{db: db} } // 创建具体数据库实现 type MySQLDatabase struct { // ...实现细节 } func (m *MySQLDatabase) Query(query string) ([]Row, error) { // 实现MySQL查询 return nil, nil } func (m *MySQLDatabase) Execute(command string) error { // 实现MySQL命令执行 return nil } // 使用依赖注入 db := \u0026amp;MySQLDatabase{} productService := NewProductService(db) 通过这种方式，ProductService不依赖于具体的数据库实现，而是依赖于Database接口。这样我们可以轻松地替换数据库实现，例如用于测试：\n// 创建测试用的模拟数据库 type MockDatabase struct { // ...模拟实现 } func (m *MockDatabase) Query(query string) ([]Row, error) { // 返回预设的测试数据 return []Row{/* 预设数据 */}, nil } func (m *MockDatabase) Execute(command string) error { // 模拟命令执行 return nil } // 在测试中使用模拟数据库 func TestProductService(t *testing.T) { mockDB := \u0026amp;MockDatabase{} service := NewProductService(mockDB) // 测试服务的行为... } 使用函数类型作为依赖 # 在Go中，函数是一等公民，我们可以将函数类型用作依赖项，这在某些场景下非常有用：\n// 定义一个记录器函数类型 type LoggerFunc func(format string, args ...interface{}) // 使用函数类型作为依赖 type Worker struct { log LoggerFunc } func NewWorker(log LoggerFunc) *Worker { // 如果没有提供日志函数，使用默认实现 if log == nil { log = func(format string, args ...interface{}) { fmt.Printf(format+\u0026#34;\\n\u0026#34;, args...) } } return \u0026amp;Worker{log: log} } func (w *Worker) DoWork() { w.log(\u0026#34;Starting work...\u0026#34;) // 执行工作... w.log(\u0026#34;Work completed\u0026#34;) } // 使用示例 worker := NewWorker(func(format string, args ...interface{}) { log.Printf(\u0026#34;[WORKER] \u0026#34;+format, args...) }) worker.DoWork() 这种方法特别适合依赖比较简单的情况，可以避免为简单功能定义完整接口。\n常见的依赖注入模式 # 在Go语言中，有几种流行的依赖注入模式，下面我们将探讨这些模式以及它们的适用场景。\n构造函数注入 # 构造函数注入是Go中最常见的依赖注入模式，我们在创建对象时通过构造函数传入所有需要的依赖：\ntype Service struct { repo Repository cache Cache logger Logger validator Validator } func NewService(repo Repository, cache Cache, logger Logger, validator Validator) *Service { return \u0026amp;Service{ repo: repo, cache: cache, logger: logger, validator: validator, } } 优点：\n确保创建的对象始终处于有效状态（有所有必要的依赖） 依赖关系明确，容易理解 适合大多数场景 缺点：\n当依赖项很多时，构造函数参数列表可能变得很长 如果某些依赖是可选的，需要创建多个构造函数或使用其他模式 功能选项模式 # 当一个组件有许多可选配置或依赖时，功能选项模式（Functional Options Pattern）非常有用：\ntype ServerOption func(*Server) func WithPort(port int) ServerOption { return func(s *Server) { s.port = port } } func WithLogger(logger Logger) ServerOption { return func(s *Server) { s.logger = logger } } func WithCache(cache Cache) ServerOption { return func(s *Server) { s.cache = cache } } type Server struct { port int logger Logger cache Cache timeout time.Duration } func NewServer(options ...ServerOption) *Server { // 设置默认值 server := \u0026amp;Server{ port: 8080, logger: \u0026amp;DefaultLogger{}, timeout: 30 * time.Second, } // 应用提供的选项 for _, option := range options { option(server) } return server } // 使用示例 server := NewServer( WithPort(9000), WithLogger(customLogger), WithCache(redisCache), ) 优点：\n提供了极大的灵活性，用户只需指定他们关心的选项 支持默认值，减少配置负担 构造函数签名保持简洁 可以随时添加新选项而不破坏现有代码 缺点：\n相比简单的构造函数，实现略微复杂 可能使依赖关系不那么明显 配置对象模式 # 当依赖项较多时，可以使用配置对象来组织它们：\ntype ServiceConfig struct { Repository Repository Cache Cache Logger Logger Timeout time.Duration MaxRetries int } type Service struct { repo Repository cache Cache logger Logger timeout time.Duration maxRetries int } func NewService(config ServiceConfig) *Service { // 验证配置 if config.Repository == nil { panic(\u0026#34;repository is required\u0026#34;) } // 设置默认值 logger := config.Logger if logger == nil { logger = \u0026amp;DefaultLogger{} } timeout := config.Timeout if timeout == 0 { timeout = 30 * time.Second } return \u0026amp;Service{ repo: config.Repository, cache: config.Cache, logger: logger, timeout: timeout, maxRetries: config.MaxRetries, } } // 使用示例 service := NewService(ServiceConfig{ Repository: myRepo, Cache: myCache, MaxRetries: 3, }) 优点：\n将多个相关参数组织成一个结构 随着依赖的增加，构造函数签名保持不变 参数命名明确，提高可读性 缺点：\n必须创建额外的结构体 可能鼓励传递不必要的空值 服务定位器模式 # 服务定位器（Service Locator）提供了一个中央注册表，组件可以从中获取其依赖：\ntype ServiceLocator struct { services map[reflect.Type]interface{} mu sync.RWMutex } func NewServiceLocator() *ServiceLocator { return \u0026amp;ServiceLocator{ services: make(map[reflect.Type]interface{}), } } func (sl *ServiceLocator) Register(service interface{}) { sl.mu.Lock() defer sl.mu.Unlock() t := reflect.TypeOf(service) sl.services[t] = service } func (sl *ServiceLocator) Get(t reflect.Type) interface{} { sl.mu.RLock() defer sl.mu.RUnlock() return sl.services[t] } // 使用示例 locator := NewServiceLocator() locator.Register(\u0026amp;MySQLRepository{}) locator.Register(\u0026amp;RedisCache{}) // 在组件中获取依赖 repo := locator.Get(reflect.TypeOf((*Repository)(nil)).Elem()).(Repository) 注意：虽然服务定位器模式在某些场景下有用，但它在Go社区中通常不被推荐，因为它隐藏了依赖关系，使代码难以理解和测试。大多数Go开发者更喜欢显式依赖注入。\n容器化依赖注入 # 随着应用程序规模增长，手动管理所有依赖关系可能变得复杂。在这种情况下，可以使用依赖注入容器：\ntype Container struct { providers map[reflect.Type]provider instances map[reflect.Type]interface{} mu sync.RWMutex } type provider struct { constructor interface{} singleton bool } func NewContainer() *Container { return \u0026amp;Container{ providers: make(map[reflect.Type]provider), instances: make(map[reflect.Type]interface{}), } } // 注册服务提供者 func (c *Container) Provide(constructor interface{}, singleton bool) { c.mu.Lock() defer c.mu.Unlock() t := reflect.TypeOf(constructor) if t.Kind() != reflect.Func { panic(\u0026#34;constructor must be a function\u0026#34;) } returnType := t.Out(0) c.providers[returnType] = provider{ constructor: constructor, singleton: singleton, } } // 解析服务 func (c *Container) Resolve(t reflect.Type) interface{} { c.mu.RLock() // 检查是否已有实例（用于单例） if instance, ok := c.instances[t]; ok { c.mu.RUnlock() return instance } c.mu.RUnlock() c.mu.Lock() defer c.mu.Unlock() // 双重检查锁定 if instance, ok := c.instances[t]; ok { return instance } provider, ok := c.providers[t] if !ok { panic(fmt.Sprintf(\u0026#34;no provider for %v\u0026#34;, t)) } constructorVal := reflect.ValueOf(provider.constructor) constructorType := constructorVal.Type() // 解析构造函数的依赖 args := make([]reflect.Value, constructorType.NumIn()) for i := 0; i \u0026lt; constructorType.NumIn(); i++ { argType := constructorType.In(i) args[i] = reflect.ValueOf(c.Resolve(argType)) } // 调用构造函数 result := constructorVal.Call(args)[0].Interface() // 如果是单例，保存实例 if provider.singleton { c.instances[t] = result } return result } // 使用示例 container := NewContainer() // 注册服务 container.Provide(NewRepository, true) container.Provide(NewService, true) // 解析服务 service := container.Resolve(reflect.TypeOf((*Service)(nil)).Elem()).(*Service) 注意：上面的实现是简化的，实际的依赖注入容器会更复杂。在Go中，使用代码生成工具（Wire）通常比运行时反射更受欢迎。\nWire # 随着应用程序的增长，手动管理依赖关系可能变得复杂。Google的Wire是一个编译时依赖注入工具，它通过代码生成简化了依赖管理。\nWire是一个代码生成工具，而不是运行时框架。它在编译时解析依赖关系并生成用于初始化应用程序的代码，具有以下优势：\n编译时检查：依赖关系在编译时验证，避免运行时错误 零运行时依赖：生成的代码不依赖于Wire库 类型安全：利用Go的类型系统确保依赖正确匹配 快速执行：没有反射开销，初始化性能与手写代码相同 透明性：生成的代码易于理解和调试 Wire的核心概念包括：\nProvider：提供依赖项的函数 Injector：将所有依赖项连接起来的函数 ProviderSet：提供者的集合，便于组织和复用 安装 Wire # 这一步是为了安装 Wire 的命令行工具wire\ngo install github.com/google/wire/cmd/wire@latest 基本使用示例 # 代码中导入 wire 依赖\ngo get -u github.com/google/wire 项目结构如下\nuser_repository.go\npackage repository type UserRepository struct { } func NewUserRepository() *UserRepository { return \u0026amp;UserRepository{} } func (u *UserRepository) Select() map[string]string { return map[string]string{ \u0026#34;name\u0026#34;: \u0026#34;tom\u0026#34;, \u0026#34;age\u0026#34;: \u0026#34;18\u0026#34;, } } user_service.go\npackage service import \u0026#34;ygang.top/demo/repository\u0026#34; type UserService struct { UserRepo *repository.UserRepository } func NewUserService(userRepo *repository.UserRepository) *UserService { return \u0026amp;UserService{ UserRepo: userRepo, } } func (u *UserService) SelectUser() map[string]string { return u.UserRepo.Select() } user_controller.go\npackage controller import \u0026#34;ygang.top/demo/service\u0026#34; type UserController struct { UserService *service.UserService } func NewUserController(userService *service.UserService) *UserController { return \u0026amp;UserController{ UserService: userService, } } func (u *UserController) SelectUser() map[string]string { return u.UserService.SelectUser() } 创建一个wire.go文件定义依赖关系：\n//go:build wireinject // +build wireinject package wire import ( \u0026#34;github.com/google/wire\u0026#34; \u0026#34;ygang.top/demo/controller\u0026#34; \u0026#34;ygang.top/demo/repository\u0026#34; \u0026#34;ygang.top/demo/service\u0026#34; ) // 创建并初始化 UserController func InitUserController() *controller.UserController { wire.Build( controller.NewUserController, service.NewUserService, repository.NewUserRepository, ) return \u0026amp;controller.UserController{} // 返回值会被 Wire 忽略并替换 } 在wire/目录中，执行wire命令，会生成一个wire_gen.go的文件，其中包含初始化组件的代码：\n// Code generated by Wire. DO NOT EDIT. //go:generate go run -mod=mod github.com/google/wire/cmd/wire //go:build !wireinject // +build !wireinject package wire import ( \u0026#34;ygang.top/demo/controller\u0026#34; \u0026#34;ygang.top/demo/repository\u0026#34; \u0026#34;ygang.top/demo/service\u0026#34; ) // Injectors from wire.go: // 创建并初始化 UserController func InitUserController() *controller.UserController { userRepository := repository.NewUserRepository() userService := service.NewUserService(userRepository) userController := controller.NewUserController(userService) return userController } 在main.go中，获取UserController，并使用\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;ygang.top/demo/wire\u0026#34; ) func main() { controller := wire.InitUserController() fmt.Println(controller.SelectUser()) } Wire高级特性 # Provider Sets # Provider Sets允许我们将提供者分组，便于组织和重用：\npackage db import \u0026#34;github.com/google/wire\u0026#34; func NewDBConnection(...) *Connection {...} func NewUserRepository(conn *Connection) *UserRepository {...} func NewProductRepository(conn *Connection) *ProductRepository {...} // DBSet 提供所有数据库相关的依赖 var DBSet = wire.NewSet( NewDBConnection, NewUserRepository, NewProductRepository, ) 在另一个包中使用：\npackage main import ( \u0026#34;myapp/db\u0026#34; \u0026#34;github.com/google/wire\u0026#34; ) func InitializeApp() *Application { wire.Build( db.DBSet, NewService, NewApplication, ) return nil } 绑定接口 # Wire支持将具体类型绑定到接口：\ntype UserRepository interface { FindByID(id string) (*User, error) } type MySQLUserRepository struct { // ... } func NewMySQLUserRepository() *MySQLUserRepository { // ... } func (r *MySQLUserRepository) FindByID(id string) (*User, error) { // ... } // 创建Provider Set，将MySQLUserRepository绑定到UserRepository接口 var RepositorySet = wire.NewSet( NewMySQLUserRepository, wire.Bind(new(UserRepository), new(*MySQLUserRepository)), ) 提供值 # 除了使用构造函数，我们还可以直接提供值：\nvar ConfigSet = wire.NewSet( wire.Value(DatabaseConfig{ Host: \u0026#34;localhost\u0026#34;, Port: 5432, Username: \u0026#34;user\u0026#34;, Password: \u0026#34;password\u0026#34;, }), ) 结构体字段注入 # Wire支持通过FieldsOf函数提取结构体字段作为依赖\ntype Config struct { DatabaseURL string APIKey string } func NewConfig() Config { return Config{ DatabaseURL: \u0026#34;postgres://localhost/myapp\u0026#34;, APIKey: \u0026#34;secret-key\u0026#34;, } } var ConfigSet = wire.NewSet( NewConfig, wire.FieldsOf(new(Config), \u0026#34;DatabaseURL\u0026#34;, \u0026#34;APIKey\u0026#34;), ) 清理函数 # 对于需要清理的资源，Wire支持使用wire.Cleanup：\nfunc NewDatabase() (*Database, func(), error) { db, err := sql.Open(\u0026#34;postgres\u0026#34;, \u0026#34;...\u0026#34;) if err != nil { return nil, nil, err } cleanup := func() { db.Close() } return \u0026amp;Database{DB: db}, cleanup, nil } func InitializeApp() (*App, func(), error) { wire.Build(NewDatabase, NewService, NewApp) return nil, nil, nil } 生成的代码会正确处理清理函数：\nfunc InitializeApp() (*App, func(), error) { database, cleanup, err := NewDatabase() if err != nil { return nil, nil, err } service := NewService(database) app := NewApp(service) return app, cleanup, nil } Wire最佳实践 # 使用Wire时的一些最佳实践：\n组织Provider Sets：按照模块或功能将提供者分组 保持提供者函数简单：每个提供者函数应只负责创建一个组件 使用接口：通过接口和wire.Bind分离抽象和实现 处理配置：使用wire.Value或专门的配置提供者函数 错误处理：提供者函数应返回错误而不是panic 测试性：设计组件便于在测试中替换依赖 ","date":"2025-05-21","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/29ede47b/fdbef724/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e依赖注入与控制反转基础概念 \n    \u003cdiv id=\"依赖注入与控制反转基础概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%be%9d%e8%b5%96%e6%b3%a8%e5%85%a5%e4%b8%8e%e6%8e%a7%e5%88%b6%e5%8f%8d%e8%bd%ac%e5%9f%ba%e7%a1%80%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在软件工程中，依赖注入（Dependency Injection, DI）和控制反转（Inversion of Control, IoC）是两个密切相关的设计原则，它们有助于创建松耦合、可测试且可维护的代码。虽然这些概念在Java等语言中更为普遍，但在Go语言中，它们同样重要且有多种实现方式。\u003c/p\u003e","title":"8、wire","type":"posts"},{"content":" 函数 # 函数构成了代码执行的逻辑结构，在Go语言中，函数的基本组成为：关键字 func、函数名、参数列表、返回值、函数体、返回语句，每一个程序都包含很多的函数，函数是基本的代码块。\n因为Go语言是编译型语言，所以函数编写的顺序是无关紧要的，鉴于可读性的需求，最好把main()函数写在文件的前面，其他函数按照一定逻辑顺序进行编写（例如函数被调用的顺序）。\nGo语言里面拥三种类型的函数：\n普通的带有名字的函数 匿名函数或者 lambda 函数 方法 普通函数 # 定义 # 函数声明包括函数名、参数列表、返回值列表（可省略）以及函数体\nfunc 函数名 (参数列表) (返回值列表) { return 函数体 } 如果一个函数在声明时，包含返回值列表，那么该函数必须以 return 语句结尾，除非函数明显无法运行到结尾处。\n在函数中，实参通过值传递的方式进行传递，因此函数的形参是实参的拷贝，对形参进行修改不会影响实参，但是，如果实参包括引用类型，如指针、slice(切片)、map、function、channel 等类型，实参可能会由于函数的间接引用被修改。\n参数列表 # 同类型参数 # 多个同类型参数可以省略前面的参数的类型声明，此时的参数a类型为string，参数b、c类型为int\nfunc funcname(a string, b, c int) {} 可变参数 # 本质上是一个切片，也就是[]int，可变参数必须位于参数列表的最后\nfunc funcname(args ...int) {} 我们也可以使用slice...的语法将一个切片传给可变参数的函数\npackage main import \u0026#34;fmt\u0026#34; func main() { s := []string{\u0026#34;lucy\u0026#34;, \u0026#34;tom\u0026#34;} multiParam(s...) } func multiParam(args ...string) { fmt.Println(args) } 返回值 # 注意：在函数有多个返回值时，只要有一个返回值有指定命名，其他的也必须有命名。如果返回值有有多个返回值必须加上括号； 如果只有一个返回值并且有命名也需要加上括号。\npackage main import \u0026#34;fmt\u0026#34; func main() { fmt.Println(add(1, 2)) // 3 fmt.Println(red(1, 2)) // -1 x, y := getPoint() fmt.Println(x, y) //52 13 } func add(a int, b int) int { return a + b } // 函数声明时，可以给返回值声明一个变量，在函数体内进行操作 func red(a int, b int) (c int) { c = a - b return } // 函数支持返回多个返回值 func getPoint() (int, int) { return 52, 13 } 函数变量 # 在Go语言中，函数也是一种类型，可以和其他类型一样保存在变量中\npackage main import \u0026#34;fmt\u0026#34; func main() { // 声明一个函数类型的变量 var f1 func(int, int) int // 将函数add赋值给f2 f1 = add // 自动类型推导 f2 := add fmt.Println(f1(1, 2)) // 3 fmt.Println(f2(1, 2)) // 3 } func add(a int, b int) int { return a + b } 匿名函数 # 匿名函数是指不需要定义函数名的一种函数实现方式，由一个不带函数名的函数声明和函数体组成，匿名函数可以声明在另一个函数的函数体中\nfunc (参数列表) (返回参数列表) { 函数体 } 匿名函数的用途非常广泛，它本身就是一种值，可以方便地保存在各种容器中实现回调函数和操作封装。\n// 定义的同时进行调用 func(a int, b int) { fmt.Println(a + b) }(1, 2) // 匿名函数赋值给变量 f := func(a int, b int) int { return a + b } fmt.Println(f(1, 2)) 作为回调函数\npackage main import \u0026#34;fmt\u0026#34; func main() { names := []string{\u0026#34;tom\u0026#34;, \u0026#34;lucy\u0026#34;, \u0026#34;lily\u0026#34;} f := func(name string) { fmt.Printf(\u0026#34;%s，Hello\\n\u0026#34;, name) } // 调用函数，并传入回调函数 handlerNames(names, f) } // 遍历名单，对名字执行操作 func handlerNames(names []string, f func(string)) { for _, k := range names { f(k) } } 闭包 # Go语言中闭包是引用了另一个函数局部变量的函数，被引用的局部变量和闭包函数一同存在，即使已经离开了局部变量的环境也不会被释放或者删除，在闭包中可以继续使用这个局部变量。\n同一个函数与不同引用环境组合，可以形成不同的实例。\n一个函数类型就像结构体一样，可以被实例化，函数本身不存储任何信息，只有与引用环境结合后形成的闭包才具有记忆性。\n函数是编译期静态的概念，而闭包是运行期动态的概念。\n闭包的作用 可以引用另一个函数内部的局部变量 让这些变量的值始终保持在内存中，使得局部变量不会在函数执行完以后被释放 例如，实现一个累加器\npackage main import \u0026#34;fmt\u0026#34; func main() { f := add() fmt.Println(f()) //1 fmt.Println(f()) //2 fmt.Println(f()) //3 fmt.Println(f()) //4 } func add() func() int { i := 0 return func() int { i++ return i } } 理解代码： 当函数add()中的匿名函数被函数add()外的变量f引用的时候，就创建了一个闭包。 由于闭包的存在，所以函数add()中的变量i始终存在，gc不会回收变量i，因为函数add()中的匿名函数执行需要依赖变量i。 注意： 由于闭包里被引用局部变量不会被立刻销毁回收，可能会占用更多的内存。过度使用闭包会导致性能下降。 defer关键字 # Go语言的 defer 语句会将其后面跟随的语句进行延迟处理，在 defer所属的函数即将返回时，将延迟处理的语句按 defer声明的逆序进行执行。也就是说，先被 defer 的语句最后被执行；最后被 defer 的语句，最先被执行。\n在同一个函数中，defer可以看作为栈操作，也就是先注册的defer语句在栈底，所以后进先出\n关键字 defer 的用法类似于面向对象编程语言Java的 finally 语句块，它一般用于释放某些已分配的资源，典型的例子就是对一个互斥解锁，或者关闭一个文件。\npackage main import \u0026#34;fmt\u0026#34; func main() { fmt.Println(\u0026#34;start\u0026#34;) defer fmt.Println(\u0026#34;end\u0026#34;) defer fmt.Println(3) defer fmt.Println(2) defer fmt.Println(1) /* start 1 2 3 end */ } defer执行匿名函数 # package main import \u0026#34;fmt\u0026#34; func main() { defer func() { fmt.Println(\u0026#34;hello\u0026#34;) fmt.Printf(\u0026#34;world\u0026#34;) }() } 即时求值的变量快照 # 程序执行到defer语句的时候（而不是defer语句执行的时候），如果defer后调用的函数需要传参，那么就会把变量值传递给函数。\npackage main import \u0026#34;fmt\u0026#34; func main() { name := \u0026#34;go\u0026#34; defer func(n string) { fmt.Println(n) // go }(name) name = \u0026#34;python\u0026#34; } 但是，如果defer后面是无参的匿名函数，那么程序无参可传，就会继续往下执行。直到defer语句执行的时候，才会去获取变量值。\npackage main import \u0026#34;fmt\u0026#34; func main() { name := \u0026#34;go\u0026#34; defer func() { fmt.Println(name) // python }() name = \u0026#34;python\u0026#34; } defer和return # 在Go语言中，return语句在defer前执行\npackage main import \u0026#34;fmt\u0026#34; func main() { compare() } func compare() int { defer deferFunc() return returnFunc() } func deferFunc() int { fmt.Println(\u0026#34;defer\u0026#34;) return 0 } func returnFunc() int { fmt.Println(\u0026#34;return\u0026#34;) return 0 } /* return defer */ 虽然return语句是在defer语句前执行，但是对于底层，return语句并不是原子操作，它分为给返回值赋值和RET指令两步。defer语句的执行时机就在返回值赋值操作后，RET指令执行前。\npackage main import \u0026#34;fmt\u0026#34; func main() { fmt.Println(ff()) // 0 fmt.Println(tt()) // 1 } /* ff()的return过程如下： 1、返回值赋值：returnVariable = (a = 0) 2、执行defer：a++ 3、RET指令执行，函数结束 第二步a++实际是改变了局部变量a，而并没有改变返回值returnVariable，所以返回值还是0 */ func ff() int { a := 0 defer func() { a++ }() return a } /* tt()的return过程如下： 1、返回值赋值：a = 0（根据Go语言规则，类型声明默认赋值为零值） 2、执行defer：a++ 3、RET指令执行，函数结束 由于是命名返回值，所以返回值就是变量a，第二步a++改变了返回值a，所以返回值是1 */ func tt() (a int) { defer func() { a++ }() return } 内置函数 # Go 语言拥有一些不需要进行导入操作就可以使用的内置函数。它们有时可以针对不同的类型进行操作，例如：len、cap 和 append，或必须用于系统级的操作，例如：panic。因此，它们需要直接获得编译器的支持。\nappend -- 用来追加元素到数组、slice中,返回修改后的数组、slice close -- 主要用来关闭channel delete -- 从map中删除key对应的value panic -- 停止常规的goroutine （panic和recover：用来做错误处理） recover -- 允许程序定义goroutine的panic动作 real -- 返回complex的实部 （complex、real imag：用于创建和操作复数） imag -- 返回complex的虚部 make -- 用来分配内存，返回Type本身(只能应用于slice, map, channel) new -- 用来分配内存，主要用来分配值类型指针，比如*int、*string。 cap -- capacity是容量的意思，用于返回某个类型的最大容量（只能用于切片和 map） copy -- 用于复制和连接slice，返回复制的数目 len -- 来求长度，比如string、array、slice、map、channel ，返回长度 print、println -- 底层打印函数，在开发中建议使用 fmt 包 函数式编程 # 函数式编程是一种强调用函数计算的编程范式，它通过组合纯函数、避免状态共享和可变数据以及使用声明式风格来构建软件。虽然Go不是一种纯函数式语言，但它提供了许多支持函数式编程风格的特性。\n函数式编程的核心概念 # 函数式编程的基本原则 # 函数作为一等公民：函数可以作为变量、参数和返回值使用 纯函数：函数的输出仅由输入决定，无副作用 不可变性：避免修改现有数据，而是创建新数据 声明式风格：描述做什么而非如何做 高阶函数：接受函数作为参数或返回函数的函数 递归：使用递归而非循环处理数据结构 组合：通过组合简单函数构建复杂功能 函数式编程的优势 # 更易于理解和推理：纯函数易于理解，因为它们仅依赖于输入 易于测试：无副作用的函数更容易进行单元测试 并发安全：不可变数据和无共享状态使并发编程更安全 惰性求值：只在需要时计算值，提高效率 模块化：通过函数组合实现高度模块化 更少的bug：消除副作用和可变状态减少了常见错误 函数式编程语言的谱系 # 纯函数式语言：Haskell、Elm，强调纯函数和不可变性 支持函数式编程的多范式语言：Scala、OCaml，支持多种编程范式 带有函数式特性的传统语言：JavaScript、Python、Go，提供部分函数式编程支持 Go语言中的函数式编程特性 # 尽管Go主要是一种命令式语言，但它提供了多种支持函数式编程的特性。\nGo语言中的一等函数 # 在Go中，函数是一等公民（first-class citizens），这意味着函数可以：\n作为变量赋值 作为参数传递给其他函数 作为函数的返回值 存储在数据结构中 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;strings\u0026#34; ) // 定义一个函数类型 type StringProcessor func(string) string // 接受函数作为参数 func processString(s string, processor StringProcessor) string { return processor(s) } // 返回函数的高阶函数 func createPrefixer(prefix string) StringProcessor { return func(s string) string { return prefix + s } } func main() { // 函数赋值给变量 toUpper := strings.ToUpper // 函数作为参数 result1 := processString(\u0026#34;hello\u0026#34;, toUpper) fmt.Println(result1) // 输出: HELLO // 使用返回的函数 addGo := createPrefixer(\u0026#34;Go \u0026#34;) result2 := addGo(\u0026#34;Programming\u0026#34;) fmt.Println(result2) // 输出: Go Programming // 匿名函数 result3 := processString(\u0026#34;hello world\u0026#34;, func(s string) string { return strings.ReplaceAll(s, \u0026#34; \u0026#34;, \u0026#34;-\u0026#34;) }) fmt.Println(result3) // 输出: hello-world } 匿名函数和闭包 # 闭包在Go中有许多实际应用：\n延迟执行：结合defer使用闭包 资源管理：创建带有清理功能的资源处理函数 回调函数：将闭包作为回调传递 中间件：构建HTTP中间件链 懒加载：按需计算值 package main import ( \u0026#34;fmt\u0026#34; ) // 返回一个计数器函数的工厂函数 func createCounter(initial int) func() int { // count变量被闭包捕获 count := initial // 返回增加计数的闭包 return func() int { count++ return count } } // 使用闭包实现带有私有状态的函数 func createMultiplier(factor int) func(int) int { return func(value int) int { return value * factor } } func main() { // 创建并使用计数器 counter1 := createCounter(0) counter2 := createCounter(10) fmt.Println(counter1()) // 1 fmt.Println(counter1()) // 2 fmt.Println(counter2()) // 11 fmt.Println(counter2()) // 12 fmt.Println(counter1()) // 3 // 创建并使用乘法器 double := createMultiplier(2) triple := createMultiplier(3) fmt.Println(double(5)) // 10 fmt.Println(triple(5)) // 15 } 函数选项模式 # 函数选项模式是Go中一种流行的函数式编程模式，用于配置复杂对象：\n函数选项模式的优势：\n灵活性：可以选择性地提供配置 可扩展性：可以添加新选项而不破坏现有代码 可读性：函数调用清晰地表达了意图 默认值：可以为未指定的选项提供合理的默认值 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) // MyServer 结构体 type MyServer struct { host string port int timeout time.Duration maxConn int tls bool } // MyServerOption 是配置 MyServer 的函数 type MyServerOption func(*MyServer) // WithHost 配置 host func WithHost(host string) MyServerOption { return func(server *MyServer) { server.host = host } } // WithPort 配置 port func WithPort(port int) MyServerOption { return func(server *MyServer) { server.port = port } } // WithTimeout 配置 timeout func WithTimeout(timeout time.Duration) MyServerOption { return func(server *MyServer) { server.timeout = timeout } } // WithMaxConn 配置 maxConn func WithMaxConn(maxConn int) MyServerOption { return func(server *MyServer) { server.maxConn = maxConn } } // WithTls 配置 tls func WithTls(tls bool) MyServerOption { return func(server *MyServer) { server.tls = tls } } // NewMyServer 创建一个新的 MyServer func NewMyServer(options ...MyServerOption) *MyServer { // 配置默认值 myServer := \u0026amp;MyServer{ host: \u0026#34;127.0.0.1\u0026#34;, port: 8080, timeout: 30 * time.Second, maxConn: 100, tls: false, } // 应用配置 for _, op := range options { op(myServer) } return myServer } func main() { // 使用默认值 ms1 := NewMyServer() // 使用自定义配置 ms2 := NewMyServer( WithHost(\u0026#34;192.168.0.1\u0026#34;), WithPort(80), WithMaxConn(60), ) fmt.Println(*ms1) fmt.Println(*ms2) } 不可变数据的模拟 # 虽然Go没有内置的不可变数据类型，但可以通过约定和封装来模拟不可变性：\npackage main import \u0026#34;fmt\u0026#34; // 不可变Point type Point struct { x, y int } // 创建新Point的构造函数 func NewPoint(x, y int) Point { return Point{x, y} } // Add返回一个新Point，而不修改原Point func (p Point) Add(other Point) Point { return Point{ x: p.x + other.x, y: p.y + other.y, } } // Scale返回一个新Point，原Point不变 func (p Point) Scale(factor int) Point { return Point{ x: p.x * factor, y: p.y * factor, } } // 不可变的字符串处理函数 func Reverse(s string) string { runes := []rune(s) for i, j := 0, len(runes)-1; i \u0026lt; j; i, j = i+1, j-1 { runes[i], runes[j] = runes[j], runes[i] } return string(runes) } // 不可变的切片操作 func AppendWithoutModifying(slice []int, elements ...int) []int { // 创建新切片而非修改原切片 newSlice := make([]int, len(slice), len(slice)+len(elements)) copy(newSlice, slice) return append(newSlice, elements...) } func main() { // 不可变Point示例 p1 := NewPoint(5, 10) p2 := NewPoint(3, 7) p3 := p1.Add(p2) p4 := p1.Scale(2) fmt.Println(\u0026#34;p1:\u0026#34;, p1) // 原点不变 fmt.Println(\u0026#34;p3:\u0026#34;, p3) // 新的点 fmt.Println(\u0026#34;p4:\u0026#34;, p4) // 新的点 // 不可变字符串操作 s1 := \u0026#34;Hello\u0026#34; s2 := Reverse(s1) fmt.Println(\u0026#34;s1:\u0026#34;, s1) // 原字符串不变 fmt.Println(\u0026#34;s2:\u0026#34;, s2) // 新字符串 // 不可变切片操作 slice1 := []int{1, 2, 3} slice2 := AppendWithoutModifying(slice1, 4, 5) fmt.Println(\u0026#34;slice1:\u0026#34;, slice1) // 原切片不变 fmt.Println(\u0026#34;slice2:\u0026#34;, slice2) // 新切片 } 高阶函数在Go中的应用 # 高阶函数是函数式编程的核心特性之一，它们接受函数作为参数或返回函数作为结果。Go支持高阶函数，使得我们可以编写更灵活和抽象的代码。\n函数变换（map、filter、reduce） # 函数式编程中最常见的高阶函数是map、filter和reduce。虽然Go的标准库没有直接提供这些函数，但我们可以轻松实现它们：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;strings\u0026#34; ) // Map对切片中的每个元素应用函数，返回新切片 func Map[T, U any](slice []T, f func(T) U) []U { result := make([]U, len(slice)) for i, v := range slice { result[i] = f(v) } return result } // Filter返回满足条件的元素切片 func Filter[T any](slice []T, f func(T) bool) []T { var result []T for _, v := range slice { if f(v) { result = append(result, v) } } return result } // Reduce将切片归约为单个值 func Reduce[T, U any](slice []T, initial U, f func(U, T) U) U { result := initial for _, v := range slice { result = f(result, v) } return result } func main() { // 原始数据 numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} words := []string{\u0026#34;apple\u0026#34;, \u0026#34;banana\u0026#34;, \u0026#34;cherry\u0026#34;, \u0026#34;date\u0026#34;, \u0026#34;elderberry\u0026#34;} // 使用Map doubled := Map(numbers, func(n int) int { return n * 2 }) fmt.Println(\u0026#34;Doubled:\u0026#34;, doubled) uppercased := Map(words, strings.ToUpper) fmt.Println(\u0026#34;Uppercased:\u0026#34;, uppercased) // 使用Filter evens := Filter(numbers, func(n int) bool { return n%2 == 0 }) fmt.Println(\u0026#34;Even numbers:\u0026#34;, evens) longWords := Filter(words, func(s string) bool { return len(s) \u0026gt; 5 }) fmt.Println(\u0026#34;Long words:\u0026#34;, longWords) // 使用Reduce sum := Reduce(numbers, 0, func(acc, n int) int { return acc + n }) fmt.Println(\u0026#34;Sum:\u0026#34;, sum) concatenated := Reduce(words, \u0026#34;\u0026#34;, func(acc string, s string) string { if acc == \u0026#34;\u0026#34; { return s } return acc + \u0026#34;, \u0026#34; + s }) fmt.Println(\u0026#34;Concatenated:\u0026#34;, concatenated) // 组合使用 sumOfEvenDoubles := Reduce( Map( Filter(numbers, func(n int) bool { return n%2 == 0 }), func(n int) int { return n * 2 }, ), 0, func(acc, n int) int { return acc + n }, ) fmt.Println(\u0026#34;Sum of doubled even numbers:\u0026#34;, sumOfEvenDoubles) } 装饰器模式 # 装饰器模式允许我们在不改变函数接口的情况下增强其功能。这在中间件、日志记录和性能监控等场景中很有用：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;time\u0026#34; ) // 定义函数类型 type StringFunc func(string) string // 日志装饰器 func LogDecorator(fn StringFunc, name string) StringFunc { return func(s string) string { log.Printf(\u0026#34;Calling %s with input: %s\u0026#34;, name, s) result := fn(s) log.Printf(\u0026#34;%s returned: %s\u0026#34;, name, result) return result } } // 计时装饰器 func TimingDecorator(fn StringFunc) StringFunc { return func(s string) string { start := time.Now() result := fn(s) elapsed := time.Since(start) log.Printf(\u0026#34;Function took %s\u0026#34;, elapsed) return result } } // 重试装饰器 func RetryDecorator(fn StringFunc, attempts int) StringFunc { return func(s string) (result string) { for i := 0; i \u0026lt; attempts; i++ { result = fn(s) if result != \u0026#34;\u0026#34; { // 假设空结果表示失败 return } log.Printf(\u0026#34;Attempt %d failed, retrying...\u0026#34;, i+1) } return } } // 一些示例函数 func Reverse(s string) string { runes := []rune(s) for i, j := 0, len(runes)-1; i \u0026lt; j; i, j = i+1, j-1 { runes[i], runes[j] = runes[j], runes[i] } return string(runes) } func Uppercase(s string) string { return strings.ToUpper(s) } func main() { // 装饰简单函数 decoratedReverse := LogDecorator(Reverse, \u0026#34;Reverse\u0026#34;) decoratedReverse = TimingDecorator(decoratedReverse) fmt.Println(decoratedReverse(\u0026#34;Hello, World!\u0026#34;)) // 组合多个装饰器 processString := RetryDecorator( TimingDecorator( LogDecorator(Uppercase, \u0026#34;Uppercase\u0026#34;), ), 3, ) fmt.Println(processString(\u0026#34;test\u0026#34;)) } 中间件模式 # 中间件是Web开发中常见的一种特殊装饰器模式，它被用于HTTP处理器的链式处理：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;time\u0026#34; ) // 定义处理器类型 type Handler func(http.ResponseWriter, *http.Request) // 将自定义Handler转换为http.HandlerFunc func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h(w, r) } // 记录请求的中间件 func LoggingMiddleware(next Handler) Handler { return func(w http.ResponseWriter, r *http.Request) { start := time.Now() log.Printf(\u0026#34;Started %s %s\u0026#34;, r.Method, r.URL.Path) next(w, r) log.Printf(\u0026#34;Completed %s %s in %v\u0026#34;, r.Method, r.URL.Path, time.Since(start)) } } // 恢复panic的中间件 func RecoveryMiddleware(next Handler) Handler { return func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { log.Printf(\u0026#34;Panic: %v\u0026#34;, err) http.Error(w, \u0026#34;Internal Server Error\u0026#34;, http.StatusInternalServerError) } }() next(w, r) } } // 认证中间件 func AuthMiddleware(next Handler) Handler { return func(w http.ResponseWriter, r *http.Request) { // 简化的认证检查 authToken := r.Header.Get(\u0026#34;Authorization\u0026#34;) if authToken == \u0026#34;\u0026#34; { http.Error(w, \u0026#34;Unauthorized\u0026#34;, http.StatusUnauthorized) return } // 通过认证，继续处理 next(w, r) } } // 应用中间件的辅助函数 func ApplyMiddleware(h Handler, middlewares ...func(Handler) Handler) Handler { for i := len(middlewares) - 1; i \u0026gt;= 0; i-- { h = middlewares[i](h) } return h } // 处理函数 func HelloHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, \u0026#34;Hello, World!\u0026#34;) } func AdminHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, \u0026#34;Admin Area\u0026#34;) } func PanicHandler(w http.ResponseWriter, r *http.Request) { panic(\u0026#34;Something went wrong!\u0026#34;) } func main() { // 应用中间件 http.Handle(\u0026#34;/\u0026#34;, ApplyMiddleware( HelloHandler, LoggingMiddleware, RecoveryMiddleware, )) http.Handle(\u0026#34;/admin\u0026#34;, ApplyMiddleware( AdminHandler, LoggingMiddleware, RecoveryMiddleware, AuthMiddleware, )) http.Handle(\u0026#34;/panic\u0026#34;, ApplyMiddleware( PanicHandler, LoggingMiddleware, RecoveryMiddleware, )) log.Println(\u0026#34;Server starting on :8080\u0026#34;) log.Fatal(http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil)) } ","date":"2025-05-16","externalUrl":null,"permalink":"/posts/2d3150b2/02a0817f/17085d5b/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e函数 \n    \u003cdiv id=\"函数\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%87%bd%e6%95%b0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e函数构成了代码执行的逻辑结构，在Go语言中，函数的基本组成为：\u003cstrong\u003e关键字 func、函数名、参数列表、返回值、函数体、返回语句\u003c/strong\u003e，每一个程序都包含很多的函数，函数是基本的代码块。\u003c/p\u003e","title":"8、函数","type":"posts"},{"content":" 单元测试 # Go测试的基本概念 # Go语言自带了 testing 测试包，可以进行自动化的单元测试，输出结果验证，并且可以测试性能。\n什么是单元测试 # 单元测试是针对程序中最小可测试单元（通常是函数或方法）的测试。在Go中，这通常指对特定函数或方法的输入和输出进行验证，确保它们按预期工作。单元测试的目标是：\n验证代码的正确性 防止回归（确保修改不会破坏现有功能） 促进代码重构 提供文档和示例 Go测试文件命名约定 # Go的测试文件遵循以下命名约定：\n测试文件以_test.go结尾 测试文件通常与被测代码在同一包中 测试函数名以Test开头 例如，如果你有一个名为calculator.go的文件，对应的测试文件应命名为calculator_test.go。\n测试函数结构 # 测试函数必须以Test开头，后跟大写字母开头的单词 测试函数必须接受*testing.T类型的参数 测试函数没有返回值 func TestXxx(t *testing.T) { // 测试代码 } 进行基本测试 # 代码编写 # // calculator.go package calculator // Add 返回两个整数的和 func Add(a, b int) int { return a + b } // Subtract 返回两个整数的差 func Subtract(a, b int) int { return a - b } // Multiply 返回两个整数的乘积 func Multiply(a, b int) int { return a * b } // Divide 返回两个整数的商，如果除数为0则panic func Divide(a, b int) int { if b == 0 { panic(\u0026#34;除数不能为0\u0026#34;) } return a / b } 现在，为这些函数编写测试：\n// calculator_test.go package calculator import ( \u0026#34;testing\u0026#34; ) func TestAdd(t *testing.T) { result := Add(3, 2) expected := 5 if result != expected { t.Errorf(\u0026#34;Add(3, 2) = %d; 期望 %d\u0026#34;, result, expected) } } func TestSubtract(t *testing.T) { result := Subtract(5, 2) expected := 3 if result != expected { t.Errorf(\u0026#34;Subtract(5, 2) = %d; 期望 %d\u0026#34;, result, expected) } } func TestMultiply(t *testing.T) { result := Multiply(4, 3) expected := 12 if result != expected { t.Errorf(\u0026#34;Multiply(4, 3) = %d; 期望 %d\u0026#34;, result, expected) } } func TestDivide(t *testing.T) { result := Divide(6, 2) expected := 3 if result != expected { t.Errorf(\u0026#34;Divide(6, 2) = %d; 期望 %d\u0026#34;, result, expected) } } func TestDivideByZero(t *testing.T) { // 使用匿名函数封装可能panic的操作 defer func() { if r := recover(); r != nil { t.Errorf(\u0026#34;%s\u0026#34;, r) } }() Divide(6, 0) } 运行测试 # 可以使用go test命令运行测试：\ngo test # 运行当前包中的所有测试 go test -v # 详细模式，显示每个测试函数的结果 go test ./... # 运行当前目录及其子目录中的所有测试 go test -run TestAdd # 只运行名称匹配\u0026#34;TestAdd\u0026#34;的测试 理解测试输出 # 使用go test -v ygang.top/demo/calculator在详细模式下进行测试，得到如下结果\nyanggang@MacBook demo % go test -v ygang.top/demo/calculator === RUN TestAdd --- PASS: TestAdd (0.00s) === RUN TestSubtract --- PASS: TestSubtract (0.00s) === RUN TestMultiply --- PASS: TestMultiply (0.00s) === RUN TestDivide --- PASS: TestDivide (0.00s) === RUN TestDivideByZero calculator_test.go:48: 除数不能为0 --- FAIL: TestDivideByZero (0.00s) FAIL FAIL ygang.top/demo/calculator 0.353s FAIL 表格驱动测试 # Go社区推崇\u0026quot;表格驱动测试\u0026quot;的模式，这种模式通过创建测试用例表格，使测试更加简洁和可维护。\n下面以表格驱动的方式对 Add 方法进行测试\n表格驱动测试的主要优势：\n易于添加新的测试用例 代码重复减少 测试报告更有信息量 更好的测试覆盖范围 func TestAdd_TableDriven(t *testing.T) { // 定义测试用例表 tests := []struct { name string // 测试名称 a, b int // 输入参数 expected int // 期望结果 }{ {\u0026#34;正数相加\u0026#34;, 3, 2, 5}, {\u0026#34;负数相加\u0026#34;, -3, -2, -5}, {\u0026#34;正负相加\u0026#34;, 3, -2, 1}, {\u0026#34;零值处理\u0026#34;, 0, 0, 0}, {\u0026#34;大数相加\u0026#34;, 1000000, 1000000, 2000000}, } // 遍历测试用例表 for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := Add(tt.a, tt.b) if result != tt.expected { t.Errorf(\u0026#34;Add(%d, %d) = %d; 期望 %d\u0026#34;, tt.a, tt.b, result, tt.expected) } }) } } 执行go test ygang.top/demo/calculator -v -run TestAdd_TableDriven\nyanggang@MacBook demo % go test ygang.top/demo/calculator -v -run TestAdd_TableDriven === RUN TestAdd_TableDriven === RUN TestAdd_TableDriven/正数相加 === RUN TestAdd_TableDriven/负数相加 === RUN TestAdd_TableDriven/正负相加 === RUN TestAdd_TableDriven/零值处理 === RUN TestAdd_TableDriven/大数相加 --- PASS: TestAdd_TableDriven (0.00s) --- PASS: TestAdd_TableDriven/正数相加 (0.00s) --- PASS: TestAdd_TableDriven/负数相加 (0.00s) --- PASS: TestAdd_TableDriven/正负相加 (0.00s) --- PASS: TestAdd_TableDriven/零值处理 (0.00s) --- PASS: TestAdd_TableDriven/大数相加 (0.00s) PASS ok ygang.top/demo/calculator (cached) 测试工具函数 # testing.T类型提供了多种断言和控制测试流程的方法：\nfunc TestWithHelpers(t *testing.T) { // 1. Fatal和Fatalf - 报告失败并立即停止测试执行 if !checkSetup() { t.Fatal(\u0026#34;环境设置失败，无法继续测试\u0026#34;) } // 2. Error和Errorf - 报告失败但继续执行测试 result := Multiply(4, 3) if result != 12 { t.Errorf(\u0026#34;Multiply(4, 3) = %d; 期望 12\u0026#34;, result) } // 3. Log和Logf - 记录测试信息（仅在详细模式或测试失败时显示） t.Log(\u0026#34;乘法测试完成\u0026#34;) // 4. Skip和Skipf - 跳过当前测试（例如特定环境不适用） if testing.Short() { t.Skip(\u0026#34;在短模式下跳过此测试\u0026#34;) } // 5. Helper - 标记函数为辅助函数（错误报告中正确标注行号） t.Helper() // 继续测试... } 创建辅助函数 # 良好的测试代码应当使用辅助函数减少重复代码：\n// 通用的断言辅助函数 func assertIntEqual(t *testing.T, got, want int, name string, args ...interface{}) { t.Helper() // 标记为辅助函数，错误将定位到调用位置 if got != want { if len(args) \u0026gt; 0 { t.Errorf(\u0026#34;%s(%v) = %d; 期望 %d\u0026#34;, name, args, got, want) } else { t.Errorf(\u0026#34;%s = %d; 期望 %d\u0026#34;, name, got, want) } } } // 使用辅助函数的测试 func TestWithAssertHelper(t *testing.T) { t.Run(\u0026#34;Add\u0026#34;, func(t *testing.T) { assertIntEqual(t, Add(3, 2), 5, \u0026#34;Add\u0026#34;, 3, 2) }) t.Run(\u0026#34;Subtract\u0026#34;, func(t *testing.T) { assertIntEqual(t, Subtract(5, 2), 3, \u0026#34;Subtract\u0026#34;, 5, 2) }) t.Run(\u0026#34;Multiply\u0026#34;, func(t *testing.T) { assertIntEqual(t, Multiply(4, 3), 12, \u0026#34;Multiply\u0026#34;, 4, 3) }) } 基准测试 # 基准测试基础 # 什么是基准测试 # 基准测试(Benchmark)是评估代码性能的测量工具，用于确定代码执行所需的时间和资源。在Go中，基准测试可以帮助我们：\n量化代码性能 比较不同实现方案的效率 发现性能退化 指导优化决策 验证性能改进 Go中的基准测试框架 # Go的testing包不仅支持单元测试，还内置了强大的基准测试功能。基准测试函数遵循以下规则：\n函数名以Benchmark开头，后跟大写字母开头的单词 接受*testing.B类型的参数 通常包含一个计时循环 package calculator import \u0026#34;testing\u0026#34; func BenchmarkAdd(b *testing.B) { // 重置计时器（可选） b.ResetTimer() // b.N由测试框架动态确定，以获得稳定的测量结果 for i := 0; i \u0026lt; b.N; i++ { Add(10, 5) } } func BenchmarkMultiply(b *testing.B) { b.ResetTimer() for i := 0; i \u0026lt; b.N; i++ { Multiply(10, 5) } } 运行基准测试 # 使用go test命令运行基准测试：\ngo test -bench=. # 运行所有基准测试 go test -bench=Add # 运行名称匹配\u0026#34;Add\u0026#34;的基准测试 go test -bench=. -benchmem # 同时显示内存分配统计 go test -bench=. -count=5 # 重复测试5次 go test -bench=. -benchtime=10s # 将测试时间延长到10秒 基准测试输出示例：\nyanggang@MacBook demo % go test -v ygang.top/demo/calculator -bench=. -benchmem goos: darwin goarch: arm64 pkg: ygang.top/demo/calculator cpu: Apple M1 Pro BenchmarkAdd BenchmarkAdd-8 1000000000 0.3163 ns/op 0 B/op 0 allocs/op BenchmarkMultiply BenchmarkMultiply-8 1000000000 0.3106 ns/op 0 B/op 0 allocs/op PASS ok ygang.top/demo/calculator 0.986s 输出项含义：\nBenchmarkAdd-8：函数名及GOMAXPROCS设置 1000000000：测试运行了1000000000次 0.3164 ns/op：每次操作平均耗时0.3164ns 0 B/op：每次操作分配了0字节 0 allocs/op：每次操作进行了0次内存分配 性能剖析 # 性能剖析基础 # 什么是性能剖析 # 性能剖析(Profiling)是分析程序执行期间资源使用情况的技术，帮助开发者识别：\n执行时间长的函数 内存分配热点 锁竞争问题 阻塞操作 Go提供了强大的内置剖析工具，主要通过runtime/pprof包和go tool pprof命令实现。\nGo支持的剖析类型 # CPU剖析(CPU Profiling)：记录函数执行期间的CPU时间 内存剖析(Memory Profiling)：记录堆内存分配情况 阻塞剖析(Block Profiling)：记录goroutine阻塞等待的时间 互斥锁剖析(Mutex Profiling)：记录锁竞争情况 Goroutine剖析：分析goroutine的创建和调度情况 启用性能剖析 # 启用性能剖析的方法有以下几种：\n1、测试时启用：通过go test的命令行参数\ngo test -cpuprofile=cpu.prof # CPU剖析 go test -memprofile=mem.prof # 内存剖析 go test -blockprofile=block.prof # 阻塞剖析 2、代码中手动启用：通过pprof包API\npackage main import ( \u0026#34;os\u0026#34; \u0026#34;runtime/pprof\u0026#34; // ... ) func main() { // CPU剖析 cpuFile, _ := os.Create(\u0026#34;cpu.prof\u0026#34;) pprof.StartCPUProfile(cpuFile) defer pprof.StopCPUProfile() // 执行需要分析的代码 doSomethingIntensive() // 内存剖析 memFile, _ := os.Create(\u0026#34;mem.prof\u0026#34;) defer memFile.Close() pprof.WriteHeapProfile(memFile) } 3、Web服务中启用：使用net/http/pprof包\npackage main import ( \u0026#34;net/http\u0026#34; _ \u0026#34;net/http/pprof\u0026#34; // 仅需导入，不需要显式使用 ) func main() { // 启动HTTP服务器 http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) } // 访问 http://localhost:8080/debug/pprof/ 查看剖析数据 使用pprof分析性能 # pprof工具介绍 # pprof是Go的性能分析工具，可以分析和可视化剖析数据。它提供了多种视图，包括：\n火焰图(Flame Graph)：直观显示调用栈和资源使用 调用图(Call Graph)：展示函数调用关系 列表视图(List)：显示每行代码的资源消耗 热点视图(Top)：显示最消耗资源的函数 分析CPU剖析数据 # 假设我们已经生成了CPU剖析文件，现在可以使用pprof工具分析它：\ngo tool pprof cpu.prof # 进入交互式模式后的常用命令: (pprof) top10 # 显示消耗最多CPU的10个函数 (pprof) list functionName # 显示特定函数的代码和CPU使用情况 (pprof) web # 在浏览器中打开可视化视图(需安装Graphviz) (pprof) pdf # 生成PDF格式的调用图 (pprof) flame # 生成火焰图(需安装FlameGraph工具) Web界面示例（需要先安装Graphviz）：\ngo tool pprof -http=:8080 cpu.prof # 启动Web服务器查看剖析数据 内存分析 # 内存分析可以帮助我们找出导致大量内存分配的代码：\ngo test -memprofile=mem.prof go tool pprof -alloc_objects mem.prof # 分析对象分配 go tool pprof -alloc_space mem.prof # 分析分配的内存空间 go tool pprof -inuse_objects mem.prof # 分析仍在使用的对象 go tool pprof -inuse_space mem.prof # 分析仍在使用的内存空间 内存泄漏分析，现将两个节点的剖析文件保存\n// 在程序的关键点获取内存快照 pprof.WriteHeapProfile(firstFile) // ...执行操作... pprof.WriteHeapProfile(secondFile) 然后再比较两个快照找出泄漏\ngo tool pprof --base firstFile secondFile 阻塞和互斥锁分析 # 对于并发程序，分析goroutine的阻塞和锁等待情况很重要：\n// 在代码中启用阻塞剖析 runtime.SetBlockProfileRate(1) // 设置阻塞剖析采样率 // 在代码中启用互斥锁剖析 runtime.SetMutexProfileFraction(1) // 设置互斥锁剖析采样率 启用测试时的阻塞和互斥锁剖析：\ngo test -blockprofile=block.prof go test -mutexprofile=mutex.prof 分析剖析数据：\ngo tool pprof block.prof go tool pprof mutex.prof ","date":"2025-05-14","externalUrl":null,"permalink":"/posts/2d3150b2/0e0cf2f4/a39de38c/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e单元测试 \n    \u003cdiv id=\"单元测试\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8d%95%e5%85%83%e6%b5%8b%e8%af%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eGo测试的基本概念 \n    \u003cdiv id=\"go测试的基本概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#go%e6%b5%8b%e8%af%95%e7%9a%84%e5%9f%ba%e6%9c%ac%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003eGo语言自带了 \u003ccode\u003etesting\u003c/code\u003e 测试包，可以进行自动化的单元测试，输出结果验证，并且可以测试性能。\u003c/p\u003e","title":"8、单元测试","type":"posts"},{"content":" 多进程和多线程的选择 # node.js 目前有两种方案，一种是使用children_process或者cluster开启多进程进行计算，一种是使用worker_thread 开启多线程进行计算\nchildren_process多进程简介 # 我们都知道 Node.js 是以单线程的模式运行的，但它使用的是事件驱动来处理并发，这样有助于我们在多核 cpu 的系统上创建多个子进程，从而提高性能。\n每个子进程总是带有三个流对象：child.stdin, child.stdout 和child.stderr。他们可能会共享父进程的 stdio 流，或者也可以是独立的被导流的流对象。\nNode 提供了 child_process 模块来创建子进程，方法有：\nexec：child_process.exec 使用子进程执行命令，缓存子进程的输出，并将子进程的输出以回调函数参数的形式返回。 spawn：child_process.spawn 使用指定的命令行参数创建新进程。 fork：child_process.fork 是 spawn()的特殊形式，用于在子进程中运行的模块，如 fork('./son.js') 相当于 spawn('node', ['./son.js']) 。与spawn方法不同的是，fork会在父进程与子进程之间，建立一个通信管道，用于进程之间的通信。 exec方法 # child_process.exec 使用子进程执行命令，缓存子进程的输出，并将子进程的输出以回调函数参数的形式返回。\nchild_process.exec(command[, options], callback)\n参数说明如下：\ncommand： 字符串， 将要运行的命令，参数使用空格隔开\noptions ：对象，可以是：\ncwd ，字符串，子进程的当前工作目录\nenv，对象 环境变量键值对\nencoding ，字符串，字符编码（默认：utf8）\nshell ，字符串，将要执行命令的 Shell（默认: 在 UNIX 中为/bin/sh， 在 Windows 中为cmd.exe， Shell 应当能识别 -c开关在 UNIX 中，或 /s /c 在 Windows 中。 在Windows 中，命令行解析应当能兼容cmd.exe）\ntimeout，数字，超时时间（默认： 0）\nmaxBuffer，数字， 在 stdout 或 stderr 中允许存在的最大缓冲（二进制），如果超出那么子进程将会被杀死 （默认: 200*1024）\nkillSignal ，字符串，结束信号（默认：\u0026lsquo;SIGTERM\u0026rsquo;）\nuid，数字，设置用户进程的 ID\ngid，数字，设置进程组的 ID\ncallback ：回调函数，包含三个参数error, stdout 和 stderr。\n返回最大的缓冲区，并等待进程结束，一次性返回缓冲区的内容。\nconst fs = require(\u0026#39;fs\u0026#39;); const child_process = require(\u0026#39;child_process\u0026#39;); for(var i=0; i\u0026lt;3; i++) { var workerProcess = child_process.exec(\u0026#39;node support.js \u0026#39;+i, function (error, stdout, stderr) { if (error) { console.log(error.stack); console.log(\u0026#39;Error code: \u0026#39;+error.code); console.log(\u0026#39;Signal received: \u0026#39;+error.signal); } console.log(\u0026#39;stdout: \u0026#39; + stdout); console.log(\u0026#39;stderr: \u0026#39; + stderr); }); workerProcess.on(\u0026#39;exit\u0026#39;, function (code) { console.log(\u0026#39;子进程已退出，退出码 \u0026#39;+code); }); } spawn方法 # child_process.spawn 使用指定的命令行参数创建新进程，语法格式如下：\nchild_process.spawn(command[, args][, options])\n参数说明如下：\ncommand： 将要运行的命令\nargs： Array 字符串参数数组\noptions\ncwd：String 子进程的当前工作目录\nenv ：Object 环境变量键值对\nstdio ：Array|String 子进程的 stdio 配置\ndetached ：Boolean 这个子进程将会变成进程组的领导\nuid ：Number 设置用户进程的 ID\ngid ：Number 设置进程组的 ID\nspawn方法返回流 (stdout \u0026amp; stderr)，在进程返回大量数据时使用。进程一旦开始执行时 spawn() 就开始接收响应。\nconst fs = require(\u0026#39;fs\u0026#39;); const child_process = require(\u0026#39;child_process\u0026#39;); for(var i=0; i\u0026lt;3; i++) { var workerProcess = child_process.spawn(\u0026#39;node\u0026#39;, [\u0026#39;support.js\u0026#39;, i]); workerProcess.stdout.on(\u0026#39;data\u0026#39;, function (data) { console.log(\u0026#39;stdout: \u0026#39; + data); }); workerProcess.stderr.on(\u0026#39;data\u0026#39;, function (data) { console.log(\u0026#39;stderr: \u0026#39; + data); }); workerProcess.on(\u0026#39;close\u0026#39;, function (code) { console.log(\u0026#39;子进程已退出，退出码 \u0026#39;+code); }); } fork 方法 # child_process.fork 是 spawn() 方法的特殊形式，用于创建进程，语法格式如下：\nchild_process.fork(modulePath[, args][, options])\nmodulePath： String，将要在子进程中运行的模块\nargs： Array 字符串参数数组\noptions：Object\ncwd ：String 子进程的当前工作目录\nenv ：Object 环境变量键值对\nexecPath ：String 创建子进程的可执行文件\nexecArgv： Array 子进程的可执行文件的字符串参数数组（默认： process.execArgv）\nsilent ：Boolean 如果为true，子进程的stdin，stdout和stderr将会被关联至父进程，否则，它们将会从父进程中继承。（默认为：false）\nuid ：Number 设置用户进程的 ID\ngid ：Number 设置进程组的 ID\n返回的对象除了拥有ChildProcess实例的所有方法，还有一个内建的通信信道。\nconst fs = require(\u0026#39;fs\u0026#39;); const child_process = require(\u0026#39;child_process\u0026#39;); for(var i=0; i\u0026lt;3; i++) { var worker_process = child_process.fork(\u0026#34;support.js\u0026#34;, [i]); worker_process.on(\u0026#39;close\u0026#39;, function (code) { console.log(\u0026#39;子进程已退出，退出码 \u0026#39; + code); }); } 多线程 # Node.js V10.5.0 提供了 worker_threads，它比 child_process 或 cluster更轻量级。 与child_process 或 cluster 不同，worker_threads 可以共享内存，通过传输 ArrayBuffer 实例或共享 SharedArrayBuffer 实例来实现。\nNode.js 保持了JavaScript在浏览器中单线程的特点。它的优势是没有线程间数据同步的性能消耗也不会出现死锁的情况。所以它是线程安全并且性能高效的。\n单线程有它的弱点，无法充分利用多核CPU 资源，CPU 密集型计算可能会导致 I/O 阻塞，以及出现错误可能会导致应用崩溃。\n为了解决单线程弱点：\n浏览器端： HTML5 制定了 Web Worker 标准（Web Worker 的作用，就是为 JavaScript 创造多线程环境，允许主线程创建 Worker 线程，将一些任务分配给后者运行）。\nNode端：采用了和 Web Worker相同的思路来解决单线程中大量计算问题 ，官方提供了 child_process 模块和 cluster 模块， cluster 底层是基于child_process实现。\n开启现成的方法 # 使用 Worker 类 # 通过 Worker 类可以创建和管理工作线程。例如，可以创建一个新的线程，并传递一个 js 文件给该线程执行：\nconst { Worker, isMainThread, parentPort } = require(\u0026#39;worker_threads\u0026#39;); if (isMainThread) { const worker = new Worker(\u0026#39;./my-worker.js\u0026#39;); // 主线程的逻辑 } else { // 工作线程的逻辑 parentPort.postMessage(\u0026#39;来自工作线程的问候\u0026#39;); } 使用线程池 # const WorkerPool = require(\u0026#39;workerpool\u0026#39;).pool; const pool = WorkerPool({ maxWorkers: 4 }); pool.exec(someTask).then(result =\u0026gt; { // 处理结果 }); 使用案例 # 1、创建工作线程 # 首先，创建一个工作线程负责计算斐波那契数列。将以下代码保存为 fibonacciWorker.js 文件：\nconst { parentPort } = require(\u0026#39;worker_threads\u0026#39;); function calculateFibonacci(n) { if (n \u0026lt;= 1) return n; return calculateFibonacci(n - 1) + calculateFibonacci(n - 2); } parentPort.on(\u0026#39;message\u0026#39;, (n) =\u0026gt; { const result = calculateFibonacci(n); parentPort.postMessage(result); }); 2、与主线程交互 # 在主线程中，创建多个工作线程，并分配任务给它们。以下是主线程的代码，可以保存为 main.js：\nconst { Worker } = require(\u0026#39;worker_threads\u0026#39;); const numThreads = 4; // 假设我们使用四个工作线程 for (let i = 0; i \u0026lt; numThreads; i++) { const worker = new Worker(\u0026#39;./fibonacciWorker.js\u0026#39;); worker.on(\u0026#39;message\u0026#39;, (result) =\u0026gt; { console.log(`线程 ${i} 返回的斐波那契结果：${result}`); }); worker.postMessage(40); // 计算第40个斐波那契数 } ","date":"2025-04-27","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/65df2b3b/fa1f47a1/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e多进程和多线程的选择 \n    \u003cdiv id=\"多进程和多线程的选择\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%a4%9a%e8%bf%9b%e7%a8%8b%e5%92%8c%e5%a4%9a%e7%ba%bf%e7%a8%8b%e7%9a%84%e9%80%89%e6%8b%a9\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003enode.js 目前有两种方案，一种是使用\u003ccode\u003echildren_process\u003c/code\u003e或者\u003ccode\u003ecluster\u003c/code\u003e开启多进程进行计算，一种是使用\u003ccode\u003eworker_thread\u003c/code\u003e 开启多线程进行计算\u003c/p\u003e","title":"8、多进程和多线程","type":"posts"},{"content":"本来 Tkinter 是有自己的图片对象PhotoImage的，但是这个对象只能解析 gif 图片，所以我们使用 PIL 库进行处理，它可以生成 Tkinter 的PhotoImage\nimport tkinter as tk from PIL import ImageTk,Image # 创建一个顶级窗口 root = tk.Tk() # 加载图片 img = ImageTk.PhotoImage(Image.open(\u0026#39;./123.jpg\u0026#39;)) # 创建一个标签来显示图片 label = tk.Label(root, image=img) label.pack() # 开始主循环 root.mainloop() ","date":"2025-04-15","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/a879fe06/0e440a9c/","section":"文章","summary":"\u003cp\u003e本来 Tkinter 是有自己的图片对象\u003ccode\u003ePhotoImage\u003c/code\u003e的，但是这个对象只能解析 gif 图片，所以我们使用 PIL 库进行处理，它可以生成 Tkinter 的\u003ccode\u003ePhotoImage\u003c/code\u003e\u003c/p\u003e","title":"8、图片","type":"posts"},{"content":" 异常的体系结构 # java.lang.Throwable java.lang.Error：一般不编写针对性的代码进行处理，发生后，会直接导致JVM不可处理。 java.lang.Exception：可以进行异常的处理 编译时异常(checked受检异常) IOException FileNotFoundException ClassNotFoundException 运行时异常(unchecked非受检异常，RuntimeException) NullPointerException ArrayIndexOutOfBoundsException ClassCastException NumberFormatException InputMismatchException ArithmeticException java.lang.Exception是所有异常的根父类\n运行时异常和非运行时异常的区别 # 1、运行时异常\n**是指编译器不要求强制处置的异常。**一般是指编程时的逻辑错误，是程序员应该积极避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常。\n对于这类异常，可以不作处理，因为这类异常很普遍，若全处理可能会对程序的可读性和运行效率产生影响。\n2、非运行时异常\n**是指编译器要求必须处置的异常。**即程序在运行时由于外界因素造成的一 般性异常。编译器要求Java程序必须捕获或声明所有编译时异常。\n对于这类异常，如果程序不处理，可能会带来意想不到的结果。\n异常的处理 # java异常处理的抓抛模型 # 过程一：\u0026ldquo;抛\u0026rdquo;：程序在正常执行的过程中，一旦出现异常，就会在异常代码处生成一个对应异常类的对象，并将此对象抛出。\n一旦抛出异常对象以后，其后的代码就不再执行。 关于异常对象的产生方式 系统自动生成的异常对象 手动的生成一个异常对象，并抛出（throw） 过程二：\u0026ldquo;抓\u0026rdquo;：可以理解为异常的处理方式：1、try-catch-finally 2、throws\n异常处理方式一： # try{ //可能出现异常的代码 }catch(异常类型1 变量名1){ //处理异常的方式1 }catch(异常类型2 变量名2){ //处理异常的方式2 }catch(异常类型3 变量名3){ //处理异常的方式3 } .... finally{ //一定会执行的代码 } 说明： # catch和finally都是可选的，至少出现一个即可！\n使用try将可能出现异常代码包装起来，在执行过程中，一旦出现异常，就会生成一个对应异常类的对象，根据此对象的类型，去catch中进行匹配\n一旦try中的异常对象匹配到某一个catch时，就进入catch中进行异常的处理。一旦处理完成，就跳出当前的try-catch结构（在没写finally的情况。继续执行其后的代码）\ncatch中的异常类型如果没子父类关系，则谁声明在上，谁声明在下无所谓。\ncatch中的异常类型如果满足子父类关系，则要求子类一定声明在父类的上面，否则会报错\n常用的异常对象处理的方式：\nString getMessage()获取异常详细信息 string toString()获取异常简要信息 void printStackTrace()打印异常堆栈信息 在try结构中声明的变量，再出了try结构以后，就不能再被调用\ntry-catch-finally结构可以嵌套\nfinally说明： # finally是可选的 finally中声明的是一定会被执行的代码。即使catch中又出现异常了，try中return语句，catch中return语句等情况。 像数据库连接、输入输出流、网络编程Socket等资源，JVM是不能自动的回收的，我们需要自己手动的进行资源的释放。此时的资源释放，就需要声明在finally中。 异常处理方式二： # public void 方法名() throws 异常类型1,异常类型2{ if(何时异常){ throw 异常对象; } } 一个方法可以声明多个类型的异常；\nthrows + 异常类型写在方法的声明处。指明此方法执行时，可能会抛出的异常类型。\n一旦当方法体执行时，出现异常，仍会在异常代码处生成一个异常类的对象，此对象满足throws后异常类型时，就会被抛出。异常代码后续的代码，就不再执行！\nthrow 和 throws区别： # throw：表示手动抛出一个异常类的对象，生成异常对象的过程，声明在方法体内。\nthrows：属于异常处理的一种方式，声明在方法的声明处。\n对比两种处理方式 # try-catch-finally：真正的将异常给处理掉了。\nthrows：只是将异常抛给了方法的调用者，并没真正将异常处理掉。\n自定义异常类 # 继承于现有的异常结构：RuntimeException 、Exception 提供全局常量：private static final long serialVersionUID = 1L 提供重载的构造器 断言 # Java 是从 JDK1.4 开始支持断言的，主要用于程序代码的调试或测试阶段，千万不能用在正式环境上。当然啦，JVM是默认关闭断言的，想要开启断言还得向 JVM 输入一个参数-enableassertions才可以启用断言，或者缩写ea。\n使用方法 # assert boolValue; assert boolValue : \u0026#34;断言失败\u0026#34;; 我们需要在assert关键字后放置一个布尔值（也可以是一个表达式，这个表达式也会变成一个布尔值），当这个布尔值为 true 时，会通过整个断言；当这个布尔值为 false 时，这个断言就会抛出一个错误（！不是异常，不能被try-catch捕获），这会让整个程序停止。\n","date":"2025-02-12","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/f742d77f/c0828172/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e异常的体系结构 \n    \u003cdiv id=\"异常的体系结构\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%bc%82%e5%b8%b8%e7%9a%84%e4%bd%93%e7%b3%bb%e7%bb%93%e6%9e%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003ejava.lang.Throwable\u003c/code\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003ejava.lang.Error\u003c/code\u003e：一般不编写针对性的代码进行处理，发生后，会直接导致JVM不可处理。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ejava.lang.Exception\u003c/code\u003e：可以进行异常的处理\n\u003cul\u003e\n\u003cli\u003e编译时异常(\u003cstrong\u003echecked受检异常\u003c/strong\u003e)\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eIOException\u003c/code\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eFileNotFoundException\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eClassNotFoundException\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e运行时异常(\u003cstrong\u003eunchecked非受检异常\u003c/strong\u003e，\u003ccode\u003eRuntimeException\u003c/code\u003e)\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eNullPointerException\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eArrayIndexOutOfBoundsException\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eClassCastException\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eNumberFormatException\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eInputMismatchException\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eArithmeticException\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"说明: untitle.png\"\n    data-zoom-src=\"/posts/3ab7256e/80aacaea/f742d77f/c0828172/image/202109181147955.gif\"\n    src=\"/posts/3ab7256e/80aacaea/f742d77f/c0828172/image/202109181147955.gif\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"8、异常处理","type":"posts"},{"content":" 什么是注解 # 注解（Annotation），也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性，与类、接口、枚举是在同一个层次。\n它作用于程序元素（类、字段、方法、局部变量、方法参数等）的上面，用于一些工具在编译、运行时进行解析和使用，起到说明、配置的功能。\n与注释的区别 # 注释：用来解释说明，是给程序员看的\n注解：用来解释说明，是给程序看的\n功能 # 生成文档这是最常见的，也是java最早提供的注解，常用的有@param，@return等\n在编译时进行格式检查，如@Override放在方法上，如果这个方法并不是覆盖了超类方法，则编译时就能检查出\n跟踪代码依赖性，实现替代配置文件功能，比较常见的是spring2.5开始的基于注解配置，作用就是减少配置\n在反射的Class，Method，Field等类的实例函数中，有许多于 Annotation 相关的接口，可以在反射中解析并使用 Annotation。\n使用注解 # 将注解写在类，方法，成员变量的上面即可\n如果注解有成员（无默认值），那么在使用的时候必须在注解名后指明成员的值\n如果一个注解中有多个属性的时候，我们在使用注解的时候，要给所有的属性附上值，之间用逗号分隔。\n@MyAnnotation(name=\u0026quot;lucy\u0026quot;,age=12) 在注解中，有一个非常特别的属性名，叫做value，如果一个注解中，只有一个属性，而且这个属性的名字叫做value的话，那我们在使用注解的时候，就可以不写该属性的名字，例如@Annotation(\u0026quot;val\u0026quot;)\n@AnnotationName public void show() {} @AnnotationName int i; @AnnotationName class user { public void show() {} } 注解的分类 # 预定义注解 # JDK定义好的注解，常用的有：\n@Override：检测该方法是否是重写的方法，如果发现父类、实现的接口中未定义该方法，会在编译器报错 @Deprecated：标记过期的方法和类，如果调用了此方法和类，会有编译警告 @SuppressWarnings：指示编译器在编译时忽略声明的警告，以下为value常用可选值 deprecation：使用过期方法、类警告 unchecked：执行了未进行检查的转换时的警告，如集合未使用泛型 fallthrough：Swith代码块未使用break的警告 path：类路径、源文件路径不存在的警告 serial：在可序列化类上缺少serialVersionUID定义的警告 finally：任何finally子句不能正常完成时的警告 all：所有警告 @FunctionalInterface：Java8支持，检测接口是否是一个函数式接口，如果不是（接口中没有抽象方法，抽象方法的个数多于一个）则编译失败 自定义注解 # 注解只有属性（抽象方法），没有方法体\npublic @interface 注解名{ public String 属性名(); public String 属性名() default 默认值; //由于注解的本质是接口，所以权限修饰符可以省略，默认是public String 属性名() default 默认值; } 注解的实质 # public interface MyAnnotation1 extends java.lang.annotation.Annotation {} 通过反编译可以了解到，注解的本质是默认继承java.lang.annotation.Annotation接口的接口\n注解的属性 # 由于注解本质就是接口，所以在接口可以定义抽象方法，在接口中叫抽象方法，在注解中就叫做属性\n在注解中，属性类型（抽象方法的返回值类型）可以写以下几种数据类型：\n基本数据类型 String 枚举 注解 还有以上几种数据类型的数组类型 元注解 # 用来标记注解的注解\n@Retention # 标识这个注解怎么保存，是只在代码中，还是编入.class文件中，或者是在运行时可以通过反射访问。\nvalue枚举如下\npackage java.lang.annotation; public enum RetentionPolicy { //Annotation信息仅存在于编译器处理期间，编译器处理完之后就没有该Annotation信息了 SOURCE, //编译器将Annotation存储于类对应的.class文件中。默认值。 CLASS, //编译器将Annotation存储于class文件中，在执行的时也加载到Java的JVM中，因此可以反射性的读取。 RUNTIME } @Target # 指定注解用于修饰哪些程序元素\nvalue枚举如下\npackage java.lang.annotation; public enum ElementType { TYPE, //类、接口（包括注释类型）或枚举声明 FIELD, //字段声明（包括枚举常量） METHOD, //方法声明 PARAMETER, //参数声明 CONSTRUCTOR, //构造方法声明 LOCAL_VARIABLE, //局部变量声明 ANNOTATION_TYPE, //注释类型声明 PACKAGE //包声明 } package-info.java # pacakge-info.java是一个 Java 文件，目标是提供一个包级的文档说明及包级的注释。在 Java 5 之前，包级的文档是package.html，是通过 JavaDoc 生成的。而在 Java 5 之后版本，包的描述以及相关的文档都可以写入pacakge-info.java文件。\n包级别注释 # /** * 包级别注释测试 * * @author ygang * @since 1.0.0-RELEASE * @version 2.0.0-RELEASE */ package top.ygang; 使用javadoc命令生成文档\njavadoc -encoding UTF-8 -charset UTF-8 top.ygang 包级别注解 # @Target(ElementType.PACKAGE) public @interface MyAnnotation{} // 在package-info.java中使用 @MyAnnotation package top.ygang; 包级别变量 # package-info.java 中只能声明 default 默认访问权限的类，只能包内访问，其它包、子包都不可访问。\npackage top.ygang; class Constant { static final String VALUE = \u0026#34;Test\u0026#34;; } @Documented # 生成文档信息的时候保留注解，对类作辅助说明\n@Inherited # 如果注解类型声明中存在@Inherited元注解，则注解所修饰类的所有子类都将会继承此注解。\n@Repeatable # 用来标注一个注解在同一个地方可重复使用的一个注解\n注解的解析 # 就是指使用反射技术，获取注解的属性值。\n注意：如果想要使用反射来解析注解，前提条件，该注解一定要有元注解@Retention，而且其值一定要为RetentionPolicy.RUNTIME，否则属性不进JVM内存，会有空指针异常\n常用方法 # getAnnotation(Class\u0026lt;A\u0026gt; annatationClass)：取得该元素指定类型的注解 Annotation[] getAnnotations()：返回此元素上存在的所有注解的数组，包括从父类继承的 Annotation[] getDeclaredAnnotations()：返回直接存在于此元素上的所有注解的数组，不包括父类的注解 boolean isAnnotation()：判断元素是否是一个注解 boolean isAnnotationPresent(Class\u0026lt;? extends Annotation\u0026gt; annotationClass) ：判断元素是否存在指定类型的注解 获取到类名上的注解的属性值 # //获取运行时类的Class对象 Class cls = 带有注解的类的类名.getClass(); //判断是否有注解 if(cls.isAnnotationPresent(注解.class)){ //获取注解对象 注解 a = (注解)cls.getAnnotation(注解.class); //获取属性值 a.属性名(); } 获取到方法上的注解的属性值 # //获取运行时类的Class对象 Class cls = 带有注解的类的类名.getClass(); Method m = cls.getMethod(\u0026#34;方法名\u0026#34;); //判断是否有注解 if(m.isAnnotationPresent(注解.class)){ //获取注解对象 注解 a = (注解)m.getAnnotation(注解.class); //获取属性值 a.属性名(); } ","date":"2024-12-13","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/fbeea4f4/b9a3fd97/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是注解 \n    \u003cdiv id=\"什么是注解\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af%e6%b3%a8%e8%a7%a3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e注解（Annotation），也叫元数据。一种代码级别的说明。它是\u003cstrong\u003eJDK1.5\u003c/strong\u003e及以后版本引入的一个特性，与类、接口、枚举是在同一个层次。\u003c/p\u003e","title":"8、注解","type":"posts"},{"content":" SpringBoot集成kaptcha实现数字计算验证码和字母验证码功能 # 1、Maven依赖 # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;pro.fessional\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;kaptcha\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.3.3\u0026lt;/version\u0026gt; \u0026lt;exclusions\u0026gt; \u0026lt;exclusion\u0026gt; \u0026lt;artifactId\u0026gt;servlet-api\u0026lt;/artifactId\u0026gt; \u0026lt;groupId\u0026gt;javax.servlet\u0026lt;/groupId\u0026gt; \u0026lt;/exclusion\u0026gt; \u0026lt;/exclusions\u0026gt; \u0026lt;/dependency\u0026gt; 2、配置类 # package top.ygang.framework.config; import java.util.Random; import com.google.code.kaptcha.text.impl.DefaultTextCreator; /** * 验证码文本生成器 * @Author: yanggang * @Date: */ public class KaptchaTextCreator extends DefaultTextCreator { private static final String[] CNUMBERS = \u0026#34;0,1,2,3,4,5,6,7,8,9,10\u0026#34;.split(\u0026#34;,\u0026#34;); @Override public String getText() { Integer result = 0; Random random = new Random(); int x = random.nextInt(10); int y = random.nextInt(10); StringBuilder suChinese = new StringBuilder(); int randomoperands = random.nextInt(3); if (randomoperands == 0) { result = x * y; suChinese.append(CNUMBERS[x]); suChinese.append(\u0026#34;*\u0026#34;); suChinese.append(CNUMBERS[y]); } else if (randomoperands == 1) { if ((x != 0) \u0026amp;\u0026amp; y % x == 0) { result = y / x; suChinese.append(CNUMBERS[y]); suChinese.append(\u0026#34;/\u0026#34;); suChinese.append(CNUMBERS[x]); } else { result = x + y; suChinese.append(CNUMBERS[x]); suChinese.append(\u0026#34;+\u0026#34;); suChinese.append(CNUMBERS[y]); } } else { if (x \u0026gt;= y) { result = x - y; suChinese.append(CNUMBERS[x]); suChinese.append(\u0026#34;-\u0026#34;); suChinese.append(CNUMBERS[y]); } else { result = y - x; suChinese.append(CNUMBERS[y]); suChinese.append(\u0026#34;-\u0026#34;); suChinese.append(CNUMBERS[x]); } } suChinese.append(\u0026#34;=?@\u0026#34; + result); return suChinese.toString(); } } package top.ygang.huijifindresource.utils.captcha; import com.google.code.kaptcha.impl.DefaultKaptcha; import com.google.code.kaptcha.util.Config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Properties; import static com.google.code.kaptcha.Constants.*; /** * @Description: 验证码配置 * @Author: yanggang * @Date: */ @Configuration public class CaptchaConfig { /** * 字符验证码生成器 * @return */ @Bean(name = \u0026#34;captchaProducer\u0026#34;) public DefaultKaptcha getKaptchaBean() { DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); Properties properties = new Properties(); // 是否有边框 默认为true 我们可以自己设置yes，no properties.setProperty(KAPTCHA_BORDER, \u0026#34;yes\u0026#34;); // 验证码文本字符颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, \u0026#34;black\u0026#34;); // 验证码图片宽度 默认为200 properties.setProperty(KAPTCHA_IMAGE_WIDTH, \u0026#34;160\u0026#34;); // 验证码图片高度 默认为50 properties.setProperty(KAPTCHA_IMAGE_HEIGHT, \u0026#34;60\u0026#34;); // 验证码文本字符大小 默认为40 properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, \u0026#34;38\u0026#34;); // KAPTCHA_SESSION_KEY properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, \u0026#34;kaptchaCode\u0026#34;); // 验证码文本字符长度 默认为5 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, \u0026#34;4\u0026#34;); // 验证码文本字体样式 默认为new Font(\u0026#34;Arial\u0026#34;, 1, fontSize), new Font(\u0026#34;Courier\u0026#34;, 1, fontSize) properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, \u0026#34;Arial,Courier\u0026#34;); // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, \u0026#34;com.google.code.kaptcha.impl.ShadowGimpy\u0026#34;); Config config = new Config(properties); defaultKaptcha.setConfig(config); return defaultKaptcha; } /** * 数学验证码生成器 * @return */ @Bean(name = \u0026#34;captchaProducerMath\u0026#34;) public DefaultKaptcha getKaptchaBeanMath() { DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); Properties properties = new Properties(); // 是否有边框 默认为true 我们可以自己设置yes，no properties.setProperty(KAPTCHA_BORDER, \u0026#34;yes\u0026#34;); // 边框颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_BORDER_COLOR, \u0026#34;105,179,90\u0026#34;); // 验证码文本字符颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, \u0026#34;blue\u0026#34;); // 验证码图片宽度 默认为200 properties.setProperty(KAPTCHA_IMAGE_WIDTH, \u0026#34;160\u0026#34;); // 验证码图片高度 默认为50 properties.setProperty(KAPTCHA_IMAGE_HEIGHT, \u0026#34;60\u0026#34;); // 验证码文本字符大小 默认为40 properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, \u0026#34;35\u0026#34;); // KAPTCHA_SESSION_KEY properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, \u0026#34;kaptchaCodeMath\u0026#34;); // 验证码文本生成器 // !!注意，这里需要手动修改为上面的KaptchaTextCreator的全类名 properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, \u0026#34;top.ygang.framework.config.KaptchaTextCreator\u0026#34;); // 验证码文本字符间距 默认为2 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, \u0026#34;3\u0026#34;); // 验证码文本字符长度 默认为5 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, \u0026#34;6\u0026#34;); // 验证码文本字体样式 默认为new Font(\u0026#34;Arial\u0026#34;, 1, fontSize), new Font(\u0026#34;Courier\u0026#34;, 1, fontSize) properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, \u0026#34;Arial,Courier\u0026#34;); // 验证码噪点颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_NOISE_COLOR, \u0026#34;white\u0026#34;); // 干扰实现类 properties.setProperty(KAPTCHA_NOISE_IMPL, \u0026#34;com.google.code.kaptcha.impl.NoNoise\u0026#34;); // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, \u0026#34;com.google.code.kaptcha.impl.ShadowGimpy\u0026#34;); Config config = new Config(properties); defaultKaptcha.setConfig(config); return defaultKaptcha; } } 3、工具类 # package top.ygang.huijifindresource.utils.captcha; import com.google.code.kaptcha.Producer; import lombok.Data; import org.apache.tomcat.util.codec.binary.Base64; import org.springframework.stereotype.Component; import org.springframework.util.FastByteArrayOutputStream; import javax.annotation.Resource; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.IOException; /** * @Description: 验证码生成工具类 * @Author: yanggang * @Date: */ @Component public class CaptchaGenerateUtil { @Resource(name = \u0026#34;captchaProducer\u0026#34;) private Producer captchaProducer; @Resource(name = \u0026#34;captchaProducerMath\u0026#34;) private Producer captchaProducerMath; private static final String BASE64_IMG_PREFIX = \u0026#34;data:image/gif;base64,\u0026#34;; @Data public static class CodeEntity { public CodeEntity(String code,String img){ this.code = code; this.img = img; } //生成的验证码，存入缓存中，用于后续校验 private String code; //生成的图片Base64编码,用于前端展示 private String img; } /** * 生成字符验证码 * @return * @throws IOException */ public CodeEntity generateCode() throws IOException { String capStr = captchaProducer.createText(); String code = capStr; BufferedImage image = captchaProducer.createImage(capStr); // 转换流信息写出 FastByteArrayOutputStream os = new FastByteArrayOutputStream(); ImageIO.write(image, \u0026#34;jpg\u0026#34;, os); return new CodeEntity(code, BASE64_IMG_PREFIX + Base64.encodeBase64String(os.toByteArray())); } /** * 生成数学验证码 * @return * @throws IOException */ public CodeEntity generateCodeMath() throws IOException { String capText = captchaProducerMath.createText(); String capStr = capText.substring(0, capText.lastIndexOf(\u0026#34;@\u0026#34;)); String code = capText.substring(capText.lastIndexOf(\u0026#34;@\u0026#34;) + 1); BufferedImage image = captchaProducerMath.createImage(capStr); // 转换流信息写出 FastByteArrayOutputStream os = new FastByteArrayOutputStream(); ImageIO.write(image, \u0026#34;jpg\u0026#34;, os); return new CodeEntity(code, BASE64_IMG_PREFIX + Base64.encodeBase64String(os.toByteArray())); } } ","date":"2024-12-11","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/0f3856f3/ed12e6b4/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eSpringBoot集成kaptcha实现数字计算验证码和字母验证码功能 \n    \u003cdiv id=\"springboot集成kaptcha实现数字计算验证码和字母验证码功能\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#springboot%e9%9b%86%e6%88%90kaptcha%e5%ae%9e%e7%8e%b0%e6%95%b0%e5%ad%97%e8%ae%a1%e7%ae%97%e9%aa%8c%e8%af%81%e7%a0%81%e5%92%8c%e5%ad%97%e6%af%8d%e9%aa%8c%e8%af%81%e7%a0%81%e5%8a%9f%e8%83%bd\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e1、Maven依赖 \n    \u003cdiv id=\"1maven依赖\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1maven%e4%be%9d%e8%b5%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003epro.fessional\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003ekaptcha\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e2.3.3\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;exclusions\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;exclusion\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003eservlet-api\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003ejavax.servlet\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;/exclusion\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;/exclusions\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e2、配置类 \n    \u003cdiv id=\"2配置类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#2%e9%85%8d%e7%bd%ae%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003epackage\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003etop.ygang.framework.config\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.util.Random\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ecom.google.code.kaptcha.text.impl.DefaultTextCreator\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 验证码文本生成器\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * @Author: yanggang\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * @Date: \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eKaptchaTextCreator\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eextends\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eDefaultTextCreator\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003efinal\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eCNUMBERS\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;0,1,2,3,4,5,6,7,8,9,10\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esplit\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;,\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Override\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003egetText\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eRandom\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erandom\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eRandom\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erandom\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enextInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erandom\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enextInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eStringBuilder\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eStringBuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erandomoperands\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erandom\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enextInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erandomoperands\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eCNUMBERS\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;*\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eCNUMBERS\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erandomoperands\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e%\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eCNUMBERS\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eCNUMBERS\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eCNUMBERS\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;+\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eCNUMBERS\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eCNUMBERS\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;-\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eCNUMBERS\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eCNUMBERS\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;-\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eCNUMBERS\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;=?@\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esuChinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003etoString\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003epackage\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003etop.ygang.huijifindresource.utils.captcha\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ecom.google.code.kaptcha.impl.DefaultKaptcha\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ecom.google.code.kaptcha.util.Config\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003eorg.springframework.context.annotation.Bean\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003eorg.springframework.context.annotation.Configuration\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.util.Properties\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport static\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ecom.google.code.kaptcha.Constants.*\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * @Description: 验证码配置\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * @Author: yanggang\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * @Date: \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nd\"\u003e@Configuration\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eCaptchaConfig\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 字符验证码生成器\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @return\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Bean\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;captchaProducer\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eDefaultKaptcha\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003egetKaptchaBean\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eDefaultKaptcha\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edefaultKaptcha\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eDefaultKaptcha\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eProperties\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eProperties\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 是否有边框 默认为true 我们可以自己设置yes，no\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_BORDER\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;yes\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 验证码文本字符颜色 默认为Color.BLACK\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_TEXTPRODUCER_FONT_COLOR\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;black\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 验证码图片宽度 默认为200\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_IMAGE_WIDTH\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;160\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 验证码图片高度 默认为50\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_IMAGE_HEIGHT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;60\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 验证码文本字符大小 默认为40\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_TEXTPRODUCER_FONT_SIZE\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;38\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// KAPTCHA_SESSION_KEY\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_SESSION_CONFIG_KEY\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;kaptchaCode\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 验证码文本字符长度 默认为5\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_TEXTPRODUCER_CHAR_LENGTH\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;4\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 验证码文本字体样式 默认为new Font(\u0026#34;Arial\u0026#34;, 1, fontSize), new Font(\u0026#34;Courier\u0026#34;, 1, fontSize)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_TEXTPRODUCER_FONT_NAMES\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Arial,Courier\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_OBSCURIFICATOR_IMPL\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;com.google.code.kaptcha.impl.ShadowGimpy\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eConfig\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eConfig\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003edefaultKaptcha\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetConfig\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edefaultKaptcha\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 数学验证码生成器\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @return\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Bean\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;captchaProducerMath\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eDefaultKaptcha\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003egetKaptchaBeanMath\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eDefaultKaptcha\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edefaultKaptcha\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eDefaultKaptcha\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eProperties\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eProperties\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 是否有边框 默认为true 我们可以自己设置yes，no\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_BORDER\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;yes\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 边框颜色 默认为Color.BLACK\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_BORDER_COLOR\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;105,179,90\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 验证码文本字符颜色 默认为Color.BLACK\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_TEXTPRODUCER_FONT_COLOR\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;blue\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 验证码图片宽度 默认为200\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_IMAGE_WIDTH\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;160\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 验证码图片高度 默认为50\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_IMAGE_HEIGHT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;60\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 验证码文本字符大小 默认为40\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_TEXTPRODUCER_FONT_SIZE\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;35\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// KAPTCHA_SESSION_KEY\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_SESSION_CONFIG_KEY\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;kaptchaCodeMath\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 验证码文本生成器\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// !!注意，这里需要手动修改为上面的KaptchaTextCreator的全类名\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_TEXTPRODUCER_IMPL\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;top.ygang.framework.config.KaptchaTextCreator\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 验证码文本字符间距 默认为2\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_TEXTPRODUCER_CHAR_SPACE\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;3\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 验证码文本字符长度 默认为5\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_TEXTPRODUCER_CHAR_LENGTH\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;6\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 验证码文本字体样式 默认为new Font(\u0026#34;Arial\u0026#34;, 1, fontSize), new Font(\u0026#34;Courier\u0026#34;, 1, fontSize)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_TEXTPRODUCER_FONT_NAMES\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Arial,Courier\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 验证码噪点颜色 默认为Color.BLACK\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_NOISE_COLOR\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;white\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 干扰实现类\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_NOISE_IMPL\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;com.google.code.kaptcha.impl.NoNoise\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetProperty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKAPTCHA_OBSCURIFICATOR_IMPL\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;com.google.code.kaptcha.impl.ShadowGimpy\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eConfig\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eConfig\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eproperties\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003edefaultKaptcha\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetConfig\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edefaultKaptcha\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e3、工具类 \n    \u003cdiv id=\"3工具类\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#3%e5%b7%a5%e5%85%b7%e7%b1%bb\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003epackage\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003etop.ygang.huijifindresource.utils.captcha\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ecom.google.code.kaptcha.Producer\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003elombok.Data\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003eorg.apache.tomcat.util.codec.binary.Base64\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003eorg.springframework.stereotype.Component\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003eorg.springframework.util.FastByteArrayOutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejavax.annotation.Resource\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejavax.imageio.ImageIO\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.awt.image.BufferedImage\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.io.IOException\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * @Description: 验证码生成工具类\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * @Author: yanggang\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * @Date: \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"nd\"\u003e@Component\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eCaptchaGenerateUtil\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Resource\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;captchaProducer\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eProducer\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecaptchaProducer\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Resource\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;captchaProducerMath\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eProducer\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecaptchaProducerMath\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003efinal\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eBASE64_IMG_PREFIX\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;data:image/gif;base64,\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Data\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eCodeEntity\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eCodeEntity\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eimg\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecode\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eimg\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eimg\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//生成的验证码，存入缓存中，用于后续校验\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//生成的图片Base64编码,用于前端展示\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eimg\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 生成字符验证码\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @return\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @throws IOException\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eCodeEntity\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003egenerateCode\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003ethrows\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eIOException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecapStr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecaptchaProducer\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecreateText\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecapStr\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedImage\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eimage\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecaptchaProducer\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecreateImage\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecapStr\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 转换流信息写出\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eFastByteArrayOutputStream\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFastByteArrayOutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eImageIO\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ewrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eimage\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;jpg\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eCodeEntity\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eBASE64_IMG_PREFIX\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eBase64\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eencodeBase64String\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003etoByteArray\u003c/span\u003e\u003cspan class=\"p\"\u003e()));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 生成数学验证码\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @return\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @throws IOException\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eCodeEntity\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003egenerateCodeMath\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003ethrows\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eIOException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecapText\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecaptchaProducerMath\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecreateText\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecapStr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecapText\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esubstring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecapText\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elastIndexOf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;@\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecapText\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esubstring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecapText\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elastIndexOf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;@\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedImage\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eimage\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecaptchaProducerMath\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecreateImage\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecapStr\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 转换流信息写出\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eFastByteArrayOutputStream\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFastByteArrayOutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eImageIO\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ewrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eimage\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;jpg\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eCodeEntity\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eBASE64_IMG_PREFIX\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eBase64\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eencodeBase64String\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003etoByteArray\u003c/span\u003e\u003cspan class=\"p\"\u003e()));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"8、验证码","type":"posts"},{"content":" 什么是Vue CLI # 如果你只是简单写几个Vue的Demo程序, 那么你不需要Vue CLI. 如果你在开发大型项目, 那么你需要, 并且必然需要使用Vue CLI 使用Vue.js开发大型应用时，我们需要考虑代码目录结构、项目结构和部署、热加载、代码单元测试等事情。 如果每个项目都要手动完成这些工作，那无疑效率比较低效，所以通常我们会使用一些脚手架工具来帮助完成这些事情。 CLI是Command-Line Interface, 翻译为命令行界面, 但是俗称脚手架 使用 vue-cli 可以快速搭建Vue开发环境以及对应的webpack配置. VueCLI使用前提 # NodeJs # 可以直接在官方网站中下载安装. 网址: http://nodejs.cn/download/ 默认情况下自动安装Node和NPM Node环境要求8.9以上或者更高版本 cnpm镜像 # 由于国内直接使用 npm 的官方镜像是非常慢的，这里推荐使用淘宝 NPM 镜像。 你可以使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm: npm install -g cnpm --registry=https://registry.npmmirror.com 这样就可以使用 cnpm 命令来安装模块了： cnpm install [name] Webpack # 全局安装：npm install webpack@3.6.0 -g\n脚手架安装 # 安装脚手架 # 全局安装：cnpm install -g @vue/cli\n注意：上面安装的是Vue CLI3的版本，如果需要想按照Vue CLI2的方式初始化项目是不可以的。\n通过vue --version查看版本\n安装全局桥接工具，拉取vuecli2模板 # 命令：cnpm install -g @vue/cli-init\n初始化项目 # 命令下载模板 # vuecli2创建项目 # 命令：vue init webpack 项目名称\nvuecli3创建项目 # 命令：vue create 项目名称\n使用vuecli2 # 1、项目起名 # 一般情况不需要改，文件夹名就是项目名称\n2、项目描述 # 默认为A Vue.js project，可以根据实际情况修改\n3、作者信息 # 默认读取gitconfig的用户\n4、构建项目选择 # 开发中通常选择第二个，因为运行效率更高，体积小\n5、是否安装路由 # 根据需求选择，一般会安装\n6、是否对js编码规范进行检查 # 一般不使用\n7、单元测试 # 一般不使用\n8、端到端测试（自动化测试） # 一般不使用，由测试人员完成\n9、选择npm或yarn管理项目 # 10、目录结构 # 使用vuecli3 # 1、选择配置 # manually select features手动选择配置\n2、选择需要添加的插件 # 空格进行选择\n3、选择Vue版本 # 4、选择语法检查时机 # 5、选择配置文件保存方式 # 一般选择第一个，保存到独立的文件\n6、是否保存当前配置 # 可以在C://users/administrator/.vuerc中进行修改删除默认配置\n7、目录结构 # index.html # \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;zh\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;utf-8\u0026#34;\u0026gt; \u0026lt;!-- 对于IE浏览器的特殊配置，让IE浏览器以最高渲染级别渲染页面 --\u0026gt; \u0026lt;meta http-equiv=\u0026#34;X-UA-Compatible\u0026#34; content=\u0026#34;IE=edge\u0026#34;\u0026gt; \u0026lt;!-- 开启移动端理想视口 --\u0026gt; \u0026lt;meta name=\u0026#34;viewport\u0026#34; content=\u0026#34;width=device-width,initial-scale=1.0\u0026#34;\u0026gt; \u0026lt;!-- 配置页签图标 --\u0026gt; \u0026lt;link rel=\u0026#34;icon\u0026#34; href=\u0026#34;\u0026lt;%= BASE_URL %\u0026gt;favicon.ico\u0026#34;\u0026gt; \u0026lt;!-- 配置网页标题，默认读取的是package.json中的name属性 --\u0026gt; \u0026lt;title\u0026gt;\u0026lt;%= htmlWebpackPlugin.options.title %\u0026gt;\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;!-- 如果网页不支持js会渲染 --\u0026gt; \u0026lt;noscript\u0026gt; \u0026lt;strong\u0026gt;We\u0026#39;re sorry but \u0026lt;%= htmlWebpackPlugin.options.title %\u0026gt; doesn\u0026#39;t work properly without JavaScript enabled. Please enable it to continue.\u0026lt;/strong\u0026gt; \u0026lt;/noscript\u0026gt; \u0026lt;!-- 容器 --\u0026gt; \u0026lt;div id=\u0026#34;app\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;!-- built files will be auto injected --\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; runtime-compiler和runtime-only的区别 # 如果在之后的开发中，你依然使用template，就需要选择Runtime-Compiler 如果你之后的开发中，使用的是.vue文件开发，那么可以选择Runtime-only render和template # 执行过程 # runtime-compiler template\u0026ndash;》ast抽象语法树\u0026ndash;》render函数\u0026ndash;》vdom虚拟DOM\u0026ndash;》真实DOM runtime-only render函数\u0026ndash;》vdom虚拟DOM\u0026ndash;》真实DOM 所以推荐使用runtime-only，执行效率更高\nrender函数的使用 # import App from \u0026#39;app\u0026#39;; new Vue({ el: \u0026#39;#app\u0026#39;, render: function(h){ return h(App); } }) //render函数中h的本质是一个createElement函数,即 import App from \u0026#39;app\u0026#39;; new Vue({ el: \u0026#39;#app\u0026#39;, render: function(createElement){ return createElement(App); } }) createElement函数 # new Vue({ el: \u0026#39;#app\u0026#39;, render: function(createElement){ //实际会有三个参数 //参数一：需要用来替换#app标签的元素或组件对象 //参数二：该标签的属性 //参数三：标签中的内容 //最终，\u0026lt;div id=\u0026#39;app\u0026#39;/\u0026gt;标签会被替换成\u0026lt;h2 class=\u0026#39;box\u0026#39;\u0026gt;hello,world!\u0026lt;/h2\u0026gt; return createElement(\u0026#39;h2\u0026#39;,{class: \u0026#39;box\u0026#39;},[\u0026#39;hello,world!\u0026#39;]); } }) npm run build流程 # npm run dev流程 # vuecli3 # vuecli3和vuecli2的区别 # vue-cli 3 是基于 webpack 4 打造，vue-cli 2 还是 webapck 3 vue-cli 3 的设计原则是“0配置”，移除的配置文件根目录下的，build和config等目录 vue-cli 3 提供了 vue ui 命令，提供了可视化配置，更加人性化 移除了static文件夹，新增了public文件夹，并且index.html移动到public中 vue ui配置文件的查看和修改 # 可以使用命令vue ui查看项目配置\n如果需要自己更改配置的话，那么需要在项目的根目录创建一个vue.config.js文件（文件名固定），格式：\nmodule.exports = { } vuecli配置 # 详细配置可以到官网查看：https://cli.vuejs.org/zh/config/\n全局 CLI 配置 # 有些针对 @vue/cli 的全局配置，例如你惯用的包管理器和你本地保存的 preset，都保存在 home 目录下一个名叫 .vuerc 的 JSON 文件。你可以用编辑器直接编辑这个文件来更改已保存的选项。\n你也可以使用 vue config 命令来审查或修改全局的 CLI 配置。\nvue.config.js # vue.config.js 是一个可选的配置文件，如果项目的 (和 package.json 同级的) 根目录中存在这个文件，那么它会被 @vue/cli-service 自动加载。你也可以使用 package.json 中的 vue 字段，但是注意这种写法需要你严格遵照 JSON 的格式来写。\n这个文件应该导出一个包含了选项的对象：\n// vue.config.js /** * @type {import(\u0026#39;@vue/cli-service\u0026#39;).ProjectOptions} */ module.exports = { // 选项... } 或者，你也可以使用 @vue/cli-service 提供的 defineConfig 帮手函数，以获得更好的类型提示：\n// vue.config.js const { defineConfig } = require(\u0026#39;@vue/cli-service\u0026#39;) module.exports = defineConfig({ // 选项 }) 样例 # const { defineConfig } = require(\u0026#39;@vue/cli-service\u0026#39;) //1.引入path： const path = require(\u0026#39;path\u0026#39;); //2.封装方法： function resolve(dir){ return path.resolve(__dirname,dir) } module.exports = defineConfig({ transpileDependencies: true, lintOnSave: false, productionSourceMap: false, configureWebpack:{ resolve: { alias: { \u0026#34;@\u0026#34;: resolve(\u0026#39;src\u0026#39;), \u0026#34;@assets\u0026#34;: resolve(\u0026#39;src/assets\u0026#39;), \u0026#34;@components\u0026#34;: resolve(\u0026#39;src/components\u0026#39;), \u0026#34;@views\u0026#34;: resolve(\u0026#39;src/views\u0026#39;), \u0026#34;@api\u0026#34;: resolve(\u0026#39;src/api\u0026#39;), \u0026#34;@util\u0026#34;: resolve(\u0026#39;src/util\u0026#39;) } } }, devServer: { // 自动打开浏览器 open: false, // 设置为0.0.0.0所有的地址均能访问 host: \u0026#39;0.0.0.0\u0026#39;, // 设置本地服务端口 port: 8081, proxy: { \u0026#39;/apis\u0026#39;: { target: \u0026#39;http://localhost:8080/\u0026#39;, secure: false, // 如果是https接口，需要配置这个参数 ws: true, //用于支持websocket changeOrigin: true, //控制请求，默认值为true pathRewrite:{\u0026#34;^/apis\u0026#34;: \u0026#34;\u0026#34;} //匹配所有/api开头的路径，替换为\u0026#34;\u0026#34; }, \u0026#39;/assets\u0026#39;: { target: \u0026#39;http://localhost:19000/\u0026#39;, secure: false, // 如果是https接口，需要配置这个参数 ws: true, //用于支持websocket changeOrigin: true, //控制请求，默认值为true pathRewrite:{\u0026#34;^/assets\u0026#34;: \u0026#34;\u0026#34;} //匹配所有/api开头的路径，替换为\u0026#34;\u0026#34; } } } }) devServer # module.exports = { devServer: { // 自动打开浏览器 open: false, // 设置为0.0.0.0所有的地址均能访问 host: \u0026#39;0.0.0.0\u0026#39;, // 设置本地服务端口 port: 1025, //此处配置后台服务器 proxy: \u0026#39;http://localhost:4000\u0026#39; } } devServer.proxy # Type: string | Object\n如果你的前端应用和后端 API 服务器没有运行在同一个主机上，你需要在开发环境下将 API 请求代理到 API 服务器。这个问题可以通过 vue.config.js 中的 devServer.proxy 选项来配置。\n这会告诉开发服务器将任何未知请求 (没有匹配到静态文件的请求) 代理到http://localhost:4000。\nmodule.exports = { devServer: { //此处配置后台服务器 proxy: \u0026#39;http://localhost:4000\u0026#39; } } 如果你想要更多的代理控制行为，也可以使用一个 path: options 成对的对象。\nmodule.exports = { devServer: { open: false, //自动打开浏览器 host: \u0026#39;0.0.0.0\u0026#39;, //设置为0.0.0.0则所有的地址均能访问 port: 8080, https: false, proxy: { \u0026#39;/apis\u0026#39;: { target: \u0026#39;\u0026lt;url\u0026gt;\u0026#39;, secure: false, // 如果是https接口，需要配置这个参数 ws: true, //用于支持websocket changeOrigin: true, //控制请求，默认值为true pathRewrite:{\u0026#34;^/apis\u0026#34;: \u0026#34;\u0026#34;} //匹配所有/api开头的路径，替换为\u0026#34;\u0026#34; }, \u0026#39;/foo\u0026#39;: { target: \u0026#39;\u0026lt;other_url\u0026gt;\u0026#39; } } } } baseUrl # Description:部署应用包时的基本 URL，例如，如果你的应用被部署在 https://www.my-app.com/my-app/，则设置 publicPath 为 /my-app/。\n从 Vue CLI 3.3 起已弃用，改为publicPath。\nType: string Default: '/' outputDir # 当运行 vue-cli-service build 时生成的生产环境构建文件的目录。\nType: string Default: 'dist' assetsDir # 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。\nType: string Default: '' indexPath # 指定生成的 index.html 的输出路径 (相对于 outputDir)。也可以是一个绝对路径。\nType: string Default: 'index.html' filenameHashing # 默认情况下，生成的静态资源在它们的文件名中包含了 hash 以便更好的控制缓存。然而，这也要求 index 的 HTML 是被 Vue CLI 自动生成的。如果你无法使用 Vue CLI 生成的 index HTML，你可以通过将这个选项设为 false 来关闭文件名哈希。\nType: boolean Default: true pages # 在 multi-page 模式下构建应用。每个“page”应该有一个对应的 JavaScript 入口文件。\nType: Object Default: undefined module.exports = { pages: { index: { // page 的入口 entry: \u0026#39;src/index/main.js\u0026#39;, // 模板来源 template: \u0026#39;public/index.html\u0026#39;, // 在 dist/index.html 的输出 filename: \u0026#39;index.html\u0026#39;, // 当使用 title 选项时， // template 中的 title 标签需要是 \u0026lt;title\u0026gt;\u0026lt;%= htmlWebpackPlugin.options.title %\u0026gt;\u0026lt;/title\u0026gt; title: \u0026#39;Index Page\u0026#39;, // 在这个页面中包含的块，默认情况下会包含 // 提取出来的通用 chunk 和 vendor chunk。 chunks: [\u0026#39;chunk-vendors\u0026#39;, \u0026#39;chunk-common\u0026#39;, \u0026#39;index\u0026#39;] }, // 当使用只有入口的字符串格式时， // 模板会被推导为 `public/subpage.html` // 并且如果找不到的话，就回退到 `public/index.html`。 // 输出文件名会被推导为 `subpage.html`。 subpage: \u0026#39;src/subpage/main.js\u0026#39; } } lintOnSave # 是否在开发环境下通过 eslint-loader 在每次保存时 lint 代码。这个值会在 @vue/cli-plugin-eslint 被安装之后生效。\n设置为 true 或 'warning' 时，eslint-loader 会将 lint 错误输出为编译警告。默认情况下，警告仅仅会被输出到命令行，且不会使得编译失败。\n如果你希望让 lint 错误在开发时直接显示在浏览器中，你可以使用 lintOnSave: 'default'。这会强制 eslint-loader 将 lint 错误输出为编译错误，同时也意味着 lint 错误将会导致编译失败。\n设置为 error 将会使得 eslint-loader 把 lint 警告也输出为编译错误，这意味着 lint 警告将会导致编译失败。\nType: boolean | 'warning' | 'default' | 'error' Default: 'default' 如果你想要在生产构建时禁用 eslint-loader，你可以用如下配置：\n// vue.config.js module.exports = { lintOnSave: process.env.NODE_ENV !== \u0026#39;production\u0026#39; } runtimeCompiler # 是否使用包含运行时编译器的 Vue 构建版本。设置为 true 后你就可以在 Vue 组件中使用 template 选项了，但是这会让你的应用额外增加 10kb 左右。\nType: boolean Default: false transpileDependencies # 默认情况下 babel-loader 会忽略所有 node_modules 中的文件。你可以启用本选项，以避免构建后的代码中出现未转译的第三方依赖。\nType: boolean | Array\u0026lt;string | RegExp\u0026gt; Default: false productionSourceMap # 去除vue打包后js目录下生成的.map文件，用于加速生产环境构建\nType: boolean Default: true configureWebpack # 可以对webpack进行一些配置，例如配置路径别名\n//1.引入path： const path = require(\u0026#39;path\u0026#39;); //2.封装方法： function resolve(dir){ return path.resolve(__dirname,dir) } //3.配置文件： configureWebpack: { resolve: { alias: { \u0026#39;@\u0026#39;: resolve(\u0026#39;src\u0026#39;), \u0026#39;@assets\u0026#39;: resolve(\u0026#39;src/assets\u0026#39;), \u0026#39;@views\u0026#39;: resolve(\u0026#39;src/views\u0026#39;), \u0026#39;@components\u0026#39;: resolve(\u0026#39;src/components\u0026#39;), } } } 函数式写法\nconfigureWebpack: (config) =\u0026gt; { if (process.env.NODE_ENV === \u0026#39;production\u0026#39;) { // 为生产环境修改配置... config.mode = \u0026#39;production\u0026#39; } else { // 为生产环境修改配置... config.mode = \u0026#39;development\u0026#39; } // 开发生产共同配置别名 Object.assign(config.resolve, { alias: { \u0026#39;@\u0026#39;: resolve(\u0026#39;src\u0026#39;), \u0026#39;@assets\u0026#39;: resolve(\u0026#39;src/assets\u0026#39;), \u0026#39;@views\u0026#39;: resolve(\u0026#39;src/views\u0026#39;), \u0026#39;@components\u0026#39;: resolve(\u0026#39;src/components\u0026#39;), } }) }, ","date":"2024-12-03","externalUrl":null,"permalink":"/posts/bafd68f1/b2321cb9/75d93b4e/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是Vue CLI \n    \u003cdiv id=\"什么是vue-cli\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%afvue-cli\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e如果你只是简单写几个Vue的Demo程序, 那么你不需要Vue CLI.\u003c/li\u003e\n\u003cli\u003e如果你在开发大型项目, 那么你需要, 并且必然需要使用Vue CLI\n\u003cul\u003e\n\u003cli\u003e使用Vue.js开发大型应用时，我们需要考虑代码目录结构、项目结构和部署、热加载、代码单元测试等事情。\u003c/li\u003e\n\u003cli\u003e如果每个项目都要手动完成这些工作，那无疑效率比较低效，所以通常我们会使用一些脚手架工具来帮助完成这些事情。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eCLI是Command-Line Interface, 翻译为命令行界面, 但是俗称脚手架\u003c/li\u003e\n\u003cli\u003e使用 vue-cli 可以快速搭建Vue开发环境以及对应的webpack配置.\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eVueCLI使用前提 \n    \u003cdiv id=\"vuecli使用前提\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#vuecli%e4%bd%bf%e7%94%a8%e5%89%8d%e6%8f%90\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eNodeJs \n    \u003cdiv id=\"nodejs\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#nodejs\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e可以直接在官方网站中下载安装.\u003c/li\u003e\n\u003cli\u003e网址: \u003ca\n  href=\"http://nodejs.cn/download/\"\n    target=\"_blank\"\n  \u003ehttp://nodejs.cn/download/\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e默认情况下自动安装Node和NPM\u003c/li\u003e\n\u003cli\u003eNode环境要求8.9以上或者更高版本\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch4 class=\"relative group\"\u003ecnpm镜像 \n    \u003cdiv id=\"cnpm镜像\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#cnpm%e9%95%9c%e5%83%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e由于国内直接使用 npm 的官方镜像是非常慢的，这里推荐使用淘宝 NPM 镜像。\u003c/li\u003e\n\u003cli\u003e你可以使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003enpm install -g cnpm --registry=https://registry.npmmirror.com\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e这样就可以使用 cnpm 命令来安装模块了：\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003ecnpm install [name]\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3 class=\"relative group\"\u003eWebpack \n    \u003cdiv id=\"webpack\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#webpack\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e全局安装：\u003ccode\u003enpm install webpack@3.6.0 -g\u003c/code\u003e\u003c/p\u003e","title":"8、VueCLI","type":"posts"},{"content":"Spring Boot默认使用SLF4J作为日志门面，并集成了Logback作为日志实现。SLF4J（Simple Logging Facade for Java）是一个通用的日志抽象层，可以与多种日志框架结合使用，如Logback、Log4j、Java Util Logging（JUL）等。Logback是一个快速、灵活且功能强大的日志框架，是Log4j的继任者。\n配置依赖 # Spring Boot项目默认包含了SLF4J和Logback的依赖。如果需要自定义，可以在pom.xml中添加或修改依赖：\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-logging\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 如果你想使用Log4j2作为日志实现，可以排除Logback依赖，并添加Log4j2依赖：\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-logging\u0026lt;/artifactId\u0026gt; \u0026lt;exclusions\u0026gt; \u0026lt;exclusion\u0026gt; \u0026lt;groupId\u0026gt;ch.qos.logback\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;logback-classic\u0026lt;/artifactId\u0026gt; \u0026lt;/exclusion\u0026gt; \u0026lt;/exclusions\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-log4j2\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 配置日志级别 # Spring Boot允许我们通过application.properties或application.yml文件来配置日志级别。\n# 全局日志级别 logging.level.root=INFO # 指定包的日志级别 logging.level.com.example.demo=DEBUG logging: level: root: INFO com.example.demo: DEBUG 自定义Logback配置 # 通过在resources目录下创建一个logback-spring.xml文件来实现自定义配置。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;!-- 日志存放路径 --\u0026gt; \u0026lt;property name=\u0026#34;log.path\u0026#34; value=\u0026#34;./logs\u0026#34; /\u0026gt; \u0026lt;!-- 日志输出格式 --\u0026gt; \u0026lt;property name=\u0026#34;log.pattern\u0026#34; value=\u0026#34;%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n\u0026#34; /\u0026gt; \u0026lt;!-- 控制台输出 --\u0026gt; \u0026lt;appender name=\u0026#34;console\u0026#34; class=\u0026#34;ch.qos.logback.core.ConsoleAppender\u0026#34;\u0026gt; \u0026lt;encoder\u0026gt; \u0026lt;pattern\u0026gt;${log.pattern}\u0026lt;/pattern\u0026gt; \u0026lt;/encoder\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;!-- 系统日志输出 --\u0026gt; \u0026lt;appender name=\u0026#34;file_info\u0026#34; class=\u0026#34;ch.qos.logback.core.rolling.RollingFileAppender\u0026#34;\u0026gt; \u0026lt;file\u0026gt;${log.path}/sys-info.log\u0026lt;/file\u0026gt; \u0026lt;!-- 循环政策：基于时间创建日志文件 --\u0026gt; \u0026lt;rollingPolicy class=\u0026#34;ch.qos.logback.core.rolling.TimeBasedRollingPolicy\u0026#34;\u0026gt; \u0026lt;!-- 日志文件名格式 --\u0026gt; \u0026lt;fileNamePattern\u0026gt;${log.path}/sys-info.%d{yyyy-MM-dd}.log\u0026lt;/fileNamePattern\u0026gt; \u0026lt;!-- 日志最大的历史 60天 --\u0026gt; \u0026lt;maxHistory\u0026gt;60\u0026lt;/maxHistory\u0026gt; \u0026lt;/rollingPolicy\u0026gt; \u0026lt;encoder\u0026gt; \u0026lt;pattern\u0026gt;${log.pattern}\u0026lt;/pattern\u0026gt; \u0026lt;/encoder\u0026gt; \u0026lt;filter class=\u0026#34;ch.qos.logback.classic.filter.LevelFilter\u0026#34;\u0026gt; \u0026lt;!-- 过滤的级别 --\u0026gt; \u0026lt;level\u0026gt;INFO\u0026lt;/level\u0026gt; \u0026lt;!-- 匹配时的操作：接收（记录） --\u0026gt; \u0026lt;onMatch\u0026gt;ACCEPT\u0026lt;/onMatch\u0026gt; \u0026lt;!-- 不匹配时的操作：拒绝（不记录） --\u0026gt; \u0026lt;onMismatch\u0026gt;DENY\u0026lt;/onMismatch\u0026gt; \u0026lt;/filter\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;appender name=\u0026#34;file_error\u0026#34; class=\u0026#34;ch.qos.logback.core.rolling.RollingFileAppender\u0026#34;\u0026gt; \u0026lt;file\u0026gt;${log.path}/sys-error.log\u0026lt;/file\u0026gt; \u0026lt;!-- 循环政策：基于时间创建日志文件 --\u0026gt; \u0026lt;rollingPolicy class=\u0026#34;ch.qos.logback.core.rolling.TimeBasedRollingPolicy\u0026#34;\u0026gt; \u0026lt;!-- 日志文件名格式 --\u0026gt; \u0026lt;fileNamePattern\u0026gt;${log.path}/sys-error.%d{yyyy-MM-dd}.log\u0026lt;/fileNamePattern\u0026gt; \u0026lt;!-- 日志最大的历史 60天 --\u0026gt; \u0026lt;maxHistory\u0026gt;60\u0026lt;/maxHistory\u0026gt; \u0026lt;/rollingPolicy\u0026gt; \u0026lt;encoder\u0026gt; \u0026lt;pattern\u0026gt;${log.pattern}\u0026lt;/pattern\u0026gt; \u0026lt;/encoder\u0026gt; \u0026lt;filter class=\u0026#34;ch.qos.logback.classic.filter.LevelFilter\u0026#34;\u0026gt; \u0026lt;!-- 过滤的级别 --\u0026gt; \u0026lt;level\u0026gt;ERROR\u0026lt;/level\u0026gt; \u0026lt;!-- 匹配时的操作：接收（记录） --\u0026gt; \u0026lt;onMatch\u0026gt;ACCEPT\u0026lt;/onMatch\u0026gt; \u0026lt;!-- 不匹配时的操作：拒绝（不记录） --\u0026gt; \u0026lt;onMismatch\u0026gt;DENY\u0026lt;/onMismatch\u0026gt; \u0026lt;/filter\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;!-- 用户访问日志输出 --\u0026gt; \u0026lt;appender name=\u0026#34;sys-user\u0026#34; class=\u0026#34;ch.qos.logback.core.rolling.RollingFileAppender\u0026#34;\u0026gt; \u0026lt;file\u0026gt;${log.path}/sys-user.log\u0026lt;/file\u0026gt; \u0026lt;rollingPolicy class=\u0026#34;ch.qos.logback.core.rolling.TimeBasedRollingPolicy\u0026#34;\u0026gt; \u0026lt;!-- 按天回滚 daily --\u0026gt; \u0026lt;fileNamePattern\u0026gt;${log.path}/sys-user.%d{yyyy-MM-dd}.log\u0026lt;/fileNamePattern\u0026gt; \u0026lt;!-- 日志最大的历史 60天 --\u0026gt; \u0026lt;maxHistory\u0026gt;60\u0026lt;/maxHistory\u0026gt; \u0026lt;/rollingPolicy\u0026gt; \u0026lt;encoder\u0026gt; \u0026lt;pattern\u0026gt;${log.pattern}\u0026lt;/pattern\u0026gt; \u0026lt;/encoder\u0026gt; \u0026lt;/appender\u0026gt; \u0026lt;!-- 系统模块日志级别控制 --\u0026gt; \u0026lt;logger name=\u0026#34;top.ygang\u0026#34; level=\u0026#34;info\u0026#34; /\u0026gt; \u0026lt;!-- Spring日志级别控制 --\u0026gt; \u0026lt;logger name=\u0026#34;org.springframework\u0026#34; level=\u0026#34;warn\u0026#34; /\u0026gt; \u0026lt;!--系统操作日志--\u0026gt; \u0026lt;root level=\u0026#34;info\u0026#34;\u0026gt; \u0026lt;appender-ref ref=\u0026#34;console\u0026#34; /\u0026gt; \u0026lt;appender-ref ref=\u0026#34;file_info\u0026#34; /\u0026gt; \u0026lt;appender-ref ref=\u0026#34;file_error\u0026#34; /\u0026gt; \u0026lt;/root\u0026gt; \u0026lt;!--系统用户操作日志--\u0026gt; \u0026lt;logger name=\u0026#34;sys-user\u0026#34; level=\u0026#34;info\u0026#34;\u0026gt; \u0026lt;appender-ref ref=\u0026#34;sys-user\u0026#34;/\u0026gt; \u0026lt;/logger\u0026gt; \u0026lt;/configuration\u0026gt; ","date":"2024-11-29","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/4f5b7444/360f5090/","section":"文章","summary":"\u003cp\u003eSpring Boot默认使用SLF4J作为日志门面，并集成了Logback作为日志实现。SLF4J（Simple Logging Facade for Java）是一个通用的日志抽象层，可以与多种日志框架结合使用，如Logback、Log4j、Java Util Logging（JUL）等。Logback是一个快速、灵活且功能强大的日志框架，是Log4j的继任者。\u003c/p\u003e","title":"8、日志","type":"posts"},{"content":" 枚举 # 枚举是一组命名整型常量。枚举类型是使用 enum 关键字声明的。\nC# 枚举是值类型。换句话说，枚举包含自己的值，且不能继承或传递继承。\n声明 # enum \u0026lt;enum_name\u0026gt; { enumeration list }; enum_name： 指定枚举的类型名称。 enumeration list： 是一个用逗号分隔的标识符列表。 枚举列表中的每个符号代表一个整数值，一个比它前面的符号大的整数值。默认情况下，第一个枚举符号的值是 0。例如：\nusing System; public class EnumTest { enum Day { Sun, Mon, Tue, Wed, Thu, Fri, Sat }; static void Main() { int x = (int)Day.Sun; int y = (int)Day.Fri; Console.WriteLine(\u0026#34;Sun = {0}\u0026#34;, x); Console.WriteLine(\u0026#34;Fri = {0}\u0026#34;, y); } } ","date":"2024-09-11","externalUrl":null,"permalink":"/posts/5ab2821f/d32d7101/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e枚举 \n    \u003cdiv id=\"枚举\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%9e%9a%e4%b8%be\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e枚举是一组命名整型常量。枚举类型是使用 \u003cstrong\u003eenum\u003c/strong\u003e 关键字声明的。\u003c/p\u003e","title":"8、枚举","type":"posts"},{"content":"import { NodePool } from \u0026#34;cc\u0026#34;; NodePool 是用于管理节点对象的对象缓存池。\n它可以帮助您提高游戏性能，适用于优化对象的反复创建和销毁。\n以前 cocos2d-x 中的 pool 和新的节点事件注册系统不兼容，因此请使用 NodePool 来代替。\n新的 NodePool 需要实例化之后才能使用，每种不同的节点对象池需要一个不同的对象池实例，这里的种类对应于游戏中的节点设计，一个 prefab 相当于一个种类的节点。\n在创建缓冲池时，可以传入一个包含 unuse, reuse 函数的组件类型用于节点的回收和复用逻辑。\n一些常见的用例是：\n在游戏中的子弹（死亡很快，频繁创建，对其他对象无副作用） 飞机大战中的敌机 import { _decorator, Component, instantiate, Node, NodePool, Prefab } from \u0026#39;cc\u0026#39;; const { ccclass, property } = _decorator; interface INodePollMap{ [name: string]: NodePool } interface IPrefabMap{ [name: string]: Prefab } // 节点池控制类 @ccclass(\u0026#39;NodePoolController\u0026#39;) export class NodePoolController { // 按照类型分类管理节点池 private static iNodePollMap: INodePollMap = {} // 缓存用过的预制 private static iPrefabMap: IPrefabMap = {} // 获取节点 public static getNode(prefab: Prefab,parent: Node): Node{ // 缓存该预制 var name = prefab.name; NodePoolController.iPrefabMap[name] = prefab; var node: Node = null; var pool = NodePoolController.iNodePollMap[name]; // 判断是否存在该类型节点池 if(pool){ // 判断该节点池中是否存在节点对象 if(pool.size() \u0026gt; 0){ // 存在节点对象则直接返回 node = pool.get(); }else{ // 不存在则实例化一个新的节点对象 node = instantiate(prefab); } }else{ // 没有该类型的节点池，则创建一个 NodePoolController.iNodePollMap[name] = new NodePool(); node = instantiate(prefab); } // 激活 node.active = true; // 设置父节点 node.setParent(parent); return node; } // 节点无用后放回节点池 public static putNode(node: Node){ var name = node.name; // 设置禁用 node.active = false; // 判断该类型节点的节点池是否存在，不存在则创建 if(!NodePoolController.iNodePollMap[name]){ NodePoolController.iNodePollMap[name] = new NodePool(); } NodePoolController.iNodePollMap[name].put(node); } } ","date":"2024-09-05","externalUrl":null,"permalink":"/posts/69064821/92082869/46f6e310/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-ts\" data-lang=\"ts\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eimport\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003eNodePool\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"kr\"\u003efrom\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;cc\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003eNodePool\u003c/code\u003e 是用于管理节点对象的对象缓存池。\u003c/p\u003e\n\u003cp\u003e它可以帮助您提高游戏性能，适用于\u003cstrong\u003e优化对象的反复创建和销毁\u003c/strong\u003e。\u003c/p\u003e","title":"8、节点池","type":"posts"},{"content":" 指针的基本概念 # 指针的作用： 可以通过指针间接访问内存，指针也是一种数据类型\n内存编号是从0开始记录的，一般用十六进制数字表示 可以利用指针变量保存地址 指针变量的定义和使用 # 指针变量定义语法： 数据类型 * 变量名；\n指针的数据类型：取决于指针指向的内存空间，该内存空间存放的是什么类型数据，如果是数组，那么变量指向的是第一个元素，所以还是取决于数组的数据类型是什么\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { //创建一个变量 int a = 10; //定义指针，语法：数据类型 * 指针名 int * p; //让指针p记录a的地址 p = \u0026amp;a; cout \u0026lt;\u0026lt; \u0026amp;a \u0026lt;\u0026lt; endl; //0113F8F0 cout \u0026lt;\u0026lt; p \u0026lt;\u0026lt; endl; //0113F8F0 //解引用，在指针变量前加*，获取指向的内存空间，进行重新赋值 *p = 100; cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; // 100 system(\u0026#34;pause\u0026#34;); return 0; } 指针变量和普通变量的区别\n普通变量存放的是数据,指针变量存放的是地址 指针变量可以通过\u0026quot; * \u0026ldquo;操作符，操作指针变量指向的内存空间（也就是可以获取指针指向的内存空间，可以进行操作这块内存空间），这个过程称为解引用 总结1： 我们可以通过 \u0026amp; 符号 获取变量的地址\n总结2：利用指针可以记录地址\n总结3：对指针变量解引用，可以操作指针指向的内存\n指针所占内存空间 # #include\u0026lt;iostream\u0026gt; using namespace std; int main() { int * p; int size = sizeof(p); cout \u0026lt;\u0026lt; size \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 所有指针类型在32位操作系统下是4个字节\n所有指针类型在64位操作系统下是8个字节\n空指针 # 空指针：指针变量指向内存中编号为0的空间\n用途：初始化指针变量\n注意：空指针指向的内存是不可以访问的\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { //用于给指针变量初始化 int * p = NULL; cout \u0026lt;\u0026lt; p \u0026lt;\u0026lt; endl; //00000000 //空指针是不可以进行解引用使用的 *p = 100; //报错 system(\u0026#34;pause\u0026#34;); return 0; } 野指针 # 野指针：指针变量指向非法的内存空间\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { //用于给指针变量初始化 int * p = (int *)0x1100; //野指针不可以进行读写，非法 *p = 100; //报错，写入访问权限冲突 system(\u0026#34;pause\u0026#34;); return 0; } const修饰指针 # const修饰指针有三种情况\nconst修饰指针 \u0026mdash; 常量指针 const修饰常量 \u0026mdash; 指针常量 const即修饰指针，又修饰常量 常量指针 # const修饰指针：指针的指向可以进行修改，不可以对指向内存空间的值进行修改\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { int a = 10; int b = 20; const int * p = \u0026amp;a; p = \u0026amp;b; // 指针的指向可以进行修改 *p = 20; // 不可以对指向内存空间的值进行修改 system(\u0026#34;pause\u0026#34;); return 0; } 指针常量 # const修饰常量：指针的指向不可以进行修改，可以对指向内存空间的值进行修改\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { int a = 10; int b = 20; int * const p = \u0026amp;a; p = \u0026amp;b; // 指针的指向不可以进行修改 *p = 20; // 可以对指向内存空间的值进行修改 system(\u0026#34;pause\u0026#34;); return 0; } const同时修饰指针和常量 # 指针的指向和指向内存空间的值都不可以进行修改\n#include\u0026lt;iostream\u0026gt; using namespace std; int main() { int a = 10; int b = 20; const int * const p = \u0026amp;a; p = \u0026amp;b; // 指针的指向不可以进行修改 *p = 20; // 不可以对指向内存空间的值进行修改 system(\u0026#34;pause\u0026#34;); return 0; } 指针和数组 # 作用：利用指针访问数组中元素\n示例：\n#include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; int main() { //定义一个数组 string a[] = { \u0026#34;lucy\u0026#34;,\u0026#34;tom\u0026#34;,\u0026#34;lily\u0026#34;,\u0026#34;james\u0026#34; }; //a其实保存的就是数组的第一个元素的地址 string * p = a; //解引用，可以获取数组的第一个元素 cout \u0026lt;\u0026lt; *p \u0026lt;\u0026lt; endl; //lucy //由于数组是连续的内存空间，而一个指针占用的内存空间是4个字节，所以只需要将p后移四个字节，就指向了下一个元素 p++; cout \u0026lt;\u0026lt; *p \u0026lt;\u0026lt; endl; //tom system(\u0026#34;pause\u0026#34;); return 0; } 利用指针遍历数组 # #include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; int main() { //定义一个数组 string a[] = { \u0026#34;lucy\u0026#34;,\u0026#34;tom\u0026#34;,\u0026#34;lily\u0026#34;,\u0026#34;james\u0026#34; }; //定义指针 string * p = a; for (int i = 0; i \u0026lt; sizeof(a) / sizeof(a[0]); i++) { cout \u0026lt;\u0026lt; *p \u0026lt;\u0026lt; endl; p++; } system(\u0026#34;pause\u0026#34;); return 0; } 指针和函数 # 由于cpp的函数是值传递，所以如果需要引用传递（类似java），那么需要使用指针作为参数\n例子，如果不使用地址传递，那么函数是无法改变a和b的值：\n#include\u0026lt;iostream\u0026gt; using namespace std; void change(int * a,int * b) { int temp = *b; *b = *a; *a = temp; } int main() { int a = 10; int b = 20; change(\u0026amp;a, \u0026amp;b); cout \u0026lt;\u0026lt; \u0026#34;a = \u0026#34; \u0026lt;\u0026lt; a \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;b = \u0026#34; \u0026lt;\u0026lt; b \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 案例1 # 封装一个函数，利用冒泡排序，实现对整型数组的升序排序\nint arr[10] = { 4,3,6,9,1,2,10,8,7,5 }\n#include\u0026lt;iostream\u0026gt; using namespace std; //冒泡升序排序 void mopoSort(int * arr,int length) { for (int i = 0; i \u0026lt; length - 1; i++) { for (int j = 0; j \u0026lt; length - 1 - i; j++) { if (arr[j] \u0026gt; arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } //遍历数组 void toStringArr(int * arr,int length) { for (int i = 0; i \u0026lt; length; i++) { cout \u0026lt;\u0026lt; arr[i] \u0026lt;\u0026lt; endl; } } int main() { int arr[10] = { 4,3,6,9,1,2,10,8,7,5 }; //数组的长度 int length = sizeof(arr) / sizeof(arr[0]); mopoSort(arr, length); toStringArr(arr, length); system(\u0026#34;pause\u0026#34;); return 0; } ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/d8245fe1/09e70634/0915395f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e指针的基本概念 \n    \u003cdiv id=\"指针的基本概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8c%87%e9%92%88%e7%9a%84%e5%9f%ba%e6%9c%ac%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e指针的作用\u003c/strong\u003e： 可以通过指针间接访问内存，指针也是一种数据类型\u003c/p\u003e","title":"8、指针","type":"posts"},{"content":" 数组 # C 语言支持数组数据结构，它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据，但它往往被认为是一系列相同类型的变量。\n数组是一个整体，它的内存是连续的；也就是说，数组元素之间是相互挨着的，彼此之间没有一点点缝隙。\n连续的内存为指针操作（通过指针来访问数组元素）和内存处理（整块内存的复制、写入等）提供了便利，这使得数组可以作为缓存（临时存储数据的一块内存）使用。\n定义 # 这一步是为了分配内存空间，例如分配10个int的内存空间，40个字节\ntype arrayName [ arraySize ]; int intArr[10]; 初始化 # int intArr[5] = {1,2,3,4,5}; int intArr[] = {1,2,3,4,5}; 访问 # int i = intArr[1]; 长度 # sizeof()，可以获取一个变量所指向内存空间的大小，所以数组的长度=数组的总内存大小/数组第一个元素内存大小\nint len = sizeof(arr)/sizeof(arr[0]); 遍历 # #include\u0026lt;stdio.h\u0026gt; int main(){ int arr[] = {1,2,3,4,5}; //获取数组长度 int len = sizeof(arr) / sizeof(arr[0]); for(int i = 0;i \u0026lt; len;i++){ printf(\u0026#34;%d\\n\u0026#34;,arr[i]); } return 0; } 字符数组 # 字符数组实际上是一系列字符的集合，也就是字符串（String）。在C语言中，没有专门的字符串变量，没有string类型，通常就用一个字符数组来存放一个字符串。\nC语言规定，可以将字符串直接赋值给字符数组\nchar str[] = {\u0026#34;hello world\u0026#34;}; char str[] = \u0026#34;hello world\u0026#34;; 注意：字符数组只有在定义时才能将整个字符串一次性地赋值给它，一旦定义完了，就只能一个字符一个字符地赋值了\nchar str[7]; str = \u0026#34;abc123\u0026#34;; //错误 字符串结束标识 # 在C语言中，字符串总是以\\0作为结尾，所以\\0也被称为字符串结束标志，或者字符串结束符。\n\\0是 ASCII 码表中的第 0 个字符，英文称为 NUL，中文称为“空字符”。该字符既不能显示，也没有控制功能，输出该字符不会有任何效果，它在C语言中唯一的作用就是作为字符串结束标志。\nC语言在处理字符串时，会从前往后逐个扫描字符，一旦遇到'\\0'就认为到达了字符串的末尾，就结束处理。'\\0'至关重要，没有\\0就意味着永远也到达不了字符串的结尾。\n由\u0026quot; \u0026quot;包围的字符串会自动在末尾添加\\0。例如，\u0026quot;abc123\u0026quot;从表面看起来只包含了 6 个字符，其实不然，C语言会在最后隐式地添加一个\\0，这个过程是在后台默默地进行的，所以我们感受不到。\n注意： 逐个字符地给数组赋值并不会自动添加\\0 当用定长字符数组存储字符串时，要特别注意'\\0'，要为'\\0'留个位置；这意味着，字符数组的长度至少要比字符串的长度大 1。 字符串长度 # 在C语言中，我们使用string.h头文件中的 strlen() 函数来求字符串的长度\n#include\u0026lt;stdio.h\u0026gt; #include\u0026lt;string.h\u0026gt; int main(){ char str[] = \u0026#34;hello world\u0026#34;; int len = strlen(str); printf(\u0026#34;length of str = %d\u0026#34;,len); return 0; } 字符串处理函数 # C语言提供了丰富的字符串处理函数，可以对字符串进行输入、输出、合并、修改、比较、转换、复制、搜索等操作\nstring.h是一个专门用来处理字符串的头文件，它包含了很多字符串处理函数\nstrcat(str1,str2); //将str2拼接到str1后面 strcpy(str1,str2); //将str2复制到str1 strcmp(str1,str2); //比较两个字符串，如果相同返回0 strlen(s1); //返回字符串 s1 的长度。 strchr(s1, ch); //返回一个指针，指向字符串 s1 中字符 ch 的第一次出现的位置。 strstr(s1, s2); //返回一个指针，指向字符串 s1 中字符串 s2 的第一次出现的位置。 ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/f571508d/fa8095d0/bb281b3e/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e数组 \n    \u003cdiv id=\"数组\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%95%b0%e7%bb%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eC 语言支持\u003cstrong\u003e数组\u003c/strong\u003e数据结构，它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据，但它往往被认为是一系列相同类型的变量。\u003c/p\u003e","title":"8、数组","type":"posts"},{"content":" 什么是索引 # ==索引用于快速找出在某个列中有一特定值的行，避免全表扫描==\nMySQL索引，默认是B+树索引\n查询表中的索引 # show index from 表名 索引的优缺点 # 优点：\n1、所有的MySQL列类型（字段）都可以被索引，也就是可以给任意字段设置索引\n2、大大加快数据的查询速度\n缺点：\n1、创建索引和维护索引要耗费时间，并且随着数据量的增加所耗费的时间也会增加\n2、索引也需要占空间，如果有大量的索引，索引文件可能会比数据文件更快达到上限值\n3、当对表中的数据进行增、删、改时，索引也需要动态的维护，降低了数据的维护速度\n索引的分类 # 单列索引（普通索引、唯一索引、主键索引）、组合索引、全文索引、空间索引\n创建索引 # 普通索引(b+树索引) # alter table table_name add index index_name(column_name) using btree; create unique index index_name on table_name(column_name); 组合索引 # 唯一索引 # 全文索引 # ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/a6ece75f/8b824d9b/5d290da9/7e83adb3/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是索引 \n    \u003cdiv id=\"什么是索引\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af%e7%b4%a2%e5%bc%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e==索引用于快速找出在某个列中有一特定值的行，避免全表扫描==\u003c/p\u003e","title":"8、索引","type":"posts"},{"content":" 文件上传 # Fileupload组件 # 专门用于实现文件上传下载的免费组件\n特点\n使用简单，编写少量代码，完成上传下载功能 能够控制上传内容 能够控制上传文件的大小、类型 缺点\n目前已停止更新 FileUpload类 # FileItem类 # 使用 # 1、环境准备 # 1、将commons-fileupload组件导入工程\n\u0026lt;!-- upload file --\u0026gt; \u0026lt;!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;commons-fileupload\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;commons-fileupload\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.3.1\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- https://mvnrepository.com/artifact/commons-io/commons-io --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;commons-io\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;commons-io\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.4\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 2、提交文件的表单需要为\n\u0026lt;form enctype=\u0026#34;multipart/form-data\u0026#34; method=\u0026#34;post\u0026#34;\u0026gt; 2、在service方法中调用FileUpload对象，以及FileItem对象 # 文件上传 # @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //判断请求消息内容是否为：multipart/form-data多段类型 if (ServletFileUpload.isMultipartContent(req)){ //获取ServletFileUpload对象 ServletFileUpload servletFileUpload = new ServletFileUpload(new DiskFileItemFactory()); try { //利用ServletFileUpload对象的parseRequest()获取一个存放表单信息FileItem对象的集合 List\u0026lt;FileItem\u0026gt; list = servletFileUpload.parseRequest(req); //遍历集合 for (FileItem fileItem : list){ //判断该对象是否为普通表单字段 if (fileItem.isFormField()){ //获取该字段的name属性值 String name = fileItem.getFieldName(); //获取该字段的value，并转码为utf-8 String value = fileItem.getString(\u0026#34;utf-8\u0026#34;); System.out.println(name + \u0026#34;:\u0026#34; + value); //否则，为文件类型 }else { String name = fileItem.getFieldName(); //获取上传字段的文件名 String fileName = fileItem.getName(); System.out.println(fileName); //如果名字是空字符串，就证明没有上传 if (fileName.isEmpty()){ System.out.println(name + \u0026#34;没有上传文件\u0026#34;); }else { //存放在项目中的位置，如果没有，就创建 String path = \u0026#34;test/\u0026#34;; File file = new File(req.getServletContext().getRealPath(\u0026#34;/\u0026#34;) + path); file.mkdirs(); //存放文件 fileItem.write(new File(req.getServletContext().getRealPath(\u0026#34;/\u0026#34;) + path + fileName)); System.out.println(\u0026#34;文件上传完毕\u0026#34;); } } } }catch (Exception e) { e.printStackTrace(); } } } 文件下载 # @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获得文件路径 String filePath = req.getParameter(\u0026#34;filePath\u0026#34;); //将需要下载的文件写入输入流 FileInputStream fileInputStream = new FileInputStream(req.getServletContext().getRealPath(\u0026#34;/\u0026#34;) + filePath); //对文件路径字符串进行分割，获得文件名 String fileName = filePath.split(\u0026#34;/\u0026#34;)[1]; //中文名字需要编码后传输 String fileName = URLEncoder.encode(\u0026#34;测试文件.doc\u0026#34;, \u0026#34;UTF-8\u0026#34;); //设置响应头，告诉浏览器该文件应该下载，并设置文件名 resp.setHeader(\u0026#34;Content-Disposition\u0026#34;,\u0026#34;attachment;filename=\u0026#34; + fileName); //前后端分离需要主动暴露响应头给前端 resp.setHeader(\u0026#34;Access-Control-Expose-Headers\u0026#34;, \u0026#34;Content-Disposition\u0026#34;); //告知浏览器这是一个字节流，浏览器处理字节流的默认方式就是下载 resp.setHeader(\u0026#34;Content-Type\u0026#34;,\u0026#34;multipart/form-data\u0026#34;); //设置字符集编码 resp.setCharacterEncoding(\u0026#34;utf-8\u0026#34;); //获得输出流 ServletOutputStream outputStream = resp.getOutputStream(); //调用方法，进行下载 IOUtils.copy(fileInputStream,outputStream); //关闭资源 outputStream.flush(); outputStream.close(); fileInputStream.close(); } ","date":"2023-12-11","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/fb007bea/4c9c616b/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e文件上传 \n    \u003cdiv id=\"文件上传\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%87%e4%bb%b6%e4%b8%8a%e4%bc%a0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eFileupload组件 \n    \u003cdiv id=\"fileupload组件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#fileupload%e7%bb%84%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e专门用于实现文件上传下载的免费组件\u003c/p\u003e","title":"8、文件上传下载","type":"posts"},{"content":"argsparse是python的命令行解析的标准模块，内置于python（2.7之后），不需要安装。这个库可以让我们直接在命令行中就可以向程序中传入参数并让程序运行。\n其核心是通过add_argument方法自定义入参的：标志、格式、类型和范围等特性\nadd_argument # 函数原型\nArgumentParser.add_argument(name or flags...[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest]) name or flags - 一个命名或者一个选项字符串的列表，例如 foo 或 -f, \u0026ndash;foo。\naction - 当参数在命令行中出现时使用的动作基本类型。\nnargs - 命令行参数值的数量，*代表零个或多个，+代表一个或多个，?表示零个ho。\nconst - 被一些 action 和 nargs 选择所需求的常数。\ndefault - 当参数未在命令行中出现并且也不存在于命名空间对象时所产生的值。\ntype - 命令行参数应当被转换成的类型。\nchoices - 可用的参数的容器。\nrequired - 此命令行选项是否可省略 （仅选项可用）。\nhelp - 一个此选项作用的简单描述。\nmetavar - 在使用方法消息中使用的参数值示例。\ndest - 被添加到 parse_args() 所返回对象上的属性名。\nArgumentParser # 参数解析器，类原型\nclass argparse.ArgumentParser(prog=None, usage=None, description=None, epilog=None, parents=[], formatter_class=argparse.HelpFormatter, prefix_chars=\u0026#39;-\u0026#39;, fromfile_prefix_chars=None, argument_default=None, conflict_handler=\u0026#39;error\u0026#39;, add_help=True, allow_abbrev=True, exit_on_error=True) prog - The name of the program (default: os.path.basename(sys.argv[0]))\nusage - 描述程序用途的字符串（默认值：从添加到解析器的参数生成）\ndescription - 在参数帮助文档之前显示的文本（默认值：无）\nepilog - 在参数帮助文档之后显示的文本（默认值：无）\nparents - 一个 ArgumentParser 对象的列表，它们的参数也应包含在内\nformatter_class - 用于自定义帮助文档输出格式的类\nprefix_chars - 可选参数的前缀字符集合（默认值： \u0026lsquo;-\u0026rsquo;）\nfromfile_prefix_chars - 当需要从文件中读取其他参数时，用于标识文件名的前缀字符集合（默认值： None）\nargument_default - 参数的全局默认值（默认值： None）\nconflict_handler - 解决冲突选项的策略（通常是不必要的）\nadd_help - 为解析器添加一个 -h/\u0026ndash;help 选项（默认值： True）\nallow_abbrev - 如果缩写是无歧义的，则允许缩写长选项 （默认值：True）\nexit_on_error - 决定当错误发生时是否让 ArgumentParser 附带错误信息退出。 (默认值: True)\n使用 # import argparse #创建解析器 parser = argparse.ArgumentParser(description=\u0026#39;学生信息记录\u0026#39;) #添加参数 #指定位置参数，是必填的 parser.add_argument(\u0026#39;id\u0026#39;) #指定-n/--name参数，是必填的 parser.add_argument(\u0026#39;-n\u0026#39;,\u0026#39;--name\u0026#39;,help=\u0026#39;姓名\u0026#39;,required=True) #指定-a/--age参数，可选值为10-18 parser.add_argument(\u0026#39;-a\u0026#39;,\u0026#39;--age\u0026#39;,type=int,choices=range(10,19),help=\u0026#39;年龄\u0026#39;) #指定-s/-sex参数，可选值：男、女 parser.add_argument(\u0026#39;-s\u0026#39;,\u0026#39;--sex\u0026#39;,choices={\u0026#39;男\u0026#39;,\u0026#39;女\u0026#39;},help=\u0026#39;性别\u0026#39;) #解析参数，返回一个类似字典的对象 args = parser.parse_args() print(args) print(\u0026#39;学号：%s，姓名：%s，年龄：%s，性别：%s\u0026#39; % (args.id,args.name,args.age,args.sex)) 查看帮助信息\npython .\\test.py -h --------\u0026gt;\u0026gt; usage: test.py [-h] -n NAME [-a {10,11,12,13,14,15,16,17,18}] [-s {男,女}] id 学生信息记录 positional arguments: id optional arguments: -h, --help show this help message and exit -n NAME, --name NAME 姓名 -a {10,11,12,13,14,15,16,17,18}, --age {10,11,12,13,14,15,16,17,18} 年龄 -s {男,女}, --sex {男,女} 性别 传参测试\npython .\\test.py 199766 -n lucy -a 18 -s 女 --------\u0026gt;\u0026gt; Namespace(age=18, id=\u0026#39;199766\u0026#39;, name=\u0026#39;lucy\u0026#39;, sex=\u0026#39;女\u0026#39;) 学号：199766，姓名：lucy，年龄：18，性别：女 ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/1b37041b/9766939c/7a70e16d/","section":"文章","summary":"\u003cp\u003eargsparse是python的命令行解析的标准模块，内置于python（2.7之后），不需要安装。这个库可以让我们直接在命令行中就可以向程序中传入参数并让程序运行。\u003c/p\u003e","title":"8、argparse","type":"posts"},{"content":" java.lang.Math # 提供了一系列静态方法用于科学计算。\n常用方法 # 绝对值、最大最小值 # 绝大部分方法都有重载，支持int、long、float、double\nint abs(int a)：绝对值 int max(int a,int b)：最大值 int min(int a,int b)：最小值 求整 # double ceil(double a)：返回大于或等于a的最小整数 double floor(double a)：返回小于或等于a的最大整数 double rint(double a)：返回最接近a的整数，如果两个同样接近的整数，那么取偶数 double round(double a)：四舍五入取整 java.math.BigInteger # BigInteger可以表示不可变的任意精度的整数。提供java.lang.Math的所有相关方法。 另外，BigInteger 还提供以下运算：模算术、GCD 计算、质数测试、素数生成、 位操作以及一些其他操作。\n构建实例 # // 利用字符串创建 BigInteger b1 = new BigInteger(\u0026#34;1\u0026#34;); // 利用long创建 BigInteger b2 = BigInteger.valueOf(1L); 常用方法 # BigInteger abs()：返回绝对值 BigInteger add(BigInteger val)：求this + val BigInteger subtract(BigInteger val)：求this - val BigInteger multiply(BigInteger val)：求this * val BigInteger divide(BigInteger val)：求this / val，保留整数部分 BigInteger remainder(BigInteger val)：求this % val BigInteger divideAndRemainder(BigInteger val)：返回包含this / val和this % val两个值的数组 BigInteger pow(int exponent)：求this ^ exponet，指数exponet java.math.BigDecimal # BigDecimal表示不可变、任意精度的浮点数，一般的Float类和Double类可以用来做科学计算或工程计算。但在商业计算中，要求数字精度比较高，故用到BigDecimal类。BigDecimal类支持不可变的、任意精度的有符号十进制定点数。\n构建实例 # // long BigDecimal d1 = BigDecimal.valueOf(1L); // double BigDecimal d2 = BigDecimal.valueOf(1.2); // long BigDecimal d3 = new BigDecimal(1L); // double BigDecimal d4 = new BigDecimal(1.2); // String BigDecimal d5 = new BigDecimal(\u0026#34;1.2\u0026#34;); 常用方法 # BigDecimal abs()：返回绝对值 BigDecimal add(BigDecimal val)：求this + val BigDecimal subtract(BigDecimal val)：求this - val BigDecimal multiply(BigDecimal val)：求this * val BigDecimal divide(BigDecimal val)：求this / val 如果结果除不尽，会抛出ArithmeticException异常，可以指定处理策略 BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)，例如 BigDecimal divide = d5.divide(d4, 20, BigDecimal.ROUND_UP); BigDecimal remainder(BigDecimal val)：求this % val BigDecimal divideAndRemainder(BigDecimal val)：返回包含this / val和this % val两个值的数组 BigDecimal pow(int exponent)：求this ^ exponet，指数expone java.text.DecimalFormat # DecimalFormat 是 NumberFormat 的一个具体子类，用于格式化十进制数字。该类设计有各种功能，使其能够分析和格式化任意语言环境中的数，包括对西方语言、阿拉伯语和印度语数字的支持。它还支持不同类型的数，包括整数 (123)、浮点数 (123.4)、科学记数法表示的数 (1.23E4)、百分数 (12%) 和金额 ($123)。\n常见符号 # 0 # 0出现在整数部分，表示整数位数最少为0的个数，少了就补0\nDecimalFormat format = new DecimalFormat(\u0026#34;000\u0026#34;); System.out.println(format.format(99)); //099 System.out.println(format.format(9999)); //9999 0出现在小数部分，表示小数位数必须为0的个数，如果多了就会四舍五入，少了就会补0\nDecimalFormat format = new DecimalFormat(\u0026#34;0.000\u0026#34;); System.out.println(format.format(99.99)); //99.990 System.out.println(format.format(99.9999)); //100.000 # #出现在整数部分，并不会有格式化\nDecimalFormat format = new DecimalFormat(\u0026#34;#\u0026#34;); System.out.println(format.format(99)); //99 System.out.println(format.format(9999)); //9999 #出现在小数部分，表示最多为#的个数，如果少了不会补0，如果多了就四舍五入\nDecimalFormat format = new DecimalFormat(\u0026#34;#.###\u0026#34;); System.out.println(format.format(99.99)); //99.99 System.out.println(format.format(99.9999)); //100 % # %的作用是将小数转换为百分比形式，搭配0和#可以格式化结果\nDecimalFormat format = new DecimalFormat(\u0026#34;#.##%\u0026#34;); System.out.println(format.format(0.8999)); //89.99% System.out.println(format.format(0.89999)); //90% 其他用法 # // 每三位使用,分割。注意只能使用 , 才会有分割效果 System.out.println(new DecimalFormat(\u0026#34;,###\u0026#34;).format(899677566)); // 899,677,566 // 嵌入自定义文本 System.out.println(new DecimalFormat(\u0026#34;value is : #.###\u0026#34;).format(3.1415)); // value is : 3.142 // 保留整数 System.out.println(new DecimalFormat(\u0026#34;#\u0026#34;).format(3.14)); // 3 ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/409de243/137bbfa2/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003ejava.lang.Math \n    \u003cdiv id=\"javalangmath\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#javalangmath\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e提供了一系列静态方法用于科学计算。\u003c/p\u003e","title":"8、数学","type":"posts"},{"content":"os包是Go语言标准库中一个非常重要的包，它提供了一系列用于操作系统交互的功能，使开发者可以方便地进行文件和目录操作、环境变量管理、进程管理、信号处理等。\n环境变量 # 环境变量是操作系统用于传递配置信息的一种机制。在Go语言中，os包提供了一些函数用于读取、设置和删除环境变量。\n读取环境变量 # os.Getenv函数可以读取指定的环境变量。如果环境变量不存在，返回空字符串。\nos.LookupEnv函数除了返回环境变量的值，还返回一个布尔值，表示环境变量是否存在。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) func main() { // 读取环境变量 value := os.Getenv(\u0026#34;PATH\u0026#34;) fmt.Println(\u0026#34;PATH:\u0026#34;, value) // 查找环境变量 value, exists := os.LookupEnv(\u0026#34;PATH\u0026#34;) if exists { fmt.Println(\u0026#34;PATH exists:\u0026#34;, value) } else { fmt.Println(\u0026#34;PATH does not exist\u0026#34;) } } 设置环境变量 # 使用os.Setenv函数可以设置指定的环境变量。如果环境变量已存在，其值将被更新：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) func main() { // 设置环境变量 err := os.Setenv(\u0026#34;MY_VAR\u0026#34;, \u0026#34;Hello, World!\u0026#34;) if err != nil { fmt.Println(\u0026#34;Error setting environment variable:\u0026#34;, err) return } // 读取设置的环境变量 value := os.Getenv(\u0026#34;MY_VAR\u0026#34;) fmt.Println(\u0026#34;MY_VAR:\u0026#34;, value) } 删除环境变量 # 使用os.Unsetenv函数可以删除指定的环境变量：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) func main() { // 设置环境变量 err := os.Setenv(\u0026#34;MY_VAR\u0026#34;, \u0026#34;Hello, World!\u0026#34;) if err != nil { fmt.Println(\u0026#34;Error setting environment variable:\u0026#34;, err) return } // 删除环境变量 err = os.Unsetenv(\u0026#34;MY_VAR\u0026#34;) if err != nil { fmt.Println(\u0026#34;Error unsetting environment variable:\u0026#34;, err) return } // 读取删除的环境变量 value := os.Getenv(\u0026#34;MY_VAR\u0026#34;) fmt.Println(\u0026#34;MY_VAR:\u0026#34;, value) // 应该输出空字符串 } os/exec # 查找可执行文件 # 在环境变量PATH指定的目录中搜索可执行文件，如file中有斜杠，则只在当前目录搜索。返回完整路径或者相对于当前目录的一个相对路径。\nfunc LookPath(file string) (string, error) 示例\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;os/exec\u0026#34; ) func main() { // 查找可执行文件 go 的位置 path, err := exec.LookPath(\u0026#34;go\u0026#34;) if err != nil { panic(err) } fmt.Println(path) // 下面的内容就是执行命令 command := exec.Command(path, \u0026#34;version\u0026#34;) output, err := command.Output() if err != nil { panic(err) } fmt.Println(string(output)) } Command # type Cmd func Command(name string, arg ...string) *Cmd func CommandContext(ctx context.Context, name string, arg ...string) *Cmd func (c *Cmd) CombinedOutput() ([]byte, error) func (c *Cmd) Environ() []string func (c *Cmd) Output() ([]byte, error) func (c *Cmd) Run() error func (c *Cmd) Start() error func (c *Cmd) StderrPipe() (io.ReadCloser, error) func (c *Cmd) StdinPipe() (io.WriteCloser, error) func (c *Cmd) StdoutPipe() (io.ReadCloser, error) func (c *Cmd) String() string func (c *Cmd) Wait() error Cmd 结构体表示一个准备或正在执行的外部命令。 调用函数 Command 或 CommandContext 可以构造一个 *Cmd 对象。 调用 Run、Start、Output、CombinedOutput 方法可以运行 *Cmd 对象所代表的命令。 调用 Environ 方法可以获取命令执行时的环境变量。 调用 StdinPipe、StdoutPipe、StderrPipe 方法用于获取管道对象。 调用 Wait 方法可以阻塞等待命令执行完成。 调用 String 方法返回命令的字符串形式。 Run 和 Start # Start方法调用后开始执行命令，但是不会阻塞当前 goroutine，代码会继续往下执行。\nRun方法会阻塞等待命令执行完成，实际上 Run 方法就等于 Start + Wait 方法，如下是 Run 方法源码的实现：\nfunc (c *Cmd) Run() error { if err := c.Start(); err != nil { return err } return c.Wait() } 创建带有 context 的命令 # os/exec 还提供了一个 exec.CommandContext 构造函数可以创建一个带有 context 的命令。那么我们就可以利用 context 的特性来控制命令的执行了。\npackage main import ( \u0026#34;context\u0026#34; \u0026#34;log\u0026#34; \u0026#34;os/exec\u0026#34; \u0026#34;time\u0026#34; ) func main() { // 创建一个超时控制的 ctx ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() // 当命令执行超时会收到 killed 信号自动取消。 cmd := exec.CommandContext(ctx, \u0026#34;sleep\u0026#34;, \u0026#34;5\u0026#34;) if err := cmd.Run(); err != nil { log.Fatalf(\u0026#34;Command failed: %v\\n\u0026#34;, err) // Command failed: signal: killed } } 获取命令的输出 # 无论是调用 *Cmd.Run 还是 *Cmd.Start 方法，默认情况下执行命令后控制台不会得到任何输出。\n我们可以使用 *Cmd.Output 方法来执行命令，以此来获取命令的标准输出：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;os/exec\u0026#34; ) func main() { // 创建一个命令 cmd := exec.Command(\u0026#34;echo\u0026#34;, \u0026#34;Hello, World!\u0026#34;) // 执行命令，并获取命令的输出，Output 内部会调用 Run 方法 output, err := cmd.Output() if err != nil { log.Fatalf(\u0026#34;Command failed: %v\u0026#34;, err) } fmt.Println(string(output)) // Hello, World! } 获取组合的标准输出和错误输出 # *Cmd.CombinedOutput 方法能够在运行命令后，返回其组合的标准输出和标准错误输出：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;os/exec\u0026#34; ) func main() { // 使用一个命令，既产生标准输出，也产生标准错误输出 cmd := exec.Command(\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;echo \u0026#39;This is stdout\u0026#39;; echo \u0026#39;This is stderr\u0026#39; \u0026gt;\u0026amp;2\u0026#34;) // 获取 标准输出 + 标准错误输出 组合内容 output, err := cmd.CombinedOutput() if err != nil { log.Fatalf(\u0026#34;Command execution failed: %v\u0026#34;, err) } // 打印组合输出 fmt.Printf(\u0026#34;Combined Output:\\n%s\u0026#34;, string(output)) } 设置标准输出和错误输出 # 我们可以利用 *Cmd 对象的 Stdout 和 Stderr 属性，重定向标准输出和标准错误输出到当前进程：\npackage main import ( \u0026#34;log\u0026#34; \u0026#34;os\u0026#34; \u0026#34;os/exec\u0026#34; ) func main() { cmd := exec.Command(\u0026#34;ls\u0026#34;, \u0026#34;-l\u0026#34;) // 设置标准输出和标准错误输出到当前进程，执行后可以在控制台看到命令执行的输出 cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { log.Fatalf(\u0026#34;Command failed: %v\u0026#34;, err) } } 使用标准输入传递数据 # package main import ( \u0026#34;bytes\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;os/exec\u0026#34; ) func main() { cmd := exec.Command(\u0026#34;grep\u0026#34;, \u0026#34;hello\u0026#34;) // 通过标准输入传递数据给命令 cmd.Stdin = bytes.NewBufferString(\u0026#34;hello world!\u0026#34;) // 获取标准输出 output, err := cmd.Output() if err != nil { log.Fatalf(\u0026#34;Command failed: %v\u0026#34;, err) return } fmt.Println(string(output)) // hello world! } 设置和使用环境变量 # *Cmd 的 Environ 方法可以获取环境变量，Env 属性则可以设置环境变量：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;os/exec\u0026#34; ) func main() { cmd := exec.Command(\u0026#34;printenv\u0026#34;, \u0026#34;ENV_VAR\u0026#34;) log.Printf(\u0026#34;ENV: %+v\\n\u0026#34;, cmd.Environ()) // 设置环境变量 cmd.Env = append(cmd.Environ(), \u0026#34;ENV_VAR=HelloWorld\u0026#34;) log.Printf(\u0026#34;ENV: %+v\\n\u0026#34;, cmd.Environ()) // 获取输出 output, err := cmd.Output() if err != nil { log.Fatalf(\u0026#34;Command failed: %v\u0026#34;, err) } fmt.Println(string(output)) // HelloWorld } 管道 # os/exec 支持管道功能，*Cmd 对象提供的 StdinPipe、StdoutPipe、StderrPipe 三个方法用于获取管道对象。故名思义，三者分别对应标准输入、标准输出、标准错误输出的管道对象。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;os/exec\u0026#34; ) func main() { // 命令中使用了管道 cmdEcho := exec.Command(\u0026#34;echo\u0026#34;, \u0026#34;hello world\\nhi there\u0026#34;) outPipe, err := cmdEcho.StdoutPipe() if err != nil { log.Fatalf(\u0026#34;Command failed: %v\u0026#34;, err) } // 注意，这里不能使用 Run 方法阻塞等待，应该使用非阻塞的 Start 方法 if err := cmdEcho.Start(); err != nil { log.Fatalf(\u0026#34;Command failed: %v\u0026#34;, err) } cmdGrep := exec.Command(\u0026#34;grep\u0026#34;, \u0026#34;hello\u0026#34;) cmdGrep.Stdin = outPipe output, err := cmdGrep.Output() if err != nil { log.Fatalf(\u0026#34;Command failed: %v\u0026#34;, err) } fmt.Println(string(output)) // hello world } 首先创建一个用于执行 echo 命令的 *Cmd 对象 cmdEcho，并调用它的 StdoutPipe 方法获得标准输出管道对象 outPipe；\n然后调用 Start 方法非阻塞的方式执行 echo 命令；\n接着创建一个用于执行 grep 命令的 *Cmd 对象 cmdGrep，将 cmdEcho 的标准输出管道对象赋值给 cmdGrep.Stdin 作为标准输入，这样，两个命令就通过管道串联起来了；\n最终通过 cmdGrep.Output 方法拿到 cmdGrep 命令的标准输出。\n指定工作目录 # 可以通过指定 *Cmd 对象的的 Dir 属性来指定工作目录：\npackage main import ( \u0026#34;log\u0026#34; \u0026#34;os\u0026#34; \u0026#34;os/exec\u0026#34; ) func main() { cmd := exec.Command(\u0026#34;cat\u0026#34;, \u0026#34;demo.log\u0026#34;) cmd.Stdout = os.Stdout // 获取标准输出 cmd.Stderr = os.Stderr // 获取错误输出 // cmd.Dir = \u0026#34;/tmp\u0026#34; // 指定绝对目录 cmd.Dir = \u0026#34;.\u0026#34; // 指定相对目录 if err := cmd.Run(); err != nil { log.Fatalf(\u0026#34;Command failed: %v\u0026#34;, err) } } 捕获退出状态 # 这里执行 ls 命令来查看一个不存在的目录 /nonexistent，程序退出状态码必然不为 0。\npackage main import ( \u0026#34;errors\u0026#34; \u0026#34;log\u0026#34; \u0026#34;os/exec\u0026#34; ) func main() { // 查看一个不存在的目录 cmd := exec.Command(\u0026#34;ls\u0026#34;, \u0026#34;/nonexistent\u0026#34;) // 运行命令 err := cmd.Run() // 检查退出状态 var exitError *exec.ExitError if errors.As(err, \u0026amp;exitError) { log.Fatalf(\u0026#34;Process PID: %d exit code: %d\u0026#34;, exitError.Pid(), exitError.ExitCode()) // 打印 pid 和退出码 } } // 2023/05/26 10:10:43 Process PID: 62746 exit code: 1 os/signal # 信号是操作系统用来通知进程发生异步事件的一种机制。Go语言的os包和os/signal包提供了处理系统信号的功能，允许程序捕获和响应各种系统信号。\n捕获系统信号 # 使用os/signal包中的signal.Notify函数可以捕获系统信号（syscall包下定义）。\n常见的信号包括：\nSIGINT：中断信号\nSIGTERM：终止信号\nSIGHUP：挂起信号\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;os/signal\u0026#34; \u0026#34;syscall\u0026#34; ) func main() { // 创建信号通道 sigs := make(chan os.Signal, 1) // 注册要接收的信号 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) // 等待信号 sig := \u0026lt;-sigs fmt.Println(\u0026#34;Received signal:\u0026#34;, sig) } 自定义信号处理函数 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;os/signal\u0026#34; \u0026#34;syscall\u0026#34; ) func main() { // 创建信号通道 sigs := make(chan os.Signal, 1) // 注册要接收的信号 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) // 捕获信号并执行自定义处理函数 go func() { sig := \u0026lt;-sigs fmt.Println(\u0026#34;Received signal:\u0026#34;, sig) cleanup() os.Exit(0) }() // 模拟长时间运行的任务 fmt.Println(\u0026#34;Running... Press Ctrl+C to exit.\u0026#34;) select {} } // 自定义清理函数 func cleanup() { fmt.Println(\u0026#34;Performing cleanup tasks...\u0026#34;) } os/user # 使用os/user包可以获取当前用户和组的信息，包括用户名、用户ID、组名和组ID等。\n获取当前用户信息 # 使用user.Current函数可以获取当前用户的信息：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;os/user\u0026#34; ) func main() { user, err := user.Current() if err != nil { fmt.Println(\u0026#34;Error getting current user:\u0026#34;, err) return } fmt.Println(\u0026#34;Username:\u0026#34;, user.Username) fmt.Println(\u0026#34;User ID:\u0026#34;, user.Uid) fmt.Println(\u0026#34;Group ID:\u0026#34;, user.Gid) fmt.Println(\u0026#34;Home Directory:\u0026#34;, user.HomeDir) } 获取指定用户信息 # 使用user.Lookup函数可以根据用户名获取指定用户的信息：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;os/user\u0026#34; ) func main() { user, err := user.Lookup(\u0026#34;root\u0026#34;) if err != nil { fmt.Println(\u0026#34;Error looking up user:\u0026#34;, err) return } fmt.Println(\u0026#34;Username:\u0026#34;, user.Username) fmt.Println(\u0026#34;User ID:\u0026#34;, user.Uid) fmt.Println(\u0026#34;Group ID:\u0026#34;, user.Gid) fmt.Println(\u0026#34;Home Directory:\u0026#34;, user.HomeDir) } 系统时钟与时间管理 # 使用os包可以获取系统的当前时间，还可以通过time包来进行更多的时间操作。\n","date":"2025-05-26","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/12d682f6/fe895985/","section":"文章","summary":"\u003cp\u003e\u003ccode\u003eos\u003c/code\u003e包是Go语言标准库中一个非常重要的包，它提供了一系列用于操作系统交互的功能，使开发者可以方便地进行文件和目录操作、环境变量管理、进程管理、信号处理等。\u003c/p\u003e","title":"9、os","type":"posts"},{"content":" JWT 概念 # JSON Web Token（JWT）是一种开放标准（RFC 7519），用于在网络应用环境间安全地传递声明（claims）。JWT 是一种紧凑且自包含的方式，用于作为 JSON 对象在各方之间安全地传输信息。由于其信息是经过数字签名的，所以可以确保发送的数据在传输过程中未被篡改。\nJWT 由三个部分组成，它们之间用 . 分隔，格式如下：eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJQcm9ncmFtbWVyIiwiaXNzIjoi56iL5bqP5ZGY6ZmI5piO5YuHIiwic3ViIjoiY2hlbm1pbmd5b25nLmNuIn0.uRnH-rUb7lsZtQ11o8wXjIOJnIMBxszkvU1gY6hCGjo\nHeader（头部）：Hedaer 部分用于描述该 JWT 的基本信息，比如其类型（通常是 JWT）以及所使用的签名算法（如 HMAC SHA256 或 RSA）。 Payload（负载）： Payload 部分包含所传递的声明。声明是关于实体（通常是用户）和其他数据的语句。声明可以分为三种类型：注册声明、公共声明 和 私有声明。 注册声明：这些声明是预定义的，非必须使用的但被推荐使用。官方标准定义的注册声明有 7 个：\nClaim（声明） 含义 iss(Issuer) 发行者，标识 JWT 的发行者。 sub(Subject) 主题，标识 JWT 的主题，通常指用户的唯一标识 aud(Audience) 观众，标识 JWT的接收者 exp(Expiration Time) 过期时间。标识 JWT 的过期时间，这个时间必须是将来的 nbf(Not Before) 不可用时间。在此时间之前，JWT 不应被接受处理 iat(Issued At) 发行时间，标识 JWT 的发行时间 jti(JWT ID) JWT 的唯一标识符，用于防止 JWT 被重放（即重复使用） 公共声明：可以由使用 JWT 的人自定义，但为了避免冲突，任何新定义的声明都应已在IANA JSON Web Token Registry中注册或者是一个 公共名称，其中包含了碰撞防抗性名称（Collision-Resistant Name）。\n私有声明：发行和使用 JWT 的双方共同商定的声明，区别于 注册声明 和 公共声明。\nSignature（签名）：为了防止数据篡改，将头部和负载的信息进行一定算法处理，加上一个密钥，最后生成签名。如果使用的是 HMAC SHA256 算法，那么签名就是将编码后的头部、编码后的负载拼接起来，通过密钥进行HMAC SHA256 运算后的结果。 golang-jwt # go get -u github.com/golang-jwt/jwt/v5 创建 Token（JWT） 对象 # 生成 JWT 字符串首先需要创建 Token 对象（代表着一个 JWT）。\njwt 库主要通过两个函数来创建 Token 对象：NewWithClaims 和 New。\nNewWithClaims 函数 # jwt.NewWithClaims 函数用于创建一个 Token 对象，该函数允许指定一个签名方法和一组声明claims以及可变参数 TokenOption。\nNewWithClaims(method SigningMethod, claims Claims, opts ...TokenOption) *Token method：这是一个 SigningMethod 接口参数，用于指定 JWT 的签名算法。常用的签名算法有 SigningMethodHS256、SigningMethodRS256等。这些算法分别代表不同的签名技术，如 HMAC、RSA。 claims：这是一个 Claims 接口参数，它表示 JWT 的声明。在 jwt 库中，预定义了一些结构体来实现这个接口，例如 RegisteredClaims 和 MapClaims 等，通过指定 Claims 的实现作为参数，我们可以为JWT 添加声明信息，例如发行人（iss）、主题（sub）等。 opts：这是一个可变参数，允许传递零个或多个 TokenOption 类型参数。TokenOption 是一个函数，它接收一个 *Token，这样就可以在创建 Token 的时候对其进行进一步的配置。 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/golang-jwt/jwt/v5\u0026#34; ) func main() { mapClaims := jwt.MapClaims{ \u0026#34;iss\u0026#34;: \u0026#34;GradyYoung\u0026#34;, \u0026#34;sub\u0026#34;: \u0026#34;ygang.top\u0026#34;, \u0026#34;aud\u0026#34;: \u0026#34;Programmer\u0026#34;, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, mapClaims) fmt.Println(token != nil) // true } New 函数 # jwt.New 函数用于创建一个 Token 对象，该函数允许指定一个签名方法和可变参数 TokenOption。\nfunc New(method SigningMethod, opts ...TokenOption) *Token { return NewWithClaims(method, MapClaims{}, opts...) } 通过源码我们可以发现，该函数内部的实现通过调用 NewWithClaims 函数，并默认传入一个空的 MapClaims 对象，从而生成一个 Token 对象。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/golang-jwt/jwt/v5\u0026#34; ) func main() { token := jwt.New(jwt.SigningMethodHS256) fmt.Println(token != nil) // true } 生成 JWT 字符串 # 通过使用 jwt.Token 对象的 SignedString 方法，我们能够对 JWT 对象进行序列化和签名处理，以生成最终的 token 字符串。该方法的签名如下：\nfunc (t *Token) SignedString(key interface{}) (string, error) key：该参数是用于签名 token 的密钥。密钥的类型取决于使用的签名算法。 如果使用 HMAC 算法（如 HS256、HS384 等），key 应该是一个对称密钥（通常是 []byte 类型的密钥）。 如果使用 RSA 或 ECDSA 签名算法（如 RS256、ES256），key 应该是一个私钥 *rsa.PrivateKey 或 *ecdsa.PrivateKey。 方法返回两个值：一个是成功签名后的 JWT 字符串，另一个是在签名过程中遇到的任何错误。 // https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/jwt/generage-token/generate_token.go package main import ( \u0026#34;crypto/rand\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/golang-jwt/jwt/v5\u0026#34; ) func GenerateJwt(key any, method jwt.SigningMethod, claims jwt.Claims) (string, error) { token := jwt.NewWithClaims(method, claims) return token.SignedString(key) } func main() { jwtKey := make([]byte, 32) // 生成32字节（256位）的密钥 if _, err := rand.Read(jwtKey); err != nil { panic(err) } jwtStr, err := GenerateJwt(jwtKey, jwt.SigningMethodHS256, jwt.MapClaims{ \u0026#34;iss\u0026#34;: \u0026#34;GradyYoung\u0026#34;, \u0026#34;sub\u0026#34;: \u0026#34;ygang.top\u0026#34;, \u0026#34;aud\u0026#34;: \u0026#34;Programmer\u0026#34;, }) if err != nil { panic(err) } fmt.Println(jwtStr) } 解析 JWT 字符串 # jwt 库主要通过两个函数来解析 jwt 字符串：Parse 和 ParseWithClaims。\nParse 函数 # Parse 函数用于解析 JWT 字符串，函数签名如下：\nfunc Parse(tokenString string, keyFunc Keyfunc, options ...ParserOption) (*Token, error) tokenString：要解析的 JWT 字符串。 keyFunc：这是一个回调函数，返回用于验证 JWT 签名的密钥。该函数签名为 func(*Token) (interface{}, error)。这种设计，有利于我们根据 token 对象的信息返回正确的密钥。例如我们可能有一个 keyMap 对象，类型为 map，该对象用于保存多个 key 的映射，通过 Token 对象的信息，拿到某个标识，就能通过 keyMap 获取到正确的密钥。 options：这是一个可变参数。允许传递零个或多个 ParserOption 类型参数。这些选项可以用来定制解析器的行为，如设置 exp 声明为必需的参数，否则解析失败。 // https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/jwt/parse-token/parse.go package main import ( \u0026#34;crypto/rand\u0026#34; \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/golang-jwt/jwt/v5\u0026#34; \u0026#34;time\u0026#34; ) func ParseJwt(key any, jwtStr string, options ...jwt.ParserOption) (jwt.Claims, error) { token, err := jwt.Parse(jwtStr, func(token *jwt.Token) (interface{}, error) { return key, nil }, options...) if err != nil { return nil, err } // 校验 Claims 对象是否有效，基于 exp（过期时间），nbf（不早于），iat（签发时间）等进行判断（如果有这些声明的话）。 if !token.Valid { return nil, errors.New(\u0026#34;invalid token\u0026#34;) } return token.Claims, nil } func main() { jwtKey := make([]byte, 32) // 生成32字节（256位）的密钥 if _, err := rand.Read(jwtKey); err != nil { panic(err) } token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ \u0026#34;iss\u0026#34;: \u0026#34;GradyYoung\u0026#34;, \u0026#34;sub\u0026#34;: \u0026#34;ygang.top\u0026#34;, \u0026#34;aud\u0026#34;: \u0026#34;Programmer\u0026#34;, \u0026#34;exp\u0026#34;: time.Now().Add(time.Second * 10).UnixMilli(), }) jwtStr, err := token.SignedString(jwtKey) if err != nil { panic(err) } // 解析 jwt claims, err := ParseJwt(jwtKey, jwtStr, jwt.WithExpirationRequired()) if err != nil { panic(err) } fmt.Println(claims) } ParseWithClaims 函数 # ParseWithClaims 函数类似 Parse，函数签名如下：\nfunc ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc, options ...ParserOption) (*Token, error) tokenString：要解析的 JWT 字符串。 claims：这是一个 Claims 接口参数，用于接收解析 JWT 后的 claims 数据。 keyFunc：与 Parse 函数中的相同，用于提供验证签名所需的密钥。 options：与 Parse 函数中的相同，用来定制解析器的行为。 // https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/jwt/parse-token/parse_with_claims.go package main import ( \u0026#34;crypto/rand\u0026#34; \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/golang-jwt/jwt/v5\u0026#34; ) func ParseJwtWithClaims(key any, jwtStr string, options ...jwt.ParserOption) (jwt.Claims, error) { mc := jwt.MapClaims{} token, err := jwt.ParseWithClaims(jwtStr, mc, func(token *jwt.Token) (interface{}, error) { return key, nil }, options...) if err != nil { return nil, err } // 校验 Claims 对象是否有效，基于 exp（过期时间），nbf（不早于），iat（签发时间）等进行判断（如果有这些声明的话）。 if !token.Valid { return nil, errors.New(\u0026#34;invalid token\u0026#34;) } return token.Claims, nil } func main() { jwtKey := make([]byte, 32) // 生成32字节（256位）的密钥 if _, err := rand.Read(jwtKey); err != nil { panic(err) } token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ \u0026#34;iss\u0026#34;: \u0026#34;GradyYoung\u0026#34;, \u0026#34;sub\u0026#34;: \u0026#34;ygang.top\u0026#34;, \u0026#34;aud\u0026#34;: \u0026#34;Programmer\u0026#34;, }) jwtStr, err := token.SignedString(jwtKey) if err != nil { panic(err) } // 解析 jwt claims, err := ParseJwtWithClaims(jwtKey, jwtStr) if err != nil { panic(err) } fmt.Println(claims) } JWT 工具 # package utils import ( \u0026#34;errors\u0026#34; \u0026#34;github.com/golang-jwt/jwt/v5\u0026#34; \u0026#34;time\u0026#34; ) // Claims 自定义 JWT 声明 type Claims struct { UserId uint `json:\u0026#34;userId\u0026#34;` jwt.RegisteredClaims } // GenerateToken 生成 JWT 令牌 func GenerateToken(userId uint, secret string, expirySecond int) (string, error) { // 设置过期时间 now := time.Now() expirationTime := now.Add(time.Duration(expirySecond) * time.Second) // 创建声明 claims := \u0026amp;Claims{ UserId: userId, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(expirationTime), IssuedAt: jwt.NewNumericDate(now), }, } // 创建令牌 token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 签名令牌 signedString, err := token.SignedString([]byte(secret)) if err != nil { return \u0026#34;\u0026#34;, err } return signedString, nil } // ValidateToken 验证令牌 func ValidateToken(tokenString, secret string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenString, \u0026amp;Claims{}, func(token *jwt.Token) (interface{}, error) { return []byte(secret), nil }) if err != nil { return nil, err } if !token.Valid { return nil, errors.New(\u0026#34;令牌无效！\u0026#34;) } claims, ok := token.Claims.(*Claims) if !ok { return nil, errors.New(\u0026#34;无法获取令牌声明！\u0026#34;) } return claims, nil } ","date":"2025-05-22","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/29ede47b/a712e45f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJWT 概念 \n    \u003cdiv id=\"jwt-概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jwt-%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eJSON Web Token\u003c/code\u003e（\u003ccode\u003eJWT\u003c/code\u003e）是一种开放标准（RFC 7519），用于在网络应用环境间安全地传递声明（\u003ccode\u003eclaims\u003c/code\u003e）。\u003ccode\u003eJWT\u003c/code\u003e 是一种紧凑且自包含的方式，用于作为 \u003ccode\u003eJSON\u003c/code\u003e 对象在各方之间安全地传输信息。由于其信息是经过数字签名的，所以可以确保发送的数据在传输过程中未被篡改。\u003c/p\u003e","title":"9、jwt","type":"posts"},{"content":"2022年3月15日，争议非常大但同时也备受期待的泛型终于伴随着Go1.18发布了。\nGo 是一种静态类型编程语言，这意味着在编译时会检查变量和参数的类型。内置类型（map、slice、channel）和内置函数（如 len、cap 或 make）能够接收和返回不同具体类型的值，但在 Go 1.18 之前，用户定义的 Go 类型和函数不能如此。\n使用前提 # go version \u0026gt;= 1.18\n没有泛型 # 泛型生命周期只在编译期，旨在为程序员生成代码，减少重复代码的编写 在比较两个数的大小时，没有泛型的时候，仅仅只是传入类型不一样，我们就要再写一份一模一样的函数，如果有了泛型就可以减少这类代码 例如\n// 比较int类型 func compareMaxInt(a, b int) int { if a \u0026gt; b { return a } return b } // 比较float64类型 func compareMaxFloat(a, b float64) float64 { if a \u0026gt; b { return a } return b } 有泛型 # // 比较int或float64类型 func compareMax[T int | float64](a, b T) T{ if a \u0026gt; b { return a } return b } 泛型的定义 # 泛型函数 # 只需要在函数名后面使用中括号[]声明泛型名称，以及泛型允许出现的类型\n// 定义泛型函数 func compareMax[T int | float64](a, b T) T{ if a \u0026gt; b { return a } return b } //调用 func main() { // 自动类型推导 compareMax(10, 20) // 指定类型 compareMax[float64](10, 20) } 泛型类型 # 自定义 # 用于泛型类型过多，或者该泛型类型在多处使用，定义方法类似于接口\n// Num 自定义泛型类 type Num interface { int | float64 | float32 | rune } func compare[B Num](a, b B) B { if a \u0026gt; b { return a } return b } 内置 # any: 表示go里面所有的内置基本类型，等价于interface{}\ncomparable: 表示go里面所有内置的可比较类型：int、uint、float、bool、struct、指针等一切可以比较的类型，只可以使用==和!=进行比较\nfunc equal(a, b comparable) bool { return a == b } 泛型结构体 # package main import \u0026#34;fmt\u0026#34; type Student[K string | []byte, V int | float64] struct { id int name K score V } func main() { s1 := Student[string, int]{1, \u0026#34;lucy\u0026#34;, 18} s2 := Student[[]byte, float64]{1, []byte(\u0026#34;lucy\u0026#34;), 18.5} fmt.Println(s1, s2) } ~符号 # 为了支持新定义的类型对其底层类型符合的方便使用，golang增加了新的~字符，意思是如果底层类型int，就可以正常进行泛型的实例化。\npackage main type Num interface { int | float64 } func equal[T Num](a, b T) bool { return a == b } type MyInt int func main() { equal[int](10, 20) //正确 equal[MyInt](10, 20) //错误 } 如果要使上面第二个调用正确，只需要修改Num中对int的定义即可\ntype Num interface { ~int | float64 } ","date":"2025-05-14","externalUrl":null,"permalink":"/posts/2d3150b2/0e0cf2f4/8897d525/","section":"文章","summary":"\u003cp\u003e2022年3月15日，争议非常大但同时也备受期待的泛型终于伴随着\u003ccode\u003eGo1.18\u003c/code\u003e发布了。\u003c/p\u003e","title":"9、泛型","type":"posts"},{"content":"Listbox（列表框）组件用于显示一个选择列表。Listbox 只能包含文本项目，并且所有的项目都需要使用相同的字体和颜色。根据组件的配置，用户可以从列表中选择一个或多个选项。\nimport tkinter as tk master = tk.Tk() # 创建一个空列表 theLB = tk.Listbox(master) theLB.pack() # 往列表里添加数据 for item in [\u0026#34;鸡蛋\u0026#34;, \u0026#34;鸭蛋\u0026#34;, \u0026#34;鹅蛋\u0026#34;, \u0026#34;李狗蛋\u0026#34;]: theLB.insert(\u0026#34;end\u0026#34;, item) master.mainloop() 使用 delete() 方法删除列表中的项目，最常用的操作是删除列表中的所有项目（更新列表时你需要做的事儿）\nlistbox.delete(0, \u0026#34;end\u0026#34;) listbox.insert(\u0026#34;end\u0026#34;, newitem) #插入新的项目 ","date":"2025-04-15","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/a879fe06/69e9fe02/","section":"文章","summary":"\u003cp\u003eListbox（列表框）组件用于显示一个选择列表。Listbox 只能包含文本项目，并且所有的项目都需要使用相同的字体和颜色。根据组件的配置，用户可以从列表中选择一个或多个选项。\u003c/p\u003e","title":"9、列表框Listbox","type":"posts"},{"content":" Json # 如果我们要在不同的编程语言之间传递对象，就必须把对象序列化为标准格式，比如XML，但更好的方法是序列化为JSON，因为JSON表示出来就是一个字符串，可以被所有语言读取，也可以方便地存储到磁盘或者通过网络传输。JSON不仅是标准格式，并且比XML更快，而且可以直接在Web页面中读取，非常方便。\nJSON表示的对象就是标准的JavaScript语言的对象，JSON和Python内置的数据类型对应如下\nJSON类型 Python类型 {} dict [] list \u0026ldquo;string\u0026rdquo; str 1234.56 int或float true/false True/False null None Python内置的json模块提供了非常完善的Python对象到JSON格式的转换\ndumps # dumps()方法返回一个str，内容就是标准的JSON\njson.dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, encoding=\u0026#34;utf-8\u0026#34;, default=None, sort_keys=False, **kw) obj：就是你要转化成json的对象。\nsort_keys =True：是告诉编码器按照字典排序(a到z)输出。如果是字典类型的python对象，就把关键字按照字典排序。\nindent：参数根据数据格式缩进显示，读起来更加清晰，None为不缩进，数值为缩进的空格数\nseparators：是分隔符的意思，参数意思分别为不同dict项之间的分隔符和dict项内key和value之间的分隔符，把：和，后面的空格都除去了。\nskipkeys：默认值是False，如果dict的keys内的数据不是python的基本类型(str,unicode,int,long,float,bool,None)，设置为False时，就会报TypeError的错误。此时设置成True，则会跳过这类key 。\nensure_ascii：默认输出ASCLL码，如果把这个该成False,就可以输出中文。\ncheck_circular：如果check_circular为false，则跳过对容器类型的循环引用检查，循环引用将导致溢出错误(或更糟的情况)。\nallow_nan：如果allow_nan为假，则ValueError将序列化超出范围的浮点值(nan、inf、-inf)，严格遵守JSON规范，而不是使用JavaScript等价值(nan、Infinity、-Infinity)。\ndefault：default(obj)是一个函数，它应该返回一个可序列化的obj版本或引发类型错误。默认值只会引发类型错误。\nimport json a = {\u0026#39;name\u0026#39;:\u0026#39;lucy\u0026#39;,\u0026#39;age\u0026#39;:18} str = json.dumps(a) print(str) \u0026#39;\u0026#39;\u0026#39; {\u0026#34;name\u0026#34;: \u0026#34;lucy\u0026#34;, \u0026#34;age\u0026#34;: 18} \u0026#39;\u0026#39;\u0026#39; dump # dump可以将转换后的json字符串写入文件\nimport json a = {\u0026#39;name\u0026#39;:\u0026#39;lucy\u0026#39;,\u0026#39;age\u0026#39;:18} file = open(\u0026#39;./person.txt\u0026#39;,\u0026#39;w\u0026#39;) json.dump(a,file) file.close() loads # 可以将json转换为python对象\nimport json a = json.loads(\u0026#39;{\u0026#34;name\u0026#34;: \u0026#34;lucy\u0026#34;, \u0026#34;age\u0026#34;: 18}\u0026#39;) print(a) print(type(a)) \u0026#39;\u0026#39;\u0026#39; {\u0026#39;name\u0026#39;: \u0026#39;lucy\u0026#39;, \u0026#39;age\u0026#39;: 18} \u0026lt;class \u0026#39;dict\u0026#39;\u0026gt; \u0026#39;\u0026#39;\u0026#39; load # 可以将json文件，转换为python对象\nimport json file = open(\u0026#39;./person.txt\u0026#39;,\u0026#39;r\u0026#39;) a = json.load(file) print(a) file.close() \u0026#39;\u0026#39;\u0026#39; {\u0026#39;name\u0026#39;: \u0026#39;lucy\u0026#39;, \u0026#39;age\u0026#39;: 18} \u0026#39;\u0026#39;\u0026#39; 自定义类型-JSON # 由于python和json之间的互相转换只限制于python内置基本数据类型，对于自定义的数据类型，如果使用dumps进行转换，会抛出TypeError；但是dumps函数有一个形参default，这个参数需要传一个函数，函数需要可以将该对象转为python数据类型，并返回，例如dict字典\nimport json class Person(object): def __init__(self,name,age): self.name = name self.age = age def person2json(per): \u0026#34;将person对象转为dict\u0026#34; return { \u0026#39;name\u0026#39;:per.name, \u0026#39;age\u0026#39;:per.age } person = Person(\u0026#39;lucy\u0026#39;,18) str = json.dumps(person,default=person2json) print(str) \u0026#39;\u0026#39;\u0026#39; {\u0026#34;name\u0026#34;: \u0026#34;lucy\u0026#34;, \u0026#34;age\u0026#34;: 18} \u0026#39;\u0026#39;\u0026#39; 通常class的实例都有一个__dict__属性，它就是一个dict，用来存储实例变量。也有少数例外，比如定义了__slots__的class，所以我们可以不用给每个对象都写一个转dict的函数\nimport json class Person(object): def __init__(self,name,age): self.name = name self.age = age person = Person(\u0026#39;lucy\u0026#39;,18) str = json.dumps(person,default=lambda per : per.__dict__) print(str) \u0026#39;\u0026#39;\u0026#39; {\u0026#34;name\u0026#34;: \u0026#34;lucy\u0026#34;, \u0026#34;age\u0026#34;: 18} \u0026#39;\u0026#39;\u0026#39; ","date":"2025-04-09","externalUrl":null,"permalink":"/posts/1b37041b/9766939c/d7052025/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJson \n    \u003cdiv id=\"json\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#json\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e如果我们要在不同的编程语言之间传递对象，就必须把对象序列化为标准格式，比如XML，但更好的方法是序列化为JSON，因为JSON表示出来就是一个字符串，可以被所有语言读取，也可以方便地存储到磁盘或者通过网络传输。JSON不仅是标准格式，并且比XML更快，而且可以直接在Web页面中读取，非常方便。\u003c/p\u003e","title":"9、json","type":"posts"},{"content":" vuex是什么 # 概念：专门在vue中实现集中式状态管理的一个Vue插件，对Vue应用中多个组件的共享状态进行集中式的管理（读、写）也是一种组件间通信的方式，且适合于任意组件间通信。\nhttps://github.com/vuejs/vuex\n什么时候是用vuex # 1、多个组件依赖于同一个状态\n2、来自不同组件的行为要变更同一状态\n工作原理 # mutations和actions区别 # Vuex中的mutations用于同步操作，而actions则处理异步操作。以下是它们的具体区别：\n同步与异步： Mutations是同步的，这意味着在mutation中的函数执行时，不能包含任何异步操作，如Promise或者setTimeout等。这样可以保证状态变更的追踪和调试相对简单直观。 Actions可以包含异步操作，它通过分发（dispatch）来触发，并且最终会提交（commit）一个mutation来变更状态。这使得actions成为处理例如API调用等需要等待响应的操作的理想选择。 修改state的方式： Mutations可以直接修改state，但必须通过提交mutation的方式来进行，通常在组件中通过this.$store.commit('mutationName', payload)来调用。 Actions无法直接修改state，它们的主要职责是执行异步操作，然后通过提交mutation来更改状态。在组件中，actions通过this.$store.dispatch('actionName', payload)来触发。 搭建vuex环境 # 1、安装vuex\n注意：vuex@4版本只能在vue3中使用，如果vue2中使用，需要安装vuex@3\ncnpm i vuex@3 3、src下创建store/index.js，声明store配置项actions、mutations、state，并且Vue使用vuex（必须在store实例化之前，所以不能写在main.js中），然后实例化store并导出\n//该文件用于创建vuex中核心store import Vue from \u0026#39;vue\u0026#39;; import Vuex from \u0026#39;vuex\u0026#39; //使用 Vue.use(Vuex); //用于响应组件的动作，处理业务逻辑，比如请求接口 const actions = { } //用于操作数据 const mutations = { } //用于存储数据 const state = { } //创建并导出store export default new Vuex.Store({ actions, mutations, state }); 4、main.js中引入store，并声明为Vue实例配置项\nimport store from \u0026#39;./store\u0026#39;; new Vue({ render: h =\u0026gt; h(App), store }).$mount(\u0026#34;#app\u0026#34;) 基本使用 # store/index.js定义多组件共享数据，并且定义操作数据的方法\nimport Vue from \u0026#39;vue\u0026#39;; import Vuex from \u0026#39;vuex\u0026#39; Vue.use(Vuex); const actions = { add(context,val){ //操作业务逻辑 val += 1; //调用mutations中的add context.commit(\u0026#34;add\u0026#34;,val); } } const mutations = { add(state,val){ //对数据进行操作 state.num += val; } } const state = { num: 0 } export default new Vuex.Store({ actions, mutations, state }); App.vue展示数据，引入两个操作数据的组件\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!-- 获取state中的num值 --\u0026gt; {{$store.state.num}} \u0026lt;Comp1/\u0026gt; \u0026lt;Comp2/\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import Comp1 from \u0026#39;./components/Comp1.vue\u0026#39;; import Comp2 from \u0026#39;./components/Comp2.vue\u0026#39;; export default { name: \u0026#34;App\u0026#34;, components: { Comp1, Comp2 } } \u0026lt;/script\u0026gt; Comp1.vue调用$store.dispatch，对数据进行业务逻辑操作（actions），然后再更改数据（mutations）\n\u0026lt;template\u0026gt; \u0026lt;button @click=\u0026#34;add2\u0026#34;\u0026gt;add2\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;Comp1\u0026#34;, methods: { add2(){ //dispatch调用的是action中add，后面的参数为val值 this.$store.dispatch(\u0026#34;add\u0026#34;, 1); } } } \u0026lt;/script\u0026gt; Comp2.vue调用$store.commit直接对数据进行操作（mutations），不需要业务逻辑操作\n\u0026lt;template\u0026gt; \u0026lt;button @click=\u0026#34;add1\u0026#34;\u0026gt;add1\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;Comp2\u0026#34;, methods: { add1(){ //commit直接调用mutations中的add函数，跳过action this.$store.commit(\u0026#34;add\u0026#34;,1); } } } \u0026lt;/script\u0026gt; getters配置项 # 类似于组件中声明的计算属性computed，用于计算state中的属性\nimport Vue from \u0026#39;vue\u0026#39;; import Vuex from \u0026#39;vuex\u0026#39; Vue.use(Vuex); const actions = {} const mutations = {} const state = { num: 9 } const getters = { tenfold(state){ return state.num * 10; } } export default new Vuex.Store({ actions, mutations, state }); \u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!-- 获取 --\u0026gt; {{$store.getters.tenfold}} \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;App\u0026#34; } \u0026lt;/script\u0026gt; 生成组件配置 # 在组件里面，我们一直使用this.$store.state.属性来获取属性或this.$store.getters.函数名来获取计算属性结果，过于麻烦，所以vuex提供了一种map...来帮助快速生成计算属性\nmapState # 用于将state中的属性，在组件中声明为计算属性computed的配置，简化编码\nimport Vue from \u0026#39;vue\u0026#39;; import Vuex from \u0026#39;vuex\u0026#39; Vue.use(Vuex); const actions = {} const mutations = {} // 声明两个属性 const state = { id: \u0026#34;123\u0026#34;, name: \u0026#34;lucy\u0026#34; } const getters = {} export default new Vuex.Store({ actions, mutations, state, getters }); \u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!-- 直接使用计算属性即可 --\u0026gt; \u0026lt;!-- id : {{userId}}, name : {{userName}} --\u0026gt; id : {{id}}, name : {{name}} \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; //引入mapState import {mapState} from \u0026#39;vuex\u0026#39;; export default { name: \u0026#34;App\u0026#34;, computed: { // 1、对象写法，用于需要给state中的属性重新起名 // ...mapState({userId:\u0026#39;id\u0026#39;,userName:\u0026#39;name\u0026#39;}) // 对象写法的属性值也可以是一个函数，例如，引入state中conf中的timer // （非常有用）例如需要在方法中获取该对象的m // ...mapState({\u0026#39;timer\u0026#39;:(state)=\u0026gt;state.conf.timer}), // 2、数组写法，属性名就是state中的属性名 ...mapState([\u0026#39;id\u0026#39;,\u0026#39;name\u0026#39;]) } } \u0026lt;/script\u0026gt; mapGetters # 用于将getters中的属性，在组件中声明为计算属性computed的配置，简化编码\nimport Vue from \u0026#39;vue\u0026#39;; import Vuex from \u0026#39;vuex\u0026#39; Vue.use(Vuex); const actions = {} const mutations = {} const state = { num: 9 } const getters = { tenfold(state){ return state.num * 10; } } export default new Vuex.Store({ actions, mutations, state, getters }); \u0026lt;template\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!-- 直接使用计算属性即可 --\u0026gt; \u0026lt;!-- {{multi10}} --\u0026gt; {{tenfold}} \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; //引入mapState import {mapGetters} from \u0026#39;vuex\u0026#39;; export default { name: \u0026#34;App\u0026#34;, computed: { // 1、对象写法，用于需要给getters中的属性重新起名 // ...mapGetters({multi10:\u0026#39;tenfold\u0026#39;}) // 2、数组写法，计算属性名就是getters中的属性名 ...mapGetters([\u0026#39;tenfold\u0026#39;]) } } \u0026lt;/script\u0026gt; mapActions # 替代调用this.$store.dispatch()，生成对应actions中的方法，作为组件methods配置\nimport Vue from \u0026#39;vue\u0026#39;; import Vuex from \u0026#39;vuex\u0026#39; Vue.use(Vuex); const actions = { add(context,val){ val += 1; context.commit(\u0026#34;add\u0026#34;,val); } } const mutations = { add(state,val){ state.num += val; } } const state = { num: 0 } const getters = {} export default new Vuex.Store({ actions, mutations, state, getters }); \u0026lt;template\u0026gt; \u0026lt;div\u0026gt; {{$store.state.num}} \u0026lt;!-- 此处的add是mapActions构造的，这个函数默认有一个参数 --\u0026gt; \u0026lt;!-- 如果就是add的val参数，如果不传，默认是event对象 --\u0026gt; \u0026lt;button @click=\u0026#34;add(1)\u0026#34;\u0026gt;add\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; //引入mapState import {mapActions} from \u0026#39;vuex\u0026#39;; export default { name: \u0026#34;App\u0026#34;, methods: { //和mapState一样，同样支持对象或数组的写法 ...mapActions([\u0026#39;add\u0026#39;]) }, } \u0026lt;/script\u0026gt; mapMutations # 替代调用this.$store.commit()，生成对应mutations中的方法，作为组件methods配置\nimport Vue from \u0026#39;vue\u0026#39;; import Vuex from \u0026#39;vuex\u0026#39; Vue.use(Vuex); const actions = {} const mutations = { add(state,val){ state.num += val; } } const state = { num: 0 } const getters = {} export default new Vuex.Store({ actions, mutations, state, getters }); \u0026lt;template\u0026gt; \u0026lt;div\u0026gt; {{$store.state.num}} \u0026lt;!-- 此处的add是mapMutations构造的，这个函数默认有一个参数 --\u0026gt; \u0026lt;!-- 如果就是add的val参数，如果不传，默认是event对象 --\u0026gt; \u0026lt;button @click=\u0026#34;add(1)\u0026#34;\u0026gt;add\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; //引入mapState import {mapMutations} from \u0026#39;vuex\u0026#39;; export default { name: \u0026#34;App\u0026#34;, methods: { //和mapState一样，同样支持对象或数组的写法 ...mapMutations([\u0026#39;add\u0026#39;]) }, } \u0026lt;/script\u0026gt; vuex的模块化开发 # 在实际开发中，如果大型项目，那么可能在actions、mutations、state、getters中，多种业务的数据、逻辑都写在同一个对象中，增大了耦合，而且不容易维护。所以，vuex提供了模块化的开发方法，可以将每个业务的actions、mutations、state、getters单独的写在一个对象（文件导入）中，互不影响，组件在使用的时候，可以按照需要对namespace使用map...进行引入\n例如，有支付、订单两个业务：\npayOptions.js用于写支付业务的actions、mutations、state、getters\nexport default{ namespaced: true, //开启namespaced，才可以按照名称进行引入 state: { money: 10000 }, actions: { add(context,val){ context.commit(\u0026#39;add\u0026#39;,val); } }, mutations: { add(state,val){ state.money += val } }, getters: { } } orderOptions.js用于写订单业务的actions、mutations、state、getters\nexport default { namespaced: true, state: { totalOrder: 1314 }, actions: { sub(context,val){ context.commit(\u0026#39;sub\u0026#39;,val); } }, mutations: { sub(state,val){ state.totalOrder -= val; } }, getters: { } } index.js，引入每个模块的文件，并配置到store\nimport Vue from \u0026#39;vue\u0026#39;; import Vuex from \u0026#39;vuex\u0026#39; Vue.use(Vuex); // 引入业务模块 import payOptions from \u0026#39;./payOptions\u0026#39;; import orderOptions from \u0026#39;./orderOptions\u0026#39;; export default new Vuex.Store({ //配置业务模块到store modules: { payOptions, orderOptions } }); App.vue，分别引入每个模块的state、actions进行测试\n\u0026lt;template\u0026gt; \u0026lt;div\u0026gt; 剩余资金：{{money}}，剩余订单：{{totalOrder}}\u0026lt;br/\u0026gt; \u0026lt;button @click=\u0026#34;add(100)\u0026#34;\u0026gt;addMoney\u0026lt;/button\u0026gt; \u0026lt;button @click=\u0026#34;sub(10)\u0026#34;\u0026gt;subOrder\u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; //引入mapState import {mapState,mapActions} from \u0026#39;vuex\u0026#39;; export default { name: \u0026#34;App\u0026#34;, methods: { ...mapActions(\u0026#34;payOptions\u0026#34;,[\u0026#39;add\u0026#39;]), ...mapActions(\u0026#34;orderOptions\u0026#34;,[\u0026#39;sub\u0026#39;]) }, computed: { //此时的第一个参数就是声明使用哪个模块 ...mapState(\u0026#34;payOptions\u0026#34;,[\u0026#39;money\u0026#39;]), ...mapState(\u0026#34;orderOptions\u0026#34;,[\u0026#39;totalOrder\u0026#39;]) } } \u0026lt;/script\u0026gt; ","date":"2025-03-03","externalUrl":null,"permalink":"/posts/bafd68f1/b2321cb9/9ee02560/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003evuex是什么 \n    \u003cdiv id=\"vuex是什么\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#vuex%e6%98%af%e4%bb%80%e4%b9%88\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e概念：专门在vue中实现\u003cstrong\u003e集中式\u003c/strong\u003e状态管理的一个Vue插件，对Vue应用中多个组件的共享状态进行集中式的管理（读、写）也是一种组件间通信的方式，且适合于任意组件间通信。\u003c/p\u003e","title":"9、vuex","type":"posts"},{"content":" 全局异常处理 # 参数校验失败或业务操作抛出的异常，当然不可能再去手动捕捉异常进行处理，不然还不如用之前BindingResult方式呢。又不想手动捕捉这个异常，又要对这个异常进行处理，那正好使用SpringBoot全局异常处理来达到一劳永逸的效果！\n首先，我们需要新建一个类，在这个类上加上@ControllerAdvice或@RestControllerAdvice注解，这个类就配置成全局处理类了。这个根据你的Controller层用的是@Controller还是@RestController来决定。\n@RestControllerAdvice也可以作用于@Controller。\n@RestControllerAdvice public class ControllerExceptionHandler { /** * 其他异常处理 * @param e * @return */ @ExceptionHandler(Exception.class) public ResultVO otherExceptionHandler(Exception e){ return ResultVO.failed(\u0026#34;操作失败，服务器异常\u0026#34;); } } spring-boot-starter-validation # spring-boot-starter-validation 是 Spring Boot 中的一个启动器依赖，用于在项目中引入数据校验的功能。它基于 javax.validation（现为 jakarta.validation）和 Hibernate Validator 实现，为对象属性提供了丰富的校验注解，同时支持自定义校验逻辑。\n\u0026lt;!-- 参数校验 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-validation\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 基本概念 # Java API规范即JSR-303是JavaEE 6中的一项子规范，又称作 Bean Validation，提供了针对 Java Bean字段的一些校验注解，如@NotNull，@Min等。JSR-349是其升级版本，添加了一些新特性。\nJSR定义了Bean校验的标准validation-api，但没有提供实现\nHibernate Validator是对这个规范的实现（与ORM框架无关），并在它的基础上增加了一些新的校验注解如@Email、@Length等。\nSpring全面支持了 JSR-303、JSR-349规范，对 Hibernate Validation 进行二次封装，在 SpringMVC 模块中添加了自动校验机制，可以利用注解对 Java Bean 的字段的值进行校验，并将校验信息封装进特定的类中。\n依赖作用 # spring-boot-starter-validation 的主要作用是提供一套基于注解的校验框架，用于验证数据是否合法。该依赖通常用于 Spring Boot 应用中的以下场景：\n前端数据校验：自动校验传入的请求数据是否符合规定的格式和要求。\n服务端逻辑校验：确保服务内部的数据符合业务逻辑，以防止数据不一致或异常情况。\n数据层保护：通过校验确保入库的数据是符合规范的，有助于保持数据的完整性和一致性。\n常用注解 # @NotNull # 作用：确保字段不为 null。 适用类型：所有类型（不能为基础数据类型如 int，因为它们不能为 null）。 @NotNull(message = \u0026#34;用户名不能为空\u0026#34;) private String username; @Null # 作用：确保字段为 null。 适用类型：所有类型。 @NotEmpty # 作用：确保集合、字符串、数组等不为空（不能为空且大小/长度不能为0）。 适用类型：字符串、集合、数组等。 @NotBlank # 作用：确保字符串不为空白（即不能为空，且至少包含一个非空白字符）。 适用类型：字符串。 @Size # 作用：限制集合、数组或字符串的大小或长度在指定范围内。 属性： min：最小长度（默认为0）。 max：最大长度。 适用类型：字符串、集合、数组等。 @Size(min = 1, max = 10, message = \u0026#34;用户名长度必须在1到10之间\u0026#34;) private String username; @Min和@Max # 作用：限制数值类型的字段值的最小值和最大值。 属性： value：允许的最小/最大值。 适用类型：数字类型（如 int、long、double 等）。 @Positive和@PositiveOrZero # 作用： @Positive：确保字段值为正数。 @PositiveOrZero：确保字段值为非负数（即正数或零）。 适用类型：数字类型。 @Negative和@NegativeOrZero # 作用： @Negative：确保字段值为负数。 @NegativeOrZero：确保字段值为非正数（即负数或零）。 适用类型：数字类型。 @Past和@PastOrPresent # 作用： @Past：确保日期在当前日期之前。 @PastOrPresent：确保日期在当前日期或之前。 适用类型：java.util.Date、java.time.LocalDate 等日期类型。 @Past(message = \u0026#34;生日必须是过去的日期\u0026#34;) private LocalDate birthDate; @Future和@FutureOrPresent # 作用： @Future：确保日期在当前日期之后。 @FutureOrPresent：确保日期在当前日期或之后。 适用类型：java.util.Date、java.time.LocalDate 等日期类型。 @Pattern # 作用：确保字符串符合指定的正则表达式。 属性： regexp：指定的正则表达式。 flags：正则表达式的匹配标志（如大小写敏感性）。 适用类型：字符串。 @Pattern(regexp = \u0026#34;^[a-zA-Z0-9]{6,12}$\u0026#34;, message = \u0026#34;密码必须是6到12位的字母和数字组合\u0026#34;) private String password; @Email # 作用：确保字符串符合电子邮件格式。 属性： regexp：正则表达式，默认符合标准的邮箱格式。 flags：正则表达式的匹配标志。 适用类型：字符串。 @Digits # 作用：限制数值字段的整数位和小数位的最大位数。 属性： integer：最大整数位数。 fraction：最大小数位数。 适用类型：数字类型。 @Digits(integer = 5, fraction = 2, message = \u0026#34;金额整数部分不能超过5位，小数部分不能超过2位\u0026#34;) private BigDecimal amount; @DecimalMin和@DecimalMax # 作用：限制字段数值的最小值和最大值（包含边界）。 属性： value：允许的最小或最大值。 inclusive：是否包含边界值，默认为 true。 适用类型：数字类型。 @DecimalMin(value = \u0026#34;0.0\u0026#34;, inclusive = false, message = \u0026#34;金额必须大于0\u0026#34;) private BigDecimal price; @AssertTrue和@AssertFalse # 作用： @AssertTrue：确保字段值为 true。 @AssertFalse：确保字段值为 false。 适用类型：布尔类型。 @AssertTrue(message = \u0026#34;必须同意条款\u0026#34;) private Boolean termsAccepted; 常见用法 # 在 DTO 中使用校验注解 # import javax.validation.constraints.*; public class UserDto { @NotBlank(message = \u0026#34;用户名不能为空\u0026#34;) private String username; @Email(message = \u0026#34;邮箱格式不正确\u0026#34;) private String email; @Size(min = 8, max = 20, message = \u0026#34;密码长度必须在8到20个字符之间\u0026#34;) private String password; @Min(value = 18, message = \u0026#34;年龄不能小于18\u0026#34;) private int age; } 在控制器中应用校验 # 在控制器中使用 @Validated或@Valid 注解，使 Spring 自动触发校验规则，如果校验失败会抛出异常。\nimport org.springframework.web.bind.annotation.*; import javax.validation.Valid; @RestController @RequestMapping(\u0026#34;/api/users\u0026#34;) public class UserController { @PostMapping public ResponseEntity\u0026lt;String\u0026gt; createObject(@Validated @RequestBody ObjectDto obj) { // 处理业务逻辑 return ResponseEntity.ok(\u0026#34;实体创建成功\u0026#34;); } @PostMapping public ResponseEntity\u0026lt;String\u0026gt; createUser(@Valid @RequestBody UserDto user) { // 处理业务逻辑 return ResponseEntity.ok(\u0026#34;用户创建成功\u0026#34;); } } 参数较少时，也可直接在参数前使用校验注解。\n// 路径变量 @GetMapping(\u0026#34;{userId}\u0026#34;) public String detail(@PathVariable(\u0026#34;userId\u0026#34;) @Min(10000000000000000L) Long userId) { // 校验通过，才会执行业务逻辑处理 UserDTO userDTO = new UserDTO(); userDTO.setUserId(userId); userDTO.setAccount(\u0026#34;11111111111111111\u0026#34;); userDTO.setUserName(\u0026#34;xixi\u0026#34;); userDTO.setAccount(\u0026#34;11111111111111111\u0026#34;); return \u0026#34;ok\u0026#34;; } // 查询参数 @GetMapping(\u0026#34;detail\u0026#34;) public String getDetail(@Length(min = 6, max = 20) @NotNull String account) { // 校验通过，才会执行业务逻辑处理 return \u0026#34;ok\u0026#34;; } 自定义校验 # 1、定义注解 # import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; @Documented // 此处是自定义校验器类 @Constraint(validatedBy = PasswordValidator.class) @Target({ ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface ValidPassword { //默认错误消息 String message() default \u0026#34;密码不符合复杂度要求\u0026#34;; //分组 Class\u0026lt;?\u0026gt;[] groups() default {}; //负载 Class\u0026lt;? extends Payload\u0026gt;[] payload() default {}; } 2、自定义校验器 # import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class PasswordValidator implements ConstraintValidator\u0026lt;ValidPassword, String\u0026gt; { @Override public void initialize(ValidPassword constraintAnnotation) { } @Override public boolean isValid(String password, ConstraintValidatorContext context) { return password != null \u0026amp;\u0026amp; password.matches(\u0026#34;^(?=.*[A-Za-z])(?=.*\\\\d)[A-Za-z\\\\d]{8,}$\u0026#34;); } } 3、使用 # public class UserDto { @ValidPassword private String password; } 全局校验异常处理 # 当校验失败时，Spring 会抛出如下两种异常\nMethodArgumentNotValidException ：是在 Spring MVC 或 Spring Boot 中处理参数校验异常时抛出的异常。\nConstraintViolationException ：是在 Java Bean Validation（JSR 380）规范中定义的一个异常类。\n可以通过全局异常处理来捕获这些异常并返回自定义的错误响应：\nimport org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import javax.validation.ConstraintViolationException; @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public Map\u0026lt;String, String\u0026gt; handleValidationExceptions(MethodArgumentNotValidException ex) { Map\u0026lt;String, String\u0026gt; errors = new HashMap\u0026lt;\u0026gt;(); ex.getBindingResult().getFieldErrors().forEach(error -\u0026gt; errors.put(error.getField(), error.getDefaultMessage())); return errors; } @ExceptionHandler(ConstraintViolationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public String handleConstraintViolationException(ConstraintViolationException ex) { return ex.getMessage(); } } ","date":"2025-02-12","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/4f5b7444/07d12f6d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e全局异常处理 \n    \u003cdiv id=\"全局异常处理\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%85%a8%e5%b1%80%e5%bc%82%e5%b8%b8%e5%a4%84%e7%90%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e参数校验失败或业务操作抛出的异常，当然不可能再去手动捕捉异常进行处理，不然还不如用之前BindingResult方式呢。\u003cstrong\u003e又不想手动捕捉这个异常，又要对这个异常进行处理\u003c/strong\u003e，那正好使用SpringBoot全局异常处理来达到一劳永逸的效果！\u003c/p\u003e","title":"9、全局异常处理","type":"posts"},{"content":" 类Class # using System; namespace BoxApplication { class Box { public double length; // 长度 public double breadth; // 宽度 public double height; // 高度 } class Boxtester { static void Main(string[] args) { Box Box1 = new Box(); // 声明 Box1，类型为 Box Box Box2 = new Box(); // 声明 Box2，类型为 Box double volume = 0.0; // 体积 // Box1 详述 Box1.height = 5.0; Box1.length = 6.0; Box1.breadth = 7.0; // Box2 详述 Box2.height = 10.0; Box2.length = 12.0; Box2.breadth = 13.0; // Box1 的体积 volume = Box1.height * Box1.length * Box1.breadth; Console.WriteLine(\u0026#34;Box1 的体积： {0}\u0026#34;, volume); // Box2 的体积 volume = Box2.height * Box2.length * Box2.breadth; Console.WriteLine(\u0026#34;Box2 的体积： {0}\u0026#34;, volume); Console.ReadKey(); } } } ToString # C#中ToString方法返回一个对象实例的字符串，在默认情况下将返回类类型的限定名\nC#中几乎所有的类型都派生自Object，所以如果当前类型没有重写ToString()方法的情况下，调用ToString()方法，默认返回当前类型的名称\nusing System; namespace HelloWorldApplication { class Dog { private string name; public Dog(string name){ this.name = name; } public override string ToString() { return \u0026#34;my name is \u0026#34; + this.name; } } class Program { static void Main(string[] args) { Dog dog = new Dog(\u0026#34;wangcai\u0026#34;); Console.WriteLine(dog); // my name is wangcai } } } 构造函数 # 默认的构造函数没有任何参数。但是如果你需要一个带有参数的构造函数可以有参数，这种构造函数叫做参数化构造函数。\nusing System; namespace LineApplication { class Line { // 线条的长度 private double length; // 参数化构造函数 public Line(double len) { Console.WriteLine(\u0026#34;对象已创建，length = {0}\u0026#34;, len); length = len; } public void setLength( double len ) { length = len; } public double getLength() { return length; } static void Main(string[] args) { Line line = new Line(10.0); Console.WriteLine(\u0026#34;线条的长度： {0}\u0026#34;, line.getLength()); // 设置线条长度 line.setLength(6.0); Console.WriteLine(\u0026#34;线条的长度： {0}\u0026#34;, line.getLength()); Console.ReadKey(); } } } 析构函数 # 类的 析构函数 是类的一个特殊的成员函数，当类的对象超出范围时执行。\n析构函数的名称是在类的名称前加上一个波浪形（~）作为前缀，它不返回值，也不带任何参数。\n析构函数用于在结束程序（比如关闭文件、释放内存等）之前释放资源。析构函数不能继承或重载。\nusing System; namespace LineApplication { class Line { private double length; // 线条的长度 public Line() // 构造函数 { Console.WriteLine(\u0026#34;对象已创建\u0026#34;); } ~Line() //析构函数 { Console.WriteLine(\u0026#34;对象已删除\u0026#34;); } public void setLength( double len ) { length = len; } public double getLength() { return length; } static void Main(string[] args) { Line line = new Line(); // 设置线条长度 line.setLength(6.0); Console.WriteLine(\u0026#34;线条的长度： {0}\u0026#34;, line.getLength()); } } } 静态成员 # 我们可以使用 static 关键字把类成员定义为静态的。当我们声明一个类成员为静态时，意味着无论有多少个类的对象被创建，只会有一个该静态成员的副本。\nusing System; namespace StaticVarApplication { class StaticVar { public static int num; public void count() { num++; } public int getNum() { return num; } } class StaticTester { static void Main(string[] args) { StaticVar s1 = new StaticVar(); StaticVar s2 = new StaticVar(); s1.count(); s1.count(); s1.count(); s2.count(); s2.count(); s2.count(); Console.WriteLine(\u0026#34;s1 的变量 num： {0}\u0026#34;, s1.getNum()); Console.WriteLine(\u0026#34;s2 的变量 num： {0}\u0026#34;, s2.getNum()); Console.ReadKey(); } } } 继承 # 一个类可以继承自另一个类，被称为基类（父类）和派生类（子类）。\n一个类可以继承多个接口，但只能继承自一个类。\n派生类会继承基类的成员（字段、方法、属性等），除非它们被明确地标记为私有（private）。\nclass BaseClass { public void SomeMethod() { // Method implementation } } class DerivedClass : BaseClass { public void AnotherMethod() { // Accessing base class method base.SomeMethod(); // Method implementation } } 基类的初始化 # 派生类继承了基类的成员变量和成员方法。因此父类对象应在子类对象创建之前被创建。可以在成员初始化列表中进行父类的初始化。\nusing System; namespace RectangleApplication { class Rectangle { // 成员变量 protected double length; protected double width; public Rectangle(double l, double w) { length = l; width = w; } public double GetArea() { return length * width; } public void Display() { Console.WriteLine(\u0026#34;长度： {0}\u0026#34;, length); Console.WriteLine(\u0026#34;宽度： {0}\u0026#34;, width); Console.WriteLine(\u0026#34;面积： {0}\u0026#34;, GetArea()); } }//end class Rectangle class Tabletop : Rectangle { private double cost; public Tabletop(double l, double w) : base(l, w) { } public double GetCost() { double cost; cost = GetArea() * 70; return cost; } public void Display() { base.Display(); Console.WriteLine(\u0026#34;成本： {0}\u0026#34;, GetCost()); } } class ExecuteRectangle { static void Main(string[] args) { Tabletop t = new Tabletop(4.5, 7.5); t.Display(); Console.ReadLine(); } } } 多态 # 和Java基本一致\n函数重载 # using System; namespace PolymorphismApplication { public class TestData { public int Add(int a, int b, int c) { return a + b + c; } public int Add(int a, int b) { return a + b; } } class Program { static void Main(string[] args) { TestData dataClass = new TestData(); int add1 = dataClass.Add(1, 2); int add2 = dataClass.Add(1, 2, 3); Console.WriteLine(\u0026#34;add1 :\u0026#34; + add1); Console.WriteLine(\u0026#34;add2 :\u0026#34; + add2); } } } 抽象类 # C# 允许您使用关键字 abstract 创建抽象类，用于提供接口的部分类的实现。当一个派生类继承自该抽象类时，实现即完成。抽象类包含抽象方法，抽象方法可被派生类实现。\n您不能创建一个抽象类的实例。 您不能在一个抽象类外部声明一个抽象方法。 通过在类定义前面放置关键字 sealed，可以将类声明为密封类。当一个类被声明为 sealed 时，它不能被继承。抽象类不能被声明为 sealed。 using System; namespace PolymorphismApplication { abstract class Shape { abstract public int area(); } class Rectangle: Shape { private int length; private int width; public Rectangle( int a=0, int b=0) { length = a; width = b; } public override int area () { Console.WriteLine(\u0026#34;Rectangle 类的面积：\u0026#34;); return (width * length); } } class RectangleTester { static void Main(string[] args) { Rectangle r = new Rectangle(10, 7); double a = r.area(); Console.WriteLine(\u0026#34;面积： {0}\u0026#34;,a); Console.ReadKey(); } } } 运算符重载 # 您可以重定义或重载 C# 中内置的运算符。因此，程序员也可以使用用户自定义类型的运算符。重载运算符是具有特殊名称的函数，是通过关键字operator后跟运算符的符号来定义的。与其他函数一样，重载运算符有返回类型和参数列表。\n运算符 描述 +, -, !, ~, ++, \u0026ndash; 这些一元运算符只有一个操作数，且可以被重载。 +, -, *, /, % 这些二元运算符带有两个操作数，且可以被重载。 ==, !=, \u0026lt;, \u0026gt;, \u0026lt;=, \u0026gt;= 这些比较运算符可以被重载。 \u0026amp;\u0026amp;, || 这些条件逻辑运算符不能被直接重载。 +=, -=, *=, /=, %= 这些赋值运算符不能被重载。 =, ., ?:, -\u0026gt;, new, is, sizeof, typeof 这些运算符不能被重载。 using System; namespace HelloWorldApplication { class Box { public int height; public int width; public Box(int h,int w){ this.height = h; this.width = w; } public static Box operator+(Box b1,Box b2) { Box box = new Box(b1.height + b2.height,b1.width + b2.width); return box; } public override string ToString() { return this.height + \u0026#34; | \u0026#34; + this.width; } } class Program { static void Main(string[] args) { Box b1 = new Box(1,2); Box b2 = new Box(3,4); Box box = b1 + b2; Console.WriteLine(box); } } } ","date":"2024-09-12","externalUrl":null,"permalink":"/posts/5ab2821f/4d470072/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e类Class \n    \u003cdiv id=\"类class\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%b1%bbclass\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-cs\" data-lang=\"cs\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003enamespace\u003c/span\u003e \u003cspan class=\"nn\"\u003eBoxApplication\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eBox\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kt\"\u003edouble\u003c/span\u003e \u003cspan class=\"n\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e   \u003cspan class=\"c1\"\u003e// 长度\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kt\"\u003edouble\u003c/span\u003e \u003cspan class=\"n\"\u003ebreadth\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// 宽度\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kt\"\u003edouble\u003c/span\u003e \u003cspan class=\"n\"\u003eheight\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e   \u003cspan class=\"c1\"\u003e// 高度\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eBoxtester\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"k\"\u003evoid\u003c/span\u003e \u003cspan class=\"n\"\u003eMain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e[]\u003c/span\u003e \u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eBox\u003c/span\u003e \u003cspan class=\"n\"\u003eBox1\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eBox\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e        \u003cspan class=\"c1\"\u003e// 声明 Box1，类型为 Box\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eBox\u003c/span\u003e \u003cspan class=\"n\"\u003eBox2\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eBox\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e        \u003cspan class=\"c1\"\u003e// 声明 Box2，类型为 Box\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kt\"\u003edouble\u003c/span\u003e \u003cspan class=\"n\"\u003evolume\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e0.0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e         \u003cspan class=\"c1\"\u003e// 体积\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"c1\"\u003e// Box1 详述\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eBox1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eheight\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e5.0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eBox1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elength\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e6.0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eBox1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ebreadth\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e7.0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"c1\"\u003e// Box2 详述\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eBox2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eheight\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e10.0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eBox2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elength\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e12.0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eBox2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ebreadth\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e13.0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e           \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"c1\"\u003e// Box1 的体积\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003evolume\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eBox1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eheight\u003c/span\u003e \u003cspan class=\"p\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eBox1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elength\u003c/span\u003e \u003cspan class=\"p\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eBox1\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ebreadth\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Box1 的体积： {0}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e  \u003cspan class=\"n\"\u003evolume\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"c1\"\u003e// Box2 的体积\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003evolume\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eBox2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eheight\u003c/span\u003e \u003cspan class=\"p\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eBox2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elength\u003c/span\u003e \u003cspan class=\"p\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003eBox2\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ebreadth\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Box2 的体积： {0}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003evolume\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eReadKey\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003eToString \n    \u003cdiv id=\"tostring\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#tostring\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003eC#中ToString方法返回一个对象实例的字符串，在默认情况下将返回类类型的限定名\u003c/p\u003e","title":"9、类","type":"posts"},{"content":" 什么是事务 # 事务是作为单个逻辑单元执行的一系列操作\n多个操作作为一个整体向系统提交，要么执行、要么都不执行，事务是一个不可分割的工作工作逻辑单元\n四大特性（ACID） # 原子性（A \u0026ndash; Atomicity）：原子是参与化学反应中最小的粒子，不能再分割了，也就是说事务就是最小的，不能再分割了，如果把事务都分割了，就会出现问题\n一致性（C \u0026ndash; Consistency）：在事务执行前数据库的数据处于正确的状态，而事务执行完成后数据库的数据还是处于正确的状态，即数据完整性约束没有被破坏\n隔离性（I \u0026ndash; Isolation）：事务与事务之间互不干扰\n持久性（D \u0026ndash; Durability）：通过事务进行操作后的数据，是永久保存的\n注意：在实际开发中，经常会打破隔离性，当多个事务共同操作同一张表的时候，一旦打破了隔离性，就会出现安全问题\n存储引擎 # 存储引擎：RDBMS中，决定了数据如何存储，如何获取，如何控制事务，如何控制外键等一系列功能的一套程序\n常用引擎：InnoDB，MyIsam\nInnoDB与MyIsam的区别 # InnoDB支持事务，支持外键；而MyIsam不支持事务，不支持外键 InnoDB由于受到事务和外键的影响，所以对数据的存储以及查询效率偏低；MyIsam相反偏高 InnoDB在存储时，表文件是2个：frm，ibd；而MyIsam是3个文件，分别存储frm，MYD，MYI InnoDB是MYSQL 5.5之后的默认存储引擎；而MyIsam是5.5之前的默认存储引擎 事务操作 # 方式一 # 开启事务：start transaction;\n提交事务：commit;\n注意：当事务开启之后，只有执行了commit，数据才会真的改变，如果没有执行commit，数据还原\n回滚事务：rollback;\n方式二 # 修改默认的提交方式，默认是自动提交，我们要改成手动提交\nshow variables like '%autocommit%'\nset @@autocommit=0（默认为1，自动提交；0，手动提交）\n隔离级别 # 隔离级别 读数据一致性及允许的并发副作用 备注 读未提交（Read uncommitted） 最低级别，只能保证不读取物理上损坏的数据，事务可以看到其他事务没有被提交的数据（脏数据） 读已提交（Read commited） 语句级，事务可以看到其他事务已经提交的数据 Oracle数据库默认 可重复读（Repeatable read） 事务级，事务中两次查询的结果相同 MySql数据库默认 可串行读（序列化Serializable） 最高级别，事务级。顺序执行 隔离等级越高，数据库事务并发执行能力越差，能处理的操作越少。因此在实际项目开发中为了考虑并发性能一般使用读已提交隔离级别，他能避免丢失更新和脏读，尽管不可重复读和幻读不能避免，但可以在可能出现的场合使用悲观锁或乐观锁来解决这些问题。\n什么是幻读、不可重复度、脏读 # 1、脏读：事务A读取了事务B更新的数据，然后B回滚操作，那么A读取到的数据是脏数据，读取到未提交的数据。\n2、不可重复读：事务 A 多次读取同一数据，事务 B 在事务A多次读取的过程中，对数据作了更新并提交，导致事务A多次读取同一数据时，结果不一致。\n3、幻读：系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级，但是系统管理员B就在这个时候插入了一条具体分数的记录，当系统管理员A改结束后发现还有一条记录没有改过来，就好像发生了幻觉一样，这就叫幻读\n隔离级别与更新丢失的情况 # 第一类更新丢失 事务A撤销时，把已经提交的事务B的更新数据覆盖了 第二类更新丢失 事务A覆盖事务B已经提交的数据，造成事务B所做的操作丢失 隔离级别 脏读 不可重复读 幻读 第一类丢失更新 第二类丢失更新 Read uncommitted √ √ √ × √ Read commited × √ √ × √ Repeatable read × × √ × × Serializable × × × × × 设置隔离级别 # set session transaction isolation level 隔离级别 查看隔离级别(当前客户端) # select @@tx_isolation 锁 # 乐观锁 # 默认存在，什么都不做，就是乐观锁。总是乐观的认为，在维护这个数据的时候，没有其他人来维护。\ninsert update delete 在执行SQL的同时，才给数据加上了乐观锁。 可能会导致的问题 # age:12 A用户:update table set age=32 where id=1 B用户:update table set age=12 where id=1 A用户:select * from table where id=1 这个时候，A用户查看的数据为B修改过的，自己的修改已经被覆盖 悲观锁 # 总是担心，在自己修改数据的时候，有其他人，将这个数据修改，所以在修改数据之前，提前锁住数据\n默认没有开启，需要自己开启\n开启命令 # #在查询语句后加for update就可以开启（悲观排他锁） select * from book for update 优缺点 # 优点：准确性高，更加安全\n缺点：效率太低，一旦一个用户执行悲观锁，那么其他用户都无法查看这个表的数据，也无法进行修改，除非执行悲观锁的用户commit\n行锁 # 只会锁住一行数据\nselect * from book where id=1 for update 表锁 # 会锁住整个的一个表\nselect * from book for update 排他锁 # A用户在给表加排他锁以后，那么其他用户都无法对表进行操作、查看、加锁\n//表级排他锁 lock table book write //解锁 unlock table 共享锁 # 所有的用户都可以对表进行加锁，但是，所有的用户都无法对表进行操作，包括上锁的用户\n//表级共享锁 lock table book read //解锁 unlock table 死锁 # 两个线程自己各自持有自己数据的锁，但互相都想锁住被对方锁住的数据，就产生了死锁\n系统检索到死锁，会自动释放一个锁\n","date":"2024-01-19","externalUrl":null,"permalink":"/posts/a6ece75f/8b824d9b/5d290da9/743206eb/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e什么是事务 \n    \u003cdiv id=\"什么是事务\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e4%bb%80%e4%b9%88%e6%98%af%e4%ba%8b%e5%8a%a1\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e事务是作为单个逻辑单元执行的一系列操作\u003c/strong\u003e\u003c/p\u003e","title":"9、事务、锁","type":"posts"},{"content":" JVM执行加载结构 # 类加载器 # 用来加载.class字节码文件，加载为Class对象存储到JVM内存中\n类加载器的执行过程 # 加载：根据全类名查找和导入.class文件; 链接（与加载交叉进行） 校验：检查载入.class文件数据的正确性和代码逻辑 准备：给类的静态变量分配存储空间，设置默认值（只是赋数据类型的零值） 例如static Integer num = 111：此时num的值只是赋了零值0（初始化阶段才会赋值） 如果static final Integer num = 111：那么当前准备阶段就会直接赋值111 解析：虚拟机将常量池内的符号引用替换为直接引用的过程 初始化：对类的静态变量、静态代码块执行初始化工作 此阶段不是所有的类都会被初始化，只有在以下五种情况才会初始化（主动使用类才会初始化） 当遇到new、getstatic、putstatic、invokestatic这 4 条直接码指令时，比如 new一个类，读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。 当 jvm 执行 new 指令时会初始化类。即当程序创建一个类的实例对象 当 jvm 执行 getstatic 指令时会初始化类。即程序访问类的静态变量(不是静态常量，常量会被加载到运行时常量池) 当 jvm 执行 putstatic 指令时会初始化类。即程序给类的静态变量赋值 当 jvm 执行 invokestatic 指令时会初始化类。即程序调用类的静态方法 使用 java.lang.reflect 包的方法对类进行反射调用时如 Class.forname(\u0026quot;...\u0026quot;), newInstance() 等等。如果类没初始化，触发其初始化。 初始化一个类，如果其父类还未初始化，则先触发该父类的初始化。 当虚拟机启动时，用户需要定义一个要执行的主类 (包含 main 方法的那个类)，虚拟机会先初始化这个类。 当一个接口中定义了 JDK8 新加入的默认方法（被 default 关键字修饰的接口方法）时，如果有这个接口的实现类发生了初始化，那该接口要在其之前被初始化。 卸载：卸载需要满足要求如下 该类的所有的实例对象都已被 GC，也就是说堆不存在该类的实例对象 该类没有在其他任何地方被引用 该类的类加载器的实例已被 GC 综上，在 JVM 生命周期内，由 jvm 自带的类加载器加载的类是不会被卸载的；但由我们自定义的类加载器加载的类有可能被卸载 类加载器的分层和分类 # 启动类加载器（根类加载器、引导类加载器）（BootstrapClassLoader） 最顶层的加载类，由 C++实现，主要用来加载 JDK 内部的核心类库（ %JAVA_HOME%/lib目录下的rt.jar、resources.jar、charsets.jar等jar包和类）以及被-Xbootclasspath参数指定的路径下的所有类。 rt.jar是Java基础类库，包含Java doc里面看到的所有的类的类文件。也就是说，我们常用内置库 java.xxx.* 都在里面，比如java.util.*、java.io.*、java.nio.*、java.lang.*、java.sql.*、java.math.*。 扩展类加载器（ExtClassLoader） 主要负责加载%JRE_HOME%/lib/ext目录下的 jar 包和类以及被 java.ext.dirs 系统变量所指定的路径下的所有类。 系统类加载器（AppClassLoader） 面向用户的加载器，负责加载当前应用classpath下的所有 jar 包和类。 除了BootstrapClassLoader是 JVM 自身的一部分（无需加载）之外，其他所有的类加载器都是在 JVM 外部实现的，并且全都继承自ClassLoader抽象类。这样做的好处是用户可以自定义类加载器，以便让应用程序自己决定如何去获取所需的类。\n扩展类加载器和系统类加载器由于也是类，也是JDK中提供的类，所以是由引导类加载器进行加载\n每个 ClassLoader 可以通过getParent()获取其父 ClassLoader，如果获取到 ClassLoader 为null的话，那么该类是通过BootstrapClassLoader加载的。\n加载顺序（双亲委派） # 类加载器加载存在委托机制，此机制的目的是保证了 Java 程序的稳定运行，可以避免类的重复加载 模型要求除顶层的启动类加载器外，其余的类加载器必须有自己的父类加载器。 在查找类或资源之前，搜索类和资源的任务会委托给父类加载器。 具体流程如下：\n系统类加载器：任何类一开始都是由系统类加载器来加载，也就说最下层来加载的。但是由于类加载器具备委托机制。所以，它不会马上去自己加载，而是委托给上一层类加载器来加载，即扩展类加载器来加载。如果扩展类加载器加载到了就加载进内存，如果扩展类加载器没有加载到，就自己再去加载，如果它自己也没有加载到，就会报异常ClassNotFoundException 扩展类加载器：如果轮到扩展类加载器来加载的话，由于类加载器具备委托机制，它会让上一层类加载器来加载，即引导类加载器来加载，如果引导类加载器加载到了，就进内存，如果引导类加载器加载不到，再由自己来加载，如果自己也没有加载到，就返回到系统类加载器来加载 引导类加载器：如果轮到引导类加载器来加载的话，由于它没有上一层，所以自己来加载。如果加载到，就进内存；如果它自己没有加载到，就会返回到扩展类加载器来加载 自定义类加载器 # 为什么自定义类加载器 # 隔离加载类\n在某些框架内进行中间件与应用的模块隔离，把类加载到不同的环境。比如：阿里内某容器框架通过自定义类加载器确保应用中依赖的jar包不会影响到中间件运行时使用的jar包。再比如：Tomcat这类Web应用服务器，内部自定义了好几种类加载器，用于隔离同一个Web应用服务器上的不同应用程序。\n两个jar包内都存在相同类名且包名相同，如果没有隔离加载类，则会报错，如：两个版本的jar\n修改类加载方式\n类的加载模型并非强制，除Bootstrap外，其他的加载并非一定要引入，或者根据实际情况在某个时间点进行按需进行动态加载 扩展加载源\n比如从数据库、网络、甚至是电视机机顶盒进行加载 防止源码泄露\nJava代码容易被编译和篡改，可以进行编译加密。那么类加载也需要自定义，还原加密的字节码。\n通常Java系统想增加License(授权)，就可以通过自定义类加载器实现。\n实现自定义类加载器 # 自定义加载器的parent为AppClassLoader\nJava提供了抽象类java.lang.ClassLoader，所有用户自定义的类加载器都应该继承ClassLoader类。\n在自定义ClassLoader的子类时候，我们常见的会有两种做法\n方式一:重写loadClass()方法\n方式二:重写findClass()方法\n这两种方法本质上差不多，毕竟loadClass()也会调用findClass()，但是从逻辑上讲我们最好不要直接修改loadClass()的内部逻辑。\nloadClass()这个方法是实现双亲委派模型逻辑的地方，擅自修改这个方法会导致模型被破坏，容易造成问题。\nMyClassLoader # public class MyClassLoader extends ClassLoader{ private String classPath; public MyClassLoader(String classPath){ this.classPath = classPath; } @Override protected Class\u0026lt;?\u0026gt; findClass(String name) throws ClassNotFoundException { // 解析字节码文件路径 String s = name.replaceAll(\u0026#34;\\\\.\u0026#34;, Matcher.quoteReplacement(File.separator)); String filePath = classPath + s + \u0026#34;.class\u0026#34;; // 读取字节码文件 FileInputStream inputStream = null; ByteArrayOutputStream outputStream = null; try { inputStream = new FileInputStream(filePath); outputStream = new ByteArrayOutputStream(); int len; byte[] bytes = new byte[1024]; while ((len = inputStream.read(bytes)) != -1){ outputStream.write(bytes,0,len); } byte[] outBytes = outputStream.toByteArray(); // 使用defineClass根据字节码创建Class实例 return defineClass(name,outBytes,0,outBytes.length); } catch (Exception e) { e.printStackTrace(); return null; }finally { if (outputStream != null){ try { outputStream.flush(); outputStream.close(); }catch (Exception e){ e.printStackTrace(); } } if (inputStream != null){ try { inputStream.close(); }catch (Exception e){ e.printStackTrace(); } } } } } top.ygang.Student # package top.ygang; public class Student { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return \u0026#34;Student{\u0026#34; + \u0026#34;name=\u0026#39;\u0026#34; + name + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, age=\u0026#34; + age + \u0026#39;}\u0026#39;; } } 使用命令javac -encoding utf-8 Student.java编译，并且将Student.class拷贝到D:\\\\下\n注意：删除项目中的Student.java！不然由于双亲委派机制，自定义类加载器向上委托，AppClassLoader会在项目classPath中找到该类并执行进行加载\nmain # public static void main(String[] args) throws Exception{ MyClassLoader myClassLoader = new MyClassLoader(\u0026#34;D:\\\\\u0026#34;); Class\u0026lt;?\u0026gt; aClass = myClassLoader.loadClass(\u0026#34;top.ygang.Student\u0026#34;); System.out.println(aClass.getClassLoader()); // classloader.MyClassLoader@4dd8dc3 Constructor\u0026lt;?\u0026gt; constructor = aClass.getConstructor(String.class, int.class); Object lucy = constructor.newInstance(\u0026#34;lucy\u0026#34;, 12); System.out.println(lucy); // Student{name=\u0026#39;lucy\u0026#39;, age=12} } 相关API # 获取类加载器对象 # // 先获取类的字节码文件对象 Class clazz = Person.class; // 通过字节码文件对象获取类加载器对象 ClassLoader classLoader = clazz.getClassLoader(); // 获取类加载器的上一层类加载器 ClassLoader classLoader1 = classLoader.getParent(); 使用类加载器来读取配置文件 # 原始方法，通过Properties类API进行读取\n// 在src的路径下，有一个jdbc.properties的配置文件 Properties p = new Properties(); p.load(new FileInputStream(\u0026#34;src/jdbc.properties\u0026#34;)); String driver = p.getProperty(\u0026#34;driver\u0026#34;); System.out.println(driver); 这种方法的弊端是如果配置文件在jar包内，也就是classpath下，则无法读取\n使用类加载器，实际上就是AppClassLoader，由于这个类加载器加载的是classpath下的类，所以同样也就可以用来读取classpath下的文件\n方式一 # // 获取类的字节码文件对象 Class clazz = Demo2.class; // 获取类加载器的对象 ClassLoader classLoader = clazz.getClassLoader(); // 使用类加载器对象读取配置文件 // 注意：使用类加载器的方式读取配置文件，默认的根目录是相对于classpath目录 InputStream is = classLoader.getResourceAsStream(\u0026#34;jdbc.properties\u0026#34;); Properties p = new Properties(); p.load(is); String driver = p.getProperty(\u0026#34;driver\u0026#34;); System.out.println(driver); 方式二 # // 获取类的字节码文件对象 Class clazz = Demo1.class; // 获取类加载器对象 ClassLoader classLoader = clazz.getClassLoader(); // 使用类加载器对象读取配置文件 // 注意：使用类加载器的方式读取配置文件，默认的根目录是相对于classpath目录 URL url = classLoader.getResource(\u0026#34;jdbc.properties\u0026#34;); String path = url.getPath(); FileInputStream fis = new FileInputStream(path); Properties p = new Properties(); p.load(fis); String driver = p.getProperty(\u0026#34;driver\u0026#34;); System.out.println(driver); ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/fbeea4f4/63fa2247/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJVM执行加载结构 \n    \u003cdiv id=\"jvm执行加载结构\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jvm%e6%89%a7%e8%a1%8c%e5%8a%a0%e8%bd%bd%e7%bb%93%e6%9e%84\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20230519151749400\"\n    data-zoom-src=\"/posts/3ab7256e/80aacaea/fbeea4f4/63fa2247/image/image-20230519151749400.png\"\n    src=\"/posts/3ab7256e/80aacaea/fbeea4f4/63fa2247/image/image-20230519151749400.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"9、类加载器","type":"posts"},{"content":" 结构体基本概念 # 结构体属于用户自定义的数据类型，允许用户存储不同的数据类型\n结构体定义和使用 # 语法：struct 结构体名 { 结构体成员列表 }；\n通过结构体创建变量的方式有三种：\nstruct 结构体名 变量名 struct 结构体名 变量名 = { 成员1值 ， 成员2值\u0026hellip;} 定义结构体时顺便创建变量 创建结构体变量 # 创建结构体变量、结构体变量指针、结构体数组，struct可以进行省略\n方式一 # #include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; struct student { string name; int age; string idCard; }; int main() { struct student st1; st1.age = 18; st1.idCard = \u0026#34;abcdefg\u0026#34;; st1.name = \u0026#34;lucy\u0026#34;; cout \u0026lt;\u0026lt; \u0026#34;name=\u0026#34; \u0026lt;\u0026lt; st1.name \u0026lt;\u0026lt; \u0026#34;,age=\u0026#34; \u0026lt;\u0026lt; st1.age \u0026lt;\u0026lt; \u0026#34;,idCard=\u0026#34; \u0026lt;\u0026lt; st1.idCard \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 方式二 # #include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; struct student { string name; int age; string idCard; }; int main() { struct student st1 = { \u0026#34;lucy\u0026#34;,18,\u0026#34;abcdef\u0026#34; }; cout \u0026lt;\u0026lt; \u0026#34;name=\u0026#34; \u0026lt;\u0026lt; st1.name \u0026lt;\u0026lt; \u0026#34;,age=\u0026#34; \u0026lt;\u0026lt; st1.age \u0026lt;\u0026lt; \u0026#34;,idCard=\u0026#34; \u0026lt;\u0026lt; st1.idCard \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 方式三 # #include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; struct student { string name; int age; string idCard; }st1; int main() { st1 = { \u0026#34;lucy\u0026#34;,18,\u0026#34;abcdef\u0026#34; }; cout \u0026lt;\u0026lt; \u0026#34;name=\u0026#34; \u0026lt;\u0026lt; st1.name \u0026lt;\u0026lt; \u0026#34;,age=\u0026#34; \u0026lt;\u0026lt; st1.age \u0026lt;\u0026lt; \u0026#34;,idCard=\u0026#34; \u0026lt;\u0026lt; st1.idCard \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 结构体数组 # 作用：将自定义的结构体放入到数组中方便维护\n语法： struct 结构体名 数组名[元素个数] = { {} , {} , ... {} }\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; struct student { string name; int age; string idCard; }; int main() { //第一种定义方式 student stus[3]; stus[0] = { \u0026#34;lucy\u0026#34;,18,\u0026#34;absgdd\u0026#34; }; stus[1] = { \u0026#34;tom\u0026#34;,25,\u0026#34;sgsg\u0026#34; }; stus[2] = { \u0026#34;james\u0026#34;,69,\u0026#34;sdhfso\u0026#34; }; //第二种定义方式 student stus1[2] = { {\u0026#34;lily\u0026#34;,28,\u0026#34;sgsd\u0026#34;},{\u0026#34;kobe\u0026#34;,22,\u0026#34;sojf\u0026#34;} }; system(\u0026#34;pause\u0026#34;); return 0; } 结构体指针 # 作用：通过指针访问结构体中的成员\n利用操作符 -\u0026gt; 可以通过结构体指针访问结构体属性\n例子：\n#include\u0026lt;iostream\u0026gt; using namespace std; #include\u0026lt;string\u0026gt; //定义学生结构体 struct student { string name; int age; string idCard; }; int main() { //创建学生结构体变量 student stu = { \u0026#34;lucy\u0026#34;,18,\u0026#34;sofof\u0026#34; }; //通过指针指向结构体变量，其中这个struct可以省略 student * p = \u0026amp;stu; //通过操作符-\u0026gt;访问结构体变量中的数据 cout \u0026lt;\u0026lt; p-\u0026gt;age \u0026lt;\u0026lt; p-\u0026gt;name \u0026lt;\u0026lt; p-\u0026gt;idCard \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 嵌套结构体 # 作用： 结构体中的成员可以是另一个结构体\n例如：每个老师辅导一个学员，一个老师的结构体中，记录一个学生的结构体\n示例：\n//学生结构体定义 struct student { //成员列表 string name; //姓名 int age; //年龄 int score; //分数 }; //教师结构体定义 struct teacher { //成员列表 int id; //职工编号 string name; //教师姓名 int age; //教师年龄 struct student stu; //子结构体 学生 }; int main() { struct teacher t1; t1.id = 10000; t1.name = \u0026#34;老王\u0026#34;; t1.age = 40; t1.stu.name = \u0026#34;张三\u0026#34;; t1.stu.age = 18; t1.stu.score = 100; cout \u0026lt;\u0026lt; \u0026#34;教师 职工编号： \u0026#34; \u0026lt;\u0026lt; t1.id \u0026lt;\u0026lt; \u0026#34; 姓名： \u0026#34; \u0026lt;\u0026lt; t1.name \u0026lt;\u0026lt; \u0026#34; 年龄： \u0026#34; \u0026lt;\u0026lt; t1.age \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;辅导学员 姓名： \u0026#34; \u0026lt;\u0026lt; t1.stu.name \u0026lt;\u0026lt; \u0026#34; 年龄：\u0026#34; \u0026lt;\u0026lt; t1.stu.age \u0026lt;\u0026lt; \u0026#34; 考试分数： \u0026#34; \u0026lt;\u0026lt; t1.stu.score \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 总结：在结构体中可以定义另一个结构体作为成员，用来解决实际问题\n结构体做函数参数 # 作用：将结构体作为参数向函数中传递\n传递方式有两种：\n值传递 地址传递 示例：\n//学生结构体定义 struct student { //成员列表 string name; //姓名 int age; //年龄 int score; //分数 }; //值传递 void printStudent(student stu ) { stu.age = 28; cout \u0026lt;\u0026lt; \u0026#34;子函数中 姓名：\u0026#34; \u0026lt;\u0026lt; stu.name \u0026lt;\u0026lt; \u0026#34; 年龄： \u0026#34; \u0026lt;\u0026lt; stu.age \u0026lt;\u0026lt; \u0026#34; 分数：\u0026#34; \u0026lt;\u0026lt; stu.score \u0026lt;\u0026lt; endl; } //地址传递 void printStudent2(student * stu) { stu-\u0026gt;age = 28; cout \u0026lt;\u0026lt; \u0026#34;子函数中 姓名：\u0026#34; \u0026lt;\u0026lt; stu-\u0026gt;name \u0026lt;\u0026lt; \u0026#34; 年龄： \u0026#34; \u0026lt;\u0026lt; stu-\u0026gt;age \u0026lt;\u0026lt; \u0026#34; 分数：\u0026#34; \u0026lt;\u0026lt; stu-\u0026gt;score \u0026lt;\u0026lt; endl; } int main() { student stu = { \u0026#34;张三\u0026#34;,18,100}; //值传递 printStudent(stu); cout \u0026lt;\u0026lt; \u0026#34;主函数中 姓名：\u0026#34; \u0026lt;\u0026lt; stu.name \u0026lt;\u0026lt; \u0026#34; 年龄： \u0026#34; \u0026lt;\u0026lt; stu.age \u0026lt;\u0026lt; \u0026#34; 分数：\u0026#34; \u0026lt;\u0026lt; stu.score \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; endl; //地址传递 printStudent2(\u0026amp;stu); cout \u0026lt;\u0026lt; \u0026#34;主函数中 姓名：\u0026#34; \u0026lt;\u0026lt; stu.name \u0026lt;\u0026lt; \u0026#34; 年龄： \u0026#34; \u0026lt;\u0026lt; stu.age \u0026lt;\u0026lt; \u0026#34; 分数：\u0026#34; \u0026lt;\u0026lt; stu.score \u0026lt;\u0026lt; endl; system(\u0026#34;pause\u0026#34;); return 0; } 总结：如果不想修改主函数中的数据，用值传递，反之用地址传递\n","date":"2024-01-19","externalUrl":null,"permalink":"/posts/d8245fe1/09e70634/24ff7c0a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e结构体基本概念 \n    \u003cdiv id=\"结构体基本概念\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%bb%93%e6%9e%84%e4%bd%93%e5%9f%ba%e6%9c%ac%e6%a6%82%e5%bf%b5\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e结构体属于用户自定义的数据类型，允许用户存储不同的数据类型\u003c/p\u003e","title":"9、结构体","type":"posts"},{"content":" Java中的JUnit单元测试 # Java原生并没有提供单元测试的方法，一般的简单测试使用main方法进行测试，但是如果对于复杂的测试，则需要使用第三方框架来实现，例如JUnit框架\n官网：https://junit.org/junit5/\nJUnit5 的组成：JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage\nJUnit5 建议使用 Java8 及以上版本\nJUnit Platform 是在 JVM 上启动测试框架的基础，它定义了TestEngine在平台运行的新测试框架的 API JUnit Jupiter 它用于编写测试代码的新的编程和扩展模型。它具有所有新的 Junit 注释和TestEngine实现来运行这些注释编写的测试。 JUnit Vintage JUnit4 已经存在了很长时间，并且有许多以 JUnit4 编写的测试。JUnit Jupiter 还需要支持这些测试。为此，开发了 JUnit Vintage 子项目。提供了一个测试引擎，用于在平台上运行基于 JUnit 3 和 JUnit 4 的测试。它要求 JUnit 4.12 或更高版本出现在类路径或模块路径中。从它的名字 Vintage（古老的）中也能有所体会。 4和5的区别 # JUnit4\n@Test的包是org.junit.Test 需要@RunWith(SpringRunner.class) 测试类和测试方法需要public修饰 JUnit5\n@Test的包是org.junit.jupiter.api.Test 不需要@RunWith(SpringRunner.class) 测试类和测试方法不需要public修饰 SE项目不同IDE的使用 # Eclipse\n选中当前工程，右键选择：build path - add libraries - JUnit 4 - 下一步 创建Java类，进行单元测试。 此类中声明单元测试方法。 此单元测试方法上需要声明注解：@Test，并在单元测试类中导入：import org.junit.Test; 声明好单元测试方法以后，就可以在方法体内测试相关的代码。 写完代码以后，左键双击单元测试方法名，右键：run as - JUnit Test IDEA\n在代码空白位置右键，Generate，Test\nTesting library选择Junit版本 Class name输入测试类名称 Destination packages选择测试类的包 Member选择要要测试的方法 OK\n注解 # Annotations 描述 @BeforeEach 在方法上注解，在每个测试方法运行之前执行。 @AfterEach 在方法上注解，在每个测试方法运行之后执行 @BeforeAll 该注解方法会在所有测试方法之前运行，该方法必须是静态的。 @AfterAll 该注解方法会在所有测试方法之后运行，该方法必须是静态的。 @Test 用于将方法标记为测试方法 @DisplayName 用于为测试类或测试方法提供任何自定义显示名称 @Disable 用于禁用或忽略测试类或方法 @Nested 用于创建嵌套测试类 @Tag 用于测试发现或过滤的标签来标记测试方法或类 @TestFactory 标记一种方法是动态测试的测试工场 常用测试方式 # 普通测试 # @Test void testDemo() { } 重复性测试 # @RepeatedTest(5) void testDemo(TestInfo testInfo,RepetitionInfo repetitionInfo){ System.out.println(\u0026#34;repeat:\u0026#34; + testInfo.getDisplayName()); System.out.println(\u0026#34;这是第 \u0026#34;+ repetitionInfo.getCurrentRepetition()+ \u0026#34;次重复\u0026#34;); } 参数测试 # @ParameterizedTest @ValueSource(strings = {\u0026#34;java\u0026#34;, \u0026#34;python\u0026#34;, \u0026#34;go\u0026#34;}) void testDemo(String candidate) { assertTrue(candidate.contains(\u0026#34;o\u0026#34;)); } 超时测试 # @Test @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) void testDemo() { // fails if execution time exceeds 500 milliseconds } ","date":"2023-12-14","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/f742d77f/747f63d4/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJava中的JUnit单元测试 \n    \u003cdiv id=\"java中的junit单元测试\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#java%e4%b8%ad%e7%9a%84junit%e5%8d%95%e5%85%83%e6%b5%8b%e8%af%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eJava原生并没有提供单元测试的方法，一般的简单测试使用\u003ccode\u003emain\u003c/code\u003e方法进行测试，但是如果对于复杂的测试，则需要使用第三方框架来实现，例如\u003ccode\u003eJUnit\u003c/code\u003e框架\u003c/p\u003e","title":"9、单元测试","type":"posts"},{"content":"Logrus 是目前 GitHub 上 Star 数量最多的 Go 日志库, 具有如下特点：\n与 Go log 标准库 API 完全兼容，这意味着任何使用 log 标准库的代码都可以将日志库无缝切换到 Logrus。 支持七种日志级别：Trace、Debug、Info、Warn、Error、Fatal、Panic。 支持结构化日志记录（key-value 形式，容易被程序解析，如 JSON 格式），通过 Filed 机制进行结构化的日志记录。 支持自定义日志格式，内置两种格式 JSONFormatter（JSON 格式） 和 TextFormatter（文本格式），并允许用户通过实现 Formatter 接口来自定义日志格式。 支持可扩展的 Hooks 机制，可以为不同级别的日志添加 Hooks 将日志记录到不同位置，例如将 Error、Fatal 和 Panic 级别的错误日志发送到 logstash、kafka 等。 支持在控制台输出带有不同颜色的日志。 并发安全。 安装 # go get -u github.com/sirupsen/logrus 简单使用 # package main import \u0026#34;github.com/sirupsen/logrus\u0026#34; func main() { logrus.SetLevel(logrus.TraceLevel) logrus.Trace(\u0026#34;trace msg\u0026#34;) logrus.Debug(\u0026#34;debug msg\u0026#34;) logrus.Info(\u0026#34;info msg\u0026#34;) logrus.Warn(\u0026#34;warn msg\u0026#34;) logrus.Error(\u0026#34;error msg\u0026#34;) logrus.Fatal(\u0026#34;fatal msg\u0026#34;) logrus.Panic(\u0026#34;panic msg\u0026#34;) } logrus的使用非常简单，与标准库log类似。logrus支持更多的日志级别：\nPanic：记录日志，然后panic。\nFatal：致命错误，出现错误时程序无法正常运转。输出日志后，程序退出；\nError：错误日志，需要查看原因；\nWarn：警告信息，提醒程序员注意；\nInfo：关键操作，核心流程的日志；\nDebug：一般程序中输出的调试信息；\nTrace：很细粒度的信息，一般用不到；\n日志级别从上向下依次增加，Trace最大，Panic最小。logrus有一个日志级别，高于这个级别的日志不会输出。默认的级别为InfoLevel。所以为了能看到Trace和Debug日志，我们在main函数第一行设置日志级别为TraceLevel。\n配置 # 创建 Logger 对象 # 可以创建自己的Logger对象，使用方式与直接调用logrus的方法类似：\npackage main import \u0026#34;github.com/sirupsen/logrus\u0026#34; func main() { log := logrus.New() log.SetLevel(logrus.InfoLevel) log.SetFormatter(\u0026amp;logrus.JSONFormatter{}) log.Info(\u0026#34;info msg\u0026#34;) } 重定向输出 # 默认情况下，日志输出到io.Stderr。可以调用logrus.SetOutput传入一个io.Writer参数。\nlogrus.SetOutput(os.Stdout) 输出文件名 # 调用logrus.SetReportCaller(true)设置在输出日志中添加文件名和方法信息。\n输出多了两个字段file为调用logrus相关方法的文件名，method为方法名。\nlogrus.SetReportCaller(true) 日志格式 # logrus支持两种日志格式：文本（TextFormatter）和 JSON（JSONFormatter），默认为文本格式。可以通过logrus.SetFormatter设置日志格式：\nlogrus.SetFormatter(\u0026amp;logrus.JSONFormatter{}) 第三方格式 # 除了内置的TextFormatter和JSONFormatter，还有不少第三方格式支持。\n就比如nested-logrus-formatter，首先安装它\ngo get github.com/antonfisher/nested-logrus-formatter 然后我们配置这个 Formatter 即可\npackage main import ( nested \u0026#34;github.com/antonfisher/nested-logrus-formatter\u0026#34; \u0026#34;github.com/sirupsen/logrus\u0026#34; \u0026#34;os\u0026#34; \u0026#34;time\u0026#34; ) func main() { logrus.SetOutput(os.Stdout) logrus.SetReportCaller(true) logrus.SetFormatter(\u0026amp;nested.Formatter{ HideKeys: true, // 隐藏键 TimestampFormat: time.DateTime, // 时间格式 ShowFullLevel: false, // 显示完整级别名称 NoColors: false, // 不使用颜色 }) logrus.Info(\u0026#34;info msg\u0026#34;) logrus.Error(\u0026#34;error msg\u0026#34;) } /* 2025-06-26 10:09:38 [INFO] info msg (/Users/yanggang/develop/go-project/gin-demo/demo.go:20 main.main) 2025-06-26 10:09:38 [ERRO] error msg (/Users/yanggang/develop/go-project/gin-demo/demo.go:21 main.main) */ 添加字段 # 有时候需要在输出中添加一些字段，可以通过调用logrus.WithField和logrus.WithFields实现。\nlogrus.WithFields接受一个logrus.Fields类型的参数，其底层实际上为map[string]interface{}。\n如果在一个函数中的所有日志都需要添加某些字段，可以使用WithFields的返回值。例如在 Web 请求的处理器中，日志都要加上user_id和ip字段：\npackage main import ( \u0026#34;github.com/sirupsen/logrus\u0026#34; ) func main() { requestLogger := logrus.WithFields(logrus.Fields{ \u0026#34;user_id\u0026#34;: 10010, \u0026#34;ip\u0026#34;: \u0026#34;192.168.32.15\u0026#34;, }) requestLogger.Info(\u0026#34;info msg\u0026#34;) requestLogger.Error(\u0026#34;error msg\u0026#34;) } ","date":"2025-06-26","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/29ede47b/fab61218/","section":"文章","summary":"\u003cp\u003eLogrus 是目前 GitHub 上 Star 数量最多的 Go 日志库, 具有如下特点：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e与 Go log 标准库 API 完全兼容，这意味着任何使用 log 标准库的代码都可以将日志库无缝切换到 Logrus。\u003c/li\u003e\n\u003cli\u003e支持七种日志级别：\u003ccode\u003eTrace\u003c/code\u003e、\u003ccode\u003eDebug\u003c/code\u003e、\u003ccode\u003eInfo\u003c/code\u003e、\u003ccode\u003eWarn\u003c/code\u003e、\u003ccode\u003eError\u003c/code\u003e、\u003ccode\u003eFatal\u003c/code\u003e、\u003ccode\u003ePanic\u003c/code\u003e。\u003c/li\u003e\n\u003cli\u003e支持结构化日志记录（key-value 形式，容易被程序解析，如 JSON 格式），通过 Filed 机制进行结构化的日志记录。\u003c/li\u003e\n\u003cli\u003e支持自定义日志格式，内置两种格式 \u003ccode\u003eJSONFormatter\u003c/code\u003e（JSON 格式） 和 \u003ccode\u003eTextFormatter\u003c/code\u003e（文本格式），并允许用户通过实现 \u003ccode\u003eFormatter\u003c/code\u003e 接口来自定义日志格式。\u003c/li\u003e\n\u003cli\u003e支持可扩展的 Hooks 机制，可以为不同级别的日志添加 Hooks 将日志记录到不同位置，例如将 \u003ccode\u003eError\u003c/code\u003e、\u003ccode\u003eFatal\u003c/code\u003e 和 \u003ccode\u003ePanic\u003c/code\u003e 级别的错误日志发送到 logstash、kafka 等。\u003c/li\u003e\n\u003cli\u003e支持在控制台输出带有不同颜色的日志。\u003c/li\u003e\n\u003cli\u003e并发安全。\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e安装 \n    \u003cdiv id=\"安装\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%89%e8%a3%85\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ego get -u github.com/sirupsen/logrus\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e简单使用 \n    \u003cdiv id=\"简单使用\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%ae%80%e5%8d%95%e4%bd%bf%e7%94%a8\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003epackage\u003c/span\u003e \u003cspan class=\"nx\"\u003emain\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;github.com/sirupsen/logrus\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nx\"\u003elogrus\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSetLevel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003elogrus\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eTraceLevel\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nx\"\u003elogrus\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eTrace\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;trace msg\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nx\"\u003elogrus\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eDebug\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;debug msg\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nx\"\u003elogrus\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eInfo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;info msg\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nx\"\u003elogrus\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eWarn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;warn msg\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nx\"\u003elogrus\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eError\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;error msg\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nx\"\u003elogrus\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eFatal\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;fatal msg\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nx\"\u003elogrus\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ePanic\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;panic msg\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003elogrus\u003c/code\u003e的使用非常简单，与标准库\u003ccode\u003elog\u003c/code\u003e类似。\u003ccode\u003elogrus\u003c/code\u003e支持更多的日志级别：\u003c/p\u003e","title":"10、logrus","type":"posts"},{"content":" Context # context（上下文）包设计的核心目标是为了在不同goroutine之间传递截止时间、取消信号以及请求范围的值，尤其适用于处理请求的场景（如HTTP请求）。Context主要解决了以下问题：\n取消控制：如何通知多个goroutine取消当前任务 超时控制：如何为操作设置截止时间或超时时间 值传递：如何安全地在请求范围内传递数据 层次化：如何构建可继承的上下文关系 Context接口设计 # type Context interface { // 返回context的截止时间，如果没有设置截止时间，ok返回false Deadline() (deadline time.Time, ok bool) // 返回一个Channel，当context被取消时，该Channel会被关闭 Done() \u0026lt;-chan struct{} // 如果Done()返回的Channel未关闭，返回nil // 如果Done()返回的Channel已关闭，返回context取消的原因 Err() error // 从context中获取与key关联的值，如果没有则返回nil Value(key interface{}) interface{} } Context树结构 # Context实例可以组成一个树状结构，当父Context取消时，所有从其派生的子Context都会被取消：\nemptyCtx | +--- withCancel ------ withCancel | | +--- withDeadline +--- withValue | +--- withTimeout | +--- withValue 使用Context取消操作 # 使用context.WithCancel可以创建一个可取消的Context：\npackage main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main() { // 创建一个可取消的 Context ctx, cancel := context.WithCancel(context.Background()) go dosomething(ctx) time.Sleep(5 * time.Second) // 取消 context cancel() // 等待观察结果 time.Sleep(5 * time.Second) } func dosomething(ctx context.Context) { for { // 监听 context select { // context 被 Cancel case \u0026lt;-ctx.Done(): fmt.Println(\u0026#34;Cancel\u0026#34;) return // 正常执行业务 default: time.Sleep(1 * time.Second) fmt.Println(time.Now()) } } } 取消传播机制 # 当一个父Context被取消时，从它派生的所有子Context也会被取消。这种传播机制非常适合处理复杂的请求场景：\nfunc main() { // 创建根Context rootCtx, rootCancel := context.WithCancel(context.Background()) defer rootCancel() // 创建子Context childCtx, childCancel := context.WithCancel(rootCtx) defer childCancel() // 启动工作goroutine go doSomething(childCtx) // 5秒后取消根Context time.Sleep(5 * time.Second) fmt.Println(\u0026#34;Cancelling root context...\u0026#34;) rootCancel() // 等待观察结果 time.Sleep(1 * time.Second) } 当rootCancel()被调用时，childCtx也会被取消，即使childCancel()没有被调用。\n示例：并发下载器 # 这个示例展示了如何使用Context控制多个下载任务，当任一下载失败或Context被取消时，所有下载都会停止。\nfunc downloadFiles(ctx context.Context, urls []string) error { // 创建一个错误通道，用于收集下载过程中的错误 errCh := make(chan error, len(urls)) // 创建一个WaitGroup，用于等待所有下载任务完成 var wg sync.WaitGroup // 为每个URL启动一个下载goroutine for _, url := range urls { wg.Add(1) go func(url string) { defer wg.Done() // 检查Context是否已取消 select { case \u0026lt;-ctx.Done(): errCh \u0026lt;- fmt.Errorf(\u0026#34;download of %s canceled: %v\u0026#34;, url, ctx.Err()) return default: // 继续下载 } // 模拟下载操作 err := downloadFile(ctx, url) if err != nil { errCh \u0026lt;- err } }(url) } // 创建一个goroutine等待所有下载完成并关闭错误通道 go func() { wg.Wait() close(errCh) }() // 收集第一个错误 for err := range errCh { return err // 返回第一个遇到的错误 } return nil } func downloadFile(ctx context.Context, url string) error { // 创建HTTP请求 req, err := http.NewRequestWithContext(ctx, \u0026#34;GET\u0026#34;, url, nil) if err != nil { return err } // 执行请求 resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() // 处理响应... return nil } 超时控制 # 截止时间控制 # 使用context.WithDeadline可以创建一个具有截止时间的Context：\n// 创建一个10秒后到期的Context deadline := time.Now().Add(10 * time.Second) ctx, cancel := context.WithDeadline(context.Background(), deadline) // 确保在函数退出时取消Context以释放资源 defer cancel() // 使用具有截止时间的Context go doSomethingWithDeadline(ctx) 超时控制 # context.WithTimeout是WithDeadline的便捷包装，用于设置相对超时时间：\n// 创建一个5秒后超时的Context ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 使用具有超时的Context go doSomethingWithTimeout(ctx) 示例：使用超时控制HTTP请求 # func fetchURL(url string) ([]byte, error) { // 创建一个30秒超时的Context ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // 创建一个带有Context的请求 req, err := http.NewRequestWithContext(ctx, \u0026#34;GET\u0026#34;, url, nil) if err != nil { return nil, err } // 执行请求 resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() // 读取响应体 return io.ReadAll(resp.Body) } 优雅处理超时 # 当Context超时时，可以通过Err()方法区分不同的错误类型：\nfunc processWithTimeout(ctx context.Context) error { select { case \u0026lt;-ctx.Done(): err := ctx.Err() if err == context.DeadlineExceeded { return fmt.Errorf(\u0026#34;operation timed out: %v\u0026#34;, err) } if err == context.Canceled { return fmt.Errorf(\u0026#34;operation canceled: %v\u0026#34;, err) } return err case result := \u0026lt;-doWork(): return processResult(result) } } 使用Context传递值 # 创建带值的Context # 使用context.WithValue可以创建一个包含键值对的Context：\n// 创建一个包含请求ID的Context ctx := context.WithValue(context.Background(), \u0026#34;request_id\u0026#34;, \u0026#34;12345\u0026#34;) // 使用包含值的Context go processRequest(ctx) 获取Context中的值 # 在goroutine中，可以通过Context的Value()方法获取传递的值：\nfunc processRequest(ctx context.Context) { // 获取请求ID requestID, ok := ctx.Value(\u0026#34;request_id\u0026#34;).(string) if !ok { requestID = \u0026#34;unknown\u0026#34; } fmt.Printf(\u0026#34;Processing request %s\\n\u0026#34;, requestID) // 处理请求... } 使用自定义类型避免键冲突 # 为了避免不同包之间的键名冲突，建议使用自定义类型作为Context的键：\n// 定义一个私有的类型作为键 type contextKey string // 定义常量作为具体的键 const ( requestIDKey contextKey = \u0026#34;request_id\u0026#34; userIDKey contextKey = \u0026#34;user_id\u0026#34; ) // 使用自定义类型作为键创建Context ctx := context.WithValue(context.Background(), requestIDKey, \u0026#34;12345\u0026#34;) ctx = context.WithValue(ctx, userIDKey, \u0026#34;user-567\u0026#34;) // 获取值时使用相同的键类型 func getRequestID(ctx context.Context) string { requestID, ok := ctx.Value(requestIDKey).(string) if !ok { return \u0026#34;unknown\u0026#34; } return requestID } 值传递的最佳实践 # Context中的值传递主要用于传递请求范围的数据，如请求ID、认证令牌等。不应该将它用于传递可选参数或功能控制。一些最佳实践包括：\n只存储请求范围内的数据，不要存储全局状态 值应该是不可变的，避免并发修改 对于简单类型，使用强类型键避免类型断言错误 不要滥用Context存储大量数据，这会降低代码可读性和可维护性 Context使用的最佳实践与陷阱 # Context传递规范 # 按照Go的惯例，Context应该是函数的第一个参数\n// 正确的Context参数位置 func DoSomething(ctx context.Context, arg Arg) error { // ... } // 不推荐的做法 func DoSomething(arg Arg, ctx context.Context) error { // ... } 避免将Context存储在结构体中 # Context应该通过函数参数显式传递，而不是存储在结构体中\n// 不推荐的做法 type Service struct { ctx context.Context // ... } // 推荐的做法 type Service struct { // 没有存储Context } func (s *Service) DoWork(ctx context.Context) error { // 通过参数使用Context } 不要传递nil Context # 如果不确定使用哪个Context，可以使用context.Background()或context.TODO()\n// 不推荐的做法 func DoSomething(ctx context.Context) { if ctx == nil { ctx = context.Background() } // ... } // 推荐的做法 func DoSomething(ctx context.Context) { // 调用者负责提供有效的Context // ... } 正确管理取消函数 # 每次调用WithCancel、WithTimeout或WithDeadline都会返回一个cancel函数，应该在不再需要Context时调用这个函数\n// 推荐的做法 func main() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 确保在函数退出时调用cancel // 使用ctx... } 避免在Context中存储过多数据 # Context主要用于传递请求范围的元数据，不应该用于传递大量数据或业务逻辑参数\n// 不推荐的做法 ctx := context.WithValue(ctx, \u0026#34;database\u0026#34;, db) ctx = context.WithValue(ctx, \u0026#34;config\u0026#34;, config) ctx = context.WithValue(ctx, \u0026#34;user_data\u0026#34;, hugeUserDataStruct) // 推荐的做法 - 只存储请求范围的元数据 ctx := context.WithValue(ctx, requestIDKey, requestID) ctx = context.WithValue(ctx, userIDKey, userID) ","date":"2025-05-12","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/12d682f6/75774588/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eContext \n    \u003cdiv id=\"context\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#context\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003econtext\u003c/code\u003e（上下文）包设计的核心目标是为了在不同goroutine之间传递截止时间、取消信号以及请求范围的值，尤其适用于处理请求的场景（如HTTP请求）。Context主要解决了以下问题：\u003c/p\u003e","title":"10、context","type":"posts"},{"content":"Scrollbar（滚动条）组件用于滚动一些组件的可见范围，根据方向可分为垂直滚动条和水平滚动条。Scrollbar 组件常常被用于实现文本、画布和列表框的滚动。\n如下面例子，给Text控件加滚动条，当 Text 组件的可视范围发生改变的时候，Text 组件通过调用 set() 方法通知 Scrollbar 组件。而当用户操纵滚动条的时候，将自动调用 Text 组件的 yview() 方法。\nimport tkinter as tk root = tk.Tk() # 创建Text text = tk.Text(root,width=20,height=5) text.pack(side=\u0026#39;left\u0026#39;,fill=\u0026#39;both\u0026#39;) # 创建滚动条 sc = tk.Scrollbar(root) sc.pack(side=\u0026#39;right\u0026#39;,fill=\u0026#39;y\u0026#39;) # 滚动条绑定 Text sc.config(command=text.yview) # Text绑定 text.config(yscrollcommand=sc.set) root.mainloop() 属性 # 选项 含义 activebackground 1. 指定当鼠标在上方飘过的时候滑块和箭头的背景颜色； 2. 默认值由系统指定 activerelief 1. 指定当鼠标在上方飘过的时候滑块的样式； 2. 默认值是 \u0026ldquo;raised\u0026rdquo; 3. 可以选择 \u0026ldquo;flat\u0026rdquo;，\u0026ldquo;sunken\u0026rdquo;，\u0026ldquo;groove\u0026rdquo;，\u0026ldquo;ridge\u0026rdquo; background 1. 指定背景颜色； 2. 默认值由系统指定 bg 跟 background 一样 borderwidth 1. 指定边框宽度 ；2. 默认值是 0 bd 跟 borderwidth 一样 command 1. 当滚动条更新时回调的函数 ；2. 通常的是指定对应组件的 xview() 或 yview() 方法 cursor 1. 指定当鼠标在上方飘过的时候的鼠标样式 ；2. 默认值由系统指定 elementborderwidth 1. 指定滚动条和箭头的边框宽度 ；2. 默认值是 -1（表示使用 borderwidth 选项的值） highlightbackground 1. 指定当滚动条没有获得焦点的时候高亮边框的颜色； 2. 默认值由系统指定 highlightcolor 1. 指定当滚动条获得焦点的时候高亮边框的颜色； 2. 默认值由系统指定 highlightthickness 1. 指定高亮边框的宽度 ；2. 默认值是 0（不带高亮边框） jump 1. 指定当用户拖拽滚动条时的行为； 2. 默认值是 False，滚动条的任何一丝变动都会即刻调用 command 选项指定的回调函数； 3. 设置为 True 则当用户松开鼠标才调用 orient 1. 指定绘制 \u0026ldquo;horizontal\u0026rdquo;（垂直滚动条）还是 \u0026ldquo;vertical\u0026rdquo;（水平滚动条）； 2. 默认值是 VERTICAL relief 1. 指定边框样式； 2. 默认值是 \u0026ldquo;sunken\u0026rdquo;； 3. 可以选择 \u0026ldquo;flat\u0026rdquo;，\u0026ldquo;raised\u0026rdquo;，\u0026ldquo;groove\u0026rdquo;，\u0026ldquo;ridge\u0026rdquo; repeatdelay 1. 该选项指定鼠标左键点击滚动条凹槽的响应时间 ；2. 默认值是 300（毫秒） repeatinterval 1. 该选项指定鼠标左键紧按滚动条凹槽时的响应间隔 ；2. 默认值是 100（毫秒） takefocus 1. 指定使用 Tab 键可以将焦点移到该 Scrollbar 组件上 ；2. 默认是开启的，可以将该选项设置为 False 避免焦点在此组件上 troughcolor 1. 指定凹槽的颜色 ；2. 默认值由系统指定 width 1. 指定滚动条的宽度 ；2. 默认值是 16 像素 方法 # activate(element) 显示 element 参数指定的元素的背景颜色和样式 element 参数可以设置为：\u0026ldquo;arrow1\u0026rdquo;（箭头1），\u0026ldquo;arrow2\u0026rdquo;（箭头2）或 \u0026ldquo;slider\u0026rdquo;（滑块） delta(deltax, deltay) 给定一个鼠标移动的范围 deltax 和 deltay（像素为单位，deltax 表示水平移动量，deltay 表示垂直移动量），然后该方法返回一个浮点类型的值（范围 -1.0 ~ 1.0） 这通常在鼠标绑定上使用，用于确定当用户拖拽鼠标时滑块的如何移动 fraction(x, y) 给定一个像素坐标 (x, y)，该方法返回最接近给定坐标的滚动条位置（范围 0.0 ~ 1.0） get() 返回当前滑块的位置 (a, b) a 值表示当前滑块的顶端或左端的位置，b 值表示当前滑块的底端或右端的位置（范围 0.0 ~ 1.0） identify(x, y) 返回一个字符串表示指定位置下（如果有的话）的滚动条部件 返回值可以是：\u0026ldquo;arrow1\u0026rdquo;（箭头1），\u0026ldquo;arrow2\u0026rdquo;（箭头2）、\u0026ldquo;slider\u0026rdquo;（滑块）或 \u0026ldquo;\u0026quot;（啥都没有） set(*args) 设置当前滚动条的位置 如果设置则需要两个参数 (first, last)，first 表示当前滑块的顶端或左端的位置，last 表示当前滑块的底端或右端的位置（范围 0.0 ~ 1.0） ","date":"2025-04-15","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/a879fe06/f4da69f7/","section":"文章","summary":"\u003cp\u003eScrollbar（滚动条）组件用于滚动一些组件的可见范围，根据方向可分为垂直滚动条和水平滚动条。Scrollbar 组件常常被用于实现文本、画布和列表框的滚动。\u003c/p\u003e","title":"10、滚动条Scrollbar","type":"posts"},{"content":"","date":"2025-02-12","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/4f5b7444/5e18a930/","section":"文章","summary":"","title":"10、SpringBoot生命周期","type":"posts"},{"content":" 加密方式 # 对称加密：客户端和服务器共用同一个密钥，可以用于加密一段内容，可以用于解密这段内容。 优点：加解密效率高。 缺点：安全性方面可能存在一些问题，因为密钥存放在客户端有被窃取的风险 对称加密的代表算法有：AES、DES等。 非对称加密：它将密钥分成了两种（公钥和私钥）。公钥通常存放在客户端，私钥通常存放在服务器。使用公钥加密的数据只有用私钥才能解密，反过来使用私钥加密的数据也只有用公钥才能解密。 优点：是安全性更高，因为客户端发送给服务器的加密信息只有用服务器的私钥才能解密，因此不用担心被别人破解。 缺点：加解密的效率相比于对称加密要差很多。 非对称加密的代表算法有：RSA、ElGamal等。 哈希算法：它的作用是对任意长度的数据生成一个固定长度的唯一标识，也叫哈希值、散列值或消息摘要（后文统称为哈希值）。 优点：原始数据的任何改变都会导致哈希值的巨大变化。不能从哈希值还原出原始数据。一般用于密码的保存或者内容校验。 常见的算法有：MD5、SHA、SM3、Bcrypt等。 对称加密 # 对称加密是一种相对来说比较常用的加密方式，它的工作原理很简单：先用一个秘钥将明文加密成密文，再用相同的秘钥将密文解密成明文。其中最常见的对称加密算法有DES、3DES、AES。\n对称加密的优点是加密解密速度快，因为它使用的是同一个秘钥进行加密和解密。但是它也有缺点，由于是同一个秘钥，一旦这个秘钥泄漏，整个加密系统就会失效。一旦数据被攻击者窃取，攻击者就可以使用这个秘钥进行解密。\npackage top.ygang.huijifindresource.util; import java.nio.charset.Charset; import java.security.Key; import java.util.Base64; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.spec.SecretKeySpec; /** * AES对称加密工具类 */ public class AESUtil { /** * 加密 * @param plaintext * @param key * @return * @throws Exception */ public static String encrypt(String plaintext, String key) throws Exception { SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), \u0026#34;AES\u0026#34;); Cipher cipher = Cipher.getInstance(\u0026#34;AES\u0026#34;); cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] bytes = cipher.doFinal(plaintext.getBytes()); String s = Base64.getEncoder().encodeToString(bytes); return s; } /** * 解密 * @param ciphertext * @param key * @return * @throws Exception */ public static String decrypt(String ciphertext, String key) throws Exception { SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), \u0026#34;AES\u0026#34;); Cipher cipher = Cipher.getInstance(\u0026#34;AES\u0026#34;); cipher.init(Cipher.DECRYPT_MODE, secretKey); byte[] decrypted = Base64.getDecoder().decode(ciphertext); byte[] decryptedBytes = cipher.doFinal(decrypted); return new String(decryptedBytes); } public static void main(String[] args) throws Exception { String msg = \u0026#34;你好\u0026#34;; String key = \u0026#34;ThisIsASecretKey\u0026#34;; String encrypt = encrypt(msg, key); System.out.println(encrypt); String decrypt = decrypt(encrypt, key); System.out.println(decrypt); } } 非对称加密RSA # RSA加密是一种非对称加密。可以在不直接传递密钥的情况下，完成解密。这能够确保信息的安全性，避免了直接传递密钥所造成的被破解的风险。是由一对密钥来进行加解密的过程，分别称为公钥和私钥。两者之间有数学相关，该加密算法的原理就是对于极大整数做因数分解的困难性来保证安全性。通常个人保存私钥，公钥是公开的（可能同时多人持有）。\nimport javax.crypto.Cipher; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import java.util.HashMap; import java.util.Map; /** * @描述 Rsa加密工具类 * @创建人 yhgh * @创建时间 2022/01/22 11:49 */ public class RsaUtil { /** * RSA最大加密明文大小 */ private static final int MAX_ENCRYPT_BLOCK = 117; /** * RSA最大解密密文大小 */ private static final int MAX_DECRYPT_BLOCK = 128; /** * 加密方式名称 */ private static final String ALGORITHM_NAME = \u0026#34;RSA\u0026#34;; private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder(); private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder(); /** * 获取密钥对 */ private static KeyPair getKeyPair() throws Exception { KeyPairGenerator generator = KeyPairGenerator.getInstance(ALGORITHM_NAME); generator.initialize(1024); return generator.generateKeyPair(); } /** * 获取base64加密后密钥对 */ public static Map\u0026lt;String, String\u0026gt; getKeyPairMap() throws Exception { KeyPair keyPair = getKeyPair(); String privateKey = new String(BASE64_ENCODER.encode(keyPair.getPrivate().getEncoded())); String publicKey = new String(BASE64_ENCODER.encode(keyPair.getPublic().getEncoded())); Map\u0026lt;String, String\u0026gt; keyMap = new HashMap\u0026lt;\u0026gt;(); keyMap.put(\u0026#34;privateKey\u0026#34;, privateKey); keyMap.put(\u0026#34;publicKey\u0026#34;, publicKey); return keyMap; } /** * 获取公钥 * * @param publicKey base64加密的公钥字符串 */ private static PublicKey getPublicKey(String publicKey) throws Exception { byte[] decodedKey = BASE64_DECODER.decode(publicKey.getBytes()); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey); KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_NAME); return keyFactory.generatePublic(keySpec); } /** * 获取私钥 * * @param privateKey base64加密的私钥字符串 */ private static PrivateKey getPrivateKey(String privateKey) throws Exception { byte[] decodedKey = BASE64_DECODER.decode(privateKey.getBytes()); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey); KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_NAME); return keyFactory.generatePrivate(keySpec); } /** * RSA加密 * * @param data 待加密数据 * @param publicKeyStr 公钥 */ public static String encrypt(String data, String publicKeyStr) throws Exception { Cipher cipher = Cipher.getInstance(ALGORITHM_NAME); cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKeyStr)); int inputLen = data.getBytes().length; ByteArrayOutputStream out = new ByteArrayOutputStream(); int offset = 0; byte[] cache; int i = 0; // 对数据分段加密 while (inputLen - offset \u0026gt; 0) { if (inputLen - offset \u0026gt; MAX_ENCRYPT_BLOCK) { cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK); } else { cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset); } out.write(cache, 0, cache.length); i++; offset = i * MAX_ENCRYPT_BLOCK; } byte[] encryptedData = out.toByteArray(); out.close(); // 获取加密内容使用base64进行编码,并以UTF-8为标准转化成字符串 // 加密后的字符串 return new String(BASE64_ENCODER.encode(encryptedData)); } /** * RSA解密 * * @param data 待解密数据 * @param privateKeyStr 私钥 */ public static String decrypt(String data, String privateKeyStr) throws Exception { Cipher cipher = Cipher.getInstance(ALGORITHM_NAME); cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(privateKeyStr)); byte[] dataBytes = BASE64_DECODER.decode(data); int inputLen = dataBytes.length; ByteArrayOutputStream out = new ByteArrayOutputStream(); int offset = 0; byte[] cache; int i = 0; // 对数据分段解密 while (inputLen - offset \u0026gt; 0) { if (inputLen - offset \u0026gt; MAX_DECRYPT_BLOCK) { cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK); } else { cache = cipher.doFinal(dataBytes, offset, inputLen - offset); } out.write(cache, 0, cache.length); i++; offset = i * MAX_DECRYPT_BLOCK; } byte[] decryptedData = out.toByteArray(); out.close(); // 解密后的内容 return new String(decryptedData, StandardCharsets.UTF_8); } // 测试 public static void main(String[] args) throws Exception{ // 获取公钥、私钥 Map\u0026lt;String, String\u0026gt; keyPairMap = getKeyPairMap(); String privateKey = keyPairMap.get(\u0026#34;privateKey\u0026#34;); String publicKey = keyPairMap.get(\u0026#34;publicKey\u0026#34;); System.out.println(\u0026#34;privateKey = \u0026#34; + privateKey); System.out.println(\u0026#34;publicKey = \u0026#34; + publicKey); // 要加密的字符串 String data = \u0026#34;admin.123\u0026#34;; // 使用公钥加密 String encrypt = encrypt(data, publicKey); System.out.println(\u0026#34;encrypt = \u0026#34; + encrypt); // 使用私钥解密 String decrypt = decrypt(encrypt, privateKey); System.out.println(\u0026#34;decrypt = \u0026#34; + decrypt); } } 前端加密\nnpm install jsencrypt import JSEncrypt from \u0026#39;jsencrypt\u0026#39; // 创建加密对象实例 const encryptor = new JSEncrypt() const pubKey = \u0026#39;\u0026#39; // 设置公钥 encryptor.setPublicKey(pubKey) // 对内容进行加密 const rsaPassWord = encryptor.encrypt(\u0026#39;要加密的内容\u0026#39;) 哈希算法 # 由于哈希算法加密的结果不可逆，常见的使用场景如下：\n保存密码到数据库时使用哈希算法进行加密，可以通过比较用户输入密码的哈希值和数据库保存的哈希值是否一致，来判断密码是否正确。 我们下载一个文件时，可以通过比较文件的哈希值和官方提供的哈希值是否一致，来判断文件是否被篡改或损坏； MD5 # MD 算法已经不被推荐使用，建议使用更安全的哈希算法比如 SHA-2、Bcrypt。\nString originalString = \u0026#34;哈希算法\u0026#34;; // 创建MD5摘要对象 MessageDigest messageDigest = MessageDigest.getInstance(\u0026#34;MD5\u0026#34;); messageDigest.update(originalString.getBytes(StandardCharsets.UTF_8)); // 计算哈希值 byte[] result = messageDigest.digest(); // 将哈希值转换为十六进制字符串 String hexString = new HexBinaryAdapter().marshal(result); System.out.println(\u0026#34;Original String: \u0026#34; + originalString); // Original String: 哈希算法 System.out.println(\u0026#34;MD5 Hash: \u0026#34; + hexString.toLowerCase()); // MD5 Hash: 16600f689cdf9ad8305749bd64d3ca32 Bcrypt # 基于 Blowfish 加密算法的密码哈希算法，专门为密码加密而设计，安全性高。\n在Spring Security中有集成，如果不使用Spring Security，也可以单独引入Bcrypt\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.mindrot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jbcrypt\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;0.4\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; package top.ygang.huijifindresource.utils.captcha; import org.mindrot.jbcrypt.BCrypt; /** * @Description: Bcrypt加密工具类 * @Author: yanggang * @Date: */ public class BcryptUtil { /** * 加密密码，自动包含盐值 * @param plainTextPassword * @return */ public static String encryptPassword(String plainTextPassword) { return BCrypt.hashpw(plainTextPassword, BCrypt.gensalt()); } /** * 验证密码 * @param plainTextPassword * @param storedHash * @return */ public static boolean verifyPassword(String plainTextPassword, String storedHash) { return BCrypt.checkpw(plainTextPassword, storedHash); } public static void main(String[] args) { String passwd = \u0026#34;abcd.123\u0026#34;; String encryptPassword = encryptPassword(passwd); System.out.println(encryptPassword); boolean b = verifyPassword(passwd, encryptPassword); System.out.println(b); } } ","date":"2024-12-09","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/409de243/14f3501f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e加密方式 \n    \u003cdiv id=\"加密方式\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8a%a0%e5%af%86%e6%96%b9%e5%bc%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e对称加密\u003c/strong\u003e：客户端和服务器共用同一个密钥，可以用于加密一段内容，可以用于解密这段内容。\n\u003cul\u003e\n\u003cli\u003e优点：加解密效率高。\u003c/li\u003e\n\u003cli\u003e缺点：安全性方面可能存在一些问题，因为密钥存放在客户端有被窃取的风险\u003c/li\u003e\n\u003cli\u003e对称加密的代表算法有：AES、DES等。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e非对称加密\u003c/strong\u003e：它将密钥分成了两种（公钥和私钥）。公钥通常存放在客户端，私钥通常存放在服务器。使用公钥加密的数据只有用私钥才能解密，反过来使用私钥加密的数据也只有用公钥才能解密。\n\u003cul\u003e\n\u003cli\u003e优点：是安全性更高，因为客户端发送给服务器的加密信息只有用服务器的私钥才能解密，因此不用担心被别人破解。\u003c/li\u003e\n\u003cli\u003e缺点：加解密的效率相比于对称加密要差很多。\u003c/li\u003e\n\u003cli\u003e非对称加密的代表算法有：RSA、ElGamal等。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e哈希算法\u003c/strong\u003e：它的作用是对任意长度的数据生成一个固定长度的唯一标识，也叫哈希值、散列值或消息摘要（后文统称为哈希值）。\n\u003cul\u003e\n\u003cli\u003e优点：原始数据的任何改变都会导致哈希值的巨大变化。不能从哈希值还原出原始数据。一般用于密码的保存或者内容校验。\u003c/li\u003e\n\u003cli\u003e常见的算法有：MD5、SHA、SM3、Bcrypt等。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003e对称加密 \n    \u003cdiv id=\"对称加密\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%af%b9%e7%a7%b0%e5%8a%a0%e5%af%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e对称加密是一种相对来说比较常用的加密方式，它的工作原理很简单：先用一个秘钥将明文加密成密文，再用相同的秘钥将密文解密成明文。其中最常见的对称加密算法有DES、3DES、AES。\u003c/p\u003e","title":"10、加密算法","type":"posts"},{"content":" 接口 # 接口除了实现是用:，其余和Java中的使用基本一致。\n定义与实现 # using System; namespace HelloWorldApplication { interface IAnimal{ string? sayName(); } class Dog : IAnimal { public string? name; public string? sayName() { return this.name; } } class Program { static void Main(string[] args) { Dog dog = new Dog(); dog.name = \u0026#34;wangcai\u0026#34;; Console.WriteLine(dog.sayName()); } } } ","date":"2024-09-12","externalUrl":null,"permalink":"/posts/5ab2821f/3986e40a/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e接口 \n    \u003cdiv id=\"接口\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8e%a5%e5%8f%a3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e接口除了实现是用\u003ccode\u003e:\u003c/code\u003e，其余和Java中的使用基本一致。\u003c/p\u003e","title":"10、接口","type":"posts"},{"content":" 数据持久化 # 数据持久化就是将内存中的数据模型转换为存储模型，以及将存储模型转换为内存中的数据模型的统称。数据模型可以是任何数据结构或对象模型，存储模型可以是关系模型、XML、二进制流等。\n数据存储状态一：瞬时状态\n保存在内存的程序数据，程序退出，数据就消失了 数据存储状态二：持久状态\n保存在磁盘上的程序数据，程序退出后依然存在 数据持久化技术 # Hibernate、JPA、JDBC（Java Datebase Connectivity）等\nJDBC框架 # Driver 接口 # java.sql.Driver接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商进行实现，不同数据库厂商提供不同的实现 在程序中不需要直接去访问实现了 Driver 接口的类，而是由**驱动程序管理器类(java.sql.DriverManager)**去调用这些Driver实现 常见Driver Oracle的驱动：oracle.jdbc.driver.OracleDriver MySQL的驱动：com.mysql.jdbc.Driver（mysql6-）、com.mysql.cj.jdbc.Driver（mysql6+） 连接、操作数据库步骤 # Connection conn = null; Statement st=null; ResultSet rs = null; try { //获得Connection //创建Statement //处理查询结果ResultSet }catch(Exception e){ e.printStackTrance(); } finally { //释放资源ResultSet,Statement,Connection } 一、获取数据库连接对象 # 1、导入jar包 # 1、在项目中创建lib文件夹\n2、将驱动jar文件放置到lib文件夹\n3、集成到项目ClassPath中，右键build(eclipse)、add as library(idea)\n2、注册驱动（Java代码中加载驱动类） # 将com.mysql.jdbc包下的Driver类的字节码文件从本地磁盘加载到方法区中\n方式一：加载 JDBC 驱动需调用 Class 类的静态方法forName()，向其传递要加载的 JDBC 驱动的类名\n//将com.mysql.jdbc包下的Driver类的字节码文件从本地磁盘加载到方法区中 Class.forname(\u0026#34;com.mysql.jdbc.Driver\u0026#34;) 方式二：DriverManager 类是驱动程序管理器类，负责管理驱动程序（不推荐）\nDriverManager.registerDriver(com.mysql.jdbc.Driver); 通常不用显式调用 DriverManager 类的registerDriver() 方法来注册驱动程序类的实例，原因如下：\n1、该方法，过于依赖jar包的存在\n2、该方法，会造成二次注册\n3、使用Class.forname可以降低耦合性\n3、获取连接对象 # //本机IP：localhost 本机端口号：3306 String url = \u0026#34;jdbc:mysql://ip:port/databasename?serverTimezone=Asia/Shanghai\u0026amp;characterEncoding=utf-8\u0026#34;; String user = \u0026#34;username\u0026#34;; String passWord = \u0026#34;password\u0026#34;; Connection conn = DriverManager.getConnection(url,user,passWord); 协议：JDBC URL中的协议总是jdbc\n子协议：子协议用于标识一个数据库驱动程序\n子名称：一种标识数据库的方法。子名称可以依不同的子协议而变化，用子名称的目的是为 了定位数据库提供足够的信息。包含主机名(对应服务端的ip地址)，端口号，数据库名\nJDBC URL # MySQL # jdbc:mysql://\u0026lt;ip\u0026gt;:\u0026lt;port\u0026gt;/\u0026lt;database_name\u0026gt;?\u0026lt;key\u0026gt;=\u0026lt;value\u0026gt;，常用的连接配置如下\nKEY VALUE Description serverTimezone Asia/Shanghai 时区 useSSL true、false 是否使用ssl加密连接 connectTimeout 10 设置连接超时时间，单位是秒 socketTimeout 30 设置读取结果集的超时时间，单位是秒 authenticationPlugins mysql_native_password、sha256_password 安全认证方式 characterEncoding uft8 数据库字符集 rewriteBatchedStatements true、false 批量执行sql zeroDateTimeBehavior EXCEPTION默认抛出异常（Zero date value prohibited）、CONVERT_TO_NULL转换为null；ROUND替换成最近的日期即0001-01-01 对于零值timestamp类型的处理方式 autoReconnect true、false 数据库连接中断是否重连 maxReconnects 3 重新连接尝试次数 initialTimeout 2 两次重连间隔时间，单位是秒 failOverReadOnly true、false 是否使用针对数据库连接池的重连策略 useUnicode true、false 是否使用Unicode字符集，如果参数characterEncoding设置为gb2312或gbk，本参数值必须设置为true Oracle # jdbc:oracle:thin:@\u0026lt;ip\u0026gt;:\u0026lt;port\u0026gt;:\u0026lt;database_name\u0026gt;?\u0026lt;key\u0026gt;=\u0026lt;value\u0026gt;\nSQLServer # jdbc:microsoft:sqlserver//\u0026lt;ip\u0026gt;:\u0026lt;port\u0026gt;;DatabaseName=\u0026lt;database_name\u0026gt;\nPostgreSQL # jdbc:postgresql://\u0026lt;ip\u0026gt;:\u0026lt;port\u0026gt;/\u0026lt;database_name\u0026gt;?\u0026lt;key\u0026gt;=\u0026lt;value\u0026gt;\n二、执行sql语句 # 1、获取Statement对象 # Statement statement = conn.createStatement(); 2、执行sql语句 # int result = statement.executeUpdate(\u0026#34;sql语句字符串\u0026#34;) Statement类方法分类 # int executeUpdate(sql) 针对数据库的增(insert into)、删(delete from)、改(update set)操作 返回值类型：实际影响的行数 ResultSet executeQuery(sql) 针对数据库的查询(select from)操作 返回值类型：一个结果集类型 boolean execute(sql) 针对数据库的增删改查操作，一般我们不会使用，jdbc的底层代码会使用 如果执行的sql语句是增删改，返回false 如果执行的sql语句是查询，返回true 3、处理执行结果(ResultSet) # //使用Statement类的方法executeQuery(String sql);获得结果集类型的对象 ResultSet set = statement.executeQuery(sql); while(set.next()){ //形参可以直接写字段名，字段名不区分大小写 int id = set.getInt(\u0026#34;book_id\u0026#34;); //也可以写字段索引，索引从1开始 int id = set.getInt(1); } 三、释放资源 # resultSet.close(); statement.close(); connection.close(); 实现JDBC工具类 # 将获取连接和关闭资源等公共、重复的代码封装成一个工具类\nimport java.sql.*; public class JDBCUtil { private static String driver; private static String url; private static String user; private static String passWord; //解析配置文件.properties static { try { Properties properties = new Properties(); properties.load(new FileInputStream(\u0026#34;jdbc.properties\u0026#34;)); driver = (String) properties.get(\u0026#34;driver\u0026#34;); url = (String) properties.get(\u0026#34;url\u0026#34;); user = (String) properties.get(\u0026#34;user\u0026#34;); passWord = (String) properties.get(\u0026#34;passWord\u0026#34;); }catch (Exception e){ e.printStackTrace(); } } //获得Connection对象 public static Connection getConnection(){ Connection connection = null; try{ Class.forName(driver); connection = DriverManager.getConnection(url,user,passWord); }catch (Exception e){ e.printStackTrace(); } return connection; } //关闭资源 -- 针对查询 public static void close(ResultSet resultset,Statement statement,Connection connection){ try { if (resultset != null) { resultset.close(); } if (statement != null) { statement.close(); } if (connection != null) { connection.close(); } }catch (Exception e){ e.printStackTrace(); } } //关闭资源 -- 针对增删改 public static void close(Statement statement,Connection connection){ close(null,statement,connection); } //针对DML语句--增删改 public static boolean executeUpdate(String sql,List\u0026lt;Object\u0026gt; list){ Connection connection = getConnection(); PreparedStatement pre = null; try { pre = connection.prepareStatement(sql); for (int i = 0;i \u0026lt; list.size();i++){ pre.setObject(i + 1,list.get(i)); } return (pre.executeUpdate() \u0026gt; 0)? true : false; }catch (Exception e){ e.printStackTrace(); }finally { close(pre,connection); } return false; } //针对查DQL语句 public static \u0026lt;T\u0026gt; List\u0026lt;T\u0026gt; executeQuery(String sql,List\u0026lt;Object\u0026gt; list,Class\u0026lt;T\u0026gt; tClass){ Connection connection = getConnection(); PreparedStatement statement = null; ResultSet resultSet = null; List\u0026lt;T\u0026gt; li = new ArrayList\u0026lt;\u0026gt;(); try { statement = connection.prepareStatement(sql); for (int i = 0;i \u0026lt; list.size();i++){ statement.setObject(i + 1,list.get(i)); } resultSet = statement.executeQuery(); ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); //获取列数 int count = resultSetMetaData.getColumnCount(); //遍历所有行 while (resultSet.next()){ T t = tClass.newInstance(); for (int i = 1;i \u0026lt;= count;i++){ //获取每一列列名 String keyName = resultSetMetaData.getColumnLabel(i); //获取每一列对应的值 Object value = resultSet.getObject(keyName); //T中对应的属性 Field key = tClass.getDeclaredField(keyName); key.setAccessible(true); key.set(t,value); } li.add(t); } }catch (Exception e){ e.printStackTrace(); }finally { close(resultSet,statement,connection); } return li; } } 封装查询返回值遍历方式 # List\u0026lt;Map\u0026gt; list = JDBCUtils.executeQuery(sql,new ArrayList()); for (Map\u0026lt;String,Object\u0026gt; map : list){ for (Map.Entry\u0026lt;String,Object\u0026gt; entry : map.entrySet()){ String s = entry.getKey(); Object o = entry.getValue(); System.out.print(s + \u0026#34;=\u0026#34; + o + \u0026#34;,\u0026#34;); } System.out.println(); } SQL注入攻击 # SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查，而在用户输入数据中注入非法的 SQL 语句段或命令，如下，从而利用系统的 SQL 引擎完成恶意行为的做法。\nSELECT user, password FROM user_table WHERE user=\u0026#39;lucy\u0026#39; AND password = # 用户输入密码为：\u0026#39;abc\u0026#39; OR \u0026#39;1\u0026#39; = \u0026#39;1\u0026#39; SELECT user, password FROM user_table WHERE user=\u0026#39;lucy\u0026#39; AND password = \u0026#39;abc\u0026#39; OR \u0026#39;1\u0026#39; = \u0026#39;1\u0026#39; 对于 Java 而言，要防范 SQL 注入，只要用 PreparedStatement(继承于Statement) 取代 Statement 就可以了\nPreparedStatement类 # 可以通过调用 Connection 对象的preparedStatement()方法获取 PreparedStatement 对象 PreparedStatement 接口是 Statement 的子接口，它表示一条预编译过的 SQL 语句，使用?作为占位符 SELECT user, password FROM user_table WHERE user='lucy' AND password = ? 使用setObject(int parameterIndex, Object x)，可对指定索引的占位符?进行替换，例如setObject(0,\u0026quot;'abc' OR '1' = '1'\u0026quot;) 最终SQL为SELECT user, password FROM user_table WHERE user='lucy' AND password = ''abc' OR '1' = '1'' PreparedStatement类和Statement的比较 # PreparedStatement，代码的可读性和可维护性更高\nPreparedStatement在使用时只需要编译一次，就可以运行多次，Statement每运行一次就编译一次，所以PreparedStatement的效率更高\nPreparedStatement 可以防止 SQL 注入\n如果拼接表名、列名、关键字，必须使用Statement，防止sql语句错误\nResultSet类 # 通过调用 PreparedStatement 对象的excuteQuery()方法创建该对象\n代表结果集\nResultSet 返回的实际上就是一张数据表，有一个指针指向数据表的第一条记录的前面。\nResultSetMetaData类 # 通过调用ResultSet对象的getMetaData()方法获取该对象 可用于获取关于 ResultSet 对象中列的类型和属性信息的对象 常用方法 # getColumnName(int column)：获取指定索引列的名称 getColumnLabel(int column)：获取指定索引列的别名 getColumnCount()：返回当前ResultSet列数 getColumnTypeName(int column)：获取指定索引列的数据库类型名称 getColumnDisplaySize(int column)：返回指定所有列最大标准宽度，单位字符 isNullable(int column)：返回指定列中的值是否可以为null isAutoIncrement(int column)：返回指定索引列是否自增 JDBC封装DAO # **DAO (Data Access objects 数据访问对象)**是指位于业务逻辑和持久化数据之间实现对持久化数据的访问。通俗来讲，就是将数据库操作都封装起来。能够是代码的结构更加清晰化。\nDAO模式组成 # DAO接口： 把对数据库的所有操作定义成抽象方法，可以提供多种实现。 DAO 实现类： 针对不同数据库给出DAO接口定义方法的具体实现。 实体类：用于存放与传输对象数据。 数据库连接和关闭工具类： 避免了数据库连接和关闭代码的重复使用，方便修改 ","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/fbeea4f4/8e5d8197/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e数据持久化 \n    \u003cdiv id=\"数据持久化\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%95%b0%e6%8d%ae%e6%8c%81%e4%b9%85%e5%8c%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e数据持久化就是\u003cstrong\u003e将内存中的数据模型转换为存储模型\u003c/strong\u003e，以及\u003cstrong\u003e将存储模型转换为内存中的数据模型\u003c/strong\u003e的统称。数据模型可以是任何数据结构或对象模型，存储模型可以是关系模型、XML、二进制流等。\u003c/p\u003e","title":"10、JDBC","type":"posts"},{"content":" JSON # JSON（JavaScript Object Notation，JS对象简谱）是一种轻量级的数据交换格式。它基于 ECMAScript（欧洲计算机协会制定的js规范）的一个子集，采用完全独立于编程语言的文本格式来存储和表示数据，但是也使用了类似于 C 语言家族的习惯（包括 C, C++, C#, Java, JavaScript, Perl, Python 等）。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。易于人阅读和编写，同时也易于机器解析和生成，并有效地提升网络传输效率。\nJSON的本质就是一个对象序列化后的字符串\nJSON广泛应用于Web开发和数据交换。它是一种通用的数据格式，常用于前后端之间的数据传输，如将服务器返回的数据转换为JSON格式在客户端进行处理。\n在前端，可以使用JavaScript解析和操作JSON数据。 在后端，可以使用各种编程语言和框架提供的JSON库进行JSON的解析和生成。 在Java中，常用的JSON库包括Jackson，fastjson等，它们提供了方便的API来解析、生成和操作JSON数据。\n语法 # JSON使用类似于JavaScript的语法规则，包括对象和数组的表示方式。\n{ \u0026#34;name\u0026#34;: \u0026#34;John\u0026#34;, \u0026#34;age\u0026#34;: 30, \u0026#34;isStudent\u0026#34;: true, \u0026#34;address\u0026#34;: { \u0026#34;street\u0026#34;: \u0026#34;123 Main St\u0026#34;, \u0026#34;city\u0026#34;: \u0026#34;New York\u0026#34; }, \u0026#34;hobbies\u0026#34;: [\u0026#34;reading\u0026#34;, \u0026#34;coding\u0026#34;, \u0026#34;traveling\u0026#34;] } 数据类型 # 字符串 # \u0026#34;Hello, World!\u0026#34; 数字 # 42 3.14 布尔 # true false 对象 # 由键值对组成的无序集合。\n{ \u0026#34;name\u0026#34;: \u0026#34;John\u0026#34;, \u0026#34;age\u0026#34;: 30 } 数组 # 由值组成的有序集合\n[\u0026#34;red\u0026#34;, \u0026#34;green\u0026#34;, \u0026#34;blue\u0026#34;] 空值 # null JavaScript解析JSON # // json字符串转js对象 var jsObj = JSON.parse(jsonStr) // js对象转json字符串 var jsonStr = JSON.stringify(jsObj) Jackson # Jackson是Java中一个流行的JSON处理库，它提供了一组功能强大的API，用于解析、生成和操作JSON数据，具有以下特点\n高性能：Jackson通过使用基于流的处理模型和有效的内部算法，提供了出色的性能。 灵活性：Jackson支持各种JSON处理方式，如树模型、流模型和数据绑定，以适应不同的使用场景。 可定制性：Jackson提供了丰富的配置选项和注解，可以根据需要进行自定义和扩展。 支持广泛：Jackson可以与Java的各种对象模型（包括POJO、Map、数组等）以及常见的数据格式（如XML）进行交互。 \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.fasterxml.jackson.core\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jackson-databind\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.9.6\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; Jackson 最常用的 API 就是基于对象绑定的 ObjectMapper\nObjectMapper可以从字符串，流或文件中解析JSON，并创建表示已解析的JSON的Java对象。 将JSON解析为Java对象也称为从JSON反序列化Java对象。 ObjectMapper也可以从Java对象创建JSON。 从Java对象生成JSON也称为将Java对象序列化为JSON。 Object映射器可以将JSON解析为自定义的类的对象，也可以解析置JSON树模型的对象。 默认情况下，Jackson通过将JSON字段的名称与Java对象中的Setter方法进行匹配，将JSON对象的字段映射到Java对象中的属性。 public class Stu { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } JSON转Object # String json = \u0026#34;{\\\u0026#34;name\\\u0026#34;: \\\u0026#34;lucy\\\u0026#34;,\\\u0026#34;age\\\u0026#34;: 18}\u0026#34;; ObjectMapper objectMapper = new ObjectMapper(); // 字符串转Object Stu s1 = objectMapper.readValue(json, Stu.class); System.out.println(s1); // 字符输入流转Object StringReader reader = new StringReader(json); Stu s2 = objectMapper.readValue(reader, Stu.class); System.out.println(s1); // json文件转Object File file = new File(\u0026#34;stu.json\u0026#34;); Stu s3 = objectMapper.readValue(file, Stu.class); System.out.println(s3); // url读取json转Object URL url = new URL(\u0026#34;http://localhost/stu.json\u0026#34;); Stu s4 = objectMapper.readValue(url, Stu.class); System.out.println(s4); // 字节输入流转Object InputStream inputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); Stu s5 = objectMapper.readValue(inputStream, Stu.class); System.out.println(s5); // 字节数组转Object byte[] bytes = json.getBytes(StandardCharsets.UTF_8); Stu s6 = objectMapper.readValue(bytes, Stu.class); System.out.println(s6); String jsonArray = \u0026#34;[\\\u0026#34;lucy\\\u0026#34;,\\\u0026#34;tom\\\u0026#34;,\\\u0026#34;grady\\\u0026#34;]\u0026#34;; // json转List List\u0026lt;String\u0026gt; list = objectMapper.readValue(jsonArray, new TypeReference\u0026lt;List\u0026lt;String\u0026gt;\u0026gt;() {}); System.out.println(list); // json转Map Map\u0026lt;String,Object\u0026gt; map = objectMapper.readValue(json,new TypeReference\u0026lt;Map\u0026lt;String,Object\u0026gt;\u0026gt;() {}); System.out.println(map); 忽略未知的JSON字段 # 有时候，与要从JSON读取的Java对象相比，JSON中的字段更多。 默认情况下，Jackson在这种情况下会抛出异常UnrecognizedPropertyException，因为在Java对象中找不到该字段。\n但是，有时应该允许JSON中的字段多于相应的Java对象中的字段。 例如，要从REST服务解析JSON，而该REST服务包含的数据远远超出所需的。 在这种情况下，可以使用Jackson配置忽略这些额外的字段。 以下是配置Jackson ObjectMapper忽略未知字段的示例：\nObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 不允许基本数据类型为null # 如果Java对象的某个属性为基本数据类型，但是对应的json中该属性为null，那么Jackson默认会忽略这个字段。我们可以通过配置，在基本属性值为null的情况下，抛出异常\nObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true); Object转JSON # ObjectMapper objectMapper = new ObjectMapper(); Stu stu = new Stu(); stu.setAge(18); stu.setName(\u0026#34;lucy\u0026#34;); String s = objectMapper.writeValueAsString(stu); System.out.println(s); 日期转化 # 默认情况下，Jackson会将java.util.Date对象序列化为其long型的值，该值是自1970年1月1日以来的毫秒数。\npublic class MyObj { private Date date; public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } } MyObj myObj = new MyObj(); myObj.setDate(new Date()); ObjectMapper objectMapper = new ObjectMapper(); // 序列化 String s = objectMapper.writeValueAsString(myObj); System.out.println(s); // {\u0026#34;date\u0026#34;:1689043359773} // 反序列化 MyObj mo = objectMapper.readValue(s, MyObj.class); System.out.println(mo); // MyObj{date=Tue Jul 11 10:43:35 CST 2023} 对于实体类如果需要序列化以及序列化使用自定义格式，那么可以使用@JsonFormat注解进行定义，该注解可以作用于方法、属性。\npublic class MyObj { @JsonFormat(pattern = \u0026#34;yyyy-MM-dd HH:mm:ss\u0026#34;,timezone = \u0026#34;Asia/Shanghai\u0026#34;) private Date date; public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } } MyObj myObj = new MyObj(); myObj.setDate(new Date()); ObjectMapper objectMapper = new ObjectMapper(); String s = objectMapper.writeValueAsString(myObj); System.out.println(s); // {\u0026#34;date\u0026#34;:\u0026#34;2023-07-11 10:49:02\u0026#34;} MyObj mo = objectMapper.readValue(s, MyObj.class); Date date = mo.getDate(); System.out.println(mo); // MyObj{date=Tue Jul 11 10:49:02 CST 2023} 自定义序列化器 # 例如自定义一个Double类型保留两位小数的序列化器\n//修改JsonSerializer\u0026lt;Double\u0026gt; 到需要的类型,默认为JsonSerializer，参数为Object value public class JsonSerializerUtils extends JsonSerializer\u0026lt;Double\u0026gt; { @Override public void serialize(Double value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { if (Objects.nonNull(value)) { //保留2位小数#代表末位是0舍去 DecimalFormat decimalFormat = new DecimalFormat(\u0026#34;0.##\u0026#34;); //四舍五入 decimalFormat.setRoundingMode(RoundingMode.HALF_UP); String result = decimalFormat.format(value); jsonGenerator.writeNumber(Double.valueOf(result)); } else { jsonGenerator.writeNumber(Double.valueOf(0)); } } } 在需要进行格式化的实体类属性上添加注解\n@JsonSerialize(using = JsonSerializerUtils.class) private Double waterPrice; 常用注解 # @JsonIgnore # 作用范围：序列化及反序列化\nJackson注解@JsonIgnore用于告诉Jackson忽略Java对象的某个属性（字段）。\npublic class Stu { private String name; @JsonIgnore private int age; } @JsonIgnoreProperties # 作用范围：序列化及反序列化\n用于指定要忽略的类的属性列表。\n@JsonIgnoreProperties({\u0026#34;id\u0026#34;,\u0026#34;age\u0026#34;}) public class Stu { private int id; private String name; private int age; } @JsonIgnoreType # 作用范围：序列化及反序列化\n用于将整个类型（类）标记为在使用该类型的任何地方都将被忽略。\n@JsonIgnoreType public class Stu { private String name; private int age; } @JsonAutoDetect # 用于告诉Jackson在读写对象时包括非public修饰的属性。\n@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY ) public class Stu { private String name; public int age; } Fastjson # \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.alibaba\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;fastjson\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.2.7\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 如果对象中属性为null，则不转化\nJAVAObject转JSON # String json = JSON.toJSONString(javaObject); JSON转JAVAObject # 需要该类具有无参构造器\nPerson newPerson = JSON.parseObject(jsonString, Person.class); List转JSONObject # JSONArray jsonArray = (JSONArray)JSON.toJSON(list); ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/fb007bea/9a74afd8/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJSON \n    \u003cdiv id=\"json\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#json\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eJSON（\u003cstrong\u003eJavaScript Object Notation\u003c/strong\u003e，JS对象简谱）是一种轻量级的数据交换格式。它基于 ECMAScript（欧洲计算机协会制定的js规范）的一个子集，采用完全独立于编程语言的文本格式来存储和表示数据，但是也使用了类似于 C 语言家族的习惯（包括 \u003ccode\u003eC\u003c/code\u003e, \u003ccode\u003eC++\u003c/code\u003e, \u003ccode\u003eC#\u003c/code\u003e, \u003ccode\u003eJava\u003c/code\u003e, \u003ccode\u003eJavaScript\u003c/code\u003e, \u003ccode\u003ePerl\u003c/code\u003e, \u003ccode\u003ePython\u003c/code\u003e 等）。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。易于人阅读和编写，同时也易于机器解析和生成，并有效地提升网络传输效率。\u003c/p\u003e","title":"10、JSON","type":"posts"},{"content":" VsCode开发插件 # Chinese (Simplified) Language Pack for Visual Studio Code：中文界面 auto rename tag code runner：可以运行nodejs、python等代码 open in brower：在默认浏览器打开 live server：web服务器 vetur：写vue必备插件 Vue 2 Snippets：写vue必备插件 axios：axios补全插件 VueHelper vscode：vue，vue-router和vuex的代码提示 Path Intellisense：路径自动补全 Auto Close Tag：自动闭合标签 Beautify：格式化文件,保证正确的缩进 HTML CSS Support： CSS提示插件 JavaScript (ES6) code snippets：es6代码提示插件 VSCode Great Icons：给文件夹增加图标的插件 NProgerss # 进度条插件，地址：https://github.com/rstacruz/nprogress\nnpm i nprogress --save 例如，每次路由切换显示进度条，在路由index.js中进行配置\n//顶部页面加载条 import NProgress from \u0026#39;nprogress\u0026#39;; import \u0026#39;nprogress/nprogress.css\u0026#39;; //配置 NProgress.configure({ easing: \u0026#39;ease\u0026#39;, //缓和模式 speed: 500, //速度 showSpinner: false, //显示右侧旋转 trickleSpeed: 200, minimum: 0.3 //最小值，也就是起始位置 }) //路由监听 router.beforeEach((to, from, next) =\u0026gt; { NProgress.start(); next(); }); //路由跳转结束 router.afterEach(() =\u0026gt; { NProgress.done() }) 在App.vue中进行样式配置\n/* 更改进度条颜色 */ #nprogress .bar { background: #f10180 !important; height: 3px !important; } 也可以绑定在Vue的原型对象上进行使用\nimport NProgress from \u0026#39;nprogress\u0026#39;; import \u0026#39;nprogress/nprogress.css\u0026#39;; //配置 NProgress.configure({ easing: \u0026#39;ease\u0026#39;, //缓和模式 speed: 500, //速度 showSpinner: false, //显示右侧旋转 trickleSpeed: 200, minimum: 0.3 //最小值，也就是起始位置 }) Vue.prototype.$NP = NProgress; api\n// 开启进度条 NProgress.start() — shows the progress bar // 引动到指定位置 NProgress.set(0.4) — sets a percentage // 前进一点点 NProgress.inc() — increments by a little // 跑完 NProgress.done() — completes the progress prerender-spa-plugin # 如果服务器端渲染 (SSR) 只是用来改善少数营销页面（例如 /, /about, /contact 等）的 SEO，那么你可能需要预渲染。无需使用 web 服务器实时动态编译 HTML，而是使用预渲染方式，在构建时 (build time) 简单地生成针对特定路由的静态 HTML 文件。优点是设置预渲染更简单，并可以将你的前端作为一个完全静态的站点。\n预渲染页面方式由于不需要web服务器的参与，设置比SSR更简单，特别适合用来展示一些静态页面，比如根据页面UI来自动生成骨架屏。\n1、安装，webpack4和webpack5不一样，原生的只支持4，所以如果是5的话，就需要使用第三方重写的\n# 对于webpack4 npm i prerender-spa-plugin --save-dev # 对于webpack5 npm i @dreysolano/prerender-spa-plugin --save-dev 2、vue.config.js中进行配置\nconst { defineConfig } = require(\u0026#39;@vue/cli-service\u0026#39;) //1.引入path： const path = require(\u0026#39;path\u0026#39;); //2.封装方法： function resolve(dir){ return path.resolve(__dirname,dir) } // 引入预渲染插件 // 如果是webpack4 // const PrerenderSPAPlugin = require(\u0026#39;prerender-spa-plugin\u0026#39;); // 如果是webpack5 const PrerenderSPAPlugin = require(\u0026#39;@dreysolano/prerender-spa-plugin\u0026#39;); const Renderer = PrerenderSPAPlugin.PuppeteerRenderer; module.exports = defineConfig({ configureWebpack: (config) =\u0026gt; { // 开发生产共同配置别名 Object.assign(config.resolve, { alias: { \u0026#39;@\u0026#39;: resolve(\u0026#39;src\u0026#39;), \u0026#39;@assets\u0026#39;: resolve(\u0026#39;src/assets\u0026#39;), \u0026#39;@views\u0026#39;: resolve(\u0026#39;src/views\u0026#39;), \u0026#39;@components\u0026#39;: resolve(\u0026#39;src/components\u0026#39;), } }); // 设置为在打包环境下运行 if (process.env.NODE_ENV !== \u0026#39;production\u0026#39;) return; return { // 主要部分 plugins: [ new PrerenderSPAPlugin({ staticDir: path.join(__dirname, \u0026#39;dist\u0026#39;), // 需要预渲染的路径，此处的路径不可以使用懒加载 routes: [\u0026#39;/home\u0026#39;,\u0026#39;/about\u0026#39;], // 这个很重要，如果没有配置这段，也不会进行预编译 renderer: new Renderer({ inject: { foo: \u0026#39;bar\u0026#39; }, headless: false, //为false会开启浏览器调试 renderAfterTime: 5000, // 在 main.js 中 document.dispatchEvent(new Event(\u0026#39;render-event\u0026#39;))，两者的事件名称要对应上。 renderAfterDocumentEvent: \u0026#39;render-event\u0026#39; }) }) ] } }, }) 3、main.js中挂载事件\nnew Vue({ router, store, render: h =\u0026gt; h(App), // 挂载预渲染事件 mounted() { document.dispatchEvent(new Event(\u0026#39;render-event\u0026#39;)) } }).$mount(\u0026#39;#app\u0026#39;) 4、执行命令npm run build，dist目录下就可以看到路径对应的静态页面\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/bafd68f1/b2321cb9/b90b7723/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eVsCode开发插件 \n    \u003cdiv id=\"vscode开发插件\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#vscode%e5%bc%80%e5%8f%91%e6%8f%92%e4%bb%b6\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eChinese (Simplified) Language Pack for Visual Studio Code：中文界面\u003c/li\u003e\n\u003cli\u003eauto rename tag\u003c/li\u003e\n\u003cli\u003ecode runner：可以运行nodejs、python等代码\u003c/li\u003e\n\u003cli\u003eopen in brower：在默认浏览器打开\u003c/li\u003e\n\u003cli\u003elive server：web服务器\u003c/li\u003e\n\u003cli\u003evetur：写vue必备插件\u003c/li\u003e\n\u003cli\u003eVue 2 Snippets：写vue必备插件\u003c/li\u003e\n\u003cli\u003eaxios：axios补全插件\u003c/li\u003e\n\u003cli\u003eVueHelper vscode：vue，vue-router和vuex的代码提示\u003c/li\u003e\n\u003cli\u003ePath Intellisense：路径自动补全\u003c/li\u003e\n\u003cli\u003eAuto Close Tag：自动闭合标签\u003c/li\u003e\n\u003cli\u003eBeautify：格式化文件,保证正确的缩进\u003c/li\u003e\n\u003cli\u003eHTML CSS Support： CSS提示插件\u003c/li\u003e\n\u003cli\u003eJavaScript (ES6) code snippets：es6代码提示插件\u003c/li\u003e\n\u003cli\u003eVSCode Great Icons：给文件夹增加图标的插件\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eNProgerss \n    \u003cdiv id=\"nprogerss\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#nprogerss\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e进度条插件，地址：https://github.com/rstacruz/nprogress\u003c/p\u003e","title":"10、常用插件","type":"posts"},{"content":" Vue3 # 相比vue2的提升 # 性能提升 打包大小减少41% 初次渲染快55%，更新渲染快133% 内存减少43% 源码升级 使用Proxy代替defineProperty实现响应式 重写虚拟DOM的实现和Tree-Shaking 支持TypeScript 可以更好的支持TypeScript 新特性 Composition API 新的内置组件 Vite创建Vue3工程 # Vue3 不推荐使用 vue-cli 来创建（使用vue/cli，要确保版本大于4.5.0），而是使用 Vite。\nVite是新一代的前端构建工具，在尤雨溪开发Vue3.0的时候诞生。类似于Webpack+ Webpack-dev-server。\n其主要利用浏览器ESM特性导入组织代码，在服务器端按需编译返回，完全跳过了打包这个概念，服务器随起随用。\n生产中利用Rollup作为打包工具，号称下一代的前端构建工具。\nnpm create vite@latest 注意 Node 的版本，如果过低可能会报错。\n安装依赖 # 默认创建的工程中，没有 vue-router 等依赖。\nvue-router # npm install vue-router 然后在 src 目录下创建 router/router.js，并声明自己定义的views\nimport { createRouter, createWebHistory } from \u0026#34;vue-router\u0026#34;; const routes = [ { path: \u0026#34;/\u0026#34;, component: () =\u0026gt; import(\u0026#34;@/views/Index.vue\u0026#34;), }, { path: \u0026#34;/about\u0026#34;, component: () =\u0026gt; import(\u0026#34;@/views/About.vue\u0026#34;), }, ]; export const router = createRouter({ history: createWebHistory(), routes, }); 在 main.js 中引入 router\nimport { createApp } from \u0026#34;vue\u0026#34;; import \u0026#34;./style.css\u0026#34;; import App from \u0026#34;./App.vue\u0026#34;; import { router } from \u0026#34;./router/index\u0026#34;; createApp(App) .use(router) .mount(\u0026#34;#app\u0026#34;); sass # npm i sass sass-loader --save-dev Vue3的变化 # main.js # // 不再是引入Vue构造函数，而是引入createApp工厂函数 import { createApp } from \u0026#39;vue\u0026#39; import App from \u0026#39;./App.vue\u0026#39; //创建应用实例对象，类似于Vue实例对象，但是比Vue实例对象更轻 createApp(App).mount(\u0026#39;#app\u0026#39;) App.vue # \u0026lt;!-- vue3中的组件模板可以没有根标签 --\u0026gt; \u0026lt;template\u0026gt; \u0026lt;img alt=\u0026#34;Vue logo\u0026#34; src=\u0026#34;./assets/logo.png\u0026#34;\u0026gt; \u0026lt;HelloWorld msg=\u0026#34;Welcome to Your Vue.js App\u0026#34;/\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import HelloWorld from \u0026#39;./components/HelloWorld.vue\u0026#39; export default { name: \u0026#39;App\u0026#39;, components: { HelloWorld } } \u0026lt;/script\u0026gt; \u0026lt;style\u0026gt; \u0026lt;/style\u0026gt; Composition API # setup # vue3中的一个新的组件配置项，值是一个函数，setup是所有Composition API表演的舞台\n组件中用到的所有数据、方法、计算属性，全部要配置在setup中\nsetup函数的返回值 返回对象：对象的属性、方法在模板中可以直接使用 返回渲染函数：从vue中引入h，返回h渲染的结果 \u0026lt;template\u0026gt; \u0026lt;h1\u0026gt;name：{{name}}\u0026lt;/h1\u0026gt; \u0026lt;h1\u0026gt;age：{{age}}\u0026lt;/h1\u0026gt; \u0026lt;button @click=\u0026#34;show\u0026#34;\u0026gt;show\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#39;App\u0026#39;, setup(){ //数据 let name = \u0026#34;lucy\u0026#34;; let age = 18; //方法 function show(){ alert(\u0026#34;姓名：\u0026#34; + name + \u0026#34;，年龄：\u0026#34; + age); } return {name,age,show} } } \u0026lt;/script\u0026gt; 注意：尽量不要和vue2配置混用（vue2配置可以读取setup的属性和方法，但是setup不能访问vue2配置的）；setup不能使用async修饰\nsetup函数的执行时机 # 在beforeCreate之前执行一次，this是undefined，也就是说，setup中不要写this\nref函数 # 对于在setup函数中返回的属性，虽然组件可以直接使用，但是不是响应式（页面随着数据进行改变）的，如果需要该属性是响应式的，也就是需要vue监测该属性，那么需要使用vue提供的ref函数\nref函数，接受的数据可以是基本类型也可以是对象类型\n对于基本类型：底层依靠的是defineProperty()的get()和set()实现\n对于对象类型：底层利用vue3的一个新函数reactive()实现\n\u0026lt;template\u0026gt; \u0026lt;h1\u0026gt;name：{{name}}\u0026lt;/h1\u0026gt; \u0026lt;h1\u0026gt;age：{{age}}\u0026lt;/h1\u0026gt; \u0026lt;button @click=\u0026#34;ageAdd10\u0026#34;\u0026gt;ageAdd10\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; //引入ref函数 import {ref} from \u0026#39;vue\u0026#39;; export default { name: \u0026#39;App\u0026#39;, setup(){ let name = \u0026#34;lucy\u0026#34;; //需要响应式的属性使用ref函数，生成引用实现对象 let age = ref(18); //方法 function ageAdd10(){ //修改值的时候需要修改引用实现对象的value属性 age.value += 10; } return {name,age,ageAdd10} } } \u0026lt;/script\u0026gt; reactive函数 # 和ref作用、用法都一样，定义响应式数据，通常用来定义对象类型数据，虽然ref也可以定义对象数据类型，但是底层调用的还是reactive\ncomputed函数 # 用于在setup中定义计算属性\n\u0026lt;template\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; v-model=\u0026#34;name\u0026#34;\u0026gt; \u0026lt;h1\u0026gt;{{sayHello}}\u0026lt;/h1\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import {ref,computed} from \u0026#39;vue\u0026#39;; export default { name: \u0026#39;App\u0026#39;, setup(){ let name = ref(\u0026#34;lucy\u0026#34;); let sayHello = computed(() =\u0026gt; { return \u0026#34;Hello \u0026#34; + name.value; }); return {name,sayHello} } } \u0026lt;/script\u0026gt; watch函数 # 用于在setup中定义监视属性\n注意：可以同时监视多个变量，只需要多个变量放到一个数组，传到第一个参数即可\n\u0026lt;template\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; v-model=\u0026#34;name\u0026#34;\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import {ref,watch} from \u0026#39;vue\u0026#39;; export default { name: \u0026#39;App\u0026#39;, setup(){ let name = ref(\u0026#34;lucy\u0026#34;); watch(name,(after,before)=\u0026gt;{ console.log(before,after); }) watch(name,(after,before)=\u0026gt;{ console.log(before,after); },{immediate: true}) //如果使用immediate:true，那么回调会在组件创建时调用一次 return {name} } } \u0026lt;/script\u0026gt; watchEffect函数 # 这个函数的参数是一个回调函数（无参），可以智能的监视这个回调函数里面使用的数据，如果数据发生变化，就会调用该回调，并且在组件创建时调用一次\n\u0026lt;template\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; v-model=\u0026#34;name\u0026#34;\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import {ref,watchEffect} from \u0026#39;vue\u0026#39;; export default { name: \u0026#39;App\u0026#39;, setup(){ let name = ref(\u0026#34;lucy\u0026#34;); watchEffect(()=\u0026gt;{ console.log(before,after); }) return {name} } } \u0026lt;/script\u0026gt; toRef函数 # 创建一个ref对象，其value值指向另一个响应式对象中的某个属性，这个value也是响应式的\nsetup(){ let student = reactive({ name: \u0026#39;lucy\u0026#39;, age: 18, clazz: { level: 9 } }) let name = toRef(student,\u0026#39;name\u0026#39;); let level = toRef(student.clazz,\u0026#39;level\u0026#39;); return {name} } toRefs函数 # 可以将一个响应式对象中的所有属性（第一层属性）都变成响应式对象，也就是不会将下面level变成响应式\nsetup(){ let student = reactive({ name: \u0026#39;lucy\u0026#39;, age: 18, clazz: { level: 9 } }) let stu = toRefs(student); return {name} } toRaw函数 # 可以将一个响应式对象转换为一个普通对象并返回\nvue3没有this # 1、由于vue3中setup函数的执行时机要比beforeCreate早，所以在setup中无法拿到this\n2、由于vue3不是使用Vue构造进行构建，而是使用App，所以如果要挂载一些属性到Vue原型对象上，方法和vue2不同\n例如挂载axios\n// 引入axios import axios from \u0026#39;axios\u0026#39;; const app = createApp(App); //挂载全局属性 app.config.globalProperties.$axios = axios; app.use(router).mount(\u0026#34;#app\u0026#34;); 在组件中使用\nsetup(){ const {proxy} = getCurrentInstance(); proxy.$axios.get(); } vue3声明周期 # 和vue2相比，有两个钩子名称发生了变化 vue2中的beforeDestroy，在vue3中是beforeUnmount vue2中的destroyed，在vue3中是unmouted Hook # Hook的本质就是一个函数，是对setup函数里面的组合式API进行封装，类似于Vue2中的mixin\n在vue3的组合式API中，对于声明周期钩子，也提供了对应的函数 选项式 API Hook inside setup beforeCreate setup created setup beforeMount onBeforeMount mounted onMounted beforeUpdate onBeforeUpdate updated onUpdated beforeUnmount onBeforeUnmount unmounted onUnmounted errorCaptured onErrorCaptured renderTracked onRenderTracked renderTriggered onRenderTriggered activated onActivated deactivated onDeactivated import {onMounted} from \u0026#39;vue\u0026#39; \u0026lt;script\u0026gt; export default { name: \u0026#39;Demo\u0026#39;, setup() { // 可以在setup中直接写 onMounted(() =\u0026gt; { }) } } \u0026lt;/script\u0026gt; 自定义hook # 在src/hooks/中新建一个js文件，将多组件复用的组合式API抽离，并将组件需要的属性返回即可\nimport {ref} from \u0026#39;vue\u0026#39;; export default () =\u0026gt; { //此处写组合式API的逻辑 let name = ref(\u0026#39;lucy\u0026#39;); return name; } 在需要该组合API的组件中引入并调用，一般函数名以use开头\nimport useName from \u0026#39;@/hooks/name.js\u0026#39;; \u0026lt;script\u0026gt; export default { name: \u0026#39;Demo\u0026#39;, setup() { // 在setup中调用，并接收参数 let name = useName(); return {name} } } \u0026lt;/script\u0026gt; 新增组件 # Fragment # 在vue2中，所有组件必须有跟标签\n在vue3中，组件可以没有跟标签，所有元素在Fragment虚拟元素中\nTeleport # 可以将指定的html结构移动到指定位置\n\u0026lt;template\u0026gt; \u0026lt;!-- 传送到body标签中 --\u0026gt; \u0026lt;teleport to=\u0026#34;body\u0026#34;\u0026gt; \u0026lt;input/\u0026gt; \u0026lt;/teleport\u0026gt; \u0026lt;/template\u0026gt; 全局API变化 # vue2 vue3 Vue.config.productionTip 移除 Vue.config.ignoredElements app.config.isCustomElement() Vue.component app.component Vue.directive app.directive Vue.mixin app.mixin Vue.use app.use() Vue.filter 移除 Vue.prototype app.config.globalProperties 其他变化 # 1、移除v-on.native\n2、由于vue3中setup没有this，所以获取ref的方法有所改变\n\u0026lt;template\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; ref=\u0026#34;inputRef\u0026#34;\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import { onMounted, ref } from \u0026#39;vue\u0026#39; /* ref获取元素: 利用ref函数获取组件中的标签元素 功能需求: 让输入框自动获取焦点 */ export default { setup() { const inputRef = ref(null) onMounted(() =\u0026gt; { inputRef.value.focus() }) return { inputRef } }, } \u0026lt;/script\u0026gt; Vite 配置 # vite使用的是vite.config.js，下面是 vue.config.js 中常用的配置项在vite.config.js中的写法。\nimport { defineConfig } from \u0026#39;vite\u0026#39; import vue from \u0026#39;@vitejs/plugin-vue\u0026#39; import path from \u0026#39;path\u0026#39; // 1. 路径别名配置（对应原Webpack的resolve.alias） const resolve = (dir) =\u0026gt; path.resolve(__dirname, dir) // 2. 代理配置函数（更清晰的Vite风格） const createProxy = (prefix, target) =\u0026gt; ({ [prefix]: { target, changeOrigin: true, configure: (proxy, _options) =\u0026gt; { proxy.on(\u0026#39;proxyReq\u0026#39;, (proxyReq) =\u0026gt; { if (proxyReq.getHeader(\u0026#39;origin\u0026#39;)) { proxyReq.setHeader(\u0026#39;origin\u0026#39;, target) } }) }, rewrite: (path) =\u0026gt; path.replace(new RegExp(`^${prefix}`), \u0026#39;\u0026#39;) } }) export default defineConfig({ plugins: [vue()], base: \u0026#34;./\u0026#34;, // 3. 路径别名配置 resolve: { alias: { \u0026#39;@\u0026#39;: resolve(\u0026#39;src\u0026#39;), \u0026#39;@assets\u0026#39;: resolve(\u0026#39;src/assets\u0026#39;), \u0026#39;@components\u0026#39;: resolve(\u0026#39;src/components\u0026#39;), \u0026#39;@views\u0026#39;: resolve(\u0026#39;src/views\u0026#39;), \u0026#39;@api\u0026#39;: resolve(\u0026#39;src/api\u0026#39;), \u0026#39;@util\u0026#39;: resolve(\u0026#39;src/util\u0026#39;), \u0026#39;@store\u0026#39;: resolve(\u0026#39;src/store\u0026#39;) } }, // 4. 开发服务器配置 server: { open: false, // 保持原配置 host: \u0026#39;0.0.0.0\u0026#39;, // 保持原配置 port: 8081, // 保持原配置 proxy: { ...createProxy(\u0026#39;/apis\u0026#39;, \u0026#39;http://localhost:8080/\u0026#39;), ...createProxy(\u0026#39;/assets\u0026#39;, \u0026#39;http://localhost:19000/\u0026#39;) } }, // 5. 生产环境配置 build: { sourcemap: false, // 对应原 productionSourceMap: false }, // 6. 其他优化配置（可选） esbuild: { // 如果需要关闭ESLint（原lintOnSave: false） // 需要安装 vite-plugin-eslint 插件并配置 } }) 解决路径别名爆红 # 在tsconfig.json文件中，添加\n{ \u0026#34;compilerOptions\u0026#34;: { \u0026#34;paths\u0026#34;: { \u0026#34;@/*\u0026#34;: [\u0026#34;src/*\u0026#34;], \u0026#34;@assets/*\u0026#34;: [\u0026#34;src/assets/*\u0026#34;], \u0026#34;@components/*\u0026#34;: [\u0026#34;src/components/*\u0026#34;], \u0026#34;@views/*\u0026#34;: [\u0026#34;src/views/*\u0026#34;], \u0026#34;@api/*\u0026#34;: [\u0026#34;src/api/*\u0026#34;], \u0026#34;@util/*\u0026#34;: [\u0026#34;src/util/*\u0026#34;], \u0026#34;@store/*\u0026#34;: [\u0026#34;src/store/*\u0026#34;], }, } ","date":"2025-07-21","externalUrl":null,"permalink":"/posts/bafd68f1/b2321cb9/38552f6d/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eVue3 \n    \u003cdiv id=\"vue3\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#vue3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e相比vue2的提升 \n    \u003cdiv id=\"相比vue2的提升\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%9b%b8%e6%af%94vue2%e7%9a%84%e6%8f%90%e5%8d%87\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e性能提升\n\u003cul\u003e\n\u003cli\u003e打包大小减少41%\u003c/li\u003e\n\u003cli\u003e初次渲染快55%，更新渲染快133%\u003c/li\u003e\n\u003cli\u003e内存减少43%\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e源码升级\n\u003cul\u003e\n\u003cli\u003e使用Proxy代替defineProperty实现响应式\u003c/li\u003e\n\u003cli\u003e重写虚拟DOM的实现和Tree-Shaking\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e支持TypeScript\n\u003cul\u003e\n\u003cli\u003e可以更好的支持TypeScript\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e新特性\n\u003cul\u003e\n\u003cli\u003eComposition API\u003c/li\u003e\n\u003cli\u003e新的内置组件\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003eVite创建Vue3工程 \n    \u003cdiv id=\"vite创建vue3工程\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#vite%e5%88%9b%e5%bb%bavue3%e5%b7%a5%e7%a8%8b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eVue3 不推荐使用 \u003ccode\u003evue-cli\u003c/code\u003e 来创建（使用\u003ccode\u003evue/cli\u003c/code\u003e，要确保版本大于\u003ccode\u003e4.5.0\u003c/code\u003e），而是使用 Vite。\u003c/p\u003e","title":"11、Vue3","type":"posts"},{"content":" pkg/errors # github.com/pkg/errors 是 Go 语言中一个广泛使用的错误处理库，它通过**错误包装（Error Wrapping）和堆栈追踪（Stack Trace）**功能，显著增强了错误信息的可读性和调试效率。虽然不是 Go 官方包，但却被很多团队当作事实标准来使用。\nGo 语言的错误处理非常简单，秉承着大道至简风格。不过也正是由于简单，也就暴露了 Go 错误处理的些许简陋。\n在日常开发中，我们对于错误处理会有两个最普遍的诉求：\n附加错误信息：在拿到原有的底层代码或第三方库返回的错误后，我们可能希望附加一些业务信息，比如 userID，这样就知道这条错误是由哪个用户产生的。 附加错误堆栈：因为错误堆栈中有出错代码的位置，以及整个调用链路，这会方便我们定位问题。 功能概览 # 错误包装：将底层错误包裹在新错误中，保留原始错误链。 堆栈追踪：自动捕获错误发生时的调用堆栈，精准定位问题源头。 因果链提取：通过 errors.Cause() 穿透包装层，直接获取原始错误。 格式化输出：支持自定义错误消息和堆栈打印格式。 安装 # go get -u github.com/pkg/errors 基础用法 # 创建错误 # import \u0026#34;github.com/pkg/errors\u0026#34; // 基础错误 err := errors.New(\u0026#34;database connection failed\u0026#34;) // 格式化错误（类似 fmt.Errorf） err = errors.Errorf(\u0026#34;invalid input: %s\u0026#34;, \u0026#34;null\u0026#34;) 包装错误 # 使用场景：底层错误（如文件读取）被上层业务逻辑包裹\n// 包裹错误并附加上下文 func readConfig(path string) error { data, err := os.ReadFile(path) if err != nil { return errors.Wrapf(err, \u0026#34;failed to read config file %q\u0026#34;, path) } // ... } 提取原始错误 # func process() error { err := readConfig(\u0026#34;/invalid/path\u0026#34;) if err != nil { return errors.Wrap(err, \u0026#34;processing failed\u0026#34;) } return nil } func main() { err := process() if err != nil { // 穿透所有包装层，获取最底层的原始错误 cause := errors.Cause(err) fmt.Println(\u0026#34;Root cause:\u0026#34;, cause) // 输出: failed to open /invalid/path: no such file... } } 自定义错误消息与堆栈 # // 附加堆栈信息（通常由 Wrap/Wrapf 自动处理） err := errors.WithStack(err) // 附加自定义消息（不覆盖原始错误） err = errors.WithMessage(err, \u0026#34;permission denied\u0026#34;) 高级特性 # 堆栈打印格式化 # // 打印完整错误链和堆栈 fmt.Printf(\u0026#34;%+v\\n\u0026#34;, err) // 输出示例： // main.process // /project/main.go:20 // main.readConfig // /project/config.go:12 // os.Open // /usr/local/go/src/os/os.go:345 // ... 类型断言与自定义错误 # // 定义自定义错误类型 type MyError struct { Code int Message string } func (e *MyError) Error() string { return fmt.Sprintf(\u0026#34;[%d] %s\u0026#34;, e.Code, e.Message) } // 包装自定义错误 func apiCall() error { return errors.Wrap(\u0026amp;MyError{Code: 500, Message: \u0026#34;API error\u0026#34;}, \u0026#34;request failed\u0026#34;) } // 使用时类型断言 if err := apiCall(); err != nil { if myErr, ok := errors.Cause(err).(*MyError); ok { fmt.Println(\u0026#34;Error Code:\u0026#34;, myErr.Code) } } 最佳实践 # 分层包装错误：\n底层库：使用 errors.Wrap 包裹系统错误（如 os.Open）。\n业务层：用 errors.Wrapf 添加业务上下文（如参数值）。\n顶层入口：最终通过 errors.Cause 提取根因。\n避免过度包装：\n不要在循环中重复包装相同错误，会导致错误链爆炸。\n仅在关键路径添加有价值的上下文。\n敏感信息过滤：\n包装错误时避免包含密码、密钥等敏感数据。\n可通过 errors.WithMessagef 动态过滤：\nerrors.WithMessagef(err, \u0026#34;user=%s\u0026#34;, maskSensitiveData(userID)) ","date":"2025-06-27","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/29ede47b/54b32700/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003epkg/errors \n    \u003cdiv id=\"pkgerrors\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#pkgerrors\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003egithub.com/pkg/errors\u003c/code\u003e 是 Go 语言中一个广泛使用的错误处理库，它通过**错误包装（Error Wrapping）\u003cstrong\u003e和\u003c/strong\u003e堆栈追踪（Stack Trace）**功能，显著增强了错误信息的可读性和调试效率。虽然不是 Go 官方包，但却被很多团队当作事实标准来使用。\u003c/p\u003e","title":"11、errors","type":"posts"},{"content":" regexp # Go 语言的 regexp 包提供了对正则表达式的支持。\nregexp 包支持对字符串的匹配、搜索和替换。它基于 RE2 正则表达式引擎，性能优异，避免了回溯带来的性能瓶颈。该包提供了多个函数来编译正则表达式并进行匹配、查找、替换等操作。\n以下是Go语言regexp包的具体操作步骤：\n使用regexp.Compile或者regexp.MustCompile函数编译正则表达式，返回一个Regexp类型的对象。 使用Regexp对象的Match、Find、FindAllString、FindAllStringSubmatch、Replace和Split方法来实现正则表达式的匹配、替换和分割操作。 常用函数 # 编译正则表达式 # Compile # regexp.Compile(pattern string) (*Regexp, error) 该函数用于编译正则表达式字符串 pattern，并返回一个 Regexp 类型对象。如果正则表达式无效，会返回一个错误。\nr, err := regexp.Compile(`\\d+`) // 匹配一个或多个数字 if err != nil { fmt.Println(\u0026#34;Error compiling regex:\u0026#34;, err) return } MustCompile # regexp.MustCompile(pattern string) *Regexp 该函数与 Compile 类似，不过如果正则表达式不合法，它会立即 panic。适合在启动时使用，确保正则表达式是合法的。\nr := regexp.MustCompile(`\\d+`) 匹配操作 # 简单匹配 # matched, _ := regexp.MatchString(`^Hello`, \u0026#34;Hello World\u0026#34;) // true MatchString # r.MatchString(s string) bool 该方法检查给定的字符串 s 是否匹配正则表达式。如果匹配，返回 true，否则返回 false。\nr := regexp.MustCompile(`\\d+`) fmt.Println(r.MatchString(\u0026#34;12345\u0026#34;)) // 输出：true fmt.Println(r.MatchString(\u0026#34;abc\u0026#34;)) // 输出：false 查找操作 # FindString # r.FindString(s string) string 该方法查找字符串中第一个匹配正则表达式的子字符串并返回。如果没有找到匹配项，则返回空字符串。\nr := regexp.MustCompile(`\\d+`) fmt.Println(r.FindString(\u0026#34;abc 12345 xyz\u0026#34;)) // 输出：12345 FindAllString # r.FindAllString(s string, n int) []string 查找字符串中所有匹配正则表达式的子字符串，并返回一个字符串切片。如果 n 为 -1，则表示返回所有匹配的子串。\nr := regexp.MustCompile(`\\d+`) fmt.Println(r.FindAllString(\u0026#34;abc 123 4567 89\u0026#34;, -1)) // 输出：[123 4567 89] FindStringSubmatch # r.FindStringSubmatch(s string) []string 查找第一个匹配的子字符串，并返回每个捕获组的内容（包括完整匹配）。\nr := regexp.MustCompile(`(\\d+)-(\\d+)`) match := r.FindStringSubmatch(\u0026#34;abc 123-4567\u0026#34;) fmt.Println(match) // 输出：[[123-4567 123 4567]] 替换操作 # ReplaceAllString # r.ReplaceAllString(s string, repl string) string 该方法用于替换字符串中所有匹配正则表达式的子字符串。repl 是替换的文本。\nr := regexp.MustCompile(`\\d+`) result := r.ReplaceAllString(\u0026#34;abc 123 4567 xyz\u0026#34;, \u0026#34;#\u0026#34;) fmt.Println(result) // 输出：abc # # xyz 分割操作 # Split # r.Split(s string, n int) []string 根据正则表达式分割字符串，返回一个字符串切片。n 参数表示最多分割的次数。\nr := regexp.MustCompile(`\\s+`) fmt.Println(r.Split(\u0026#34;this is a test\u0026#34;, -1)) // 输出：[this is a test] ","date":"2025-05-22","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/12d682f6/261417ca/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eregexp \n    \u003cdiv id=\"regexp\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#regexp\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eGo 语言的 \u003ccode\u003eregexp\u003c/code\u003e 包提供了对正则表达式的支持。\u003c/p\u003e","title":"11、regexp","type":"posts"},{"content":"对于任何⼀⻔图形界⾯编程来说，布局都是⾮常重要的⼀关，它的英⽂翻译叫做“layout”。不管是MFC、Java、还是Qt等图形界⾯编程， 都会有有布局的相关知识。 Python 的 Tkinter 也⼀样。\nTkinter的三种布局： pack 布局：其实我们已经接触过 tkinter 的⼀种布局，就是 pack 布局，它⾮常简单，我们不⽤做过多的设置，直接使⽤⼀个 pack 函数就可以了。 grid 布局：grid 可以理解为⽹格，或者表格，它可以把 界⾯设置为⼏⾏⼏列的⽹格，我们在⽹格⾥插⼊我们想要的元素。这种布局的好处是不管我们如何拖动窗⼝，相对位置是不会变化的，⽽且这种布局也超简单。 place 布局：它直接使⽤死板的位置坐标来布局，这样做的最⼤的问题在于当我们向窗⼝添加⼀个新部件的时候，⼜得重新测⼀遍数据，且我们不能随便地变⼤或者缩⼩窗⼝，否则可能会导致混乱。 注意： pack 和 grid 不能同时⽤。⽽且通常对于较为复杂点的界⾯, 还是建议⼤家⽤gird；如果布局相对简单，使⽤pack 也很不错。\npack 布局 # 我们使⽤ pack 函数的时候，默认先使⽤的放到上⾯，然后依次向下排，它会给我们的组件⼀个⾃认为合适的位置和⼤⼩，这是默认⽅式，也是我们上⾯⼀直采⽤的⽅式。\n选项 含义 anchor 1. 控制组件在 pack 分配的空间中的位置 ；2. \u0026ldquo;n\u0026rdquo;, \u0026ldquo;ne\u0026rdquo;, \u0026ldquo;e\u0026rdquo;, \u0026ldquo;se\u0026rdquo;, \u0026ldquo;s\u0026rdquo;, \u0026ldquo;sw\u0026rdquo;, \u0026ldquo;w\u0026rdquo;, \u0026ldquo;nw\u0026rdquo;, 或者 \u0026ldquo;center\u0026rdquo; 来定位（ewsn 代表东西南北，上北下南左西右东）； 3. 默认值是 \u0026ldquo;center\u0026rdquo; expand 1. 指定是否填充父组件的额外空间 2. 默认值是 False fill 1. 指定填充 pack 分配的空间 ；2. 默认值是 NONE，表示保持子组件的原始尺寸 ；3. 还可以使用的值有：\u0026ldquo;x\u0026rdquo;（水平填充），\u0026ldquo;y\u0026rdquo;（垂直填充）和 \u0026ldquo;both\u0026rdquo;（水平和垂直填充） in_ 1. 将该组件放到该选项指定的组件中 ；2. 指定的组件必须是该组件的父组件 ipadx 指定水平方向上的内边距 ipady 指定垂直方向上的内边距 padx 指定水平方向上的外边距 pady 指定垂直方向上的外边距 side 1. 指定组件的放置位置；2. 默认值是 \u0026ldquo;top\u0026rdquo; ；3. 还可以设置的值有：\u0026ldquo;left\u0026rdquo;，\u0026ldquo;bottom\u0026rdquo;，\u0026ldquo;right\u0026rdquo; grid 布局 # 由于我们的程序⼤多数都是矩形，因此特别适合于⽹格布局，也就是 grid 布局。\n选项 含义 column 1. 指定组件插入的列（0 表示第 1 列） ；2. 默认值是 0 columnspan 指定用多少列（跨列）显示该组件 in_ 1. 将该组件放到该选项指定的组件中 ；2. 指定的组件必须是该组件的父组件 ipadx 指定水平方向上的内边距 ipady 指定垂直方向上的内边距 padx 指定水平方向上的外边距 pady 指定垂直方向上的外边距 row 指定组件插入的行（0 表示第 1 行） rowspan 指定用多少行（跨行）显示该组件 sticky 1. 控制组件在 grid 分配的空间中的位置； 2. 可以使用 \u0026ldquo;n\u0026rdquo;, \u0026ldquo;e\u0026rdquo;, \u0026ldquo;s\u0026rdquo;, \u0026ldquo;w\u0026rdquo; 以及它们的组合来定位（ewsn代表东西南北，上北下南左西右东）；3.使用加号（+）表示拉长填充，例如 \u0026ldquo;n\u0026rdquo; + \u0026ldquo;s\u0026rdquo; 表示将组件垂直拉长填充网格，\u0026ldquo;n\u0026rdquo; + \u0026ldquo;s\u0026rdquo; + \u0026ldquo;w\u0026rdquo; + \u0026ldquo;e\u0026rdquo; 表示填充整个网格；4.不指定该值则居中显示 ","date":"2025-04-15","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/a879fe06/53b3809a/","section":"文章","summary":"\u003cp\u003e对于任何⼀⻔图形界⾯编程来说，布局都是⾮常重要的⼀关，它的英⽂翻译叫做“layout”。不管是MFC、Java、还是Qt等图形界⾯编程， 都会有有布局的相关知识。 Python 的 Tkinter 也⼀样。\u003c/p\u003e","title":"11、布局layout","type":"posts"},{"content":" 异常处理 # 异常是在程序执行期间出现的问题。C# 中的异常是对程序运行时出现的特殊情况的一种响应，比如尝试除以零。\n异常提供了一种把程序控制权从某个部分转移到另一个部分的方式。C# 异常处理时建立在四个关键词之上的：try、catch、finally 和 throw。\ntry：一个 try 块标识了一个将被激活的特定的异常的代码块。后跟一个或多个 catch 块。 catch：程序通过异常处理程序捕获异常。catch 关键字表示异常的捕获。 finally：finally 块用于执行给定的语句，不管异常是否被抛出都会执行。例如，如果您打开一个文件，不管是否出现异常文件都要被关闭。 throw：当问题出现时，程序抛出一个异常。使用 throw 关键字来完成。 try { // 引起异常的语句 } catch( ExceptionName e1 ) { // 错误处理代码 } catch( ExceptionName e2 ) { // 错误处理代码 } catch( ExceptionName eN ) { // 错误处理代码 } finally { // 要执行的语句 } C# 中的异常类 # C# 异常是使用类来表示的。C# 中的异常类主要是直接或间接地派生于 System.Exception 类。\nSystem.ApplicationException 和 System.SystemException 类是派生于 System.Exception 类的异常类。\nSystem.ApplicationException 类支持由应用程序生成的异常。所以程序员定义的异常都应派生自该类。\nSystem.SystemException 类是所有预定义的系统异常的基类。\n下表列出了一些派生自 System.SystemException 类的预定义的异常类：\n异常类 描述 System.IO.IOException 处理 I/O 错误。 System.IndexOutOfRangeException 处理当方法指向超出范围的数组索引时生成的错误。 System.ArrayTypeMismatchException 处理当数组类型不匹配时生成的错误。 System.NullReferenceException 处理当依从一个空对象时生成的错误。 System.DivideByZeroException 处理当除以零时生成的错误。 System.InvalidCastException 处理在类型转换期间生成的错误。 System.OutOfMemoryException 处理空闲内存不足生成的错误。 System.StackOverflowException 处理栈溢出生成的错误。 using System; namespace UserDefinedException { class TestTemperature { static void Main(string[] args) { Temperature temp = new Temperature(); try { temp.showTemp(); } catch(TempIsZeroException e) { Console.WriteLine(\u0026#34;TempIsZeroException: {0}\u0026#34;, e.Message); } Console.ReadKey(); } } } public class TempIsZeroException: ApplicationException { public TempIsZeroException(string message): base(message) { } } public class Temperature { int temperature = 0; public void showTemp() { if(temperature == 0) { throw (new TempIsZeroException(\u0026#34;Zero Temperature found\u0026#34;)); } else { Console.WriteLine(\u0026#34;Temperature: {0}\u0026#34;, temperature); } } } ","date":"2024-09-12","externalUrl":null,"permalink":"/posts/5ab2821f/54418e25/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e异常处理 \n    \u003cdiv id=\"异常处理\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%bc%82%e5%b8%b8%e5%a4%84%e7%90%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e异常是在程序执行期间出现的问题。C# 中的异常是对程序运行时出现的特殊情况的一种响应，比如尝试除以零。\u003c/p\u003e","title":"11、异常处理","type":"posts"},{"content":" javac # javac 是java中的编译源代码的命令工具，将.java文件编译成.class文件\njavac \u0026lt;options\u0026gt; \u0026lt;source files\u0026gt;\noptions # -sourcepath：指定你依赖的类的.java文件的查找位置，如果找到会自动编译 -classpath（-cp）：指定你依赖的类的.class文件的查找位置，在Linux中，用“:”分隔classpath，而在windows中，用“;”分隔。 常用语法 # # 直接编译源文件，并将编译后的.class文件放到与源文件统一目录下，如果编译多个源文件，可以使用javac *.java javac xxx.java # 编译源文件，并将编译后的.class文件放到-d参数后的目标路径target\\目录中，目录需要自己手动创建 javac xxx.java -d target\\ # 如果遇到类似于，错误: 编码GBK的不可映射字符，需要指定字符集编码 javac xxx.java -d target\\ -encoding utf-8 # 对于大型项目有很多源码文件，可以使用sourcesfiles.txt指定，文件中使用回车符分割源文件 javac @sourcesfiles.txt -d target\\ -encoding utf-8 编译Demo # 单目录 # 目录结构\nStart.java\npackage top.ygang.javac; public class Start { public static void main(String[] args) { Student lucy = new Student(\u0026#34;lucy\u0026#34;, 18); System.out.println(lucy); } } Student.java\npackage top.ygang.javac; public class Student { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return \u0026#34;Student{\u0026#34; + \u0026#34;name=\u0026#39;\u0026#34; + name + \u0026#39;\\\u0026#39;\u0026#39; + \u0026#34;, age=\u0026#34; + age + \u0026#39;}\u0026#39;; } } 编译 # # 在javacdemo目录下执行命令，创建target目录 mkdir target # 在javacdemo目录下执行命令，编译 javac src/top/ygang/javac/*.java -d target/ -encoding utf-8 # 进入target目录 cd target # 运行java程序 java top.ygang.javac.Start # Student{name=\u0026#39;lucy\u0026#39;, age=18} 多目录编译 # 目录结构\n方式一：分目录编译 # # 以下命令在javacdemo目录下执行 # 编译entity目录 javac src/top/ygang/javac/entity/*.java -d target/ -encoding utf-8 # 编译javac目录 # 由于Start.java中引用了Student，所以需要指定-cp，用于编译器寻找Studnet.class javac src/top/ygang/javac/*.java -d target/ -encoding utf-8 -cp target/ 方式二：清单文件 # javacdemo目录下创建sourcefiles.txt文件，并写入所有类路径\nsrc/top/ygang/javac/entity/Student.java src/top/ygang/javac/Start.java 编译\n# 在javacdemo目录下执行命令 javac @sourcefiles.txt -d target/ -encoding utf-8 方式三：启动类编译 # 由于程序的入口类是main方法所在类，整个依赖类的引用链也是从main方法类开始的，所以，我们可以利用-sourcepath，来指定源文件根路径，让编译器自己去寻找其他源文件并编译\n# 在javacdemo目录下执行 javac src/top/ygang/javac/Start.java -d target/ -encoding utf-8 -sourcepath src/ 有依赖项目编译 # 目录结构\nStart.java\npackage top.ygang.javac; import com.alibaba.fastjson.JSON; import top.ygang.javac.entity.Student; public class Start { public static void main(String[] args) { Student lucy = new Student(\u0026#34;lucy\u0026#34;, 18); String jsonString = JSON.toJSONString(lucy); System.out.println(jsonString); } } 由于此时项目依赖了第三方jar，所以需要使用-cp给编译器指出jar路径\n# 在javacdemo目录下执行 javac src/top/ygang/javac/Start.java -d target/ -encoding utf-8 -sourcepath src/ -cp lib/fastjson-1.2.34.jar 运行，上面的编译命令只是可以让程序通过编译检查，并不会复制依赖jar，所以需要我们手动把lib目录复制到target目录下后运行\n运行程序时，同样需要指定-cp，需要显式的指定当前目录./也是classpath\n# 在target目录下执行 java -cp ./;lib/fastjson-1.2.34.jar top.ygang.javac.Start jar # jar命令用于操作.jar文件\njar {c t x u f }[ v m e 0 M i ][-C 目录]文件名 # 其中{ctxu}这四个选项必须选其一。[v f m e 0 M i ]是可选选项，文件名也是必须的。 -c 创建一个jar包 -t 显示jar中的内容列表 -x 解压jar包 -u 添加文件到jar包中 -f 指定jar包的文件名 -v 生成详细的报造，并输出至标准设备 -m 指定manifest.mf文件.(manifest.mf文件中可以对jar包及其中的内容作一些一设置) -0 产生jar包时不对其中的内容进行压缩处理 -M 不产生所有文件的清单文件(Manifest.mf)。这个参数与忽略掉-m参数的设置 -i 为指定的jar文件创建索引文件 -C 表示转到相应的目录下执行jar命令,相当于cd到那个目录，然后不带-C执行jar命令 打包 # 普通项目 # 目录结构\n编译项目\n# jardemo目录下执行 javac src/top/ygang/jar/Start.java -d target/ -encoding utf-8 -sourcepath src/ 进入target目录，创建清单文件MANIFEST.MF（如果只是工具类jar包，不单独启动执行，可跳过该步骤，会自动创建默认的清单文件）\nMANIFEST.MF # 第一行不能是空行 最后一行一定是一个空行。 每一行的:后面都要有一个空格 清单文件常见属性 Manifest-Version：用来定义manifest文件的版本，例如：Manifest-Version: 1.0（固定存在） Created-By：声明该文件的生成者，一般该属性是由jar命令行工具生成的（固定存在） Main-Class：定义jar文件的入口类，该类必须是一个可执行的类(包含main方法的类)，一旦定义了该属性即可通过 java -jar x.jar来运行该jar文件。 Class-Path：应用程序或者类装载器使用该值来构建内部的类搜索路径，可指定多个路径 Manifest-Version: 1.0 Created-By: 1.8.0_261 (Oracle Corporation) Main-Class: top.ygang.jar.Start 打包\n# 进入target目录执行 jar -cvfm jardemo.jar MANIFEST.MF top/ 执行\njava -jar jardemo.jar 有依赖的项目 # 目录结构\n编译项目\n# 在jardemo目录下执行 javac src/top/ygang/jar/Start.java -sourcepath src/ -cp lib/fastjson-1.2.34.jar -encoding utf-8 -d target 编写MANIFEST.MF文件\nManifest-Version: 1.0 Created-By: 1.8.0_261 (Oracle Corporation) Main-Class: top.ygang.jar.Start Class-Path: lib/fastjson-1.2.34.jar 将lib目录复制到target目录下\n# 在target目录下执行 jar -cvfm jardemo.jar MANIFEST.MF top/ lib/ 打包后，jardemo.jar中目录结构\n解压 # # 会解压jar到当前目录 jar -xvf jardemo.jar 更新 # 可以替换、新增jar中的文件\n例如，在lib目录下面新增一个1.txt\njar -uf jardemo.jar lib/1.txt java # java命令用于启动Java虚拟机并执行Java程序。使用java命令可以在命令行中直接运行编译后的Java程序。\njava [options] class [args...] # options：Java命令提供了多个选项来控制Java虚拟机和应用程序的行为。 # class：要运行的Java启动类名（含main方法） # args…：传递给主方法的参数。这些参数将作为字符串数组传递给main()方法 Option # 标准选项 -version：查看Java版本信息。 -jar file：执行指定的JAR文件。 -classpath path：指定Java虚拟机应该搜索类文件的路径。与javac编译器选项-cp相同。 虚拟机选项 -Xmx size：指定堆大小的最大值，以字节为单位。例如，-Xmx1024m表示堆大小的最大值为1024 MB。 -Xms size：指定初始堆大小，以字节为单位。 系统选项-Dproperty=value，通过System.getProperty()方法可以获取 -Djava.awt.headless=true：表示启用无头模式。 -Dfile.encoding=utf-8：设置系统字符集编码 ","date":"2024-04-03","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/fbeea4f4/fdb03ec6/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003ejavac \n    \u003cdiv id=\"javac\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#javac\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003ejavac\u003c/code\u003e 是java中的编译源代码的命令工具，将\u003ccode\u003e.java\u003c/code\u003e文件编译成\u003ccode\u003e.class\u003c/code\u003e文件\u003c/p\u003e","title":"11、常用命令","type":"posts"},{"content":"cron是golang实现定时任务比较好的库, 这个库提供了一个简单而强大的接口，用于创建和管理基于cron表达式的定时任务。cron库的主要特点有：\n基于cron表达式的任务调度 多任务支持 容错和错误处理 可靠性 易用的API 灵活性 并发安全 安装 # go get -u github.com/robfig/cron/v3@v3.0.0 简单使用 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/robfig/cron/v3\u0026#34; ) func main() { // 创建一个默认的cron对象 c := cron.New() // 添加任务 c.AddFunc(\u0026#34;30 * * * *\u0026#34;, func() { fmt.Println(\u0026#34;Every hour on the half hour\u0026#34;) }) c.AddFunc(\u0026#34;30 3-6,20-23 * * *\u0026#34;, func() { fmt.Println(\u0026#34;.. in the range 3-6am, 8-11pm\u0026#34;) }) c.AddFunc(\u0026#34;@hourly\u0026#34;, func() { fmt.Println(\u0026#34;Every hour, starting an hour from now\u0026#34;) }) c.AddFunc(\u0026#34;@every 1h30m\u0026#34;, func() { fmt.Println(\u0026#34;Every hour thirty, starting an hour thirty from now\u0026#34;) }) c.Start() //开始执行任务 c.Start() //阻塞 select {} } 预定的 Schedules # 条目 描述 等效于 @yearly (or @annually) Run once a year, midnight, Jan. 1st 0 0 1 1 * @monthly Run once a month, midnight, first of month 0 0 1 * * @weekly Run once a week, midnight between Sat/Sun 0 0 * * 0 @daily (or @midnight) Run once a day, midnight 0 0 * * * @hourly Run once an hour, beginning of hour 0 * * * * @every \u0026lt;duration\u0026gt; 固定间隔执行，@every 1h30m10s 表示 1小时30分钟10秒后执行，并且之后的每个时间间隔都执行。 精确到秒的 Cron 表达式 # Cron v3 版本的表达式从六个参数调整为五个，取消了对秒的默认支持，需要精确到秒的控制可以使用 cron.WithSeconds() 解析器。\nc := cron.New(cron.WithSeconds()) c.AddFunc(\u0026#34;*/1 * * * * *\u0026#34;, func() { fmt.Println(\u0026#34;Every 1 Second\u0026#34;) }) c.Start() 如果你仅仅需要每隔 N 秒运行一次 Job，可以使用 @every 这样的特殊 spec 表达式。\nc := cron.New() c.AddFunc(\u0026#34;@every 10s\u0026#34;, func() { fmt.Println(\u0026#34;Every 10 Seconds\u0026#34;) }) c.Start() 时区设置 # // 本地时区早六点 cron.New().AddFunc(\u0026#34;0 6 * * ?\u0026#34;, ...) // 上海时区早六点 nyc, _ := time.LoadLocation(\u0026#34;Asia/Shanghai\u0026#34;) c := cron.New(cron.WithLocation(nyc)) c.AddFunc(\u0026#34;0 6 * * ?\u0026#34;, ...) // 上海时区早六点 cron.New().AddFunc(\u0026#34;CRON_TZ=Asia/Shanghai 0 6 * * ?\u0026#34;, ...) // 重庆时区早六点 c := cron.New(cron.WithLocation(nyc)) c.SetLocation(\u0026#34;Asia/Tokyo\u0026#34;) c.AddFunc(\u0026#34;CRON_TZ=Asia/Chongqing 0 6 * * ?\u0026#34;, ...) Cron 方法 # AddFunc() - 支持传入如 @every 这样的 spec 表达式和标准表达式及 func() 函数，它封装了 AddJob()。 AddJob() - 支持传入 spec 和标准表达式及 Job，Job 是一个接口，实现 Run() 方法的类型即是一个 Job，它封装了 Schedule()。 Schedule() - 执行标准表达式和 Job，被 AddJob() 函数调用，只支持标准 Cron 表达式。 Entries() - 获取全部 Entry，一个 Entry 即为一个定时任务条目。 Entry() - 根据 EntryID 获取指定条目。 Remove() - 根据 EntryID 移除指定条目。 Location() - 获取 Local 时区。 Start() - 使用新的 goroutine 启动，不会阻塞当前协程，已运行的调度器重复调用会被忽略。 Run() - 在当前 goroutine 启动，会阻塞当前协程，已运行的调度器重复调用会被忽略。 Stop() - 停止调度器，返回 Context，可根据 Context 来等待任务执行完成。 可以获取任务上次、下次运行时间。\nc = cron.New() entryId, _ := c.AddFunc(\u0026#34;@every 15m\u0026#34;, func() { fmt.Println(\u0026#34;Every 15 Minutes\u0026#34;) }) entry := c.Entry(entryId) fmt.Println(entry.Next) Job 接口 # 除了使用无参的回调方式，cron还提供了实现job接口的方式：\ntype Job interface { Run() } 示例如下：\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/robfig/cron/v3\u0026#34; ) func main() { job := cron.New(cron.WithSeconds()) j := \u0026amp;myJob{} job.AddJob(\u0026#34;@every 1s\u0026#34;, j) job.Start() select {} } type myJob struct { i int } func (j *myJob) Run() { j.i++ fmt.Println(\u0026#34;hello world:\u0026#34;, j.i) } ","date":"2025-07-15","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/29ede47b/353e3cff/","section":"文章","summary":"\u003cp\u003ecron是golang实现定时任务比较好的库, 这个库提供了一个简单而强大的接口，用于创建和管理基于cron表达式的定时任务。cron库的主要特点有：\u003c/p\u003e","title":"12、cron","type":"posts"},{"content":" pinia # pinia和Vuex的作用是一样的，它也充当的是一个存储数据的作用，存储在pinia的数据允许我们在各个组件中使用。\n优点：\nVue2和Vue3都支持，这让我们同时使用Vue2和Vue3的小伙伴都能很快上手。 pinia中只有state、getter、action，抛弃了Vuex中的Mutation，Vuex中mutation一直都不太受待见，pinia直接抛弃它了，这无疑减少了我们工作量。 pinia中action支持同步和异步，Vuex不支持 良好的Typescript支持，毕竟我们Vue3都推荐使用TS来编写，这个时候使用pinia就非常合适了 无需再创建各个模块嵌套了，Vuex中如果数据过多，我们通常分模块来进行管理，稍显麻烦，而pinia中每个store都是独立的，互相不影响。 体积非常小，只有1KB左右。 pinia支持插件来扩展自身功能。 支持服务端渲染。 安装pinia # npm install pinia 安装完成后我们需要将pinia挂载到Vue应用中，也就是我们需要创建一个根存储传递给应用程序，简单来说就是创建一个存储数据的数据桶，放到应用程序中去。\n修改main.ts，引入pinia提供的createPinia方法，创建根存储。\nimport { createApp } from \u0026#34;vue\u0026#34;; import App from \u0026#34;./App.vue\u0026#34;; import { createPinia } from \u0026#34;pinia\u0026#34;; const pinia = createPinia(); const app = createApp(App); app.use(pinia); app.mount(\u0026#34;#app\u0026#34;); 创建store # store简单来说就是数据仓库的意思，我们数据都放在store里面。当然你也可以把它理解为一个公共组件，只不过该公共组件只存放数据，这些数据我们其它所有的组件都能够访问且可以修改。\n在项目src目录下新建store文件夹，用来存放我们创建的各种store，然后在该目录下新建user.ts文件，主要用来存放与user相关的store。\nimport { defineStore } from \u0026#39;pinia\u0026#39; // 第一个参数是应用程序中 store 的唯一 id export const useUsersStore = defineStore(\u0026#39;users\u0026#39;, { // 其它配置项 }) 创建store很简单，调用pinia中的defineStore函数即可，该函数接收两个参数：\nname：一个字符串，必传项，该store的唯一id。 options：一个对象，store的配置项，比如配置store内的数据，修改数据的方法等等。 我们可以定义任意数量的store，因为我们其实一个store就是一个函数，这也是pinia的好处之一，让我们的代码扁平化了，这和Vue3的实现思想是一样的。\n使用store # \u0026lt;script setup lang=\u0026#34;ts\u0026#34;\u0026gt; import { useUsersStore } from \u0026#34;../src/store/user\u0026#34;; const store = useUsersStore(); console.log(store); \u0026lt;/script\u0026gt; state # state是defineStore参数配置项里面的一个属性。\n该属性就是用来存储数据的。\n需要注意的是，state接收的是一个箭头函数返回的值，它不能直接接收一个对象。\n添加state # 前面我们利用defineStore函数创建了一个store，该函数第二个参数是一个options配置项，我们需要存放的数据就放在options对象中的state属性内。\nexport const useUsersStore = defineStore(\u0026#34;users\u0026#34;, { state: () =\u0026gt; { return { name: \u0026#34;Tom\u0026#34;, age: 25, sex: \u0026#34;Boy\u0026#34;, }; }, }); 读取state # \u0026lt;template\u0026gt; \u0026lt;img alt=\u0026#34;Vue logo\u0026#34; src=\u0026#34;./assets/logo.png\u0026#34; /\u0026gt; \u0026lt;p\u0026gt;姓名：{{ name }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;年龄：{{ age }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;性别：{{ sex }}\u0026lt;/p\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script setup lang=\u0026#34;ts\u0026#34;\u0026gt; import { ref } from \u0026#34;vue\u0026#34;; import { useUsersStore } from \u0026#34;../src/store/user\u0026#34;; const store = useUsersStore(); const name = ref\u0026lt;string\u0026gt;(store.name); const age = ref\u0026lt;number\u0026gt;(store.age); const sex = ref\u0026lt;string\u0026gt;(store.sex); \u0026lt;/script\u0026gt; 其实可以用解构的方式来获取值，使得代码更简洁一点。\nimport { useUsersStore } from \u0026#34;../src/store/user\u0026#34;; const store = useUsersStore(); const { name, age, sex } = store; 修改state # 如果我们想要修改store中的数据，可以直接重新赋值即可，我们在App.vue里面添加一个按钮，点击按钮修改store中的某一个数据。\n\u0026lt;template\u0026gt; \u0026lt;img alt=\u0026#34;Vue logo\u0026#34; src=\u0026#34;./assets/logo.png\u0026#34; /\u0026gt; \u0026lt;p\u0026gt;姓名：{{ name }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;年龄：{{ age }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;性别：{{ sex }}\u0026lt;/p\u0026gt; \u0026lt;button @click=\u0026#34;changeName\u0026#34;\u0026gt;更改姓名\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script setup lang=\u0026#34;ts\u0026#34;\u0026gt; import child from \u0026#39;./child.vue\u0026#39;; import { useUsersStore } from \u0026#34;../src/store/user\u0026#34;; const store = useUsersStore(); const { name, age, sex } = store; const changeName = () =\u0026gt; { store.name = \u0026#34;张三\u0026#34;; console.log(store); }; \u0026lt;/script\u0026gt; 但是需要注意的是：上面这种方法获取的 name 并不是响应式的。\n其实，pinia提供了storeToRefs方法给我们，让我们获得的name等属性变为响应式的。\nimport { storeToRefs } from \u0026#39;pinia\u0026#39;; const store = useUsersStore(); const { name, age, sex } = storeToRefs(store); 重置state # 有时候我们修改了state数据，想要将它还原，我们直接调用store的$reset()方法即可\n\u0026lt;button @click=\u0026#34;reset\u0026#34;\u0026gt;重置store\u0026lt;/button\u0026gt; // 重置store const reset = () =\u0026gt; { store.$reset(); }; 批量更改state数据 # 如果我们一次性需要修改很多条数据的话，有更加简便的方法，使用store的$patch方法\n\u0026lt;button @click=\u0026#34;patchStore\u0026#34;\u0026gt;批量修改数据\u0026lt;/button\u0026gt; \u0026lt;script setup\u0026gt; // 批量修改数据 const patchStore = () =\u0026gt; { store.$patch({ name: \u0026#34;张三\u0026#34;, age: 100, sex: \u0026#34;女\u0026#34;, }); }; \u0026lt;/script\u0026gt; 直接替换整个state # pinia提供了方法让我们直接替换整个state对象，使用store的$state方法。\nstore.$state = { counter: 666, name: \u0026#39;张三\u0026#39; } getters # getters是defineStore参数配置项里面的另一个属性。getter属性值是一个对象，该对象里面是各种各样的方法。\n可以把getter想象成Vue中的计算属性，它的作用就是返回一个新的结果，既然它和Vue中的计算属性类似，那么它肯定也是会被缓存的，就和computed一样。\n添加getter # export const useUsersStore = defineStore(\u0026#34;users\u0026#34;, { state: () =\u0026gt; { return { name: \u0026#34;Tom\u0026#34;, age: 25, sex: \u0026#34;男\u0026#34;, }; }, getters: { getAddAge: (state) =\u0026gt; { return state.age + 100; }, }, }); 使用getter # \u0026lt;template\u0026gt; \u0026lt;p\u0026gt;新年龄：{{ store.getAddAge }}\u0026lt;/p\u0026gt; \u0026lt;button @click=\u0026#34;patchStore\u0026#34;\u0026gt;批量修改数据\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script setup lang=\u0026#34;ts\u0026#34;\u0026gt; import { useUsersStore } from \u0026#34;../src/store/user\u0026#34;; const store = useUsersStore(); // 批量修改数据 const patchStore = () =\u0026gt; { store.$patch({ name: \u0026#34;张三\u0026#34;, age: 100, sex: \u0026#34;女\u0026#34;, }); }; \u0026lt;/script\u0026gt; getter中调用其它getter # export const useUsersStore = defineStore(\u0026#34;users\u0026#34;, { state: () =\u0026gt; { return { name: \u0026#34;小猪课堂\u0026#34;, age: 25, sex: \u0026#34;男\u0026#34;, }; }, getters: { getAddAge: (state) =\u0026gt; { return state.age + 100; }, getNameAndAge(): string { return this.name + this.getAddAge; // 调用其它getter }, }, }); getter传参 # 我们getter函数getAddAge接收了一个参数num，这种写法其实有点闭包的概念在里面了，相当于我们整体返回了一个新的函数，并且将state传入了新的函数。\nexport const useUsersStore = defineStore(\u0026#34;users\u0026#34;, { state: () =\u0026gt; { return { name: \u0026#34;小猪课堂\u0026#34;, age: 25, sex: \u0026#34;男\u0026#34;, }; }, getters: { getAddAge: (state) =\u0026gt; { return (num: number) =\u0026gt; state.age + num; }, getNameAndAge(): string { return this.name + this.getAddAge; // 调用其它getter }, }, }); actions # actions 也是defineStore参数配置项里面的一个属性。\n如果我们有业务代码的话，最好就是写在actions属性里面，该属性就和我们组件代码中的methods相似，用来放置一些处理业务逻辑的方法。\n添加actions # export const useUsersStore = defineStore(\u0026#34;users\u0026#34;, { state: () =\u0026gt; { return { name: \u0026#34;Tom\u0026#34;, age: 25, sex: \u0026#34;男\u0026#34;, }; }, getters: { getAddAge: (state) =\u0026gt; { return (num: number) =\u0026gt; state.age + num; }, getNameAndAge(): string { return this.name + this.getAddAge; // 调用其它getter }, }, actions: { saveName(name: string) { this.name = name; }, }, }); 使用actions # const saveName = () =\u0026gt; { store.saveName(\u0026#34;Lucy\u0026#34;); }; ","date":"2025-06-03","externalUrl":null,"permalink":"/posts/bafd68f1/b2321cb9/3b12aa8f/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003epinia \n    \u003cdiv id=\"pinia\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#pinia\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003epinia和Vuex的作用是一样的，它也充当的是一个存储数据的作用，存储在pinia的数据允许我们在各个组件中使用。\u003c/p\u003e","title":"12、pinia","type":"posts"},{"content":"Go语言的runtime包是Go语言运行时环境的核心组件，提供了丰富的功能来管理和优化程序的执行。\n获取 Go 环境 # GOOS # 获取本机操作系统\nfmt.Println(runtime.GOOS) GOARCH # 获取本机 CPU 架构\nfmt.Println(runtime.GOARCH) GOROOT # GOROOT返回Go的根目录。如果存在GOROOT环境变量，返回该变量的值；否则，返回创建Go时的根目录\nfunc GOROOT() string Go Version # 返回Go的版本字符串。它要么是递交的hash和创建时的日期；要么是发行标签如go1.3\nfunc Version() string CPU # 获取本机CPU个数 # NumCPU返回本地机器的逻辑CPU个数\nfunc NumCPU() int 设置最大可同时执行的最大CPU数 # GOMAXPROCS设置可同时执行的最大CPU数，并返回先前的设置。 若 n \u0026lt; 1，它就不会更改当前设置。本地机器的逻辑CPU数可通过 NumCPU 查询。\nfunc GOMAXPROCS(n int) int 设置cup profile 记录的速录 # SetCPUProfileRate设置CPU profile记录的速率为平均每秒hz次。如果hz\u0026lt;=0，SetCPUProfileRate会关闭profile的记录。如果记录器在执行，该速率必须在关闭之后才能修改。\nfunc SetCPUProfileRate(hz int) GC # 立即执行一次垃圾回收 # GC执行一次垃圾回收\nfunc GC() 给变量绑定方法，当垃圾回收的时候进行监听 # 注意x必须是指针类型，f 函数的参数一定要和x保持一致，或者写interface{}，不然程序会报错\nfunc SetFinalizer(x, f interface{}) ","date":"2025-05-19","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/12d682f6/52fc4b6f/","section":"文章","summary":"\u003cp\u003eGo语言的\u003ccode\u003eruntime\u003c/code\u003e包是Go语言运行时环境的核心组件，提供了丰富的功能来管理和优化程序的执行。\u003c/p\u003e","title":"12、runtime","type":"posts"},{"content":"Frame（框架）组件是在屏幕上的一个矩形区域。Frame 主要是作为其他组件的框架基础，或为其他组件提供间距填充。\n例如使用 Frame 做一个分割线\nimport tkinter as tk root = tk.Tk() tk.Label(root,text=\u0026#39;Python\u0026#39;).pack() tk.Frame(root,height=1,bg=\u0026#39;black\u0026#39;).pack(fill=\u0026#39;x\u0026#39;) tk.Label(root,text=\u0026#39;Tkinter\u0026#39;).pack() root.mainloop() 属性 # 选项 含义 background 1. 设置 Frame 组件的背景颜色； 2. 默认值由系统指定 ；3. 为了防止更新，可以将颜色值设置为空字符串 bg 跟 background 一样 borderwidth 1. 指定 Frame 的边框宽度 ；2. 默认值是 0 bd 跟 borderwidth 一样 class_ 默认值是 Frame colormap 1. 有些显示器只支持 256 色（有些可能更少），这种显示器通常提供一个颜色映射来指定要使用要使用的 256 种颜色 ；2. 该选项允许你指定用于该组件以及其子组件的颜色映射 ；3. 默认情况下，Frame 使用与其父组件相同的颜色映射 ；4.使用此选项，你可以使用其他窗口的颜色映射代替（两窗口必须位于同个屏幕并且具有相同的视觉特性）；5.你也可以直接使用 \u0026ldquo;new\u0026rdquo; 为 Frame 组件分配一个新的颜色映射6.一旦创建 Frame 组件实例，你就无法修改这个选项的值 container 1. 该选项如果为 True，意味着该窗口将被用作容器，一些其它应用程序将被嵌入 ；2. 默认值是 False cursor 1. 指定当鼠标在 Frame 上飘过的时候的鼠标样式 ；2. 默认值由系统指定 height 1. 设置 Frame 的高度； 2. 默认值是 0 highlightbackground 1. 指定当 Frame 没有获得焦点的时候高亮边框的颜色 ；2. 默认值由系统指定，通常是标准背景颜色 highlightcolor 1. 指定当 Frame 获得焦点的时候高亮边框的颜色 ；2. 默认值由系统指定 highlightthickness 1. 指定高亮边框的宽度 ；2. 默认值是 0（不带高亮边框） padx 水平方向上的边距 pady 垂直方向上的边距 relief 1. 指定边框样式 ；2. 默认值是 \u0026ldquo;flat\u0026rdquo; ；3. 另外你还可以设置 \u0026ldquo;sunken\u0026rdquo;，\u0026ldquo;raised\u0026rdquo;，\u0026ldquo;groove\u0026rdquo; 或 \u0026ldquo;ridge\u0026rdquo; ；4. 注意，如果你要设置边框样式，记得设置 borderwidth 或 bd 选项不为 0，才能看到边框 takefocus 1. 指定该组件是否接受输入焦点（用户可以通过 tab 键将焦点转移上来） ；2. 默认值是 False visual 1. 为新窗口指定视觉信息 ；2. 该选项没有默认值 width 1. 设置 Frame 的宽度； 2. 默认值是 0 ","date":"2025-04-15","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/a879fe06/96ddbb03/","section":"文章","summary":"\u003cp\u003eFrame（框架）组件是在屏幕上的一个矩形区域。Frame 主要是作为其他组件的框架基础，或为其他组件提供间距填充。\u003c/p\u003e","title":"12、Frame","type":"posts"},{"content":" 类 描述和用法 动态数组（ArrayList） 它代表了可被单独索引的对象的有序集合。它基本上可以替代一个数组。但是，与数组不同的是，您可以使用索引在指定的位置添加和移除项目，动态数组会自动重新调整它的大小。它也允许在列表中进行动态内存分配、增加、搜索、排序各项。 哈希表（Hashtable） 它使用键来访问集合中的元素。当您使用键访问元素时，则使用哈希表，而且您可以识别一个有用的键值。哈希表中的每一项都有一个键/值对。键用于访问集合中的项目。 排序列表（SortedList） 它可以使用键和索引来访问列表中的项。排序列表是数组和哈希表的组合。它包含一个可使用键或索引访问各项的列表。如果您使用索引访问各项，则它是一个动态数组（ArrayList），如果您使用键访问各项，则它是一个哈希表（Hashtable）。集合中的各项总是按键值排序。 堆栈（Stack） 它代表了一个后进先出的对象集合。当您需要对各项进行后进先出的访问时，则使用堆栈。当您在列表中添加一项，称为推入元素，当您从列表中移除一项时，称为弹出元素。 队列（Queue） 它代表了一个先进先出的对象集合。当您需要对各项进行先进先出的访问时，则使用队列。当您在列表中添加一项，称为入队，当您从列表中移除一项时，称为出队。 点阵列（BitArray） 它代表了一个使用值 1 和 0 来表示的二进制数组。当您需要存储位，但是事先不知道位数时，则使用点阵列。您可以使用整型索引从点阵列集合中访问各项，索引从零开始。 ArrayList # using System; using System.Collections; namespace CollectionApplication { class Program { static void Main(string[] args) { ArrayList al = new ArrayList(); Console.WriteLine(\u0026#34;Adding some numbers:\u0026#34;); al.Add(45); al.Add(78); al.Add(33); al.Add(56); al.Add(12); al.Add(23); al.Add(9); Console.WriteLine(\u0026#34;Capacity: {0} \u0026#34;, al.Capacity); Console.WriteLine(\u0026#34;Count: {0}\u0026#34;, al.Count); Console.Write(\u0026#34;Content: \u0026#34;); foreach (int i in al) { Console.Write(i + \u0026#34; \u0026#34;); } Console.WriteLine(); Console.Write(\u0026#34;Sorted Content: \u0026#34;); al.Sort(); foreach (int i in al) { Console.Write(i + \u0026#34; \u0026#34;); } Console.WriteLine(); Console.ReadKey(); } } } 属性 # 属性 描述 Capacity 获取或设置 ArrayList 可以包含的元素个数。 Count 获取 ArrayList 中实际包含的元素个数。 IsFixedSize 获取一个值，表示 ArrayList 是否具有固定大小。 IsReadOnly 获取一个值，表示 ArrayList 是否只读。 IsSynchronized 获取一个值，表示访问 ArrayList 是否同步（线程安全）。 Item[Int32] 获取或设置指定索引处的元素。 SyncRoot 获取一个对象用于同步访问 ArrayList。 方法 # 序号 方法名 \u0026amp; 描述 1 public virtual int Add( object value ); 在 ArrayList 的末尾添加一个对象。 2 public virtual void AddRange( ICollection c ); 在 ArrayList 的末尾添加 ICollection 的元素。 3 public virtual void Clear(); 从 ArrayList 中移除所有的元素。 4 public virtual bool Contains( object item ); 判断某个元素是否在 ArrayList 中。 5 public virtual ArrayList GetRange( int index, int count ); 返回一个 ArrayList，表示源 ArrayList 中元素的子集。 6 public virtual int IndexOf(object); 返回某个值在 ArrayList 中第一次出现的索引，索引从零开始。 7 public virtual void Insert( int index, object value ); 在 ArrayList 的指定索引处，插入一个元素。 8 public virtual void InsertRange( int index, ICollection c ); 在 ArrayList 的指定索引处，插入某个集合的元素。 9 public virtual void Remove( object obj ); 从 ArrayList 中移除第一次出现的指定对象。 10 public virtual void RemoveAt( int index ); 移除 ArrayList 的指定索引处的元素。 11 public virtual void RemoveRange( int index, int count ); 从 ArrayList 中移除某个范围的元素。 12 public virtual void Reverse(); 逆转 ArrayList 中元素的顺序。 13 public virtual void SetRange( int index, ICollection c ); 复制某个集合的元素到 ArrayList 中某个范围的元素上。 14 public virtual void Sort(); 对 ArrayList 中的元素进行排序。 15 public virtual void TrimToSize(); 设置容量为 ArrayList 中元素的实际个数。 Hashtable # using System; using System.Collections; namespace CollectionsApplication { class Program { static void Main(string[] args) { Hashtable ht = new Hashtable(); ht.Add(\u0026#34;001\u0026#34;, \u0026#34;Zara Ali\u0026#34;); ht.Add(\u0026#34;002\u0026#34;, \u0026#34;Abida Rehman\u0026#34;); ht.Add(\u0026#34;003\u0026#34;, \u0026#34;Joe Holzner\u0026#34;); ht.Add(\u0026#34;004\u0026#34;, \u0026#34;Mausam Benazir Nur\u0026#34;); ht.Add(\u0026#34;005\u0026#34;, \u0026#34;M. Amlan\u0026#34;); ht.Add(\u0026#34;006\u0026#34;, \u0026#34;M. Arif\u0026#34;); ht.Add(\u0026#34;007\u0026#34;, \u0026#34;Ritesh Saikia\u0026#34;); if (ht.ContainsValue(\u0026#34;Nuha Ali\u0026#34;)) { Console.WriteLine(\u0026#34;This student name is already in the list\u0026#34;); } else { ht.Add(\u0026#34;008\u0026#34;, \u0026#34;Nuha Ali\u0026#34;); } // 获取键的集合 ICollection key = ht.Keys; foreach (string k in key) { Console.WriteLine(k + \u0026#34;: \u0026#34; + ht[k]); } Console.ReadKey(); } } } 属性 # 属性 描述 Count 获取 Hashtable 中包含的键值对个数。 IsFixedSize 获取一个值，表示 Hashtable 是否具有固定大小。 IsReadOnly 获取一个值，表示 Hashtable 是否只读。 Item 获取或设置与指定的键相关的值。 Keys 获取一个 ICollection，包含 Hashtable 中的键。 Values 获取一个 ICollection，包含 Hashtable 中的值。 方法 # 序号 方法名 \u0026amp; 描述 1 public virtual void Add( object key, object value ); 向 Hashtable 添加一个带有指定的键和值的元素。 2 public virtual void Clear(); 从 Hashtable 中移除所有的元素。 3 public virtual bool ContainsKey( object key ); 判断 Hashtable 是否包含指定的键。 4 public virtual bool ContainsValue( object value ); 判断 Hashtable 是否包含指定的值。 5 public virtual void Remove( object key ); 从 Hashtable 中移除带有指定的键的元素。 SortedList # using System; using System.Collections; namespace CollectionsApplication { class Program { static void Main(string[] args) { SortedList sl = new SortedList(); sl.Add(\u0026#34;001\u0026#34;, \u0026#34;Zara Ali\u0026#34;); sl.Add(\u0026#34;002\u0026#34;, \u0026#34;Abida Rehman\u0026#34;); sl.Add(\u0026#34;003\u0026#34;, \u0026#34;Joe Holzner\u0026#34;); sl.Add(\u0026#34;004\u0026#34;, \u0026#34;Mausam Benazir Nur\u0026#34;); sl.Add(\u0026#34;005\u0026#34;, \u0026#34;M. Amlan\u0026#34;); sl.Add(\u0026#34;006\u0026#34;, \u0026#34;M. Arif\u0026#34;); sl.Add(\u0026#34;007\u0026#34;, \u0026#34;Ritesh Saikia\u0026#34;); if (sl.ContainsValue(\u0026#34;Nuha Ali\u0026#34;)) { Console.WriteLine(\u0026#34;This student name is already in the list\u0026#34;); } else { sl.Add(\u0026#34;008\u0026#34;, \u0026#34;Nuha Ali\u0026#34;); } // 获取键的集合 ICollection key = sl.Keys; foreach (string k in key) { Console.WriteLine(k + \u0026#34;: \u0026#34; + sl[k]); } } } } 属性 # 属性 描述 Capacity 获取或设置 SortedList 的容量。 Count 获取 SortedList 中的元素个数。 IsFixedSize 获取一个值，表示 SortedList 是否具有固定大小。 IsReadOnly 获取一个值，表示 SortedList 是否只读。 Item 获取或设置与 SortedList 中指定的键相关的值。 Keys 获取 SortedList 中的键。 Values 获取 SortedList 中的值。 方法 # 序号 方法名 \u0026amp; 描述 1 public virtual void Add( object key, object value ); 向 SortedList 添加一个带有指定的键和值的元素。 2 public virtual void Clear(); 从 SortedList 中移除所有的元素。 3 public virtual bool ContainsKey( object key ); 判断 SortedList 是否包含指定的键。 4 public virtual bool ContainsValue( object value ); 判断 SortedList 是否包含指定的值。 5 public virtual object GetByIndex( int index ); 获取 SortedList 的指定索引处的值。 6 public virtual object GetKey( int index ); 获取 SortedList 的指定索引处的键。 7 public virtual IList GetKeyList(); 获取 SortedList 中的键。 8 public virtual IList GetValueList(); 获取 SortedList 中的值。 9 public virtual int IndexOfKey( object key ); 返回 SortedList 中的指定键的索引，索引从零开始。 10 public virtual int IndexOfValue( object value ); 返回 SortedList 中的指定值第一次出现的索引，索引从零开始。 11 public virtual void Remove( object key ); 从 SortedList 中移除带有指定的键的元素。 12 public virtual void RemoveAt( int index ); 移除 SortedList 的指定索引处的元素。 13 public virtual void TrimToSize(); 设置容量为 SortedList 中元素的实际个数。 Stack # using System; using System.Collections; namespace CollectionsApplication { class Program { static void Main(string[] args) { Stack st = new Stack(); st.Push(\u0026#39;A\u0026#39;); st.Push(\u0026#39;M\u0026#39;); st.Push(\u0026#39;G\u0026#39;); st.Push(\u0026#39;W\u0026#39;); Console.WriteLine(\u0026#34;Current stack: \u0026#34;); foreach (char c in st) { Console.Write(c + \u0026#34; \u0026#34;); } Console.WriteLine(); st.Push(\u0026#39;V\u0026#39;); st.Push(\u0026#39;H\u0026#39;); Console.WriteLine(\u0026#34;The next poppable value in stack: {0}\u0026#34;, st.Peek()); Console.WriteLine(\u0026#34;Current stack: \u0026#34;); foreach (char c in st) { Console.Write(c + \u0026#34; \u0026#34;); } Console.WriteLine(); Console.WriteLine(\u0026#34;Removing values \u0026#34;); st.Pop(); st.Pop(); st.Pop(); Console.WriteLine(\u0026#34;Current stack: \u0026#34;); foreach (char c in st) { Console.Write(c + \u0026#34; \u0026#34;); } } } } 属性 # 属性 描述 Count 获取 Stack 中包含的元素个数。 方法 # 序号 方法名 \u0026amp; 描述 1 public virtual void Clear(); 从 Stack 中移除所有的元素。 2 public virtual bool Contains( object obj ); 判断某个元素是否在 Stack 中。 3 public virtual object Peek(); 返回在 Stack 的顶部的对象，但不移除它。 4 public virtual object Pop(); 移除并返回在 Stack 的顶部的对象。 5 public virtual void Push( object obj ); 向 Stack 的顶部添加一个对象。 6 public virtual object[] ToArray(); 复制 Stack 到一个新的数组中。 Queue # using System; using System.Collections; namespace CollectionsApplication { class Program { static void Main(string[] args) { Queue q = new Queue(); q.Enqueue(\u0026#39;A\u0026#39;); q.Enqueue(\u0026#39;M\u0026#39;); q.Enqueue(\u0026#39;G\u0026#39;); q.Enqueue(\u0026#39;W\u0026#39;); Console.WriteLine(\u0026#34;Current queue: \u0026#34;); foreach (char c in q) Console.Write(c + \u0026#34; \u0026#34;); Console.WriteLine(); q.Enqueue(\u0026#39;V\u0026#39;); q.Enqueue(\u0026#39;H\u0026#39;); Console.WriteLine(\u0026#34;Current queue: \u0026#34;); foreach (char c in q) Console.Write(c + \u0026#34; \u0026#34;); Console.WriteLine(); Console.WriteLine(\u0026#34;Removing some values \u0026#34;); char ch = (char)q.Dequeue(); Console.WriteLine(\u0026#34;The removed value: {0}\u0026#34;, ch); ch = (char)q.Dequeue(); Console.WriteLine(\u0026#34;The removed value: {0}\u0026#34;, ch); Console.ReadKey(); } } } 属性 # 属性 描述 Count 获取 Queue 中包含的元素个数。 方法 # 序号 方法名 \u0026amp; 描述 1 public virtual void Clear(); 从 Queue 中移除所有的元素。 2 public virtual bool Contains( object obj ); 判断某个元素是否在 Queue 中。 3 public virtual object Dequeue(); 移除并返回在 Queue 的开头的对象。 4 public virtual void Enqueue( object obj ); 向 Queue 的末尾添加一个对象。 5 public virtual object[] ToArray(); 复制 Queue 到一个新的数组中。 6 public virtual void TrimToSize(); 设置容量为 Queue 中元素的实际个数。 ","date":"2024-09-12","externalUrl":null,"permalink":"/posts/5ab2821f/d9eeaab2/","section":"文章","summary":"\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003e类\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e描述和用法\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e动态数组（ArrayList）\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e它代表了可被单独\u003cstrong\u003e索引\u003c/strong\u003e的对象的有序集合。它基本上可以替代一个数组。但是，与数组不同的是，您可以使用\u003cstrong\u003e索引\u003c/strong\u003e在指定的位置添加和移除项目，动态数组会自动重新调整它的大小。它也允许在列表中进行动态内存分配、增加、搜索、排序各项。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e哈希表（Hashtable）\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e它使用\u003cstrong\u003e键\u003c/strong\u003e来访问集合中的元素。当您使用键访问元素时，则使用哈希表，而且您可以识别一个有用的键值。哈希表中的每一项都有一个\u003cstrong\u003e键/值\u003c/strong\u003e对。键用于访问集合中的项目。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e排序列表（SortedList）\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e它可以使用\u003cstrong\u003e键\u003c/strong\u003e和\u003cstrong\u003e索引\u003c/strong\u003e来访问列表中的项。排序列表是数组和哈希表的组合。它包含一个可使用键或索引访问各项的列表。如果您使用索引访问各项，则它是一个动态数组（ArrayList），如果您使用键访问各项，则它是一个哈希表（Hashtable）。集合中的各项总是按键值排序。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e堆栈（Stack）\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e它代表了一个\u003cstrong\u003e后进先出\u003c/strong\u003e的对象集合。当您需要对各项进行后进先出的访问时，则使用堆栈。当您在列表中添加一项，称为\u003cstrong\u003e推入\u003c/strong\u003e元素，当您从列表中移除一项时，称为\u003cstrong\u003e弹出\u003c/strong\u003e元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e队列（Queue）\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e它代表了一个\u003cstrong\u003e先进先出\u003c/strong\u003e的对象集合。当您需要对各项进行先进先出的访问时，则使用队列。当您在列表中添加一项，称为\u003cstrong\u003e入队\u003c/strong\u003e，当您从列表中移除一项时，称为\u003cstrong\u003e出队\u003c/strong\u003e。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e点阵列（BitArray）\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e它代表了一个使用值 1 和 0 来表示的\u003cstrong\u003e二进制\u003c/strong\u003e数组。当您需要存储位，但是事先不知道位数时，则使用点阵列。您可以使用\u003cstrong\u003e整型索引\u003c/strong\u003e从点阵列集合中访问各项，索引从零开始。\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2 class=\"relative group\"\u003eArrayList \n    \u003cdiv id=\"arraylist\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#arraylist\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-cs\" data-lang=\"cs\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem.Collections\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003enamespace\u003c/span\u003e \u003cspan class=\"nn\"\u003eCollectionApplication\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eProgram\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"k\"\u003evoid\u003c/span\u003e \u003cspan class=\"n\"\u003eMain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e[]\u003c/span\u003e \u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eArrayList\u003c/span\u003e \u003cspan class=\"n\"\u003eal\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eArrayList\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Adding some numbers:\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e45\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e78\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e33\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e56\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e12\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e23\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e9\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Capacity: {0} \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eCapacity\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Count: {0}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eCount\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                      \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Content: \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eforeach\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eal\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34; \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Sorted Content: \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eSort\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eforeach\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eal\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34; \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eReadKey\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e属性 \n    \u003cdiv id=\"属性\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b1%9e%e6%80%a7\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003e属性\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e描述\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eCapacity\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取或设置 ArrayList 可以包含的元素个数。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eCount\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取 ArrayList 中实际包含的元素个数。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eIsFixedSize\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取一个值，表示 ArrayList 是否具有固定大小。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eIsReadOnly\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取一个值，表示 ArrayList 是否只读。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eIsSynchronized\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取一个值，表示访问 ArrayList 是否同步（线程安全）。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eItem[Int32]\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取或设置指定索引处的元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eSyncRoot\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取一个对象用于同步访问 ArrayList。\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch3 class=\"relative group\"\u003e方法 \n    \u003cdiv id=\"方法\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e6%b3%95\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003e序号\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e方法名 \u0026amp; 描述\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e1\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual int Add( object value );\u003c/strong\u003e 在 ArrayList 的末尾添加一个对象。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e2\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void AddRange( ICollection c );\u003c/strong\u003e 在 ArrayList 的末尾添加 ICollection 的元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e3\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void Clear();\u003c/strong\u003e 从 ArrayList 中移除所有的元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e4\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual bool Contains( object item );\u003c/strong\u003e 判断某个元素是否在 ArrayList 中。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e5\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual ArrayList GetRange( int index, int count );\u003c/strong\u003e 返回一个 ArrayList，表示源 ArrayList 中元素的子集。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e6\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual int IndexOf(object);\u003c/strong\u003e 返回某个值在 ArrayList 中第一次出现的索引，索引从零开始。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e7\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void Insert( int index, object value );\u003c/strong\u003e 在 ArrayList 的指定索引处，插入一个元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e8\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void InsertRange( int index, ICollection c );\u003c/strong\u003e 在 ArrayList 的指定索引处，插入某个集合的元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e9\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void Remove( object obj );\u003c/strong\u003e 从 ArrayList 中移除第一次出现的指定对象。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e10\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void RemoveAt( int index );\u003c/strong\u003e 移除 ArrayList 的指定索引处的元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e11\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void RemoveRange( int index, int count );\u003c/strong\u003e 从 ArrayList 中移除某个范围的元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e12\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void Reverse();\u003c/strong\u003e 逆转 ArrayList 中元素的顺序。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e13\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void SetRange( int index, ICollection c );\u003c/strong\u003e 复制某个集合的元素到 ArrayList 中某个范围的元素上。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e14\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void Sort();\u003c/strong\u003e 对 ArrayList 中的元素进行排序。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e15\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void TrimToSize();\u003c/strong\u003e 设置容量为 ArrayList 中元素的实际个数。\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2 class=\"relative group\"\u003eHashtable \n    \u003cdiv id=\"hashtable\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#hashtable\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-cs\" data-lang=\"cs\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem.Collections\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003enamespace\u003c/span\u003e \u003cspan class=\"nn\"\u003eCollectionsApplication\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eProgram\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"k\"\u003evoid\u003c/span\u003e \u003cspan class=\"n\"\u003eMain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e[]\u003c/span\u003e \u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eHashtable\u003c/span\u003e \u003cspan class=\"n\"\u003eht\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eHashtable\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eht\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;001\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Zara Ali\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eht\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;002\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Abida Rehman\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eht\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;003\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Joe Holzner\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eht\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;004\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Mausam Benazir Nur\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eht\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;005\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;M. Amlan\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eht\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;006\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;M. Arif\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eht\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;007\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Ritesh Saikia\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eht\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eContainsValue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Nuha Ali\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;This student name is already in the list\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003eelse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eht\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;008\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Nuha Ali\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"c1\"\u003e// 获取键的集合 \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eICollection\u003c/span\u003e \u003cspan class=\"n\"\u003ekey\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eht\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eKeys\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003eforeach\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;: \u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eht\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e]);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eReadKey\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e属性 \n    \u003cdiv id=\"属性-1\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b1%9e%e6%80%a7-1\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003e属性\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e描述\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eCount\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取 Hashtable 中包含的键值对个数。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eIsFixedSize\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取一个值，表示 Hashtable 是否具有固定大小。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eIsReadOnly\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取一个值，表示 Hashtable 是否只读。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eItem\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取或设置与指定的键相关的值。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eKeys\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取一个 ICollection，包含 Hashtable 中的键。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eValues\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取一个 ICollection，包含 Hashtable 中的值。\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch3 class=\"relative group\"\u003e方法 \n    \u003cdiv id=\"方法-1\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e6%b3%95-1\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003e序号\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e方法名 \u0026amp; 描述\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e1\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void Add( object key, object value );\u003c/strong\u003e 向 Hashtable 添加一个带有指定的键和值的元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e2\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void Clear();\u003c/strong\u003e 从 Hashtable 中移除所有的元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e3\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual bool ContainsKey( object key );\u003c/strong\u003e 判断 Hashtable 是否包含指定的键。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e4\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual bool ContainsValue( object value );\u003c/strong\u003e 判断 Hashtable 是否包含指定的值。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e5\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void Remove( object key );\u003c/strong\u003e 从 Hashtable 中移除带有指定的键的元素。\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2 class=\"relative group\"\u003eSortedList \n    \u003cdiv id=\"sortedlist\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#sortedlist\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-cs\" data-lang=\"cs\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem.Collections\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003enamespace\u003c/span\u003e \u003cspan class=\"nn\"\u003eCollectionsApplication\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eProgram\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"k\"\u003evoid\u003c/span\u003e \u003cspan class=\"n\"\u003eMain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e[]\u003c/span\u003e \u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eSortedList\u003c/span\u003e \u003cspan class=\"n\"\u003esl\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eSortedList\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003esl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;001\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Zara Ali\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003esl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;002\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Abida Rehman\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003esl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;003\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Joe Holzner\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003esl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;004\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Mausam Benazir Nur\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003esl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;005\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;M. Amlan\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003esl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;006\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;M. Arif\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003esl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;007\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Ritesh Saikia\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eContainsValue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Nuha Ali\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;This student name is already in the list\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003eelse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003esl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAdd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;008\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Nuha Ali\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"c1\"\u003e// 获取键的集合 \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eICollection\u003c/span\u003e \u003cspan class=\"n\"\u003ekey\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eKeys\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003eforeach\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;: \u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003esl\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e]);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e属性 \n    \u003cdiv id=\"属性-2\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b1%9e%e6%80%a7-2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003e属性\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e描述\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eCapacity\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取或设置 SortedList 的容量。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eCount\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取 SortedList 中的元素个数。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eIsFixedSize\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取一个值，表示 SortedList 是否具有固定大小。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eIsReadOnly\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取一个值，表示 SortedList 是否只读。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eItem\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取或设置与 SortedList 中指定的键相关的值。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eKeys\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取 SortedList 中的键。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eValues\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取 SortedList 中的值。\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch3 class=\"relative group\"\u003e方法 \n    \u003cdiv id=\"方法-2\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e6%b3%95-2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003e序号\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e方法名 \u0026amp; 描述\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e1\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void Add( object key, object value );\u003c/strong\u003e 向 SortedList 添加一个带有指定的键和值的元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e2\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void Clear();\u003c/strong\u003e 从 SortedList 中移除所有的元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e3\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual bool ContainsKey( object key );\u003c/strong\u003e 判断 SortedList 是否包含指定的键。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e4\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual bool ContainsValue( object value );\u003c/strong\u003e 判断 SortedList 是否包含指定的值。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e5\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual object GetByIndex( int index );\u003c/strong\u003e 获取 SortedList 的指定索引处的值。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e6\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual object GetKey( int index );\u003c/strong\u003e 获取 SortedList 的指定索引处的键。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e7\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual IList GetKeyList();\u003c/strong\u003e 获取 SortedList 中的键。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e8\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual IList GetValueList();\u003c/strong\u003e 获取 SortedList 中的值。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e9\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual int IndexOfKey( object key );\u003c/strong\u003e 返回 SortedList 中的指定键的索引，索引从零开始。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e10\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual int IndexOfValue( object value );\u003c/strong\u003e 返回 SortedList 中的指定值第一次出现的索引，索引从零开始。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e11\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void Remove( object key );\u003c/strong\u003e 从 SortedList 中移除带有指定的键的元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e12\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void RemoveAt( int index );\u003c/strong\u003e 移除 SortedList 的指定索引处的元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e13\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void TrimToSize();\u003c/strong\u003e 设置容量为 SortedList 中元素的实际个数。\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2 class=\"relative group\"\u003eStack \n    \u003cdiv id=\"stack\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#stack\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-cs\" data-lang=\"cs\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem.Collections\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003enamespace\u003c/span\u003e \u003cspan class=\"nn\"\u003eCollectionsApplication\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eProgram\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"k\"\u003evoid\u003c/span\u003e \u003cspan class=\"n\"\u003eMain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e[]\u003c/span\u003e \u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eStack\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eStack\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ePush\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sc\"\u003e\u0026#39;A\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ePush\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sc\"\u003e\u0026#39;M\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ePush\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sc\"\u003e\u0026#39;G\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ePush\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sc\"\u003e\u0026#39;W\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Current stack: \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eforeach\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e \u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34; \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ePush\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sc\"\u003e\u0026#39;V\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ePush\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sc\"\u003e\u0026#39;H\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;The next poppable value in stack: {0}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ePeek\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Current stack: \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e           \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eforeach\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e \u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34; \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Removing values \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ePop\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ePop\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ePop\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Current stack: \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eforeach\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e \u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34; \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e属性 \n    \u003cdiv id=\"属性-3\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b1%9e%e6%80%a7-3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003e属性\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e描述\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eCount\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取 Stack 中包含的元素个数。\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch3 class=\"relative group\"\u003e方法 \n    \u003cdiv id=\"方法-3\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e6%b3%95-3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003e序号\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e方法名 \u0026amp; 描述\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e1\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void Clear();\u003c/strong\u003e 从 Stack 中移除所有的元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e2\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual bool Contains( object obj );\u003c/strong\u003e 判断某个元素是否在 Stack 中。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e3\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual object Peek();\u003c/strong\u003e 返回在 Stack 的顶部的对象，但不移除它。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e4\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual object Pop();\u003c/strong\u003e 移除并返回在 Stack 的顶部的对象。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e5\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void Push( object obj );\u003c/strong\u003e 向 Stack 的顶部添加一个对象。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e6\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual object[] ToArray();\u003c/strong\u003e 复制 Stack 到一个新的数组中。\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch2 class=\"relative group\"\u003eQueue \n    \u003cdiv id=\"queue\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#queue\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-cs\" data-lang=\"cs\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem.Collections\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003enamespace\u003c/span\u003e \u003cspan class=\"nn\"\u003eCollectionsApplication\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eProgram\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"k\"\u003evoid\u003c/span\u003e \u003cspan class=\"n\"\u003eMain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e[]\u003c/span\u003e \u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eQueue\u003c/span\u003e \u003cspan class=\"n\"\u003eq\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eQueue\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eEnqueue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sc\"\u003e\u0026#39;A\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eEnqueue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sc\"\u003e\u0026#39;M\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eEnqueue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sc\"\u003e\u0026#39;G\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eEnqueue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sc\"\u003e\u0026#39;W\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Current queue: \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003eforeach\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e \u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34; \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eEnqueue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sc\"\u003e\u0026#39;V\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eEnqueue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sc\"\u003e\u0026#39;H\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Current queue: \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e         \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"k\"\u003eforeach\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e \u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34; \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Removing some values \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"kt\"\u003echar\u003c/span\u003e \u003cspan class=\"n\"\u003ech\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eDequeue\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;The removed value: {0}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ech\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003ech\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eDequeue\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;The removed value: {0}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ech\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eReadKey\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e属性 \n    \u003cdiv id=\"属性-4\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%b1%9e%e6%80%a7-4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003e属性\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e描述\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003eCount\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e获取 Queue 中包含的元素个数。\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ch3 class=\"relative group\"\u003e方法 \n    \u003cdiv id=\"方法-4\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e6%b3%95-4\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003e序号\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e方法名 \u0026amp; 描述\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e1\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void Clear();\u003c/strong\u003e 从 Queue 中移除所有的元素。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e2\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual bool Contains( object obj );\u003c/strong\u003e 判断某个元素是否在 Queue 中。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e3\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual object Dequeue();\u003c/strong\u003e 移除并返回在 Queue 的开头的对象。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e4\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void Enqueue( object obj );\u003c/strong\u003e 向 Queue 的末尾添加一个对象。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e5\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual object[] ToArray();\u003c/strong\u003e 复制 Queue 到一个新的数组中。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e6\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003cstrong\u003epublic virtual void TrimToSize();\u003c/strong\u003e 设置容量为 Queue 中元素的实际个数。\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e","title":"12、集合","type":"posts"},{"content":"crypto库是Go生态中密码学功能的核心，它为Go开发者提供了一套全面、安全、保持现代化、提供安全默认值且易于使用的密码学工具，使得在Go应用程序中实现各种密码学功能需求时变得简单而可靠。\nGo的密码学功能（即我们统一称的crypto库）分为两个主要部分：标准库的crypto相关包和扩展库golang.org/x/crypto。这种分离设计有其特定的目的和优势：\nGo标准库的crypto相关包，包含了最基础、最稳定和使用最广泛的密码学算法。这些算法实现经过Go团队的严格审查，保证了长期稳定性和向后兼容性。同时，这些包是随Go安装包分发的，使用时再无需引入额外的依赖。 而golang.org/x/crypto则号称是Go标准库crypto相关包的补充库，虽然它同样由Go团队维护，但由于不是标准库，它可以包含更多实验性或较新的密码学算法及实现，并可以更快速的迭代和更新。这样它也可以成为Go标准库中一些crypto相关包的“孵化器”，就像当年golang.org/x/net/context提升为标准库context一样。 哈希函数 # md5 # 功能：实现MD5哈希算法 用途：生成数据的128位哈希值 package main import ( \u0026#34;crypto/md5\u0026#34; \u0026#34;encoding/hex\u0026#34; \u0026#34;fmt\u0026#34; ) func main() { sum := md5.Sum([]byte(\u0026#34;HelloWorld\u0026#34;)) str := hex.EncodeToString(sum[:]) fmt.Println(str) } 使用建议：不推荐用于安全相关用途，因为MD5已被证明不够安全。 sha1 # 功能：实现SHA-1哈希算法 用途：生成数据的160位哈希值 package main import ( \u0026#34;crypto/sha1\u0026#34; \u0026#34;encoding/hex\u0026#34; \u0026#34;fmt\u0026#34; ) func main() { sum := sha1.Sum([]byte(\u0026#34;HelloWorld\u0026#34;)) str := hex.EncodeToString(sum[:]) fmt.Println(str) } 使用建议：不推荐用于安全相关用途，因为SHA-1已被证明存在碰撞风险。 sha256 # 功能：实现SHA-256哈希算法 用途：生成数据的256位哈希值 package main import ( \u0026#34;crypto/sha256\u0026#34; \u0026#34;encoding/hex\u0026#34; \u0026#34;fmt\u0026#34; ) func main() { sum := sha256.Sum256([]byte(\u0026#34;HelloWorld\u0026#34;)) str := hex.EncodeToString(sum[:]) fmt.Println(str) } 使用建议：推荐使用，安全性高。 sha512 # 功能：实现SHA-512哈希算法 用途：生成数据的512位哈希值 package main import ( \u0026#34;crypto/sha512\u0026#34; \u0026#34;encoding/hex\u0026#34; \u0026#34;fmt\u0026#34; ) func main() { sum := sha512.Sum512([]byte(\u0026#34;HelloWorld\u0026#34;)) str := hex.EncodeToString(sum[:]) fmt.Println(str) } 使用建议：推荐使用，安全性很高。 加密和解密 # 密码加密 # 用于密码存储和校验。\npackage utils import ( \u0026#34;golang.org/x/crypto/bcrypt\u0026#34; ) // HashPassword 对密码进行哈希加密 func HashPassword(password string) (string, error) { fromPassword, err := bcrypt.GenerateFromPassword([]byte(password), 14) return string(fromPassword), err } // CheckPasswordHash 验证密码 func CheckPasswordHash(hash, password string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil } ","date":"2025-05-21","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/12d682f6/22fd12f2/","section":"文章","summary":"\u003cp\u003e\u003ccode\u003ecrypto\u003c/code\u003e库是Go生态中密码学功能的核心，它为Go开发者提供了一套全面、安全、保持现代化、提供安全默认值且易于使用的密码学工具，使得在Go应用程序中实现各种密码学功能需求时变得简单而可靠。\u003c/p\u003e","title":"13、crypto","type":"posts"},{"content":"Tkinter 为了提供了三种标准对话框模块，它们分别是：\nmessagebox filedialog colorchooser messagebox（消息对话框） # from logging import root import tkinter as tk from tkinter import messagebox root = tk.Tk() label = tk.Label(root) label.pack() def open_message(): label[\u0026#39;text\u0026#39;] = messagebox.askokcancel(\u0026#34;Python Demo\u0026#34;, \u0026#34;发射核弹？\u0026#34;) tk.Button(root,text=\u0026#39;open_message\u0026#39;,command=open_message).pack() root.mainloop() 使用函数 对话框样式 askokcancel(title, message, options) askquestion(title, message, options) askretrycancel(title, message, options) askyesno(title, message, options) showerror(title, message, options) showinfo(title, message, options) showwarning(title, message, options) 参数 # 所有的这些函数都有相同的参数：\ntitle 参数毋庸置疑是设置标题栏的文本 message 参数是设置对话框的主要文本内容，你可以用 \u0026lsquo;\\n\u0026rsquo; 来实现换行 options 参数可以设置的选项和含义如下表所示 选项 含义 default 1. 设置默认的按钮（也就是按下回车响应的那个按钮） 2. 默认是第一个按钮（像“确定”，“是”或“重试”） 3. 可以设置的值根据对话框函数的不同可以选择：CANCEL，IGNORE，OK，NO，RETRY 或 YES icon 1. 指定对话框显示的图标 2. 可以指定的值有：ERROR，INFO，QUESTION 或 WARNING 3. 注意：不能指定自己的图标 parent 1. 如果不指定该选项，那么对话框默认显示在根窗口上 2. 如果想要将对话框显示在子窗口 w 上，那么可以设置 parent=w 返回值 # askokcancel()，askretrycancel() 和 askyesno() 返回布尔类型的值： 返回 True 表示用户点击了“确定”或“是”按钮 返回 False 表示用户点击了“取消”或“否”按钮 askquestion() 返回“yes”或“no”字符串表示用户点击了“是”或“否”按钮 showerror()，showinfo() 和 showwarning() 返回“ok”表示用户按下了“是”按钮 filedialog（文件对话框） # from logging import root import tkinter as tk from tkinter import filedialog root = tk.Tk() label = tk.Label(root) label.pack() def open_file(): label[\u0026#39;text\u0026#39;] = filedialog.askopenfilename() tk.Button(root,text=\u0026#39;open_file\u0026#39;,command=open_file).pack() root.mainloop() askopenfilename(**option) 和 asksaveasfilename(**option)，分别用于打开文件和保存文件。\n两个函数可供设置的选项是一样的，下边列举了可用的选项及含义：\n选项 含义 defaultextension 1. 指定文件的后缀 ；2. 例如：defaultextension=\u0026quot;.jpg\u0026quot;，那么当用户输入一个文件名 \u0026ldquo;logo\u0026rdquo; 的时候，文件名会自动添加后缀为 \u0026ldquo;logo.jpg\u0026rdquo; ；3. 注意：如果用户输入文件名包含后缀，那么该选项不生效 filetypes 1. 指定筛选文件类型的下拉菜单选项 ；2. 该选项的值是由 2 元祖构成的列表 ；3. 每个 2 元祖由（类型名，后缀）构成，例如：filetypes=[(\u0026quot;PNG\u0026quot;, \u0026quot;.png\u0026quot;), (\u0026quot;JPG\u0026quot;, \u0026quot;.jpg\u0026quot;), (\u0026quot;GIF\u0026quot;, \u0026quot;.gif\u0026quot;)] initialdir 1. 指定打开/保存文件的默认路径 ；2. 默认路径是当前文件夹 parent 1. 如果不指定该选项，那么对话框默认显示在根窗口上 ；2. 如果想要将对话框显示在子窗口 w 上，那么可以设置 parent=w title 指定文件对话框的标题栏文本 colorchooser（颜色选择对话框） # import tkinter as tk root = tk.Tk() def callback(): fileName = tk.colorchooser.askcolor() print(fileName) tk.Button(root, text=\u0026#34;选择颜色\u0026#34;, command=callback).pack() root.mainloop() askcolor(color, **option) 函数的 color 参数用于指定初始化的颜色，默认是浅灰色\noption 参数可以指定的选项及含义如下：\n选项 含义 title 指定颜色对话框的标题栏文本 parent 1. 如果不指定该选项，那么对话框默认显示在根窗口上 2. 如果想要将对话框显示在子窗口 w 上，那么可以设置 parent=w ","date":"2025-04-15","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/a879fe06/04d1b382/","section":"文章","summary":"\u003cp\u003eTkinter 为了提供了三种标准对话框模块，它们分别是：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003emessagebox\u003c/li\u003e\n\u003cli\u003efiledialog\u003c/li\u003e\n\u003cli\u003ecolorchooser\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2 class=\"relative group\"\u003emessagebox（消息对话框） \n    \u003cdiv id=\"messagebox消息对话框\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#messagebox%e6%b6%88%e6%81%af%e5%af%b9%e8%af%9d%e6%a1%86\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003elogging\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eroot\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003etkinter\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nn\"\u003etk\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003etkinter\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003emessagebox\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eTk\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elabel\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLabel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elabel\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epack\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003eopen_message\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elabel\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;text\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emessagebox\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003easkokcancel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Python Demo\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;发射核弹？\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003etk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eButton\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003etext\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;open_message\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ecommand\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eopen_message\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epack\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emainloop\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"image-20250415145958365\"\n    data-zoom-src=\"/posts/1b37041b/cf2af7cb/a879fe06/04d1b382/image/image-20250415145958365.png\"\n    src=\"/posts/1b37041b/cf2af7cb/a879fe06/04d1b382/image/image-20250415145958365.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"13、标准对话框","type":"posts"},{"content":"PanedWindow 组件（Tk8.4 新增）是一个空间管理组件。跟Frame 组件类似，都是为组件提供一个框架，不过 PanedWindow 允许让用户调整应用程序的空间划分。\nimport tkinter as tk root = tk.Tk() pw = tk.PanedWindow(root,orient=\u0026#39;vertical\u0026#39;,sashwidth=3,background=\u0026#39;black\u0026#39;) pw.pack(fill=\u0026#39;both\u0026#39;) top_label = tk.Label(pw,text=\u0026#39;top_label\u0026#39;) pw.add(top_label) bottom_label = tk.Label(pw,text=\u0026#39;bottom_label\u0026#39;) pw.add(bottom_label) root.mainloop() 属性 # 选项 含义 background 设置背景颜色 bg 跟 background 一样 borderwidth 设置边框宽度 bd 跟 borderwidth 一样 cursor 1. 指定当鼠标在 PanedWindow 上飘过的时候的鼠标样式 2. 默认值由系统指定 handlepad 1. 调节“手柄”的位置 2. 例如 orient=\u0026ldquo;vertical\u0026rdquo;，则 handlepad 选项表示“分割线”上的手柄与左端的距离 3. 默认值是 8 像素 handlesize 1. 设置“手柄”的尺寸（由于“手柄”必须是一个正方形，所以是设置正方形的边长） 2. 默认值是 8 像素 height 1. 设置 PanedWindow 的高度 2. 如果忽略该选项，则高度由子组件的尺寸决定 opaqueresize 1. 该选项定义了用户调整窗格尺寸的操作 2. 如果该选项的值为 True（默认），窗格的尺寸随用户鼠标的拖拽而改变 3. 如果该选项的值为 False，窗格的尺寸在用户释放鼠标的时候才更新到新的位置 orient 1. 指定窗格的分布方式 2. 可以是 \u0026ldquo;horizontal\u0026rdquo;（横向分布）和 \u0026ldquo;vertical\u0026rdquo;（纵向分布） relief 1. 指定边框样式 2. 默认值是 \u0026ldquo;flat\u0026rdquo; 3. 另外你还可以设置 \u0026ldquo;sunken\u0026rdquo;，\u0026ldquo;raised\u0026rdquo;，\u0026ldquo;groove\u0026rdquo; 或 \u0026ldquo;ridge\u0026rdquo; sashpad 设置每一条分割线到窗格间的间距 sashrelief 1. 设置分割线的样式 2. 默认值是：\u0026ldquo;flat\u0026rdquo; 3. 另外你还可以设置 \u0026ldquo;sunken\u0026rdquo;，\u0026ldquo;raised\u0026rdquo;，\u0026ldquo;groove\u0026rdquo; 或 \u0026ldquo;ridge\u0026rdquo; sashwidth 设置分割线的宽度 showhandle 1. 设置是否显示调节窗格的手柄 2. 默认值为 False width 1. 设置 PanedWindow 的宽度 2. 如果忽略该选项，则高度由子组件的尺寸决定 方法 # add(child, **options) 添加一个新的子组件到窗格中 下方表格列举了各个 options 选项具体含义： 选项 含义 after 添加新的子组件到指定子组件后边 before 添加新的子组件到指定子组件前边 height 指定子组件的高度 minsize 1. 该选项控制窗格不得低于的值 2. 如果 orient=\u0026ldquo;horizontal\u0026rdquo;，表示窗格的宽度不得低于该选项的值 3. 如果 orient=\u0026ldquo;vertical\u0026rdquo;，表示窗格的高度不得低于该选项的值 padx 指定子组件的水平间距 pady 指定子组件的垂直间距 sticky 1. 当窗格的尺寸大于子组件时，该选项指定子组件位于窗格的位置 2. 可选的值有：\u0026ldquo;e\u0026rdquo;、\u0026ldquo;s\u0026rdquo;、\u0026ldquo;w\u0026rdquo;、\u0026ldquo;n\u0026rdquo;（分别代表东南西北四个方位）以及它们的组合值 3. 例如 NE 表示子组件显示在右上角的位置 width 指定子组件的宽度 forget(child) 删除一个子组件 identify(x, y) 给定一个坐标（x, y），返回该坐标所在的元素名称 如果该坐标位于子组件上，返回空字符串 如果该坐标位于分割线上，返回一个二元组（n, \u0026lsquo;sash\u0026rsquo;），n 为 0 表示第一个分割线 如果该坐标位于手柄上，返回一个二元组（n, \u0026lsquo;handle\u0026rsquo;），n 为 0 表示第一个手柄 panecget(child, option) 获得子组件指定选项的值 paneconfig(child, **options) 设置子组件的各种选项 下方表格列举了各个 options 选项具体含义： 选项 含义 after 添加新的子组件到指定子组件后边 before 添加新的子组件到指定子组件前边 height 指定子组件的高度 minsize 1. 该选项控制窗格不得低于的值 ；2. 如果 orient=\u0026ldquo;horizontal\u0026rdquo;，表示窗格的宽度不得低于该选项的值 ；3. 如果 orient=\u0026ldquo;vertical\u0026rdquo;，表示窗格的高度不得低于该选项的值 padx 指定子组件的水平间距 pady 指定子组件的垂直间距 sticky 1. 当窗格的尺寸大于子组件时，该选项指定子组件位于窗格的位置 ；2. 可选的值有：\u0026ldquo;e\u0026rdquo;、\u0026ldquo;s\u0026rdquo;、\u0026ldquo;w\u0026rdquo;、\u0026ldquo;n\u0026rdquo;（分别代表东南西北四个方位）以及它们的组合值 ；3. 例如 NE 表示子组件显示在右上角的位置 width 指定子组件的宽度 ","date":"2025-04-15","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/a879fe06/693f91c1/","section":"文章","summary":"\u003cp\u003ePanedWindow 组件（Tk8.4 新增）是一个空间管理组件。跟Frame 组件类似，都是为组件提供一个框架，不过 PanedWindow 允许让用户调整应用程序的空间划分。\u003c/p\u003e","title":"14、PanedWindow","type":"posts"},{"content":"","date":"2025-07-31","externalUrl":null,"permalink":"/categories/bug-record/","section":"分类","summary":"","title":"Bug Record","type":"categories"},{"content":"","date":"2025-07-31","externalUrl":null,"permalink":"/posts/cdbc5e74/","section":"文章","summary":"","title":"Bug Record","type":"posts"},{"content":" 报错信息 # 服务启动后的前几次查询没有问题，多次查询后报这个错。\norg.postgresql.util.PSQLException:Unsupported binary encoding of timestamp. 原因 # 是因为PostgreSql默认使用二进制进行数据的传输，导致的jdbc解析失败。\n解决方案 # 数据库连接信息中配置不使用二进制传输binaryTransfer=false\njdbc:postgresql://localhost:5432/test?binaryTransfer=false ","date":"2025-07-31","externalUrl":null,"permalink":"/posts/cdbc5e74/06931720/e7e325a4/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e报错信息 \n    \u003cdiv id=\"报错信息\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8a%a5%e9%94%99%e4%bf%a1%e6%81%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e服务启动后的前几次查询没有问题，多次查询后报这个错。\u003c/p\u003e","title":"PostgreSQL出现二进制Timestamp转换的问题","type":"posts"},{"content":"","date":"2025-07-31","externalUrl":null,"permalink":"/posts/cdbc5e74/06931720/","section":"文章","summary":"","title":"数据库Bug","type":"posts"},{"content":"","date":"2025-07-31","externalUrl":null,"permalink":"/series/%E6%95%B0%E6%8D%AE%E5%BA%93bug/","section":"Series","summary":"","title":"数据库Bug","type":"series"},{"content":"","date":"2025-07-31","externalUrl":null,"permalink":"/tags/%E6%95%B0%E6%8D%AE%E5%BA%93bug/","section":"标签","summary":"","title":"数据库Bug","type":"tags"},{"content":"","date":"2025-07-30","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/1bd46fa5/","section":"文章","summary":"","title":"Gin","type":"posts"},{"content":"","date":"2025-07-30","externalUrl":null,"permalink":"/series/gin/","section":"Series","summary":"","title":"Gin","type":"series"},{"content":"","date":"2025-07-30","externalUrl":null,"permalink":"/tags/gin/","section":"标签","summary":"","title":"Gin","type":"tags"},{"content":"","date":"2025-07-30","externalUrl":null,"permalink":"/categories/golang/","section":"分类","summary":"","title":"GoLang","type":"categories"},{"content":"","date":"2025-07-30","externalUrl":null,"permalink":"/posts/2d3150b2/","section":"文章","summary":"","title":"GoLang","type":"posts"},{"content":"","date":"2025-07-30","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/","section":"文章","summary":"","title":"GoWeb","type":"posts"},{"content":"","date":"2025-07-30","externalUrl":null,"permalink":"/tags/goweb/","section":"标签","summary":"","title":"GoWeb","type":"tags"},{"content":"","date":"2025-07-23","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/","section":"文章","summary":"","title":"常用包","type":"posts"},{"content":"","date":"2025-07-23","externalUrl":null,"permalink":"/tags/%E5%B8%B8%E7%94%A8%E5%8C%85/","section":"标签","summary":"","title":"常用包","type":"tags"},{"content":"","date":"2025-07-23","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/29ede47b/","section":"文章","summary":"","title":"第三方包","type":"posts"},{"content":"","date":"2025-07-23","externalUrl":null,"permalink":"/series/%E7%AC%AC%E4%B8%89%E6%96%B9%E5%8C%85/","section":"Series","summary":"","title":"第三方包","type":"series"},{"content":"","date":"2025-07-23","externalUrl":null,"permalink":"/tags/%E7%AC%AC%E4%B8%89%E6%96%B9%E5%8C%85/","section":"标签","summary":"","title":"第三方包","type":"tags"},{"content":"","date":"2025-07-21","externalUrl":null,"permalink":"/posts/bafd68f1/b2321cb9/","section":"文章","summary":"","title":"Vue","type":"posts"},{"content":"","date":"2025-07-21","externalUrl":null,"permalink":"/series/vue/","section":"Series","summary":"","title":"Vue","type":"series"},{"content":"","date":"2025-07-21","externalUrl":null,"permalink":"/tags/vue/","section":"标签","summary":"","title":"Vue","type":"tags"},{"content":"","date":"2025-07-21","externalUrl":null,"permalink":"/categories/%E5%89%8D%E7%AB%AF/","section":"分类","summary":"","title":"前端","type":"categories"},{"content":"","date":"2025-07-21","externalUrl":null,"permalink":"/posts/bafd68f1/","section":"文章","summary":"","title":"前端","type":"posts"},{"content":"","date":"2025-07-11","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/db9fe107/","section":"文章","summary":"","title":"数据库","type":"posts"},{"content":"","date":"2025-07-11","externalUrl":null,"permalink":"/series/%E6%95%B0%E6%8D%AE%E5%BA%93/","section":"Series","summary":"","title":"数据库","type":"series"},{"content":"","date":"2025-07-11","externalUrl":null,"permalink":"/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/","section":"标签","summary":"","title":"数据库","type":"tags"},{"content":" url/sys-dict.js # 接口请求，根据字典的 type 请求\nimport {getUrl} from \u0026#34;@/api/requester\u0026#34;; export const getDictDataByType = (type) =\u0026gt; getUrl(\u0026#34;/dict/getDictDataByType/\u0026#34;,type) store/Dict.js # 负责 Vuex 存取字典相关内容\nexport default { namespaced: true, state: { dictPairs: { }, dictList: { } }, getters: { dictPair: state =\u0026gt; state.dictPair, dictList: state =\u0026gt; state.dictList }, mutations: { setDictPairs(state, {type, pair}){ state.dictPairs[type] = pair }, setDictList(state, {type, list}){ state.dictList[type] = list } }, actions: { }, } store/index.js # import Vue from \u0026#39;vue\u0026#39; import Vuex from \u0026#39;vuex\u0026#39; import Dict from \u0026#34;./Dict\u0026#34;; Vue.use(Vuex) export default new Vuex.Store({ modules: { Dict } }) util/dict.js # 核心逻辑，负责字典的解析、读取、混入\nimport {getDictDataByType} from \u0026#34;@/api/url/sys-dict\u0026#34;; // 解析接口请求的响应，获取 value 和 lable function parseDictList(list){ let dictPair = {} let dictList = [] list.forEach(item =\u0026gt; { // 根据实际请求修改参数名称 let value = item.dictValue; let label = item.dictLabel; dictList.push({ value:value, label:label }) dictPair[value] = label; }) return { dictPair:dictPair, dictList:dictList } } // Mixin export default { data(){ if (this.$options === undefined || this.$options.dicts === undefined || this.$options.dicts === null) { return {} } return{ dict:{}, dictPairs:{}, } }, methods: { // 根据字典类型的 value 获取 label dictLabel(type,value){ let pair = this.dictPairs[type]; if (pair){ let label = pair[value]; if (label){ return label; } } return value; } }, created() { const dicts = this.$options.dicts; if (!dicts || dicts.length \u0026lt;= 0){ return } let that = this; dicts.forEach(type =\u0026gt; { // 判断 store 中是否存在 if (!that.$store.state.Dict.dictPairs[type] || !that.$store.state.Dict.dictList[type]){ // store 中不存在则请求 getDictDataByType(type).then( (data) =\u0026gt; { if (data.code == 200){ let result = parseDictList(data.data) let dictPair = result.dictPair; let dictList = result.dictList; that.dict[type] = dictList; that.dictPairs[type] = dictPair; // 存储到 Vuex 中，可以根据实际情况调整 that.$store.commit(\u0026#39;Dict/setDictPairs\u0026#39;, {type:type, pair:dictPair}); that.$store.commit(\u0026#39;Dict/setDictList\u0026#39;, {type:type, list:dictList}); // 确保字典加载完成后页面重新渲染 that.$nextTick(() =\u0026gt; { that.$forceUpdate(); }); }else { this.$message.error(data.msg) } } ) }else { that.dict[type] = this.$store.state.Dict.dictList[type]; that.dictPairs[type] = this.$store.state.Dict.dictPairs[type]; } }) } } main.js # 全局混入\nimport dict from \u0026#34;./utils/dict\u0026#34; Vue.mixin(dict); 使用 # \u0026lt;template\u0026gt; \u0026lt;span\u0026gt;{{dictLabel(\u0026#39;sys_user_sex\u0026#39;,userInfo.userSex)}}\u0026lt;/span\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026#34;BaseInfo\u0026#34;, dicts:[\u0026#34;sys_user_sex\u0026#34;], data(){ userInfo: { nickName: null, userSex: null } }, methods: {}, } \u0026lt;/script\u0026gt; ","date":"2025-07-04","externalUrl":null,"permalink":"/posts/add78db0/83b3b1a8/b598b498/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eurl/sys-dict.js \n    \u003cdiv id=\"urlsys-dictjs\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#urlsys-dictjs\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e接口请求，根据字典的 type 请求\u003c/p\u003e","title":"vue2 实现Dict 自动加载","type":"posts"},{"content":"","date":"2025-07-04","externalUrl":null,"permalink":"/categories/%E4%BB%A3%E7%A0%81%E5%9D%97/","section":"分类","summary":"","title":"代码块","type":"categories"},{"content":"","date":"2025-07-04","externalUrl":null,"permalink":"/posts/add78db0/","section":"文章","summary":"","title":"代码块","type":"posts"},{"content":"","date":"2025-07-04","externalUrl":null,"permalink":"/posts/add78db0/83b3b1a8/","section":"文章","summary":"","title":"前端代码","type":"posts"},{"content":"","date":"2025-07-04","externalUrl":null,"permalink":"/series/%E5%89%8D%E7%AB%AF%E4%BB%A3%E7%A0%81/","section":"Series","summary":"","title":"前端代码","type":"series"},{"content":"","date":"2025-07-04","externalUrl":null,"permalink":"/tags/%E5%89%8D%E7%AB%AF%E4%BB%A3%E7%A0%81/","section":"标签","summary":"","title":"前端代码","type":"tags"},{"content":"","date":"2025-07-03","externalUrl":null,"permalink":"/categories/java/","section":"分类","summary":"","title":"Java","type":"categories"},{"content":"","date":"2025-07-03","externalUrl":null,"permalink":"/posts/3ab7256e/","section":"文章","summary":"","title":"Java","type":"posts"},{"content":"","date":"2025-07-03","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/","section":"文章","summary":"","title":"JavaEE","type":"posts"},{"content":"","date":"2025-07-03","externalUrl":null,"permalink":"/tags/javaee/","section":"标签","summary":"","title":"JavaEE","type":"tags"},{"content":"","date":"2025-07-03","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/4ee304f3/","section":"文章","summary":"","title":"Maven","type":"posts"},{"content":"","date":"2025-07-03","externalUrl":null,"permalink":"/series/maven/","section":"Series","summary":"","title":"Maven","type":"series"},{"content":"","date":"2025-07-03","externalUrl":null,"permalink":"/tags/maven/","section":"标签","summary":"","title":"Maven","type":"tags"},{"content":"","date":"2025-06-27","externalUrl":null,"permalink":"/posts/2d3150b2/0e0cf2f4/","section":"文章","summary":"","title":"GoLang高级","type":"posts"},{"content":"","date":"2025-06-27","externalUrl":null,"permalink":"/series/golang%E9%AB%98%E7%BA%A7/","section":"Series","summary":"","title":"GoLang高级","type":"series"},{"content":"","date":"2025-06-27","externalUrl":null,"permalink":"/tags/golang%E9%AB%98%E7%BA%A7/","section":"标签","summary":"","title":"GoLang高级","type":"tags"},{"content":"","date":"2025-06-27","externalUrl":null,"permalink":"/posts/2d3150b2/90176009/12d682f6/","section":"文章","summary":"","title":"标准包","type":"posts"},{"content":"","date":"2025-06-27","externalUrl":null,"permalink":"/series/%E6%A0%87%E5%87%86%E5%8C%85/","section":"Series","summary":"","title":"标准包","type":"series"},{"content":"","date":"2025-06-27","externalUrl":null,"permalink":"/tags/%E6%A0%87%E5%87%86%E5%8C%85/","section":"标签","summary":"","title":"标准包","type":"tags"},{"content":"","date":"2025-06-25","externalUrl":null,"permalink":"/series/goweb/","section":"Series","summary":"","title":"GoWeb","type":"series"},{"content":"","date":"2025-05-27","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/","section":"文章","summary":"","title":"NodeJS","type":"posts"},{"content":"","date":"2025-05-27","externalUrl":null,"permalink":"/tags/nodejs/","section":"标签","summary":"","title":"NodeJS","type":"tags"},{"content":"","date":"2025-05-27","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/65df2b3b/","section":"文章","summary":"","title":"NodeJS API","type":"posts"},{"content":"","date":"2025-05-27","externalUrl":null,"permalink":"/series/nodejs-api/","section":"Series","summary":"","title":"NodeJS API","type":"series"},{"content":"","date":"2025-05-27","externalUrl":null,"permalink":"/tags/nodejs-api/","section":"标签","summary":"","title":"NodeJS API","type":"tags"},{"content":"","date":"2025-05-27","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/6d366d27/","section":"文章","summary":"","title":"TypeScript","type":"posts"},{"content":"","date":"2025-05-27","externalUrl":null,"permalink":"/series/typescript/","section":"Series","summary":"","title":"TypeScript","type":"series"},{"content":"","date":"2025-05-27","externalUrl":null,"permalink":"/tags/typescript/","section":"标签","summary":"","title":"TypeScript","type":"tags"},{"content":"","date":"2025-05-27","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/","section":"文章","summary":"","title":"语言","type":"posts"},{"content":"","date":"2025-05-27","externalUrl":null,"permalink":"/tags/%E8%AF%AD%E8%A8%80/","section":"标签","summary":"","title":"语言","type":"tags"},{"content":"","date":"2025-05-23","externalUrl":null,"permalink":"/posts/bafd68f1/3233cd21/79502aa8/","section":"文章","summary":"","title":"NodeJS环境","type":"posts"},{"content":"","date":"2025-05-23","externalUrl":null,"permalink":"/series/nodejs%E7%8E%AF%E5%A2%83/","section":"Series","summary":"","title":"NodeJS环境","type":"series"},{"content":"","date":"2025-05-23","externalUrl":null,"permalink":"/tags/nodejs%E7%8E%AF%E5%A2%83/","section":"标签","summary":"","title":"NodeJS环境","type":"tags"},{"content":"","date":"2025-05-22","externalUrl":null,"permalink":"/posts/2d3150b2/a56b86e6/","section":"文章","summary":"","title":"CLI","type":"posts"},{"content":"","date":"2025-05-22","externalUrl":null,"permalink":"/series/cli/","section":"Series","summary":"","title":"CLI","type":"series"},{"content":"","date":"2025-05-22","externalUrl":null,"permalink":"/tags/cli/","section":"标签","summary":"","title":"CLI","type":"tags"},{"content":"","date":"2025-05-22","externalUrl":null,"permalink":"/posts/2d3150b2/9f158c96/","section":"文章","summary":"","title":"GUI","type":"posts"},{"content":"","date":"2025-05-22","externalUrl":null,"permalink":"/tags/gui/","section":"标签","summary":"","title":"GUI","type":"tags"},{"content":"","date":"2025-05-22","externalUrl":null,"permalink":"/posts/2d3150b2/9f158c96/f88084c8/","section":"文章","summary":"","title":"Wails","type":"posts"},{"content":"","date":"2025-05-22","externalUrl":null,"permalink":"/series/wails/","section":"Series","summary":"","title":"Wails","type":"series"},{"content":"","date":"2025-05-22","externalUrl":null,"permalink":"/tags/wails/","section":"标签","summary":"","title":"Wails","type":"tags"},{"content":"","date":"2025-05-19","externalUrl":null,"permalink":"/posts/2d3150b2/02a0817f/","section":"文章","summary":"","title":"GoLang基础","type":"posts"},{"content":"","date":"2025-05-19","externalUrl":null,"permalink":"/series/golang%E5%9F%BA%E7%A1%80/","section":"Series","summary":"","title":"GoLang基础","type":"series"},{"content":"","date":"2025-05-19","externalUrl":null,"permalink":"/tags/golang%E5%9F%BA%E7%A1%80/","section":"标签","summary":"","title":"GoLang基础","type":"tags"},{"content":"","date":"2025-05-19","externalUrl":null,"permalink":"/posts/2d3150b2/89595a91/8b430461/","section":"文章","summary":"","title":"基础","type":"posts"},{"content":"","date":"2025-05-19","externalUrl":null,"permalink":"/series/%E5%9F%BA%E7%A1%80/","section":"Series","summary":"","title":"基础","type":"series"},{"content":"","date":"2025-05-19","externalUrl":null,"permalink":"/tags/%E5%9F%BA%E7%A1%80/","section":"标签","summary":"","title":"基础","type":"tags"},{"content":"","date":"2025-05-14","externalUrl":null,"permalink":"/posts/2d3150b2/6742e0c3/","section":"文章","summary":"","title":"设计模式","type":"posts"},{"content":"","date":"2025-05-14","externalUrl":null,"permalink":"/series/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/","section":"Series","summary":"","title":"设计模式","type":"series"},{"content":"","date":"2025-05-14","externalUrl":null,"permalink":"/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/","section":"标签","summary":"","title":"设计模式","type":"tags"},{"content":"","date":"2025-05-12","externalUrl":null,"permalink":"/posts/26654e7b/5a80d994/","section":"文章","summary":"","title":"MacOS","type":"posts"},{"content":"","date":"2025-05-12","externalUrl":null,"permalink":"/series/macos/","section":"Series","summary":"","title":"MacOS","type":"series"},{"content":"","date":"2025-05-12","externalUrl":null,"permalink":"/tags/macos/","section":"标签","summary":"","title":"MacOS","type":"tags"},{"content":"","date":"2025-05-12","externalUrl":null,"permalink":"/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/","section":"分类","summary":"","title":"操作系统","type":"categories"},{"content":"","date":"2025-05-12","externalUrl":null,"permalink":"/posts/26654e7b/","section":"文章","summary":"","title":"操作系统","type":"posts"},{"content":"","date":"2025-05-08","externalUrl":null,"permalink":"/posts/2d3150b2/9f158c96/afc23a3a/","section":"文章","summary":"","title":"Fyne","type":"posts"},{"content":"","date":"2025-05-08","externalUrl":null,"permalink":"/series/fyne/","section":"Series","summary":"","title":"Fyne","type":"series"},{"content":"","date":"2025-05-08","externalUrl":null,"permalink":"/tags/fyne/","section":"标签","summary":"","title":"Fyne","type":"tags"},{"content":"","date":"2025-05-07","externalUrl":null,"permalink":"/posts/3ab7256e/4e1989d8/","section":"文章","summary":"","title":"AWT和Swing","type":"posts"},{"content":"","date":"2025-05-07","externalUrl":null,"permalink":"/series/awt%E5%92%8Cswing/","section":"Series","summary":"","title":"AWT和Swing","type":"series"},{"content":"","date":"2025-05-07","externalUrl":null,"permalink":"/tags/awt%E5%92%8Cswing/","section":"标签","summary":"","title":"AWT和Swing","type":"tags"},{"content":"","date":"2025-04-28","externalUrl":null,"permalink":"/categories/ai/","section":"分类","summary":"","title":"AI","type":"categories"},{"content":"","date":"2025-04-28","externalUrl":null,"permalink":"/posts/ffe909ab/","section":"文章","summary":"","title":"AI","type":"posts"},{"content":"","date":"2025-04-28","externalUrl":null,"permalink":"/posts/bafd68f1/8e24967e/","section":"文章","summary":"","title":"Electron","type":"posts"},{"content":"","date":"2025-04-28","externalUrl":null,"permalink":"/series/electron/","section":"Series","summary":"","title":"Electron","type":"series"},{"content":"","date":"2025-04-28","externalUrl":null,"permalink":"/tags/electron/","section":"标签","summary":"","title":"Electron","type":"tags"},{"content":"","date":"2025-04-28","externalUrl":null,"permalink":"/posts/ffe909ab/2fd62ad9/","section":"文章","summary":"","title":"MCP","type":"posts"},{"content":"","date":"2025-04-28","externalUrl":null,"permalink":"/series/mcp/","section":"Series","summary":"","title":"MCP","type":"series"},{"content":"","date":"2025-04-28","externalUrl":null,"permalink":"/tags/mcp/","section":"标签","summary":"","title":"MCP","type":"tags"},{"content":"","date":"2025-04-27","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/0d08345a/","section":"文章","summary":"","title":"JavaScript","type":"posts"},{"content":"","date":"2025-04-27","externalUrl":null,"permalink":"/tags/javascript/","section":"标签","summary":"","title":"JavaScript","type":"tags"},{"content":"","date":"2025-04-27","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/0d08345a/47e69f85/","section":"文章","summary":"","title":"JavaScript 高级","type":"posts"},{"content":"","date":"2025-04-27","externalUrl":null,"permalink":"/series/javascript-%E9%AB%98%E7%BA%A7/","section":"Series","summary":"","title":"JavaScript 高级","type":"series"},{"content":"","date":"2025-04-27","externalUrl":null,"permalink":"/tags/javascript-%E9%AB%98%E7%BA%A7/","section":"标签","summary":"","title":"JavaScript 高级","type":"tags"},{"content":"","date":"2025-04-25","externalUrl":null,"permalink":"/posts/bafd68f1/81ac3c6b/0d08345a/1403736b/","section":"文章","summary":"","title":"JavaScript基础","type":"posts"},{"content":"","date":"2025-04-25","externalUrl":null,"permalink":"/series/javascript%E5%9F%BA%E7%A1%80/","section":"Series","summary":"","title":"JavaScript基础","type":"series"},{"content":"","date":"2025-04-25","externalUrl":null,"permalink":"/tags/javascript%E5%9F%BA%E7%A1%80/","section":"标签","summary":"","title":"JavaScript基础","type":"tags"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/","section":"文章","summary":"","title":"GUI","type":"posts"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/901ba1e3/","section":"文章","summary":"","title":"PySide6","type":"posts"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/series/pyside6/","section":"Series","summary":"","title":"PySide6","type":"series"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/tags/pyside6/","section":"标签","summary":"","title":"PySide6","type":"tags"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/categories/python/","section":"分类","summary":"","title":"Python","type":"categories"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/","section":"文章","summary":"","title":"Python","type":"posts"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/aae761a6/","section":"文章","summary":"","title":"python基础","type":"posts"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/series/python%E5%9F%BA%E7%A1%80/","section":"Series","summary":"","title":"Python基础","type":"series"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/tags/python%E5%9F%BA%E7%A1%80/","section":"标签","summary":"","title":"Python基础","type":"tags"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/9766939c/","section":"文章","summary":"","title":"内建模块","type":"posts"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/series/%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97/","section":"Series","summary":"","title":"内建模块","type":"series"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/tags/%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97/","section":"标签","summary":"","title":"内建模块","type":"tags"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/2e132ccd/","section":"文章","summary":"","title":"打包工具","type":"posts"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/series/%E6%89%93%E5%8C%85%E5%B7%A5%E5%85%B7/","section":"Series","summary":"","title":"打包工具","type":"series"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/tags/%E6%89%93%E5%8C%85%E5%B7%A5%E5%85%B7/","section":"标签","summary":"","title":"打包工具","type":"tags"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/46b8de4c/","section":"文章","summary":"","title":"第三方模块","type":"posts"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/series/%E7%AC%AC%E4%B8%89%E6%96%B9%E6%A8%A1%E5%9D%97/","section":"Series","summary":"","title":"第三方模块","type":"series"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/tags/%E7%AC%AC%E4%B8%89%E6%96%B9%E6%A8%A1%E5%9D%97/","section":"标签","summary":"","title":"第三方模块","type":"tags"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/posts/1b37041b/b730e80e/","section":"文章","summary":"","title":"虚拟环境","type":"posts"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/series/%E8%99%9A%E6%8B%9F%E7%8E%AF%E5%A2%83/","section":"Series","summary":"","title":"虚拟环境","type":"series"},{"content":"","date":"2025-04-24","externalUrl":null,"permalink":"/tags/%E8%99%9A%E6%8B%9F%E7%8E%AF%E5%A2%83/","section":"标签","summary":"","title":"虚拟环境","type":"tags"},{"content":"","date":"2025-04-22","externalUrl":null,"permalink":"/posts/1b37041b/a64b122d/","section":"文章","summary":"","title":"python高级","type":"posts"},{"content":"","date":"2025-04-22","externalUrl":null,"permalink":"/series/python%E9%AB%98%E7%BA%A7/","section":"Series","summary":"","title":"Python高级","type":"series"},{"content":"","date":"2025-04-22","externalUrl":null,"permalink":"/tags/python%E9%AB%98%E7%BA%A7/","section":"标签","summary":"","title":"Python高级","type":"tags"},{"content":"","date":"2025-04-18","externalUrl":null,"permalink":"/posts/69064821/f1464f3a/","section":"文章","summary":"","title":"Blender","type":"posts"},{"content":"","date":"2025-04-18","externalUrl":null,"permalink":"/series/blender/","section":"Series","summary":"","title":"Blender","type":"series"},{"content":"","date":"2025-04-18","externalUrl":null,"permalink":"/tags/blender/","section":"标签","summary":"","title":"Blender","type":"tags"},{"content":"","date":"2025-04-18","externalUrl":null,"permalink":"/categories/%E6%B8%B8%E6%88%8F/","section":"分类","summary":"","title":"游戏","type":"categories"},{"content":"","date":"2025-04-18","externalUrl":null,"permalink":"/posts/69064821/","section":"文章","summary":"","title":"游戏","type":"posts"},{"content":"","date":"2025-04-15","externalUrl":null,"permalink":"/posts/1b37041b/cf2af7cb/a879fe06/","section":"文章","summary":"","title":"Tkinter","type":"posts"},{"content":"","date":"2025-04-15","externalUrl":null,"permalink":"/series/tkinter/","section":"Series","summary":"","title":"Tkinter","type":"series"},{"content":"","date":"2025-04-15","externalUrl":null,"permalink":"/tags/tkinter/","section":"标签","summary":"","title":"Tkinter","type":"tags"},{"content":"pymysql 是对 Python 中对 MySQL 进行操作的库\n安装 # pip install pymysql ","date":"2025-04-10","externalUrl":null,"permalink":"/posts/1b37041b/c6b05214/eb769d18/","section":"文章","summary":"\u003cp\u003epymysql 是对 Python 中对 MySQL 进行操作的库\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e安装 \n    \u003cdiv id=\"安装\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%ae%89%e8%a3%85\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003epip install pymysql\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"pymysql","type":"posts"},{"content":"","date":"2025-04-10","externalUrl":null,"permalink":"/posts/1b37041b/c6b05214/","section":"文章","summary":"","title":"数据库","type":"posts"},{"content":"","date":"2025-03-31","externalUrl":null,"permalink":"/posts/69064821/92082869/","section":"文章","summary":"","title":"Cocos","type":"posts"},{"content":"","date":"2025-03-31","externalUrl":null,"permalink":"/series/cocos/","section":"Series","summary":"","title":"Cocos","type":"series"},{"content":"","date":"2025-03-31","externalUrl":null,"permalink":"/tags/cocos/","section":"标签","summary":"","title":"Cocos","type":"tags"},{"content":"","date":"2025-03-25","externalUrl":null,"permalink":"/posts/b3f5e6ce/b7344362/","section":"文章","summary":"","title":"Gradle","type":"posts"},{"content":"","date":"2025-03-25","externalUrl":null,"permalink":"/series/gradle/","section":"Series","summary":"","title":"Gradle","type":"series"},{"content":"","date":"2025-03-25","externalUrl":null,"permalink":"/tags/gradle/","section":"标签","summary":"","title":"Gradle","type":"tags"},{"content":"","date":"2025-03-25","externalUrl":null,"permalink":"/categories/kotlin/","section":"分类","summary":"","title":"Kotlin","type":"categories"},{"content":"","date":"2025-03-25","externalUrl":null,"permalink":"/posts/b3f5e6ce/","section":"文章","summary":"","title":"Kotlin","type":"posts"},{"content":"","date":"2025-03-25","externalUrl":null,"permalink":"/posts/b3f5e6ce/1e30d624/","section":"文章","summary":"","title":"Kotlin高级","type":"posts"},{"content":"","date":"2025-03-25","externalUrl":null,"permalink":"/series/kotlin%E9%AB%98%E7%BA%A7/","section":"Series","summary":"","title":"Kotlin高级","type":"series"},{"content":"","date":"2025-03-25","externalUrl":null,"permalink":"/tags/kotlin%E9%AB%98%E7%BA%A7/","section":"标签","summary":"","title":"Kotlin高级","type":"tags"},{"content":"","date":"2025-03-24","externalUrl":null,"permalink":"/posts/b3f5e6ce/129ca5af/","section":"文章","summary":"","title":"Kotlin基础","type":"posts"},{"content":"","date":"2025-03-24","externalUrl":null,"permalink":"/series/kotlin%E5%9F%BA%E7%A1%80/","section":"Series","summary":"","title":"Kotlin基础","type":"series"},{"content":"","date":"2025-03-24","externalUrl":null,"permalink":"/tags/kotlin%E5%9F%BA%E7%A1%80/","section":"标签","summary":"","title":"Kotlin基础","type":"tags"},{"content":"","date":"2025-03-17","externalUrl":null,"permalink":"/posts/bafd68f1/f2b2596e/","section":"文章","summary":"","title":"uni-app","type":"posts"},{"content":"","date":"2025-03-17","externalUrl":null,"permalink":"/series/uni-app/","section":"Series","summary":"","title":"Uni-App","type":"series"},{"content":"","date":"2025-03-17","externalUrl":null,"permalink":"/tags/uni-app/","section":"标签","summary":"","title":"Uni-App","type":"tags"},{"content":"","date":"2025-02-26","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/","section":"文章","summary":"","title":"SpringFramework","type":"posts"},{"content":"","date":"2025-02-26","externalUrl":null,"permalink":"/tags/springframework/","section":"标签","summary":"","title":"SpringFramework","type":"tags"},{"content":"","date":"2025-02-26","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/82b168e2/","section":"文章","summary":"","title":"SpringSecurity","type":"posts"},{"content":"","date":"2025-02-26","externalUrl":null,"permalink":"/series/springsecurity/","section":"Series","summary":"","title":"SpringSecurity","type":"series"},{"content":"","date":"2025-02-26","externalUrl":null,"permalink":"/tags/springsecurity/","section":"标签","summary":"","title":"SpringSecurity","type":"tags"},{"content":"","date":"2025-02-24","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/","section":"文章","summary":"","title":"组件与中间件","type":"posts"},{"content":"","date":"2025-02-24","externalUrl":null,"permalink":"/series/%E7%BB%84%E4%BB%B6%E4%B8%8E%E4%B8%AD%E9%97%B4%E4%BB%B6/","section":"Series","summary":"","title":"组件与中间件","type":"series"},{"content":"","date":"2025-02-24","externalUrl":null,"permalink":"/tags/%E7%BB%84%E4%BB%B6%E4%B8%8E%E4%B8%AD%E9%97%B4%E4%BB%B6/","section":"标签","summary":"","title":"组件与中间件","type":"tags"},{"content":"","date":"2025-02-18","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/","section":"文章","summary":"","title":"JavaSE","type":"posts"},{"content":"","date":"2025-02-18","externalUrl":null,"permalink":"/tags/javase/","section":"标签","summary":"","title":"JavaSE","type":"tags"},{"content":"","date":"2025-02-18","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/fbeea4f4/","section":"文章","summary":"","title":"JavaSE高级","type":"posts"},{"content":"","date":"2025-02-18","externalUrl":null,"permalink":"/series/javase%E9%AB%98%E7%BA%A7/","section":"Series","summary":"","title":"JavaSE高级","type":"series"},{"content":"","date":"2025-02-18","externalUrl":null,"permalink":"/tags/javase%E9%AB%98%E7%BA%A7/","section":"标签","summary":"","title":"JavaSE高级","type":"tags"},{"content":"","date":"2025-02-12","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/f742d77f/","section":"文章","summary":"","title":"JavaSE基础","type":"posts"},{"content":"","date":"2025-02-12","externalUrl":null,"permalink":"/series/javase%E5%9F%BA%E7%A1%80/","section":"Series","summary":"","title":"JavaSE基础","type":"series"},{"content":"","date":"2025-02-12","externalUrl":null,"permalink":"/tags/javase%E5%9F%BA%E7%A1%80/","section":"标签","summary":"","title":"JavaSE基础","type":"tags"},{"content":"","date":"2025-02-12","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/4f5b7444/","section":"文章","summary":"","title":"SpringBoot","type":"posts"},{"content":"","date":"2025-02-12","externalUrl":null,"permalink":"/series/springboot/","section":"Series","summary":"","title":"SpringBoot","type":"series"},{"content":"","date":"2025-02-12","externalUrl":null,"permalink":"/tags/springboot/","section":"标签","summary":"","title":"SpringBoot","type":"tags"},{"content":"","date":"2025-02-11","externalUrl":null,"permalink":"/posts/f1b56a1d/712987f6/","section":"文章","summary":"","title":"Git","type":"posts"},{"content":"","date":"2025-02-11","externalUrl":null,"permalink":"/series/git/","section":"Series","summary":"","title":"Git","type":"series"},{"content":"","date":"2025-02-11","externalUrl":null,"permalink":"/tags/git/","section":"标签","summary":"","title":"Git","type":"tags"},{"content":"","date":"2025-02-11","externalUrl":null,"permalink":"/categories/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/","section":"分类","summary":"","title":"开发工具","type":"categories"},{"content":"","date":"2025-02-11","externalUrl":null,"permalink":"/posts/f1b56a1d/","section":"文章","summary":"","title":"开发工具","type":"posts"},{"content":"","date":"2025-01-16","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/","section":"文章","summary":"","title":"常用API","type":"posts"},{"content":"","date":"2025-01-16","externalUrl":null,"permalink":"/tags/%E5%B8%B8%E7%94%A8api/","section":"标签","summary":"","title":"常用API","type":"tags"},{"content":"","date":"2025-01-16","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/0f3856f3/","section":"文章","summary":"","title":"第三方","type":"posts"},{"content":"","date":"2025-01-16","externalUrl":null,"permalink":"/series/%E7%AC%AC%E4%B8%89%E6%96%B9/","section":"Series","summary":"","title":"第三方","type":"series"},{"content":"","date":"2025-01-16","externalUrl":null,"permalink":"/tags/%E7%AC%AC%E4%B8%89%E6%96%B9/","section":"标签","summary":"","title":"第三方","type":"tags"},{"content":" 报错信息 # InvalidPathException: Malformed input or input contains unmappable characters 原因 # 在Linux中操作中文名称的文件时出现该异常.\n解决方案 # 这是带有中文字符在linux乱码导致识别不出来而抛出的异常，设置字体字符集编码为UTF-8即可。\n若是docker部署的，有KubeSphere，则编辑yaml文件 添加即可。\nJAVA_TOOL_OPTIONS=\u0026#39;-Dfile.encoding=\u0026#34;UTF-8\u0026#34; -Dsun.jnu.encoding=\u0026#34;UTF-8\u0026#34;\u0026#39; ","date":"2025-01-03","externalUrl":null,"permalink":"/posts/cdbc5e74/30722c77/c2d22c66/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e报错信息 \n    \u003cdiv id=\"报错信息\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%8a%a5%e9%94%99%e4%bf%a1%e6%81%af\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eInvalidPathException: Malformed input or input contains unmappable characters\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e原因 \n    \u003cdiv id=\"原因\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%8e%9f%e5%9b%a0\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e在Linux中操作中文名称的文件时出现该异常.\u003c/p\u003e","title":"InvalidPathException","type":"posts"},{"content":"","date":"2025-01-03","externalUrl":null,"permalink":"/posts/cdbc5e74/30722c77/","section":"文章","summary":"","title":"Java","type":"posts"},{"content":"","date":"2025-01-03","externalUrl":null,"permalink":"/series/java/","section":"Series","summary":"","title":"Java","type":"series"},{"content":"","date":"2025-01-03","externalUrl":null,"permalink":"/tags/java/","section":"标签","summary":"","title":"Java","type":"tags"},{"content":"","date":"2025-01-03","externalUrl":null,"permalink":"/posts/bafd68f1/d4aa59a0/","section":"文章","summary":"","title":"框架","type":"posts"},{"content":"","date":"2025-01-03","externalUrl":null,"permalink":"/series/%E6%A1%86%E6%9E%B6/","section":"Series","summary":"","title":"框架","type":"series"},{"content":"","date":"2025-01-03","externalUrl":null,"permalink":"/tags/%E6%A1%86%E6%9E%B6/","section":"标签","summary":"","title":"框架","type":"tags"},{"content":"","date":"2024-12-13","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/","section":"文章","summary":"","title":"Java代码","type":"posts"},{"content":"","date":"2024-12-13","externalUrl":null,"permalink":"/series/java%E4%BB%A3%E7%A0%81/","section":"Series","summary":"","title":"Java代码","type":"series"},{"content":"","date":"2024-12-13","externalUrl":null,"permalink":"/tags/java%E4%BB%A3%E7%A0%81/","section":"标签","summary":"","title":"Java代码","type":"tags"},{"content":"","date":"2024-12-13","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/caadfbf9/","section":"文章","summary":"","title":"Redis","type":"posts"},{"content":"","date":"2024-12-13","externalUrl":null,"permalink":"/series/redis/","section":"Series","summary":"","title":"Redis","type":"series"},{"content":"","date":"2024-12-13","externalUrl":null,"permalink":"/tags/redis/","section":"标签","summary":"","title":"Redis","type":"tags"},{"content":"如果使用了nginx代理后台，则需要在nginx代理中将用户ip添加在请求头中\nlocation /api { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://localhost:8080/; } 后台代码如下：\npackage top.ygang.huijifindresource.utils; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import org.springframework.web.client.RestTemplate; import javax.servlet.http.HttpServletRequest; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Map; /** * @Description: IP工具类 * @Author: yanggang * @Date: */ public class IpUtil { /** * 获取请求IP * @param request * @return */ public static String getIpByRequest(HttpServletRequest request) { if (request == null) { return \u0026#34;unknown\u0026#34;; } String ipAddress = null; try { ipAddress = request.getHeader(\u0026#34;x-forwarded-for\u0026#34;); if (ipAddress == null || ipAddress.length() == 0 || \u0026#34;unknown\u0026#34;.equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader(\u0026#34;X-Forwarded-For\u0026#34;); } if (ipAddress == null || ipAddress.length() == 0 || \u0026#34;unknown\u0026#34;.equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader(\u0026#34;Proxy-Client-IP\u0026#34;); } if (ipAddress == null || ipAddress.length() == 0 || \u0026#34;unknown\u0026#34;.equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader(\u0026#34;WL-Proxy-Client-IP\u0026#34;); } // X-Real-IP 需要在Nginx proxy中配置 if (ipAddress == null || ipAddress.length() == 0 || \u0026#34;unknown\u0026#34;.equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader(\u0026#34;X-Real-IP\u0026#34;); } if (ipAddress == null || ipAddress.length() == 0 || \u0026#34;unknown\u0026#34;.equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); if (ipAddress.equals(\u0026#34;127.0.0.1\u0026#34;)) { // 根据网卡取本机配置的IP InetAddress inet = null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } ipAddress = inet.getHostAddress(); } } // 对于通过多个代理的情况，第一个IP为客户端真实IP,多个IP按照\u0026#39;,\u0026#39;分割 if (ipAddress != null \u0026amp;\u0026amp; ipAddress.length() \u0026gt; 15) { // \u0026#34;***.***.***.***\u0026#34;.length() // = 15 if (ipAddress.indexOf(\u0026#34;,\u0026#34;) \u0026gt; 0) { ipAddress = ipAddress.substring(0, ipAddress.indexOf(\u0026#34;,\u0026#34;)); } } } catch (Exception e) { ipAddress = \u0026#34;\u0026#34;; } return ipAddress; } /** * 获取ip的真实地址 * @param ip * @return */ public static String getAddressByIp(String ip){ String url = \u0026#34;http://whois.pconline.com.cn/ipJson.jsp?ip=\u0026#34; + ip + \u0026#34;\u0026amp;json=true\u0026#34;; RestTemplate restTemplate = new RestTemplate(); try { String resp = restTemplate.getForObject(url,String.class); JSONObject jsonObject = JSON.parseObject(resp); return jsonObject.getString(\u0026#34;addr\u0026#34;); }catch (Exception e){ e.printStackTrace(); return \u0026#34;xx_xx\u0026#34;; } } } ","date":"2024-12-13","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/1697c3e6/","section":"文章","summary":"\u003cp\u003e如果使用了nginx代理后台，则需要在nginx代理中将用户ip添加在请求头中\u003c/p\u003e","title":"获取请求的ip地址","type":"posts"},{"content":"@Data public class ResultVO\u0026lt;T\u0026gt; implements Serializable { private Integer code; private String msg; private T data; public ResultVO(int code,String msg,T data){ this.code = code; this.msg = msg; this.data = data; } /** * 成功200、有数据 * @param msg * @param data * @return */ public static\u0026lt;T\u0026gt; ResultVO\u0026lt;T\u0026gt; success(String msg,T data){ return new ResultVO\u0026lt;\u0026gt;(ResultCode.SUCCESS,msg,data); } /** * 成功200、无数据 * @param msg * @return */ public static\u0026lt;T\u0026gt; ResultVO\u0026lt;T\u0026gt; success(String msg){ return success(msg,null); } /** * 失败500 * @param msg * @param \u0026lt;T\u0026gt; * @return */ public static\u0026lt;T\u0026gt; ResultVO\u0026lt;T\u0026gt; failed(String msg){ return new ResultVO\u0026lt;\u0026gt;(ResultCode.FAILED,msg,null); } /** * 自定义返回 * @param msg * @param code * @param data * @param \u0026lt;T\u0026gt; * @return */ public static\u0026lt;T\u0026gt; ResultVO\u0026lt;T\u0026gt; result(String msg,Integer code,T data){ return new ResultVO\u0026lt;T\u0026gt;(code,msg,data); } } public interface ResultCode { /** * 请求成功 */ int SUCCESS = 200; /** * 服务器异常 */ int FAILED = 500; /** * 未授权、未认证访问 */ int UNAUTHORIZED = 401; /** * 无权限 */ int NOACCESS = 403; } ","date":"2024-12-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/f1e915e0/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Data\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eResultVO\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eimplements\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eSerializable\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emsg\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eResultVO\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emsg\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecode\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003emsg\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emsg\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003edata\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 成功200、有数据\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @param msg\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @param data\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @return\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eResultVO\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003esuccess\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emsg\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eResultVO\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eResultCode\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eSUCCESS\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003emsg\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 成功200、无数据\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @param msg\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @return\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eResultVO\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003esuccess\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emsg\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esuccess\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emsg\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 失败500\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @param msg\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @param \u0026lt;T\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @return\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eResultVO\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003efailed\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emsg\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eResultVO\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eResultCode\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eFAILED\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003emsg\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 自定义返回\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @param msg\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @param code\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @param data\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @param \u0026lt;T\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @return\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eResultVO\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emsg\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eResultVO\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003emsg\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003einterface\u003c/span\u003e \u003cspan class=\"nc\"\u003eResultCode\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 请求成功\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eSUCCESS\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e200\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 服务器异常\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFAILED\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e500\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 未授权、未认证访问\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eUNAUTHORIZED\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e401\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 无权限\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eNOACCESS\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e403\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"ResultVo","type":"posts"},{"content":"","date":"2024-12-09","externalUrl":null,"permalink":"/posts/3ab7256e/2caf48b8/409de243/","section":"文章","summary":"","title":"JDK原生","type":"posts"},{"content":"","date":"2024-12-09","externalUrl":null,"permalink":"/series/jdk%E5%8E%9F%E7%94%9F/","section":"Series","summary":"","title":"JDK原生","type":"series"},{"content":"","date":"2024-12-09","externalUrl":null,"permalink":"/tags/jdk%E5%8E%9F%E7%94%9F/","section":"标签","summary":"","title":"JDK原生","type":"tags"},{"content":"","date":"2024-12-09","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/79c2bc57/","section":"文章","summary":"","title":"Mybatis","type":"posts"},{"content":"","date":"2024-12-09","externalUrl":null,"permalink":"/series/mybatis/","section":"Series","summary":"","title":"Mybatis","type":"series"},{"content":"","date":"2024-12-09","externalUrl":null,"permalink":"/tags/mybatis/","section":"标签","summary":"","title":"Mybatis","type":"tags"},{"content":"","date":"2024-12-09","externalUrl":null,"permalink":"/posts/b18f3aef/7b41ff88/","section":"文章","summary":"","title":"加密算法","type":"posts"},{"content":"","date":"2024-12-09","externalUrl":null,"permalink":"/series/%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95/","section":"Series","summary":"","title":"加密算法","type":"series"},{"content":"","date":"2024-12-09","externalUrl":null,"permalink":"/tags/%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95/","section":"标签","summary":"","title":"加密算法","type":"tags"},{"content":"","date":"2024-12-09","externalUrl":null,"permalink":"/categories/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8/","section":"分类","summary":"","title":"网络安全","type":"categories"},{"content":"","date":"2024-12-09","externalUrl":null,"permalink":"/posts/b18f3aef/","section":"文章","summary":"","title":"网络安全","type":"posts"},{"content":"","date":"2024-12-05","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/63e90e89/","section":"文章","summary":"","title":"Swagger","type":"posts"},{"content":"","date":"2024-12-05","externalUrl":null,"permalink":"/series/swagger/","section":"Series","summary":"","title":"Swagger","type":"series"},{"content":"","date":"2024-12-05","externalUrl":null,"permalink":"/tags/swagger/","section":"标签","summary":"","title":"Swagger","type":"tags"},{"content":"","date":"2024-12-02","externalUrl":null,"permalink":"/series/%E8%AF%AD%E8%A8%80/","section":"Series","summary":"","title":"语言","type":"series"},{"content":"","date":"2024-11-20","externalUrl":null,"permalink":"/posts/26654e7b/1a145064/","section":"文章","summary":"","title":"Linux","type":"posts"},{"content":"","date":"2024-11-20","externalUrl":null,"permalink":"/series/linux/","section":"Series","summary":"","title":"Linux","type":"series"},{"content":"","date":"2024-11-20","externalUrl":null,"permalink":"/tags/linux/","section":"标签","summary":"","title":"Linux","type":"tags"},{"content":"","date":"2024-11-20","externalUrl":null,"permalink":"/posts/cdbc5e74/7b52924b/","section":"文章","summary":"","title":"操作系统Bug","type":"posts"},{"content":"","date":"2024-11-20","externalUrl":null,"permalink":"/series/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9Fbug/","section":"Series","summary":"","title":"操作系统Bug","type":"series"},{"content":"","date":"2024-11-20","externalUrl":null,"permalink":"/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9Fbug/","section":"标签","summary":"","title":"操作系统Bug","type":"tags"},{"content":"","date":"2024-09-24","externalUrl":null,"permalink":"/posts/3ab7256e/ebb2f342/","section":"文章","summary":"","title":"JVM","type":"posts"},{"content":"","date":"2024-09-24","externalUrl":null,"permalink":"/series/jvm/","section":"Series","summary":"","title":"JVM","type":"series"},{"content":"","date":"2024-09-24","externalUrl":null,"permalink":"/tags/jvm/","section":"标签","summary":"","title":"JVM","type":"tags"},{"content":"","date":"2024-09-20","externalUrl":null,"permalink":"/posts/a6ece75f/8b824d9b/","section":"文章","summary":"","title":"MySQL","type":"posts"},{"content":"","date":"2024-09-20","externalUrl":null,"permalink":"/tags/mysql/","section":"标签","summary":"","title":"MySQL","type":"tags"},{"content":"","date":"2024-09-20","externalUrl":null,"permalink":"/posts/a6ece75f/8b824d9b/6930a0a9/","section":"文章","summary":"","title":"MySQL高级","type":"posts"},{"content":"","date":"2024-09-20","externalUrl":null,"permalink":"/series/mysql%E9%AB%98%E7%BA%A7/","section":"Series","summary":"","title":"MySQL高级","type":"series"},{"content":"","date":"2024-09-20","externalUrl":null,"permalink":"/tags/mysql%E9%AB%98%E7%BA%A7/","section":"标签","summary":"","title":"MySQL高级","type":"tags"},{"content":"","date":"2024-09-20","externalUrl":null,"permalink":"/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/","section":"分类","summary":"","title":"数据库","type":"categories"},{"content":"","date":"2024-09-20","externalUrl":null,"permalink":"/posts/a6ece75f/","section":"文章","summary":"","title":"数据库","type":"posts"},{"content":"","date":"2024-09-19","externalUrl":null,"permalink":"/posts/69064821/af96a2fa/","section":"文章","summary":"","title":"Unity","type":"posts"},{"content":"","date":"2024-09-19","externalUrl":null,"permalink":"/series/unity/","section":"Series","summary":"","title":"Unity","type":"series"},{"content":"","date":"2024-09-19","externalUrl":null,"permalink":"/tags/unity/","section":"标签","summary":"","title":"Unity","type":"tags"},{"content":"","date":"2024-09-12","externalUrl":null,"permalink":"/categories/c%23/","section":"分类","summary":"","title":"C#","type":"categories"},{"content":"","date":"2024-09-12","externalUrl":null,"permalink":"/posts/5ab2821f/","section":"文章","summary":"","title":"C#","type":"posts"},{"content":"","date":"2024-09-12","externalUrl":null,"permalink":"/series/c%23/","section":"Series","summary":"","title":"C#","type":"series"},{"content":"","date":"2024-08-09","externalUrl":null,"permalink":"/posts/a6ece75f/8b824d9b/5d290da9/","section":"文章","summary":"","title":"MySQL基础","type":"posts"},{"content":"","date":"2024-08-09","externalUrl":null,"permalink":"/series/mysql%E5%9F%BA%E7%A1%80/","section":"Series","summary":"","title":"MySQL基础","type":"series"},{"content":"","date":"2024-08-09","externalUrl":null,"permalink":"/tags/mysql%E5%9F%BA%E7%A1%80/","section":"标签","summary":"","title":"MySQL基础","type":"tags"},{"content":"","date":"2024-04-03","externalUrl":null,"permalink":"/categories/c/","section":"分类","summary":"","title":"C","type":"categories"},{"content":"","date":"2024-04-03","externalUrl":null,"permalink":"/posts/f571508d/","section":"文章","summary":"","title":"C","type":"posts"},{"content":"","date":"2024-04-03","externalUrl":null,"permalink":"/posts/f571508d/8c7e092d/","section":"文章","summary":"","title":"C语言高级","type":"posts"},{"content":"","date":"2024-04-03","externalUrl":null,"permalink":"/series/c%E8%AF%AD%E8%A8%80%E9%AB%98%E7%BA%A7/","section":"Series","summary":"","title":"C语言高级","type":"series"},{"content":"","date":"2024-04-03","externalUrl":null,"permalink":"/tags/c%E8%AF%AD%E8%A8%80%E9%AB%98%E7%BA%A7/","section":"标签","summary":"","title":"C语言高级","type":"tags"},{"content":"","date":"2024-04-03","externalUrl":null,"permalink":"/posts/61bf7bc9/5e6103fa/","section":"文章","summary":"","title":"Docker","type":"posts"},{"content":"","date":"2024-04-03","externalUrl":null,"permalink":"/tags/docker/","section":"标签","summary":"","title":"Docker","type":"tags"},{"content":"","date":"2024-04-03","externalUrl":null,"permalink":"/posts/61bf7bc9/5e6103fa/36ca2e73/","section":"文章","summary":"","title":"Docker基础","type":"posts"},{"content":"","date":"2024-04-03","externalUrl":null,"permalink":"/series/docker%E5%9F%BA%E7%A1%80/","section":"Series","summary":"","title":"Docker基础","type":"series"},{"content":"","date":"2024-04-03","externalUrl":null,"permalink":"/tags/docker%E5%9F%BA%E7%A1%80/","section":"标签","summary":"","title":"Docker基础","type":"tags"},{"content":"","date":"2024-04-03","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/2c72a928/","section":"文章","summary":"","title":"Nginx","type":"posts"},{"content":"","date":"2024-04-03","externalUrl":null,"permalink":"/series/nginx/","section":"Series","summary":"","title":"Nginx","type":"series"},{"content":"","date":"2024-04-03","externalUrl":null,"permalink":"/tags/nginx/","section":"标签","summary":"","title":"Nginx","type":"tags"},{"content":"","date":"2024-04-03","externalUrl":null,"permalink":"/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/","section":"分类","summary":"","title":"云原生","type":"categories"},{"content":"","date":"2024-04-03","externalUrl":null,"permalink":"/posts/61bf7bc9/","section":"文章","summary":"","title":"云原生","type":"posts"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/categories/c++/","section":"分类","summary":"","title":"C++","type":"categories"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/posts/d8245fe1/","section":"文章","summary":"","title":"C++","type":"posts"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/posts/d8245fe1/09e70634/","section":"文章","summary":"","title":"c++基础","type":"posts"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/series/c++%E5%9F%BA%E7%A1%80/","section":"Series","summary":"","title":"C++基础","type":"series"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/tags/c++%E5%9F%BA%E7%A1%80/","section":"标签","summary":"","title":"C++基础","type":"tags"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/posts/d8245fe1/f50d4583/","section":"文章","summary":"","title":"c++核心","type":"posts"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/series/c++%E6%A0%B8%E5%BF%83/","section":"Series","summary":"","title":"C++核心","type":"series"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/tags/c++%E6%A0%B8%E5%BF%83/","section":"标签","summary":"","title":"C++核心","type":"tags"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/posts/f571508d/fa8095d0/","section":"文章","summary":"","title":"C语言基础","type":"posts"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/series/c%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80/","section":"Series","summary":"","title":"C语言基础","type":"series"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/tags/c%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80/","section":"标签","summary":"","title":"C语言基础","type":"tags"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/fb007bea/","section":"文章","summary":"","title":"JavaWeb","type":"posts"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/series/javaweb/","section":"Series","summary":"","title":"JavaWeb","type":"series"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/tags/javaweb/","section":"标签","summary":"","title":"JavaWeb","type":"tags"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/c3527798/","section":"文章","summary":"","title":"Shiro","type":"posts"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/series/shiro/","section":"Series","summary":"","title":"Shiro","type":"series"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/tags/shiro/","section":"标签","summary":"","title":"Shiro","type":"tags"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/c70ae83e/","section":"文章","summary":"","title":"Spring","type":"posts"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/series/spring/","section":"Series","summary":"","title":"Spring","type":"series"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/tags/spring/","section":"标签","summary":"","title":"Spring","type":"tags"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/617a1910/","section":"文章","summary":"","title":"SpringMVC","type":"posts"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/series/springmvc/","section":"Series","summary":"","title":"SpringMVC","type":"series"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/tags/springmvc/","section":"标签","summary":"","title":"SpringMVC","type":"tags"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/posts/1b37041b/339aff44/","section":"文章","summary":"","title":"爬虫","type":"posts"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/series/%E7%88%AC%E8%99%AB/","section":"Series","summary":"","title":"爬虫","type":"series"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/tags/%E7%88%AC%E8%99%AB/","section":"标签","summary":"","title":"爬虫","type":"tags"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/","section":"分类","summary":"","title":"计算机基础","type":"categories"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/posts/c343b13e/","section":"文章","summary":"","title":"计算机基础","type":"posts"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/posts/c343b13e/f6ce07bb/","section":"文章","summary":"","title":"计算机组成原理","type":"posts"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/series/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/","section":"Series","summary":"","title":"计算机组成原理","type":"series"},{"content":"","date":"2024-01-19","externalUrl":null,"permalink":"/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/","section":"标签","summary":"","title":"计算机组成原理","type":"tags"},{"content":" JavaSE基础 # Java面向对象 # \u0026raquo;\u0026gt;面向对象有哪些特性，以及对这些特性的理解 # 封装、继承、多态、（抽象）\n封装：封装就是将数据和对数据操作的方法绑定起来，对数据的访问只可以通过对外暴露的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装；我们编写的一个类就是对数据和数据操作的封装。可以说，封装就是隐藏一切可以隐藏的东西，只向外界提供最简单的编程接口\n继承：是从已有类得到继承信息并创建新类的过程。提供继承信息的类被称为父类、超类或基类，得到继承信息的类被称为子类（派生类）。继承让变化中的软件系统有了一定的延续性，同时继承也是封装程序中可变因素的重要手段\n多态：多态是指允许不同子类型的对象对同一消息做出不同的相应，简单地说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时多态性和运行时多态性。如果将对象的方法视为对象向外界提供的服务，那么运行时的多态性可以理解为：当A系统访问B系统提供的服务时，B系统有多种提供服务的方式，但一切对A系统来说对是透明的。方法重载实现的是编译时的多态性，而方法重写实现的是运行时的多态性。运行时的多态是面向对象最精髓的东西，要实现多态需要做两件事：1、方法重写；2对象造型\n抽象：抽象是将一类对象的共同特性总结出来构造类的过程，包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为，并不关注这些行为的细节\n\u0026raquo;\u0026gt;访问权限修饰符public、private、protected以及缺省时的区别 # pulic的可见性为：当前类、同包、子类、其他包\nprotected的可见性为：当前类、同包、子类\ndefault的可见性为：当前类、同包\nprivate的可见性为：当前类\n\u0026raquo;\u0026gt;为什么使用clone # 在实际编程过程中，我们常常遇到这种情况：有一个对象A，在某一时刻A中已经包含了一些有效值，此时可能会需要一个和A对象完全相同的新对象B，并且此后对B任何改动不会影响到A中的值，也是说，A与B是两个独立的对象，但是B的初始值是由A对象确定的，Java语言中，用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径，但实现clone方法是最简单最高效的\n\u0026raquo;\u0026gt;new一个对象过程和clone一个对象过程区别 # new操作符的本意是分配内存，程序执行到new操作符时，首先去看new操作符后面的类型，因为知道了类型，才能知道分配多大的内存空间。分配完内存之后，再调用构造函数，填充对象的各个域，这一步叫对象的初始化，构造方法返回后，一个对象创建完毕，可以把他的引用发布到外部，在外部就可以使用这个引用操作这个对象\nclone在第一步和new相似，都是分配内存，调用clone方法时，分配的内存和原对象相同，然后在使用原对象中对应的各个域，填充新对象的域，填充成功之后，clone方法返回，一个新的相同的对象被创建，同样可以把这个新对象的引用发布到外部\nJavaSE语法 # \u0026raquo;\u0026gt;java中有没有goto语句 # goto是java的保留字，在目前版本的java中没有使用。根据James Gosling编写书中的java关键字列表，其中有goto和const，但是目前这两个是无法使用的关键字，因此有些地方称其为保留字\n\u0026raquo;\u0026gt;\u0026amp;和\u0026amp;\u0026amp;的区别 # \u0026amp;运算符有两种用法：按位与、逻辑与\n\u0026amp;\u0026amp;运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的，虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true\n\u0026amp;\u0026amp;之索引成为短路运算符是因为，如果\u0026amp;\u0026amp;左边的表达式值为false，右边的表达式会直接短路掉，不会进行运算。很多时候我们可能使用的是\u0026amp;\u0026amp;而不是\u0026amp;，例如在验证用户登录时判断用户名不可以是null而且不是空字符串，应该写为username != null \u0026amp;\u0026amp; !username.equals(\u0026quot;\u0026quot;)，二者的顺序不能交换，更不能使用\u0026amp;运算符，因为第一个条件如果不成立，就不可以进行字符串的equals比较，否则会造成空指针异常\n\u0026raquo;\u0026gt;java中，如何跳出当前的多重嵌套循环 # 在最外层循环前加一个标记如A，然后使用break A；可以跳出多重循环\n\u0026raquo;\u0026gt;两个对象值相同x.equals(y) == true，但却可能有不同的hashCode，这句话对不对 # 不对，如果两个对象x、y的equals比较返回true，那么他们的hashCode应该相同。\njava对于equals方法和hashCode方法是这样规定的：1、如果两个对象相同即equals方法返回true，那么他们的hashCode值一定要相同2、如果两个对象的hashCode值相同，那么它们并不一定相同。当然，你未必要按照要去做，但是如果你违背上述原则，就会发现在使用容器时，相同的对象可以出现在Set集合中，同时增加新元素的效率大大下降\n\u0026raquo;\u0026gt;是否可以继承String # 由于String类是final类，所以不可以被继承\n继承String本身就是一个错误的行为，对String类型最好的重用方式就是关联关系和依赖关系，而不是继承关系\n\u0026raquo;\u0026gt;当一个对象被当作参数传递到一个方法后，此方法可以改变这个对象的属性，并可以返回变化后的结果，那么这里到底是值传递还是引用传递 # 是值传递，java语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时，参数的值就是该对象的引用。对象的属性可以在被调用过程中被改变，但是在方法内部对引用的改变，并不会影响到调用者传入方法的对象\n\u0026raquo;\u0026gt;重载overload和重写override的区别，重载的方法能否根据返回类型进行区分 # 方法的重载和重写都是实现多态的方式，区别在于前者实现的是编译时的多态性，后者实现的是运行时的多态性。重载发生在同一个类中，同名的方法如果有不同的参数列表，则视为重载；重写发生在子父类之间，重写要求子类重写的方法和父类被重写的方法有相同的返回类型，比父类被重写方法更好访问（访问权限大于父类方法），不能比父类被重写的方法声明更多的异常（里氏代换原则）。重载对返回值类型没有特殊的要求，只针对方法名和参数列表\n\u0026raquo;\u0026gt;为什么函数不能根据返回类型来区分重载 # 因为在调用的时候，编译器只能通过方法名和形参列表来确定返回类型\n方法的返回值，只可以作为方法运行之后的一个状态，而并不可以作为方法的一个标识\n\u0026raquo;\u0026gt;char 型变量中能不能存储一个中文汉字，为什么？ # char类型可以存储一个中文汉字，因为java使用的编码是Unicode，Unicode编码是不使用任何特定的编码格式，而是所有的字符都使用字符集中的编码，一个char类型占用两个字节，所以可以放一个中文\n\u0026raquo;\u0026gt;抽象类(abstract class)和接口(interface)有什么异同 # 不同点：\n1、抽象类中可以定义构造函数，接口中不可以\n2、可以有抽象方法和具体的方法，接口中的方法只可以是抽象方法\n3、接口中所有的成员都是public的，抽象类的成员可以是四种权限任意的\n4、抽象类中可以定义成员变量，接口中定义的成员变量都是常量psf的\n5、凡是拥有抽象方法的类一定是抽象类，但是抽象类中未必有抽象方法\n6、抽象类中有静态的方法，接口中不可以有静态方法\n7、类的单继承多实现\n相同点：\n1、都不可以实例化\n2、可以将抽象类和接口类型作为引用类型，也就是多态的体现\n3、一个类继承抽象类或实现接口，都需要将抽象方法全部实现，否则该类还是抽象的\n\u0026raquo;\u0026gt;抽象的(abstract)方法是否可同时是静态的(static), 是否可同时是本地方法(native)，是否可同时被 synchronized # 都不可以，因为抽象方法需要被重写，静态方法不可以被重写；至于native，因为native是用本地代码例如C语言实现的，所以不可以通过重写实现；synchronized是和方法具体实现有关的，但是抽象方法没有具体实现细节，所以也不可以\n\u0026raquo;\u0026gt;静态变量和实例变量的区别 # 静态变量：也就是使用static关键字修饰的变量，它属于类，在类加载的时候就会进行初始化，分配内存，所以无论这个对象有多少实例，静态变量都只有一份在内存\n实例变量：属于某一个具体的实例，需要通过创建对象，才会在内存中分配空间，才可以进行操作\n\u0026raquo;\u0026gt;==和 equals 的区别 # ==：如果比较的是基本数据类型，那么比较的就是值是否相等；如果比较的是引用数据类型，那么比较的就是地址值是否相等\nequals：是Object类的一个方法，用来比较两个对象的内容是否相等\n\u0026raquo;\u0026gt;break和continue的区别 # break：用于跳出循环体，一旦程序执行到break，立即跳出当前循环\ncontinue：用于跳出本次循环，程序执行到continue，会跳出本次循环，开始执行下一次循环\n\u0026raquo;\u0026gt;String s = \u0026quot;Hello\u0026quot;;和s = s + \u0026quot; world!\u0026quot;;这两行代码执行后，原始的 String 对象中的内容到底变了没有？ # 没有改变，因为String是不可变类，所以它所有对象都是不可变对象，代码中，变量s指向一个String对象，内容是“hello”，然后对s+“world!”此时s已经指向了一个新的String对象，内容是“Hello world!”，原来的对象还存在于内存中，只是s没有再指向它了\n所以，得出一个结论，如果我们需要对于一个字符串类型的对象经常修改，那么使用String会给内存带来很大的开销，因为每次修改，String都需要创建新的对象，这个时候，就应该考虑使用StringBuffer类了\nJava中的多态 # \u0026raquo;\u0026gt;Java 中实现多态的机制是什么 # 靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象，而程序调用的方法在运行期才动态绑定，就是引用变量所指向的具体实例对象的方法，也就是内存里正在运行的那个对象的方法，而不是引用变量的类型中定义的方法\nJava的异常处理 # \u0026raquo;\u0026gt;Java中异常分为哪些种类 # 按照异常需要处理的实际分为编译时异常（也称为受检时异常）CheckedException和运行时异常RuntimeException，其中编译时异常，Java认为都是可以被处理的异常，所以需要显示的进行处理，如果没有处理，那么程序会在编译期就会报错，无法编译，编译时异常的处理方法有两种，一个是使用try-catch捕获并处理异常，一个是抛出该异常；运行时异常，只有在程序运行的过程中才会出现的异常，例如除零异常，数组角标越界等，这类异常不要求强制处理\n\u0026raquo;\u0026gt;以下代码，返回值是多少，为什么 # public int getNum(){ try { int a = 1/0; return 1; } catch (Exception e) { return 2; }finally{ return 3; } 返回值应该是3，因为程序在运行中，遇到除零异常，那么会进入catch块中，但是，Java异常机制是，如果程序执行到return，但是存在finally块，那么必须先执行finally块中的代码，再进行返回，但是finally块中的语句是return，所以程序到此结束，返回的是3\n\u0026raquo;\u0026gt;error和exception的区别 # Error类和Exception类的父类都是Throwable类\n但是Error类主要声明了一些与虚拟机相关的错误，例如系统崩溃、虚拟机错误、内存空间不足等，这些错误，无法靠程序本身来解决或预防，所以出现这种错误，建议程序终止运行\nException类表示的是程序可以处理的异常，可以捕获，并且恢复，出现这种异常，应该及时的处理，而不是停止程序的运行\n\u0026raquo;\u0026gt;说出5个常见的RuntimeException # NullPointerException：空指针异常，一般出现调用未初始化的对象的变量或方法\nClassNotFoundException：类加载异常，一般出现再通过字符串加载类时，如加载Mysql的Driver使用Class.forName(\u0026quot;\u0026quot;)\nNumberFormatException：数值转换异常，一般出现再通过字符串转数值类型的时候\nIllegalArgumentException：非法参数异常，一般出现在方法传入的参数不合法，例如使用SimpleDateFormat的构造方法里面，字符串的格式不正确\nClassCastException：类型转换异常，一般出现在强转类型的时候\nSQLException：SQL异常，一般出现在操作数据的SQL语句错误\nInstantiationException：实例化异常，一般出现在构造函数私有化，无法创建对象\n\u0026raquo;\u0026gt;throw和throws的区别 # throw：\n1、throw语句用于方法体内，表示抛出异常\n2、throw时具体向外抛出异常的动作，抛出的是一个异常实例\nthrows：\n1、在方法后面进行声明，如果抛出异常，那么需要上层的调用者来处理\n2、throws主要声明这个方法可能抛出的异常类型，不是一定会出现\n\u0026raquo;\u0026gt;final、finally、finalize的区别 # final：用于声明方法、属性、类，表示属性不可变、方法不可重写、类不可继承\nfinally：异常处理语句结构的一部分，表示总是执行\nfinalize：Object类的一个方法，GC在垃圾回收时会调用被回收对象的该方法\nJavaSE的常见API # \u0026raquo;\u0026gt;Math.round(11.5)和Math.round(-11.5)各等于多少 # Math.round(11.5)等于12，Math.round(-11.5)等于-11，因为Math.round取整四舍五入，无论正负，都会在原参数上加0.5\n\u0026raquo;\u0026gt;switch能否作用与byte上，能否作用在long上，能否作用在String上 # 以常用的Java8来说，可以作用在byte、String上，不可以作用在long上\nJava5之前，switch可以作用在byte、short、char、int上，java5增加enum类型，Java7增加String类型\n\u0026raquo;\u0026gt;数组有没有length方法、String有没有length方法 # 数组没有length方法，而是length属性；String有length方法，一般来说JavaScript中String的长度是length属性，所以容易和Java中的混淆\n\u0026raquo;\u0026gt;String、StringBuffer、StringBuilder的区别 # String：只读字符串，也就是String类型的对象只支持读，而不支持写，因为如果String的字符串内容改变，那么引用指向的就不是原来的对象，而是新的对象\nStringBuffer：读写字符串，就是修改字符串的内容而不会产生新的对象，而且StringBuffer线程安全，所有方法使用synchronized修饰\nStringBuilder：读写字符串，和StringBuffer的方法完全相同，只不过，所有的方法没有加synchronized关键字，所以，线程不安全，但是效率高\nJava的数据类型 # \u0026raquo;\u0026gt;Java的基本数据类型都有哪些，各占多少字节 # byte：1字节\nshort：2字节\nint：4字节\nlong：8字节\nfloat：4字节\ndouble：8字节\nchar：2字节\nboolean：1字节\n\u0026raquo;\u0026gt;String是基本数据类型吗 # 不是，String是引用数据类型，底层使用char数组实现\n\u0026raquo;\u0026gt;short s1 = 1;s1 = s1 + 1和short s1 = 1;s1 += 1的对错 # 前者不对，后者对，因为对于前者表达式中的+1，1是int类型，所以计算结果自动类型提升为int，需要强转才可以赋值给short类型，而后者可以正确进行编译，结果就是short类型\n\u0026raquo;\u0026gt;int和Integer的区别 # int是基本数据类型，Integer是引用数据类型，在编码的时候，int可以自动装箱为Integer，Integer也可以自动拆箱为int\n\u0026raquo;\u0026gt;String类的常用方法 # //返回当前字符串长度 int length(); //查找字符ch第一次出现的位置 int indexOf(char ch); //查找字符串str第一次出现的位置 int indexOf(String str); //查找字符ch最后一次出现的位置 int lastIndexOf(char ch); //查找字符串str最后一次出现的位置 int lastIndexOf(String str); //获取从begin位置开始到结束的字符串 String subString(int begin); //获取从begin带end直接的字符串 String subString(int begin,int end); //返回去除前后空格的字符串 String trim(); //字符串转换为全小写 String toLowerCase(); //字符串转换为全大写 String toUpperCase(); //获取字符串中index位置的子父 char charAt(int index); Java的IO # \u0026raquo;\u0026gt;Java中有几种类型的流 # 按照数据的流向：输入流（inputStream）和输出流（outputStream）\n按照实现功能：节点流（如FileReader）和处理流（如BufferedReader）\n按照处理数据的单位：字节流（继承于InputStream和OutputStream）和字符流（继承与Reader和Writer）\n\u0026raquo;\u0026gt;字节流如何转为字符流 # 字节输入流转字符输入流：通过InputStreamReader实现，该类的构造器可以传入InputStream对象\n字节输出流转子父输出流：通过OutputStreamWriter实现，该类的构造器可以传入OuttputStream对象\n\u0026raquo;\u0026gt;如何将一个对象序列化到文件里面 # 需要被序列化对象的类必须实现Serializable接口\n//建立对象输出流 ObjectOutputStream oop = new ObjectOutputStream(new FileOutputStream(new File(\u0026#34;指定文件路径\u0026#34;))); //写入 oop.writeObject(object); //关闭资源 oop.close(); \u0026raquo;\u0026gt;字节流和字符流的区别 # 字节流读取的时候，读到一个字节就返回一个字节；字符流在读取的时候，读到一个字符返回一个字符。字节流可以处理所有数据类型的数据，而字符流只能处理字符数据，就是说，如果处理纯文本数据，那么优先使用字符流，其他情况都用字节流，字节流主要操作的是byte数组\n\u0026raquo;\u0026gt;如何实现对象的克隆 # 一共有两种方法实现：\n1、实现Cloneable接口，并且重写clone方法，但是这种是浅克隆\n2、实现Serializable接口，通过对象的序列化和反序列化实现，这种的方式，可以实现深克隆\n\u0026raquo;\u0026gt;什么是Java序列化，如何实现Java序列化 # 序列化就是用来处理对象流的机制，所谓对象流雁去吃将对象的内容进行流化。可以对流化后的对象进行读写操作，也可以将流化后的对象传输与网络之间，序列化是为了解决对对象流进行读写操作时所引发的问题\n序列化的实现：将需要被序列化的类实现Serializble接口，该接口没有需要实现的方法，然后使用一个输出流来构造一个ObjectOutputStream对象，接着使用ObjectOutputStream对象的writeObject方法可以将参数对象写出\nJava的集合 # \u0026raquo;\u0026gt;ArrayList、HashSet、HashMap是线程安全的吗？如果不是我想要的线程安全的集合怎么办？ # 三个集合都是线程不安全的，源码中也没有对核心方法进行加锁\n在集合中的Vector、HashTable是线程安全的，他们的核心方法上都添加了synchronized关键字\n而且，Collections工具类提供了API可以将线程不安全的集合转为线程安全，如Collections.synchronized(c)，原理也就是在核心方法上添加synchronized关键字\n\u0026raquo;\u0026gt;ArrayList的内部实现 # JDK1.8中，ArrayList底层是一个Object类型的数组，该数组使用transient关键字修饰，transient关键字就是如果序列化ArrayList对象，这个数组不会进行序列化，当调用无参构造创建对象的时候，构造方法会对该数组进行赋值，一个空数组，长度为0，所以，ArrayList的初始长度为0，在第一次add的时候，底层会使用Math.max方法进行判断，如果当前的数组的长度为0，会创建一个长度为10的数组，如果新添加元素后的长度大于当前数组的长度，那么会进行1.5倍的扩容\n\u0026raquo;\u0026gt;并发集合和普通集合的区别 # Java中的常用的并发集合，有对应List的CopyOnWriteArrayList；对应Set的CopyOnWriteArraySet；对应Map的ConcurrentHashMap，都是在JUC包下。在Java中有普通集合、同步集合、并发集合，普通集合的性能最高，但是不能保证多线程的安全和并发可靠性，同步集合，如Vector、HashTable都是使用synchronized关键字强同步，严重的牺牲了性能，而且如果在并发的条件下，效率更低，并发集合通过复杂的策略例如写时复制的做法，实现了读写分离，也就是在往集合中添加元素的时候，并不是直接进行添加，而是，复制原集合，并添加，同时，用于保存数据的Obejct数组，使用volatile修饰，添加完成之后，再将原集合的引用指向新集合\n\u0026raquo;\u0026gt;List的三个子类的特点 # ArrayList：底层结构是一个数组，因为数组是连续空间，所以读操作效率高、增删效率低\nLinkedList：底层结构式链表，因为链表是不连续的空间结构，所以，增删效率高、读效率低\nVector：底层结合是一个数组，由于所有的核心方法都添加了synchronized关键字，虽然保证了线程安全，但是操作效率低\n\u0026raquo;\u0026gt;List、Map、Set的区别 # List和Set都是用来存储单列数据的集合，Map存取由key-value组成的双列数据，其中List存储的数据有序，并且运行重复元素；Set存储的数据无序、要求元素不可以重复，Set底层使用的也是对应的Map；Map存储的数据没有顺序，key不可以重复、value可以重复，因为Map集合中元素的位置是根据HashCode进行计算的，所以这个位置是固定的，但是位置不是用户可以控制的，所以说无序\n\u0026raquo;\u0026gt;List、Map、Set的实现类 # List接口有三个实现类：ArrayList：基于数组实现，线程不安全、但是效率高，查询的效率高，增删效率低；LinkedList：基于双向链表实现，由于链表的在内存中是散乱的，所以，每个元素除了存储自身的地址，还需要存储指向上一个、下一个元素的地址，增删快、查找慢；Vector：基于数组实现，和ArrayList不同的是，Vector初始长度10，扩容时为2倍，而ArrayList初始长度0，扩容为原来的1.5倍\nSet接口有三个实现类：HashSet：基于HashMap实现，Map中所有的key组成了HashSet，由于HashMap中key不允许重复，所有，HashSet的值，不可以重复；LinkedHashSet：继承于HashSet，同时基于LinkedHashMap实现，底层除了维护一个hash表，还维护了一个链表，保证有序；TreeSet，底层是一个红黑树，可以对元素进行排序，一种是自然排序、一种是定制排序\nMap接口有五个实现类：HashMap：底层是一个数组+链表/红黑树的结构，HashTable：底层数组+链表/红黑树，由于多有的关键方法都使用了synchronized关键字修饰，所以线程安全，但是效率不高；LinkedHashMap，继承与HashMap，底层在HashMap的基础上，使用了双向链表，来保证元素的顺序；TreeMap，底层红黑树，可以对元素根据key进行排序，一种自然排序，一种定制排序；Properties：继承于HashTable，所以线程安全，常用来保存配置\n\u0026raquo;\u0026gt;HashMap和HashTable的区别 # 1、HashMap线程不安全、HashTable线程安全\n2、HashTable不允许存放null的键值对，HashMap可以\n3、HashMap的默认长度16，HashTable默认长度11\n4、扩容时，HashMap扩容原来的2倍，HashTable扩容原来的2倍+1\n\u0026raquo;\u0026gt;HashMap在jdk1.7和1.8的区别 # 1、7中，初始创建一个长度为16的数组，8中在第一次添加元素的时候才会创建16的数组\n2、7中底层是一个Entry数组，8中底层是一个Node数组\n3、7中底层只有数组+链表，8中加入红黑树\n4、链表中的指向，七上八下，7中指向的是旧元素，8中指向的是新元素\n\u0026raquo;\u0026gt;ArrayList和LinkedList的区别 # ArrayList使用了数组的实现，可以认为ArrayList里面封装了对内部数组的操作，比如添加、删除、修改，以及对数组的动态扩容\nLinkedList使用循环双向链表的数据结构，链表由一系列的表项连接而成，在JDK中，无论LinkedList中是否有值，链表的内部都有一个header表项，即表示了链表的开始，也表示了链表的结尾表项header后驱表是链表的第一个元素，header表项的前去表项，就是链表的最后一个元素\n\u0026raquo;\u0026gt;List a = new ArrayList()和ArrayList a = new ArrayList()的区别 # 第一种，体现了Java面向对象的多态，也就是父类或接口引用指向子类或实现类对象，ArrayList有但是List没有的方法和属性，例如trimToSize方法，就不能用了；第二种，是ArrayList的对象指向ArrayList的引用，所有的方法和属性都可以使用\n\u0026raquo;\u0026gt;要对集合更新操作时，ArrayList和LinkedList哪个更合适 # 首先，ArrayList基于动态数组的数据结构，LinkedList基于链表的数据结构\n如果对集合访问，ArrayList绝对比LinkedList效率高，因为LinkedList需要移动指针\n如果对集合增删，那么LinkedList更加具有优势，因为ArrayList需要移动数据\n\u0026raquo;\u0026gt;Map中的key和value可以为null吗 # HashMap对象的key、value都可以为null\nHashTable对象的key、value都不可以为null\n而且二者的key都不可以重复，如果添加了相同key的键值对，后面的value会自动覆盖前面的value\nJava的多线程和并发库 # \u0026raquo;\u0026gt;ThreadLocal # ThreadLocal的作用和目的是：实现线程内的数据共享，即对于相同的程序代码，多个模块在同一个线程中运行时要共享一份数据，而在另外线程中运行时又共享另外一份数据\n\u0026raquo;\u0026gt;Java中，线程关闭就成了死线程，不可以再启动，启动就会报错，怎么解决这个问题 # 可以使用Executors.newSingleThreadExecutor()来再次启动一个线程\n\u0026raquo;\u0026gt;多线程的创建方式 # 1、继承Thread类，但是Thread本质上实现了Runnable接口的一个实例，他代表一个线程的实例，并且，启动线程的唯一方法就是通过Thread类的start实例方法。start方法是一个native方法，它将启动一个新的线程，并执行run方法\n2、实现Runnable接口，实现run方法，实例化Thread，并且构造传入Runnable实例\n3、实现Callable接口，并且实现call方法，然后将实现Callable接口的实例传入FutureTask构造，实例化FutrueTask对象，然后将FutureTask对象传入Thread构造器，实例化Thread对象，调用Thread的start方法开启线程，调用FutrueTask对象的get方法可以获取call方法的返回值\n4、使用线程池\n\u0026raquo;\u0026gt;在Java中wait和sleep方法的区别 # 1、wait方法会释放锁，sleep方法不会释放锁\n2、wait方法为Object中的native方法，sleep方法为Thread中的static native方法\n3、sleep方法可以在程序的任何位置使用，wait方法必须使用在同步代码块或同步方法中\n\u0026raquo;\u0026gt;synchronized和volatile关键字的作用 # 一旦一个变量被volatile修饰后，那么就有两个作用\n1、保证了不同线程对这个变量操作的可见性，即一个线程修改了这个变量，新值对其他线程是可见的\n2、禁止进行指令重排\n一、本质\nvolatile本质上就是告诉jvm当前变量在寄存器（工作内存）中的值不确定的，需要到贮存中读取\nsynchronized则是锁定当前变量，只有当前线程可以访问该变量，其他线程被阻塞\n二、作用级别\nvolatile只可以用在变量级别\nsynchronized可以使用在变量、方法、类级别\n三、可见性、原子性\nvolatile只可以保证修改的可见性，不可以保证修改的原子性\nsynchronized既可以保证变量修改的可见性、也可以保证原子性\n四、线程阻塞\nvolatile不会导致线程阻塞\nsynchronized会导致线程阻塞\n五、编译器优化\nvolatile标记的变量不会被编译器优化\nsynchronized标记的变量可以被编译器优化\n\u0026raquo;\u0026gt;什么是线程池，如何使用 # 线程池就是事先将多个线程放在一个容器中，当使用的时候就不用new线程，而是直接去池中拿线程就可以了，节省了开辟新的线程的时间，大大提高了代码执行效率\n在JUC包中的Executors线程池工厂类，提供了四种线程池，分别是单线程线程池newSingleThreadExecutor、固定数目的线程池newFixedThreadPool、可缓存线程的线程池newCachedThreadPool、定时周期执行的线程池newScheduledThreadPool\n但是，根据阿里开发手册，不建议使用以上方法进行创建线程池，建议使用new ThreadPoolExecutor();进行创建，可以自己控制相关的参数，明确线程运行规则\n\u0026raquo;\u0026gt;对线程池的理解 # 1、降低资源消耗，通过反复利用已经创建的线程降低线程的创建和销毁造成的小号\n2、提高响应速度，任务到达时，任务可以不需要等待线程的创建就可以立即执行\n3、提高线程的可管理性，线程是稀缺资源，如果无限制的创建，不仅会消耗系统的资源，还会降低系统的稳定性，使用线程池，可以统一进行分配、调优、监控\n\u0026raquo;\u0026gt;线程池的启动策略（线程池的启动、运行流程） # 1、线程池的创建，new ThreadPoolExecutor()中一共有七个参数：\n参数1：线程池的核心线程数corePoolSize\n参数2：线程池的最大线程数maxPoolSize\n参数3：线程池除了核心线程外，其他线程的超时时间\n参数4：超时时间的单位\n参数5：用于存放任务的阻塞队列\n参数6：用于创建线程的线程工厂\n参数7：用于拒绝任务的拒绝策略\n2、线程池中调用execute方法添加任务的执行流程\na、当线程池中核心线程有空闲，那么直接使用核心线程进行任务\nb、如果线程池中核心线程没有空闲，如果阻塞队列没有满，那么将任务放入阻塞队列\nc、如果阻塞队列已经满了，而且线程池中的线程数小于最大线程数，那么新建线程，进行任务\nd、如果线程池中，核心线程、最大线程、阻塞队列都满了，那么抛出异常\n\u0026raquo;\u0026gt;什么情况下导致死锁，遇到线程死锁怎么解决 # 死锁指的是多个线程因为竞争资源而造成的一个僵局，如果没有外力作用，那么进程无法向前推进\n产生死锁的条件：\n1、互斥条件：线程中所分配的资源必须进行排他控制，也就是这个资源在同一时间内，只可以被一个线程占用，如果其他线程请求这个资源，只可以等待\n2、不剥夺条件：也就是线程获得的资源不可以被其他线程强行夺走，只可以是当前线程自己释放\n3、请求和保持条件：线程已经保持了至少一个资源，但是又提出新的资源请求，这个资源已经被其他线程占用，那么此时进程阻塞，但是线程不释放自己持有的资源\n4、循环等待条件：在一个由线程组成的循环等待链中，链中的每一个线程都以获得一个资源，而且这个资源被下一个线程所请求，同样这个线程也在上一个线程所持有的资源\n如何避免线程：\n1、清晰加锁顺序，避免出现循环等待\n2、线程时限，线程尝试获取锁的时候一定加一定的时限，超时的话则放弃自己对锁的请求，同时释放自己的锁\n\u0026raquo;\u0026gt;Java中多线程的通信怎么实现 # 1、共享变量，例如一个boolean类型的变量\n2、wait/notify机制，通过一个线程wait并notify其他线程\n\u0026raquo;\u0026gt;线程和进程的区别 # 进程：具有一个独立功能的程序，是操作系统进行资源分配和调度的一个独立单位\n线程：是进程的一个实体，是CPU调度和分派的基本单位，是比进程更小的可以独立运行的基本单位\n\u0026raquo;\u0026gt;同步线程及线程调度相关的方法 # wait()：使一个线程处于等待（阻塞）状态，并且释放当前线程持有的锁\nsleep()：使一个正在运行的线程处于睡眠状态，是一个静态方法，此方法不会使线程释放锁\nnotify()：唤醒一个处于等待状态的线程，当然在调用这个方法时，并不能明确唤醒的是哪一个线程\nnotifyAll()：唤醒所有处于等待状态的线程，该方法并不是将锁给所有线程，而只是唤醒，让这些线程自己竞争\n\u0026raquo;\u0026gt;启动一个线程是调用run()还是调用start() # 启动一个线程调用的是start方法，使线程所代表的虚拟处理机处于可运行状态，这意味着它可以由JVM调度并执行，但是并不一定会立即执行\nrun()方法是启动线程后进行回调的方法，由虚拟机执行\nJava的内部类 # \u0026raquo;\u0026gt;静态内部类（静态嵌套类）和非静态内部类（内部类）的区别 # 静态内部类：是被声明再一个类中的静态的内部类，它可以不依赖外部类实例被实例化\n非静态内部类：需要在外部类实例化之后才可以进行实例化\nJavaSE高级 # Java中的反射 # \u0026raquo;\u0026gt;对Java中反射的理解 # Java的反射首先是能够获取到Java中需要反射的类的字节码，获取字节码有三种方式，1、Class.forName(className)2、类名.class3、this.getClass()。然后将字节码中的方法、变量、构造等映射为相应的Method、Filed、Constructor对象，这些对象有丰富的方法可以使用\nJava中的代理 # \u0026raquo;\u0026gt;写一个ArrayList的动态代理类 # List\u0026lt;String\u0026gt; list = new ArrayList\u0026lt;\u0026gt;(); List\u0026lt;String\u0026gt; proxy = (List\u0026lt;String\u0026gt;) Proxy.newProxyInstance(list.getClass().getClassLoader(), list.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(list, args); } }); proxy.add(\u0026#34;你好\u0026#34;); System.out.println(list); \u0026raquo;\u0026gt;动静态代理的区别，使用场景 # 1、静态代理通常只代理一个类，动态代理是代理一个接口下的多个实现类\n2、静态代理再程序运行之前就知道代理的是什么，而动态代理只有到运行才知道\n3、两种代理都需要实现接口，实际上都是代理接口\n4、除了Proxy实现动态代理，还有一种是CGLIB代理，这种代理不需要业务类实现接口，而是通过派生的子类实现代理，通过运行时，动态修改字节码达到修改类的目的。\nJava中的设计模式\u0026amp;回收机制 # \u0026raquo;\u0026gt;你所知道的设计模式有哪些 # Java中一般认为有23中设计模式\n创建型模式：工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式\n结构型模式：适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式\n行为型模式：策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式\n\u0026raquo;\u0026gt;JVM垃圾回收机制和常见算法 # GC整体分为发现无用对象和回收无用对象两个部分组成\n搜索算法\n1、引用计数器算法（弃用）\n引用计数器算法就是给每个对象设置一个计数器，当对象被引用一次，计数器加一，引用失效一个，计数器建议，当该对象的计数器为零时，JVM就认为该对象无用了，可以进行回收\n2、根搜索算法（使用中）\n根搜索算法，是通过一些“GC Roots”对象作为起点，从这些起点开始向下搜索，搜索通过的路径成为引用链，当一个对象没有被GC Roots的引用链链接的时候，声明这个对象是不可用的\nGC Roots对象包括\na、虚拟机栈（栈帧中的本地变量表）中引用的对象\nb、方法区中类的静态属性引用的对象\nc、方法区中常量引用的对象\nd、本地方法栈中Native方法引用的对象\n搜索到无用对象后，就可以进行回收了，GC的回收算法有四种\n1、标记清除算法，这种算法是直接将对象从内存中移除，但是会产生大片的不连续空间，也就是内存碎片\n2、复制算法，这种算法，就是将内存划分为两个区域，大小相同，每次将一个区域的对象复制到另一个区域，过程中，将无用的对象移除，这种算法不会造成内存不连续\n3、标记整理算法，这种算法是将无用对象回收后，然后将内存中的对象统一移向一段，不会造成内存不连续，一般用来回收老年代\n4、分代收集，这种是将对象按照存活时间分为新生代和老年代，然后根据对象存活特点，每代采用不同的算法进行回收\n\u0026raquo;\u0026gt;Java内存模型 # Java虚拟机将其管辖的内存大致分为三个逻辑部分：方法区、Java栈、Java堆\n1、方法区是静态分配的，编译器将变量绑定再某个存储位置上，而且这些绑定不会在运行时改变。常量池，源代码中的命名常量、String常量、static变量保存在方法区\n2、Java栈是一个逻辑概念，特点先进后出，内存中的空间可能是连续的，也可能是不连续的\n3、Java堆在运行时，进行存储空间分配和回收内存管理\n\u0026raquo;\u0026gt;Java的内存分配 # 通常我们定义的基本数据类型变量、对象的引用、还有函数调用的现场保存都使用JVM中的栈空间，而通过new关键字和构造器创建的对象则放在堆空间，堆是垃圾收集器管理的主要区域，由于现在的垃圾收集器都是采用分代算法，所以对空间还可以细分为新生代和老年代，再具体新生代可以分为Eden、Survivor（分为From Survivor和To Survivor）、Tenured；方法区和堆都是各个线程共享的内存区域，用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据；程序中的字面量如直接书写的100、“hello”和常量都是放在常量池中，常量池是方法区的一部分。栈空间操作起来最快但是栈很小，通常大量的对象都是放在堆空间，栈和堆的大小都可以通过JVM的启动参数来进行调整，栈空间用光了会引发StackOverflowError，而堆和常量池不足会引发OutOfMemoryError\nJava的类加载器 # \u0026raquo;\u0026gt;Java类加载器种类有哪些 # 1、根类加载器（BootStrapClassLoader）：C++写的，加载Java的核心类\n2、扩展类加载器（ExtClassLoader）：加载位置jre\\lib\\ext，加载Java的扩展类\n3、系统、程序类加载器（AppClassLoader）：加载位置classpath中\n4、自定义加载器：必须继承ClassLoader\n\u0026raquo;\u0026gt;类什么时候被初始化 # 1、类实例化的时候\n2、调用类或接口的静态变量\n3、调用类的静态方法\n4、类的反射\n5、初始化一个类的子类，首先初始化子类的父类\n6、JVM启动标明的启动类，即文件名和类名相同的那个类\n\u0026raquo;\u0026gt;ClassLoader双亲委托机制 # Java是一种类型安全的语言，Java程序的.java文件编译完成会生成.class文件，而.class文件被类加载器所加载，当加载的时候，首先AppClassLoader加载，它并不会自己尝试加载，而是把加载请求交给父类加载器ExtClassLoader，ExtClassLoader加载的时候也不会自己尝试加载，而是把加载请求委托给BootStrapClassLoader\n如果BootStrapClassLoader加载失败，也就是没有在jre/lib中找到该class，那么会使用ExtClassLoader尝试加载，如果ExtClassLoader也加载失败，那么使用AppClassLoader加载，如果也加载失败，那么就会抛出ClassNotFoundException\n\u0026raquo;\u0026gt;描述JVM加载class # JVM中类的装载由类加载器ClassLoader和它的子类实现，Java中类加载器是一个重要的Java运行时系统组件，它负责在运行时查找和装入类文件中的类\n由于Java的跨平台性，经编译的Java源程序并不是一个可执行程序，而是一个或多个类文件。当Java程序需要使用某个类时，JVM会确保这个类已经被加载、连接和初始化。类的加载是指类的.class文件中的数据读入到内存中，通常时创建一个字节数组读入.class文件，然后产生与所加载类对应的Class对象。加载完成后，Class对象还不完整，所以此时的类还不可用。当类被加载后进入连接阶段，这个阶段包括验证、准备（伪静态变量分配内存并设置默认的初始值）和解析（将符号引用替换为直接引用），最后JVM对类进行初始化\n\u0026raquo;\u0026gt;获取Class对象的方式 # 1、类.class\n2、对象.getClass()\n3、Class.formName()\n4、ClassLoader.getSystemClassLoader().loadClass(\u0026quot;Person\u0026quot;)\nJVM基础知识 # \u0026raquo;\u0026gt;既然有GC机制，为什么还会出现内存泄漏 # Java中存在内存泄漏风险的因素：\n1、各种静态容器类，由于这些容器是静态的，不会被GC回收，其中的元素引用的对象，也不会被回收\n2、单例造成内存泄漏，单例对象一旦实例化，那么再JVM的声明周期中存在\n3、各种的连接\n例如数据库的连接，网络连接，IO连接，这些实例属于持久态，如果没有显示的关闭，对象无法被GC回收\n\u0026raquo;\u0026gt;在开发中遇到过内存溢出吗？原因、解决方法 # 引起内存溢出的原因有很多种，常见的：\n1、内存中加载数据量过大，如一次从数据库取出过多数据\n2、集合类中有对对象的引用，使用完没有清空，导致JVM不能进行回收\n3、代码中出现死循环或循环产生的过多重复的实体对象\n4、使用第三方软件的BUG\n5、启动参数内存值设定过小\n解决方法：\n1、修改JVM的启动参数，直接增加内存\n2、检查错误日志，查看OutOfMemory错误前是否存在其他异常或错误\n3、对代码进行走查和分析，找出可能发生内存溢出的位置\n重点排查以下几点：\n1、检查对数据库查询中，是否有一次获得大量数据的查询，一般来说，如果一次取十万条数据，那么可能导致内存溢出，这个问题比较隐蔽，因为在上线前，数据量不大，所以不容易出现问题，因此对于数据库的查询，尽量采用分页\n2、检查代码中是否存在死循环或递归调用\n3、检查代码中是否存在，集合类使用完后，保存了大量的对象，但是没有清空\nJava8的新特性及使用 # \u0026raquo;\u0026gt;什么是Stream流 # 拿常见的集合作为比较，集合更关注与数据的存储，和内存打交道，但是Stream更关注数据的运算，和CPU打交道，Stream流本身不具备存储能力，Stream流并不会改变原对象，而是创建一个新的对象，Stream流是懒加载，也就是需要结果的时候，才会进行运算\n\u0026raquo;\u0026gt;创建一个Stream流有几种方式 # 对于数组，可以通过Arrays.stream创建，或者通过Stream.of，这个方法的形参是可变参数，可以传入一个数组或多个参数\n对于集合，可以通过集合对象.stream()这个方法创建的顺序流，也可以创建并行流集合对象.parallelStream()\nJavaWEB基础 # JDBC技术 # \u0026raquo;\u0026gt;说下原生jdbc操作数据库的流程 # 1、Class.formName()加载数据库连接驱动\n2、DriverManager.getConnection()获取数据连接对象\n3、根据SQL获取sql会话对象，一般有两种，statement、PreParedStatement\n4、执行SQL处理结果集，执行SQL前如果有参数值，就设置参数值\n5、关闭结果集，关闭会话，关闭连接\n\u0026raquo;\u0026gt;为什么使用PreparedStatement # 1、PreparedStatement接口继承Statement，PreparedStatement实例包换已编译的SQL语句，所以执速度比Statement快\n2、作为Statement的子类，PreparedStatement继承了Statement的所有功能，三种方法execute、executeQuery和executeUpdate\n3、在JDBC中，任何时候都不要使用Statement，因为：\na、代码的可读性和维护性，Statement需要拼接SQL，但是PreparedStatement不会\nb、PreparedStatement尽最大的可能提高了性能，DB有缓存机制，相同的预编译语句再次被调用不会再次编译\nc、最重要的一点就是极大的提高了安全性，Statement容易被SQL注入，而PreparedStatement传入的内容不会和sql语句发生匹配关系\n\u0026raquo;\u0026gt;关系数据的连接池机制是什么 # 前提是为数据库连接建立一个缓冲池\n1、从连接池获取或创建可用链接\n2、使用完毕之后，把连接返回给连接池\n3、在系统关闭前，断开所有的连接并释放连接占用的系统资源\n4、能够处理无效的连接，限制连接池中连接总数不低于或不超过某一个值\nHTTP协议 # \u0026raquo;\u0026gt;http的长连接和短连接 # HTTP协议有HTTP/1.0和HTTP/1.1版本，1.1默认保持长连接，数据传输完成保持TCP连接不会断开，等待在同域名下继续使用这个通道传输数据，反之就是短连接\n1.0默认使用短连接，也就是浏览器和服务器每一次进行HTTP操作，就建立一次连接，任务结束就中断连接\n\u0026raquo;\u0026gt;HTTP/1.1和HTTP/1.0的区别 # 可扩展性\n1、1.1中，在消息中增加了版本号，用于兼容性判断\n2、1.1增加了OPTIONS方法，同允许客户端获取一个服务器支持的方法列表\n3、为了与未来的协议规范兼容，1.1在请求消息中包含了Upgrade头域，通过该头域，客户端可以让服务器知道同可以支持的其他备用通信协议，服务器可以据此进行协议转换，使用备用协议与客户端进行通信\n缓存\n在 HTTP/1.0 中，使用 Expire 头域来判断资源的 fresh 或 stale，并使用条件请求（conditional request）来判断资源是否仍有效。HTTP/1.1 在 1.0 的基础上加入了一些 cache 的新特性，当缓存对象的 Age 超过 Expire 时变为stale 对象，cache 不需要直接抛弃 stale 对象，而是与源服务器进行重新激活（revalidation）\n带宽优化\nHTTP/1.0 中，存在一些浪费带宽的现象，例如客户端只是需要某个对象的一部分，而服务器却将整个对象送过来了。例如，客户端只需要显示一个文档的部分内容，又比如下载大文件时需要支持断点续传功能，而不是在发生断连后不得不重新下载完整的包\n长连接\nHTTP 1.1 支持长连接（PersistentConnection）和请求的流水线（Pipelining）处理，在一个 TCP 连接上可以传送多个 HTTP 请求和响应，减少了建立和关闭连接的消耗和延迟\n\u0026raquo;\u0026gt;http常见的状态码 # 200：OK，客户端请求成功\n301：永久移除，请求的URL已经被移走，Response中应该包含一个Location URL，说明资源现在所处的位置\n302：重定向\n400：客户端请求语法错误，服务器不能理解\n401：请求未经授权\n403：服务器收到请求，但是拒绝服务\n404：请求资源不存在\n500：服务器异常\n503：服务器当前不能处理请求，一段时间后可能会恢复\n\u0026raquo;\u0026gt;GET和POST的区别 # 1、get的请求参数会放在URL后面，但是post会放在请求体中\n2、get的参数长度最大1024字节，get的参数限制实际上是URL的限制，取决于操作系统和浏览器，但是post理论上没有限制\n3、post的安全性比get高，get一般用来对服务器索取数据，post用来提交数据\n\u0026raquo;\u0026gt;http中重定向和转发的区别 # 本质上，转发是服务器的行为，重定向是客户端的行为\n重定向的特点是，两次请求，浏览器的地址会发生变化，可以访问自己web外的资源，但是传输的数据丢失\n转发的话，一次请求，浏览器地址不变，访问的是自己web的资源，数据不会丢失\nCookie和Session # \u0026raquo;\u0026gt;Cookie和Session的区别 # Cookie保存在浏览器可就是客户端，web服务器发送给浏览器的一块信息，浏览器会在本地文件中给每一个web服务器存储cookie，以后再向服务器发送请求的时候，都会携带该服务器的cookie\nSession存储在服务器，Session一般存储特定用户会话所需要的属性和配置信息，当用户在应用程序的Web页跳转的时候，存储在Session中的信息不会丢失\n区别就是，无论浏览器怎么做，Session都可以正常工作，但是如果浏览器禁用Cookie，那么无法使用Cookie；存储数据方面，session可以存储任意Java对象，但是cookie只可以存储字符串\n\u0026raquo;\u0026gt;单点登录中，cookie被禁用了怎么办 # 单点登录的原理，就是后端生成的令牌，这个令牌可以存储在cookie中，但是也可以存储在响应数据中，由前端保存\nJSP # \u0026raquo;\u0026gt;什么是JSP，什么是Servlet，有什么区别 # JSP本质就是一个servlet，他是Servlet的一个特殊形式，每个jsp页面都是一个servlet实例\nServlet是一个由Java提供用于开发web服务器应用程序的组件，运行在服务端，由servlet容器管理，用于生成动态内容，一个Servlet实例，是实现了特殊接口Servlet的Java类，所有自定义个servlet必须实现Servlet接口\n区别：\n1、jsp是html页面中内嵌java代码，侧重页面展示\n2、Servlet是html代码和java代码分离，侧重逻辑控制mvc设计思想中jsp位于视图层，servlet位于控制层\njsp的执行流程：\nJVM只可以识别Java类，并不能识别JSP代码，所以在web容器收到.jsp后缀的url请求时，会将访问请求交给tomcat中Jsp引擎处理，每个jsp页面第一次被访问的时候，jsp引擎将jsp代码解释为一个servlet源程序，编译为class文件，再有web容器servlet引擎去装载执行servlet程序，实现页面交互\n\u0026raquo;\u0026gt;jsp有哪些域对象和内置对象及他们的作用 # 四大域对象：\npageContext：作用域为当前的jsp页面\nrequest：一次请求范围内有效\nsession：一次会话中有效\napplication context：在一个web中使用\n九个内置对象：\nRequest\nResponse\nSession\nOut\nPageContext\nPage\nException\nApplication\nConfig\nJavaWEB高级 # Filter和Listener # \u0026raquo;\u0026gt;什么是Filter，在哪用过 # Filter是Web三大组件（Filter、Listener、Servlet）之一，可以对请求和响应进行拦截，对路径、数据进行过滤，一般在使用tomcat8之前的版本，post请求的数据会出现乱码，所以使用Filter对数据进行处理，解决乱码，也可以使用Filter进行权限控制\n\u0026raquo;\u0026gt;什么是Listener，在哪用过 # Listener是Web三大组件之一，可以对Web程序中的事件进行监听，例如创建、删除、修改对象等，并且触发相应事件，JavaWeb提供了八种监听器，这八种监听器可以分为三类，监听Request、Context、Session对象的创建，监听对象属性的变化，监听Session内对象的变化，一般可以用来监听网站的登陆人数\nLinux # \u0026raquo;\u0026gt;说出几个Linux的常用命令 # 列出文件列表：ls\n创建目录和移除目录：mkdir、rmdir\n用于显示文件后几行内容：tail\n打包：tar -xvf\n打包并压缩：tar -zcvf\n查找字符串：grep\n显示当前所在目录：pwd\n创建空文件：touch\n编辑：vim、vi\n\u0026raquo;\u0026gt;Linux如何查看日志 # 动态打印日志信息：tail -f日志文件\n\u0026raquo;\u0026gt;Linux怎么关闭进程 # 可以使用ps查看进程PID，用kill终止进程\nps用于查看当前正在运行的进程\ngrep搜索\n例如：ps -ef|grep java\n数据库 # MySQL # \u0026raquo;\u0026gt;SQL的select语句完整执行顺序 # 1、from子句组装来自不同数据源的数据\n2、where子句基于条件，对记录记录行进行筛选\n3、group by子句将数据划分为多个分组\n4、使用聚合函数进行计算\n5、使用having子句筛选分组\n6、计算所有的表达式\n7、select的字段\n8、使用order by对结果集进行排序\n\u0026raquo;\u0026gt;SQL的聚合函数 # 聚合函数用来对一组值进行计算，并且返回单一值的函数，经常与select语句中的group by一起使用\navg：平均值\ncount：个数\nmax：最大值\nmin：最小值\nsum：求和，只可以用在数字列\n\u0026raquo;\u0026gt;SQL的sql注入问题 # 一般出现在表单输入恶意的sql语句，例如，登陆的时候，用户名为a or 1=1，那么这个时候sql条件部分恒成立\n防止sql注入：\n1、使用预编译语句，也就是在sql中使用占位符，然后传递参数，preparedStatement使用的就是这种\n2、Mybatis框架中使用井号（#）也可以防止sql注入\n\u0026raquo;\u0026gt;MySQL优化 # 1、一般明知道查询结果只会有一条，那么使用limit 1，这样MySQL引擎查询到一条就会立即终止，防止全表扫描\n2、正确的选择数据库引擎，mysql有两种引擎，MyISAM和InnoDB\nMyISAM适合大量用来查询的应用，对写不友好，因为你有的时候哪怕只是update一个字段，也会锁整个表，别的进程就算读，也需要等update操作完才可以，另外，MyISAM对于select count(*)这种操作效率非常高，MyISAM不支持事务或外键约束\nInnoDB支持行锁和事务，所以在写的这方面比较优秀\n\u0026raquo;\u0026gt;事务的四大特征 # 1、原子性：事务中的所有操作是一个最小的单位，要么全部成功，要么全部失败\n2、一致性：事务开始和结束，数据库的约束没有破坏，多个结点的数据保持一致\n3、隔离性：隔离状态下执行事务，多个事务之间相互不可以干扰\n4、持久性：事务完成后的更改操作永久保存在数据库，而不会回滚消失\n\u0026raquo;\u0026gt;MySQL的四种隔离级别 # 读未提交：未提交读隔离级别也叫读脏，就是事务可以读取其他事务还没有提交的数据\n读已提交：在其他数据库系统比如Oracal默认的隔离级别就是提交读，读已提交就是事务没有提交之前所做的修改其他的事务是不可见的\n可重复读：保证同一个事物在多次相同的查询结果是一致的，比如一个事务一开始查询了一条记录然后过了几秒钟后又执行相同的查询，这两次查询的结果是相同的，可重复读也是MySQL的默认隔离级别\n可串行化：可串行化也就是保证了读取范围内没有新的数据插入，比如事务第一次查询得到某个范围的数据，第二次查询也同样得到相同范围的数据，中间没有新的数据插入到该范围中\n\u0026raquo;\u0026gt;MySQL语句优化 # 1、where子句可以对字段进行null值判断吗\n可以，比如select id from t where num is null这样的sql是可以的，但是最好不要给数据库留null，尽可能使用NOT NULL填充数据库，因为例如char(100)中，即使这个字段的值为null，也会占用100字符的空间，但是如果是varchar，那么不会占用空间，对于数值类型，如果可以的话，那么设置默认值为0\n2、select * from admin left join log on admin.admin_id=log.admin_id where log.admin_id\u0026gt;10如何优化\n这sql语句的优化，应该使用小表驱动大表的方法，也就是将对于admin的查询作为子查询，查询admin中id大于10的，然后再使用结果查询log表，也就是select * from (select * from admin where admin_id\u0026gt;10) T1 left join log on T1.admin_id=log.admin_id\n3、limit基数比较大的时候，使用between\n例如select * from admin order by admin_id limit 100000,10可以优化为select * from admin where admin_id between 100000 and 100010 order by admin_id\n4、避免在索引列上进行任何操作，例如数据类型转换，由于MySQL优化器在优化的时候自动进行数据类型转换，比如一个字段的类型是数值类型，但是我们传入的是一个字符串类型的数值，那么会导致索引失效\n\u0026raquo;\u0026gt;如何提高MySQL的安全性 # 1、如果MySQL客户端和服务端的链接需要跨越并通过不可信任的网络，那么需要使用ssh隧道来加密该链接的通信\n2、删除MySQL默认的test数据库\n3、修改root的用户名\n4、设置MySQL中只有root用户可以访问MySQL主数据库的user表\n5、修改MySQL的3306端口，避免端口扫描工具\nOracle # 框架 # SpringMVC # \u0026raquo;\u0026gt;SpringMVC的工作原理 # 1、用户向服务器发送请求，请求被SpringMVC的前端控制器DispatchServlet捕获\n2、DispatcherServlet对请求URL进行解析，得到请求资源标识符URL，然后根据该URL调用HandlerMapping将请求映射到处理器HandlerExcutionChain\n3、DispatchServlet根据获得Handler选择一个合适的HandlerAdapter适配器处理\n4、Handler对数据处理完成后将返回一个ModelAndView对象给DispatchServlet\n5、Handler返回的ModelAndView只是一个逻辑视图并不是一个真正的视图，DispatchServlet通过ViewResolver视图解析器将逻辑视图转化为真正的视图View\n6、DispatcherServlet通过Model解析出ModelAndView中的参数进行解析最终展现出完整的View并返回给客户端\n\u0026raquo;\u0026gt;SpringMVC的常用注解 # @RequestMapping：用于请求url映射\n@RequestBody：实现接受http请求的json数据，将json数据转化为java对象\n@ResponseBody：实现将Controller方法返回对象转化为json响应给客户端\n\u0026raquo;\u0026gt;如何开启注解处理器和适配器 # 项目中一般会在springmvc的xml文件中通过开启\u0026lt;mvc:annotation-driven\u0026gt;来实现注解处理器和适配器的开启\n\u0026raquo;\u0026gt;怎么解决get和post乱码的问题 # 解决post请求乱码，可以在web.xml中配置一个CharaterEncodingFilter过滤器，设置为utf-8\nget请求乱码，有两种方法可以解决：\n1、修改tomcat配置文件添加编码与工程编码一致\n2、使用字节数组，对参数进行重新编码\nSpring # \u0026raquo;\u0026gt;谈谈对spring的理解 # Spring是一个开源的框架，主要是为了简化企业级应用开发，可以使用简单的JavaBean实现以前只有EJB才能实现的功能，Spring是一个IOC和AOP容器框架\nSpring容器的核心：\nIOC控制反转，也就是在以前的传统java开发，我们需要通过new或者反射来创建对象，但是在Spring中，容器使用了工厂模式为我们创建所需要的对象，不需要我们自己创建，也就是把Bean的管理交给了容器管理；还有就是依赖注入DI，spring使用Bean对象的set或有参构造，在容器创建时将属性进行设值\nAOP面向切面编程，与面向对象不同的是，面向对象是将事务纵向抽成了一个个的对象，而面向切面是将一个个对象相同的地方，横向抽成一个切面，并且在这个切面上进行一些如权限控制、日志等公共操作处理，AOP的底层是动态代理，如果是接口的话，使用的是JDK动态代理如果是类采用的是CGLIB进行动态代理，所以说AOP是OOP的扩展，而不是替代\n\u0026raquo;\u0026gt;Spring中的设计模式 # 1、单例模式：spring中的代理模式，如果目标对象实现了接口，那么spring使用jdk的Proxy类代理，如果目标没有实现接口，那么使用CGLIB库生成目标类的子类，生成的对象默认为单例模式\n2、前端控制器模式：spring提供了前端控制器DispatherServlet对请求进行分发\n3、工厂模式：创建对象的逻辑不会对客户端进行暴露，而是通过使用接口来指向创建的对象，使用BeanFactory创建对象实例\n\u0026raquo;\u0026gt;spring的常用注解 # 在spring2.5以后，可以使用注解来配置依赖注入，可以使用注解代替xml中的bean描述，但是注解模式默认是关闭的，所以需要在spring的核心配置文件中进行配置\u0026lt;context:annotation-config/\u0026gt;\n常用的注解：\n@Autowried\n@Resource\n@Aspect\n@Component\n\u0026raquo;\u0026gt;spring bean的生命周期 # bean的定义：在spring核心配置文件中，使用\u0026lt;bean\u0026gt;定义\n1、通过构造器，实例化，默认调用无参构造\n2、通过set对对象的属性进行赋值\n3、将bean实例传递给bean的后置处理器方法\n4、调用bean的初始化方法\n5、将bean实例传递给bean的后置处理器方法\n6、bean创建完毕，可以使用\n7、调用bean的销毁方法\n\u0026raquo;\u0026gt;spring可以帮我们做什么 # 1、可以帮我们根据配置文件创建、组装对象之间的依赖关系\n2、spring面向切面编程，可以帮助我们无耦合实现日志记录、性能统计等\n3、spring可以非常简单的帮我们管理数据库事务，我们只需要获取连接，执行sql\n4、spring可以和第三方数据库访问框架集成，而且自己也提供了一套jdbc访问模板\n5、spring可以和第三方web框架集成，自己也提供了SpringMVC框架\n\u0026raquo;\u0026gt;spring的事务 # spring提供了声明式事务管理，和传统的编程式事务管理方式相比，无侵入，并不会和业务代码耦合，使用AOP的思想，所以大大的提高了系统的可维护性\n经常使用的方式，实在spring核心配置文件中注入需要的transactionalManager，然后通过\u0026lt;context:annotation-config/\u0026gt;开启spring注解功能，然后在需要添加事物的方法或类上添加注解@Transactional开启事务\n\u0026raquo;\u0026gt;BeanFactory的常用实现类 # BeanFactory一般是spring内部使用的一个接口，我们经常使用的是继承了BeanFactory接口的ApplicationContext接口，常见的实现类有，ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext\n\u0026raquo;\u0026gt;解释SpringJDBC、SpringDAO和SpringORM # springdao不是一个模块，而是只要写dao操作、写好dao的操作规范以后，使用@Repository进行注解\nspringjdbc提供了jdbc模板类，移除了连接代码，只需要写好sql以及相关参数\nspringorm是一个包含了多个持久层基数的总括模板\n\u0026raquo;\u0026gt;spring的配置文件有什么用 # spring配置文件是一个xml文件，文件里面包含了类信息，描述了如何配置，如何相互调用\n\u0026raquo;\u0026gt;什么是springIOC容器 # IOC即控制反转，springIOC负责创建对象，管理对象，通过依赖注入DI，装配对象，配置对象，也就是管理了对象的整个生命周期\n\u0026raquo;\u0026gt;IOC的优点 # IOC可以把应用的代码量降到最低，容易进行测试，而且默认单例，大大减少了代码量，可以以最小的代价和最小的侵入性松散代码的耦合，而且IOC支持服务的饿汉初始化以及懒加载\n\u0026raquo;\u0026gt;什么是spring的依赖注入 # 在传统的java开发中，我们在一个类中需要另一个类的属性或方法，做法就是new一个实例，然后调用，但是new的实例不好管理，所以使用DI，依赖类的实例不再需要我们new，而是spring容器帮我们new然后注入\n\u0026raquo;\u0026gt;什么是spring beans # spring beans是那些形成spring应用主干的java对象，它们被springIOC容器初始化、装配、管理，这些bean通过容器中配置的元数据创建\n\u0026raquo;\u0026gt;怎么定义类的作用域 # 当定义一个bean标签在spring里，可以通过该标签的scope属性来定义作用域，如果需要spring在容器在每次调用该对象的时候都产生一个新的对象，那么可以将scope属性值指定为prototype，如果需要单例的，那么属性为singleton，这个也是默认的\n\u0026raquo;\u0026gt;spring支持几种bean的作用域 # 五种：\nsingleton：单例，也是默认的\nprototype：一个bean可以有多个实例\nrequest：web环境下，作用域是一次请求\nsession：web环境下，作用于是一次会话\nglobal-session：web环境下，作用域全局会话\n\u0026raquo;\u0026gt;spring的单例bean线程安全吗 # 线程不安全\n\u0026raquo;\u0026gt;spring中如何注入一个java集合 # list标签，可以注入一列值，允许相同的值\nset标签，注入一组值，但是不允许相同\nmap，注入一组键值对，键值随意\nprops，注入一组键值对，但是都只能是字符串\n\u0026raquo;\u0026gt;不同方式的自动装配 # 四种自动装配方式，可以用来指导spring容器自动装配的方式进行依赖注入\n1、no：默认方式不进行自动装配，需要显式的设置ref属性进行装配\n2、byName：通过参数名进行装配，spring容器在配置文件中发现bean的autowired属性被设置为byname，之后容器尝试匹配、装配\n3、byType：通过参数类型自动装配，spring容器在配置文件中发现bean的autowired属性设置为bytype，之后容器尝试匹配、装配相同类型的bean，如果有多个，抛出异常\n4、default：通过对beans标签声明全局注入方式，确定bean标签的注入方式\n\u0026raquo;\u0026gt;简单解释Spring的AOP # AOP，就是面向切面编程，对于常见的面向对象编程OOP来说，OOP通过引入封装、集成、多态来建立对象的层次结构，但是通常OOP是一种纵向的编程，并不支持定义横向关系，所以使用AOP，可以完成例如日志功能，日志功能的代码，可以横向的放入在多个对象的层次中，而不会影响原来的功能代码，可以降低代码的耦合和重复代码，这些添加增强的地方也就是切面，在SpringAOP里面，可以通过@Aspect注解来定义增强类\n\u0026raquo;\u0026gt;spring两种代理JDK和CGLIB的区别 # JDK的动态代理，是利用反射机制生成一个实现代理接口的匿名类，在具体调用方法前调用InvokeHandler处理，而CGLIB动态代理是利用开源包，对代理对象类的class文件加载，通过修改其字节码生成子类来处理\n1、如果目标对象实现了接口，那么默认使用JDK来动态代理\n2、如果对象没有实现接口，那么必须采用CGLIB\n\u0026raquo;\u0026gt;Spring的通知是什么，有哪几种 # 通知就是在方法的前或后进行的动作，也就是程序执行时，通过SpringAOP触发的代码段\n一共有五种通知\n前置通知、后置通知、环绕通知、后置返回通知、异常通知\nShiro # \u0026raquo;\u0026gt;什么是Shiro # Apache Shiro是一个java的安全框架，使用shiro不但可以在javaSE环境也可以使用在javaEE环境，Shiro可以帮助我们完成认证、授权、加密、会话管理、与Web集成、缓存\nShiro三个核心的组件：\n1、subject，主体对象，它不仅仅指人，也可以是任何与程序交互的东西\n2、SecurityManager：他是Shiro框架的核心，Shiro也是通过安全管理器，完成对各种服务的管理\n3、Realm：充当了Shiro与应用安全数据的桥梁和连接器，也就是用户在认证和授权的时候，需要在配置的Realm中查询\n\u0026raquo;\u0026gt;Shiro的运行原理 # 1、应用程序如果需要进行权限控制，那么调用Subject的API\n2、Subejct为主体，所有的Subject都绑定在SecurityManager上，与Subject的所有交互，都会委托给SecurityManager\n3、Realm域，需要在Realm中，对于用户的身份以及权限进行验证\nMybatis # \u0026raquo;\u0026gt;Mybatis中#和$的区别 # #相当于对数据加双引号，$相当于直接拼接数据\n也就是在传入数据的时候，会进行数据转换，所以如果是数值列，而且不担心出现sql注入的问题的话，那么使用$，因为如果#传入数值99，那么传入的sql为'99\u0026rsquo;，这样会导致该字段的索引失效\n\u0026raquo;\u0026gt;JDBC的不足之处，Mybatis怎么解决 # 1、数据库连接的创建和销毁过于频繁，浪费系统性能，使用数据库连接池，可以解决这个问题\n2、sql语句不容易维护，而使用mybatis的时候，在mapper的xml文件中进行维护\n3、sql传参过于麻烦，需要将参数和占位符一一对应，而mybatis可以使用自动映射\n4、对于结果集的处理也比较麻烦，mybatis可以将数据的记录解析为对象\n\u0026raquo;\u0026gt;mybatis的mapper接口调用要求 # 1、mapper接口方法名和xml中的sql的id相同\n2、mapper接口方法的参数类型和xml中parameterType类型相同\n3、接口方法的返回类型需要和xml中的resultType类型相同\n4、xml中的namespace是mapper接口的类路径\n\u0026raquo;\u0026gt;mabatis的一级缓存和二级缓存 # 一级缓存：作用域是一次SqlSession，如果session被flush或close，那么这个缓存将会被清空\n二级缓存和以及缓存的机制相同，只不过二级缓存是基于namespace的，这样多个线程的会话，可以使用同一个缓存，如果进行了cud操作，那么该作用于下的所有select的缓存，都会被clear\n\u0026raquo;\u0026gt;Mybatis怎么在insert的时候返回主键id # 数据库为mysql的时候，可以在insert标签设置属性，keyproperty为需要返回的属性，useGeneratedKeys改为true，表示id自增\n如果数据库是Oracle，因为oracle没有自增，所以不可以使用useGeneratedKeys，使用selectKey标签获取Id，赋值到对象的属性\n最新技术 # Redis # \u0026raquo;\u0026gt;Redis的特点 # Redis是使用C语言编写的，典型的NoSql数据库，基于key-value类型的内存存储系统，一共有五种数据类型：list、set、zset、hash、string\n由于Redis是默认是纯内存操作，所以每秒可以处理10万次读写操作，是已知速度最快的key-value DB\n但是Redis的缺点也是由于纯内存操作，不能进行海量数据的读写，所以Redis适合用在较小数据量的高性能操作上\n\u0026raquo;\u0026gt;redis为什么把数据放在内存上 # redis因为需要最快的读写速度，所以全部放在内存中，而且可以通过配置RDB或AOF的方式，将数据异步写入磁盘，如果不把数据放在内存，而是磁盘，往往磁盘IO是很慢的，严重影响redis的性能\n\u0026raquo;\u0026gt;redis的持久化 # RDB：这种持久化方案，不会记录数据产生、修改过程，只会根据某一时间点的快照保存最终数据，一种命令是使用save，这种是同步阻塞命令，还有一种使用bgsave命令，这种是异步非阻塞式命令，RDB也支持自动持久化，需要在redis.conf文件中进行配置\nAOF：这种持久化方案，会记录数据的所有产生和修改的命令，服务器重启，会根据这些命令恢复数据，如果RDB和AOF同时存在，会优先恢复AOF记录的数据\n","date":"2024-01-04","externalUrl":null,"permalink":"/posts/3ab7256e/153a4d49/899a8f98/","section":"文章","summary":"\u003ch3 class=\"relative group\"\u003eJavaSE基础 \n    \u003cdiv id=\"javase基础\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#javase%e5%9f%ba%e7%a1%80\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003eJava面向对象 \n    \u003cdiv id=\"java面向对象\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#java%e9%9d%a2%e5%90%91%e5%af%b9%e8%b1%a1\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\n\u003ch5 class=\"relative group\"\u003e\u0026raquo;\u0026gt;面向对象有哪些特性，以及对这些特性的理解 \n    \u003cdiv id=\"面向对象有哪些特性以及对这些特性的理解\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e9%9d%a2%e5%90%91%e5%af%b9%e8%b1%a1%e6%9c%89%e5%93%aa%e4%ba%9b%e7%89%b9%e6%80%a7%e4%bb%a5%e5%8f%8a%e5%af%b9%e8%bf%99%e4%ba%9b%e7%89%b9%e6%80%a7%e7%9a%84%e7%90%86%e8%a7%a3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h5\u003e\n\u003cp\u003e封装、继承、多态、（抽象）\u003c/p\u003e","title":"面试宝典","type":"posts"},{"content":"","date":"2024-01-04","externalUrl":null,"permalink":"/posts/3ab7256e/153a4d49/","section":"文章","summary":"","title":"面试题","type":"posts"},{"content":"","date":"2024-01-04","externalUrl":null,"permalink":"/series/%E9%9D%A2%E8%AF%95%E9%A2%98/","section":"Series","summary":"","title":"面试题","type":"series"},{"content":"","date":"2024-01-04","externalUrl":null,"permalink":"/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/","section":"标签","summary":"","title":"面试题","type":"tags"},{"content":"","date":"2023-12-25","externalUrl":null,"permalink":"/posts/26654e7b/4e28ccf1/","section":"文章","summary":"","title":"Windows","type":"posts"},{"content":"","date":"2023-12-25","externalUrl":null,"permalink":"/series/windows/","section":"Series","summary":"","title":"Windows","type":"series"},{"content":"","date":"2023-12-25","externalUrl":null,"permalink":"/tags/windows/","section":"标签","summary":"","title":"Windows","type":"tags"},{"content":" 方法一 # public class ArraysTest { public static void main(String[] args) { int num = 0; // 遍历2~100的所有整数 for (int i = 2; i \u0026lt;= 100; i++) { // 内层循环，遍历2到i for (int j = 2; j \u0026lt;= i; j++) { // 如果可以被除了i本身以外的数整除，跳出内层循环 if (i % j == 0 \u0026amp;\u0026amp; i != j) { break; // 如果不可以整除，重新开始内层循环 } else if (i % j != 0) { continue; // 输出质数，并且叠加计数num } else { System.out.print(i + \u0026#34;,\u0026#34;); num++; } } } System.out.println(\u0026#34;一共\u0026#34; + num + \u0026#34;个\u0026#34;); } } 方法二 # public class ArraysTest { public static void main(String[] args) { int sum = 0; for (int i = 2; i \u0026lt;= 100; i++) { boolean isFlag = true; for (int j = 2; j \u0026lt; i; j++) { if (i % j == 0) { isFlag = false; } } if (isFlag == true) { System.out.print(i + \u0026#34;,\u0026#34;); sum++; } } System.out.println(\u0026#34;一共\u0026#34; + sum + \u0026#34;个\u0026#34;); } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/7bfe5d95/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e方法一 \n    \u003cdiv id=\"方法一\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e6%b3%95%e4%b8%80\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eArraysTest\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enum\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 遍历2~100的所有整数\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e100\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 内层循环，遍历2到i\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 如果可以被除了i本身以外的数整除，跳出内层循环\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e%\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"k\"\u003ebreak\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 如果不可以整除，重新开始内层循环\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e%\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"k\"\u003econtinue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 输出质数，并且叠加计数num\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;,\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003enum\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;一共\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enum\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;个\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003e方法二 \n    \u003cdiv id=\"方法二\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%96%b9%e6%b3%95%e4%ba%8c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eArraysTest\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esum\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e100\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eisFlag\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e%\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003eisFlag\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eisFlag\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;,\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003esum\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;一共\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esum\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;个\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"img\"\n    data-zoom-src=\"/posts/add78db0/8aee08f5/7bfe5d95/image/202109181437764.png\"\n    src=\"/posts/add78db0/8aee08f5/7bfe5d95/image/202109181437764.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"100以内的所有质数","type":"posts"},{"content":" 冒泡排序 # 思想： # 1、一个无序的数组，n个元素，一共需要排序n-1轮\n2、在每一轮中，从数组第0位开始，比较相邻两个元素，如果与需求逆序，就交换这两个元素，在每一轮中，可以将当前最大（最小）的元素交换到最后，\n3、直到执行完n-1轮，没有需要比较的元素为止。\n代码实现： # public static void bubSort(int[] a){ for (int i = 0;i \u0026lt; a.length - 1;i++){ for (int j = 0;j \u0026lt; a.length - 1 - i;j++){ if (a[j] \u0026gt; a[j + 1]){ int temp = a[j]; a[j] = a[j + 1]; a[j + 1] = temp; } } } } 快速排序 # 思想： # 代码实现： # 选择排序 # 思想： # 1、一个无序的数组，一共有n个元素，需要排序n-1轮\n2、在每一轮的排序中，需要将当前轮的第0个元素的角标记录下来，然后依次与后面的元素进行比较，如果发现比当前轮第0个元素更小（更大）的元素，就记录角标，当比较完数组最后一个元素，如果最小的元素不是当前轮第0个元素，进行交换。\n3、直到执行完n-1轮，所有元素都按顺序排好\n代码实现： # public static void seletSort(int[] a){ //外层控制比较的轮数 for (int i = 0;i \u0026lt; a.length - 1;i++){ //声明一个变量，用于存储最小元素的角标,起始位轮数的第一个元素的角标 int minNum = i; //内层循环控制每一轮的比较，第0个元素从当前轮第一个元素开始比较 for (int j = i + 1;j \u0026lt; a.length;j++){ minNum = (a[minNum] \u0026lt; a[j])?minNum : j; } //一轮比完以后，将第0位的元素，如果当前轮最小元素不是第0位，就交换位置 if (minNum != i){ int temp = a[minNum]; a[minNum] = a[i]; a[i] = temp; } } } 插入排序 # 思想： # 1、一个无序的数组，n个元素，一共执行n-1轮\n2、在每一轮中，需要将当前有序区边界后一个元素，依次与有序区元素右后向前进行比较，如果是逆序的，就将有序区的该元素向后移一位，直到遇到顺序的有序区元素，就停止比较，进行下一轮，并将该元素插入该有序区元素的后面。\n3、直到执行完n-1轮，将所有元素按照顺序排好\n代码实现： # public static void insertSort(int[] a){ //外层循环控制轮数 for (int i = 1;i \u0026lt; a.length;i++){ //每一轮中进行比较的元素为a[i] //变量j为有序区最后一个元素的角标 int num = a[i]; int j = i - 1; /*内层循环控制每一轮比较的细节 q 要插入的元素一次向前比较，如果小于前面的元素就将比较的元素向后移动 直到数组的第0位 */ while (j \u0026gt;= 0 \u0026amp;\u0026amp; num \u0026lt; a[j]){ a[j + 1] = a[j]; j--; } //如果到了数组的第0位或插入的元素比要比较的元素大的情况下，就在比较的元素后面插入 a[j + 1] = num; } } 排序算法性能对比 # ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/80aacaea/f742d77f/930c988f/","section":"文章","summary":"\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"说明: AElFTkSuQmCC.png\"\n    data-zoom-src=\"/posts/3ab7256e/80aacaea/f742d77f/930c988f/image/202109181145252.gif\"\n    src=\"/posts/3ab7256e/80aacaea/f742d77f/930c988f/image/202109181145252.gif\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"说明: untitle.png\"\n    data-zoom-src=\"/posts/3ab7256e/80aacaea/f742d77f/930c988f/image/202109181145178.gif\"\n    src=\"/posts/3ab7256e/80aacaea/f742d77f/930c988f/image/202109181145178.gif\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"说明: untitle.png\"\n    data-zoom-src=\"/posts/3ab7256e/80aacaea/f742d77f/930c988f/image/202109181145169.gif\"\n    src=\"/posts/3ab7256e/80aacaea/f742d77f/930c988f/image/202109181145169.gif\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003e冒泡排序 \n    \u003cdiv id=\"冒泡排序\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%86%92%e6%b3%a1%e6%8e%92%e5%ba%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e思想： \n    \u003cdiv id=\"思想\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e6%80%9d%e6%83%b3\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003e1、一个无序的数组，n个元素，一共需要排序n-1轮\u003c/p\u003e","title":"4.1、排序算法","type":"posts"},{"content":"class ForTest{ public static void main(String[] args){ int a,b = 1; while (b \u0026lt; 10){ for (a = 1;a \u0026lt;= b;a++){ System.out.print(a + \u0026#34;x\u0026#34; + b + \u0026#34;=\u0026#34; + (a * b) + \u0026#34; \u0026#34;); } System.out.print(\u0026#34;\\n\u0026#34;); b++; } } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/d28b20a4/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eForTest\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ewhile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;x\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;=\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34; \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\\n\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"img\"\n    data-zoom-src=\"/posts/add78db0/8aee08f5/d28b20a4/image/202109181437144.png\"\n    src=\"/posts/add78db0/8aee08f5/d28b20a4/image/202109181437144.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"99乘法表","type":"posts"},{"content":"\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;meta http-equiv=\u0026#34;X-UA-Compatible\u0026#34; content=\u0026#34;IE=edge\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;viewport\u0026#34; content=\u0026#34;width=device-width, initial-scale=1.0\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Document\u0026lt;/title\u0026gt; \u0026lt;script src=\u0026#34;http://libs.baidu.com/jquery/2.0.0/jquery.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;button id=\u0026#34;b\u0026#34;\u0026gt;donwload\u0026lt;/button\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; // 处理Blob数据下载 function executeDownload(data, fileName) { if (!data) { return } let url = window.URL.createObjectURL(new Blob([data])); let link = document.createElement(\u0026#39;a\u0026#39;); link.style.display = \u0026#39;none\u0026#39;; link.href = url; link.setAttribute(\u0026#39;download\u0026#39;, fileName); document.body.appendChild(link); link.click(); document.body.removeChild(link); alert(\u0026#34;download success\u0026#34;) } let url = \u0026#34;http://example\u0026#34; let params = {} // 发送请求，接收文件数据并解析文件名 $(\u0026#34;#b\u0026#34;).click(function(){ axios.post( url, params, {responseType: \u0026#39;blob\u0026#39;} ) .then(res =\u0026gt; { let reader = new FileReader(); let data = res.data; reader.onload = e =\u0026gt; { if (e.target.result.indexOf(\u0026#39;Result\u0026#39;) != -1 \u0026amp;\u0026amp; JSON.parse(e.target.result).Result == false) { // 进行错误处理 } else { // 获取响应，前后端分离需要后端主动暴露响应头 // resp.setHeader(\u0026#34;Access-Control-Expose-Headers\u0026#34;, \u0026#34;Content-Disposition\u0026#34;); let contentDisposition = res.headers[\u0026#39;content-disposition\u0026#39;]; if (contentDisposition) { fileName = window.decodeURI(res.headers[\u0026#39;content-disposition\u0026#39;].split(\u0026#39;=\u0026#39;)[1], \u0026#34;UTF-8\u0026#34;); } executeDownload(data, fileName); } }; reader.readAsText(data); }) }) ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/83b3b1a8/6326022d/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-html\" data-lang=\"html\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e\u0026lt;!DOCTYPE html\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ehtml\u003c/span\u003e \u003cspan class=\"na\"\u003elang\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;en\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ehead\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003emeta\u003c/span\u003e \u003cspan class=\"na\"\u003echarset\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;UTF-8\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003emeta\u003c/span\u003e \u003cspan class=\"na\"\u003ehttp-equiv\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;X-UA-Compatible\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003econtent\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;IE=edge\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003emeta\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;viewport\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003econtent\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;width=device-width, initial-scale=1.0\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003eDocument\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003escript\u003c/span\u003e \u003cspan class=\"na\"\u003esrc\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;http://libs.baidu.com/jquery/2.0.0/jquery.min.js\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003escript\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003escript\u003c/span\u003e \u003cspan class=\"na\"\u003esrc\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003escript\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ehead\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebutton\u003c/span\u003e \u003cspan class=\"na\"\u003eid\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;b\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003edonwload\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ebutton\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ehtml\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 处理Blob数据下载\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e \u003cspan class=\"nx\"\u003eexecuteDownload\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003efileName\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"nx\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nx\"\u003eurl\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003ewindow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecreateObjectURL\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nx\"\u003eBlob\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"nx\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e]));\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nx\"\u003elink\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003edocument\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecreateElement\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;a\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003elink\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003estyle\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003edisplay\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;none\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003elink\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ehref\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003elink\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003esetAttribute\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;download\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003efileName\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003edocument\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eappendChild\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003elink\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003elink\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eclick\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003edocument\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eremoveChild\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003elink\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ealert\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;download success\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nx\"\u003eurl\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;http://example\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nx\"\u003eparams\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 发送请求，接收文件数据并解析文件名\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003e$\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;#b\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nx\"\u003eclick\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e\u003cspan class=\"p\"\u003e(){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eaxios\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003epost\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003eparams\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003eresponseType\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;blob\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ethen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eres\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nx\"\u003ereader\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nx\"\u003eFileReader\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nx\"\u003edata\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003ereader\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eonload\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003ee\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003etarget\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eindexOf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;Result\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"nx\"\u003eJSON\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eparse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003etarget\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nx\"\u003eResult\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"c1\"\u003e// 进行错误处理\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"c1\"\u003e// 获取响应，前后端分离需要后端主动暴露响应头\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e                \u003cspan class=\"c1\"\u003e// resp.setHeader(\u0026#34;Access-Control-Expose-Headers\u0026#34;, \u0026#34;Content-Disposition\u0026#34;);\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e                \u003cspan class=\"kd\"\u003elet\u003c/span\u003e \u003cspan class=\"nx\"\u003econtentDisposition\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eheaders\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;content-disposition\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003econtentDisposition\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"nx\"\u003efileName\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003ewindow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nb\"\u003edecodeURI\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eres\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eheaders\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;content-disposition\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e].\u003c/span\u003e\u003cspan class=\"nx\"\u003esplit\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;=\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;UTF-8\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nx\"\u003eexecuteDownload\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003efileName\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e};\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003ereader\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ereadAsText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"axios实现post文件下载","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/d1774b5b/","section":"文章","summary":"","title":"ElasticSearch","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/elasticsearch/","section":"Series","summary":"","title":"ElasticSearch","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/elasticsearch/","section":"标签","summary":"","title":"ElasticSearch","type":"tags"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/3910e7bf/","section":"文章","summary":"","title":"GoLang代码","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/golang%E4%BB%A3%E7%A0%81/","section":"Series","summary":"","title":"GoLang代码","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/golang%E4%BB%A3%E7%A0%81/","section":"标签","summary":"","title":"GoLang代码","type":"tags"},{"content":"//使用IO流实现文件复制 public static void copy(String by,String to) throws IOException { File fileby = new File(by); File fileto = new File(to); BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(fileby)); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(fileto)); byte[] bytes = new byte[1024]; int i; while ((i = bufferedInputStream.read(bytes)) != -1){ bufferedOutputStream.write(bytes,0,i); } bufferedInputStream.close(); bufferedOutputStream.flush(); bufferedOutputStream.close(); } /** * 复制文件夹(使用缓冲字节流) * @param sourcePath 源文件夹路径 * @param targetPath 目标文件夹路径 */ public static void copyFolder(String sourcePath,String targetPath) throws Exception{ //源文件夹路径 File sourceFile = new File(sourcePath); //目标文件夹路径 File targetFile = new File(targetPath); if(!sourceFile.exists()){ throw new Exception(\u0026#34;文件夹不存在\u0026#34;); } if(!sourceFile.isDirectory()){ throw new Exception(\u0026#34;源文件夹不是目录\u0026#34;); } if(!targetFile.exists()){ targetFile.mkdirs(); } if(!targetFile.isDirectory()){ throw new Exception(\u0026#34;目标文件夹不是目录\u0026#34;); } File[] files = sourceFile.listFiles(); if(files == null || files.length == 0){ return; } for(File file : files){ //文件要移动的路径 String movePath = targetFile+File.separator+file.getName(); if(file.isDirectory()){ //如果是目录则递归调用 copyFolder(file.getAbsolutePath(),movePath); }else { //如果是文件则复制文件 BufferedInputStream in = new BufferedInputStream(new FileInputStream(file)); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(movePath)); byte[] b = new byte[1024]; int temp = 0; while((temp = in.read(b)) != -1){ out.write(b,0,temp); } out.close(); in.close(); } } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/d342b76e/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//使用IO流实现文件复制\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003ecopy\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eby\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eto\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003ethrows\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eIOException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efileby\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eby\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efileto\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eto\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedInputStream\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebufferedInputStream\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedInputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFileInputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efileby\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedOutputStream\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebufferedOutputStream\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedOutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFileOutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efileto\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kt\"\u003ebyte\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebytes\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003ebyte\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1024\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003ewhile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebufferedInputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eread\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebytes\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003ebufferedOutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ewrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebytes\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003ebufferedInputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003ebufferedOutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eflush\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003ebufferedOutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e  * 复制文件夹(使用缓冲字节流)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e  * @param sourcePath 源文件夹路径\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e  * @param targetPath 目标文件夹路径\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e  */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003ecopyFolder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esourcePath\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etargetPath\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003ethrows\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eException\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//源文件夹路径\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esourceFile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esourcePath\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//目标文件夹路径\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etargetFile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etargetPath\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"n\"\u003esourceFile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eexists\u003c/span\u003e\u003cspan class=\"p\"\u003e()){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ethrow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eException\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;文件夹不存在\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"n\"\u003esourceFile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eisDirectory\u003c/span\u003e\u003cspan class=\"p\"\u003e()){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ethrow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eException\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;源文件夹不是目录\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"n\"\u003etargetFile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eexists\u003c/span\u003e\u003cspan class=\"p\"\u003e()){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003etargetFile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003emkdirs\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"n\"\u003etargetFile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eisDirectory\u003c/span\u003e\u003cspan class=\"p\"\u003e()){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ethrow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eException\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;目标文件夹不是目录\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efiles\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esourceFile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elistFiles\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efiles\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efiles\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efiles\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//文件要移动的路径\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emovePath\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etargetFile\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eseparator\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetName\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eisDirectory\u003c/span\u003e\u003cspan class=\"p\"\u003e()){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e//如果是目录则递归调用\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003ecopyFolder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetAbsolutePath\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e\u003cspan class=\"n\"\u003emovePath\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e//如果是文件则复制文件\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedInputStream\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ein\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedInputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFileInputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedOutputStream\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedOutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFileOutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emovePath\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"kt\"\u003ebyte\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003ebyte\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1024\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etemp\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003ewhile\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"n\"\u003etemp\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ein\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eread\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ewrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003etemp\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003ein\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"IO复制文件","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/d9073060/","section":"文章","summary":"","title":"JavaFX","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/javafx/","section":"Series","summary":"","title":"JavaFX","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/javafx/","section":"标签","summary":"","title":"JavaFX","type":"tags"},{"content":" JSP: # \u0026lt;tr\u0026gt; \u0026lt;td colspan=\u0026#34;9\u0026#34;\u0026gt;共${pageInfo.pages}页，当前第${pageInfo.pageNum}页 \u0026lt;div style=\u0026#34;float: right\u0026#34;\u0026gt; \u0026lt;button ${pageInfo.pageNum == 1 ? \u0026#34;disabled = \u0026#39;disabled\u0026#39;\u0026#34;:\u0026#34;\u0026#34;} onclick=\u0026#34;pageGo(${pageInfo.pageNum - 1})\u0026#34;\u0026gt; 上一页 \u0026lt;/button\u0026gt; \u0026lt;c:forEach begin=\u0026#34;${pageInfo.pageNum \u0026lt; 3? 1 : pageInfo.pageNum - 2}\u0026#34; end=\u0026#34;${pageInfo.pageNum \u0026lt; 3? 1 + 4 : pageInfo.pageNum - 2 + 4}\u0026#34; var=\u0026#34;num\u0026#34;\u0026gt; \u0026lt;c:choose\u0026gt; \u0026lt;%-- 当按钮超过总页数，不可点击 --%\u0026gt; \u0026lt;c:when test=\u0026#34;${num \u0026gt; pageInfo.pages}\u0026#34;\u0026gt; \u0026lt;button disabled=\u0026#34;disabled\u0026#34;\u0026gt;${num}\u0026lt;/button\u0026gt; \u0026lt;/c:when\u0026gt; \u0026lt;%-- 当按钮等于当前页数，不可点击，更改样式 --%\u0026gt; \u0026lt;c:when test=\u0026#34;${num == pageInfo.pageNum}\u0026#34;\u0026gt; \u0026lt;button onclick=\u0026#34;pageGo(${num})\u0026#34; disabled=\u0026#34;disabled\u0026#34;\u0026gt;${num}\u0026lt;/button\u0026gt; \u0026lt;/c:when\u0026gt; \u0026lt;%-- 其他情况正常点击 --%\u0026gt; \u0026lt;c:otherwise\u0026gt; \u0026lt;button onclick=\u0026#34;pageGo(${num})\u0026#34;\u0026gt;${num}\u0026lt;/button\u0026gt; \u0026lt;/c:otherwise\u0026gt; \u0026lt;/c:choose\u0026gt; \u0026lt;/c:forEach\u0026gt; \u0026lt;button ${pageInfo.pageNum == pageInfo.pages ? \u0026#34;disabled = \u0026#39;disabled\u0026#39;\u0026#34;:\u0026#34;\u0026#34;} onclick=\u0026#34;pageGo(${pageInfo.pageNum + 1})\u0026#34;\u0026gt; 下一页 \u0026lt;/button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; js: # function pageGo(pageGo){ location.href=\u0026#34;findAll.do?pageGo=\u0026#34;+pageGo; } java： # 如果是SpringMVC，需要在spring配置文件中添加插件\n\u0026lt;!-- 配置SessionFactory --\u0026gt; \u0026lt;bean id=\u0026#34;sqlSessionFactory\u0026#34; class=\u0026#34;org.mybatis.spring.SqlSessionFactoryBean\u0026#34;\u0026gt; \u0026lt;!-- 数据库连接池 --\u0026gt; \u0026lt;property name=\u0026#34;dataSource\u0026#34; ref=\u0026#34;dataSource\u0026#34; /\u0026gt; \u0026lt;!-- 加载mybatis的全局配置文件 --\u0026gt; \u0026lt;property name=\u0026#34;configLocation\u0026#34; value=\u0026#34;classpath:mybatisConfig.xml\u0026#34; /\u0026gt; \u0026lt;property name=\u0026#34;plugins\u0026#34;\u0026gt; \u0026lt;array\u0026gt; \u0026lt;bean class=\u0026#34;com.github.pagehelper.PageInterceptor\u0026#34;\u0026gt; \u0026lt;property name=\u0026#34;properties\u0026#34;\u0026gt; \u0026lt;props\u0026gt; \u0026lt;prop key=\u0026#34;helperDialect\u0026#34;\u0026gt;mysql\u0026lt;/prop\u0026gt; \u0026lt;prop key=\u0026#34;reasonable\u0026#34;\u0026gt;true\u0026lt;/prop\u0026gt; \u0026lt;/props\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;/array\u0026gt; \u0026lt;/property\u0026gt; \u0026lt;/bean\u0026gt; @RequestMapping(\u0026#34;/findAll.do\u0026#34;) public String findAll(ModelMap modelMap,Integer pageGo){ PageInfo\u0026lt;User\u0026gt; pageInfo = null; System.out.println(pageGo); if(pageGo == null){ pageInfo = userService.findAllByPage(1, 5); }else{ pageInfo = userService.findAllByPage(pageGo, 5); } modelMap.put(\u0026#34;pageInfo\u0026#34;, pageInfo); System.out.println(pageInfo); return \u0026#34;jsp/main.jsp\u0026#34;; } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/5e8555b4/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eJSP: \n    \u003cdiv id=\"jsp\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#jsp\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode class=\"language-jsp\" data-lang=\"jsp\"\u003e\u0026lt;tr\u0026gt;\n    \u0026lt;td colspan=\u0026#34;9\u0026#34;\u0026gt;共${pageInfo.pages}页，当前第${pageInfo.pageNum}页\n        \u0026lt;div style=\u0026#34;float: right\u0026#34;\u0026gt;\n            \u0026lt;button \n                    ${pageInfo.pageNum == 1 ? \u0026#34;disabled = \u0026#39;disabled\u0026#39;\u0026#34;:\u0026#34;\u0026#34;}\n                    onclick=\u0026#34;pageGo(${pageInfo.pageNum - 1})\u0026#34;\u0026gt;\n                上一页\n            \u0026lt;/button\u0026gt;\n            \u0026lt;c:forEach\n                       begin=\u0026#34;${pageInfo.pageNum \u0026lt; 3? 1 : pageInfo.pageNum - 2}\u0026#34;\n                       end=\u0026#34;${pageInfo.pageNum \u0026lt; 3? 1 + 4 : pageInfo.pageNum - 2 + 4}\u0026#34;\n                       var=\u0026#34;num\u0026#34;\u0026gt;\n                \u0026lt;c:choose\u0026gt;\n                    \u0026lt;%--   当按钮超过总页数，不可点击  --%\u0026gt;\n                    \u0026lt;c:when test=\u0026#34;${num \u0026gt; pageInfo.pages}\u0026#34;\u0026gt;\n                        \u0026lt;button disabled=\u0026#34;disabled\u0026#34;\u0026gt;${num}\u0026lt;/button\u0026gt;\n                    \u0026lt;/c:when\u0026gt;\n                    \u0026lt;%--   当按钮等于当前页数，不可点击，更改样式  --%\u0026gt;\n                    \u0026lt;c:when test=\u0026#34;${num == pageInfo.pageNum}\u0026#34;\u0026gt;\n                        \u0026lt;button onclick=\u0026#34;pageGo(${num})\u0026#34; disabled=\u0026#34;disabled\u0026#34;\u0026gt;${num}\u0026lt;/button\u0026gt;\n                    \u0026lt;/c:when\u0026gt;\n                    \u0026lt;%--   其他情况正常点击  --%\u0026gt;\n                    \u0026lt;c:otherwise\u0026gt;\n                        \u0026lt;button onclick=\u0026#34;pageGo(${num})\u0026#34;\u0026gt;${num}\u0026lt;/button\u0026gt;\n                    \u0026lt;/c:otherwise\u0026gt;\n                \u0026lt;/c:choose\u0026gt;\n            \u0026lt;/c:forEach\u0026gt;\n            \u0026lt;button \n                    ${pageInfo.pageNum == pageInfo.pages ? \u0026#34;disabled = \u0026#39;disabled\u0026#39;\u0026#34;:\u0026#34;\u0026#34;}\n                    onclick=\u0026#34;pageGo(${pageInfo.pageNum + 1})\u0026#34;\u0026gt;\n                下一页\n            \u0026lt;/button\u0026gt;\n        \u0026lt;/div\u0026gt;\n    \u0026lt;/td\u0026gt;\n\u0026lt;/tr\u0026gt;\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch2 class=\"relative group\"\u003ejs: \n    \u003cdiv id=\"js\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#js\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e \u003cspan class=\"nx\"\u003epageGo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003epageGo\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003elocation\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ehref\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;findAll.do?pageGo=\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003epageGo\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003ejava： \n    \u003cdiv id=\"java\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#java\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e如果是SpringMVC，需要在spring配置文件中添加插件\u003c/p\u003e","title":"jsp实现分页功能","type":"posts"},{"content":" js字符串转换成Date # 输入的时间格式为yyyy-MM-dd # function convertDateFromString(dateString) { var date = new Date(dateString.replace(/-/,\u0026#34;/\u0026#34;)) return date; } 输入的时间格式为yyyy-MM-dd hh:mm:ss # function convertDateFromString(dateString) { var arr1 = dateString.split(\u0026#34; \u0026#34;); var sdate = arr1[0].split(\u0026#39;-\u0026#39;); var date = new Date(sdate[0], sdate[1]-1, sdate[2]); return date; } Date转字符串 # (new Date()).Format(\u0026#34;yyyy-MM-dd hh:mm:ss.S\u0026#34;) ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/83b3b1a8/d9733bcd/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003ejs字符串转换成Date \n    \u003cdiv id=\"js字符串转换成date\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#js%e5%ad%97%e7%ac%a6%e4%b8%b2%e8%bd%ac%e6%8d%a2%e6%88%90date\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003e输入的时间格式为yyyy-MM-dd \n    \u003cdiv id=\"输入的时间格式为yyyy-mm-dd\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%be%93%e5%85%a5%e7%9a%84%e6%97%b6%e9%97%b4%e6%a0%bc%e5%bc%8f%e4%b8%bayyyy-mm-dd\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e \u003cspan class=\"nx\"\u003econvertDateFromString\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003edateString\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003edate\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nb\"\u003eDate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003edateString\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ereplace\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sr\"\u003e/-/\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003edate\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch3 class=\"relative group\"\u003e输入的时间格式为yyyy-MM-dd hh:mm:ss \n    \u003cdiv id=\"输入的时间格式为yyyy-mm-dd-hhmmss\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e8%be%93%e5%85%a5%e7%9a%84%e6%97%b6%e9%97%b4%e6%a0%bc%e5%bc%8f%e4%b8%bayyyy-mm-dd-hhmmss\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e \u003cspan class=\"nx\"\u003econvertDateFromString\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003edateString\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003earr1\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003edateString\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003esplit\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34; \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003esdate\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003earr1\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e].\u003c/span\u003e\u003cspan class=\"nx\"\u003esplit\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;-\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003edate\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nb\"\u003eDate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003esdate\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"nx\"\u003esdate\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003esdate\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e]);\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003edate\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003ch2 class=\"relative group\"\u003eDate转字符串 \n    \u003cdiv id=\"date转字符串\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#date%e8%bd%ac%e5%ad%97%e7%ac%a6%e4%b8%b2\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nb\"\u003eDate\u003c/span\u003e\u003cspan class=\"p\"\u003e()).\u003c/span\u003e\u003cspan class=\"nx\"\u003eFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;yyyy-MM-dd hh:mm:ss.S\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"js中string和date互转","type":"posts"},{"content":"//variable为地址栏中字段名 function getQueryVariable(variable){ var query = window.location.search.substring(1); var vars = query.split(\u0026#34;\u0026amp;\u0026#34;); for (var i=0;i\u0026lt;vars.length;i++) { var pair = vars[i].split(\u0026#34;=\u0026#34;); if(pair[0] == variable){return pair[1];} } return(false); } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/83b3b1a8/8a0ffbbf/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//variable为地址栏中字段名\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e \u003cspan class=\"nx\"\u003egetQueryVariable\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003evariable\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003equery\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003ewindow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elocation\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003esearch\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003esubstring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003evars\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003equery\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003esplit\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u0026amp;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"nx\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nx\"\u003evars\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"nx\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003epair\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003evars\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e].\u003c/span\u003e\u003cspan class=\"nx\"\u003esplit\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;=\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003epair\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"nx\"\u003evariable\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003epair\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e];}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"js获取地址栏参数","type":"posts"},{"content":"SELECT CONCAT(SUBSTRING(\u0026#39;赵钱孙李周吴郑王冯陈诸卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章云苏潘葛奚范彭郎鲁韦昌马苗凤花方俞任袁柳酆鲍史唐费廉岑薛雷贺倪汤滕殷罗毕郝邬安常乐于时傅皮齐康伍余元卜顾孟平黄和穆萧尹姚邵堪汪祁毛禹狄米贝明臧计伏成戴谈宋茅庞熊纪舒屈项祝董粱杜阮蓝闵席季麻强贾路娄危江童颜郭梅盛林刁钟徐邱骆高夏蔡田樊胡凌霍虞万支柯咎管卢莫经房裘干解应宗丁宣贲邓郁单杭洪包诸左石崔吉钮龚\u0026#39;,FLOOR(1+190*RAND()),1),SUBSTRING(\u0026#39;明国华建文平志伟东海强晓生光林小民永杰军金健一忠洪江福祥中正振勇耀春大宁亮宇兴宝少剑云学仁涛瑞飞鹏安亚泽世汉达卫利胜敏群波成荣新峰刚家龙德庆斌辉良玉俊立浩天宏子松克清长嘉红山贤阳乐锋智青跃元武广思雄锦威启昌铭维义宗英凯鸿森超坚旭政传康继翔栋仲权奇礼楠炜友年震鑫雷兵万星骏伦绍麟雨行才希彦兆贵源有景升惠臣慧开章润高佳虎根远力进泉茂毅富博霖顺信凡豪树和恩向道川彬柏磊敬书鸣芳培全炳基冠晖京欣廷哲保秋君劲轩帆若连勋祖锡吉崇钧田石奕发洲彪钢运伯满庭申湘皓承梓雪孟其潮冰怀鲁裕翰征谦航士尧标洁城寿枫革纯风化逸腾岳银鹤琳显焕来心凤睿勤延凌昊西羽百捷定琦圣佩麒虹如靖日咏会久昕黎桂玮燕可越彤雁孝宪萌颖艺夏桐月瑜沛诚夫声冬奎扬双坤镇楚水铁喜之迪泰方同滨邦先聪朝善非恒晋汝丹为晨乃秀岩辰洋然厚灿卓杨钰兰怡灵淇美琪亦晶舒菁真涵爽雅爱依静棋宜男蔚芝菲露娜珊雯淑曼萍珠诗璇琴素梅玲蕾艳紫珍丽仪梦倩伊茜妍碧芬儿岚婷菊妮媛莲娟一\u0026#39;,FLOOR(1+400*RAND()),1),SUBSTRING(\u0026#39;明国华建文平志伟东海强晓生光林小民永杰军金健一忠洪江福祥中正振勇耀春大宁亮宇兴宝少剑云学仁涛瑞飞鹏安亚泽世汉达卫利胜敏群波成荣新峰刚家龙德庆斌辉良玉俊立浩天宏子松克清长嘉红山贤阳乐锋智青跃元武广思雄锦威启昌铭维义宗英凯鸿森超坚旭政传康继翔栋仲权奇礼楠炜友年震鑫雷兵万星骏伦绍麟雨行才希彦兆贵源有景升惠臣慧开章润高佳虎根远力进泉茂毅富博霖顺信凡豪树和恩向道川彬柏磊敬书鸣芳培全炳基冠晖京欣廷哲保秋君劲轩帆若连勋祖锡吉崇钧田石奕发洲彪钢运伯满庭申湘皓承梓雪孟其潮冰怀鲁裕翰征谦航士尧标洁城寿枫革纯风化逸腾岳银鹤琳显焕来心凤睿勤延凌昊西羽百捷定琦圣佩麒虹如靖日咏会久昕黎桂玮燕可越彤雁孝宪萌颖艺夏桐月瑜沛诚夫声冬奎扬双坤镇楚水铁喜之迪泰方同滨邦先聪朝善非恒晋汝丹为晨乃秀岩辰洋然厚灿卓杨钰兰怡灵淇美琪亦晶舒菁真涵爽雅爱依静棋宜男蔚芝菲露娜珊雯淑曼萍珠诗璇琴素梅玲蕾艳紫珍丽仪梦倩伊茜妍碧芬儿岚婷菊妮媛莲娟一\u0026#39;,FLOOR(1+400*RAND()),1)) ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/0781e2fa/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eSELECT\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"n\"\u003eCONCAT\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003eSUBSTRING\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;赵钱孙李周吴郑王冯陈诸卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章云苏潘葛奚范彭郎鲁韦昌马苗凤花方俞任袁柳酆鲍史唐费廉岑薛雷贺倪汤滕殷罗毕郝邬安常乐于时傅皮齐康伍余元卜顾孟平黄和穆萧尹姚邵堪汪祁毛禹狄米贝明臧计伏成戴谈宋茅庞熊纪舒屈项祝董粱杜阮蓝闵席季麻强贾路娄危江童颜郭梅盛林刁钟徐邱骆高夏蔡田樊胡凌霍虞万支柯咎管卢莫经房裘干解应宗丁宣贲邓郁单杭洪包诸左石崔吉钮龚\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eFLOOR\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"mi\"\u003e190\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003eRAND\u003c/span\u003e\u003cspan class=\"p\"\u003e()),\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\u003cspan class=\"k\"\u003eSUBSTRING\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;明国华建文平志伟东海强晓生光林小民永杰军金健一忠洪江福祥中正振勇耀春大宁亮宇兴宝少剑云学仁涛瑞飞鹏安亚泽世汉达卫利胜敏群波成荣新峰刚家龙德庆斌辉良玉俊立浩天宏子松克清长嘉红山贤阳乐锋智青跃元武广思雄锦威启昌铭维义宗英凯鸿森超坚旭政传康继翔栋仲权奇礼楠炜友年震鑫雷兵万星骏伦绍麟雨行才希彦兆贵源有景升惠臣慧开章润高佳虎根远力进泉茂毅富博霖顺信凡豪树和恩向道川彬柏磊敬书鸣芳培全炳基冠晖京欣廷哲保秋君劲轩帆若连勋祖锡吉崇钧田石奕发洲彪钢运伯满庭申湘皓承梓雪孟其潮冰怀鲁裕翰征谦航士尧标洁城寿枫革纯风化逸腾岳银鹤琳显焕来心凤睿勤延凌昊西羽百捷定琦圣佩麒虹如靖日咏会久昕黎桂玮燕可越彤雁孝宪萌颖艺夏桐月瑜沛诚夫声冬奎扬双坤镇楚水铁喜之迪泰方同滨邦先聪朝善非恒晋汝丹为晨乃秀岩辰洋然厚灿卓杨钰兰怡灵淇美琪亦晶舒菁真涵爽雅爱依静棋宜男蔚芝菲露娜珊雯淑曼萍珠诗璇琴素梅玲蕾艳紫珍丽仪梦倩伊茜妍碧芬儿岚婷菊妮媛莲娟一\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eFLOOR\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"mi\"\u003e400\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003eRAND\u003c/span\u003e\u003cspan class=\"p\"\u003e()),\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\u003cspan class=\"k\"\u003eSUBSTRING\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;明国华建文平志伟东海强晓生光林小民永杰军金健一忠洪江福祥中正振勇耀春大宁亮宇兴宝少剑云学仁涛瑞飞鹏安亚泽世汉达卫利胜敏群波成荣新峰刚家龙德庆斌辉良玉俊立浩天宏子松克清长嘉红山贤阳乐锋智青跃元武广思雄锦威启昌铭维义宗英凯鸿森超坚旭政传康继翔栋仲权奇礼楠炜友年震鑫雷兵万星骏伦绍麟雨行才希彦兆贵源有景升惠臣慧开章润高佳虎根远力进泉茂毅富博霖顺信凡豪树和恩向道川彬柏磊敬书鸣芳培全炳基冠晖京欣廷哲保秋君劲轩帆若连勋祖锡吉崇钧田石奕发洲彪钢运伯满庭申湘皓承梓雪孟其潮冰怀鲁裕翰征谦航士尧标洁城寿枫革纯风化逸腾岳银鹤琳显焕来心凤睿勤延凌昊西羽百捷定琦圣佩麒虹如靖日咏会久昕黎桂玮燕可越彤雁孝宪萌颖艺夏桐月瑜沛诚夫声冬奎扬双坤镇楚水铁喜之迪泰方同滨邦先聪朝善非恒晋汝丹为晨乃秀岩辰洋然厚灿卓杨钰兰怡灵淇美琪亦晶舒菁真涵爽雅爱依静棋宜男蔚芝菲露娜珊雯淑曼萍珠诗璇琴素梅玲蕾艳紫珍丽仪梦倩伊茜妍碧芬儿岚婷菊妮媛莲娟一\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eFLOOR\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"mi\"\u003e400\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003eRAND\u003c/span\u003e\u003cspan class=\"p\"\u003e()),\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"MySQL生成随机姓名","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/d8245fe1/83556763/","section":"文章","summary":"","title":"QT","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/qt/","section":"Series","summary":"","title":"QT","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/qt/","section":"标签","summary":"","title":"QT","type":"tags"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/675117b0/e2f9d0f6/","section":"文章","summary":"","title":"RabbitMQ","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/rabbitmq/","section":"Series","summary":"","title":"RabbitMQ","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/rabbitmq/","section":"标签","summary":"","title":"RabbitMQ","type":"tags"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/3f5635d6/fd17f7dc/","section":"文章","summary":"","title":"SpringCloud","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/springcloud/","section":"Series","summary":"","title":"SpringCloud","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/springcloud/","section":"标签","summary":"","title":"SpringCloud","type":"tags"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/d8245fe1/bf736d53/","section":"文章","summary":"","title":"STL","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/stl/","section":"Series","summary":"","title":"STL","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/stl/","section":"标签","summary":"","title":"STL","type":"tags"},{"content":" 1、项目导入依赖 # 下载地址：https://codechina.csdn.net/mirrors/rainabba/jquery-table2excel\njquery.table2excel.min.js\n2、下载事件，可以绑定给按钮，或是其他 # $(\u0026#34;button\u0026#34;).click(function(){ //table的id $(\u0026#34;#infoTable\u0026#34;).table2excel({ exclude: \u0026#34;.noExl\u0026#34;,//table里面，名为.noExl的class不包含生成excel范围内 name: \u0026#34;Excel Document Name\u0026#34;, filename: \u0026#34;fileName\u0026#34;,//生成的excel文件名称 exclude_img: true,//把img标签过滤掉 exclude_links: true,//把link标签过滤掉 exclude_inputs: true//把input标签过滤掉 }); }) ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/83b3b1a8/1922b1ea/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003e1、项目导入依赖 \n    \u003cdiv id=\"1项目导入依赖\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1%e9%a1%b9%e7%9b%ae%e5%af%bc%e5%85%a5%e4%be%9d%e8%b5%96\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e下载地址：https://codechina.csdn.net/mirrors/rainabba/jquery-table2excel\u003c/p\u003e","title":"table2excel","type":"posts"},{"content":" Tomcat的缺省端口是多少，怎么修改 # 缺省端口是8080\n修改tomcat目录下conf目录中的server.xml文件，打开这个xml文件，在文件中找到\n\u0026lt;Connector connectionTimeout=\u0026#34;20000\u0026#34; port=\u0026#34;8080\u0026#34; protocol=\u0026#34;HTTP/1.1\u0026#34; redirectPort=\u0026#34;8443\u0026#34; uriEncoding=\u0026#34;utf-8\u0026#34;/\u0026gt; 将里面的port参数修改成你想要的端口就可以了\nTomcat有几种部署方式 # ①直接将Web项目，一般是war包，放在webapps下，Tomcat会自动将其部署\n②在server.xml文件上配置\u0026lt;Context\u0026gt;节点，设置相关的属性就可以了\n\u0026lt;Context Path=\u0026#34;/jstore\u0026#34;Docbase=\u0026#34;C:\\work\\jstore\\WebContent\u0026#34; Debug=\u0026#34;0\u0026#34; Privileged=\u0026#34;True\u0026#34; Reloadable=\u0026#34;True\u0026#34;/\u0026gt; ③通过Catalina进行配置，进入到conf\\Catalina\\localhost\\文件下，创建一个xml文件，改文件的名字就是站点的名字\n\u0026lt;Context docBase=\u0026#34;C:\\work\\jstore\\web\u0026#34; path=\u0026#34;/jstore\u0026#34; reloadable=\u0026#34;true\u0026#34;/\u0026gt; Tomcat如何创建servlet实例，用到了什么原理 # 当容器启动的时候，会读取在webapps目录下的所有web应用中的web.xml文件，然后对xml文件进行解析，并读取servlet注册信息，然后将每个应用中注册的servlet类都进行加载，然后通过反射的方式进行实例化\n如果在servlet进行注册的时候，添加了\u0026lt;load-on-startup\u0026gt;标签，标签中只可以使用整数，如果是正数，会在容器启动时进行实例化，如果是负数，那么会在第一次请求时进行实例化；同时这个值越小，越早被加载\nTomcat的工作模式 # Tomcat是一个JSP/Servlet容器。其作为Servlet容器，有三种工作模式：独立的Servlet容器、进程内的Servlet容器和进程外的Servlet容器\n进入Tomcat的请求可以根据Tomcat的工作模式分为两类：\n①Tomcat作为应用程序服务器：请求来自于前端的Web服务器，这可能是Apache，IIS，Nginx等\n②Tomcat作为独立服务器：请求来自于Web浏览器\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/153a4d49/4c2d5465/","section":"文章","summary":"\u003ch4 class=\"relative group\"\u003eTomcat的缺省端口是多少，怎么修改 \n    \u003cdiv id=\"tomcat的缺省端口是多少怎么修改\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#tomcat%e7%9a%84%e7%bc%ba%e7%9c%81%e7%ab%af%e5%8f%a3%e6%98%af%e5%a4%9a%e5%b0%91%e6%80%8e%e4%b9%88%e4%bf%ae%e6%94%b9\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cp\u003e缺省端口是8080\u003c/p\u003e","title":"Tomcat","type":"posts"},{"content":"\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;script src=\u0026#34;../js/vue.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div id=\u0026#34;app\u0026#34;\u0026gt; \u0026lt;p\u0026gt; 全选： \u0026lt;/p\u0026gt; \u0026lt;input type=\u0026#34;checkbox\u0026#34; id=\u0026#34;checkbox\u0026#34; v-model=\u0026#34;checked\u0026#34; @change=\u0026#34;changeAllChecked()\u0026#34;\u0026gt;{{checked}} \u0026lt;p\u0026gt; 多个复选框： \u0026lt;/p\u0026gt; \u0026lt;input type=\u0026#34;checkbox\u0026#34; id=\u0026#34;runoob\u0026#34; value=\u0026#34;Runoob\u0026#34; v-model=\u0026#34;checkedNames\u0026#34;\u0026gt;Runoob \u0026lt;input type=\u0026#34;checkbox\u0026#34; id=\u0026#34;google\u0026#34; value=\u0026#34;Google\u0026#34; v-model=\u0026#34;checkedNames\u0026#34;\u0026gt;Google \u0026lt;input type=\u0026#34;checkbox\u0026#34; id=\u0026#34;taobao\u0026#34; value=\u0026#34;Taobao\u0026#34; v-model=\u0026#34;checkedNames\u0026#34;\u0026gt;taobao\u0026lt;br\u0026gt; 选择的值为:{{checkedNames}} \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: \u0026#39;#app\u0026#39;, data: { checked: false, checkedNames: [], checkedArr: [\u0026#34;Runoob\u0026#34;, \u0026#34;Taobao\u0026#34;, \u0026#34;Google\u0026#34;] }, methods: { changeAllChecked: function() { if (this.checked) { checkedNames = []; this.checkedNames = this.checkedArr; } else { this.checkedNames = []; } } }, watch: { \u0026#34;checkedNames\u0026#34;: function() { if (this.checkedNames.length == this.checkedArr.length) { this.checked = true; } else { this.checked = false; } } } }) \u0026lt;/script\u0026gt; \u0026lt;/html\u0026gt; ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/83b3b1a8/17c5a606/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-html\" data-lang=\"html\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e\u0026lt;!DOCTYPE html\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ehtml\u003c/span\u003e \u003cspan class=\"na\"\u003elang\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;en\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ehead\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003emeta\u003c/span\u003e \u003cspan class=\"na\"\u003echarset\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;UTF-8\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003eTitle\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003escript\u003c/span\u003e \u003cspan class=\"na\"\u003esrc\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;../js/vue.js\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003escript\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ehead\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ediv\u003c/span\u003e \u003cspan class=\"na\"\u003eid\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;app\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                全选：\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003einput\u003c/span\u003e \u003cspan class=\"na\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;checkbox\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eid\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;checkbox\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003ev-model\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;checked\u0026#34;\u003c/span\u003e \u003cspan class=\"err\"\u003e@\u003c/span\u003e\u003cspan class=\"na\"\u003echange\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;changeAllChecked()\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e{{checked}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                多个复选框：\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003einput\u003c/span\u003e \u003cspan class=\"na\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;checkbox\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eid\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;runoob\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003evalue\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Runoob\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003ev-model\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;checkedNames\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003eRunoob\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003einput\u003c/span\u003e \u003cspan class=\"na\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;checkbox\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eid\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;google\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003evalue\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Google\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003ev-model\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;checkedNames\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003eGoogle\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003einput\u003c/span\u003e \u003cspan class=\"na\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;checkbox\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eid\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;taobao\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003evalue\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Taobao\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003ev-model\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;checkedNames\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003etaobao\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            选择的值为:{{checkedNames}}\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ediv\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003escript\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nx\"\u003eVue\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nx\"\u003eel\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;#app\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nx\"\u003edata\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nx\"\u003echecked\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nx\"\u003echeckedNames\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nx\"\u003echeckedArr\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Runoob\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Taobao\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Google\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nx\"\u003emethods\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nx\"\u003echangeAllChecked\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"kd\"\u003efunction\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003echecked\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        \u003cspan class=\"nx\"\u003echeckedNames\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        \u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003echeckedNames\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003echeckedArr\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        \u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003echeckedNames\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nx\"\u003ewatch\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;checkedNames\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"kd\"\u003efunction\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003echeckedNames\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elength\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003echeckedArr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        \u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003echecked\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        \u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003echecked\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003escript\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ehtml\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"vue实现全选","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/98c7af00/578bc70a/","section":"文章","summary":"","title":"WebService","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/webservice/","section":"Series","summary":"","title":"WebService","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/webservice/","section":"标签","summary":"","title":"WebService","type":"tags"},{"content":"\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.belerweb\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;pinyin4j\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.5.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; @Slf4j public class PinYinUtil { /** * 将字符串中的中文转化为拼音,其他字符不变 * * @param inputString * @return */ public static String getPinYin(String inputString) { HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat(); format.setCaseType(HanyuPinyinCaseType.LOWERCASE); format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); format.setVCharType(HanyuPinyinVCharType.WITH_V); char[] input = inputString.trim().toCharArray(); String output = \u0026#34;\u0026#34;; try { for (int i = 0; i \u0026lt; input.length; i++) { if (java.lang.Character.toString(input[i]).matches(\u0026#34;[\\\\u4E00-\\\\u9FA5]+\u0026#34;)) { String[] temp = PinyinHelper.toHanyuPinyinStringArray(input[i], format); output += temp[0]; } else output += java.lang.Character.toString(input[i]); } } catch (BadHanyuPinyinOutputFormatCombination e) { log.error(\u0026#34;中文转拼音出错\u0026#34;,e); } return output; } /** * 获取所有汉字串拼音首字母，英文字符不变 * @param chinese 汉字串 * @return 汉语拼音首字母 */ public static String getFirstSpell(String chinese) { StringBuffer pybf = new StringBuffer(); char[] arr = chinese.toCharArray(); HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat(); defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE); defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE); for (int i = 0; i \u0026lt; arr.length; i++) { if (arr[i] \u0026gt; 128) { try { String[] temp = PinyinHelper.toHanyuPinyinStringArray(arr[i], defaultFormat); if (temp != null) { pybf.append(temp[0].charAt(0)); } } catch (BadHanyuPinyinOutputFormatCombination e) { e.printStackTrace(); } } else { pybf.append(arr[i]); } } return pybf.toString().replaceAll(\u0026#34;\\\\W\u0026#34;, \u0026#34;\u0026#34;).trim(); } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/6158c09c/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003ecom.belerweb\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003epinyin4j\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e2.5.0\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Slf4j\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003ePinYinUtil\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 将字符串中的中文转化为拼音,其他字符不变\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     *\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @param inputString\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @return\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003egetPinYin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003einputString\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eHanyuPinyinOutputFormat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eformat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eHanyuPinyinOutputFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eformat\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetCaseType\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eHanyuPinyinCaseType\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eLOWERCASE\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eformat\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetToneType\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eHanyuPinyinToneType\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eWITHOUT_TONE\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eformat\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetVCharType\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eHanyuPinyinVCharType\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eWITH_V\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003einput\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003einputString\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003etrim\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003etoCharArray\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eoutput\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003einput\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elang\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eCharacter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003etoString\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einput\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"na\"\u003ematches\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;[\\\\u4E00-\\\\u9FA5]+\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etemp\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ePinyinHelper\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003etoHanyuPinyinStringArray\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einput\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eformat\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003eoutput\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etemp\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003eoutput\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elang\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eCharacter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003etoString\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einput\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ecatch\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eBadHanyuPinyinOutputFormatCombination\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eerror\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;中文转拼音出错\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eoutput\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * 获取所有汉字串拼音首字母，英文字符不变\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @param chinese 汉字串\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     * @return 汉语拼音首字母\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e     */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003egetFirstSpell\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003echinese\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eStringBuffer\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epybf\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eStringBuffer\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003echinese\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003etoCharArray\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eHanyuPinyinOutputFormat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edefaultFormat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eHanyuPinyinOutputFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003edefaultFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetCaseType\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eHanyuPinyinCaseType\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eLOWERCASE\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003edefaultFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetToneType\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eHanyuPinyinToneType\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eWITHOUT_TONE\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e128\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003etemp\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ePinyinHelper\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003etoHanyuPinyinStringArray\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edefaultFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etemp\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                        \u003c/span\u003e\u003cspan class=\"n\"\u003epybf\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etemp\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003echarAt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ecatch\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eBadHanyuPinyinOutputFormatCombination\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintStackTrace\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003epybf\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epybf\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003etoString\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003ereplaceAll\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\\\\W\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"na\"\u003etrim\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"中文字符串转拼音","type":"posts"},{"content":"public class ArrayTest { public static void main(String[] args) { int[] arr = new int[6]; int i = 0; label: while (i \u0026lt; 6) { int num = (int) (Math.random() * 30 + 1); arr[i] = num; for (int j = 0; j \u0026lt; i; j++) { if (arr[j] == arr[i]) { continue label; } } i++; } for (int j = 0; j \u0026lt; 6; j++) { System.out.print(arr[j] + \u0026#34;\\t\u0026#34;); } } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/5232d26e/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eArrayTest\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e6\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003elabel\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ewhile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e6\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enum\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eMath\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003erandom\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e30\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enum\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"k\"\u003econtinue\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003elabel\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e6\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\\t\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"六个不重复随机数数组","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/ebb2f342/81e7e5df/","section":"文章","summary":"","title":"内存与垃圾回收","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/%E5%86%85%E5%AD%98%E4%B8%8E%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/","section":"Series","summary":"","title":"内存与垃圾回收","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/%E5%86%85%E5%AD%98%E4%B8%8E%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/","section":"标签","summary":"","title":"内存与垃圾回收","type":"tags"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/6e08a29a/16c47591/","section":"文章","summary":"","title":"创建型模式","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/%E5%88%9B%E5%BB%BA%E5%9E%8B%E6%A8%A1%E5%BC%8F/","section":"Series","summary":"","title":"创建型模式","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/%E5%88%9B%E5%BB%BA%E5%9E%8B%E6%A8%A1%E5%BC%8F/","section":"标签","summary":"","title":"创建型模式","type":"tags"},{"content":"public class ImageServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //设置当前页面的文档类型，默认text/html resp.setContentType(\u0026#34;image/gif\u0026#34;); //申请了一块画布 BufferedImage bufferedImage = new BufferedImage(50,20,BufferedImage.TYPE_INT_RGB); //得到一个画笔,设置背景颜色为白色 Graphics graphics = bufferedImage.getGraphics(); graphics.setColor(Color.white); graphics.fillRect(0, 0, 50, 20); graphics.setColor(Color.red); //生成验证码，随机颜色以及数字字母 String number = \u0026#34;\u0026#34;; for (int i = 0; i \u0026lt; 4; i++) { graphics.setColor(getRandomColor()); String code = getRandomChar(); number+=code; graphics.drawString(code, 10 + (i * 10), 12); } //生成五条随机的干扰线 for (int i = 0; i \u0026lt; 5; i++) { graphics.setColor(getRandomColor()); graphics.drawLine(new Random().nextInt(50), new Random().nextInt(20), new Random().nextInt(50), new Random().nextInt(20)); } //响应给浏览器 ImageIO.write(bufferedImage, \u0026#34;jpg\u0026#34;, resp.getOutputStream()); } //生成随机颜色 public Color getRandomColor(){ return new Color(new Random().nextInt(255), new Random().nextInt(255), new Random().nextInt(255)); } //所有的数字字母，存入list集合 public String getRandomChar(){ ArrayList list = new ArrayList(); for (int i = 0; i \u0026lt; 10; i++) { list.add(i); } for (int i = 65; i \u0026lt; 65+26; i++) { list.add((char)i); } for (int i = 97; i \u0026lt; 97+26; i++) { list.add((char)i); } return list.get(new Random().nextInt(list.size()))+\u0026#34;\u0026#34;; } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/6ff83226/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eImageServlet\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eextends\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eHttpServlet\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Override\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprotected\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eHttpServletRequest\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ereq\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eHttpServletResponse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eresp\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003ethrows\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eServletException\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eIOException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//设置当前页面的文档类型，默认text/html\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eresp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetContentType\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;image/gif\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//申请了一块画布\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedImage\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebufferedImage\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedImage\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e50\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003e20\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedImage\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eTYPE_INT_RGB\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//得到一个画笔,设置背景颜色为白色\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eGraphics\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003egraphics\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebufferedImage\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetGraphics\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003egraphics\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetColor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eColor\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ewhite\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003egraphics\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003efillRect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e50\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e20\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003egraphics\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetColor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eColor\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ered\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//生成验证码，随机颜色以及数字字母\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enumber\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003egraphics\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetColor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003egetRandomColor\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003egetRandomChar\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003enumber\u003c/span\u003e\u003cspan class=\"o\"\u003e+=\u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003egraphics\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003edrawString\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecode\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e10\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e12\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//生成五条随机的干扰线\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003egraphics\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetColor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003egetRandomColor\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003egraphics\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003edrawLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eRandom\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003enextInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e50\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eRandom\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003enextInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e20\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eRandom\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003enextInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e50\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eRandom\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003enextInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e20\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e//响应给浏览器\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eImageIO\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ewrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebufferedImage\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;jpg\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eresp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetOutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//生成随机颜色\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eColor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003egetRandomColor\u003c/span\u003e\u003cspan class=\"p\"\u003e(){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eColor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eRandom\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003enextInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e255\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eRandom\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003enextInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e255\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eRandom\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003enextInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e255\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//所有的数字字母，存入list集合\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003egetRandomChar\u003c/span\u003e\u003cspan class=\"p\"\u003e(){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eArrayList\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eArrayList\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e10\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e65\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e65\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"n\"\u003e26\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e97\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e97\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"n\"\u003e26\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eRandom\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003enextInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esize\u003c/span\u003e\u003cspan class=\"p\"\u003e()))\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"动态生成验证码","type":"posts"},{"content":"public static String tran(int a){ //con:商；rem:余数;last:最后承载结果的变量 int con; String rem = \u0026#34;\u0026#34;,last = \u0026#34;\u0026#34;; //循环将余数储存在变量rem中 flag:while (true){ rem += (a % 2 + \u0026#34;\u0026#34;); a /= 2; if (a == 0){ break flag; } } //将获得的余数倒置，存储在变量last中 for (int i = rem.length() - 1;i \u0026gt;= 0;i--){ last += rem.charAt(i); } return last; } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/eb9bec24/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003etran\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//con:商；rem:余数;last:最后承载结果的变量\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003econ\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erem\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003elast\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//循环将余数储存在变量rem中\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eflag\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"k\"\u003ewhile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003erem\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e%\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e/=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003ebreak\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eflag\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//将获得的余数倒置，存储在变量last中\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e--\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003elast\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003echarAt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003elast\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"十进制整数转化为二进制","type":"posts"},{"content":"//在input file内容改变的时候触发事件 $(\u0026#34;#filed\u0026#34;).change(function(){ //获取input file的files文件数组; //$(\u0026#39;#filed\u0026#39;)获取的是jQuery对象，.get(0)转为原生对象; //这边默认只能选一个，但是存放形式仍然是数组，所以取第一个元素使用[0]; var file = $(\u0026#39;#filed\u0026#39;).get(0).files[0]; //创建用来读取此文件的对象 var reader = new FileReader(); //使用该对象读取file文件 reader.readAsDataURL(file); //读取文件成功后执行的方法函数 reader.onload=function(e){ //读取成功后返回的一个参数e，整个的一个进度事件 console.log(e); //选择所要显示图片的img，要赋值给img的src就是e中target下result里面 //的base64编码格式的地址 $(\u0026#39;#imgshow\u0026#39;).get(0).src = e.target.result; } }) ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/83b3b1a8/2e64be69/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//在input file内容改变的时候触发事件\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003e$\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;#filed\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nx\"\u003echange\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e\u003cspan class=\"p\"\u003e(){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e//获取input file的files文件数组;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"c1\"\u003e//$(\u0026#39;#filed\u0026#39;)获取的是jQuery对象，.get(0)转为原生对象;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"c1\"\u003e//这边默认只能选一个，但是存放形式仍然是数组，所以取第一个元素使用[0];\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003efile\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003e$\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;#filed\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nx\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nx\"\u003efiles\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e];\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e//创建用来读取此文件的对象\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003ereader\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"nx\"\u003eFileReader\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e//使用该对象读取file文件\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"nx\"\u003ereader\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ereadAsDataURL\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e//读取文件成功后执行的方法函数\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"nx\"\u003ereader\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eonload\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e//读取成功后返回的一个参数e，整个的一个进度事件\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e        \u003cspan class=\"nx\"\u003econsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e//选择所要显示图片的img，要赋值给img的src就是e中target下result里面\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e        \u003cspan class=\"c1\"\u003e//的base64编码格式的地址\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e        \u003cspan class=\"nx\"\u003e$\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;#imgshow\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nx\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nx\"\u003esrc\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003etarget\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"图片文件上传预览","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/d9073060/edd0d20f/","section":"文章","summary":"","title":"基本概念","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/","section":"Series","summary":"","title":"基本概念","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5/","section":"标签","summary":"","title":"基本概念","type":"tags"},{"content":"//定义一个日志写入器接口（LogWriter），要求写入设备必须遵守这个接口协议才能被日志器（Logger）注册。 //日志器有一个写入器的注册方法（Logger 的 RegisterWriter()方法）。 package main //LogWriter 日志写入器接口 type LogWriter interface { // Write 写入方法 Write(data interface{}) error } // Logger 日志 type Logger struct { //注册的写入器 logWriters []LogWriter } // RegisterWriter 注册日志写入器 func (log *Logger) RegisterWriter(writer LogWriter) { log.logWriters = append(log.logWriters, writer) } //Log 日志写入 func (log *Logger) Log(data interface{}) { for _, writer := range log.logWriters { writer.Write(data) } } // NewLogger 创建日志器实例 func NewLogger() *Logger { return \u0026amp;Logger{} } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/3910e7bf/8c9510fc/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//定义一个日志写入器接口（LogWriter），要求写入设备必须遵守这个接口协议才能被日志器（Logger）注册。\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//日志器有一个写入器的注册方法（Logger 的 RegisterWriter()方法）。\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003epackage\u003c/span\u003e \u003cspan class=\"nx\"\u003emain\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//LogWriter 日志写入器接口\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eLogWriter\u003c/span\u003e \u003cspan class=\"kd\"\u003einterface\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"c1\"\u003e// Write 写入方法\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nf\"\u003eWrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003edata\u003c/span\u003e \u003cspan class=\"kd\"\u003einterface\u003c/span\u003e\u003cspan class=\"p\"\u003e{})\u003c/span\u003e \u003cspan class=\"kt\"\u003eerror\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Logger 日志\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eLogger\u003c/span\u003e \u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"c1\"\u003e//注册的写入器\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003elogWriters\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"nx\"\u003eLogWriter\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// RegisterWriter 注册日志写入器\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eLogger\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"nf\"\u003eRegisterWriter\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ewriter\u003c/span\u003e \u003cspan class=\"nx\"\u003eLogWriter\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elogWriters\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elogWriters\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//Log 日志写入\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eLogger\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"nf\"\u003eLog\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003edata\u003c/span\u003e \u003cspan class=\"kd\"\u003einterface\u003c/span\u003e\u003cspan class=\"p\"\u003e{})\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ewriter\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"k\"\u003erange\u003c/span\u003e \u003cspan class=\"nx\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003elogWriters\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\t\u003cspan class=\"nx\"\u003ewriter\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eWrite\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// NewLogger 创建日志器实例\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eNewLogger\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eLogger\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"nx\"\u003eLogger\u003c/span\u003e\u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"实现日志系统","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/b18f3aef/5b0557cd/","section":"文章","summary":"","title":"常用工具","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/%E5%B8%B8%E7%94%A8%E5%B7%A5%E5%85%B7/","section":"Series","summary":"","title":"常用工具","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/%E5%B8%B8%E7%94%A8%E5%B7%A5%E5%85%B7/","section":"标签","summary":"","title":"常用工具","type":"tags"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/d9073060/d57b26b2/","section":"文章","summary":"","title":"常用组件","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/%E5%B8%B8%E7%94%A8%E7%BB%84%E4%BB%B6/","section":"Series","summary":"","title":"常用组件","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/%E5%B8%B8%E7%94%A8%E7%BB%84%E4%BB%B6/","section":"标签","summary":"","title":"常用组件","type":"tags"},{"content":" 现有T1、T2、T3三个线程，你怎么保证T2在T1执行完后执行，T3在T2执行完后执行 # 可以在T1的start方法后添加T1.join方法，在T2的start方法后添加T2.join方法\n例如\npublic class TestJoin{ public static void main(String[] args){ Thread t1 = new MyThread(“线程1”); Thread t2 = new MyThread(“线程2”); Thread t3 = new MyThread(“线程3”); try{ //t1先启动 t1.start(); t1.join(); //t2 t2.start(); t2.join(); //t3 t3.start(); t3.join(); } catch (InterruptedException e){ e.printStackTrace(); } } } java中Lock接口比synchronized代码块的优势是什么，你需要实现一个高效的缓存，它允许多个用户读，但是只允许一个用户写，一次来保证它的完整性，你怎么实现 # Lock接口在多线程和并发编程中最大的优势就是它为读和写分别提供了锁，它满足你在例如ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。而且，Lock接口可以通过lock和unlock方法，实现加锁和解锁，可以出现在不同的方法中，例如加锁可以出现登陆方法中，而解锁可以出现在登出方法中，\n高效的缓存，可以使用juc的locks包下的ReadWriteLock接口，这个接口有一个实现类ReentrantReadWriteLock，这个实现类中维护了两个锁，读锁和写锁\nJava中wait和sleep方法的不同 # 最大的不同实现等待的时候，wait会释放同步锁，但是sleep会一直持有同步锁。wait常用于线程间的交互，sleep通常被用在暂停一个线程的执行，声明位置方面，sleep声明在Thread类中，而wait声明在Object类中\n用Java实现阻塞队列 # 可以使用wait和notify实现简单的阻塞队列，当一个线程做完特定操作以后，wait这个线程，然后notify另一个线程，这个线程同样做完特定操作以后，wait掉，notify另一个线程，如果多线程组成的阻塞队列，那么可以对线程设置优先级，notify会优先唤醒优先级高的线程\nJava中创建线程的方式 # 一共三种方式\n①声明一个类继承Thread类，然后重写run方法，然后调用该类的start方法开启线程\n②实现Runable接口，重写该接口中抽象run方法，然后创建此类对象，将这个对象传入Thread构造器，实例化Thread对象，调用Thread对象的start方法开启线程\n③实现Callable接口，实现call方法，然后叫这个对象传入FutrueTask构造器中，实例化FutrueTask对象，然后将FutrueTask对象传入Thread构造器中，实例化Thread对象，调用Thread对象可以开启线程\n用Java编程一个会导致死锁程序，你将怎么解决 # 如果使用Lock作为同步锁，线程A在调用了lock方法加锁后，发生了异常，没有执行unlock，此时会出现死锁，这个的解决方法就是将unlock放在finally中\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/153a4d49/f9a9646b/","section":"文章","summary":"\u003ch4 class=\"relative group\"\u003e现有T1、T2、T3三个线程，你怎么保证T2在T1执行完后执行，T3在T2执行完后执行 \n    \u003cdiv id=\"现有t1t2t3三个线程你怎么保证t2在t1执行完后执行t3在t2执行完后执行\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e7%8e%b0%e6%9c%89t1t2t3%e4%b8%89%e4%b8%aa%e7%ba%bf%e7%a8%8b%e4%bd%a0%e6%80%8e%e4%b9%88%e4%bf%9d%e8%af%81t2%e5%9c%a8t1%e6%89%a7%e8%a1%8c%e5%ae%8c%e5%90%8e%e6%89%a7%e8%a1%8ct3%e5%9c%a8t2%e6%89%a7%e8%a1%8c%e5%ae%8c%e5%90%8e%e6%89%a7%e8%a1%8c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cp\u003e可以在T1的start方法后添加T1.join方法，在T2的start方法后添加T2.join方法\u003c/p\u003e","title":"并发编程","type":"posts"},{"content":"//序列化 public static void serialization(List list,String file) throws IOException { File file1 = new File(file); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file)); for (Object o : list){ outputStream.writeObject(o); } outputStream.flush(); outputStream.close(); } //反序列化 public static List deserialization(String file) throws IOException, ClassNotFoundException { File file1 = new File(file); List list = new ArrayList(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file)); Object o; while (true){ try { o = inputStream.readObject(); }catch (Exception e){ break; } list.add(o); } inputStream.close(); return list; } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/be76684a/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//序列化\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eserialization\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003ethrows\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eIOException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efile1\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eObjectOutputStream\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eoutputStream\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eObjectOutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFileOutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eObject\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eoutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ewriteObject\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eoutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eflush\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eoutputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"c1\"\u003e//反序列化\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003edeserialization\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003ethrows\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eIOException\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eClassNotFoundException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efile1\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eArrayList\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eObjectInputStream\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003einputStream\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eObjectInputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eFileInputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eObject\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003ewhile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003einputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereadObject\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"k\"\u003ecatch\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003ebreak\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eo\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003einputStream\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"序列化及反序列化","type":"posts"},{"content":" 1、数据库的三范式是什么 # 第一范式：表中字段具有原子性，不可再分\n第二范式：满足第一范式，要求表中每个实例或每行必须被唯一区分，通常需要为表加上一列，存储各个实例的唯一标识，主键，如果主键是复合主键，除了主键以外的其他列必须完全依赖于主键列，不能只依赖于主键的一部分\n第三范式：满足第二范式，表中不包含已在其他表中已包含的非主关键字信息，表中的的其他列必须直接依赖主键，不可以间接依赖\n2、数据库优化方面的经验 # ①使用PreparedStatement，一般来说比Statement性能高，因为一个sql发送到服务器去执行，涉及到语法检查、语义分析、编译、缓存等\n②有外键约束会影响插入和删除的性能，所以如果程序能够保证数据完整性的情况下，那么在设计数据库的时候，就去掉外键约束\n3、简述常用的索引都有哪些种类 # 普通索引：针对数据库表创建索引\n唯一索引：与普通索引类似，不同就是MySQL数据库索引列的值必须唯一，但是允许为空值\n主键索引：是一种特殊的唯一索引，不允许为空值，一般在建表的时候创建主键索引\n组合索引：为了进一步压榨MySQL效率，就要考虑建立组件索引，就是将数据库表的多个字段联合起来作为一个组合索引\n4、在mysql数据库中索引的工作机制是什么 # 数据库索引，是数据库管理系统中一个排序的数据结构，以协助快速查询，更新数据库表中的数据，索引的实现通常使用B树及其变种B+树\n5、MySQL基础操作命令 # MySQL是否处于运行状态：service mysqld status\n开启MySQL服务：service mysqld start\n停止MySQL服务：service mysqld stop\n登入MySQL：mysql -u root -p\n列出所有的数据库：show databases\n切换到某个数据库：use databasename\n列出某个数据库所有表：show tables\n获取表内所有Field对象的名称和类型：describe table_name\n6、MySQL复制原理以及流程 # mysql内建的复制功能是构建大型，高性能应用程序的基础。将mysql的数据分布到多个系统上去，这种分布的机制，是通过将mysql的某一台主机的数据复制到其他主机上，并重新执行一遍来实现的。复制过程中一个服务器充当主服务器，而一个或多个其他服务器充当服务器。主服务器将更新写入二进制日志文件，并维护文件的一个索引以跟踪日志循环。这些日志可以记录发送到从服务器的更新。当一个从服务器连接主服务器时，他通知主服务器在日志中读取的最后一次成功更新的位置。从服务器接收从那时起发生的任何更新，然后封锁并等待主服务器通知新的更新。过程如下1、主服务器把更新记录到二级制文件中2、从服务器把主服务器的二级制文件拷贝到自己的中继日志中3、从服务器重做中继日志中的事件，把更新应用到自己的数据库上\n7、mysql支持的复制类型 # ①基于语句的复制：在主服务器上执行的SQL语句，在从服务器上执行同样的语句，MySQL默认采用基于语句的复制，效率比较高。一旦发现没法精确赋值时，会自动选择基于行的复制\n②基于行的复制：把改变的内容复制获取，而不是把命令在从服务器上执行一遍，从mysql5.0开始支持\n③混合类型的复制：默认采用基于语句的复制，一旦发现基于语句的复制无法精确复制时，就只采用基于行的复制\n8、mysql中varchar和char的区别，以及varchar(50)中，50代表的含义 # varchar和char的区别是，char是一种固定长度的类型，varchar是可变长度的类型\nvarchar(50)中的50，是表明最多可以存放50个字节\n9、mysql中InnoDB支持的四种事务隔离级别的名称，以及区别 # ①Read Uncommitted读取未提交，该隔离级别，所有的事务都可以看到其他未提交事务的执行结果，这个隔离级别很少用于实际应用，因为它的性能也不比其他级别好多少，读取未提交的数据，也被称为脏读\n②Read Committed读取已提交，这是大多数数据库默认的隔离级别，但不是MySQL的，他满足了隔离的简单定义：一个事务只可以看到已经提交事务所做的改变，这种隔离级别支持所谓的不可重复度，因为同一事务的其他实例在该实例处理期间可能会有新的commit，所以同一个select可能返回不同的结果\n③Repeatable Read可重复读，这是MySQL的默认事务隔离级别，它确保同一事务的多个实例在并发读取数据时，会看到同样的数据，不过在理论上，这会导致另一个问题，幻读：幻读是指当前用户读取某一范围的数据行时，另一个事务在该范围内插入了新行，当用户再读取该范围的数据行时，会发现新的幻影行\n④Serializable可串行化，这是最高的隔离级别，他通过强制事务排序，使之不可能互相冲突，从而解决幻读问题，简而言之，就是在每个读的数据行上加上共享锁，但是这个级别可能会导致大量的超时现象和锁竞争\n10、InnoDB与MyIsam的区别 # InnoDB支持事务，支持外键；而MyIsam不支持事务，不支持外键 InnoDB由于受到事务和外键的影响，所以对数据的存储以及查询效率偏低；MyIsam相反偏高 InnoDB在存储时，表文件是2个：frm，ibd；而MyIsam是3个文件，分别存储frm，MYD，MYI InnoDB是MYSQL 5.5之后的默认存储引擎；而MyIsam是5.5之前的默认存储引擎 11、表中有大字段X（例如：text类型），且字段X不会经常更新，以读为主，将该字段拆成子表的好处是什么 # 如果字段里面有大字段（text、blob）类型的，而且这些字段的访问并不多，这时候放在一起就变成了缺点了。MySQL数据库的记录存储是按行存储的，数据块的大小又是固定的（16K），每条记录越小，相同的块存储的记录就越多。此时应该把大字段拆走，这样应付大部分小字段的查询时，就可以提高效率。当需要查询大字段的时候，此时的关联查询是不可避免的，但也是值得的。拆开后，对字段的Update就要Update多个表了\n12、MySQL中InnoDB引擎的行锁是通过加在什么上完成（或称实现）的 # InnoDB的行锁是通过给索引项加锁来实现的，这一点MySQL与Oracle不同，后者是通过在数据块中对相应的数据行进行加锁来实现的。InnoDB这种行锁实现特点意味着：只有通过索引条件检索数据，InnoDB才使用行级锁，否则，InnoDB将使用表锁\n13、如果一张表只有一个字段VARCHAR(N)类型，utf8编码，则N最大值为多少（精确到数量级即可） # 由于utf8的每个字符最多占用3个字节。而MySQL定义行的长度不能超过65535，因此N的最大值计算方法为：(65535-1-2)/3。减去1的原因是实际存储从第二个字节开始，减去2的原因是因为要在列表长度存储实际的字符长度，除以3是因为utf8限制。\n14、select *和select 全部字段两种写法的优缺点 # ①前者需要解析数据字典，后者不需要\n②结果输出顺序，前者与建表列顺序相同，后者按指定字段顺序\n③表字段改名，前者不需要修改，后者需要修改\n④后者可以建立索引进行优化，前者无法优化\n⑤后者的可读性比前者高\n15、having字句和where的异同点 # ①语法上，where用于表中列名，having用于select结果别名\n②影响结果范围：where从表读出数据的行数，having返回客户端的行数\n③索引：where可以使用索引，having不可以使用索引，只能在临时结果集操作\n④where后面不可以使用聚合函数，having专门使用聚合函数的\n16、MySQL当记录不存在时insert，当记录存在时update，语句怎么写 # insert into table (a,b,c) values (1,2,3) on duplicate key update c=c+1\n17、MySQL的insert和update的select语法 # insert into sutdent (sid,sname) select 10,'xzm' from stu\nupdate A inner join(select id,name from B) c on A.id = c.id set A.name = c.name\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/153a4d49/7719799e/","section":"文章","summary":"\u003ch4 class=\"relative group\"\u003e1、数据库的三范式是什么 \n    \u003cdiv id=\"1数据库的三范式是什么\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1%e6%95%b0%e6%8d%ae%e5%ba%93%e7%9a%84%e4%b8%89%e8%8c%83%e5%bc%8f%e6%98%af%e4%bb%80%e4%b9%88\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cp\u003e第一范式：表中字段具有原子性，不可再分\u003c/p\u003e","title":"数据库","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/c343b13e/6eb2df4f/2cc3f3c2/","section":"文章","summary":"","title":"数据结构","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/","section":"Series","summary":"","title":"数据结构","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/","section":"标签","summary":"","title":"数据结构","type":"tags"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/c343b13e/6eb2df4f/","section":"文章","summary":"","title":"数据结构与算法","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/","section":"标签","summary":"","title":"数据结构与算法","type":"tags"},{"content":"public class ArrayTest1 { public static void main(String[] args) { // 创建二维数组并初始化，并且内层数组长度不确定 int[][] yangHui = new int[10][]; // 赋值 // 遍历外层数组 for (int i = 0; i \u0026lt; yangHui.length; i++) { // 初始化内层数组 yangHui[i] = new int[i + 1]; // 遍历内层数组 for (int j = 0; j \u0026lt; yangHui[i].length; j++) { // 如果是首位或末位元素，赋值为1 if (j == 0 || j == (yangHui[i].length - 1)) { yangHui[i][j] = 1; // 其他中间的元素，按照公式赋值 } else { yangHui[i][j] = yangHui[i - 1][j - 1] + yangHui[i - 1][j]; } } } // 输出 // 遍历外层数组 for (int i = 0; i \u0026lt; yangHui.length; i++) { // 输出最左侧的行号 System.out.print(\u0026#34;[\u0026#34; + i + \u0026#34;]\\t\u0026#34;); // 遍历内层数组 for (int j = 0; j \u0026lt; yangHui[i].length; j++) { // 输出元素 System.out.print(yangHui[i][j] + \u0026#34;\\t\u0026#34;); } // 输出完一行后换行 System.out.println(); // 输出最后一行的列数 // 判断是否输出到最后一行 if (i == yangHui.length - 1) { for (int j = 0; j \u0026lt; yangHui[yangHui.length - 1].length; j++) { System.out.print(\u0026#34;\\t\u0026#34; + \u0026#34;[\u0026#34; + j + \u0026#34;]\u0026#34;); } } } } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/4389e49c/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eArrayTest1\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 创建二维数组并初始化，并且内层数组长度不确定\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[][]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eyangHui\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e10\u003c/span\u003e\u003cspan class=\"o\"\u003e][]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 赋值\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 遍历外层数组\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eyangHui\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 初始化内层数组\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eyangHui\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 遍历内层数组\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eyangHui\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 如果是首位或末位元素，赋值为1\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eyangHui\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003eyangHui\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 其他中间的元素，按照公式赋值\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003eyangHui\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eyangHui\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eyangHui\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 输出\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 遍历外层数组\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eyangHui\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 输出最左侧的行号\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;[\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;]\\t\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 遍历内层数组\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eyangHui\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 输出元素\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eyangHui\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\\t\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 输出完一行后换行\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 输出最后一行的列数\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 判断是否输出到最后一行\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eyangHui\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eyangHui\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eyangHui\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\\t\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;[\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;]\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n      \u003cimg\n    class=\"my-0 rounded-md\"\n    loading=\"lazy\"\n    decoding=\"async\"\n    fetchpriority=\"low\"\n    alt=\"img\"\n    data-zoom-src=\"/posts/add78db0/8aee08f5/4389e49c/image/202109181437808.png\"\n    src=\"/posts/add78db0/8aee08f5/4389e49c/image/202109181437808.png\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"杨辉三角","type":"posts"},{"content":" windows下cmd进行操作 # #查询端口的pid netstat -ano | findstr \u0026#34;port\u0026#34; #关闭端口 taskkill /f /t /pid p ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/8dfdbf39/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003ewindows下cmd进行操作 \n    \u003cdiv id=\"windows下cmd进行操作\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#windows%e4%b8%8bcmd%e8%bf%9b%e8%a1%8c%e6%93%8d%e4%bd%9c\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#查询端口的pid\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enetstat -ano \u003cspan class=\"p\"\u003e|\u003c/span\u003e findstr \u003cspan class=\"s2\"\u003e\u0026#34;port\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#关闭端口\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003etaskkill /f /t /pid p\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"查杀端口","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/6e08a29a/5d67f48c/","section":"文章","summary":"","title":"结构式模式","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/%E7%BB%93%E6%9E%84%E5%BC%8F%E6%A8%A1%E5%BC%8F/","section":"Series","summary":"","title":"结构式模式","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/%E7%BB%93%E6%9E%84%E5%BC%8F%E6%A8%A1%E5%BC%8F/","section":"标签","summary":"","title":"结构式模式","type":"tags"},{"content":"private String getStackTraceInfo(Exception e){ StackTraceElement[] stackTraceElements = e.getStackTrace(); String result = e.toString() + \u0026#34;\\n\u0026#34;; for (int index = stackTraceElements.length - 1; index \u0026gt;= 0; --index) { result += \u0026#34;at [\u0026#34; + stackTraceElements[index].getClassName() + \u0026#34;,\u0026#34;; result += stackTraceElements[index].getFileName() + \u0026#34;,\u0026#34;; result += stackTraceElements[index].getMethodName() + \u0026#34;,\u0026#34;; result += stackTraceElements[index].getLineNumber() + \u0026#34;]\\n\u0026#34;; } return result; } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/b3843602/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003egetStackTraceInfo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eStackTraceElement\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estackTraceElements\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetStackTrace\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003etoString\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\\n\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eindex\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estackTraceElements\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eindex\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e--\u003c/span\u003e\u003cspan class=\"n\"\u003eindex\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;at [\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estackTraceElements\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eindex\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetClassName\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;,\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estackTraceElements\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eindex\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetFileName\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;,\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estackTraceElements\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eindex\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetMethodName\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;,\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estackTraceElements\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eindex\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetLineNumber\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;]\\n\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"获取异常堆栈信息","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/c343b13e/45c36453/","section":"文章","summary":"","title":"计算机网络","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/","section":"Series","summary":"","title":"计算机网络","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/","section":"标签","summary":"","title":"计算机网络","type":"tags"},{"content":" 列举在JDK中常用的设计模式 # 单例模式：用于Runtime，Calendar和其他的一些类中\n工厂模式：用于各种不可变的类中，如Boolean\n观察者模式：用于Swing和多个事件监听类中\n装饰器模式：用于多个JavaIO类中\n什么是设计模式，是否在代码中使用过设计模式 # 设计模式是世界上各种程序员用来解决特定实际问题的尝试和测试的方法，设计模式是代码可用性的延申\nJava中什么叫单例设计模式？请用Java写出线程安全的单例模式 # 单例模式重点在于在整个系统上共享一些创建时比较耗费资源的对象。整个应用中只维护一个特定类的实力，他被所有的组件共同使用。Java.lang.Runtime是单例模式中的经典例子。从Java5开始就可以使用枚举来实现线程安全的单例，饿汉式就是线程安全的\njava5前：\n//由于直接在方法上添加synchronized，开销比较大，所以使用双重检查锁 public class Singlet(){ private static Singlet singlet; private Singlet(){}; public static Singlet getSinglet(){ if(singlet == null){ synchronized(Singlet.class){ if(singlet == null){ singlet = new Singlet(); } } } } } java5后：\n可以将枚举Type看作一个类，A为Type的实例\npublic enum Type { A; public void hello(){ System.out.println(\u0026#34;HELLO!!!\u0026#34;); } } Java中，什么叫做观察者模式 # 观察者模式基于对象的状态变化和观察者的通讯，以便他们做出相应的操作。简单的例子就是一个天气系统，当天气变化时必须在展示给公众的试图中进行反应，这个视图对象就是一个主题，而不同的视图就是观察者\n工厂模式最主要的好处是什么，在哪里使用 # 工厂模式最大的好处就是增加了创建对象时的封装层次。如果使用工厂来创建对象，之后你可以使用更高级和更高性能的实现来替换原始的产品实现或类，这不需要在调用层做任何的修改\n举例一个Java类实现的装饰模式，它作用域对象层次还是类层次 # 装饰模式增强了单个对象的能力。JavaIO到处都使用了装饰模式，典型例子就是Buffered系列类，如BufferedReader和BufferedWriter，它增强了Reader和Writer对的，以实现提升性能的Buffer层次的读取和写入\nJava中，为什么不允许静态方法中访问非静态变量 # Java中不能从静态上下文中访问非静态数据是因为非静态变量是跟具体的对象实例关联的，而静态的却没有和任何的实例进行关联\n设计一个ATM机，说出设计思路 # 设计金融系统来说，必须知道它们应该在任何情况下都可以正常工作，不管是断电还是其他情况，所以ATM应该保持正确的状态（事务），比如加锁、事务、错误条件、边界条件等等。\n什么时候用重载，什么时候用重写 # 如果看到一个类的不同实现有着不同的方式来做同一件事情，那么就应该用重写。而重载使用不同的输入做同一件事情，Java中，重载的方法签名不同，而重写并不是\n什么情况下更倾向使用抽象类而不是接口 # 接口和抽象类都遵循“面向接口而不是实现编码”的设计原则，可以增加代码的灵活性，可以适应不断变化的需求。\n在Java中，你只能继承一个类，但可以实现多个接口，所以一旦继承了一个类，你就失去了继承其他类的机会。\n接口通常用来表示附属描述或行为如：例如Runable、Clonable、Serializable等等，所以如果这些类是抽象类，那么你的类就不能不是去继承，但这些类是接口，所以你的类可以同时拥有多种不同的行为。\n在一些对时间要求比较高的应用中，更倾向使用抽象类，因为他比接口稍微快一些。如果希望把一系列的行为都规范在类继承层次里面，并且可以更好地在同一个地方进行编码，那么使用抽象类是一个更好的选择。有时，接口和抽象类可以一起使用，接口中定义函数，而抽象类中定义默认的实现。\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/153a4d49/38113737/","section":"文章","summary":"\u003ch4 class=\"relative group\"\u003e列举在JDK中常用的设计模式 \n    \u003cdiv id=\"列举在jdk中常用的设计模式\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#%e5%88%97%e4%b8%be%e5%9c%a8jdk%e4%b8%ad%e5%b8%b8%e7%94%a8%e7%9a%84%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cp\u003e单例模式：用于Runtime，Calendar和其他的一些类中\u003c/p\u003e","title":"设计模式","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/6e08a29a/","section":"文章","summary":"","title":"设计模式","type":"posts"},{"content":"INI 文件由多行文本组成，整个配置由[ ]拆分为多个“段”（section）。每个段中又以＝分割为“键”和“值”。\nINI 文件以;置于行首视为注释，注释后将不会被处理和识别\n[sectionl] key1=value1 ;key2=value2 [section2] 反射实现 # /demo/config/ini.go # package config import ( \u0026#34;bufio\u0026#34; \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;os\u0026#34; \u0026#34;reflect\u0026#34; \u0026#34;strings\u0026#34; ) type Ini struct { Path string } //根据段名，读取配置到结构体 func (i *Ini) Read(duan string, conf any) error { if i.Path == \u0026#34;\u0026#34; { return errors.New(\u0026#34;path not defined\u0026#34;) } val := reflect.ValueOf(conf) if val.Kind() != reflect.Ptr { return errors.New(\u0026#34;conf must be a Struct\u0026#34;) } val = val.Elem() typ := reflect.TypeOf(conf).Elem() if val.Kind() != reflect.Struct { return errors.New(\u0026#34;conf must be a Struct\u0026#34;) } m, err := ParseFile(i.Path) if err != nil { return err } data := m[duan] if len(data) == 0 { return nil } for num := 0; num \u0026lt; typ.NumField(); num++ { field := typ.Field(num) tag := field.Tag.Get(\u0026#34;ini\u0026#34;) val.Field(num).SetString(data[tag]) } return nil } //将结构体以指定的段名写入ini func (i *Ini) Write(duan string, conf any) error { if i.Path == \u0026#34;\u0026#34; { return errors.New(\u0026#34;path not defined\u0026#34;) } typ := reflect.TypeOf(conf) val := reflect.ValueOf(conf) //如果以指针传入 if typ.Kind() == reflect.Ptr { typ = typ.Elem() if typ.Kind() != reflect.Struct { return errors.New(\u0026#34;conf must a struct\u0026#34;) } val = val.Elem() } else if typ.Kind() != reflect.Struct { return errors.New(\u0026#34;conf must a struct\u0026#34;) } m := make(map[string]string) for i := 0; i \u0026lt; typ.NumField(); i++ { tag := typ.Field(i).Tag.Get(\u0026#34;ini\u0026#34;) value := val.Field(i).String() m[tag] = value } return WriteFile(i.Path, duan, m) } // ParseFile 将ini文件中的数据以map切片形式读出 func ParseFile(path string) (map[string]map[string]string, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() reader := bufio.NewReader(f) m := make(map[string]map[string]string) //表示当前记录的段名 duanName := \u0026#34;\u0026#34; for { s, err := reader.ReadString(\u0026#39;\\n\u0026#39;) if err != nil { break } // 去掉行两边的空格 s = strings.TrimSpace(s) //如果是空行或者注释行跳过 if s == \u0026#34;\u0026#34; || s[0] == \u0026#39;;\u0026#39; { continue } //如果以[开头并以]结尾，说明是段 if s[0] == \u0026#39;[\u0026#39; \u0026amp;\u0026amp; s[len(s)-1] == \u0026#39;]\u0026#39; { duanName = s[1 : len(s)-1] m[duanName] = make(map[string]string) continue } kv := strings.Split(s, \u0026#34;=\u0026#34;) if duanName != \u0026#34;\u0026#34; { key := strings.TrimSpace(kv[0]) val := strings.TrimSpace(kv[1]) m[duanName][key] = val } } return m, nil } // WriteFile 将map作为指定段的内容写入ini，其余的配置不变 func WriteFile(path string, duan string, m map[string]string) error { b, err := ioutil.ReadFile(path) if err != nil { return err } data := string(b) //将数据按照段切分为切片 dataSlice := strings.Split(data, \u0026#34;[\u0026#34;) dataSlice = dataSlice[1:] fmt.Println(dataSlice) duan = fmt.Sprintf(\u0026#34;[%s]\u0026#34;, duan) index := -1 //判断该段是否已经存在 for i := 0; i \u0026lt; len(dataSlice); i++ { dataSlice[i] = \u0026#34;[\u0026#34; + dataSlice[i] fmt.Println(dataSlice[i]) if strings.Index(dataSlice[i], duan) != -1 { index = i } } duanData := \u0026#34;\\n\u0026#34; + duan + \u0026#34;\\n\u0026#34; //将参数封装为字符串 for k, v := range m { duanData += fmt.Sprintf(\u0026#34;%s = %s\\n\u0026#34;, k, v) } //判断该段是否已经存在，存在就覆写，不存在就追加一个 if index != -1 { dataSlice[index] = duanData } else { dataSlice = append(dataSlice, duanData) } data = strings.Join(dataSlice, \u0026#34;\u0026#34;) err = ioutil.WriteFile(path, []byte(data), 0777) if err != nil { return err } return nil } /demo/config/model.go # package config type Mysql struct { Driver string `ini:\u0026#34;driver\u0026#34;` UserName string `ini:\u0026#34;username\u0026#34;` PassWord string `ini:\u0026#34;password\u0026#34;` } type Oracle struct { UserName string `ini:\u0026#34;username\u0026#34;` PassWord string `ini:\u0026#34;password\u0026#34;` } /demo/db.ini # [mysql] username = root password = 123456 driver = com.jdbc.mysql [oracle] username = orl password = 123456 /demo/main.go # 读\npackage main import ( \u0026#34;demo/config\u0026#34; \u0026#34;fmt\u0026#34; ) func main() { i := \u0026amp;config.Ini{\u0026#34;./db.ini\u0026#34;} mysql := config.Mysql{} oracle := config.Oracle{} err := i.Read(\u0026#34;mysql\u0026#34;, \u0026amp;mysql) if err != nil { fmt.Println(err) } err = i.Read(\u0026#34;oracle\u0026#34;, \u0026amp;oracle) if err != nil { fmt.Println(err) } fmt.Println(mysql) //{com.jdbc.mysql root 123456} fmt.Println(oracle) //{orl 123456} } 写\npackage main import ( \u0026#34;demo/config\u0026#34; \u0026#34;fmt\u0026#34; ) func main() { m := config.Mysql{\u0026#34;con.test.mysql\u0026#34;, \u0026#34;mysql\u0026#34;, \u0026#34;78787878\u0026#34;} ini := config.Ini{\u0026#34;./db.ini\u0026#34;} err := ini.Write(\u0026#34;mysql\u0026#34;, m) if err != nil { fmt.Println(err) } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/3910e7bf/cad06346/","section":"文章","summary":"\u003cp\u003eINI 文件由多行文本组成，整个配置由\u003ccode\u003e[ ]\u003c/code\u003e拆分为多个“段”（section）。每个段中又以\u003ccode\u003e＝\u003c/code\u003e分割为“键”和“值”。\u003c/p\u003e","title":"读写ini配置文件","type":"posts"},{"content":"//编写Java程序：删除指定的文件或文件夹，如果文件夹中有文件或子文件夹也一并删除 public static void deleteAll(File file){ if (file.isDirectory()){ File[] files = file.listFiles(); if (files != null){ for (File fi : files){ deleteAll(fi); } } } file.delete(); } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/5b80d19b/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//编写Java程序：删除指定的文件或文件夹，如果文件夹中有文件或子文件夹也一并删除\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003edeleteAll\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eisDirectory\u003c/span\u003e\u003cspan class=\"p\"\u003e()){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efiles\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elistFiles\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efiles\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efi\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efiles\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003edeleteAll\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efi\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003efile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"递归删除文件夹内所有文件","type":"posts"},{"content":"/** * 使用递归计算斐波那契数列 * 1 1 2 3 5 8 13 21 34 55 */ public class Fibonacci { public int fibonacciArr(int n){ if (n == 1){ return 1; }else if (n == 2){ return 1; }else { return fibonacciArr(n - 1) + fibonacciArr(n - 2); } } } ","date":"2023-11-11","externalUrl":null,"permalink":"/posts/add78db0/8aee08f5/56778ef5/","section":"文章","summary":"\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e/**\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e* 使用递归计算斐波那契数列\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e* 1 1 2 3 5 8 13 21 34 55 \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e*/\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eFibonacci\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003efibonacciArr\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e          \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e      \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e){\u003c/span\u003e\u003cspan class=\"w\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e     \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e       \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efibonacciArr\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efibonacciArr\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e     \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e   \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"递归解决斐波那契数列","type":"posts"},{"content":" List # ArrayList # 1、ArrayList用过吗，是什么东西，可以用来干嘛 # 用过，ArrayList就是数组列表，主要用来装在数据，当我们装在的是基本数据类型如int、long、boolean、short、byte时，只可以存储他们的包装类，他的底层实现是一个Object类型的数组，特点就是查询效率高，增删的效率低，线程不安全，但是使用的频率高\n与他类似的是LinkedList、和LinkedList相比，ArrayList查找和访问元素的速度较快，但是新增和删除的速度较慢\n2、为啥线程不安全还要使用它呢 # 因为在我们正常的使用场景里，都是用来查询的，不会涉及太频繁的增删，如果涉及台频繁的增删，那么使用LinkedList，如果需要线程安全，就是用Vector\n不存在一个集合工具查询效率高、增删效率也高，而且还线程安全，所以需要根据实际需求来取舍\n3、ArrayList底层实现是数组，但是数组的大小是定长的，如果不断的往里面添加数据的话，不会有什么问题吗 # ArrayList可以通过构造方法在初始化的时候指定底层数组的大小\n通过无参构造方法实例化ArrayList的时候，底层为默认的空数组，只有在真正添加数据add的时候，才会分配初始容量10\n4、数组长度是有限制的，而ArrayList可以存放任意数量的对象，是怎么实现的 # 通过数组扩容实现的，如果在新增元素的时候，发现数组已经满了，那么会重新定义一个长度为原来1.5倍的数组，并且将原数组的数据原封不动的复制的新数组里，然后再将指向原数组的地址换为新数组\n5、具体说下1.7和1.8中初始化的区别 # ArrayList在1.7中，初始化的时候就会调用this(10)，创建一个容量为10的数组，在1.8中，默认创建的是一个空数组，只有在第一次add的时候，才会将容量变为10\n6、ArrayList增删效率低，底层在增删的时候是怎么做的，为什么慢 # ArrayList有指定index新增，也可以直接新增，在新增的时候，会先判断长度是否够，如果不够需要就需要扩容，这是导致增加效率低的一个原因，还有，在jdk8后，扩容采用的是为运算，相较于7已经快了许多了\n如果在指定位置新增，在校验长度以后，实际上是数组的元素的移动，将指定位置后面的元素，统统向后移动一位，然后再讲新元素插入，删除元素的时候同理，在数据量大的时候，元素的移动，增加了很多系统开销，这是导致效率低的第二个原因\n7、ArrayList是线程安全的吗 # 不是，线程安全的容器是Vector\n但是也可以不使用Vector，使用Collections.synchronizedList方法可以将一个普通的ArrayList包装成一个线程安全版本的容器\n8、ArrayList适合用来做队列吗 # 不适合，队列一般是先进先出的，如果用ArrayList做队列的话，那么需要在数组尾部追加数据，在数组的头部删除数据，反过来也可以，但是无论哪种方式，都会涉及到数组中数据的移动，非常耗费性能\n9、那什么适合用来做队列 # ArrayBlockingQueue，内部实现的就是一个环形队列，是一个定长的队列，内部使用的是定长数组实现\nLibrary，也是通过环形数组实现高性能队列\n10、ArrayList和LinkedList遍历性能比较 # 遍历的话，ArrayList快得多，因为ArrayList遍历最大的优势在于内存的连续性，CPU的内部缓存结构会缓存连续的内存片段，可以大幅度降低读取内存的性能开销\nMap # HashMap # 1、了解HashMap吗，聊聊它的结构和底层原理 # 了解，HashMap是非常常用的双列集合，底层有数据+链表或红黑树组成，默认长度16\n数组里面每个地方都存在着以key-value形式存在的实例，jdk7中为entry，jdk8中为node\n所以在没有插入的时候，它本身的所有位置都是null，put的时候根据key的hash去计算index值，也就是插入的位置\n2、提到了链表，为什么需要用链表，链表是什么样子的 # 因为数组的长度优先，在有限的长度里面使用hash计算，因为hash本身具有概率性，也就是说，两次插入元素的key有概率一样，那么在同样的位置上就需要使用链表\n3、新的Entry节点在插入链表的时候，是怎么插入的 # jdk7之前是头插法，也就是新来的值，会取代原有的值，那么原来的值就会被顺推到链表中\njdk8之后，使用的尾部插入\n4、什么时候resize # 导致resize扩容的因素是两个，一个是Capacity，HashMap当前的长度，一个是LoadFactor，负载因子，默认是0.75，比如当前的容量为100，当存入第76个元素的时候，判断就需要进行resize了，那么就会进行扩容\n5、怎么扩容的 # 第一步、创建一个新的Entry空数组，长度为原数组的2倍，遍历原来的Entry数组，并且将原来的Entry重新通过Hash计算添加到新数组中\n6、为什么需要重新Hash计算而不是直接复制 # 因为计算index的Hash计算规则和数组长度有关\n7、HashMap中链表大小超过8时自动转为红黑树，小于6时重新变为链表，为什么 # 根据泊松分布，在负载因子默认为0.75的时候，单个hash槽内元素个数为8的概率小于百万分之一，所以将7作为一个分水岭，等于7不转换，大于等于8时候转换为红黑树，小于等于6转换为链表\nHashTable # 1、说说HashTable # 跟HashMap相比，HashTable是线程安全的，适合在多线程的情况下使用，因为底层在所有对数据操作的方法上都会添加synchronized，所以效率不高\n2、处理线程方面，说说和HashMap的区别 # 存储元素：HashTable的key和value都不允许为null，但是HashMap的key和value都可以为null\n实现方式：HashTable继承了Dictionary类，而HashMap继承的是AbstractMap类\n初始化容量：HashMap的初始化容量16，HashTable初始容量为11\n扩容机制：二者都是在到达总容量乘负载因子时扩容，但是HashMap是对当前容量乘2，而HashTable是当前容量乘2再加1\n迭代器不同：HashMap中Iterator迭代器是fail-fast的，而HashTable的Enumerator不是fail-fast的\n3、什么是fail-fast # fail-fast快速失败是java集合中的一种机制，在用迭代器遍历一个集合对时，如果在过程中，对集合对象内容进行了增、删、改，那么会抛出异常\n原理是，在遍历时，直接访问集合中的内容，并且在遍历过程中使用一个变量，如果过程中集合内容改变，那么会改变这个变量值，每次在使用hashNext或next方法的时候，会检测这个变量是否为最开始的值，如果是就正常遍历，不是就会抛出异常\n4、为什么HashTable的key-value不可以为null，HashMap可以 # 从底层原码上，HashMap对传入的key进行的了非空判断，而HashTable存入null会抛出空指针异常\n由于HashTable使用的安全失败机制，这种机制会是此次读到的数据不一定是最新的数据，如果使用了null，那么无法判断对应的key是不存在还是为空\n","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/153a4d49/fe8dbcc5/","section":"文章","summary":"\u003ch2 class=\"relative group\"\u003eList \n    \u003cdiv id=\"list\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#list\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eArrayList \n    \u003cdiv id=\"arraylist\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#arraylist\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\n\u003ch4 class=\"relative group\"\u003e1、ArrayList用过吗，是什么东西，可以用来干嘛 \n    \u003cdiv id=\"1arraylist用过吗是什么东西可以用来干嘛\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#1arraylist%e7%94%a8%e8%bf%87%e5%90%97%e6%98%af%e4%bb%80%e4%b9%88%e4%b8%9c%e8%a5%bf%e5%8f%af%e4%bb%a5%e7%94%a8%e6%9d%a5%e5%b9%b2%e5%98%9b\" aria-label=\"锚点\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h4\u003e\n\u003cp\u003e用过，ArrayList就是数组列表，主要用来装在数据，当我们装在的是基本数据类型如int、long、boolean、short、byte时，只可以存储他们的包装类，他的底层实现是一个Object类型的数组，特点就是查询效率高，增删的效率低，线程不安全，但是使用的频率高\u003c/p\u003e","title":"集合","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/posts/3ab7256e/d9073060/8ed1dfd5/","section":"文章","summary":"","title":"项目构建和打包","type":"posts"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/series/%E9%A1%B9%E7%9B%AE%E6%9E%84%E5%BB%BA%E5%92%8C%E6%89%93%E5%8C%85/","section":"Series","summary":"","title":"项目构建和打包","type":"series"},{"content":"","date":"2023-11-11","externalUrl":null,"permalink":"/tags/%E9%A1%B9%E7%9B%AE%E6%9E%84%E5%BB%BA%E5%92%8C%E6%89%93%E5%8C%85/","section":"标签","summary":"","title":"项目构建和打包","type":"tags"},{"content":" Hi, everyone! I\u0026rsquo;m GradyYoung.\nThis is my blog site.\nTechnology stack Language Tools and Middlewares Others ","date":"13 June 2022","externalUrl":null,"permalink":"/en/about/","section":"GradyYoung's Site","summary":"\u003ch2 class=\"relative group\"\u003eHi, everyone! \n    \u003cdiv id=\"hi-everyone\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n\u003c/h2\u003e\n\u003cp\u003eI\u0026rsquo;m GradyYoung.\u003c/p\u003e\n\u003cp\u003eThis is my blog site.\u003c/p\u003e\n\n\u003ch2 class=\"relative group\"\u003eTechnology stack \n    \u003cdiv id=\"technology-stack\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n\u003c/h2\u003e\n\n\u003ch3 class=\"relative group\"\u003eLanguage \n    \u003cdiv id=\"language\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n\u003c/h3\u003e\n\u003cdiv style=\"display:flex;gap:5px;flex-flow:wrap;\"\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Java\" src=\"https://img.shields.io/badge/Java-00ca00?style=flat-square\u0026amp;logo=Java\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Golang\" src=\"https://img.shields.io/badge/Golang-e69138?style=flat-square\u0026amp;logo=Go\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Python\" src=\"https://img.shields.io/badge/Python-0000ff?style=flat-square\u0026amp;logo=Python\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"JavaScript\" src=\"https://img.shields.io/badge/JavaScript-ff7070?style=flat-square\u0026amp;logo=JavaScript\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"C/C\u0026#43;\u0026#43;\" src=\"https://img.shields.io/badge/C/C\u0026#43;\u0026#43;-32CD32?style=flat-square\u0026amp;logo=C/C\u0026#43;\u0026#43;\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Lua\" src=\"https://img.shields.io/badge/Lua-050080?style=flat-square\u0026amp;logo=lua\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e\n\u003c/div\u003e\n\n\u003ch3 class=\"relative group\"\u003eTools and Middlewares \n    \u003cdiv id=\"tools-and-middlewares\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n\u003c/h3\u003e\n\u003cdiv style=\"display:flex;gap:5px;flex-flow:wrap;\"\u003e\n\u003cp\u003e\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Spring\" src=\"https://img.shields.io/badge/Spring-32CD32?style=flat-square\u0026amp;logo=Spring\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Mysql\" src=\"https://img.shields.io/badge/Mysql-01758F?style=flat-square\u0026amp;logo=Mysql\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Redis\" src=\"https://img.shields.io/badge/Redis-D92C21?style=flat-square\u0026amp;logo=Redis\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Docker\" src=\"https://img.shields.io/badge/Docker-01AEFF?style=flat-square\u0026amp;logo=Docker\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Kubernetes\" src=\"https://img.shields.io/badge/Kubernetes-3476E5?style=flat-square\u0026amp;logo=Kubernetes\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Dubbo\" src=\"https://img.shields.io/badge/Dubbo-FA7343?style=flat-square\u0026amp;logo=Dubbo\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"RokectMQ\" src=\"https://img.shields.io/badge/RokectMQ-0C974D?style=flat-square\u0026amp;logo=RocketMq\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"RabbitMQ\" src=\"https://img.shields.io/badge/RabbitMQ-014256?style=flat-square\u0026amp;logo=RabbitMQ\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Kafuka\" src=\"https://img.shields.io/badge/Kafuka-32CD32?style=flat-square\u0026amp;logo=Kafuka\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Linux\" src=\"https://img.shields.io/badge/Linux-17161B?style=flat-square\u0026amp;logo=Linux\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"apache\" src=\"https://img.shields.io/badge/Apache-32CD32?style=flat-square\u0026amp;logo=apache\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Git\" src=\"https://img.shields.io/badge/Git-FA7343?style=flat-square\u0026amp;logo=Git\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"IntelliJ IDEA\" src=\"https://img.shields.io/badge/IntelliJ_IDEA-1575F9?style=flat-square\u0026amp;logo=IntelliJ-IDEA\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Google Chrome\" src=\"https://img.shields.io/badge/Google_Chrome-F7DF1E?style=flat-square\u0026amp;logo=Google-Chrome\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"nginx\" src=\"https://img.shields.io/badge/nginx-0C974D?style=flat-square\u0026amp;logo=nginx\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Vue\" src=\"https://img.shields.io/badge/Vue-32CD32?style=flat-square\u0026amp;logo=Vue\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\n\n\n\n\n\n\n\n\u003cfigure\u003e\n    \u003cimg class=\"my-0 rounded-md\" loading=\"lazy\" alt=\"Shell\" src=\"https://img.shields.io/badge/Shell-FA7343?style=flat-square\u0026amp;logo=shell\u0026amp;logoColor=white\"\u003e\n\n  \n\u003c/figure\u003e\n\u003c/p\u003e","title":"About","type":"page"},{"content":"","date":"13 June 2022","externalUrl":null,"permalink":"/en/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"13 June 2022","externalUrl":null,"permalink":"/en/","section":"GradyYoung's Site","summary":"","title":"GradyYoung's Site","type":"page"},{"content":" All articles are here.\n","date":"13 June 2022","externalUrl":null,"permalink":"/en/posts/","section":"Posts","summary":"\u003cblockquote\u003e\n\u003cp\u003eAll articles are here.\u003c/p\u003e\u003c/blockquote\u003e","title":"Posts","type":"posts"},{"content":"","date":"13 June 2022","externalUrl":null,"permalink":"/en/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","externalUrl":null,"permalink":"/en/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/en/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","externalUrl":null,"permalink":"/posts/c343b13e/6eb2df4f/f74e06b3/","section":"文章","summary":"","title":"算法","type":"posts"},{"content":"","externalUrl":null,"permalink":"/posts/c343b13e/300e87c6/","section":"文章","summary":"","title":"计算机操作系统","type":"posts"},{"content":"","externalUrl":null,"permalink":"/posts/c343b13e/9a15b27f/","section":"文章","summary":"","title":"计算机编译原理","type":"posts"}]