📌 高并发系统设计: 基于Redis与MQ的秒杀系统架构:库存预减、防超卖与限流防刷全流程解析
📋 目录
- 一、工具类功能
- 二、所需环境
- 三、方法与调用示例(按需调用)
- 四、工具类源码(可直接复制)
一、工具类功能
把表单语义(空格编成 +)与 URI 组件语义(空格编成 %20、+ 编成 %2B)两套编解码分开命名,再给查询串拼装解析与开放平台签名原文拼接。
二、所需环境
JDK 17或以上
三、方法与调用示例(按需调用)
🔢 方法序号:1
🎯 方法用途:
- 按表单语义(application/x-www-form-urlencoded)做 UTF-8 编码,空格编成 +
- 提交表单、拼老开放平台的签名串这类场景用它,与浏览器的编码规则一致
🧩 方法定义:public static String encode(String content)
🗣️ 入参解释:public static String encode(待编码内容)
📤 返回值解释:String,表单语义的编码结果
💡 边界口径:
- 保留字符由 JDK URLEncoder 决定:* – _ . 不编码、~ 编成 %7E、/ 编成 %2F,与前端 encodeURIComponent 不完全一致
- 它不判断是否已经编过码,重复编码会得到 %2520 这种二次编码
- 内容为 null 抛 IllegalArgumentException
🧪 方法示例:
- encode("门店 A+B 有 空 格") 拿到 %E9%97%A8%E5%BA%97+A%2BB+%E6%9C%89+%E7%A9%BA+%E6%A0%BC
- encode(null) 抛 IllegalArgumentException: 待编码内容不能为null
🔢 方法序号:2
🎯 方法用途:
- 表单语义按指定字符集编码,对接只收 GBK 的老系统时用
- 编与解必须成对使用同一个字符集,这一版把编码显式写出来
🧩 方法定义:public static String encode(String content, Charset charset)
🗣️ 入参解释:public static String encode(待编码内容, 字符集)
📤 返回值解释:String,指定字符集下的表单编码结果
💡 边界口径:
- 同一句中文在 GBK 下是 2 字节一组、UTF-8 下是 3 字节一组,编码结果完全不同
- charset 传 null 抛 IllegalArgumentException: 字符集不能为null
- 接收方的 URIEncoding 与 Nginx 重编码都可能改动字符集,跨端要核对
🧪 方法示例:
- encode("门店 A+B 有 空 格", Charset.forName("GBK")) 拿到 %C3%C5%B5%EA+A%2BB+%D3%D0+%BF%D5+%B8%F1
- 同一句按默认 UTF-8 编是 %E9%97%A8%E5%BA%97+A%2BB+%E6%9C%89+%E7%A9%BA+%E6%A0%BC,两种结果互不通用
🔢 方法序号:3
🎯 方法用途:
- 按 URI 组件语义(RFC 3986)做 UTF-8 编码,空格编成 %20、+ 编成 %2B
- 要塞进 path 片段或 query 值里的内容用它,不会被接收方当成字面加号
🧩 方法定义:public static String encodeComponent(String content)
🗣️ 入参解释:public static String encodeComponent(待编码内容)
📤 返回值解释:String,URI 组件语义的编码结果
💡 边界口径:
- 与表单语义的差别就两处:空格是 %20 而不是 +,+ 必须编成 %2B
- 空串是合法输入,编出来还是空串
- 把表单编码结果当组件拼进 URL,接收方按 RFC 3986 解析时 + 就是字面加号,签名类场景会直接对不上
🧪 方法示例:
- encodeComponent("门店 A+B 有 空 格") 拿到 %E9%97%A8%E5%BA%97%20A%2BB%20%E6%9C%89%20%E7%A9%BA%20%E6%A0%BC
- encodeComponent(""") 空串拿到空串
- encodeComponent("商品/开票明细") 拼进 path 后是 https://img.example.com/%E5%95%86%E5%93%81%2F%E5%BC%80%E7%A5%A8%E6%98%8E%E7%BB%86.xlsx,斜杠也被编成 %2F
🔢 方法序号:4
🎯 方法用途:
- URI 组件语义按指定字符集编码
- 老系统既要求组件语义又只认 GBK 时用这一版
🧩 方法定义:public static String encodeComponent(String content, Charset charset)
🗣️ 入参解释:public static String encodeComponent(待编码内容, 字符集)
📤 返回值解释:String,指定字符集下的组件编码结果
💡 边界口径:
- 与组件语义 UTF-8 版只差字节生成规则,%20/%2B 口径不变
- 字符集为 null 抛 IllegalArgumentException
🧪 方法示例:
- encodeComponent("门店 A+B 有 空 格", Charset.forName("GBK")) 拿到 %C3%C5%B5%EA%20A%2BB%20%D3%D0%20%BF%D5%20%B8%F1
🔢 方法序号:5
🎯 方法用途:
- 按表单语义做 UTF-8 解码,+ 按空格还原
- 解表单、解老开放平台回传的查询串用它
🧩 方法定义:public static String decode(String content)
🗣️ 入参解释:public static String decode(已编码内容)
📤 返回值解释:String,解码后的原文
💡 边界口径:
- 百分号转义非法(%zz、结尾只剩 a%2)抛 IllegalArgumentException,消息点明是解码而非组件解码
- 内容为 null 抛 IllegalArgumentException
🧪 方法示例:
- decode("%E9%97%A8%E5%BA%97+A%2BB+%E6%9C%89+%E7%A9%BA+%E6%A0%BC") 拿到 门店 A+B 有 空 格
- decode("a+b") 拿到 a b
🔢 方法序号:6
🎯 方法用途:
- 表单语义按指定字符集解码
- 收 GBK 系统回传的串时用,字符集要和对方编码时一致
🧩 方法定义:public static String decode(String content, Charset charset)
🗣️ 入参解释:public static String decode(已编码内容, 字符集)
📤 返回值解释:String,解码后的原文
💡 边界口径:
- 字符集用错不会报错:JDK 解码器默认 REPLACE,解不出的字节换成 U+FFFD
- 所以 GBK 串按 UTF-8 硬解会得到一半乱码字符,成对约定仍要靠调用方
- 百分号转义非法抛 IllegalArgumentException
🧪 方法示例:
- decode("%C3%C5%B5%EA+A%2BB+%D3%D0+%BF%D5+%B8%F1", Charset.forName("GBK")) 拿到 门店 A+B 有 空 格
- 同一串按 UTF-8 硬解,16 个字符里有 8 个 U+FFFD 替换字符
🔢 方法序号:7
🎯 方法用途:
- 按 URI 组件语义解码,+ 保持字面加号
- 解 path 片段、query 值时用它,和表单解码不会互相冒充
🧩 方法定义:public static String decodeComponent(String content)
🗣️ 入参解释:public static String decodeComponent(已编码内容)
📤 返回值解释:String,解码后的原文
💡 边界口径:
- 与表单解码的唯一区别是 + 不还原成空格
- 半截转义(a%2)抛 IllegalArgumentException,消息是「URL组件解码失败(非法百分号转义)」
🧪 方法示例:
- decodeComponent("%E9%97%A8%E5%BA%97%20A%2BB%20%E6%9C%89%20%E7%A9%BA%20%E6%A0%BC") 拿到 门店 A+B 有 空 格
- decodeComponent("a+b") 拿到 a+b
- 同一输入 decode("a+b") 拿到的是 a b,两种语义互不通用
🔢 方法序号:8
🎯 方法用途:
- URI 组件语义按指定字符集解码
- 组件编码加 GBK 这一组合的老系统用它解
🧩 方法定义:public static String decodeComponent(String content, Charset charset)
🗣️ 入参解释:public static String decodeComponent(已编码内容, 字符集)
📤 返回值解释:String,解码后的原文
💡 边界口径:
- 口径同方法7,只是字节按传入字符集还原
- 字符集为 null 抛 IllegalArgumentException
🧪 方法示例:
- decodeComponent("%C3%C5%B5%EA%20A%2BB%20%D3%D0%20%BF%D5%20%B8%F1", Charset.forName("GBK")) 拿到 门店 A+B 有 空 格
🔢 方法序号:9
🎯 方法用途:
- 把参数 Map 拼成查询串,按 key 字典序、值做组件编码、null 值整个跳过
- 拼回调地址、请求 URL 后面那段查询串,不用自己拼 & 再逐个编码
🧩 方法定义:public static String encodeQuery(Map<String, ?> params)
🗣️ 入参解释:public static String encodeQuery(参数 Map)
📤 返回值解释:String,形如 k1=v1&k2=v2 的查询串
💡 边界口径:
- 值是集合或数组时展开成同名多参数,例如 tags=a&tags=b
- 值为 null 的项整个跳过,不输出 key=;键为 null 的项同样跳过
- 默认按 key 字典序、UTF-8 编码,空 Map 得到空串
- 参数 Map 为 null 抛 IllegalArgumentException
🧪 方法示例:
- encodeQuery(params) 拿到 amount=128.5&merchantId=M8001&shopName=%E9%97%A8%E5%BA%97%20A%2BB&tags=%E7%94%9F%E9%B2%9C&tags=24h,shopName 里的空格是 %20
- encodeQuery(new LinkedHashMap<>()) 空 Map 拿到空串
- 参数表里放一个 null 键,拿到 k=v,这一项被跳过
🔢 方法序号:10
🎯 方法用途:
- 拼查询串时指定字符集
- 接收方按 GBK 解参数就按 GBK 编,保持成对
🧩 方法定义:public static String encodeQuery(Map<String, ?> params, Charset charset)
🗣️ 入参解释:public static String encodeQuery(参数 Map, 字符集)
📤 返回值解释:String,指定字符集下的查询串
💡 边界口径:
- 除字节生成规则外,排序与跳过 null 值的口径同方法9
- 参数 Map 为 null 抛 IllegalArgumentException
🧪 方法示例:
- encodeQuery(params, Charset.forName("GBK")) 拿到 amount=128.5&merchantId=M8001&shopName=%C3%C5%B5%EA%20A%2BB&tags=%C9%FA%CF%CA&tags=24h
🔢 方法序号:11
🎯 方法用途:
- 拼查询串时选择是否排序,sorted=false 就保持传入顺序
- 参数顺序本身有意义(老接口按顺序取参)时用这一版
🧩 方法定义:public static String encodeQuery(Map<String, ?> params, Charset charset, boolean sorted)
🗣️ 入参解释:public static String encodeQuery(参数 Map, 字符集, 是否排序)
📤 返回值解释:String,查询串
💡 边界口径:
- sorted=false 时按 Map 的迭代顺序输出,配 LinkedHashMap 就能控制顺序
- 多值展开与 null 值跳过口径不变
- 参数 Map、字符集任一为 null 抛 IllegalArgumentException
🧪 方法示例:
- encodeQuery(params, StandardCharsets.UTF_8, false) 拿到 merchantId=M8001&shopName=%E9%97%A8%E5%BA%97%20A%2BB&tags=%E7%94%9F%E9%B2%9C&tags=24h&amount=128.5,amount 排到了最后
🔢 方法序号:12
🎯 方法用途:
- 把查询串解析成「键到值列表」的多值参数表,键按出现顺序保留
- 收到的 URL 参数要原样读出来(含重复键)时用它,split("&") 手切会漏掉多值和编码
🧩 方法定义:public static Map<String, List<String>> parseQuery(String query)
🗣️ 入参解释:public static Map<String, List<String>> parseQuery(查询串)
📤 返回值解释:Map<String, List<String>>,键到值列表,每个值列表不可变
💡 边界口径:
- 按表单语义解码,所以 + 会还原成空格
- 没有等号的裸参数(key)取值为一个空串,不会没有这个键
- 与 encodeQuery 互为逆运算
- 查询串为 null 抛 IllegalArgumentException: 查询串不能为null
🧪 方法示例:
- parseQuery(query) 的键顺序拿到 [amount, merchantId, shopName, tags]
- 多值参数 tags 拿到 [生鲜, 24h]
- 含空格的 shopName 拿到 [门店 A+B]
- parseQuery("key&flag=") 拿到键 [key, flag],两项的取值都是一个空串
🔢 方法序号:13
🎯 方法用途:
- 按指定字符集解析查询串里的 %XX 字节
- 老系统用 GBK 编的参数,用它才解得出中文
🧩 方法定义:public static Map<String, List<String>> parseQuery(String query, Charset charset)
🗣️ 入参解释:public static Map<String, List<String>> parseQuery(查询串, 字符集)
📤 返回值解释:Map<String, List<String>>,多值参数表
💡 边界口径:
- 只有 %XX 字节的解释跟随传入字符集,+ 与空值口径同方法12
- 字符集传错不会抛异常,会得到替换字符,成对约定仍要靠调用方
🧪 方法示例:
- parseQuery(gbkQuery, Charset.forName("GBK")) 里 shopName 拿到 [门店 A+B]
🔢 方法序号:14
🎯 方法用途:
- 拼开放平台的签名原文:按 key 字典序拼 k1=v1&k2=v2,不做 URL 编码
- 对接微信、支付宝、钉钉这类要拼签名原文的网关用它,比拿查询串去签名可靠
🧩 方法定义:public static String buildSignSource(Map<String, ?> params)
🗣️ 入参解释:public static String buildSignSource(参数 Map)
📤 返回值解释:String,签名原文,值保持字面量
💡 边界口径:
- 空值与 null 值参数按微信、支付宝、钉钉的惯例跳过,不参与签名
- 值里的中文和空格都是字面量,不做任何编码
- 拿 encodeQuery 的结果去签名,参数里只要有中文或空格就必然验签失败
- 参数 Map 为 null 抛 IllegalArgumentException
🧪 方法示例:
- buildSignSource(params) 拿到 amount=128.50&appId=wx8001&nonce=abc def,带空格的值仍是字面空格
- 同一份参数走 encodeQuery(params) 得到 amount=128.50&appId=wx8001&empty=&nonce=abc%20def,已经编成 %20
四、工具类源码(可直接复制)
📦 源码文件





