当前位置: 首页 > news >正文

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&#xff0c;但是对于我这个习惯于键盘打字的人来说&#xff0c;慢慢在 ipad 上打字&#xff0c;实在是…...

卸载wps office的几种方法收录

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

SpringCloud第一篇Docker基础

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

从零开始学习PX4源码3(如何上传官网源码到自己的仓库中)

目录 文章目录 目录摘要1.将PX4源码上传至腾讯工蜂2.从腾讯工蜂克隆源码到本地ubuntu3.如何查看自己源码的版本信息 摘要 本节主要记录从零开始学习PX4源码3(如何上传官网源码到自己的仓库中)及如何查看PX4的固件版本信息&#xff0c;欢迎批评指正&#xff01; 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中分布式认证与授权的实现策略

守护服务之门&#xff1a;Eureka中分布式认证与授权的实现策略 引言 在微服务架构中&#xff0c;服务间的通信安全至关重要。Eureka作为Netflix开源的服务发现框架&#xff0c;虽然本身提供了服务注册与发现的功能&#xff0c;但并不直接提供认证与授权机制。为了实现服务的分…...

核密度估计KDE和概率密度函数PDF(深入浅出)

目录 1. 和密度估计&#xff08;KDE&#xff09;核密度估计的基本原理核密度估计的公式核密度估计的应用Python中的KDE实现示例代码 结果解释解释结果 总结 2. 概率密度函数&#xff08;PDF&#xff09;概率密度函数&#xff08;PDF&#xff09;是怎么工作的&#xff1a;用图画…...

免开steam 脱离steam 进行游戏的小工具

链接&#xff1a;https://pan.baidu.com/s/1k2C8b4jEqKIGLtLZp8YCgA?pwd6666 提取码&#xff1a;6666 我们只需选择游戏根目录 然后输入AppID 点击底部按钮 进行就可以了 关于AppID在&#xff1a;...

深度学习--系统配置流程

Win10系统配置双系统Ubuntu18.04 深度学习台式服务器自装练手1.win10磁盘管理2.下载系统镜像制作U盘3.系统安装4. 安装后的系统设置工作5.配置CUDA环境CUDNN安装 深度学习台式服务器自装练手 写在最前 CUDA最高支持11.4 显卡3060 1.win10磁盘管理 首先对原有磁盘进行分区整理…...

把Docker的虚拟磁盘文件移动到别的盘符

今天清理C盘空间&#xff0c;发现一个很大的文件 ext4.vhdx 足有 15G 之多&#xff0c;发现这个是Docker的虚拟磁盘文件&#xff0c;于是在网上找到移到它的办法&#xff0c;使用 PowerShell 执行下面命令 查看Docker状态和版本 wsl -l -v 关闭Docker服务 wsl --shutdown …...

Oracle 19c RAC 心跳异常处理

客户机房异常断电后&#xff0c;启动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/路径下***

使用原生小程序开发时候&#xff0c;会报下面的错误&#xff0c; [ pages/button/button.json 文件内容错误] pages/button/button.json: [“usingComponents”][“second-component”]: “…/…/components/second-child/index”&#xff0c;在 C:/Users/***/WeChatProjects/m…...

【图解大数据技术】流式计算:Spark Streaming、Flink

【图解大数据技术】流式计算&#xff1a;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集群的技术挑战

文末有福利&#xff01; 1. 集群高能效计算技术 随着大模型从千亿参数的自然语言模型向万亿参数的多模态模型升级演进&#xff0c;超万卡集群吸需全面提升底层计算能力。 具体而言&#xff0c;包括增强单芯片能力、提升超节点计算能力、基于 DPU (Data Processing Unit) 实现…...

RuntimeError: CUDA error: invalid device ordinal

RuntimeError: CUDA error: invalid device ordinal 报错分析&#xff1a;可能原因1&#xff1a;设置CUDA_VISIBLE_DEVICES的问题解决办法&#xff1a; 可能原因2&#xff1a;硬件或驱动原因解决方法&#xff1a; 参考资料 报错分析&#xff1a; 如果你在运行代码时报错&#…...

如何在Qt中添加文本

在Qt中添加文本通常涉及到使用几种不同的Qt控件&#xff0c;具体取决于你想要在何处以及以何种方式显示文本。以下是一些常见的方法&#xff1a; 1. 使用QLabel显示文本 QLabel是Qt中用于显示文本或图片的简单控件。你可以通过构造函数或setText()方法设置其显示的文本。 #i…...

解决打印PDF文本不清楚的处理办法

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

C++_核心编程_多态案例二-制作饮品

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

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

《基于Apache Flink的流处理》笔记

思维导图 1-3 章 4-7章 8-11 章 参考资料 源码&#xff1a; https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

JVM虚拟机:内存结构、垃圾回收、性能优化

1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

Linux 中如何提取压缩文件 ?

Linux 是一种流行的开源操作系统&#xff0c;它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间&#xff0c;使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的&#xff0c;要在 …...

Java数值运算常见陷阱与规避方法

整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...