跳到主要内容

91 篇博文 含有标签「后端」

后端开发技术文章

查看所有标签

Python 去除文件名称内的特殊字符

· 阅读需 1 分钟
素明诚
Full stack development
import os
import re

def sanitize_filename(filename):
""" 使用'-'替换文件名中的特殊字符和空格 """
return re.sub(r'[^\w.-]', '-', filename)

def rename_files_in_directory(directory):
""" 遍历目录并重命名文件 """
for root, dirs, files in os.walk(directory):
for filename in files:
# 生成新文件名
new_filename = sanitize_filename(filename)
if new_filename != filename:
# 构建完整的文件路径
original_file_path = os.path.join(root, filename)
new_file_path = os.path.join(root, new_filename)
# 重命名文件
os.rename(original_file_path, new_file_path)
print(f"已将文件 {filename} 重命名为 {new_filename}")

def main():
# 获取当前脚本的目录
current_directory = os.path.dirname(os.path.abspath(__file__))
# 开始重命名流程
rename_files_in_directory(current_directory)
print("所有文件处理完毕!")

if __name__ == '__main__':
main()

你可以从 docker inspect 获取哪些信息

· 阅读需 2 分钟
素明诚
Full stack development

对正在运行的 MySQLdocker inspect 后内容如下

[
{
"Id": "92fe41a6dfd5...", // 容器的唯一标识符
"Created": "2024-10-09T08:32:41.659Z", // 容器创建的时间戳
"Path": "docker-entrypoint.sh", // 容器启动时执行的脚本
"Args": ["--default-authentication-plugin=mysql_native_password"], // 脚本参数
"State": {
"Status": "running", // 容器当前状态,正在运行
"Running": true, // 容器是否在运行
"Health": {
"Status": "healthy", // 容器健康状态为健康
"FailingStreak": 0, // 健康检查失败次数
"Log": [] // 健康检查日志
}
},
"Image": "mysql:8.0.36", // 使用的MySQL镜像版本
"Name": "/mysql_server", // 容器名称
"RestartCount": 0, // 重启次数
"HostConfig": {
"NetworkMode": "mysql_mysql", // 网络模式
"PortBindings": { // 端口绑定配置
"3306/tcp": [{ "HostIp": "", "HostPort": "3306" }] // 映射到主机的3306端口
},
"RestartPolicy": {
"Name": "always" // 重启策略,始终重启
}
},
"Config": {
"Env": [ // 环境变量配置
"MYSQL_DATABASE=demo", // 数据库名
"MYSQL_USER=admin", // 用户名
"MYSQL_PASSWORD=123456", // 用户密码
"MYSQL_ROOT_PASSWORD=123456" // 根密码
],
"Cmd": ["--default-authentication-plugin=mysql_native_password"], // 容器启动命令
"Healthcheck": { // 健康检查配置
"Test": ["CMD", "mysqladmin", "ping", "-h", "localhost"], // 使用mysqladmin ping测试
"Interval": 30000000000, // 检查间隔
"Retries": 3 // 重试次数
}
},
"NetworkSettings": {
"IPAddress": "172.27.0.2", // 容器分配的IP地址
"Networks": {
"mysql_mysql": { // 网络配置
"IPAddress": "172.27.0.2", // 网络中的IP地址
"Gateway": "172.27.0.1" // 网关
}
}
}
}
]

四种主要RBAC模型

· 阅读需 2 分钟
素明诚
Full stack development

**角色基于访问控制(RBAC)**是一种广泛使用的访问控制机制,它通过将权限与角色关联,而不是直接与单个用户关联,从而简化了权限管理。RBAC 可以根据不同的复杂度分为多个级别,这些级别能够满足不同安全需求的组织。

RBAC0 - 基础 RBAC

RBAC0 是最简单的 RBAC 模型,实现了 RBAC 的核心概念。在这个模型中,系统管理员将权限分配给角色,然后将角色分配给用户。用户通过其角色获得执行特定任务所需的权限。这种模型是 RBAC 的基本形式,适用于访问控制需求相对简单的环境。

