[{"content":"背景 在 Go + GORM 的日常开发中，事务写法通常如下：\nerr := db.Transaction(func(tx *gorm.DB) error { // 业务逻辑 if err := tx.Create(\u0026amp;user).Error; err != nil { return err // 回滚 } // 更多操作... return nil // 提交 }) 这个模型很直觉：返回 nil → 提交事务，返回 error → 回滚事务。\n但最近踩了一个坑：在事务内吞掉了错误并返回 nil，GORM 却报了 commit unexpectedly resulted in rollback。\n问题复现 简化后的伪代码如下：\nerr := db.Transaction(func(tx *gorm.DB) error { // 第一条 SQL：创建记录 if err := tx.Create(\u0026amp;recordA).Error; err != nil { if errors.Is(err, gorm.ErrDuplicatedKey) { // 唯一键冲突，认为是可以接受的，跳过 return nil // ← 问题出在这里 } return err } // 第二条 SQL：创建另一条记录 if err := tx.Create(\u0026amp;recordB).Error; err != nil { return err } return nil }) 预期行为：遇到重复键冲突时，跳过并提交事务。\n实际行为：\nERROR: commit unexpectedly resulted in rollback 事务直接失败，err 不为 nil。\n根因分析 关键在于 GORM（以及底层的数据库驱动）的事务状态机：\nSQL 执行报错时，数据库驱动会将事务标记为 aborted 状态。 PostgreSQL 等数据库在遇到约束违反（如唯一键冲突）后，当前事务即进入 aborted 状态，后续所有操作都会被拒绝，直到执行 ROLLBACK。\nGORM 的 Transaction() 方法看到你返回 nil，就会尝试 COMMIT。\n但 COMMIT 一个已经 aborted 的事务，数据库会返回错误——因为事务已经被隐式回滚了，无法提交。\n时序如下：\ntx.Create(\u0026amp;recordA) → 唯一键冲突，PostgreSQL 标记事务为 aborted\rreturn nil → GORM 尝试 COMMIT\rCOMMIT → 数据库拒绝：事务已经是 aborted 状态\r→ GORM 收到错误，报 \u0026#34;commit unexpectedly resulted in rollback\u0026#34; 核心问题：你吞掉了错误，但事务状态并没有\u0026quot;恢复原状\u0026quot;。数据库驱动已经记住了这个错误。\n解决方案 思路：把\u0026quot;可预期的冲突\u0026quot;包装成一个特殊的错误类型，在事务外处理 第一步：定义业务错误类型\n// ErrDuplicated 重复键冲突的业务错误 var ErrDuplicated = errors.New(\u0026#34;record already exists\u0026#34;) 第二步：事务内返回该错误，而不是 return nil\nerr := db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(\u0026amp;recordA).Error; err != nil { if errors.Is(err, gorm.ErrDuplicatedKey) { return ErrDuplicated // ← 返回业务错误，让事务正常回滚 } return err } if err := tx.Create(\u0026amp;recordB).Error; err != nil { return err } return nil }) 第三步：在事务外部区分处理\nif err != nil { if errors.Is(err, ErrDuplicated) { // 重复键冲突是可预期的，正常处理 log.Println(\u0026#34;记录已存在，跳过\u0026#34;) return nil } // 其他错误正常上报 return err } 为什么这样是对的？ 事务内部：任何错误都如实返回，让 GORM 执行 ROLLBACK，保持事务状态干净。 事务外部：通过错误类型区分\u0026quot;业务可接受的冲突\u0026quot;和\u0026quot;真正的失败\u0026quot;。 不会触碰数据库事务状态机的\u0026quot;禁区\u0026quot;。 总结 做法 结果 事务内吞掉 DB 错误，返回 nil 事务状态已 aborted，COMMIT 必然失败 事务内返回自定义错误，外部判断处理 事务正常 ROLLBACK，业务逻辑正确分流 记住：GORM 事务中，return nil 永远意味着\u0026quot;请提交\u0026quot;。如果你不能提交，就不要返回 nil。\n即使某个错误在业务上是\u0026quot;可以接受的\u0026quot;，也要在事务外部去接受它，而不是在事务内部悄悄吞掉它。\n","date":"2026-05-28T10:07:03+08:00","permalink":"/gorm-%E4%BA%8B%E5%8A%A1%E4%B8%AD%E7%9A%84%E9%9A%90%E8%97%8F%E9%99%B7%E9%98%B1return-nil-%E4%B8%8D%E4%BB%A3%E8%A1%A8%E6%B2%A1%E4%BA%8B%E5%8F%91%E7%94%9F/","title":"GORM 事务中的隐藏陷阱：return nil 不代表没事发生"},{"content":"CentOS Stream 9、Rocky Linux 9 等 rhel 9 系统。在 Docker 容器的 CentOS7 镜像执行 yum 操作 特别慢。 是因为 open files 太大了。\n先查看之前的 ulimit\ndocker run -it --tty --network host --name centos7 centos:centos7.9.2009 /bin/bash\rulimit -a 输出\ncore file size (blocks, -c) unlimited\rdata seg size (kbytes, -d) unlimited\rscheduling priority (-e) 0\rfile size (blocks, -f) unlimited\rpending signals (-i) 30537\rmax locked memory (kbytes, -l) 8192\rmax memory size (kbytes, -m) unlimited\ropen files (-n) 1073741816\rpipe size (512 bytes, -p) 8\rPOSIX message queues (bytes, -q) 819200\rreal-time priority (-r) 0\rstack size (kbytes, -s) 8192\rcpu time (seconds, -t) unlimited\rmax user processes (-u) unlimited\rvirtual memory (kbytes, -v) unlimited\rfile locks (-x) unlimited 替换源\nfor f in /etc/yum.repos.d/*.repo\rdo\rmv -f \u0026#34;$f\u0026#34; \u0026#34;${f}.backup\u0026#34;\rdone\rcurl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.huaweicloud.com/artifactory/os-conf/centos/centos-7.repo\ryum makecache -y yum update -y 直接卡死在 Updating : glibc-2.17-326.el7_9.3.x86_64\n现在我们重新加上 ulimit\ndocker run -it --tty --network host --name centos7 --ulimit \u0026#34;nofile=1024:1048576\u0026#34; centos:centos7.9.2009 /bin/bash\rulimit -a 输出\ncore file size (blocks, -c) unlimited\rdata seg size (kbytes, -d) unlimited\rscheduling priority (-e) 0\rfile size (blocks, -f) unlimited\rpending signals (-i) 30537\rmax locked memory (kbytes, -l) 8192\rmax memory size (kbytes, -m) unlimited\ropen files (-n) 1024\rpipe size (512 bytes, -p) 8\rPOSIX message queues (bytes, -q) 819200\rreal-time priority (-r) 0\rstack size (kbytes, -s) 8192\rcpu time (seconds, -t) unlimited\rmax user processes (-u) unlimited\rvirtual memory (kbytes, -v) unlimited\rfile locks (-x) unlimited 执行上面的步骤，就不会卡死了。\n参考 https://github.com/moby/moby/issues/45838\n","date":"2026-02-03T10:02:00+08:00","permalink":"/rhel-9-%E7%B3%BB%E7%BB%9F-docker-%E8%BF%90%E8%A1%8C-centos-yum-%E5%91%BD%E4%BB%A4%E5%8D%A1%E6%AD%BB/","title":"rhel 9 系统 Docker 运行 CentOS yum 命令卡死"},{"content":" pg_dump pg_dump 导出 同时 去掉 public 符号 pg_dump -d d_test --inserts --column-inserts --quote-all-identifiers -T public.goose_db_version -T public.goose_db_version_id_seq | sed -e \u0026#39;/^--.*/d\u0026#39; -e \u0026#39;/^SET /d\u0026#39; -e \u0026#39;/^[[:space:]]*$/d\u0026#39; -e \u0026#39;/^SELECT pg_catalog./d\u0026#39; -e \u0026#39;/^ALTER TABLE .* OWNER TO \u0026#34;postgres\u0026#34;;/d\u0026#39; -e \u0026#39;s/\u0026#34;public\u0026#34;\\.//g\u0026#39; \u0026gt; /tmp/d_test.sql ","date":"2025-05-15T15:45:00+08:00","permalink":"/postgresql-%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/","title":"postgresql 常用命令"},{"content":"查看某个包的所有版本\ngo list -m -versions \u0026lt;pkg\u0026gt; go list -m -versions github.com/go-kratos/kratos/v2\n查看某个二进制的构建信息\ngo version -m \u0026lt;path\u0026gt; go version -m /usr/bin/go/bin/go\n","date":"2025-04-24T16:29:00+08:00","permalink":"/golang-go%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/","title":"golang  go常用命令"},{"content":"缓存键 (proxy_cache_key): proxy_cache_key 指定了 NGINX 如何根据请求生成一个缓存键。 缓存键 (proxy_cache_key) proxy_cache_key 指定了 NGINX 如何根据请求生成一个缓存键。 哈希计算 对缓存键进行哈希处理，常使用 MD5 算法。 例如，如果缓存键是 example.com/image.png , 进行 MD5 哈希后会得到一个 32 字符的哈希值（c877fe850c17cb1a4d0b158dd2b64cfb）。 这里有一种特殊情况：\n当第一次缓存的时候，得到的哈希，就是MD5(example.com/image.png) 第二次缓存，如果源站有 Vary 响应头 并且 Vary 的值 与第一次缓存的不一致，则会通过 Vary 的值 计算一个新的哈希，如果没有 Vary 响应头，则不会计算新的哈希。 就是说，如果源站响应头没有 Vary，则每次的哈希都是一样的，缓存的是同一个文件，否则会生成新的缓存文件。 就是说，无论第一次的 Accept-Encoding 是什么，都不会被Accept-Encoding影响，第二次会因为Accept-Encoding影响。 第一次缓存算法：\n// 直接对缓存键哈希 // cacheKey: 缓存键 func getCacheFileHash(cacheKey string) string { keyHash := md5.Sum([]byte(cacheKey)) return hex.EncodeToString(keyHash[:]) } 第二次缓存算法：\n// 计算缓存文件哈希\r//\r// cacheKey: 缓存键\r// vary: 响应头的Vary字段 Accept-Encoding | Accept-Charset | Accept-Language | other\r// varyValue: deflate, gzip\rfunc getCacheFileHash(cacheKey string, vary string, varyValue string) string {\r// 转小写 并且去除两边空格\rvary = strings.TrimSpace(strings.ToLower(vary))\r// 通过逗号或者空格分隔，然后再通过逗号join\r// 先转小写 并且去除两边空格\rvaryValue = strings.TrimSpace(strings.ToLower(varyValue))\rvaryValueList := strings.FieldsFunc(varyValue, func(r rune) bool {\rreturn r == \u0026#39;,\u0026#39; || unicode.IsSpace(r)\r})\r// 再去掉空白字符\rvalueList := make([]string, 0, len(varyValueList))\rfor _, s := range varyValueList {\rif s == \u0026#34;\u0026#34; {\rcontinue\r}\rvalueList = append(valueList, s)\r}\rvaryValue = strings.Join(valueList, \u0026#34;,\u0026#34;)\r// 开始 计算\r// hexEncode(md5(md5(cacheKey) + vary + \u0026#34;:\u0026#34; + varyValue + \u0026#34;\\r\\n\u0026#34;))\r// example.com/image.png Accept-Encoding deflate, gzip\r// = hexEncode(md5(md5(\u0026#34;example.com/image.png\u0026#34;) + \u0026#34;accept-encoding:deflate,gzip\\r\\n\u0026#34;))\rbuf := bytes.NewBuffer(nil)\rkeyHash := md5.Sum([]byte(cacheKey))\rbuf.Write(keyHash[:])\rbuf.WriteString(vary)\rbuf.WriteString(\u0026#34;:\u0026#34;)\rbuf.WriteString(varyValue)\rbuf.WriteString(\u0026#34;\\r\\n\u0026#34;)\rnewHash := md5.Sum(buf.Bytes())\rreturn hex.EncodeToString(newHash[:])\r} 路径层次结构\n在 proxy_cache_path 配置中，我们可以看到 levels=1:2。这表示：\n生成的缓存路径会被拆分成两级目录结构。 第一层目录使用哈希值的倒数第一位字符（b）。 第二层目录使用哈希值的倒数第三位和倒数第二位字符（cf）。 最终的缓存文件名将使用哈希值（c877fe850c17cb1a4d0b158dd2b64cfb）。 例如，基于缓存键 example.com/image.png，假设其哈希值为 c877fe850c17cb1a4d0b158dd2b64cfb，则缓存文件的路径为：\n/var/cache/nginx/b/cf/c877fe850c17cb1a4d0b158dd2b64cfb\n参考：\nnginx反向代理缓存实现，md5加密规则，gzip压缩问题：[https://blog.51cto.com/u_14210437/11868562][1]\n源代码：[https://github.com/nginx/nginx/blob/master/src/http/ngx_http_file_cache.c][2]\n","date":"2024-12-13T09:28:00+08:00","permalink":"/nginx-%E7%BC%93%E5%AD%98-%E6%96%87%E4%BB%B6%E5%90%8D%E5%92%8C%E8%B7%AF%E5%BE%84%E8%AE%A1%E7%AE%97/","title":"Nginx 缓存 文件名和路径计算"},{"content":"什么是 dnsdist dnsdist 是一个现代化的 UNIX 守护进程,主要用于 DNS 流量的负载均衡和流量整形。它是由 PowerDNS 团队开发的一款开源软件,可以帮助您构建高性能、高可用的 DNS 基础设施。\ndnsdist 的主要功能包括:\n负载均衡：dnsdist 可以将 DNS 查询均匀地分配到多个后端服务器上,提高整体的处理能力。它支持多种负载均衡算法,如 leastOutstanding、firstAvailable、wrandom 等。\n流量整形：dnsdist 可以根据各种规则对 DNS 流量进行限制和阻挡,比如限制每个客户端的查询速率、阻挡恶意流量等。这有助于保护您的 DNS 基础设施免受 DDoS 攻击。\n动态规则生成：dnsdist 可以根据实时监控的流量情况,自动生成并应用动态的阻挡规则,以应对突发的攻击或异常流量。这使得您的 DNS 系统能够更好地抵御各种安全威胁。\n灵活的配置：dnsdist 的配置是通过 Lua 脚本来实现的,这使得它非常灵活和可扩展。您可以根据自己的需求编写定制的 Lua 脚本,实现各种复杂的流量管理策略。\n缓存: dnsdist 实现了一个简单但高效的数据包缓存,可以大幅提高查询响应速度,减少带宽使用和服务器负载。\n##安装##\n当前最新版是1.9.x 这里以 centos9 安装 dnsdist 1.9.x为例\ndnf install epel-release \u0026amp;\u0026amp; curl -o /etc/yum.repos.d/powerdns-dnsdist-19.repo https://repo.powerdns.com/repo-files/el-dnsdist-19.repo \u0026amp;\u0026amp; dnf install dnsdist 编辑配置文件\nvim /etc/dnsdist/dnsdist.conf\nlocal downstreamServers = { \u0026#34;10.20.0.50\u0026#34;, \u0026#34;10.20.0.51\u0026#34; } addCapabilitiesToRetain(\u0026#34;CAP_SYS_ADMIN\u0026#34;) addCapabilitiesToRetain(\u0026#34;CAP_NET_ADMIN\u0026#34;) -- 设置访问控制 setACL(\u0026#34;0.0.0.0/0\u0026#34;) -- 设置UDP缓存区大小 setUDPSocketBufferSizes(134217728,134217728) -- 设置服务器策略 setServerPolicy(leastOutstanding) -- 设置缓存 local pc = newPacketCache(10000000, {maxTTL=10000000, minTTL=0, temporaryFailureTTL=10, staleTTL=10, dontAge=true, cookieHashing=false}) getPool(\u0026#34;\u0026#34;):setCache(pc) -- 添加本地监听和下游服务器 function addServers(i) for k, v in ipairs(downstreamServers) do newServer({address=v, name=\u0026#39;dns\u0026#39;..k, checkInterval=1, checkType=\u0026#34;ANY\u0026#34;, checkName=\u0026#34;.\u0026#34;, useProxyProtocol=true}) end end function addListeners(i) addLocal(\u0026#34;0.0.0.0:53\u0026#34;, {reusePort=true, sockets=64}) end local cpuCores = tonumber(io.popen(\u0026#34;nproc\u0026#34;):read(\u0026#34;*n\u0026#34;)) if not cpuCores or cpuCores \u0026lt; 1 then cpuCores = 1 end for i = 1, cpuCores do addListeners(i) addServers(i) end -- 以下配置内容可选 -- 启用统计和监控界面 --[[ webserver(\u0026#34;0.0.0.0:8083\u0026#34;) setWebserverConfig({password=hashPassword(\u0026#34;3gR#v8P!xWzL9kDt\u0026#34;), apiKey=hashPassword(\u0026#34;!dNVcJHks!xWdSKs\u0026#34;), acl=\u0026#34;0.0.0.0/0\u0026#34;}) --]] -- 设置QPS限制 --[[ addAction(NotRule(MaxQPSRule(300000)), DropAction()) --]] -- 开启动态规则过滤 --[[ local bpf = newBPFFilter({ipv4MaxItems=10240, ipv6MaxItems=10240, qnamesMaxItems=1024, cidr4MaxItems=1024,cidr6MaxItems=1024,}) setDefaultBPFFilter(bpf) local dbr = dynBlockRulesGroup() dbr:setQueryRate(200, 1, \u0026#34;Exceeded query rate\u0026#34;, 20) dbr:setRCodeRate(DNSRCode.NXDOMAIN, 20, 10, \u0026#34;Exceeded NXD rate\u0026#34;, 60) dbr:setRCodeRate(DNSRCode.SERVFAIL, 20, 10, \u0026#34;Exceeded ServFail rate\u0026#34;, 60) dbr:setQTypeRate(DNSQType.ANY, 5, 10, \u0026#34;Exceeded ANY rate\u0026#34;, 60) dbr:setResponseByteRate(10000, 10, \u0026#34;Exceeded resp BW rate\u0026#34;, 60) function maintenance() dbr:apply() end --]] 启动dnsdist\nsystemctl start dnsdist\n设置自启\nsystemctl enable dnsdist\n性能调优 调整 Linux 的网络参数：在sysctl.conf 添加以下内容，然后执行 sysctl -p 加载\nnet.core.somaxconn = 65535\rnet.core.netdev_max_backlog = 16384\rnet.core.rmem_max = 134217728\rnet.core.rmem_default=1048576\rnet.core.wmem_max = 134217728\rnet.core.wmem_default = 1048576 ","date":"2024-06-28T17:51:00+08:00","permalink":"/dnsdist-dns%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E5%99%A8/","title":"dnsdist dns负载均衡器"},{"content":"goimports-reviser 是一个管理 import 分组的工具，当前也已支持（ gofmt ）格式化代码。github 地址 [https://github.com/incu6us/goimports-reviser][https_github.com_incu6us_goimports-reviser]\n这里来\n安装 goimports-reviser go install -v github.com/incu6us/goimports-reviser/v3@latest\r设置 File Watcher Goland 打开 ***文件 →设置 → 工具→File Watcher ***\n新增一个自定义 ，参数如下\n配置项 值 说明 文件类型 Go文件 作用域 当前文件 程序 goimports-reviser goimports-reviser 可执行程序路径 实参 -rm-unused -format -use-cache -company-prefixes=git.test.com $FilePath$ 这里没有加 -set-alias ，根据个人需求，prefixes是私有库地址 要刷新的输出路径 $FileName$ 这个一定要加，不然会容易出现 “文件缓存冲突” 自动保存编辑的文件以触发观察程序 false 取消勾选，否则容易出现还没编辑完成就被格式化了 如图所示\n取消保存时的操作 Goland保存时会默认重新格式化代码和*优化 import ，*因为 goimports-reviser 会帮我们做 goimports 和 gofmt 操作，我们保存时没必要再重新执行这些功能。这个根据个人情况修改\nGoland 打开 ***文件 →******设置 → 工具→保存时的操作 ，***取消勾选 重新格式化代码、优化 import\n如图所示\n","date":"2023-10-28T15:51:00+08:00","permalink":"/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/","title":"使用 File Watcher 和 goimports-reviser 自动格式化 Golang 代码"},{"content":"Windows下Goland 等Jetbains IDEA运行测试用例时，出现如下问题之一\n未进行任何测试（no test were run） 输出文本被截断 多出空格 最开始以为是用的fmt.Print打印，换成t.Log还是不行，换成Idea也不行，网站一直没找到答案\n最后参照[Goland测试用例打印出现空格和显示不全问题 (zdzyzy.com)][Goland_ _zdzyzy.com]\n修复成功，这里记录一下。\n先 双击shift 输入registry\n然后点击 注册表（Registry）\n找到 go.run.processes.with.pty 去掉勾选\n点击关闭，然后重启Goland\n","date":"2022-09-01T16:49:00+08:00","permalink":"/goland-no-test-were-run/","title":"Goland (Jetbains IDEA) 在Windows下测试用例出现空格、未进行任何测试（no test were run）、输出被截断"},{"content":"三个集合，颜色分别为白、灰、黑；\n1.把所有对象放入白色集合；\n2.遍历GC-Roots直接可访问到的节点，将其从白色集合放入灰色集合；\n3.遍历灰色集合，将灰色集合引用的对象从白色放入灰色集合，最后将自己放入黑色集合；\n4.重复3操作,直到灰色集合中的元素为空;\n5.通过写屏障检测对象变化，重复以上操作；\n6.回收白色集合中的对象\n","date":"2021-08-27T16:25:55+08:00","permalink":"/golang-gc/","title":"Golang三色标记法"},{"content":"一、下载软件 咪咕音乐的3D音乐只能用咪咕音乐听，有没有什么办法能让我们在任何APP都能听了，最近发现一个软件可以直接解密\n地址如下：\n[https://gitee.com/lagoushangdiao/migu3d-decrypt/releases/latest][https_gitee.com_lagoushangdiao_migu3d-decrypt_releases_latest]\n二、使用 使用方法：\nA migu 3d audio decrypt cli.\rUsage:\rmigu3d-decrypt [command]\rAvailable Commands:\rcompletion Generate the autocompletion script for the specified shell\rdecrypt Decrypt local file, only support [mg3d|m4a|wav] format\rhelp Help about any command\rserve Start a http server\rupdate Check for update\rFlags:\r-h, --help help for migu3d-decrypt\r-t, --toggle Help message for toggle\r-v, --version version for migu3d-decrypt\rUse \u0026quot;migu3d-decrypt [command] --help\u0026quot; for more information about a command.\r这里解密 梁静茹-勇气.wav，歌曲ID为3403\n下载地址\n链接：[https://pan.baidu.com/s/1z7hSjPlmcztEJkZW5lUvEA][https_pan.baidu.com_s_1z7hSjPlmcztEJkZW5lUvEA]\n提取码：6666\n解密命令如下\n直接解密\n./migu3d-decrypt.exe decrypt ./梁静茹-勇气.wav 使用歌曲id解密\n./migu3d-decrypt.exe decrypt --song-id 3403 ./梁静茹-勇气.wav 使用filekey解密\n./migu3d-decrypt.exe decrypt --file-key 29B55E6F0BDF79EA5D7EDBCA4C2B7404 ./梁静茹-勇气.wav ![image-6.png][]解密结果\n三、搭建HTTP服务 首先下载对应平台的软件，我这里是windows\n然后运行软件，命令如下\n./migu3d-decrypt.exe serve\n再复制http地址（内网or公网大家自行更改）\n比如 http://127.0.0.1:10085\n确定保存就可以了，然后就可以播放or下载3D音乐了\n","date":"2021-07-31T19:16:00+08:00","permalink":"/migu3d-decrypt/","title":"咪咕音乐 3D音乐解密"},{"content":"Redis没有使用C语言传统的字符串表示，而是构建了一种简单动态字符串（SDS）的类型，C字符串只会作为字符串字面量在一些无需对字符串值进行修改的地方，比如打印日志。\nSDS用来保存数据库中的字符串值、用作缓冲区：AOF缓冲区，客户端状态缓冲区。\nSDS定义：\nstruct sdshdr { int len;//记录buf数组中已使用字节的数量，等于SDS所保存字符串的长度 int free;//记录buf数组中未使用字节的数量 char buf[];//字节数组，用于保存字符串}\rSDS遵循C字符串以空字符结尾的惯例，保留空字符的1字节空间，不计算在len属性里面，并且为空字符分配额外的1字节空间。\nSDS与C字符串的区别\n常数复杂度获取字符串长度 杜绝缓冲区溢出 减少修改字符串时带来的内存重分配次数 二进制安全 兼容部分C字符串函数 常数复杂度获取字符串长度\nC语言：遍历整个字符串\nSDS：读取len属性\n杜绝缓冲区溢出\nC语言：不记录自身长度，没有判断空间是否满足修改后的大小\nSDS：api会先检查SDS的空间是否满足修改所需的需求\n减少修改字符串时带来的内存重分配次数\nC语言拼接操作：先通过内存分配扩展底层数组空间大小\nC语言截断操作：截断后释放不再使用的那部分空间\nSDS：空间预分配和惰性空间释放\n空间预分配\n如果对SDS修改后，len小于1MB，将会分配len属性同样的大小的未使用空间，free = len,buf的长度为len + free + 1byte = 2 * len + 1byte，如果len大于等于1MB，将会分配1MB的未使用空间,free = 1MB,buf的长度为len+free+1byte=len+1MB+1byte\n惰性空间释放\n惰性空间释放用于优化SDS的字符串缩短操作，当缩短字符串时，程序并不立即使用内存重新分配来回收缩短后多出来的字节，而是使用free记录下来，避免了缩短字符串所需的的内存重新分配操作，并且为将来可能有得增长操作提供了优化。同时，SDS也提供了相应的API，在我们有需要时，真正的释放SDS的未使用空间，不用担心内存浪费。\n二进制安全\nC语言：字符必须符合某种编码，并且除了字符串末尾，不能包含空字符，所以C字符串只能保存文本数据\nSDS：使用buf来存字节数组，len来判断字符串长度，字符串是否结束，所以可以保存文本数据和二进制数据\n兼容部分C字符串函数\nSDS的buf因为以空字符结尾，所以可以兼容部分C字符串函数\nC字符串 SDS 获取字符串长度的复杂度为 O(N) 获取字符串长度的复杂度为 O(1) API是不安全的，可能会造成缓冲区溢出 API是安全的，不会造成缓冲区溢出 修改字符串长度N次必然需要执行N次内存重分配 修改字符串长度N次最多需要执行N次内存重分配 只能保存文本数据 可以保存文本或者二进制数据 可以使用所有 string.h 库中的函数 可以使用一部分 string.h 库中的函数 C字符串和SDS之间的区别\n","date":"2021-05-31T10:11:19+08:00","permalink":"/redis-sds/","title":"（1）Redis SDS 简单动态字符串"},{"content":"主要引用[https://blog.csdn.net/djzhao627/article/details/102812783][https_blog.csdn.net_djzhao627_article_details_102812783]\n安卓手机需要root\n打开fiddler,访问xxx.xxx.xxx.xxx:8888下载证书\n1.把cer证书转换为pem证书\nopenssl x509 -inform DER -in FiddlerRoot.cer -out cacert.pem\r2.进行MD5的hash显示\n查看openssl版本 openssl version\nopenssl版本在1.0以上的版本的执行这一句\nopenssl x509 -inform PEM -subject_hash_old -in cacert.pem\ropenssl版本在1.0以下的版本的执行这一句\nopenssl x509 -inform PEM -subject_hash -in cacert.pem\r3.文件重命名 为hash加.0 ,我这里hash为e5c3944b\nmv cacert.pem e5c3944b.0\r4.将证书放入/system/etc/security/cacerts，再重启手机，就可以看到系统证书里面有该证书了\n","date":"2020-11-16T17:34:00+08:00","permalink":"/fiddler-%E5%AE%89%E5%8D%93%E8%AF%81%E4%B9%A6%E5%AE%89%E8%A3%85%E5%88%B0%E7%B3%BB%E7%BB%9F%E8%AF%81%E4%B9%A6/","title":"fiddler 安卓证书，安装到系统证书"},{"content":"阿里系的app，用的mtopsdk，默认Spdy是开启的，fiddler是不显示网络请求的。\n今天偶然搜到可以用frida hook，把Spdy关闭。这里记一下过程：\n本文主要引用[https://www.jianshu.com/p/fa14e8063f79][https_www.jianshu.com_p_fa14e8063f79]的过程\n要求 安卓手机需要已经root\n首先安装frida模块和frida-tools工具,需要python3\npip3 install fridapip3 install frida-tools 如果下载比较慢，可以使用国内清华大学镜像\npip3 install frida -i https://pypi.mirrors.ustc.edu.cn/simple/pip3 install frida-tools -i https://pypi.mirrors.ustc.edu.cn/simple/ 然后下载frida-server，这个是要运行在安卓手机上的，首先查看cpu类型\n命令如下\nadb shellgetprop ro.product.cpu.abi 我这里是arm64\nfrida-server 下载地址是[https://github.com/frida/frida/releases][https_github.com_frida_frida_releases] 目前最新版是14.0.8\n我下载的是 [frida-server-14.0.8-android-arm64.xz][]\n下载后，把文件解压，再push到安卓手机上\nadb push frida-server-14.0.8-android-arm64 /data/local/tmp/frida-server14.0.8 然后adb shell进入虚拟机：\ncd /data/local/tmp/ //进入frida-server所在目录chmod 777 frida-server14.0.8 //赋予权限./frida-server14.0.8 \u0026amp; //启动运行 重新打开一个cmd窗口，本机执行 frida-ps -U 应该能看到模拟器上启动的包名。\n然后启动一段python代码\nimport frida, sys jscode = \u0026quot;\u0026quot;\u0026quot; Java.perform(function () { var SwitchConfig = Java.use('mtopsdk.mtop.global.SwitchConfig'); SwitchConfig.isGlobalSpdySwitchOpen.overload().implementation = function(){ var ret = this.isGlobalSpdySwitchOpen.apply(this, arguments); console.log(\u0026quot;isGlobalSpdySwitchOpenl \u0026quot;+ret) return false } }) \u0026quot;\u0026quot;\u0026quot; def on_message(message, data): if message['type'] == 'send': print(\u0026quot;[*] {0}\u0026quot;.format(message['payload'])) else: print(message) process = frida.get_usb_device().attach('fm.xiami.main') script = process.create_script(jscode) script.on('message', on_message) script.load() sys.stdin.read() 然后我们就可以测试抓包了。\n[frida-hook.py][][下载][frida-hook.py]\n[frida-server-14.0.8-android-arm64.xz] [frida-server-14.0.8-android-arm64.xz 1][下载][frida-server-14.0.8-android-arm64.xz 1]\n","date":"2020-11-16T17:20:00+08:00","permalink":"/%E8%99%BE%E7%B1%B3%E9%9F%B3%E4%B9%90%E7%AC%94%E8%AE%B0/","title":"虾米音乐fiddler显示网络请求"},{"content":"这两天在写一个cocos项目，打包成安卓启动黑屏，用Android Studio 打包也是黑屏，报错是\n2020-09-16 16:00:43.693 6525-6558/com.wangjian.hemusic E/jswrapper: ScriptEngine::evalString catch exception:\r2020-09-16 16:00:43.702 6525-6558/com.wangjian.hemusic E/jswrapper: ERROR: Uncaught Error: Cannot find module 'index.js', location: assets/main/index.js:0:0\rSTACK:\r[0]o@assets/main/index.js:11\r[1]o@assets/main/index.js:9\r[2]anonymous@assets/main/index.js:19\r[3]anonymous@jsb-adapter/jsb-engine.js:3322\r[4]download@jsb-adapter/jsb-engine.js:3333\r[5]downloadScript@jsb-adapter/jsb-engine.js:3321\r[6]anonymous@jsb-adapter/jsb-engine.js:3472\r[7]anonymous@jsb-adapter/jsb-engine.js:3149\r[8]readFile@jsb-adapter/jsb-engine.js:3116\r[9]readJson@jsb-adapter/jsb-engine.js:3137\r[10]parseJson@jsb-adapter/jsb-engine.js:3430\r[11]download@jsb-adapter/jsb-engine.js:3333\r[12]downloadJson@jsb-adapter/jsb-engine.js:3442\r[13]downloadBundle@jsb-adapter/jsb-engine.js:3464\r[14]a@src/cocos2d-jsb.js:10125\r[15]anonymous@src/cocos2d-jsb.js:10135\r[16]retry@src/cocos2d-jsb.js:11539\r[17]download@src/cocos2d-jsb.js:10120\r[18]load@src/cocos2d-jsb.js:10773\r[19]51.e.exports@src/cocos2d-jsb.js:105842020-09-16 16:00:43.702 6525-6558/com.wangjian.hemusic E/jswrapper: ScriptEngine::evalString script main.js, failed!\r开始以为是creator的问题，重新打开其它项目构建编译就可以\n然后从代码上面入手，require有的地方我用的是相对路径，全部改为模块名字，还是黑屏\n百度了好久也找不到错误，然后以为是ts检查的问题，去掉 @ts-check，还是黑屏\n最后 发现 是因为Cocos Creator JS 这个插件 ，支持require跳转，帮我在一些自定义模块（不是cc.class）的顶部require了其它模块！！去掉它，还是老老实实在代码中require模块。\n启动就没有黑屏了\n","date":"2020-09-16T17:20:00+08:00","permalink":"/cocos-creator-%E6%89%93%E5%8C%85%E5%AE%89%E5%8D%93%E9%BB%91%E5%B1%8F/","title":"cocos creator 打包安卓黑屏"},{"content":"The Sound of Silence （可以下载无损） 已经凉了## 一个界面敲好看的，可以在线听歌和下载的网站，支持网易云、QQ音乐。\n地址是[http://music.jsososo.com/][http_music.jsososo.com]\nMyFreeMP3 （可以下载无损） 已经凉了 适配手机，支持收听和下载国内外大部分音乐平台的网站。\n地址是[http://tool.liumingye.cn/music/][http_tool.liumingye.cn_music]\nmmPlayer （只能听，不能下载 ） 一款支持网易云音乐收听和查看评论的网站。\n地址是[https://netease-music.fe-mm.com/][https_netease-music.fe-mm.com]\n搜你妹音乐网站（可以下载无损） 已经凉了 适配手机，支持国内五大音乐平台，QQ音乐支持付费专辑下载。\n[https://wsmusic.sounm.com/][https_wsmusic.sounm.com]\n果壳音乐搜索（可以下载无损） 已经凉了 支持酷我、网易云、QQ搜索和下载，QQ音乐也支持付费专辑下载。\n[http://music.ghpym.com/][http_music.ghpym.com]\n下歌吧音乐下载平台（可以下载无损） 线路A：网易云音乐\n线路B：酷我音乐\n线路C：咪咕音乐\n[http://music.y444.cn/][http_music.y444.cn]\n[https://xiageba.com/][https_xiageba.com]\n无损生活 [https://www.flac.life/][https://www.flac.life/]\nmusicBox 项目开源地址：https://github.com/xfmujie/musicBox\n笒鬼鬼音乐盒：[https://cenguigui.cn/music/][https://cenguigui.cn/music/] 昔枫音乐盒：[https://mu-jie.cc/musicBox/][https://mu-jie.cc/musicBox/] GD音乐台 https://music.gdstudio.xyz/\n","date":"2020-07-20T10:19:00+08:00","permalink":"/%E6%95%B4%E7%90%86%E4%B8%80%E4%BA%9B%E5%85%8D%E8%B4%B9%E4%B8%8B%E8%BD%BD%E4%BB%98%E8%B4%B9%E9%9F%B3%E4%B9%90%E7%9A%84%E7%BD%91%E7%AB%99/","title":"整理一些免费播放和下载付费音乐的网站"},{"content":"项目是用golang作为服务器，nginx负责反向代理。\n但是现在有一个问题，golang的web服务器，目录浏览不能自定义关闭。\n最初是想nginx代理静态文件，然后后台请求反向代理到golang，但是这样有更新每次都要git pull两次，有点麻烦。最后还是选择向nginx下手。目录浏览的路径最后肯定是 / ,然后除去根目录， 其它文件夹肯定是/xxx/这样的，直接一个通配符就好了。\nlocation ~ (/.*/)${ return 404;}\r目前够用了。\n","date":"2020-07-16T19:03:50+08:00","permalink":"/nginx-%E4%BB%A3%E7%90%86-golang%E7%A6%81%E6%AD%A2%E8%AE%BF%E9%97%AE%E7%9B%AE%E5%BD%95%E7%9A%84%E5%8A%9E%E6%B3%95/","title":"nginx 代理 golang，禁止访问目录的办法"}]