GO 库与框架篇

news2025/2/16 3:52:24

1. 需要重点掌握的库和框架?

输入输出: io,ioutil,fmt,bufio
字符处理: strings,bytes,strconv,regex,unicode,json

日期: 定时器-time.Tick,延时器-time.After/time.AfterFunc

数据库: database/sql

单元测试: testing

非类型安全操作: unsafe

同步:sync-同步操作,atomic-原子操作,os/signal-信号

web框架: gin,beego,echo

微服务框架: kit 构建分布式系统微服务,提供服务发现,负载均衡,熔断等功能.

go-zero,web和rpc框架.
go-micro,基于gRPC的微服务框架

2. net/http启动http server的执行过程是怎样的?

2.1 创建http server对象

调用http.NewServeMux()函数创建一个http server对象。
是http请求的多路复用器,用于注册路由和中间件。

http server对象也可以使用http包中内置的DefaultServeMux

2.2 注册路由和中间件

调用http.HandleFunc()或http.Handle()函数来注册路由和中间件。
http.HandleFunc()函数用于注册路由,而http.Handle()函数用于注册中间件。

2.3 启动http服务器,传入监听地址和多路复用器

调用http.ListenAndServe()函数来启动http server。
该函数会监听指定的网络地址和端口,并在接收到请求时调用相应的路由或中间件。

3. go http server如何处理表单输入内容的?

可以通过req下面的4个变量或方法来获取表单提交的值:req.Form,req.PostForm, req.FormValue()和 req.PostFormValue()

req.Form和req.PostForm是map类型,初始值是nil,当调用了 req.FormValue或者 req.PostFormValue方法后,这两个成员变量才被赋值;

req.FormValue可以获取到URL参数以及通过POST,PUT、PATCH方法提交的表单,而 req.PostFormValue无法获取到URL参数.

package main

import (
	"fmt"
	"html/template"
	"net/http"
)

func main() {
	http.HandleFunc("/login", loginHandler)  //设置访问的路由
	err := http.ListenAndServe(":8888", nil) //设置监听的端口
	if err != nil {
		fmt.Println(err)
	}
}

func loginHandler(rep http.ResponseWriter, req *http.Request) {
	if req.Method == http.MethodGet {
		t, err := template.ParseFiles("login.html")
		if err != nil {
			fmt.Fprintf(rep, "err %v", err)
			return
		}
		err = t.Execute(rep, nil)
		if err != nil {
			fmt.Fprintf(rep, "err %v", err)
		}
	} else {
		fmt.Println(" req.Form:", req.Form)                            //req.Form: map[]
		fmt.Println(" req.Form:", req.Form == nil)                     // req.Form: true
		fmt.Println("FormValue:", req.FormValue("username"))           //FormValue: admin
		fmt.Println("PostFormValue", req.PostFormValue("username"))    //PostFormValue admin
		fmt.Println("FormValue:param:", req.FormValue("param"))        //FormValue:param: 123
		fmt.Println("PostFormValue:param", req.PostFormValue("param")) //PostFormValue:param
		fmt.Println(" req.Form:", req.Form)                            // req.Form: map[param:[123] password:[123] username:[admin]]
	}
}

4. go http server如何防止表单重复提交?

4.1 使用隐藏的token或验证码,每次生成表单时生成一个随机的或基于时间的token或验证码,并保存在服务器端(通常是存储到redis等缓存服务器中),然后将其作为隐藏字段添加到表单中.在处理表单数据时,检查token或验证码是否有效,无效则拒绝提交.

4.2 使用Post-Redirect-Get模式,在处理完表单数据后,重定向到另一个页面,这样刷新或返回时不会再次提交表单.

4.3 使用AJAX异步提交表单数据,提交表单后使用js立即禁用提交按钮,也能在一定程度上避免表单的重复提交.

5. Gin中间件的实现原理?

5.1 gin中间件是什么?

是在请求处理过程中执行一些操作的函数,它可以访问请求上下文(context)和响应写入器(writer),可以控制是否继续执行后续的中间件和处理器.

例如gin内置中间件Logger:

func Logger() HandlerFunc {
	return LoggerWithConfig(LoggerConfig{})
}

HandlerFunc类型是入参为gin Context的函数:

type HandlerFunc func(*Context)

总结: Gin中间件本质上就是一个入参为gin Context的函数.

