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

一次诡异的报错排查:为什么时间戳变成了 ١٧٥٦٦٣٢٧٨

在做服务端登录校验时,我们线上遇到了一个很奇怪的报错:

strconv.Atoi: parsing "١٧٥٦٦٣٢٧٨": invalid syntax

字符串看起来和数字个数对得上,但又像是乱码,Go 服务无法解析这段字符。这背后,其实是一个 国际化 (i18n) 的大坑


现象复盘

  • 服务端使用 Go,解析客户端传来的时间戳:

    v, err := strconv.Atoi(tsString)
    
  • 部分用户(主要在阿联酋)登录时,报错 invalid syntax

  • 打印出来的时间戳长这样:١٧٥٦٦٣٢٧٨٫١٧٢

看上去就是 175663278.172,为什么不行?


真相揭晓:Locale 搞的鬼

排查后发现:

  • 用户手机设置为 阿拉伯语 (Arabic) + 地区 = 阿联酋 (United Arab Emirates)

  • Android/Java 默认的 DecimalFormat 会跟随 Locale 决定数字符号;

  • ar_AE 下,数字会被格式化成 阿拉伯-印地数字

    • ١٧٥٦٦٣٢٧٨ = 175663278
    • ٫ = 小数点

所以客户端传过来的根本不是 ASCII 数字,而是另一套 Unicode 数字。Go 的 Atoi 当然解析失败。


为什么我们测试没复现?

我们在国内测试时,把系统语言切成阿拉伯语,却始终输出 123456...

原因是:

  • 只改 语言 不够,还要改 地区
  • 必须同时是 语言 = Arabic,地区 = 阿联酋 (ar_AE),并且启用「本地数字」选项,才会显示 ١٢٣٤٥٦...
  • MIUI 等国产 ROM 把“地区”设置藏得比较深(设置 → 更多设置 → 地区),所以一开始没找到。
  • 更坑的是,不同手机厂商 / Android 版本的 ICU/CLDR 数据不同,有的 ar_AE 默认就用阿拉伯数字,有的还是拉丁数字,所以有时根本复现不了。

解决方案

客户端改造(推荐)

  1. 强制使用 US Locale

    DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US);
    df.applyPattern("#.######");
    df.setGroupingUsed(false);
    String ts = df.format(timeMillis / 1000.0);
    
  2. 避免字符串传输

    • JSON 用 number 类型:

      { "ts": 175663278 }
      
    • 而不是字符串:

      { "ts": "١٧٥٦٦٣٢٧٨" }
      
  3. 如果只需要整数时间戳,直接:

    String ts = Long.toString(timeMillis / 1000);
    

服务端兜底

即使客户端改了,服务端也要健壮,能容错。加个数字规范化函数,把阿拉伯/波斯数字转成 ASCII:

func normalizeDigits(s string) string {out := make([]rune, 0, len(s))for _, r := range s {switch {case r >= '\u0660' && r <= '\u0669': // Arabic-Indic ٠..٩out = append(out, '0'+(r-'\u0660'))case r >= '\u06F0' && r <= '\u06F9': // Persian ۰..۹out = append(out, '0'+(r-'\u06F0'))default:out = append(out, r)}}return string(out)
}

这样再 strconv.Atoi(normalizeDigits(ts)) 就不会出错了。


总结经验

  1. 国际化的坑很多:不要依赖默认 Locale,显式指定才安全。
  2. 测试要全面:仅切语言不够,还要切地区;不同系统实现也可能有差异。
  3. 服务端要健壮:客户端可能各种情况,服务端要兜底。
  4. 最佳实践:跨端传递时间戳、ID 等数据,推荐直接用 数值,而不是字符串。
http://www.dtcms.com/a/362295.html

相关文章:

  • 9.1日IO作业
  • 大模型RAG项目实战:文本向量模型>Embedding模型、Reranker模型以及ColBERT模型
  • nCode 后处理常见问题汇总
  • 生成知识图谱与技能树的工具指南:PlantUML、Mermaid 和 D3.js
  • 过拟合 正则化(L1,L2,Dropout)
  • linux内核 - 文件系统相关的几个概念介绍
  • Ceres学习笔记
  • 从理论到RTL,实战实现高可靠ECC校验(附完整开源代码/脚本)(3) RTL实现实战
  • 智慧班牌系统基于Java+Vue技术栈构建,实现教育信息化综合管理。
  • ES6手录01-let与const
  • 2024 年 AI 技术全景图:大模型轻量化、多模态融合如何重塑产业边界?
  • c#:抽象类中的方法
  • Windows 使用 Compass 访问MongoDb
  • 笔记:现代操作系统:原理与实现(1)
  • 利用本地电脑上的MobaXterm连接虚拟机上的Ubuntu
  • 【Python知识】Playwright for Python 脚本录制指南
  • Nature Communications发布智能光电探测研究:实现0.3-1.1 THz波段强度-偏振-频率连续高维感知
  • 第7.6节:awk语言 break 语句
  • 刷题日记0901
  • 动态代理设计模式
  • 从Redisson分布式锁看锁的设计思路
  • 自动化运维-ansible中的变量运用
  • LeetCode Hot 100 Python (61~70)
  • 芯片的可编程字
  • Ps画笔和橡皮擦工具
  • 分布式事务相关02
  • 国内服务器如何安装docker或者是1panel
  • 关闭页面强制清除所有循环定时器
  • Linux 进程间通信(IPC)
  • Android14 init.rc各个阶段的主要操作