Golang 开发SSE流接口,实现ChatGpt逐字打印效果
一直来没时间优化一下我这个gpt,因为最开始弄这个设计成所有数据请求完之后一次性返给前端,这样就需要等gpt处理完所有数据,消耗的时间比较多。所以造成的结果就是请求一次对话需要等待5-15秒不等。实在是影响体验性。这两天稍微有点时间,就琢磨把gpt接口改成一个字一个字或者一段话一段话输出,在网上查找了一些资料,发现有很多处理方式,比如Websocket,SSE之类的,最常见的还是SSE,因为它没有消耗太多资源,对于这样低配置服务器来说是最好的处理方式了。好,接下里我直接上代码,查找资料过程略过。后端:我这里拉Gin服务,其他也行。SSE流接口处理:(流接口主要是三个地方调整)Content-Type":"text/event-stream"Cache-Control":"no-cache"Connection":"keep-alive"package main import ( "time" "github.com/gin-gonic/gin" ) func main() { // 创建路由 router := gin.Default() router.Any("/stream", func(c *gin.Context) { c.Header("Content-Type", "text/event-stream") c.Header("Cache-Control", "no-cache") c.Header("Connection", "keep-alive") c.SSEvent("start", "start") for i := 0; i < 10; i++ { c.Writer.WriteString("data: SSE data\n\n") if i == 9 { c.SSEvent("end", "end") } c.Writer.Flush() time.Sleep(10 * time.Millisecond) } }) // 启动服务 router.Run(":8080") } 前端:(我是在nuxt3框架中处理的,你也可以直接在html页面处理一样的方法,直接copy)<div> <ul> <li> {{ messageStr }} </li> </ul> <div @click="clickHandle">点我</div> </div> </template> <script setup> import { reactive, onMounted, onBeforeUnmount } from "vue"; let messageStr = ref(""); let getData = ()=>{ const sseSource = new window.EventSource('/ccc/stream'); sseSource.addEventListener('message', (event) => { console.log(event.data); messageStr.value = messageStr.value+event.data; }); sseSource.addEventListener('end', (event) => { sseSource.close(); }); } let clickHandle = ()=>{ getData(); }好了,前后端已完成,是不是很简单。现在我们来看下测试效果。
查看详情点赞23评论收藏4浏览13622023-10-20 10:13:27Go 接入RabbitMq实操
基本概念什么是消息队列消息队列是一种应用(进程)间的通信方式。生产者只需把消息发布到MQ,消费者只需重MQ中取出,可靠传递由消息队列中的消息系统来确保。消息队列有什么用消息队列是一种异步协作机制,最根本的用处在于将一些不需要即时生效的操作拆分出来异步执行,从而达到可靠传递、流量削峰等目的。比如如果有一个业务需要发送短信,可以在主流程完成之后发送消息到MQ后,让主流程完结。而由另外的线程拉取MQ的消息,完成发送短信的操作。常用的消息队列常用的MQ大概有ActiveMQ、RabbitMQ、RocketMQ、KafkaActiveMQ,基于Java优点:对Java的JMS支持最好;多线程并发;缺点:历史悠久,版本更新慢。现在慢慢用的少了;RabbitMQ,基于Erlang优点:生态丰富,是现在主流的MQ;支持多种客户端、支持AJAX;缺点:对想深入源码的Java选手不太友好;RocketMQ,基于Java优点:为海量数据打造;主张拉模式;天然集群、HA、负载均衡;缺点:生态较小Kafka,基于Scala优点:分布式高可拓展;高性能;容错强缺点:消息重复;乱序;维护成本高什么是RabbitMQ消息中间件erlang:一种并发函数式语言AMQP:Advanced Message Queuing Protocol,高级消息队列协议。由Exchange、Queue和Bind组成RabbitMQ是一个erlang开发的AMQP实现生产者将消息发送到Exchange上,通过Exchange从而Binding到Queues上。Exchange有三种具体类型:direct:如果消息中的RoutingKey和Binding中的BindingKey一致就转发fanout:消息被分发到所有队列中topic:将RoutingKey和队列的模式进行匹配应用场景异步可以理解为将遇到非必须的业务时,立即响应客户端,不关系业务何时完成比如在用户注册时,有将信息写入数据库和发送注册成功邮件两项业务。数据库写入完成即标志着用户注册成功,此时如果继续处理发送邮件的业务,会给客户端带来不必要的等待时间。引入消息队列后,在队列中写入完成注册的消息后,即可完成整个注册流程。至于邮件,可以等到邮件业务从消息队列中取出消息再发送。把不紧急的业务从主线中剥离出来,主线不必考虑不紧急的业务何时完成的时候,可以考虑使用消息队列实现异步。解耦考虑两个系统间存在消息传递,一个系统的故障会影响到整个业务的正常运转。可以用消息队列来保证消息可靠传递比如一个订单系统和一个库存系统,完成订单之后,需要进行库存调度。考虑到如果库存系统故障,会引起已完成的订单消息的丢失,而做很多异常处理会使业务变得臃肿。这个时候可考虑引入消息队列,使用消息队列保证可靠传输,从而减少业务逻辑。削峰考虑短时间的大量请求,可能会带来内存溢出、大面积连接超时等情况,使得服务器崩溃。引入消息队列后,可以控制请求到业务处理系统的流量,从而防止崩溃现象的出现。比如秒杀场景。大量请求同时涌入,服务器不能分配足够的资源响应,或者带宽不足,导致宕机。可以引入消息队列来限流,MQ通过限制同一时间的出口消息,使得流量在服务器能够承受的范围之内。等待一部分请求处理完成之后,再向业务处理系统导入新的消息。----------------------------------------------------------------------------------------------------------------------------------------昨天我们用docker在虚拟机上装了RabbitMq,今天我们就开始用它来实际操作一下。不说废话了,我们开始搞,我这里用的是Go语言。首先,我们先封装方法:package rabbitmq import ( "fmt" "github.com/streadway/amqp" ) type RabbitMq struct { Conn *amqp.Connection Ch *amqp.Channel QueueName string // 队列名称 ExchangeName string // 交换机名称 ExchangeType string // 交换机类型 RoutingKey string // routingKey } type QueueAndExchange struct { QueueName string // 队列名称 ExchangeName string // 交换机名称 ExchangeType string // 交换机类型 RoutingKey string // routingKey } func (r *RabbitMq) ConnMq() { conn, err := amqp.Dial("amqp://admin:123456@192.168.11.66:5672/my_vhost") if err != nil { fmt.Printf("连接mq出错,错误信息为:%v\n", err) return } r.Conn = conn } func (r *RabbitMq) CloseMq() { err := r.Conn.Close() if err != nil { fmt.Printf("关闭连接出错,错误信息为:%v\n", err) return } } // 开启channel通道 func (r *RabbitMq) OpenChan() { ch, err := r.Conn.Channel() if err != nil { fmt.Printf("开启channel通道出错,错误信息为:%v\n", err) return } r.Ch = ch } // 关闭channnel通道 func (r *RabbitMq) CloseChan() { err := r.Ch.Close() if err != nil { fmt.Printf("关闭channel通道出错,错误信息为:%v\n", err) } } // 生产者 func (r *RabbitMq) PublishMsg(body string) { ch := r.Ch // 创建队列 ch.QueueDeclare(r.QueueName, true, false, false, false, nil) // 创建交换机 ch.ExchangeDeclare(r.ExchangeName, r.ExchangeType, true, false, false, false, nil) // 队列绑定交换机 ch.QueueBind(r.QueueName, r.RoutingKey, r.ExchangeName, false, nil) // 生产任务 ch.Publish(r.ExchangeName, r.RoutingKey, false, false, amqp.Publishing{ ContentType: "text/plain", Body: []byte(body), DeliveryMode: amqp.Persistent, }) } // 创建实例 func NewRabbitMq(qe QueueAndExchange) RabbitMq { return RabbitMq{ QueueName: qe.QueueName, ExchangeName: qe.ExchangeName, ExchangeType: qe.ExchangeType, RoutingKey: qe.RoutingKey, } } 接下来我们就我们创建的生产者发送消息:package main import ( "test_rabbitmq/rabbitmq" "test_rabbitmq/utils" ) func main() { qe := rabbitmq.QueueAndExchange{ QueueName: "test_queue", ExchangeName: "test_exchange", ExchangeType: "direct", RoutingKey: "test_routingKey", } mq := rabbitmq.NewRabbitMq(qe) mq.ConnMq() mq.OpenChan() defer func() { mq.CloseMq() }() defer func() { mq.CloseChan() }() test_map := map[string]interface{}{ "mail": "9527@qq.com", "msg": "今天大太阳", } //这里我们发送100条消息 for i := 0; i < 100; i++ { mq.PublishMsg(utils.MapToStr(test_map)) } } 我们可以看到,发送的消息已经在队列当中了。然后我们开始消费消息:package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/streadway/amqp" ) func main() { router := gin.Default() router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "Hello World") }) router.Run(":8081") } func init() { conn, err := amqp.Dial("amqp://admin:123456@192.168.11.66:5672/my_vhost") fmt.Println(err) defer conn.Close() ch, err_ch := conn.Channel() fmt.Println(err_ch) defer ch.Close() ch.Qos(1, 0, false) deliveries, err := ch.Consume("test_queue", "consumer", false, false, false, false, nil) if err != nil { fmt.Println(err) } //消费成功delivery.Ack(true) for delivery := range deliveries { delivery.Ack(true) body := string(delivery.Body) fmt.Println(body) fmt.Printf("%T\n", body) } } 然后我们可以看到数据已经取出来了,队列的消息也已经消费了。
查看详情点赞5评论收藏2浏览822023-05-24 10:27:24Go 生成静态二维码和动态二维码
在我们生活和工作中,会遇到很多需要用到二维码的地方。二维码在很多场合使用,比如打开链接,扫码支付,扫码打开链接,扫码加好友等等。今天我们使用go来生成一下二维码试试,我们可以用这个库来生成。github.com/skip2/go-qrcode1、生成静态二维码比如我们经常用到的,扫码打开一个网页。package main import ( "image/color" "log" "github.com/skip2/go-qrcode" ) func main() { qr, err := qrcode.New("http://www.alingfeng.cn/", qrcode.Medium) if err != nil { log.Fatal(err) } else { qr.BackgroundColor = color.RGBA{255, 255, 255, 255} qr.ForegroundColor = color.Black qr.WriteFile(256, "./go_code.png") } }运行一下,就可以看到我们的二维码图片2、生成动态二维码也有很多时候我们会用到动态的二维码,如扫码支付,扫码打开链接,扫码加好友等,结合http 库,动态生成qrcode 并返回。而无需保存成图片。package main import ( "fmt" "log" "net/http" "time" "github.com/skip2/go-qrcode" ) func main() { http.HandleFunc("/qrcode", Qrcode) log.Fatal(http.ListenAndServe(":8008", nil)) } func Qrcode(w http.ResponseWriter, req *http.Request) { var err error defer func() { if err != nil { w.WriteHeader(500) return } }() q, err := qrcode.New(fmt.Sprintf("http://www.alingfeng.cn/?t=%d", time.Now().Unix()), qrcode.Medium) if err != nil { return } png, err := q.PNG(256) if err != nil { return } w.Header().Set("Content-Type", "image/png") w.Header().Set("Content-Length", fmt.Sprintf("%d", len(png))) w.Write(png) }然后我们访问地址就可以看到图片: 127.0.0.1:8008/qrcode,每刷新一下二维码会相应变化。
查看详情点赞2评论收藏1浏览742023-06-12 16:12:55go 发送邮件功能
上一篇 go-zero 数字图形验证码功能 之后,接下来我们要开发一个发送邮件的功能。框架还是go-zero,好吧,我们开始撸!1、引包:我们用到的是这个包(github.com/jordan-wright/email)github.com/jordan-wright/email2、新建一个邮件发送的工具:新建一个utils文件加,然后创建mail.go文件直接撸代码:我用的是163邮箱,开干之前。需要在邮箱进行配置。具体步骤百度一下或者联系我沟通。package utils import ( "crypto/tls" "fmt" "net/smtp" "strings" "github.com/jordan-wright/email" ) func Email(To, subject string, body string) error { to := strings.Split(To, ",") return send(to, subject, body) } func send(to []string, subject string, body string) error { from := "这里是发件人邮箱地址,需要自己配置" nickname := "这里是名称" secret := "秘钥" host := "smtp.163.com" port := 465 isSSL := true auth := smtp.PlainAuth("", from, secret, host) e := email.NewEmail() if nickname != "" { e.From = fmt.Sprintf("%s <%s>", nickname, from) } else { e.From = from } e.To = to e.Subject = subject e.HTML = []byte(body) var err error hostAddr := fmt.Sprintf("%s:%d", host, port) if isSSL { err = e.SendWithTLS(hostAddr, auth, &tls.Config{ServerName: host}) } else { err = e.Send(hostAddr, auth) } return err } OK,工具方法已经好了。我们开始引用:utils.Email(in.Email, "xxx", "您好,欢迎加入xxxxx!邮箱已发送,祝您使用愉快!")
查看详情点赞1评论收藏1浏览752023-03-03 16:22:48Go 强大的时间处理库之一,go-carbon
Carboncarbon 是一个轻量级、语义化、对开发者友好的 Golang 时间处理库,支持链式调用和 gorm、xorm、zorm 等主流 orm。安装使用// 1、使用 github 库 go get -u github.com/golang-module/carbon import ( "github.com/golang-module/carbon" ) ///2、使用 gitee 库 go get -u gitee.com/go-package/carbon import ( "gitee.com/go-package/carbon" )默认时区为 Local,即服务器所在时区,假设当前时间为 2020-08-05 13:14:15//昨天、今天、明天 // 今天此刻 carbon.Now().ToDateTimeString() // 2020-08-05 13:14:15 // 今天日期 carbon.Now().ToDateString() // 2020-08-05 // 今天时间 carbon.Now().ToTimeString() // 13:14:15 // 今天秒级时间戳 carbon.Now().ToTimestamp() // 1596604455 carbon.Now().ToTimestampWithSecond() // 1596604455 // 今天毫秒级时间戳 carbon.Now().ToTimestampWithMillisecond() // 1596604455000 // 今天微秒级时间戳 carbon.Now().ToTimestampWithMicrosecond() // 1596604455000000 // 今天纳秒级时间戳 carbon.Now().ToTimestampWithNanosecond() // 1596604455000000000 // 指定时区的今天此刻 carbon.SetTimezone(Carbon.NewYork).Now().ToDateTimeString() // 2020-08-05 01:14:15 // 昨天此刻 carbon.Yesterday().ToDateTimeString() // 2020-08-04 13:14:15 // 昨天日期 carbon.Yesterday().ToDateString() // 2020-08-04 // 昨天时间 carbon.Yesterday().ToTimeString() // 13:14:15 // 昨天秒级时间戳 carbon.Yesterday().ToTimestamp() // 1596518055 carbon.Yesterday().ToTimestampWithSecond() // 1596518055 // 明天毫秒级时间戳 carbon.Yesterday().ToTimestampWithMillisecond() // 1596518055000 // 明天微秒级时间戳 carbon.Yesterday().ToTimestampWithMicrosecond() // 1596518055000000 // 明天纳秒级时间戳 carbon.Yesterday().ToTimestampWithNanosecond() // 1596518055000000000 // 指定时区的昨天此刻 carbon.SetTimezone(Carbon.NewYork).Yesterday().ToDateTimeString() // 2020-08-04 01:14:15 // 指定日期的昨天此刻 carbon.Parse("2021-01-28 13:14:15").Yesterday().ToDateTimeString() // 2021-01-27 13:14:15 // 明天此刻 carbon.Tomorrow().ToDateTimeString() // 2020-08-06 13:14:15 // 明天日期 carbon.Tomorrow().ToDateString() // 2020-08-06 // 明天时间 carbon.Tomorrow().ToTimeString() // 13:14:15 // 明天秒级时间戳 carbon.Tomorrow().ToTimestamp() // 1596690855 carbon.Tomorrow().ToTimestampWithSecond() // 1596690855 // 明天毫秒级时间戳 carbon.Tomorrow().ToTimestampWithMillisecond() // 1596690855000 // 明天微秒级时间戳 carbon.Tomorrow().ToTimestampWithMicrosecond() // 1596690855000000 // 明天纳秒级时间戳 carbon.Tomorrow().ToTimestampWithNanosecond() // 1596690855000000000 // 指定时区的明天此刻 carbon.SetTimezone(Carbon.NewYork).Tomorrow().ToDateTimeString() // 2020-08-06 01:14:15 // 指定日期的明天此刻 carbon.Parse("2021-01-28 13:14:15").Tomorrow().ToDateTimeString() // 2021-01-29 13:14:15 //创建 Carbon 实例 // 从秒级时间戳创建 Carbon 实例 carbon.CreateFromTimestamp(1596604455).ToDateTimeString() // 2020-08-05 13:14:15 // 从毫秒级时间戳创建 Carbon 实例 carbon.CreateFromTimestamp(1596604455000).ToDateTimeString() // 2020-08-05 13:14:15 // 从微秒级时间戳创建 Carbon 实例 carbon.CreateFromTimestamp(1596604455000000).ToDateTimeString() // 2020-08-05 13:14:15 // 从纳级时间戳创建 Carbon 实例 carbon.CreateFromTimestamp(1596604455000000000).ToDateTimeString() // 2020-08-05 13:14:15 // 从年月日时分秒创建 Carbon 实例 carbon.CreateFromDateTime(2020, 8, 5, 13, 14, 15).ToDateTimeString() // 2020-08-05 13:14:15 // 从年月日创建 Carbon 实例(时分秒默认为当前时分秒) carbon.CreateFromDate(2020, 8, 5).ToDateTimeString() // 2020-08-05 13:14:15 // 从时分秒创建 Carbon 实例(年月日默认为当前年月日) carbon.CreateFromTime(13, 14, 15).ToDateTimeString() // 2020-08-05 13:14:15 //将标准格式时间字符串解析成 Carbon 实例 carbon.Parse("").ToDateTimeString() // 空字符串 carbon.Parse("0000-00-00 00:00:00").ToDateTimeString() // 空字符串 carbon.Parse("0000-00-00").ToDateTimeString() // 空字符串 carbon.Parse("2020-08-05 13:14:15").ToDateTimeString() // 2020-08-05 13:14:15 carbon.Parse("2020-08-05").ToDateTimeString() // 2020-08-05 00:00:00 carbon.Parse("20200805131415").ToDateTimeString() // 2020-08-05 13:14:15 carbon.Parse("20200805").ToDateTimeString() // 2020-08-05 00:00:00 carbon.Parse("2020-08-05T13:14:15+08:00").ToDateTimeString() // 2020-08-05 00:00:00 //将特殊格式时间字符串解析成 Carbon 实例 carbon.ParseByFormat("2020|08|05 13|14|15", "Y|m|d H|i|s").ToDateTimeString // 2020-08-05 13:14:15 carbon.ParseByFormat("It is 2020-08-05 13:14:15", "It is Y-m-d H:i:s").ToDateTimeString // 2020-08-05 13:14:15 carbon.ParseByFormat("今天是 2020年08月05日13时14分15秒", "今天是 Y年m月d日H时i分s秒").ToDateTimeString // 2020-08-05 13:14:15 //将布局时间字符串解析成 Carbon 实例 carbon.ParseByLayout("2020|08|05 13|14|15", "2006|01|02 15:04:05").ToDateTimeString // 2020-08-05 13:14:15 carbon.ParseByLayout("It is 2020-08-05 13:14:15", "It is 2006-01-02 15:04:05").ToDateTimeString // 2020-08-05 13:14:15 carbon.ParseByLayout("今天是 2020年08月05日13时14分15秒", "今天是 2006年01月02日15时04分05秒").ToDateTimeString() // 2020-08-05 13:14:15 //carbon和time.Time互转 // 将 time.Time 转换成 Carbon carbon.Time2Carbon(time.Now()) // 将 Carbon 转换成 time.Time carbon.Now().Carbon2Time() 或 carbon.Now().Time //时间设置 // 设置时区 carbon.SetTimezone(carbon.PRC).Now().ToDateTimeString() // 2020-08-05 13:14:15 carbon.SetTimezone(carbon.Tokyo).Now().ToDateTimeString() // 2020-08-05 14:14:15 carbon.SetTimezone(carbon.Tokyo).SetTimezone(carbon.PRC).Now().ToDateTimeString() // 2020-08-05 13:14:15 // 设置年 carbon.Parse("2019-08-05").SetYear(2020).ToDateString() // 2020-08-05 carbon.Parse("2020-02-29").SetYear(2019).ToDateString() // 2019-03-01 // 设置月 carbon.Parse("2020-01-31").SetMonth(2).ToDateString() // 2020-03-02 carbon.Parse("2020-08-05").SetMonth(2).ToDateString() // 2020-02-05 // 设置日 carbon.Parse("2019-08-05").SetDay(31).ToDateString() // 2020-08-31 carbon.Parse("2020-02-01").SetDay(31).ToDateString() // 2020-03-02 // 设置时 carbon.Parse("2020-08-05 13:14:15").SetHour(10).ToDateTimeString() // 2020-08-05 10:14:15 carbon.Parse("2020-08-05 13:14:15").SetHour(24).ToDateTimeString() // 2020-08-06 00:14:15 // 设置分 carbon.Parse("2020-08-05 13:14:15").SetMinute(10).ToDateTimeString() // 2020-08-05 13:10:15 carbon.Parse("2020-08-05 13:14:15").SetMinute(60).ToDateTimeString() // 2020-08-05 14:00:15 // 设置秒 carbon.Parse("2020-08-05 13:14:15").SetSecond(10).ToDateTimeString() // 2020-08-05 13:14:10 carbon.Parse("2020-08-05 13:14:15").SetSecond(60).ToDateTimeString() // 2020-08-05 13:15:00 //开始时间、结束时间 // 本年开始时间 carbon.Parse("2020-08-05 13:14:15").StartOfYear().ToDateTimeString() // 2020-01-01 00:00:00 // 本年结束时间 carbon.Parse("2020-08-05 13:14:15").EndOfYear().ToDateTimeString() // 2020-12-31 23:59:59 // 本月开始时间 carbon.Parse("2020-08-05 13:14:15").StartOfMonth().ToDateTimeString() // 2020-08-01 00:00:00 // 本月结束时间 carbon.Parse("2020-08-05 13:14:15").EndOfMonth().ToDateTimeString() // 2020-08-31 23:59:59 // 本周开始时间 carbon.Parse("2020-08-05 13:14:15").StartOfWeek().ToDateTimeString() // 2020-08-03 00:00:00 // 本周结束时间 carbon.Parse("2020-08-05 13:14:15").EndOfWeek().ToDateTimeString() // 2020-08-09 23:59:59 // 本日开始时间 carbon.Parse("2020-08-05 13:14:15").StartOfDay().ToDateTimeString() // 2020-08-05 00:00:00 // 本日结束时间 carbon.Parse("2020-08-05 13:14:15").EndOfDay().ToDateTimeString() // 2020-08-05 23:59:59 // 本小时开始时间 carbon.Parse("2020-08-05 13:14:15").StartOfHour().ToDateTimeString() // 2020-08-05 13:00:00 // 本小时结束时间 carbon.Parse("2020-08-05 13:14:15").EndOfHour().ToDateTimeString() // 2020-08-05 13:59:59 // 本分钟开始时间 carbon.Parse("2020-08-05 13:14:15").StartOfMinute().ToDateTimeString() // 2020-08-05 13:14:00 // 本分钟结束时间 carbon.Parse("2020-08-05 13:14:15").EndOfMinute().ToDateTimeString() // 2020-08-05 13:14:59 //时间旅行 // 三世纪后 carbon.Parse("2020-02-29 13:14:15").AddCenturies(3).ToDateTimeString() // 2320-02-29 13:14:15 // 三世纪后(月份不溢出) carbon.Parse("2020-02-29 13:14:15").AddCenturiesNoOverflow(3).ToDateTimeString() // 2320-02-29 13:14:15 // 一世纪后 carbon.Parse("2020-02-29 13:14:15").AddCentury().ToDateTimeString() // 2120-02-29 13:14:15 // 一世纪后(月份不溢出) carbon.Parse("2020-02-29 13:14:15").AddCenturyNoOverflow().ToDateTimeString() // 2120-02-29 13:14:15 // 三世纪前 carbon.Parse("2020-02-29 13:14:15").SubCenturies(3).ToDateTimeString() // 1720-02-29 13:14:15 // 三世纪前(月份不溢出) carbon.Parse("2020-02-29 13:14:15").SubCenturiesNoOverflow(3).ToDateTimeString() // 1720-02-29 13:14:15 // 一世纪前 carbon.Parse("2020-02-29 13:14:15").SubCentury().ToDateTimeString() // 1920-02-29 13:14:15 // 一世纪前(月份不溢出) carbon.Parse("2020-02-29 13:14:15").SubCenturyNoOverflow().ToDateTimeString() // 1920-02-29 13:14:15 // 三年后 carbon.Parse("2020-02-29 13:14:15").AddYears(3).ToDateTimeString() // 2023-03-01 13:14:15 // 三年后(月份不溢出) carbon.Parse("2020-02-29 13:14:15").AddYearsNoOverflow(3).ToDateTimeString() // 2023-02-28 13:14:15 // 一年后 carbon.Parse("2020-02-29 13:14:15").AddYear().ToDateTimeString() // 2021-03-01 13:14:15 // 一年后(月份不溢出) carbon.Parse("2020-02-29 13:14:15").AddYearNoOverflow().ToDateTimeString() // 2021-02-28 13:14:15 // 三年前 carbon.Parse("2020-02-29 13:14:15").SubYears(3).ToDateTimeString() // 2017-03-01 13:14:15 // 三年前(月份不溢出) carbon.Parse("2020-02-29 13:14:15").SubYearsNoOverflow(3).ToDateTimeString() // 2017-02-28 13:14:15 // 一年前 carbon.Parse("2020-02-29 13:14:15").SubYear().ToDateTimeString() // 2019-03-01 13:14:15 // 一年前(月份不溢出) carbon.Parse("2020-02-29 13:14:15").SubYearNoOverflow().ToDateTimeString() // 2019-02-28 13:14:15 // 三季度后 carbon.Parse("2019-08-31 13:14:15").AddQuarters(3).ToDateTimeString() // 2019-03-02 13:14:15 // 三季度后(月份不溢出) carbon.Parse("2019-08-31 13:14:15").AddQuartersNoOverflow(3).ToDateTimeString() // 2019-02-29 13:14:15 // 一季度后 carbon.Parse("2019-11-30 13:14:15").AddQuarter().ToDateTimeString() // 2020-03-01 13:14:15 // 一季度后(月份不溢出) carbon.Parse("2019-11-30 13:14:15").AddQuarterNoOverflow().ToDateTimeString() // 2020-02-29 13:14:15 // 三季度前 carbon.Parse("2019-08-31 13:14:15").SubQuarters(3).ToDateTimeString() // 2019-03-03 13:14:15 // 三季度前(月份不溢出) carbon.Parse("2019-08-31 13:14:15").SubQuartersNoOverflow(3).ToDateTimeString() // 2019-02-28 13:14:15 // 一季度前 carbon.Parse("2020-05-31 13:14:15").SubQuarter().ToDateTimeString() // 2020-03-02 13:14:15 // 一季度前(月份不溢出) carbon.Parse("2020-05-31 13:14:15").SubQuarterNoOverflow().ToDateTimeString() // 2020-02-29 13:14:15 // 三月后 carbon.Parse("2020-02-29 13:14:15").AddMonths(3).ToDateTimeString() // 2020-05-29 13:14:15 // 三月后(月份不溢出) carbon.Parse("2020-02-29 13:14:15").AddMonthsNoOverflow(3).ToDateTimeString() // 2020-05-29 13:14:15 // 一月后 carbon.Parse("2020-01-31 13:14:15").AddMonth().ToDateTimeString() // 2020-03-02 13:14:15 // 一月后(月份不溢出) carbon.Parse("2020-01-31 13:14:15").AddMonthNoOverflow().ToDateTimeString() // 2020-02-29 13:14:15 // 三月前 carbon.Parse("2020-02-29 13:14:15").SubMonths(3).ToDateTimeString() // 2019-11-29 13:14:15 // 三月前(月份不溢出) carbon.Parse("2020-02-29 13:14:15").SubMonthsNoOverflow(3).ToDateTimeString() // 2019-11-29 13:14:15 // 一月前 carbon.Parse("2020-03-31 13:14:15").SubMonth().ToDateTimeString() // 2020-03-02 13:14:15 // 一月前(月份不溢出) carbon.Parse("2020-03-31 13:14:15").SubMonthNoOverflow().ToDateTimeString() // 2020-02-29 13:14:15 // 三周后 carbon.Parse("2020-02-29 13:14:15").AddWeeks(3).ToDateTimeString() // 2020-03-21 13:14:15 // 一周后 carbon.Parse("2020-02-29 13:14:15").AddWeek().ToDateTimeString() // 2020-03-07 13:14:15 // 三周前 carbon.Parse("2020-02-29 13:14:15").SubWeeks(3).ToDateTimeString() // 2020-02-08 13:14:15 // 一周前 carbon.Parse("2020-02-29 13:14:15").SubWeek().ToDateTimeString() // 2020-02-22 13:14:15 // 三天后 carbon.Parse("2020-08-05 13:14:15").AddDays(3).ToDateTimeString() // 2020-08-08 13:14:15 // 一天后 carbon.Parse("2020-08-05 13:14:15").AddDay().ToDateTimeString() // 2020-08-05 13:14:15 // 三天前 carbon.Parse("2020-08-05 13:14:15").SubDays(3).ToDateTimeString() // 2020-08-02 13:14:15 // 一天前 carbon.Parse("2020-08-05 13:14:15").SubDay().ToDateTimeString() // 2020-08-04 13:14:15 // 三小时后 carbon.Parse("2020-08-05 13:14:15").AddHours(3).ToDateTimeString() // 2020-08-05 16:14:15 // 二小时半后 carbon.Parse("2020-08-05 13:14:15").AddDuration("2.5h").ToDateTimeString() // 2020-08-05 15:44:15 carbon.Parse("2020-08-05 13:14:15").AddDuration("2h30m").ToDateTimeString() // 2020-08-05 15:44:15 // 一小时后 carbon.Parse("2020-08-05 13:14:15").AddHour().ToDateTimeString() // 2020-08-05 14:14:15 // 三小时前 carbon.Parse("2020-08-05 13:14:15").SubHours(3).ToDateTimeString() // 2020-08-05 10:14:15 // 二小时半前 carbon.Parse("2020-08-05 13:14:15").SubDuration("2.5h").ToDateTimeString() // 2020-08-05 10:44:15 carbon.Parse("2020-08-05 13:14:15").SubDuration("2h30m").ToDateTimeString() // 2020-08-05 10:44:15 // 一小时前 carbon.Parse("2020-08-05 13:14:15").SubHour().ToDateTimeString() // 2020-08-05 12:14:15 // 三分钟后 carbon.Parse("2020-08-05 13:14:15").AddMinutes(3).ToDateTimeString() // 2020-08-05 13:17:15 // 二分钟半后 carbon.Parse("2020-08-05 13:14:15").AddDuration("2.5m").ToDateTimeString() // 2020-08-05 13:16:45 carbon.Parse("2020-08-05 13:14:15").AddDuration("2m30s").ToDateTimeString() // 2020-08-05 13:16:45 // 一分钟后 carbon.Parse("2020-08-05 13:14:15").AddMinute().ToDateTimeString() // 2020-08-05 13:15:15 // 三分钟前 carbon.Parse("2020-08-05 13:14:15").SubMinutes(3).ToDateTimeString() // 2020-08-05 13:11:15 // 二分钟半前 carbon.Parse("2020-08-05 13:14:15").SubDuration("2.5m").ToDateTimeString() // 2020-08-05 13:11:45 carbon.Parse("2020-08-05 13:14:15").SubDuration("2m30s").ToDateTimeString() // 2020-08-05 13:11:45 // 一分钟前 carbon.Parse("2020-08-05 13:14:15").SubMinute().ToDateTimeString() // 2020-08-05 13:13:15 // 三秒钟后 carbon.Parse("2020-08-05 13:14:15").AddSeconds(3).ToDateTimeString() // 2020-08-05 13:14:18 // 二秒钟半后 carbon.Parse("2020-08-05 13:14:15").AddDuration("2.5s").ToDateTimeString() // 2020-08-05 13:14:17 // 一秒钟后 carbon.Parse("2020-08-05 13:14:15").AddSecond().ToDateTimeString() // 2020-08-05 13:14:16 // 三秒钟前 carbon.Parse("2020-08-05 13:14:15").SubSeconds(3).ToDateTimeString() // 2020-08-05 13:14:12 // 二秒钟半前 carbon.Parse("2020-08-05 13:14:15").SubDuration("2.5s").ToDateTimeString() // 2020-08-05 13:14:12 // 一秒钟前 carbon.Parse("2020-08-05 13:14:15").SubSecond().ToDateTimeString() // 2020-08-05 13:14:14 //时间差 // 相差多少周 carbon.Parse("2020-08-05 13:14:15").DiffInWeeks(carbon.Parse("2020-07-28 13:14:15")) // -1 // 相差多少周(绝对值) carbon.Parse("2020-08-05 13:14:15").DiffInWeeksWithAbs(carbon.Parse("2020-07-28 13:14:15")) // 1 // 相差多少天 carbon.Parse("2020-08-05 13:14:15").DiffInDays(carbon.Parse("2020-08-04 13:14:15")) // -1 // 相差多少天(绝对值) carbon.Parse("2020-08-05 13:14:15").DiffInDaysWithAbs(carbon.Parse("2020-08-04 13:14:15")) // 1 // 相差多少小时 carbon.Parse("2020-08-05 13:14:15").DiffInHours(carbon.Parse("2020-08-05 12:14:15")) // -1 // 相差多少小时(绝对值) carbon.Parse("2020-08-05 13:14:15").DiffInHoursWithAbs(carbon.Parse("2020-08-05 12:14:15")) // 1 // 相差多少分 carbon.Parse("2020-08-05 13:14:15").DiffInMinutes(carbon.Parse("2020-08-05 13:13:15")) // -1 // 相差多少分(绝对值) carbon.Parse("2020-08-05 13:14:15").DiffInMinutesWithAbs(carbon.Parse("2020-08-05 13:13:15")) // 1 // 相差多少秒 carbon.Parse("2020-08-05 13:14:15").DiffInSeconds(carbon.Parse("2020-08-05 13:14:14")) // -1 // 相差多少秒(绝对值) carbon.Parse("2020-08-05 13:14:15").DiffInSecondsWithAbs(carbon.Parse("2020-08-05 13:14:14")) // 1 //时间比较 // 是否大于 carbon.Parse("2020-08-05 13:14:15").Gt(carbon.Parse("2020-08-04 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").Gt(carbon.Parse("2020-08-05 13:14:15")) // false carbon.Parse("2020-08-05 13:14:15").Compare(">", carbon.Parse("2020-08-04 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").Compare(">", carbon.Parse("2020-08-05 13:14:15")) // false // 是否小于 carbon.Parse("2020-08-05 13:14:15").Lt(carbon.Parse("2020-08-06 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").Lt(carbon.Parse("2020-08-05 13:14:15")) // false carbon.Parse("2020-08-05 13:14:15").Compare("<", carbon.Parse("2020-08-06 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").Compare("<", carbon.Parse("2020-08-05 13:14:15")) // false // 是否等于 carbon.Parse("2020-08-05 13:14:15").Eq(carbon.Parse("2020-08-05 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").Eq(carbon.Parse("2020-08-05 13:14:00")) // false carbon.Parse("2020-08-05 13:14:15").Compare("=", carbon.Parse("2020-08-05 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").Compare("=", carbon.Parse("2020-08-05 13:14:00")) // false // 是否不等于 carbon.Parse("2020-08-05 13:14:15").Ne(carbon.Parse("2020-08-06 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").Ne(carbon.Parse("2020-08-05 13:14:15")) // false carbon.Parse("2020-08-05 13:14:15").Compare("!=", carbon.Parse("2020-08-06 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").Compare("<>", carbon.Parse("2020-08-05 13:14:15")) // false // 是否大于等于 carbon.Parse("2020-08-05 13:14:15").Gte(carbon.Parse("2020-08-04 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").Gte(carbon.Parse("2020-08-05 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").Compare(">=", carbon.Parse("2020-08-04 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").Compare(">=", carbon.Parse("2020-08-05 13:14:15")) // true // 是否小于等于 carbon.Parse("2020-08-05 13:14:15").Lte(carbon.Parse("2020-08-06 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").Lte(carbon.Parse("2020-08-05 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").Compare("<=", carbon.Parse("2020-08-06 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").Compare("<=", carbon.Parse("2020-08-05 13:14:15")) // true // 是否在两个时间之间(不包括这两个时间) carbon.Parse("2020-08-05 13:14:15").Between(carbon.Parse("2020-08-05 13:14:15"), carbon.Parse("2020-08-06 13:14:15")) // false carbon.Parse("2020-08-05 13:14:15").Between(carbon.Parse("2020-08-04 13:14:15"), carbon.Parse("2020-08-06 13:14:15")) // true // 是否在两个时间之间(包括开始时间) carbon.Parse("2020-08-05 13:14:15").BetweenIncludedStartTime(carbon.Parse("2020-08-05 13:14:15"), carbon.Parse("2020-08-06 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").BetweenIncludedStartTime(carbon.Parse("2020-08-04 13:14:15"), carbon.Parse("2020-08-06 13:14:15")) // true // 是否在两个时间之间(包括结束时间) carbon.Parse("2020-08-05 13:14:15").BetweenIncludedEndTime(carbon.Parse("2020-08-04 13:14:15"), carbon.Parse("2020-08-05 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").BetweenIncludedEndTime(carbon.Parse("2020-08-04 13:14:15"), carbon.Parse("2020-08-06 13:14:15")) // true // 是否在两个时间之间(包括这两个时间) carbon.Parse("2020-08-05 13:14:15").BetweenIncludedBoth(carbon.Parse("2020-08-05 13:14:15"), carbon.Parse("2020-08-06 13:14:15")) // true carbon.Parse("2020-08-05 13:14:15").BetweenIncludedBoth(carbon.Parse("2020-08-04 13:14:15"), carbon.Parse("2020-08-05 13:14:15")) // true //时间判断 // 是否是零值时间 carbon.Parse("").IsZero() // true carbon.Parse("0").IsZero() // true carbon.Parse("0000-00-00 00:00:00").IsZero() // true carbon.Parse("0000-00-00").IsZero() // true carbon.Parse("00:00:00").IsZero() // true carbon.Parse("2020-08-05 00:00:00").IsZero() // false carbon.Parse("2020-08-05").IsZero() // false // 是否是当前时间 carbon.Now().IsNow() // true // 是否是未来时间 carbon.Tomorrow().IsFuture() // true // 是否是过去时间 carbon.Yesterday().IsPast() // true // 是否是闰年 carbon.Parse("2020-08-05 13:14:15").IsLeapYear() // true // 是否是长年 carbon.Parse("2020-08-05 13:14:15").IsLongYear() // true // 是否是一月 carbon.Parse("2020-08-05 13:14:15").IsJanuary() // false // 是否是二月 carbon.Parse("2020-08-05 13:14:15").IsFebruary() // false // 是否是三月 carbon.Parse("2020-08-05 13:14:15").IsMarch() // false // 是否是四月 carbon.Parse("2020-08-05 13:14:15").IsApril() // false // 是否是五月 carbon.Parse("2020-08-05 13:14:15").IsMay() // false // 是否是六月 carbon.Parse("2020-08-05 13:14:15").IsJune() // false // 是否是七月 carbon.Parse("2020-08-05 13:14:15").IsJuly() // false // 是否是八月 carbon.Parse("2020-08-05 13:14:15").IsAugust() // false // 是否是九月 carbon.Parse("2020-08-05 13:14:15").IsSeptember() // true // 是否是十月 carbon.Parse("2020-08-05 13:14:15").IsOctober() // false // 是否是十一月 carbon.Parse("2020-08-05 13:14:15").IsNovember() // false // 是否是十二月 carbon.Parse("2020-08-05 13:14:15").IsDecember() // false // 是否是周一 carbon.Parse("2020-08-05 13:14:15").IsMonday() // false // 是否是周二 carbon.Parse("2020-08-05 13:14:15").IsTuesday() // true // 是否是周三 carbon.Parse("2020-08-05 13:14:15").IsWednesday() // false // 是否是周四 carbon.Parse("2020-08-05 13:14:15").IsThursday() // false // 是否是周五 carbon.Parse("2020-08-05 13:14:15").IsFriday() // false // 是否是周六 carbon.Parse("2020-08-05 13:14:15").IsSaturday() // false // 是否是周日 carbon.Parse("2020-08-05 13:14:15").IsSunday() // false // 是否是工作日 carbon.Parse("2020-08-05 13:14:15").IsWeekday() // false // 是否是周末 carbon.Parse("2020-08-05 13:14:15").IsWeekend() // true // 是否是昨天 carbon.Parse("2020-08-04 13:14:15").IsYesterday() // true carbon.Parse("2020-08-04 00:00:00").IsYesterday() // true carbon.Parse("2020-08-04").IsYesterday() // true // 是否是今天 carbon.Parse("2020-08-05 13:14:15").IsToday() // true carbon.Parse("2020-08-05 00:00:00").IsToday() // true carbon.Parse("2020-08-05").IsToday() // true // 是否是明天 carbon.Parse("2020-08-06 13:14:15").IsTomorrow() // true carbon.Parse("2020-08-06 00:00:00").IsTomorrow() // true carbon.Parse("2020-08-06").IsTomorrow() // true //时间输出 // 输出秒级时间戳 carbon.Parse("2020-08-05 13:14:15").ToTimestamp() // 1596604455 carbon.Parse("2020-08-05 13:14:15").ToTimestampWithSecond() // 1596604455 // 输出毫秒级时间戳 carbon.Parse("2020-08-05 13:14:15").ToTimestampWithMillisecond() // 1596604455000 // 输出微秒级时间戳 carbon.Parse("2020-08-05 13:14:15").ToTimestampWithMicrosecond() // 1596604455000000 // 输出纳秒级时间戳 carbon.Parse("2020-08-05 13:14:15").ToTimestampWithNanosecond() // 1596604455000000000 // 输出日期时间字符串 carbon.Parse("2020-08-05 13:14:15").ToDateTimeString() // 2020-08-05 13:14:15 // 输出日期字符串 carbon.Parse("2020-08-05 13:14:15").ToDateString() // 2020-08-05 // 输出时间字符串 carbon.Parse("2020-08-05 13:14:15").ToTimeString() // 13:14:15 // 输出Ansic格式字符串 carbon.Parse("2020-08-05 13:14:15").ToAnsicString() // Wed Aug 5 13:14:15 2020 // 输出Atom字符串 carbon.Parse("2020-08-05 13:14:15").ToAtomString() // Wed Aug 5 13:14:15 2020 // 输出UnixDate格式字符串 carbon.Parse("2020-08-05 13:14:15").ToUnixDateString() // Wed Aug 5 13:14:15 CST 2020 // 输出RubyDate格式字符串 carbon.Parse("2020-08-05 13:14:15").ToRubyDateString() // Wed Aug 05 13:14:15 +0800 2020 // 输出Kitchen格式字符串 carbon.Parse("2020-08-05 13:14:15").ToKitchenString() // 1:14PM // 输出Cookie格式字符串 carbon.Parse("2020-08-05 13:14:15").ToCookieString() // Wednesday, 05-Aug-2020 13:14:15 CST // 输出DayDateTime格式字符串 carbon.Parse("2020-08-05 13:14:15").ToDayDateTimeString() // Wed, Aug 5, 2020 1:14 PM // 输出RSS格式字符串 carbon.Parse("2020-08-05 13:14:15").ToRssString() // Wed, 05 Aug 2020 13:14:15 +0800 // 输出W3C格式字符串 carbon.Parse("2020-08-05 13:14:15").ToW3cString() // 2020-08-05T13:14:15+08:00 // 输出RFC822格式字符串 carbon.Parse("2020-08-05 13:14:15").ToRfc822String() // 05 Aug 20 13:14 CST // 输出RFC822Z格式字符串 carbon.Parse("2020-08-05 13:14:15").ToRfc822zString() // 05 Aug 20 13:14 +0800 // 输出RFC850格式字符串 carbon.Parse("2020-08-05 13:14:15").ToRfc850String() // Wednesday, 05-Aug-20 13:14:15 CST // 输出RFC1036格式字符串 carbon.Parse("2020-08-05 13:14:15").ToRfc1036String() // Wed, 05 Aug 20 13:14:15 +0800 // 输出RFC1123格式字符串 carbon.Parse("2020-08-05 13:14:15").ToRfc1123String() // Wed, 05 Aug 2020 13:14:15 CST // 输出RFC2822格式字符串 carbon.Parse("2020-08-05 13:14:15").ToRfc2822String() // Wed, 05 Aug 2020 13:14:15 +0800 // 输出RFC3339格式字符串 carbon.Parse("2020-08-05 13:14:15").ToRfc3339String() // 2020-08-05T13:14:15+08:00 // 输出RFC7231格式字符串 carbon.Parse("2020-08-05 13:14:15").ToRfc7231String() // Wed, 05 Aug 2020 05:14:15 GMT // 输出字符串 carbon.Parse("2020-08-05 13:14:15").ToString() // 2020-08-05 13:14:15 +0800 CST // 输出格式化字符串,Format()是ToFormatString()的简写 carbon.Parse("2020-08-05 13:14:15").ToFormatString("YmdHis") // 20200805131415 carbon.Parse("2020-08-05 13:14:15").ToFormatString("Y年m月d H时i分s秒") // 2020年08月05日 13时14分15秒 carbon.Parse("2020-08-05 13:14:15").Format("YmdHis") // 20200805131415 carbon.Parse("2020-08-05 13:14:15").Format("l jS of F Y h:i:s A") // Wednesday 5th of August 2020 01:14:15 PM //时间获取 // 获取本年总天数 carbon.Parse("2019-08-05 13:14:15").DaysInYear() // 365 carbon.Parse("2020-08-05 13:14:15").DaysInYear() // 366 // 获取本月总天数 carbon.Parse("2020-02-01 13:14:15").DaysInMonth() // 29 carbon.Parse("2020-04-01 13:14:15").DaysInMonth() // 30 carbon.Parse("2020-08-01 13:14:15").DaysInMonth() // 31 // 获取本年第几天 carbon.Parse("2020-08-05 13:14:15").DayOfYear() // 218 // 获取本年第几周 carbon.Parse("2019-12-31 13:14:15").WeekOfYear() // 1 carbon.Parse("2020-08-05 13:14:15").WeekOfYear() // 32 // 获取本月第几天 carbon.Parse("2020-08-05 13:14:15").DayOfMonth() // 5 // 获取本月第几周 carbon.Parse("2020-08-05 13:14:15").WeekOfMonth() // 1 // 获取本周第几天 carbon.Parse("2020-08-05 13:14:15").DayOfWeek() // 3 // 获取当前年 carbon.Parse("2020-08-05 13:14:15").Year() // 2020 // 获取当前季度 carbon.Parse("2020-08-05 13:14:15").Quarter() // 3 // 获取当前月 carbon.Parse("2020-08-05 13:14:15").Month() // 8 // 获取当前日 carbon.Parse("2020-08-05 13:14:15").Day() // 5 // 获取当前时 carbon.Parse("2020-08-05 13:14:15").Hour() // 13 // 获取当前分 carbon.Parse("2020-08-05 13:14:15").Minute() // 14 // 获取当前秒 carbon.Parse("2020-08-05 13:14:15").Second() // 15 // 获取当前毫秒 carbon.Parse("2020-08-05 13:14:15").Millisecond() // 1596604455000 // 获取当前微秒 carbon.Parse("2020-08-05 13:14:15").Microsecond() // 1596604455000000 // 获取当前纳秒 carbon.Parse("2020-08-05 13:14:15").Nanosecond() // 1596604455000000000 // 获取时区 carbon.SetTimezone(carbon.PRC).Timezone() // PRC carbon.SetTimezone(carbon.Tokyo).Timezone() // Asia/Tokyo // 获取年龄 carbon.Parse("2002-01-01 13:14:15").Age() // 17 carbon.Parse("2002-12-31 13:14:15").Age() // 18 //农历支持 // 获取生肖年 carbon.Parse("2020-08-05 13:14:15").ToAnimalYear() // 鼠 // 获取农历年 carbon.Parse("2020-08-05 13:14:15").ToLunarYear() // 庚子 // 是否是鼠年 carbon.Parse("2020-08-05 13:14:15").IsYearOfRat() // true // 是否是牛年 carbon.Parse("2020-08-05 13:14:15").IsYearOfOx() // false // 是否是虎年 carbon.Parse("2020-08-05 13:14:15").IsYearOfTiger() // false // 是否是兔年 carbon.Parse("2020-08-05 13:14:15").IsYearOfRabbit() // false // 是否是龙年 carbon.Parse("2020-08-05 13:14:15").IsYearOfDragon() // false // 是否是蛇年 carbon.Parse("2020-08-05 13:14:15").IsYearOfSnake() // false // 是否是马年 carbon.Parse("2020-08-05 13:14:15").IsYearOfHorse() // false // 是否是羊年 carbon.Parse("2020-08-05 13:14:15").IsYearOfGoat() // false // 是否是猴年 carbon.Parse("2020-08-05 13:14:15").IsYearOfMonkey() // false // 是否是鸡年 carbon.Parse("2020-08-05 13:14:15").IsYearOfRooster() // false // 是否是狗年 carbon.Parse("2020-08-05 13:14:15").IsYearOfDog() // false // 是否是猪年 carbon.Parse("2020-08-05 13:14:15").IsYearOfPig() // false 参考:链接参考:https://blog.csdn.net/angelstreet23/article/details/113616889
查看详情点赞1评论收藏浏览862023-05-18 13:57:46Go 简单实现根据网址爬取整站URL(Go爬虫,生成sitemap.xml)
想要实现一个根据一个网站的域名,然后把整个网站的所有链接都爬取出来,以前都是Python爬取,突发奇想用Go也来弄一个,借鉴网上很多资料。整理了一个可以实现的功能,有兴趣的同学可以试试。package main import ( "flag" "fmt" "regexp" "time" "github.com/PuerkitoBio/goquery" "github.com/douyacun/gositemap" "github.com/gocolly/colly" "github.com/gocolly/colly/extensions" ) var priority float64 = 1 func main() { //命令参数获取 targetUrl := flag.String("url", "", "目标站点地址") path := flag.String("path", "", "目标站点地址") Parallelnum := flag.Int("parallel", 500, "并发数") Delay := flag.Int("delay", 0, "延迟(毫秒)") MaxLinks := flag.Int("maxlink", 50000, "最大链接数") //[必须调用]:从 arguments 中解析注册的 flag,不然参数获取都没值 flag.Parse() //参数验证 if len(*targetUrl) == 0 { panic("请传递目前站点地址") } if len(*path) == 0 { *path = fmt.Sprintf("gositemap/%s/", time.Now().Format("20060102_150405")) } st := gositemap.NewSiteMap() st.SetDefaultHost(*targetUrl) st.SetPretty(true) // 每个sitemap文件不能多于50000个链接,这里可以自己配置每个文件最多,如果超过MaxLinks,会自动生成sitemap_index.xml文件 st.SetMaxLinks(*MaxLinks) //xml文件输出地址 st.SetPublicPath(*path) t := time.Now() number := 1 //初始化 创建收集器 c := colly.NewCollector(func(c *colly.Collector) { extensions.RandomUserAgent(c) // 设置随机头 c.Async = true }, //过滤url,去除外链 colly.URLFilters( //regexp.MustCompile("^(https://www\\.uppdd\\.com/)"), regexp.MustCompile("^("+*targetUrl+"/(.*))"), ), ) //控制下速度 c.Limit(&colly.LimitRule{ //DomainGlob: "*uppdd.*", //过滤规则的作用域,不限制则全部链接皆使用该规则 DomainGlob: "*", Parallelism: *Parallelnum, Delay: time.Duration(*Delay), }) // 响应的格式为HTML,提取页面中的链接 c.OnHTML("a[href]", func(e *colly.HTMLElement) { link := e.Attr("href") href := e.Request.AbsoluteURL(link) // 访问url 内部会检查 是否符合 正则表达式 。如果不符合 终止访问该url c.Visit(href) }) c.OnHTML("body", func(e *colly.HTMLElement) { e.DOM.Each(func(i int, selection *goquery.Selection) { //匹配数据(页面dom结构) href := e.Request.AbsoluteURL(e.Attr("href")) //写入链接 url := gositemap.NewUrl() url.SetLoc(href) url.SetLastmod(time.Now()) url.SetChangefreq(gositemap.Daily) //获取页面权重 url.SetPriority(getPriority(number)) st.AppendUrl(url) number += 1 fmt.Printf("计数:%d,链接:%s \n", number, href) }) }) c.OnError(func(response *colly.Response, err error) { //fmt.Println(err) }) c.Visit(*targetUrl + "/") c.Wait() fmt.Printf("连接收集花费时间:%s,收集链接:%d个", time.Since(t), number) //bt,err := st.ToXml() //if err != nil{ // fmt.Printf("异常:%v", err) // return //} //byte切片转string,好查看是否有错误 //btString := string(bt) //输出 //fmt.Println(btString) //生成文件导出 filePath, err := st.Storage() if err != nil { fmt.Printf("%v", err) return } fmt.Println(filePath) } // 递减乱获取权重|很随意 func getPriority(num int) float64 { if num < 20 { return 1 } newS := float64(num) / priority numQ := int(newS) //fmt.Printf("权重:%d \n",numQ) if num >= numQ { priority -= 0.01 } return priority } 我们随便找个网址来试试!#启动命令,其他参数自己也可以尝试,然后完成会把所有的链接都存入一个sitemap.xml文件中 go run . -url=https://gs.dgg.cn然后这个也可以为SEO的同学,爬取整站链接,当做sitemap.xml文件
查看详情点赞1评论收藏1浏览2002023-05-25 11:33:11go 8个常用排序测试(冒泡排序,插入排序,选择排序,希尔排序,堆排序,基数排序,快速排序,计数排序)
前两天在做项目的时候,用到了排序,在网上查阅了一些资料做了部分对比,所以对常用的排序做了一些对比。当然不是全部,例如归并排序,二叉树之类的。等以后有时间可以了解一下。下面我们就了解一下这些排序;首先,我们先生成100位随机数字,然后对这一组数字进行排序。package main import ( "fmt" "math" mathRand "math/rand" "time" ) func main() { // 生成随机数 mathRand.Seed(time.Now().UnixNano()) var arr [100]int // 不重复生成 for i := 0; i < 100; i++ { arr[i] = mathRand.Intn(100) loop: for k := 0; k < i; k++ { if arr[k] == arr[i] { arr[i] = mathRand.Intn(100) goto loop } } } //打印随机数据 fmt.Println("生成的随机数切片:\n", arr) }1、冒泡排序// 冒泡排序方法 func mpArr(data [100]int) [100]int { var arrx int for i := 0; i < len(data)-1; i++ { for j := 0; j < len(data)-i-1; j++ { arrx++ if data[j] > data[j+1] { //交换 data[j], data[j+1] = data[j+1], data[j] } } } fmt.Println("冒泡排序排序次数", arrx) return data } mpArr := mpArr(arr) fmt.Println("冒泡排序之后的切片", mpArr)我们可以看到冒泡排序是比较稳定的。2、插入排序// 插入排序 func insertSort(data [100]int) [100]int { var j int var pxNum int for i := 1; i < len(data); i++ { temp := data[i] for j = i; j > 0 && temp < data[j-1]; j-- { data[j] = data[j-1] pxNum++ } data[j] = temp } fmt.Println("改进版插入排序次数:", pxNum) return data } // 改进版插入排序 func InsertionSort2(arr [100]int) [100]int { n := len(arr) var pxNum int for i := 1; i < n; i++ { // 无序区 tmp := arr[i] left, right := 0, i-1 for left <= right { pxNum++ mid := (left + right) / 2 if arr[mid] > tmp { right = mid - 1 } else { left = mid + 1 } } j := i - 1 for ; j >= left; j-- { // 有序区 arr[j+1] = arr[j] pxNum++ } arr[left] = tmp } fmt.Println("插入排序次数:", pxNum) return arr } // 使用插入排序 cr2Arr := InsertionSort2(arr) fmt.Println("插入排序之后的切片", cr2Arr) // 使用改进版插入排序 crArr := insertSort(arr) fmt.Println("改进版插入排序之后的切片", crArr)我们可以看到插入排序也是比较稳定的。3、选择排序// 选择排序 func SelectionSort(arr [100]int) [100]int { var pxNum int n := len(arr) for i := 0; i < n-1; i++ { minNumIndex := i // 无序区第一个 for j := i + 1; j < n; j++ { if arr[j] < arr[minNumIndex] { minNumIndex = j pxNum++ } } arr[i], arr[minNumIndex] = arr[minNumIndex], arr[i] } fmt.Println("选择排序次数:", pxNum) return arr } // 使用选择排序 xzArr := SelectionSort(arr) fmt.Println("选择排序之后的切片", xzArr)由此可以看出,选择排序是不稳定的4、希尔排序// 希尔排序 func shellSort(data [100]int) [100]int { var j int var pxNum int for h := len(data) / 2; h > 0; h /= 2 { //外层循环控制步长 for i := h; i < len(data); i++ { //内层循环是对步长个子切片做插入排序 temp := data[i] for j = i; j >= h && temp < data[j-h]; j -= h { data[j] = data[j-h] pxNum++ } data[j] = temp } } fmt.Println("希尔排序次数:", pxNum) return data } xrArr := shellSort(arr) fmt.Println("希尔排序之后的切片", xrArr)希尔排序也是不稳定的5、堆排序// 堆排序 func heapSort(data [100]int) [100]int { m := len(data) var pxNum int s := m / 2 for i := s; i >= 0; i-- { heap(data, i, m-1) } for i := m - 1; i > 0; i-- { data[i], data[0] = data[0], data[i] heap(data, 0, i-1) pxNum++ } fmt.Println("堆排序次数:", pxNum) return data } dArr := heapSort(arr) fmt.Println("堆排序之后的切片", dArr)堆排序也是不稳定的6、基数排序// 基数排序 func RadixSort(arr [100]int) [100]int { var pxNum int maxn := maxBitNum(arr) // arr最大位数 dev := 1 // 除数,保证商最后一位是我们想要的 mod := 10 // 模,取商的最后一位 for i := 0; i < maxn; i++ { // 进行maxn次排序 bucket := make([][]int, 10) // 定义10个空桶 result := make([]int, 0) // 存储中间结果 for _, v := range arr { n := v / dev % mod // 取出对应位的值,放入对应桶中 bucket[n] = append(bucket[n], v) pxNum++ } dev *= 10 // 按顺序存入中间切片 for j := 0; j < 10; j++ { result = append(result, bucket[j]...) pxNum++ } // 转存到原切片(结果) for k := range arr { arr[k] = result[k] pxNum++ } } fmt.Println("基数排序次数:", pxNum) return arr } zsArr := RadixSort(arr) fmt.Println("基数排序之后的切片", zsArr)基数排序是稳定的。7、快速排序// 快速排序 func QuickSort(left int, right int, array *[100]int) { l := left r := right // pivot 是中轴, 支点 pivot := array[(left+right)/2] temp := 0 // for 循环的目标是将比 pivot 小的数放到左边,比 pivot 大的数放到右边 for l < r { // 从 pivot 的左边找到大于等于pivot的值 for array[l] < pivot { l++ } // 从 pivot 的右边边找到小于等于pivot的值 for array[r] > pivot { r-- } // 1 >= r 表明本次分解任务完成, break if l >= r { break } // 交换 temp = array[l] array[l] = array[r] array[r] = temp // 优化 if array[l] == pivot { r-- } if array[r] == pivot { l++ } } // 如果 1== r, 再移动下 if l == r { l++ r-- } // 向左递归 if left < r { QuickSort(left, r, array) } // 向右递归 if right > l { QuickSort(l, right, array) } } ksArr := arr QuickSort(0, len(ksArr)-1, &ksArr) fmt.Println("快速排序之后的切片", ksArr)快排也是不稳定的。8、计数排序// 计数排序 func CountingSort(arr [100]int) [100]int { var pxNum int length := len(arr) maxValue := getMaxValue(arr) bucketLen := maxValue + 1 bucket := make([]int, bucketLen) sortedIndex := 0 // 统计每个元素出现的个数 for i := 0; i < length; i++ { bucket[arr[i]] += 1 pxNum++ } // 按照统计结果写入arr for j := 0; j < length; j++ { for bucket[j] > 0 { pxNum++ arr[sortedIndex] = j // bucket[j]的值是统计结果,后面会变化,j是真正值 sortedIndex++ bucket[j]-- } } fmt.Println("计数排序次数:", pxNum) return arr } jsArr := CountingSort(arr) fmt.Println("计数排序之后的切片", jsArr)计数排序也是稳定的 稳定的排序算法有:冒泡排序、插入排序、计数排序、基数排序。 不稳定的排序算法有:选择排序、希尔排序、快速排序、堆排序。 当然每个排序方法只有最适合的场景,不能单从稳定性考虑,也要从性能上考虑,不能一概而论。
查看详情点赞1评论收藏浏览762023-06-14 09:58:31RSA加密与签名有什么区别
区别:RSA加密:RSA密码体制是一种公钥密码体制,加密算法公开,以分配的密钥作为加密解密的关键。RSA签名:签名就是在这份资料后面增加一段强而有力的证明,以此证明这段信息的发布者和这段信息的有效性完整性。在使用RSA进行通讯的时候,一般是两者结合,即:加密>签名>解密>验签公钥加密、私钥解密、私钥签名、公钥验签。加密是可逆的,而签名是不可逆的我们可以对一份资料用公钥加密,再用私钥解密,但我们对这份资料进行签名则是不可逆的,因为哈希本身是不可逆的。加密和签名都涉及到了使用公钥加密,前者加密了信息,后者加密了信息的hash加密和签名都是为了安全性考虑,但略有不同。加密是为了防止信息被泄露,而签名是为了防止信息被篡改。为什么签名是对信息hash之后加密,而不是加密一些特定的字符?这是因为防止中间人尝试向私钥拥有者反复发送一些特定的字符,得到加密后的信息,达到破解或者伪造之类的目的。所以用私钥随便加密信息是不安全的。RSA的签名与加密一般用在需要非常安全的环境下,例如支付总结:签名的本质其实就是加密,但是由于签名无需还原成明文,因此可以在加密前进行哈希处理。所以签名其实就是哈希+加密,而验签就是哈希+解密+比较。签名过程:对明文做哈希,拼接头信息,用私钥进行加密,得到签名。验签过程:用公钥解密签名,然后去除头信息,对明文做哈希,比较2段哈希值是否相同,相同则验签成功。
查看详情点赞1评论收藏浏览652023-06-27 10:07:20go 数字图形验证码功能
最近在开发一个项目,需要用到验证码,很久没有用到验证码的项目还不习惯;在网上参考了一些资料,简单用Go语言开发了一个数字验证码功能;用到的框架是最近比较火的一款 go-zero 微服务框架;首先,应用包,我用的是常用的包,验证码用的默认数字图形验证码;引入包:github.com/mojocn/base64Captchaetc yaml配置文件配置如下:Captcha: KeyLong: 6 ImgWidth: 240 ImgHeight: 80Config 文件验证码结构体:KeyLong int64 ImgWidth int64 ImgHeight int64 }到此,配置完成了,最后直接调用包new一个验证码// 获取验证码 func (l *GetCaptchaCodeLogic) GetCaptchaCode(in *member.GetCaptchaReq) (*member.GetCaptchaResp, error) { // 验证码 driver := base64Captcha.NewDriverDigit(int(l.svcCtx.Config.Captcha.ImgHeight), int(l.svcCtx.Config.Captcha.ImgWidth), int(l.svcCtx.Config.Captcha.KeyLong), 0.7, 80) cp := base64Captcha.NewCaptcha(driver, storeBlog) id, b64s, err := cp.Generate() fmt.Println(err) if err != nil { return &member.GetCaptchaResp{ Code: 500, Msg: "获取验证码失败", }, nil } return &member.GetCaptchaResp{ Code: 200, Msg: "获取成功", CaptchaId: id, PicPath: b64s, }, nil }用go-zero完成的简单的图形数字验证码功能就OK了。如果想要了解go-zero的api和protobuf文件,可以点击下面链接进行跳转。 go-zero微服务框架 api 案例 go-zero微服务框架 proto 案例
查看详情点赞评论收藏浏览372023-03-03 15:52:41MD5 与 SHA-256比较!示例(Go)
什么是MD5?MD5(Message-Digest Algorithm 5)是一种消息摘要算法,用于计算数据的哈希值。它可以将任意长度的数据转换为一个128位的哈希值,该哈希值可以用作数据的识别码。 MD5算法的计算过程非常简单:对数据进行哈希处理,生成一个128位的数字,该数字表示原始数据的特征值。由于MD5算法采用的是单向哈希函数,即只能从哈希值中计算出原始数据,而不能从原始数据中计算出哈希值,因此可以确保原始数据不被篡改。 虽然MD5算法被广泛使用,但其安全性一直受到质疑。SHA-256算法相比MD5算法更强,能够更好地保护数据的安全性。因此,在实际应用中,建议使用SHA-256等更强的算法来保护数据的安全性。什么是SHA-256?SHA-256(Secure Hash Algorithm 256)是一种哈希算法,常用于数字签名、文件完整性校验、数据加密等场景。它是一种消息认证码,用于确保数据完整性,可以有效地防止数据被篡改或者伪造。 SHA-256算法的输入是任意长度的二进制数据,输出是一个长度为32字节的哈希值。该哈希值可以用作数字签名的密钥,或者用于文件完整性校验。在数字签名中,使用SHA-256算法将数据和密钥一起计算,生成一个哈希值,该哈希值可以用于验证数据的完整性。在文件完整性校验中,将待校验的文件的前n个字节与一个哈希值进行比较,如果哈希值匹配,则说明文件未被篡改。 SHA-256算法的安全性得到了广泛认可,是目前最常用的哈希算法之一。不过需要注意的是,任何算法都不是绝对安全的,需要根据实际情况选择合适的算法和安全策略。示例(Go)md5生成:import ( "crypto/md5" ) // 计算哈希值 func hash(data string) (string, error) { h := md5.New() _, err := h.Write([]byte(data)) if err != nil { return "", err } return hex.EncodeToString(h.Sum(nil)), nil } // 使用示例 data := "hello world" result, err := hash(data) if err != nil { return } fmt.Println(result) // "b0baee9d279d34fa1dfd71aadb908c3f"SHA-256生成:import ( "crypto/sha256" ) // 计算哈希值 func hash(data string) (string, error) { h := sha256.New() _, err := h.Write([]byte(data)) if err != nil { return "", err } return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil } // 使用示例 data := "hello world" result, err := hash(data) if err != nil { return } fmt.Println(result) // "sha256:a3c25f34ed9f1dfcc4b4b41a8058d3438df8e224b3973ab0bb5408cddbc58ee3c2"
查看详情点赞评论收藏浏览1632023-06-12 15:28:13