golang json反序列化科学计数法的坑
问题背景
func CheckSign(c *gin.Context, signKey string, singExpire int) (string, error) {r := c.Requestvar formParams map[string]interface{}if c.Request.Body != nil {bodyBytes, _ := io.ReadAll(c.Request.Body)defer c.Request.Body.Close()if len(bodyBytes) > 0 {//原始有问题的写法//err := json.Unmarshal(bodyBytes, &formParams)// 直接解析,会导致数字类型解析出来的是科学计数法d := json.NewDecoder(bytes.NewReader([]byte(bodyBytes)))d.UseNumber()err := d.Decode(&formParams)if err != nil {return "", err}}// 创建新的reader,使用bytes.NewReader// 恢复r.Body,以便可以多次读取r.Body = io.NopCloser(bytes.NewReader(bodyBytes))}sign := c.GetHeader("api-sign")timestamp := c.GetHeader("api-timestamp")if sign == "" || timestamp == "" {return "", errors.New("api-sign 或 api-timestamp为空")}//验证时间戳格式timestampValue, err := strconv.ParseInt(timestamp, 10, 64)if err != nil {return "", errors.New("timetamp 格式错误")}//验证时间戳nowstamp := time.Now().UnixNano() / int64(time.Millisecond)ago := timestampValue + int64(singExpire)if nowstamp > ago {return "", errors.New("签名时间已过期")}//验证签名// 按照字段名正序排序keys := make([]string, 0, len(formParams))for k := range formParams {keys = append(keys, k)}sort.Strings(keys)targetArr := make([]string, 0, len(formParams))// TODO 暂不支持嵌套json格式for _, k := range keys {val := reflect.ValueOf(formParams[k])switch val.Kind() {case reflect.Slice:strSlice := make([]string, 0, val.Len())for i := 0; i < val.Len(); i++ {v := val.Index(i)strSlice = append(strSlice, fmt.Sprintf("%v", v))}targetArr = append(targetArr, fmt.Sprintf("%s=%v", k, strings.Join(strSlice, ",")))default:targetArr = append(targetArr, fmt.Sprintf("%s=%v", k, formParams[k]))}}str := strings.Join(targetArr, "&") + timestamp + signKeyhash := md5.Sum([]byte(str))md5Str := hex.EncodeToString(hash[:])if sign != md5Str {return md5Str, errors.New("签名错误~")}if gin.Mode() == "prod" {md5Str = ""}return md5Str, nil
}
前端传参:
{"id":33,"old_warranty_end_time":1720713600,"new_warranty_end_time":1720800000}
前端生成验签加密之前的字符串如下:
33&new_warranty_end_time=1720800000&old_warranty_end_time=17207136001720755503589{{加密盐值}}
服务端验签加密之前的字符串如下:
id=33&new_warranty_end_time=1.7208e+09&old_warranty_end_time=1.7207136e+091720755503589{{加密盐值}}
显而易见加密之前拼接的字符串不一样。服务端拼接的字符串变成了科学计数法的格式。
问题原因定位
经过查询发现,问题出现在json.Unmarshal(bodyBytes, &jsonBody)这个地方,反序列化之后,出来的就是科学计数法的类型。
我们可以看一下反序列化之后的数据类型和值,解析为了float类型。
为什么float类型出来的是科学计数法的表示样式呢?这个问题我们到源码中寻找答案,我们先看这个问题的解决方案。
解决方案
d := json.NewDecoder(bytes.NewReader([]byte(bodyBytes)))
d.UseNumber()
d.Decode(&jsonBody)
可以通过这种方式解决这个问题,这个问题很容易解决。但是有一点值得注意,通过这种方式反序列化数字类型会被反射为Number类型。
而这个json.Number的类型本质是个string类型。
问题原因
我们通过json.Unmarshal这个方法进到源码去看一下其执行逻辑。
func Unmarshal(data []byte, v any) error {// Check for well-formedness.// Avoids filling out half a data structure// before discovering a JSON syntax error.var d decodeStateerr := checkValid(data, &d.scan)if err != nil {return err}d.init(data)return d.unmarshal(v)
}
chekValid这个方法是校验json格式是否合法,貌似是通过逐字节进行处理的。这个部分跟我们的问题不太相关,我们暂且略过。
func (d *decodeState) unmarshal(v any) error {rv := reflect.ValueOf(v)if rv.Kind() != reflect.Pointer || rv.IsNil() {return &InvalidUnmarshalError{reflect.TypeOf(v)}}d.scan.reset()d.scanWhile(scanSkipSpace)// We decode rv not rv.Elem because the Unmarshaler interface// test must be applied at the top level of the value.err := d.value(rv)if err != nil {return d.addErrorContext(err)}return d.savedError
}
我们重点关注d.vale中的逻辑。
func (d *decodeState) value(v reflect.Value) error {switch d.opcode {default:panic(phasePanicMsg)case scanBeginArray:if v.IsValid() {if err := d.array(v); err != nil {return err}} else {d.skip()}d.scanNext()case scanBeginObject:if v.IsValid() {if err := d.object(v); err != nil {return err}} else {d.skip()}d.scanNext()case scanBeginLiteral:// All bytes inside literal return scanContinue op code.start := d.readIndex()d.rescanLiteral()if v.IsValid() {if err := d.literalStore(d.data[start:d.readIndex()], v, false); err != nil {return err}}}return nil
}
func stateBeginValue(s *scanner, c byte) int {if isSpace(c) {return scanSkipSpace}switch c {case '{':s.step = stateBeginStringOrEmptyreturn s.pushParseState(c, parseObjectKey, scanBeginObject)case '[':s.step = stateBeginValueOrEmptyreturn s.pushParseState(c, parseArrayValue, scanBeginArray)case '"':s.step = stateInStringreturn scanBeginLiteralcase '-':s.step = stateNegreturn scanBeginLiteralcase '0': // beginning of 0.123s.step = state0return scanBeginLiteralcase 't': // beginning of trues.step = stateTreturn scanBeginLiteralcase 'f': // beginning of falses.step = stateFreturn scanBeginLiteralcase 'n': // beginning of nulls.step = stateNreturn scanBeginLiteral}//以数字开头的都是字面量if '1' <= c && c <= '9' { // beginning of 1234.5s.step = state1return scanBeginLiteral}return s.error(c, "looking for beginning of value")
}
以数字开头的都归属于字面量类型。所以,我们看一下d.vale中的scanBeginLiteral这个分支。
相关文章:

golang json反序列化科学计数法的坑
问题背景 func CheckSign(c *gin.Context, signKey string, singExpire int) (string, error) {r : c.Requestvar formParams map[string]interface{}if c.Request.Body ! nil {bodyBytes, _ : io.ReadAll(c.Request.Body)defer c.Request.Body.Close()if len(bodyBytes) >…...

罗技K380无线键盘及鼠标:智慧互联,一触即通
目录 1. 背景2. K380无线键盘连接电脑2.1 键盘准备工作2.2 电脑配置键盘的连接 3. 无线鼠标的连接3.1 鼠标准备工作3.2 电脑配置鼠标的连接 1. 背景 有一阵子经常使用 ipad,但是对于我这个习惯于键盘打字的人来说,慢慢在 ipad 上打字,实在是…...

卸载wps office的几种方法收录
第一种方法: 1.打开【任务管理器】,找到相关程序,点击【结束任务】。任务管理器可以通过左下角搜索找到。 2.点击【开始】-【设置】-【应用】-下拉找到WPS应用,右键卸载,不保留软件配置 …...

SpringCloud第一篇Docker基础
文章目录 一、常见命令二、数据卷三、数据挂载四、自定义镜像五、网络 一、常见命令 Docker最常见的命令就是操作镜像、容器的命令,详见官方文档: https://docs.docker.com/ 需求: 在DockerHub中搜索Nginx镜像,查看镜像的名称 …...

从零开始学习PX4源码3(如何上传官网源码到自己的仓库中)
目录 文章目录 目录摘要1.将PX4源码上传至腾讯工蜂2.从腾讯工蜂克隆源码到本地ubuntu3.如何查看自己源码的版本信息 摘要 本节主要记录从零开始学习PX4源码3(如何上传官网源码到自己的仓库中)及如何查看PX4的固件版本信息,欢迎批评指正! PX4源码版本V1.…...
Docker Compose 启动容器例子
Docker Compose 启动容器例子 Docker Compose 文件 (docker-compose.yml) version: 3.8services:web:image: nginx:latestports:- "8080:80"volumes:- ./html:/usr/share/nginx/htmlnetworks:- webnetdb:image: mysql:latestenvironment:MYSQL_ROOT_PASSWORD: exam…...
守护服务之门:Eureka中分布式认证与授权的实现策略
守护服务之门:Eureka中分布式认证与授权的实现策略 引言 在微服务架构中,服务间的通信安全至关重要。Eureka作为Netflix开源的服务发现框架,虽然本身提供了服务注册与发现的功能,但并不直接提供认证与授权机制。为了实现服务的分…...

核密度估计KDE和概率密度函数PDF(深入浅出)
目录 1. 和密度估计(KDE)核密度估计的基本原理核密度估计的公式核密度估计的应用Python中的KDE实现示例代码 结果解释解释结果 总结 2. 概率密度函数(PDF)概率密度函数(PDF)是怎么工作的:用图画…...

免开steam 脱离steam 进行游戏的小工具
链接:https://pan.baidu.com/s/1k2C8b4jEqKIGLtLZp8YCgA?pwd6666 提取码:6666 我们只需选择游戏根目录 然后输入AppID 点击底部按钮 进行就可以了 关于AppID在:...

深度学习--系统配置流程
Win10系统配置双系统Ubuntu18.04 深度学习台式服务器自装练手1.win10磁盘管理2.下载系统镜像制作U盘3.系统安装4. 安装后的系统设置工作5.配置CUDA环境CUDNN安装 深度学习台式服务器自装练手 写在最前 CUDA最高支持11.4 显卡3060 1.win10磁盘管理 首先对原有磁盘进行分区整理…...
把Docker的虚拟磁盘文件移动到别的盘符
今天清理C盘空间,发现一个很大的文件 ext4.vhdx 足有 15G 之多,发现这个是Docker的虚拟磁盘文件,于是在网上找到移到它的办法,使用 PowerShell 执行下面命令 查看Docker状态和版本 wsl -l -v 关闭Docker服务 wsl --shutdown …...
Oracle 19c RAC 心跳异常处理
客户机房异常断电后,启动19c集群报错如下 2024-07-05 17:43:27.934 [GIPCD(5964292)]CRS-42216: No interfaces are configured on the local node for interface definition en3(:.*)?:100.100.100.0: available interface definitions are [en0(:.*)?:10.88.0.…...

微信小程序引入自定义子组件报错,在 C:/Users/***/WeChatProjects/miniprogram-1/components/路径下***
使用原生小程序开发时候,会报下面的错误, [ pages/button/button.json 文件内容错误] pages/button/button.json: [“usingComponents”][“second-component”]: “…/…/components/second-child/index”,在 C:/Users/***/WeChatProjects/m…...

【图解大数据技术】流式计算:Spark Streaming、Flink
【图解大数据技术】流式计算:Spark Streaming、Flink 批处理 VS 流式计算Spark StreamingFlinkFlink简介Flink入门案例Streaming Dataflow Flink架构Flink任务调度与执行task slot 和 task EventTime、Windows、WatermarksEventTimeWindowsWatermarks 批处理 VS 流式…...
启动完 kubelet 日志显示 failed to get azure cloud in GetVolumeLimits, plugin.host: 1
查看 kubelet 日志组件命令 journalctl -xefu kubelet 文字描述问题 Jul 09 07:45:17 node01 kubelet[1344]: I0709 07:45:17.410786 1344 operation_generator.go:568] MountVolume.SetUp succeeded for volume "default-token-mfzqf" (UniqueName: "ku…...

C语言基础and数据结构
C语言程序和程序设计概述 程序:可以连续执行的一条条指令的集合 开发过程:C源程序(.c文件) --> 目标程序(.obj二进制文件,目标文件) --> 可执行文件(.exe文件) -->结果 在任何机器上可以运行C源程序生成的 .exe 文件 没有安装C语言集成开发环境,不能编译C语言程…...

【超万卡GPU集群关键技术深度分析 2024】_构建10万卡gpu集群的技术挑战
文末有福利! 1. 集群高能效计算技术 随着大模型从千亿参数的自然语言模型向万亿参数的多模态模型升级演进,超万卡集群吸需全面提升底层计算能力。 具体而言,包括增强单芯片能力、提升超节点计算能力、基于 DPU (Data Processing Unit) 实现…...
RuntimeError: CUDA error: invalid device ordinal
RuntimeError: CUDA error: invalid device ordinal 报错分析:可能原因1:设置CUDA_VISIBLE_DEVICES的问题解决办法: 可能原因2:硬件或驱动原因解决方法: 参考资料 报错分析: 如果你在运行代码时报错&#…...
如何在Qt中添加文本
在Qt中添加文本通常涉及到使用几种不同的Qt控件,具体取决于你想要在何处以及以何种方式显示文本。以下是一些常见的方法: 1. 使用QLabel显示文本 QLabel是Qt中用于显示文本或图片的简单控件。你可以通过构造函数或setText()方法设置其显示的文本。 #i…...

解决打印PDF文本不清楚的处理办法
之前打印PDF格式的电子书,不清晰,影响看书的心情,有时看到打印的书的质量,根本不想看,今天在打印一本页数不多,但PDF格式的书感觉也不太清楚,我想应该有办法解决,我使用的是解决福昕…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...

【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...

初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...