golang实现自动申请lets encrypt证书_acme golang_白马啸西施的博客-CSDN博客


本站和网页 https://blog.csdn.net/wangge20091126/article/details/128000425 的作者无关,不对其内容负责。快照谨为网络故障时之索引,不代表被搜索网站的即时页面。

golang实现自动申请lets encrypt证书_acme golang_白马啸西施的博客-CSDN博客
golang实现自动申请lets encrypt证书
白马啸西施
于 2022-11-23 15:24:44 发布
588
收藏
分类专栏:
笔记
文章标签:
golang
开发语言
后端
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wangge20091126/article/details/128000425
版权
笔记
专栏收录该内容
11 篇文章
0 订阅
订阅专栏
在工作中,有这么一个需求,通过后台管理域名的证书自动申请及续签的功能; 方案有两种: 一种是通过官方的golang.org/x/crypto/acme/autocert包来实现域名的申请及续签,这个不用多说。 另一种是通过lego包来实现,重点说明一下这中方案,只实现了http的挑战,需要在路由中设置挑战路径 我这个Echo的路由
// http域名挑战
s.e.GET("/.well-known/acme-challenge/:token", func(c echo.Context) error {
fmt.Println("域名挑战......")
token := c.Param("token")
exist, value := cert.NewMemoryProviderServer().GetKeyAuth("", token)
fmt.Printf("exist:%v, token:%v, value:%v", exist, token, value)
if !exist {
return errors.New("挑战失败")
c.Response().Writer.Write([]byte(value))
return nil
}).Name = "域名申请挑战"
接下来是对lego的封装,http-001挑战需要实现Present,CleanUp这两个方法:
var httpData *MemoryProviderServer
var once1 sync.Once
type MemoryProviderServer struct {
data map[string]string
lock sync.Mutex
func NewMemoryProviderServer() *MemoryProviderServer {
once1.Do(func() {
httpData = &MemoryProviderServer{
data: map[string]string{},
})
return httpData
func (s *MemoryProviderServer) Present(domain, token, keyAuth string) error {
s.lock.Lock()
fmt.Printf("保存token:%v,value:%v\n", token, keyAuth)
s.data[token] = keyAuth
s.lock.Unlock()
return nil
func (s *MemoryProviderServer) CleanUp(domain, token, keyAuth string) error {
s.lock.Lock()
delete(s.data, domain+token)
fmt.Printf("清空token:%v\n", token)
s.lock.Unlock()
return nil
func (s *MemoryProviderServer) GetKeyAuth(domain, token string) (exist bool, keyAuth string) {
s.lock.Lock()
fmt.Printf("domain data:%+v", s.data)
keyAuth, exist = s.data[token]
s.lock.Unlock()
return
接下来是lets encrypt账号注册,注同一个ip一定时间内注册账号是有限制的,所以需要将账号信息缓存起来,比如存redis,db等,这里是直接存的数据库,表结构如下:
type LetsEncrypt struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
Email string `gorm:"column:email;type:varchar(191);uniqueIndex:idx_email;not null;default:'';comment:email" json:"email"`
PrivateKey string `gorm:"column:private_key;type:text;comment:账号key" json:"privateKey"`
Registration string `gorm:"column:registration;type:text;comment:lets Encrypt注册信息" json:"registration"`
CreateAt int64 `gorm:"column:create_at;not null;autoCreateTime" json:"createAt"`
UpdateAt int64 `gorm:"column:update_at;not null;autoUpdateTime" json:"updateAt"`
缓存实现:
type DataCache interface {
SetKey(key crypto.PrivateKey) error
GetKey() crypto.PrivateKey
SetRegistration(reg *registration.Resource) error
GetRegistration() *registration.Resource
SetEmail(email string) error
type DBCache struct {
Email string
Key crypto.PrivateKey
Registration *registration.Resource
Data *models.LetsEncrypt
func (d *DBCache) SetKey(key crypto.PrivateKey) error {
d.Key = key
d.Data.PrivateKey = string(certcrypto.PEMEncode(d.Key))
return d.saveData()
func (d *DBCache) GetKey() crypto.PrivateKey {
return d.Key
func (d *DBCache) SetRegistration(reg *registration.Resource) error {
d.Registration = reg
marshal, err := json.Marshal(d.Registration)
if err != nil {
return err
d.Data.Registration = string(marshal)
return d.saveData()
func (d *DBCache) GetRegistration() *registration.Resource {
return d.Registration
func (d *DBCache) SetEmail(email string) error {
d.Email = email
// 查询数据库是否存在
var account models.LetsEncrypt
if err := database.GetGormDb().Where(models.LetsEncrypt{Email: d.Email}).FirstOrCreate(&account).Error; err != nil {
log.Error(err)
return err
d.Data = &account
// 解析key
if d.Data.PrivateKey != "" {
key, err := certcrypto.ParsePEMPrivateKey([]byte(d.Data.PrivateKey))
if err != nil {
log.Info("初始化key错误")
return err
d.Key = key
// 解析registration
if d.Data.Registration != "" {
var reg registration.Resource
if err := json.Unmarshal([]byte(d.Data.Registration), &reg); err != nil {
log.Info("初始化registration错误")
return err
d.Registration = &reg
return nil
func (d *DBCache) saveData() error {
return database.GetGormDb().Save(d.Data).Error
最后实现lego的封装
type user struct {
//email 邮箱
email string
//registration 表示已在ACME服务器上注册的帐户信息
registration *registration.Resource
//key 表示ECDSA私钥
key crypto.PrivateKey
func (u *user) GetEmail() string {
return u.email
func (u *user) GetRegistration() *registration.Resource {
return u.registration
func (u *user) GetPrivateKey() crypto.PrivateKey {
return u.key
type LetsEncrypt struct {
User *user
Client *lego.Client
Cache DataCache
Account string
var letsEncrypt *LetsEncrypt
var once sync.Once
var defaultEmail = "xxx@163.com"
var dao DataCache
func NewLetsEncrypt(email string) *LetsEncrypt {
once.Do(func() {
if email == "" {
email = defaultEmail
dao = &DBCache{}
if err := dao.SetEmail(email); err != nil {
log.Error(err)
return
letsEncrypt = &LetsEncrypt{
User: &user{
email: email,
},
Cache: dao,
letsEncrypt.newKey()
letsEncrypt.newRegistration()
})
return letsEncrypt
// 生成lets encrypt 注册账号private key
func (l *LetsEncrypt) newKey() {
if l.Cache.GetKey() != nil {
l.User.key = l.Cache.GetKey()
return
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Error(err)
l.User.key = key
log.Info("设置key缓存")
if err = l.Cache.SetKey(key); err != nil {
log.Error(err)
// 生成lets encrypt的注册信息
// newClient将会执行2次,第一次用于注册账号信息,第二次解决No key Id in jws的bug,/lego/client.go在45行
//
func (l *LetsEncrypt) newRegistration() {
var err error
l.newClient()
if l.Cache.GetRegistration() != nil {
l.User.registration = l.Cache.GetRegistration()
l.newClient()
return
reg, err := l.Client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
log.Error(err)
return
l.User.registration = reg
l.newClient()
// todo 保存信息
if l.User.registration.Body.Status != "valid" {
err = errors.New("Returning registration status not valid ")
return
log.Info("保存registration")
if err = l.Cache.SetRegistration(reg); err != nil {
log.Error(err)
return
return
const (
//CALetsEncryptDebug(默认) Let's Encrypt 测试地址
CALetsEncryptDebug = "https://acme-staging-v02.api.letsencrypt.org/directory"
//CALetsEncrypt Let's Encrypt 正式地址
CALetsEncrypt = "https://acme-v02.api.letsencrypt.org/directory"
func (l *LetsEncrypt) newClient() {
var err error
cfg := lego.NewConfig(l.User)
cfg.CADirURL = CALetsEncrypt
cfg.Certificate.KeyType = certcrypto.RSA2048
client, err := lego.NewClient(cfg)
if err != nil {
log.Error(err)
return
l.Client = client
return
func (l *LetsEncrypt) SetProvider(typ challenge.Type) error {
var err error
switch typ {
case challenge.DNS01:
err = l.Client.Challenge.SetDNS01Provider(NewDNSProviderBestDNS())
case challenge.HTTP01:
err = l.Client.Challenge.SetHTTP01Provider(NewMemoryProviderServer())
default:
err = errors.New("Unknown Challenge types ")
return err
func (l *LetsEncrypt) Obtain(domains []string) ([]byte, []byte, error) {
if len(domains) == 0 {
return nil, nil, errors.New("the domain cannot be empty")
var (
err error
res *certificate.Resource
request := certificate.ObtainRequest{
Domains: domains,
Bundle: true,
//可以在privateKey参数中提供自己的私钥
times := 1
for times > 0 {
times--
if res, err = l.Client.Certificate.Obtain(request); err != nil {
log.Info(err)
if err = l.SetProvider(challenge.HTTP01); err != nil {
continue
//return nil, nil, err
break
fmt.Printf("cert:%v,key:%v", string(res.Certificate), string(res.PrivateKey))
return res.Certificate, res.PrivateKey, err
func (l *LetsEncrypt) Renew(ce, privateKey []byte) ([]byte, []byte, error) {
//私钥重用的话 privateKey 不为空,反之则重新生成
var (
res *certificate.Resource
err error
if res, err = l.Client.Certificate.Renew(certificate.Resource{
PrivateKey: privateKey,
Certificate: ce,
}, true, true, ""); err != nil {
return nil, nil, err
return res.Certificate, res.PrivateKey, nil
最后使用,c,k就是公钥和私钥
certService := NewLetsEncrypt("")
obmains := []string{"xxx.com","xxx2.com"}
c, k, err := certService.Obtain(obmains);
完成!搞定 就可以把拿到的公钥和私钥去配置https了.
阅读终点,创作起航,您可以撰写心得或摘录文章要点写篇博文。去创作
白马啸西施
关注
关注
点赞
收藏
觉得还不错?
一键收藏
打赏
知道了
评论
golang实现自动申请lets encrypt证书
golang 自动申请Lets Encrypt证书
复制链接
扫一扫
专栏目录
Go-lego-采用纯Go编写的Let'sEncrypt客户端痛ACME库
08-14
lego - 采用纯Go编写的Let's Encrypt 客户端痛ACME库
:lock:acmetool,用于ACME(让我们加密)的自动证书获取工具-Golang开发
05-26
acmetool是一个易于使用的命令行工具,用于自动从ACME服务器(例如Let's Encrypt)获取证书。
acmetool是一种易于使用的命令行工具,旨在灵活地集成到您的Web服务器设置中以启用自动验证功能,它是一种易于使用的命令行工具,用于自动从ACME服务器获取证书(例如Let's Encrypt)。
旨在灵活地集成到您的Web服务器设置中以启用自动验证。
与官方的“加密”客户端不同,它不会修改您的Web服务器配置。
:check_mark_button:零停机时间自动更新:check_mark_button:支持任何Web服务器:check_mark_button:完全可自动化:check_mark_button:单文件无依赖性二进制文件:check_mark_button:幂等:check_mark_button:快速设置您可以使用po执行验证
参与评论
您还未登录,请先
登录
后发表或查看评论
Go-用于自动获取证书LetsEncryptSSL证书的Golang库
08-14
用于自动获取证书LetsEncrypt SSL证书的Golang库
autocertdelegate:通过委派的golang.orgxcryptoacmeautocert服务器获取仅供内部使用的TLS服务器的LetsEncrypt TLS证书
03-21
自动证书委托
什么
内部HTTPS服务器具有有效的TLS证书,而不必大惊小怪。
特别是:
我不想成为自己的CA或将所有设备配置为信任新的根。
我不想使用LetsEncrypt DNS挑战,因为这里有大量的DNS提供程序,并且我不希望API客户端提供大量的DNS提供程序,也不想在任何地方配置机密(或其他任何内容)。
我不想将我的内部服务公开给Internet或处理更新防火墙规则以仅允许LetsEncrypt。
如何
参见
它提供了一个客户端,该客户端可插入http.Server以获取证书,并为进行LetsEncrypt ALPN挑战的面向公众的服务器获取服务器处理程序。然后,您将进行水平分割DNS,以将内部IP提供给内部客户端,并将(代表服务器的)公共IP提供给其他所有人(即,让LetsEncrypt进行ALPN挑战)。
然后内部客户只需向委托服务器索要证书,委托服务器就会对自己进行一点挑战
ssl-proxy:简单的零配置SSL反向代理,带有真实的自动生成的证书(LetsEncrypt,自签名,提供)
02-05
ssl代理
具有自动生成的证书的简单单命令SSL反向代理(LetsEncrypt,自签名)
将SSL添加到在VM上运行的事物的便捷简便方法-无论是您的jupyter笔记本还是团队的jenkins实例。 ssl-proxy通过单个命令自动生成SSL证书和代理HTTPS流量到现有HTTP服务器。
用法
带有自动自签名证书
ssl-proxy -from 0.0.0.0:4430 -to 127.0.0.1:8000
这将立即生成自签名证书,并开始将HTTPS流量从代理到 。 无需致电openssl。 如果愿意,它将打印用于您在浏览器中执行手动证书验证的证书的SHA256指纹(在“信任”证书之前
go语言生成ssl证书
蛐蛐的博客
08-07
1114
1.简介
使用https协议时一般需要两个文件,cert.pem和key.pem
cert.pem文件是SSL证书,而key.pem是私钥
可以使用Go标准库中的crypto包群来生成证书与私钥
2.实现
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"os"
"time"
..
linux 使用 go get 报 unrecognized import path “golang.org/x/crypto/XXX“ 的解决方法及手动安装 golang.org/x 包方法
qq_36532540的博客
07-31
1372
问题描述
在linux上使用 go get -u github.com/astaxie/beego ,安装beego框架环境时报 unrecognized import path “golang.org/x/crypto/acme/autocert”: XXX 请求超时:
unrecognized import path "golang.org/x/crypto/acme/autocert": https fetch: Get "https://golang.org/x/crypto/acme/auto
Golang微服务micro 环境搭建,纯小白..
zgf1991 IT新人
10-25
900
go micro 搭建
微服务搭建
目的是跟着github上面的微服务教程走一遍
链接:
构建微服务
第一章 用户服务 第一章中,有个micro new指令,生成模板
micro new --namespace=mu.micro.book --type=srv --alias=user github.com/micro-in-cn/tutorials/microservice-in-micro/p...
使用golang生成证书
yevvzi的博客
01-11
4631
在k8s的源码里看的,记录一下
package main
import (
"bytes"
cryptorand "crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"time"
func main() {
ip := []
用于letencrypt的golang autocert库-Golang开发
05-26
_ _ _ ___(_)_ __ ___ _ __ |
___ ___ ___ _ __ |
| _ / __ |
'_`_ \ |
'_ \ |
| / _ \ / __ / _ \'__ |
__ |
\ __ \ |
| _)|
__ /(_ | __ / | | ______ // _ | _ | | _ | | _ | .__ / | _ | \ ___ | \ ___ \ ___ | _ | \ __ | _ _ _ ___(_) _ __ ___ _ __ | | ___ ___ ___ _ __ | | _ / __ | |'_`_ \ |'_ \ | | / _ \ / __ / _ \'__ | __ | \ __ \ | | | | | | | _)|
__ /(_ | __ / | | ______ // _ | _ | | _ | | _ | .__ / | _ | \ ___ | \ ___ \ ___ | _ | \ __ | | _ | Golang自动库LetsEncrypt SSL证书自动获取证书,并管理Golan
使用Go语言编写的基于ACME的证书颁发机构。-Golang开发
05-26
Boulder-ACME CA这是基于ACME的CA的实现。
ACME协议允许CA自动验证证书的申请人实际上控制了标识符,并允许域持有者颁发Boulder-ACME CA这是基于ACME的CA的实现。
ACME协议允许CA自动验证证书的申请人实际上控制了标识符,并允许域持有者为其域颁发和撤销证书。
Boulder是运行“让我们加密”的软件。
目录概述设置Boulder开发与Certbot一起使用与另一个ACME客户生产贡献许可证概述Boulder分为以下几类:
A专用证书颁发机构(X.509和SSH)和ACME服务器,用于安全的自动证书管理,因此您可以在SSH各处和TLS中使用TLS。-Golang开发
05-26
步骤证书用于安全自动证书管理的在线证书颁发机构和相关工具,因此您可以在任何地方使用TLS。
该存储库用于step-ca,这是一个证书颁发机构,它公开了用于自动Step Certificate的API在线证书颁发机构以及用于安全自动证书管理的相关工具,因此您可以在任何地方使用TLS。
该存储库用于step-ca,这是一个证书颁发机构,它公开用于自动证书管理的API。
它还包含一个golang SDK,用于以编程方式与step-ca进行交互。
但是,您可能要使用step命令行工具来操作step-ca并获取证书,而不是直接使用此低级SDK。
问题?
让我们加密用Go编写的客户端和ACME库-Golang开发
05-26
lego让我们对Go安装二进制文件中编写的客户端和ACME库进行加密要获取二进制文件,只需从发布页面下载适用于您的OS / Arch的最新版本,然后将二进制文件放在某处即可。lego让我们对Go Go安装二进制文件中编写的客户端和ACME库进行加密二进制文件只需从发行页面下载适用于您的OS / Arch的最新版本,然后将二进制文件放在方便的位置即可。
乐高不假设您从中运行它的任何位置。
从docker docker运行xenolf / lego -h从软件包管理器ArchLinux(AUR):yay -S lego注意:lego团队仅正式支持Arch Linux的软件包管理器。
从来源到insta
通过Go语言创建CA与签发证书
期待幸福
07-01
1248
本篇文章中,将描述如何使用go创建CA,并使用CA签署证书。在使用openssl创建证书时,遵循的步骤是 创建秘钥 > 创建CA > 生成要颁发证书的秘钥 > 使用CA签发证书。这种步骤,那么我们现在就来尝试下。首先,会从将从创建 CA 开始。CA 会被用来签署其他证书
接下来需要对证书生成公钥和私钥
然后生成证书:
我们看到的证书内容是PEM编码后的,现在我们有了生成的证书,我们将其进行 PEM 编码以供以后使用:
创建证书
证书的 与CA的 属性有稍微不同,需要进行一些修改
为该证书创建私钥和公钥
快速配置Let's encrypt通配符证书
owenzhang的博客
10-21
1187
本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
利用certbot工具配置Let’s encrypt通配符证书,所域名下所有的子域名都能方便的使用 https证书,而且完全免费。值得关注的是,Let’s encrypt通配符证书只是针对二级域名,并不能针对主域名,如*.hubinqiang.com和hubinqiang.com 被认为是两个域名,如果和我一样使用的是主域名,在申请...
go初学者安装echo框架
热门推荐
qq849635649的博客
02-20
1万+
一、echo简介
go语言中,web框架非常多,但是echo绝对是性能非常好的一种,下面是各种go框架的性能对比
中国有这个框架的翻译版本,不是非常全,但是也基本上差不多了,如果英文基础好的话可以查阅英文版 - 点击打开链接
二、安装
因为golang.org在我们伟大的天朝无法访问的原因,所以按照官网上面的介绍是万万不可能安装成功的,这里我来把我之前安装的步骤整理一下,自
Go-Boulder-一个采用Go编写基于ACME的CA
08-14
Boulder - 一个采用Go编写基于 ACME 的 CA
2020-11-29 golang获取ssl证书信息
Linux运维菜
09-15
1236
简介
目前,很多网站都使用了https,证书的过期检查也是运维需要关注的地方,可以通过直接连接域名,获取证书信息,来判断是否已经更新了证书。
代码
package main
import (
"crypto/tls"
"fmt"
"net"
"os"
"time"
func main() {
args := os.Args
usage := os.Args[0] + " [domain|ip]"
if le...
golang 实现 rsync
最新发布
06-01
Golang 中可以通过 os 和 io 包来实现文件和目录的操作,可以通过 net 包实现网络传输。因此,可以使用这些包来实现一个简单的 rsync 工具。
以下是一个基本的 rsync 工具实现:
```go
package main
import (
"fmt"
"io"
"net"
"os"
"path/filepath"
const (
bufferSize = 1024
func main() {
// 获取源文件和目标文件路径
sourcePath := os.Args[1]
destPath := os.Args[2]
// 打开源文件
sourceFile, err := os.Open(sourcePath)
if err != nil {
fmt.Println("failed to open source file:", err)
return
defer sourceFile.Close()
// 创建目标文件
destFile, err := os.Create(destPath)
if err != nil {
fmt.Println("failed to create dest file:", err)
return
defer destFile.Close()
// 获取源文件信息
sourceStat, err := sourceFile.Stat()
if err != nil {
fmt.Println("failed to get source file stat:", err)
return
// 获取源文件的最后修改时间
modTime := sourceStat.ModTime()
// 写入文件头信息:文件名和最后修改时间
header := fmt.Sprintf("%s|%d\n", filepath.Base(sourcePath), modTime.Unix())
_, err = destFile.WriteString(header)
if err != nil {
fmt.Println("failed to write header:", err)
return
// 读取源文件并写入目标文件
buffer := make([]byte, bufferSize)
for {
bytesRead, err := sourceFile.Read(buffer)
if err == io.EOF {
break
} else if err != nil {
fmt.Println("failed to read source file:", err)
return
_, err = destFile.Write(buffer[:bytesRead])
if err != nil {
fmt.Println("failed to write dest file:", err)
return
fmt.Println("rsync completed successfully!")
```
这个实现比较简单,它只是把源文件的内容直接写入目标文件。在实际应用中,需要考虑更多的情况,例如文件的权限、链接和目录等。此外,还需要使用 net 包实现网络传输,以实现远程 rsync 的功能。
“相关推荐”对你有帮助么?
非常没帮助
没帮助
一般
有帮助
非常有帮助
提交
白马啸西施
CSDN认证博客专家
CSDN认证企业博客
码龄14年
暂无认证
21
原创
22万+
周排名
7万+
总排名
1万+
访问
等级
214
积分
400
粉丝
获赞
评论
11
收藏
私信
关注
试试用AI创作助手写篇关于[golan...]、[开发语言]、[后端]的文章吧
用AI写文章
热门文章
golang 实现大文件上传
1765
golang实现输入json字符串生成struct
1129
golang http服务实现多ip监听,及优雅重启
1122
golang 应用在windows环境实现自我更新
1082
记录一下golang在 ubuntu 通过go build 编译 windows可执行程序
881
分类专栏
笔记
11篇
golang 工具
5篇
最新评论
golang实现WebSSH的功能
m0_59141524:
前端输入命令的时候,输入1也直接当命令处理了,还没等按回车?请问处理方法是什么呢?
go:embed打包前段静态文件夹到二进制,并访问静态资源
白马啸西施:
处理F5刷新问题:
// 静态文件
staticEngine := gin.New()
staticEngine.StaticFS("/static", gin.Dir("./html/index/static", false))
staticEngine.StaticFS("/img", gin.Dir("./html/index/img", false))
staticEngine.LoadHTMLGlob("./html/index/index.html")
// 首页处理
staticEngine.GET("/", func(ctx *gin.Context) {
ctx.HTML(stdhttp.StatusOK, "index.html", nil)
})
// 其他页F5刷新
staticEngine.NoRoute(func(ctx *gin.Context) {
ctx.HTML(stdhttp.StatusOK, "index.html", nil)
})
golang实现输入json字符串生成struct
CSDN-Ada助手:
推荐 云原生入门 技能树:https://edu.csdn.net/skill/cloud_native?utm_source=AI_act_cloud_native
您愿意向朋友推荐“博客详情页”吗?
强烈不推荐
不推荐
一般般
推荐
强烈推荐
提交
最新文章
golang读取图片base64内容
使用golang在centos安装vsftpd服务,并添加虚拟用户
Docker: Failed to get D-Bus connection, Operation not permitted
2023年10篇
2022年11篇
目录
目录
分类专栏
笔记
11篇
golang 工具
5篇
目录
评论
被折叠的 条评论
为什么被折叠?
到【灌水乐园】发言
查看更多评论
添加红包
祝福语
请填写红包祝福语或标题
红包数量
红包个数最小为10个
红包总金额
红包金额最低5元
余额支付
当前余额3.43元
前往充值 >
需支付:10.00元
取消
确定
下一步
知道了
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝
规则
hope_wisdom 发出的红包
打赏作者
白马啸西施
你的鼓励将是我创作的最大动力
¥1
¥2
¥4
¥6
¥10
¥20
扫码支付:¥1
获取中
扫码支付
您的余额不足,请更换扫码支付或充值
打赏作者
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额
抵扣说明:
1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。 2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。
余额充值