a4392d47861fbd6e00f579f18fbb776a### RBAC1 - 具有角色层级的 RBAC

在 RBAC1 模型中,角色之间可以建立层级关系,形成父子角色的结构。子角色继承父角色的所有权限,并且可以拥有特定的附加权限。这种层级结构使得权限管理更加灵活和精细。例如,某个“高级管理员”角色可以包含多个子角色,如“数据库管理员”和“网络管理员”,这些子角色继承“高级管理员”的通用权限,同时也可以有独立的特定权限。

105defe386ea88c2e592530d56d791d3### RBAC2 - 具有约束的 RBAC

RBAC2 模型在 RBAC 的基础上增加了约束条件,以实现更细粒度的安全控制。这些约束包括角色的互斥性(例如,禁止同一用户同时拥有财务和审计角色),以及基于时间或上下文的约束(例如,某个角色的权限只在特定时间或满足特定条件下有效)。这种模型适用于对安全性有较高要求的环境,能够防止潜在的权限滥用。

c7b9fcc50ff2ce7bffff14f525aa6c08### RBAC3 - 综合 RBAC

RBAC3 是最全面的 RBAC 模型,结合了 RBAC1 中的角色层级和 RBAC2 中的约束规则。它提供了完整的访问控制机制,适用于对安全性和权限管理有严格要求的复杂系统。通过同时引入层级结构和约束条件,RBAC3 能够在确保安全性的同时,提供更大的灵活性和可扩展性。

33d2fe39f4468487d2229a5b08b33151

Python 多线程编程实践

· 阅读需 4 分钟
素明诚
Full stack development

全局解释器锁(GIL)

在 Python 的并发编程中,全局解释器锁(GIL)是一个重要的考量因素。它限制了在 CPython(解释器)中同时只有一个线程执行字节码,从而影响 CPU 密集型任务的性能。尽管如此,GIL 的存在对 I/O 密集型任务影响较小,因为线程在等待 I/O 操作时可以释放 GIL,允许其他线程运行。在此背景下,开发者通常根据任务性质选择合适的并发模式。

对于 I/O 密集型任务,Threading模块和ThreadPoolExecutor提供了较为简单的实现,允许多个线程并行执行 I/O 操作。而asyncio则利用事件循环和协程来处理高并发 I/O 请求,其非阻塞模型在处理大量连接时更为高效。

在 CPU 密集型任务的场景中,multiprocessing模块是更优的选择。它通过创建多个独立进程,每个进程都有自己的 Python 解释器和内存空间,从而绕过 GIL,实现真正的并行计算。这使得multiprocessing能充分利用多核处理器的能力。

并发编程方式

特性threadingmultiprocessingThreadPoolExecutorasyncio
适用场景I/O 密集型CPU 密集型I/O 密集型I/O 密集型
并行性受 GIL 限制真正的并行受 GIL 限制真正的并行(基于协程)
资源管理共享内存进程间内存隔离线程池自动管理单线程事件循环
管理复杂性简单较复杂简单中等复杂(需要理解异步概念)
性能对于 I/O 效率较高对于计算效率较高对于 I/O 效率较高对于大量并发 I/O 操作效率高
适合并发量较小(线程上下文切换开销)大(多个进程)较大(可配置最大线程数)很大(可处理成千上万的连接)
编程模型基于线程基于进程基于线程基于协程

threading

最适合处理 I/O 密集型任务,但在 CPU 密集型任务中性能受限于 GIL。管理简单,适合小规模并发场景。

import threading
import requests


# 下载单个 URL 的函数
def download_url(url):
try:
response = requests.get(url, timeout=5)
print(f"成功下载: {url}, 内容长度: {len(response.content)}")
except Exception as e:
print(f"下载失败: {url}, 错误: {e}")