5.2 中间件使用场景?

前端请求到达后端响应接口之前,拦截请求进行相应处理,如权限验证等,类比前置过滤器,
后端响应接口回去之前,拦截请求做处理,添加响应头等,类比后置过滤器.
也就是对请求做些处理,
 

全局中间件: r.Use(gin.Logger())
路由组中间件: r.Group("/api").Use(timeCost)
单个路由中间件引入方式1: r.GET("test",timeCost,func(context *gin.Context){})
单个引入方式2: r.Use(timeCost).GET("test",func(context *gin.Context){})
自定义中间件: func timeCost(c *gin.Context){}

当调用Use方法传入中间件函数时,gin会将传入的中间件函数append到当前路由的Handlers函数中,
也就是gin在一个路由中允许使用多个handler函数,这些handler被存储在一个切片中,根据切片中的顺序来执行这些请求的处理函数,
实际上这些处理函数共同构成了一个处理链,gin还实现了在前一个处理函数中控制是否继续执行下一个处理函数的功能,
而这个功能是通过c.Next()和c.Abort()这两个函数实现的.

c.Next的实现其实是基于路由的Handlers函数的索引下标来实现的,
路由的Handlers函数是个切片,c.Next方法是通过索引来控制执行流程的,
每个handler函数都有一个对应的索引,当调用c.Next方法时,索引会加一,根据索引找到下一个中间件或路由处理函数并执行.

6. Gin是如何实现参数校验的?

gin通过binding标签定义校验规则,本质上是通过反射获取结构体的标签来实现的.

而参数校验的具体实现则是使用的第三方库:github.com/go-playground/validator/v10

7. gin如何加载静态资源?

本质上静态资源文件会通过gin的handler新增一个静态文件的路由,相当于在现有的web server中增加了一个静态文件的路由,提供了静态文件服务的访问能力.

8. 如何使用cron实现定时任务?

8.1 注册定时任务的两种方法

cron 库的 AddFunc 方法,
该方法接受两个参数:时间表达式和任务函数.


时间表达式是一个字符串,用于表示定时任务的执行时间.
任务函数是一个无参无返回值的函数,用于表示定时任务的具体操作.

cron 库的 AddJob 方法
接收两个参数,时间表达式和一个Job的接口的实现,该接口类型定义了一个Run方法.
支持对当前任务做一些设置,比如异常后可以Recover或者设置使用的日志.
执行start后,到了执行时间便会执行定义的Run方法.

设置时间表达式和任务后,需要调用Start()方法来启动任务.也可以在调度过程中使用Stop()停止或者Remove()移除任务.

8.2 提前设置时区

通过调用 time.LoadLocation加载环境变量:
loc, err := time.LoadLocation("Asia/Shanghai")

直接创建一个东八区时间:
loc := time.FixedZone("CST", 8*3600)

在 cron.New时通过cron.WithLocation传入设置好的时区
c := cron.New(cron.WithLocation(loc), cron.WithSeconds())

8.3 时间表达式

使用cron.WithSeconds()配置时,cron.New(),cron占位符有5个,分时日月周,否则加秒有6个.

cron表达式占位符为*号表示每隔时间单位就会执行一次.

比如:

* * * * *:表示每分钟执行一次任务。

0 * * * *:表示每小时执行一次任务。

0 0 * * *:表示每天午夜执行一次任务。

0 0 * * 1:表示每周一午夜执行一次任务。

0 0 1 * *:表示每月第一天午夜执行一次任务

