using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; namespace VOL.Core.Utilities { public static class SaTokenSignHelper { /// /// 根据业务参数生成 Java Sa-Token Sign 兼容的 QueryString /// 自动生成 timestamp 和 nonce,如果外部未提供 /// /// 业务参数字典(可包含 teacherNo 等) /// 应用密钥 /// 应用编码,例如 sk-yuedong /// 带 sign 的完整 QueryString public static string BuildQueryString(Dictionary paramMap, string secretKey) { if (paramMap == null) paramMap = new Dictionary(); // 1️⃣ TreeMap 排序(ASCII 升序) var sorted = new SortedDictionary(StringComparer.Ordinal); foreach (var kv in paramMap) sorted[kv.Key] = kv.Value.ToString(); // 2️⃣ 拼接签名字符串 k=v&k=v&...&key=secretKey var sbSign = new StringBuilder(); foreach (var kv in sorted) sbSign.Append(kv.Key).Append("=").Append(kv.Value).Append("&"); sbSign.Append("key=").Append(secretKey.Trim()); string sign = Md5(sbSign.ToString()); // 3️⃣ 拼接 QueryString(保留外部字典原顺序) var sbQuery = new StringBuilder(); foreach (var kv in paramMap) sbQuery.Append(Uri.EscapeDataString(kv.Key)) .Append("=") .Append(Uri.EscapeDataString(kv.Value.ToString())) .Append("&"); sbQuery.Append("sign=").Append(sign); return sbQuery.ToString(); } /// /// 构建 POST JSON 上传接口的 QueryString(包含 sign) /// ⚠️ 不与 GET 共用,避免误用 /// public static string BuildPostJsonQueryString( Dictionary paramMap, string secretKey, string jsonBody) { if (paramMap == null) throw new ArgumentNullException(nameof(paramMap)); if (string.IsNullOrWhiteSpace(jsonBody)) throw new ArgumentException("POST JSON body 不能为空"); // 1️⃣ 参与签名的参数(TreeMap + ASCII 排序) var signParams = new SortedDictionary(StringComparer.Ordinal); foreach (var kv in paramMap) signParams[kv.Key] = kv.Value.ToString(); // ⭐ POST JSON 特有:整个 body 作为 reqbody signParams["reqbody"] = jsonBody; // 2️⃣ 拼接签名字符串 // k=v&k=v&...&key=secretKey var sbSign = new StringBuilder(); foreach (var kv in signParams) sbSign.Append(kv.Key) .Append("=") .Append(kv.Value) .Append("&"); sbSign.Append("key=").Append(secretKey.Trim()); string sign = Md5(sbSign.ToString()); // 3️⃣ 构建 URL QueryString(⚠️ 不包含 reqbody) var sbQuery = new StringBuilder(); foreach (var kv in paramMap) sbQuery.Append(Uri.EscapeDataString(kv.Key)) .Append("=") .Append(Uri.EscapeDataString(kv.Value.ToString())) .Append("&"); sbQuery.Append("sign=").Append(sign); return sbQuery.ToString(); } /// /// MD5 小写 32 位 /// private static string Md5(string input) { using var md5 = MD5.Create(); var bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(input)); var sb = new StringBuilder(); foreach (var b in bytes) sb.Append(b.ToString("x2")); return sb.ToString(); } /// /// 生成随机字符串 nonce,可指定长度 /// public static string RandomString(int length = 20) { const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; var random = new Random(); return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray()); } } }