def main(urls):
threads = []
for url in urls:
thread = threading.Thread(target=download_url, args=(url,))
threads.append(thread)
thread.start()

for thread in threads:
thread.join() # 等待所有线程完成


if __name__ == "__main__":
urls = [f"https://www.example.com/{i}" for i in range(10)]
main(urls)

multiprocessing

适合 CPU 密集型任务,能够实现真正的并行。进程间内存隔离,适合大规模计算,但管理较复杂。

import multiprocessing
import requests


# 下载单个 URL 的函数
def download_url(url):
try:
response = requests.get(url, timeout=5)
print(f"成功下载: {url}, 内容长度: {len(response.content)}")
except Exception as e:
print(f"下载失败: {url}, 错误: {e}")


def main(urls):
with multiprocessing.Pool(processes=5) as pool: # 使用进程池
pool.map(download_url, urls)


if __name__ == "__main__":
urls = [f"https://www.example.com/{i}" for i in range(10)]
main(urls)

ThreadPoolExecutor

提供了简单的线程池管理,适合 I/O 密集型任务。更高效地管理线程,易于使用。

import requests
from concurrent.futures import ThreadPoolExecutor


# 下载单个 URL 的函数
def download_url(url):
try:
response = requests.get(url, timeout=5)
print(f"成功下载: {url}, 内容长度: {len(response.content)}")
except Exception as e:
print(f"下载失败: {url}, 错误: {e}")


def main(urls):
with ThreadPoolExecutor(max_workers=5) as executor:
executor.map(download_url, urls)


if __name__ == "__main__":
urls = [f"https://www.example.com/{i}" for i in range(10)]
main(urls)

asyncio

语法类似于 JS,通过事件循环和协程处理 I/O 密集型任务,能处理大量并发。编程模型相对复杂,需要理解异步编程的概念,但能显著提升 I/O 性能。

import asyncio
import aiohttp


# 异步下载单个 URL 的函数
async def download_url(session, url):
try:
async with session.get(url, timeout=5) as response:
content = await response.read()
print(f"成功下载: {url}, 内容长度: {len(content)}")
except Exception as e:
print(f"下载失败: {url}, 错误: {e}")


async def main(urls):
async with aiohttp.ClientSession() as session:
tasks = [download_url(session, url) for url in urls]
await asyncio.gather(*tasks)


if __name__ == "__main__":
urls = [f"https://www.example.com/{i}" for i in range(10)]
asyncio.run(main(urls))

四种方式对比

对于 I/O 密集型任务asyncioThreadPoolExecutor 是优秀的选择,但 asyncio 更适合处理高并发连接。

对于 CPU 密集型任务multiprocessing 模块允许在 Python 中创建多个进程,每个进程都有自己的 Python 解释器和内存空间。因此,它可以绕过全局解释器锁(GIL),实现真正的并行计算。这使得 multiprocessing 非常适合 CPU 密集型任务,因为它能够充分利用多核处理器的计算能力。

生成自签名 SSL 证书

· 阅读需 5 分钟
素明诚
Full stack development

生成自签名 SSL 证书

生成私钥 (openssl genrsa)

私钥是整个证书体系的基础,用于生成公钥和签署证书。它必须保密,因为拥有私钥就能够解密通过相应公钥加密的所有信息。

生成证书签名请求 (CSR) (openssl req)

CSR 包含证书的相关信息(如组织名、常用名等),以及由对应私钥生成的公钥。CSR 本质上是请求某个证书授权机构(CA)发行证书的正式请求。尽管在自签名过程中不需要外部 CA 的介入,生成 CSR 仍是标准流程的一部分,以确保所有必要信息的包含和正确性。

生成自签名证书 (openssl x509)

自签名证书意味着使用同一把私钥对 CSR 进行签名,即自己是自己的 CA。这样生成的证书可用于测试环境或内部网络,因为它不由外部信任的 CA 签名。在没有外部验证的情况下,自签名证书能够提供加密和服务器身份验证的功能。