8.4 源码
func (c *Cron) run() {
	c.logger.Info("start")

	// Figure out the next activation times for each entry.
	now := c.now()
	for _, entry := range c.entries {
		entry.Next = entry.Schedule.Next(now)
		c.logger.Info("schedule", "now", now, "entry", entry.ID, "next", entry.Next)
	}

	for {
		// Determine the next entry to run.
		sort.Sort(byTime(c.entries))

		var timer *time.Timer
		if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
			// If there are no entries yet, just sleep - it still handles new entries
			// and stop requests.
			timer = time.NewTimer(100000 * time.Hour)
		} else {
			timer = time.NewTimer(c.entries[0].Next.Sub(now))
		}

		for {
			select {
			case now = <-timer.C:
				now = now.In(c.location)
				c.logger.Info("wake", "now", now)

				// Run every entry whose next time was less than now
				for _, e := range c.entries {
					if e.Next.After(now) || e.Next.IsZero() {
						break
					}
					c.startJob(e.WrappedJob)
					e.Prev = e.Next
					e.Next = e.Schedule.Next(now)
					c.logger.Info("run", "now", now, "entry", e.ID, "next", e.Next)
				}

			case newEntry := <-c.add:
				timer.Stop()
				now = c.now()
				newEntry.Next = newEntry.Schedule.Next(now)
				c.entries = append(c.entries, newEntry)
				c.logger.Info("added", "now", now, "entry", newEntry.ID, "next", newEntry.Next)

			case replyChan := <-c.snapshot:
				replyChan <- c.entrySnapshot()
				continue

			case <-c.stop:
				timer.Stop()
				c.logger.Info("stop")
				return

			case id := <-c.remove:
				timer.Stop()
				now = c.now()
				c.removeEntry(id)
				c.logger.Info("removed", "entry", id)
			}

			break
		}
	}
}

// startJob runs the given job in a new goroutine.
func (c *Cron) startJob(j Job) {
	c.jobWaiter.Add(1)
	go func() {
		defer c.jobWaiter.Done()
		j.Run()
	}()
}

cron定时任务的实现是通过for {}启动了一个常驻进程,结合for select 语句使用timer定时器来监听当前时间是否到了时间表达式设置的时间.

9. 如何使用mergo实现map与struct的互转?

结构体转map: mergo.Map(&map01,struct01),map[string]interface{}
map转结构体: 注意map键要与结构体对象字段对应,才能从map转为结构体,mergo.Map(&struct02,map02)

合并结构体: mergo.Merge(&struct03目标,struct04原),将04中字段复制到03,
必须为同一个结构体类型的实例,当目标结构体中字段是默认值时,会被原结构体的字段覆盖,非默认值则不被覆盖,
若目标结构体字段为小写开头的非导出字段,该字段值不被合并.

合并两个map: mergo.Merge(&map2,map1),将map1中字段复制到map2,当key值相同时,目标map为默认值会被覆盖;两个map中有新字段合并后的也会新增字段,最终字段数量取并集.

mergo的优点:简单易用,灵活可配置,支持自定义转换器.

10. 如何使用ants管理 goroutine ?

ants是一个高性能的goroutine池,实现了对大规模goroutine的调度管理,goroutine复用,在执行一些异步并发任务的时候,可以用来限制goroutine数量,复用资源.

10.1 导致线上服务出现脏数据的情况

goroutine池在for循环中使用,由于异步协程中变量值的读取时机不同,导致最终在协程中获取的变量值不符合预期.

在goroutine传递指针数据,由于异步协程的执行顺序不是绝对可控的,在协程中使用指针数据需要特别留意,比如在协程外有数据修改操作,则协程中读取到的值可能是修改前的值也可能是修改后的值.

10.2 使用细节

使用ants.NewPool函数创建一个Ants池,设置可同时执行的最大goroutine数.
接着,向Ants池提交指定数量任务,每个任务都会模拟一定的处理时间,并增加计数器的值.

需要注意的是,需要等待所有任务执行完成才能退出主协程,否则协程池中的任务会随着主协程退出.
可以轮询调用p.Running()方法判断当前线程池的数量是否为0来判断线程池中所有任务是否已经全部执行完成.

实际开发中使用go关键字启动协程要慎用避免协程泄露.

11. 如何优雅的关闭服务?

当重启或关闭服务时,一些正在执行的任务,比如正在处理的http请求,或者操作数据的数据库或消费队列,优雅关闭是很重要的,直接关系到是否丢数据或是否异常等.

实现原理: 优雅关闭服务的本质是在服务关闭前监听操作系统给服务发的退出信号,暂时拦截退出操作,让服务先执行一些收尾工作.

捕获系统信号:
使用 os/signal 包捕获 SIGINT 和 SIGTERM 信号.
这些信号通常由操作系统发送,表示用户请求终止程序(如通过 Ctrl+C 或 kill 命令).
 

使用上下文控制超时:
使用 context.WithTimeout 创建一个带有超时的上下文,确保服务器在指定时间内完成关闭操作.
如果超时,server.Shutdown 会返回错误,但不会阻塞程序.
 

