YD_SmartSports.Api/VOL.Core/Utilities/JSSaTokenSignHelper.cs
2026-01-16 10:11:54 +08:00

125 lines
4.6 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace VOL.Core.Utilities
{
public static class SaTokenSignHelper
{
/// <summary>
/// 根据业务参数生成 Java Sa-Token Sign 兼容的 QueryString
/// 自动生成 timestamp 和 nonce,如果外部未提供
/// </summary>
/// <param name="paramMap">业务参数字典(可包含 teacherNo 等)</param>
/// <param name="secretKey">应用密钥</param>
/// <param name="channel">应用编码,例如 sk-yuedong</param>
/// <returns>带 sign 的完整 QueryString</returns>
public static string BuildQueryString(Dictionary<string, object> paramMap, string secretKey)
{
if (paramMap == null)
paramMap = new Dictionary<string, object>();
// 1️⃣ TreeMap 排序(ASCII 升序)
var sorted = new SortedDictionary<string, string>(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();
}
/// <summary>
/// 构建 POST JSON 上传接口的 QueryString(包含 sign)
/// ⚠️ 不与 GET 共用,避免误用
/// </summary>
public static string BuildPostJsonQueryString(
Dictionary<string, object> 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<string, string>(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();
}
/// <summary>
/// MD5 小写 32 位
/// </summary>
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();
}
/// <summary>
/// 生成随机字符串 nonce,可指定长度
/// </summary>
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());
}
}
}