生成一个 RSA 私钥

openssl genrsa -out $KEY_FILE 2048

参数说明
genrsa生成 RSA 密钥命令。
-out指定输出文件,后面跟文件名。
$KEY_FILE被 -out 使用,指定私钥的文件名。
2048生成密钥的位数,这里为 2048 位。

创建一个证书签名请求(CSR)

openssl req -new -key $KEY_FILE -out $CSR_FILE -subj "/C=CN/ST=Beijing/L=Beijing/O=Example Corp/OU=IT Department/CN=example.com"

参数说明
req证书请求命令,可以用来创建 CSR 或自签名证书。
-new表示创建一个新的请求。
-key指定使用的私钥文件,用于签署这个请求。
$KEY_FILE由 -key 使用,指向存储私钥的文件。
-out指定输出文件,这里用于指定 CSR 的输出文件。
$CSR_FILE被 -out 使用,指定 CSR 的文件名。
-subj指定在 CSR 或证书中包含的主题详情,格式为 /C=.../ST=.../...

使用 CSR 和私钥生成自签名的证书

openssl x509 -req -days 365 -in $CSR_FILE -signkey $KEY_FILE -out $CRT_FILE

参数说明
x509X.509 证书处理命令。
-req指示输入文件是一个 CSR。
-days指定证书的有效期,这里为 365 天。
365证书的有效天数。
-in指定输入文件,这里是 CSR 文件。
$CSR_FILE由 -in 使用,指定 CSR 的文件名。
-signkey指定用于签署证书的私钥文件。
$KEY_FILE被 -signkey 使用,指定私钥的文件名。
-out指定输出文件,这里用于指定证书的输出文件。
$CRT_FILE被 -out 使用,指定证书的文件名。

测试证书

在使用自签名 SSL 证书时,你需要按照上述三步操作生成密钥、CSR 和证书后,然后在实际的服务器配置中应用这些生成的文件。下面是将生成的自签名证书和密钥应用到常见服务器设置的一些基本步骤:

生成密钥和证书

root.key:私钥文件。

root.csr:证书签名请求。

root.crt:自签名的证书文件。

服务器配置

取决于你的应用服务器或服务类型(如 Apache、Nginx、Tomcat 等),你需要在服务器的配置文件中指定证书和密钥的路径。这里以 Nginx 和 Apache 为例进行说明:

Nginx

对于 Nginx,你可以在其配置文件(通常位于 /etc/nginx/nginx.conf 或某个特定的站点配置文件中)设置 SSL 证书和密钥路径:

server {
listen 443 ssl;
server_name example.com;

ssl_certificate /path/to/root.crt;
ssl_certificate_key /path/to/root.key;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}

这里,ssl_certificatessl_certificate_key 指向你的证书文件和私钥文件。

测试和验证

配置完成后,重启你的 Web 服务器以使更改生效。然后可以使用浏览器或工具(如 curl)来验证 HTTPS 是否正常工作

curl -k https://example.com

选项 -k 允许 curl 忽略 SSL 证书验证错误

OpenSSL 自签名脚本

#!/bin/bash

# 配置变量
KEY_FILE="root.key" # 私钥文件名
CRT_FILE="root.crt" # 证书文件名
CSR_FILE="root.csr" # CSR 文件名
DAYS_VALID=365 # 证书有效天数
COUNTRY="CN" # 国家
STATE="Beijing" # 省份
LOCALITY="Beijing" # 城市
ORG="Example Corp" # 组织
ORG_UNIT="IT Department" # 组织单位
COMMON_NAME="harbor.com" # 公共名称

# 生成私钥
if openssl genrsa -out "$KEY_FILE" 2048; then
echo "私钥生成成功:$KEY_FILE"
else
echo "私钥生成失败" >&2
exit 1
fi