调用 server.Shutdown:
server.Shutdown 是一个优雅关闭服务器的方法,它会等待所有活跃的请求完成,然后关闭服务器.
 

package main

import (
    "context"
    "fmt"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    // 创建一个 HTTP 服务器
    server := &http.Server{
        Addr:    ":8080",
        Handler: http.HandlerFunc(handleRequest),
    }

    // 启动服务器
    go func() {
        fmt.Println("Starting server on :8080")
        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            fmt.Println("Error starting server:", err)
        }
    }()

    // 捕获系统信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

    // 等待系统信号
    fmt.Println("Waiting for signal to shut down...")
    <-quit
    fmt.Println("Signal received, shutting down gracefully...")

    // 创建一个上下文,用于超时控制
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    // 关闭服务器
    if err := server.Shutdown(ctx); err != nil {
        fmt.Println("Error shutting down server:", err)
    }

    fmt.Println("Server exited gracefully")
}

// handleRequest 是一个简单的 HTTP 处理函数
func handleRequest(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

以上代码示例是原生版的,也有第三方库提供的钩子函数,例如 github.com/goinggo/shutdown.

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"

    "github.com/goinggo/shutdown"
)

func main() {
    // 创建一个关闭钩子
    hook := shutdown.NewHook()

    // 注册清理函数
    hook.Add(func() {
        fmt.Println("Cleaning up resources...")
        // 在这里执行清理操作,例如关闭文件、释放资源等
    })

    // 捕获系统信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

    // 等待系统信号
    fmt.Println("Waiting for signal to shut down...")
    <-quit
    fmt.Println("Signal received, shutting down gracefully...")

    // 触发关闭钩子
    hook.Close()
}

捕获系统信号:
使用 os/signal 包捕获 SIGINT 和 SIGTERM 信号.

注册清理函数:
使用 hook.Add 方法注册多个清理函数,这些函数会在程序退出时被调用.


触发关闭钩子:
调用 hook.Close 方法触发所有注册的清理函数,等待完成.


shutdown.NewHook().Close,通过捕获系统信号并注册清理函数,在监听到操作系统的退出操作信号时会回调钩子函数.可以确保程序在退出时完成所有必要的清理操作,避免资源泄漏和未完成的操作.

细节

在启动服务时需要注意服务间的依赖,在关闭服务时,也要考虑.比如服务通过接收http请求将收到的数据发送到kafka,这个过程中使用redis缓存,那么初始化服务的顺序应该是最后依赖的服务最先被初始化,而关闭流程刚好相反.

12. Go ORM中如何进行事务操作?

回调函数:通过Transaction方法传入一个回调函数,在回调函数中执行一系列数据库操作,如果回调函数返回错误,则回滚事务,否则提交事务.这种方式可以实现嵌套事务,在一个事务中再开启一个事务.
需要注意的是,内层事务需要使用匿名函数中传入的*gorm.DB实例.

常规操作:通过Begin开启事务之后会返回一个事务操作对象*gdb.TX,通过该对象调用Rollback()和Commit() 方法来实现事务的回滚和提交.

以上两种方式都支持通过通过SavePoint和RollbackTo方法设置和回滚到保存点.

13. 如何使用viper实现配置的动态监听?

一些配置使用经验

尽可能避免使用动态配置;

极少更新的配置常量化;

配置值合理性校验.

使用

通过定义对应结构体让读取的配置映射成指定的数据结构.需要使用mapstructure作为序列化标签.

14. Golang中如何使用gRPC?

14.1 GRPC是什么?

gRPC 远程过程调用(RPC)框架,可以在不同的平台和编程语言之间进行通信.

14.2 实现原理:

Protocol Buffers定义IDL文件,自动生成客户端和服务端的代码,并使用HTTP/2传输二进制流实现客户端与服务端的数据交互.


Protocol Buffers是Google开发的一种序列化协议.作为gRPC默认的序列化协议,它可以将结构化的数据序列化为二进制码或者其他格式,并且可以自动生成相应的代码.


正是使用了Protocol Buffers,gRPC可以实现跨语言的通信.
IDL(Interface Definition Language)文件是用来定义服务接口和数据结构的,gRPC中的这些IDL文件可以自动生成客户端和服务端的代码.