# 创建证书签名请求 (CSR)
if openssl req -new -key "$KEY_FILE" -out "$CSR_FILE" -subj "/C=$COUNTRY/ST=$STATE/L=$LOCALITY/O=$ORG/OU=$ORG_UNIT/CN=$COMMON_NAME"; then
echo "证书签名请求生成成功:$CSR_FILE"
else
echo "CSR 生成失败" >&2
exit 1
fi

# 自签名证书
if openssl x509 -req -days "$DAYS_VALID" -in "$CSR_FILE" -signkey "$KEY_FILE" -out "$CRT_FILE"; then
echo "自签名证书生成成功:$CRT_FILE"
else
echo "证书生成失败" >&2
exit 1
fi

echo "证书生成完毕:"
echo "私钥文件:$KEY_FILE"
echo "证书文件:$CRT_FILE"

Go 使用 ORM 插入一个 NULL 值

· 阅读需 2 分钟
素明��诚
Full stack development

使用 sql.NullXXX 这一家族类

定义了一个User结构体,其中手机号和邮箱字段被定义为sql.NullString。这允许它们接收来自数据库的 NULL 值,Valid字段用于检查值是否为 NULL,如果不为 NULL,可以安全地访问String字段。

package main

import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)

type User struct {
ID int
PhoneNumber sql.NullString // 使用 sql.NullString 来处理可能为 NULL 的手机号
Email sql.NullString // 使用 sql.NullString 来处理可能为 NULL 的邮箱
}

func main() {
// 假设这是数据库连接字符串
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
panic(err)
}
defer db.Close()

// 查询数据库
rows, err := db.Query("SELECT id, phone_number, email FROM users")
if err != nil {
panic(err)
}
defer rows.Close()

for rows.Next() {
var user User
// 使用 Scan 时,确保对应的字段可以接收 sql.NullString
if err := rows.Scan(&user.ID, &user.PhoneNumber, &user.Email); err != nil {
panic(err)
}

// 打印结果,如果字段为NULL,则显示为 "NULL"
fmt.Printf("ID: %d, Phone: %s, Email: %s\n",
user.ID,
if user.PhoneNumber.Valid { user.PhoneNumber.String } else { "NULL" },
if user.Email.Valid { user.Email.String } else { "NULL" },
)
}
}

使用指针

你在 Go 中使用指针来处理可能为 NULL 的数据库字段时,每次访问这些字段的值时确实需要解引用。

package main

import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)

type User struct {
ID uint
PhoneNumber *string // 使用指针允许手机号为 NULL
Email *string // 使用指针允许邮箱为 NULL
}

func main() {
// 假设这是连接数据库的DSN(数据源名)
dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}

// 查询所有用户
var users []User
result := db.Find(&users)
if result.Error != nil {
panic(result.Error)
}

// 打印用户信息
for _, user := range users {
fmt.Printf("ID: %d, Phone: %v, Email: %v\n", user.ID, user.PhoneNumber, user.Email)
}
}

Go 项目打包不同平台和版本

· 阅读需 2 分钟
素明诚
Full stack development

在 Windows 上打包 Windows 版本

go build -o xApp-windows.exe

在 macOS 上打包 macOS 版本

go build -o xApp-macos

在 Linux 上编译 x86 版本

GOARCH=amd64 go build -o xApp-linux-x86

在 Linux 上编译 ARM 版本

GOARCH=arm64 go build -o xApp-linux-arm

Windows 32 位

如果你需要支持 32 位的 Windows 系统,你可以使用 GOARCH=386 来编译 32 位版本的可执行文件。

SET GOARCH=386&& go build -o xApp-windows-32bit.exe

macOS ARM 架构(Apple Silicon)

对于采用 Apple Silicon(M1 芯片)的 macOS 设备,你可以使用 GOARCH=arm64 来编译适用于 ARM 架构的 macOS 可执行文件。

GOOS=darwin GOARCH=arm64 go build -o xApp-macos-arm

CGO 和交叉编译

如果你的 Go 项目使用了 CGO(调用 C 代码),交叉编译可能会更加复杂。你可能需要安装目标平台的 C 编译器和相关工具链。

在这种情况下,你可能需要使用 Docker 来设置适当的交叉编译环境。

外部依赖和静态链接

如果你的项目依赖于外部 C 库,你可能需要考虑静态链接这些库,以确保在目标系统上的可移植性。

你可以使用 -ldflags "-linkmode external -extldflags -static" 标志来实现静态链接。

go build -ldflags "-linkmode external -extldflags -static" -o xApp

Go bcrypt 加密和验证

· 阅读需 2 分钟
素明诚
Full stack development

用户密码操作逻辑

ab8728506ace7f3843be0b7ec061dcee## 解密原理

假设用户加密后的密码是 $2a$10$nOUIs5kJ7naTuTFkBy1veuK0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa

应用程序从数据库读取存储的 bcrypt 哈希字符串,就是上面那一串

bcrypt 库解析这个哈希字符串

  • 提取算法标识符: $2a$
  • 提取成本因子: 10
  • 提取盐值: nOUIs5kJ7naTuTFkBy1veu

bcrypt 解密流程

  • bcrypt 库现在知道它需要使用 $2a$ 算法,成本因子 10,以及盐值 nOUIs5kJ7naTuTFkBy1veu。
  • 应用程序将用户输入的密码,提取的盐值 (nOUIs5kJ7naTuTFkBy1veu) 和成本因子 (10) 传递给 bcrypt。
  • bcrypt 使用提供的盐值和成本因子,根据 $2a$ 算法对用户输入的密码进行哈希计算。
  • bcrypt 返回计算出的哈希值。
  • 程序比较计算出的哈希值和存储的哈希值 (K0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa)。
  • 如果两个哈希值匹配,则密码验证通过。否则,密码验证失败。

流程图如下

2a655cea1bc7fe3d7c3f1996bb0b5c08## Go 加密解密示例代码

package main

import (
"fmt"
"golang.org/x/crypto/bcrypt"
)

func main() {
// 原始密码
password := "MyPassword123"

// 加密密码
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
fmt.Println("加密错误:", err)
return
}
fmt.Println("加密后的密码:", string(hashedPassword))

// 验证密码
err = bcrypt.CompareHashAndPassword(hashedPassword, []byte(password))
if err != nil {
fmt.Println("密码不匹配")
} else {
fmt.Println("密码匹配")
}
}

Go 切片缩容

· 阅读需 3 分钟
素明诚
Full stack development

在 Go 中,由于切片底层使用数组实现,真正的缩容通常指的是减少切片的容量,以释放不需要的内存。这通常通过创建一个新的切片并复制所需数据来实现。

创建新切片并拷贝数据

通过创建一个新的切片,并且将旧切片的数据复制到这个新的切片中。新切片的容量和长度被精确控制。

注意,这里使用make([]Type, len, cap)创建新的切片时,Go 运行时会分配一个全新的底层数组。

这种方式是在你永远不会让这个切片再扩容的时候使用。

func ShrinkSlice(original []int, newLength int) []int {
if newLength > len(original) {
newLength = len(original)
}
newSlice := make([]int, newLength, newLength) // 设置新切片的长度和容量
copy(newSlice, original[:newLength])
return newSlice
}

利用 append 函数

append 函数可以在增加切片长度的同时控制其容量。如果在使用 append 时开始一个新的切片(nil 切片),Go 的编译器和运行时会优化内存分配,通常只会分配到所需的容量。

这里append函数在添加元素超过原始切片容量时会自动分配新的底层数组,并复制原始数据到新数组。即使开始时指定了容量,append操作仍可能触发新数组的创建,这是为了保证切片的扩展不会影响到其他依赖原数组的切片。