gRPC使用HTTP/2作为底层传输协议,HTTP/2可以提供多路复用、服务器推送等功能,提高了传输效率.同时gRPC还使用二进制流来传输数据,相比于文本协议,
二进制协议可以减小传输数据的大小,gRPC支持异步流,可以实现流式的数据传输.

客户端(Client): 服务的调用方.

服务端(Server): 真正的服务提供者.

客户端存根(Client Stub): 存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方.

服务端存根(Server Stub): 接收客户端发送过来的消息,将消息解包,并调用本地的方法.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2285102.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

24-25出差交流体会-25-01-28

简洁版 如果发现自己走下坡路&#xff0c;工资下降等&#xff0c;如何办&#xff1f; &#xff08;环境因素等不在此文讨论范围&#xff0c;个人无法改变大环境。&#xff09; 多思考&#xff0c;是否是自身已经具备的能力在新模式下大幅贬值。 出路只有一条&#xff0c;提升自…

Linux 学习笔记__Day3

十八、设置虚拟机的静态IP 1、VMware的三种网络模式 安装VMware Workstation Pro之后&#xff0c;会在Windows系统中虚拟出两个虚拟网卡&#xff0c;如下&#xff1a; VMware提供了三种网络模式&#xff0c;分别是&#xff1a;桥接模式&#xff08;Bridged&#xff09;、NAT…

SOME/IP--协议英文原文讲解2

前言 SOME/IP协议越来越多的用于汽车电子行业中&#xff0c;关于协议详细完全的中文资料却没有&#xff0c;所以我将结合工作经验并对照英文原版协议做一系列的文章。基本分三大块&#xff1a; 1. SOME/IP协议讲解 2. SOME/IP-SD协议讲解 3. python/C举例调试讲解 4.1 Speci…

JWT实现单点登录

文章目录 JWT实现单点登录JWT 简介存在问题及解决方案登录流程后端程序实现前端保存Tokenstore存放信息的缺点及解决 校验流程&#xff1a;为gateway增加登录校验拦截器 另一种单点登录方法&#xff1a;Token&#xff0b;Redis实现单点登录 JWT实现单点登录 登录流程&#xff…

使用Avalonia UI实现DataGrid

1.Avalonia中的DataGrid的使用 DataGrid 是客户端 UI 中一个非常重要的控件。在 Avalonia 中&#xff0c;DataGrid 是一个独立的包 Avalonia.Controls.DataGrid&#xff0c;因此需要单独通过 NuGet 安装。接下来&#xff0c;将介绍如何安装和使用 DataGrid 控件。 2.安装 Dat…

特权模式docker逃逸

目录 1.环境 2.上线哥斯拉 3.特权模式逃逸 1.判断是否为docker环境 2.判断是否为特权模式 3.挂载宿主机磁盘到docker 4.计划任务反弹shell 1.环境 ubuntu部署一个存在CVE-2017-12615的docker: (ip:192.168.117.147) kali(ip:192.168.117.128) 哥斯拉 2.上线哥斯拉…

Ollama+DeepSeek本地大模型部署

1、Ollama 官网&#xff1a;https://ollama.com/ Ollama可以干什么&#xff1f; 可以快速在本地部署和管理各种大语言模型&#xff0c;操作命令和dokcer类似。 mac安装ollama&#xff1a; # 安装ollama brew install ollama# 启动ollama服务&#xff08;默认11434端口&#xf…

公司的税收日期的确定(OBCK)

本文主要介绍在S4 HANA OP中S4 HANA公司的税收日期的确定(OBCK)相关设置。具体请参照如下内容&#xff1a; 如果税率是基于日期的&#xff0c;那么以上配置点用来确定基于什么日期来确定最终使用的税率。 如果勾选&#xff0c;则代表以“凭证日期”作为税率确定的日期如果不勾…

通过高效的侦察发现关键漏洞接管整个IT基础设施

视频教程在我主页简介或专栏里 在这篇文章中&#xff0c; 我将深入探讨我是如何通过详细分析和利用暴露的端点、硬编码的凭据以及配置错误的访问控制&#xff0c;成功获取目标组织关键IT基础设施和云服务访问权限的全过程。 我们先提到目标网站的名称 https://*sub.domain*.co…

PostGIS笔记:PostgreSQL中表、键和索引的基础操作

创建、查看与删除表 在数据库中创建一个表&#xff0c;使用如下代码&#xff1a; create table streets (id serial not null primary key, name varchar(50));这里的表名是streets&#xff0c;id是主键所以非空&#xff0c;采用serial数据类型&#xff0c;这个数据类型会自动…