func ShrinkSliceUsingAppend(original []int, newLength int) []int {
if newLength > len(original) {
newLength = len(original)
}
newSlice := make([]int, 0, newLength) // 创建一个容量为newLength的空切片
newSlice = append(newSlice, original[:newLength]...)
return newSlice
}

使用全局切片重新切片

如果不担心原始数据的保护,可以通过重新切片(reslicing)操作直接在原切片上操作,这种方法不需要额外的内存分配,但是需要手动管理原始数据。(使用全局切片重新切片)会保持与原始底层数组的直接联系,而前两种方法都会创建一个新的底层数组。

func ShrinkSliceInPlace(original []int, newLength int) []int {
if newLength > len(original) {
newLength = len(original)
}
return original[:newLength:newLength] // 设置新切片的长度和容量相同
}

缩容后,尽量少的使用 CPU

整个实现的核心是希望在后续少触发扩容的前提下,一次性释放尽可能多的内存

func calCapacity(c, l int) (int, bool) { // usage = Deng Ming
// 容量 <=64 缩不缩都无所谓, 因为浪费内存也浪费不了多少
// 你可以考虑调大这个阈值, 或者调小这个阈值
if c <= 64 {
return c, false
}
// 如果容量大于 2048, 但是元素不足一半,
// 降低为 0.625, 也就是 5/8
// 也就是比一半多一点, 和正向扩容的 1.25 倍相呼应
if c > 2048 && (c/l >= 2) {
factor := 0.625
return int(float32(c) * float32(factor)), true
}
// 如果在 2048 以内, 并且元素不足 1/4, 那么直接缩减为一半
if c <= 2048 && (c/l >= 4) {
return c / 2, true
}
return c, false
}

Go strconv 包的主要功能

· 阅读需 2 分钟
素明诚
Full stack development

整数转换

Atoi(ASCII to Integer)将字符串转换为整数

Itoa(Integer to ASCII)将整数转换为字符串

ParseInt将字符串解析为指定基数(进制)的整数

FormatInt将整数格式化为字符串,可指定基数

浮点数转换

ParseFloat将字符串解析为浮点数,可以指定精度(32 位或 64 位)

FormatFloat将浮点数格式化为字符串,可以指定格式和精度

布尔值转换

ParseBool将字符串解析为布尔值

FormatBool将布尔值格式化为字符串

整数转换示例

package main

import (
"fmt"
"strconv"
)

func main() {
// 字符串转整数
s := "1024"
num, err := strconv.Atoi(s)
if err != nil {
fmt.Println("转换错误:", err)
} else {
fmt.Println("转换结果:", num)
}

// 整数转字符串
numStr := strconv.Itoa(num)
fmt.Println("整数转字符串:", numStr)

// 使用 ParseInt 解析十六进制字符串
hexNum, _ := strconv.ParseInt("1f4", 16, 32)
fmt.Println("十六进制 '1f4' 转为十进制:", hexNum)

// 使用 FormatInt 将数字转换为二进制表示的字符串
binStr := strconv.FormatInt(int64(hexNum), 2)
fmt.Println("数字转二进制字符串:", binStr)
}

浮点数转换示例

package main

import (
"fmt"
"strconv"
)

func main() {
// 字符串转浮点数
floatStr := "3.14159"
f, err := strconv.ParseFloat(floatStr, 64)
if err != nil {
fmt.Println("转换错误:", err)
} else {
fmt.Println("转换结果:", f)
}

// 浮点数转字符串
formattedFloat := strconv.FormatFloat(f, 'f', 3, 64)
fmt.Println("浮点数格式化:", formattedFloat)
}

布尔值转换示例

package main

import (
"fmt"
"strconv"
)

func main() {
// 字符串转布尔值
boolStr := "true"
b, err := strconv.ParseBool(boolStr)
if err != nil {
fmt.Println("转换错误:", err)
} else {
fmt.Println("转换结果:", b)
}

// 布尔值转字符串
bStr := strconv.FormatBool(b)
fmt.Println("布尔值转字符串:", bStr)
}