Yolo11 + OCR 营业执照识别+信息抽取(预期后续改用其他ocr更简单,推理预计使用onnxruntim加速,分c++和python两种方式部署)

目录 一 数据集制作 1 labelimg的安装与使用 2 标注方式 3 数据集制作 二 模型训练 三 使用Yolo11 + OCR 实现“营业执照”信息解析完整方案 1 cutLinesforcode.py 2 getBusinessLicenseContentPart.py 3 getPartWords.py 4 pdfTojpg.py 5 main.py 本项目可用于毕业…

Linux 学习笔记__Day2

目录 十二、上传和下载文件 十三、软件包的安装和卸载 十四、打包和压缩 1、zip命令 2、tar命令 3、其它打包压缩的命令 十五、Linux进程 1、查看进程 2、终止进程 十六、性能分析top 1、top输出结果说明 2、top常用的选项 3、top交互命令 4、demo01.cpp 5、de…

“腾讯、钉钉、飞书” 会议开源平替,免费功能强大

在数字化时代&#xff0c;远程办公和线上协作越来越火。然而&#xff0c;市面上的视频会议工具要么贵得离谱&#xff0c;要么功能受限&#xff0c;甚至还有些在数据安全和隐私保护上让人不放心。 今天开源君给大家安利一个超棒的开源项目 - Jitsi Meet&#xff0c;这可是我在网…

接口技术-第4次作业

目录 作业内容 解答 1、设8255A接到系统中&#xff0c;端口A、B、C及控制口地址分别为304H、305H、306H及307H&#xff0c;工作在方式0&#xff0c;试编程将端口B的数据输入后&#xff0c;从端口C输出&#xff0c;同时&#xff0c;将其取反后从端口A输出。 2、下图中&#x…

【Elasticsearch】Elasticsearch的查询

Elasticsearch的查询 DSL查询基础语句叶子查询全文检索查询matchmulti_match 精确查询termrange 复合查询算分函数查询bool查询 排序分页基础分页深度分页 高亮高亮原理实现高亮 RestClient查询基础查询叶子查询复合查询排序和分页高亮 数据聚合DSL实现聚合Bucket聚合带条件聚合…

day6手机摄影社区,可以去苹果摄影社区学习拍摄技巧

逛自己手机的社区&#xff1a;即&#xff08;手机牌子&#xff09;摄影社区 拍照时防止抖动可以控制自己的呼吸&#xff0c;不要大喘气 拍一张照片后&#xff0c;如何简单的用手机修图&#xff1f; HDR模式就是让高光部分和阴影部分更协调&#xff08;拍风紧时可以打开&…

Linux - 进程间通信(2)

目录 2、进程池 1&#xff09;理解进程池 2&#xff09;进程池的实现 整体框架&#xff1a; a. 加载任务 b. 先描述&#xff0c;再组织 I. 先描述 II. 再组织 c. 创建信道和子进程 d. 通过channel控制子进程 e. 回收管道和子进程 问题1&#xff1a; 解答1&#xff…

langchain基础(二)

一、输出解析器&#xff08;Output Parser&#xff09; 作用&#xff1a;&#xff08;1&#xff09;让模型按照指定的格式输出&#xff1b; &#xff08;2&#xff09;解析模型输出&#xff0c;提取所需的信息 1、逗号分隔列表 CommaSeparatedListOutputParser&#xff1a;…

解除阿里云盘压缩包分享限制的最新工具(2025年更新)

前言 前段时间&#xff0c;为了在阿里云盘分享一些资料&#xff0c;尝试了好多种方法&#xff1a;改文件名后缀&#xff0c;打包自解压&#xff0c;使用将压缩文件追加在图片文件后&#xff0c;还有的一些工具&#xff0c;虽然能伪装文件但并不太好用&#xff0c;最后自己写了…

2025神奇的数字—新年快乐

2025年&#xff0c;一个神奇的数字&#xff0c;承载着数学的奥秘与无限可能。它是45的平方&#xff08;45&#xff09;&#xff0c;上一个这样的年份是1936年&#xff08;44&#xff09;&#xff0c;下一个则是2116年&#xff08;46&#xff09;&#xff0c;一生仅此一次。2025…