f
BIN
public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
public/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 249 KiB |
BIN
public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
282
public/examples/csharp/demo.cs
Normal file
@@ -0,0 +1,282 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class Program
|
||||
{
|
||||
// ==================== 配置区域 ====================
|
||||
// 请根据实际情况修改以下配置参数
|
||||
|
||||
// AES加密密钥 (16进制字符串,32位)
|
||||
|
||||
// API接口配置
|
||||
private const string InterfaceName = "XXXXXXXX"; // 接口编号
|
||||
private const string AccessId = "XXXXXXXXXXX"; // 访问ID
|
||||
private const string EncryptionKey = "XXXXXXXXXXXXXXXXXXX"; // 加密密钥
|
||||
private const string BaseUrl = "https://api.haiyudata.com"; // API基础URL
|
||||
|
||||
// 测试数据配置
|
||||
private const string TestMobileNo = "13700000000"; // 测试手机号
|
||||
private const string TestIdCard = "XXXXXXXXXXXXX"; // 测试身份证号
|
||||
private const string TestName = "XXXXXXXX"; // 测试姓名
|
||||
private const string TestAuthDate = "20250318-20270318"; // 测试授权日期
|
||||
|
||||
// HTTP请求配置
|
||||
private const int RequestTimeout = 30000; // 请求超时时间(毫秒)
|
||||
|
||||
// ==================== 主程序 ====================
|
||||
public static async Task Main()
|
||||
{
|
||||
Console.WriteLine("===== AES CBC 加密解密演示 =====");
|
||||
|
||||
// 1. 创建一个JSON对象
|
||||
var data = new
|
||||
{
|
||||
mobile_no = TestMobileNo
|
||||
};
|
||||
|
||||
// 将JSON对象序列化为字符串
|
||||
string jsonString = JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true });
|
||||
|
||||
Console.WriteLine("\n原始JSON:");
|
||||
Console.WriteLine(jsonString);
|
||||
|
||||
// 2. 加密JSON
|
||||
Console.WriteLine("\n开始加密...");
|
||||
string encryptedJson = Encrypt(jsonString);
|
||||
Console.WriteLine($"加密成功! 加密后Base64:");
|
||||
Console.WriteLine(encryptedJson);
|
||||
|
||||
// 3. 解密JSON
|
||||
Console.WriteLine("\n开始解密...");
|
||||
string decryptedJson = Decrypt(encryptedJson);
|
||||
Console.WriteLine($"解密成功! 解密后的原始JSON:");
|
||||
Console.WriteLine(decryptedJson);
|
||||
|
||||
// 4. 验证加解密一致性
|
||||
Console.WriteLine("\n验证结果:");
|
||||
if (jsonString == decryptedJson)
|
||||
{
|
||||
Console.WriteLine("✅ 加解密前后内容完全一致");
|
||||
|
||||
// 验证原始数据是否可用
|
||||
Console.WriteLine("\n尝试解析解密后的JSON:");
|
||||
try
|
||||
{
|
||||
var deserializedObject = JsonSerializer.Deserialize(decryptedJson, typeof(object));
|
||||
Console.WriteLine($"✅ JSON解析成功,类型: {deserializedObject.GetType()}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ JSON解析失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("❌ 加解密前后内容不一致!");
|
||||
Console.WriteLine("原始长度: " + jsonString.Length);
|
||||
Console.WriteLine("解密后长度: " + decryptedJson.Length);
|
||||
|
||||
// 找出第一个不同的位置
|
||||
for (int i = 0; i < Math.Min(jsonString.Length, decryptedJson.Length); i++)
|
||||
{
|
||||
if (jsonString[i] != decryptedJson[i])
|
||||
{
|
||||
Console.WriteLine($"第一个差异在位置 {i}:");
|
||||
Console.WriteLine($"原始字符: '{jsonString[i]}' ({(int)jsonString[i]})");
|
||||
Console.WriteLine($"解密字符: '{decryptedJson[i]}' ({(int)decryptedJson[i]})");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 演示API调用流程(可选)
|
||||
Console.WriteLine("\n===== 演示API调用流程 =====");
|
||||
await DemonstrateApiCall();
|
||||
}
|
||||
|
||||
// ==================== AES加密解密方法 ====================
|
||||
|
||||
/// <summary>
|
||||
/// AES CBC 加密函数
|
||||
/// 使用PKCS7填充,随机IV,返回Base64编码的密文
|
||||
/// </summary>
|
||||
/// <param name="plainText">要加密的明文</param>
|
||||
/// <returns>Base64编码的密文</returns>
|
||||
public static string Encrypt(string plainText)
|
||||
{
|
||||
byte[] key = HexToBytes(EncryptionKey);
|
||||
using Aes aes = Aes.Create();
|
||||
aes.Key = key;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
|
||||
aes.GenerateIV();
|
||||
byte[] iv = aes.IV;
|
||||
|
||||
using ICryptoTransform encryptor = aes.CreateEncryptor();
|
||||
using MemoryStream ms = new();
|
||||
// 先写入IV
|
||||
ms.Write(iv, 0, iv.Length);
|
||||
|
||||
using (CryptoStream cs = new(ms, encryptor, CryptoStreamMode.Write))
|
||||
using (StreamWriter sw = new(cs))
|
||||
{
|
||||
sw.Write(plainText);
|
||||
}
|
||||
|
||||
return Convert.ToBase64String(ms.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AES CBC 解密函数
|
||||
/// 从Base64密文中提取IV和密文数据,进行解密
|
||||
/// </summary>
|
||||
/// <param name="cipherText">Base64编码的密文</param>
|
||||
/// <returns>解密后的明文</returns>
|
||||
public static string Decrypt(string cipherText)
|
||||
{
|
||||
byte[] key = HexToBytes(EncryptionKey);
|
||||
byte[] fullData = Convert.FromBase64String(cipherText);
|
||||
|
||||
// 提取前16字节作为IV
|
||||
byte[] iv = new byte[16];
|
||||
Buffer.BlockCopy(fullData, 0, iv, 0, iv.Length);
|
||||
|
||||
// 实际密文数据
|
||||
byte[] cipherData = new byte[fullData.Length - 16];
|
||||
Buffer.BlockCopy(fullData, 16, cipherData, 0, cipherData.Length);
|
||||
|
||||
using Aes aes = Aes.Create();
|
||||
aes.Key = key;
|
||||
aes.IV = iv;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
|
||||
using ICryptoTransform decryptor = aes.CreateDecryptor();
|
||||
using MemoryStream ms = new(cipherData);
|
||||
using CryptoStream cs = new(ms, decryptor, CryptoStreamMode.Read);
|
||||
using StreamReader sr = new(cs);
|
||||
return sr.ReadToEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将16进制字符串转换为字节数组
|
||||
/// </summary>
|
||||
/// <param name="hex">16进制字符串</param>
|
||||
/// <returns>字节数组</returns>
|
||||
private static byte[] HexToBytes(string hex)
|
||||
{
|
||||
byte[] bytes = new byte[hex.Length / 2];
|
||||
for (int i = 0; i < hex.Length; i += 2)
|
||||
{
|
||||
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// ==================== API调用演示方法 ====================
|
||||
|
||||
/// <summary>
|
||||
/// 演示完整的API调用流程
|
||||
/// 包括:参数构建、加密、发送请求、接收响应、解密响应
|
||||
/// </summary>
|
||||
private static async Task DemonstrateApiCall()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 构建完整的API URL
|
||||
string url = $"{BaseUrl}/api/v1/{InterfaceName}";
|
||||
|
||||
// 构建请求参数
|
||||
var apiParams = new
|
||||
{
|
||||
mobile_no = TestMobileNo,
|
||||
id_card = TestIdCard,
|
||||
auth_date = TestAuthDate,
|
||||
name = TestName
|
||||
};
|
||||
|
||||
// 将参数转换为JSON字符串并加密
|
||||
string jsonStr = JsonSerializer.Serialize(apiParams);
|
||||
Console.WriteLine($"请求参数: {jsonStr}");
|
||||
|
||||
string encryptedData = Encrypt(jsonStr);
|
||||
Console.WriteLine($"加密后的数据: {encryptedData}");
|
||||
|
||||
// 构建请求体
|
||||
var payload = new { data = encryptedData };
|
||||
string requestBody = JsonSerializer.Serialize(payload);
|
||||
|
||||
Console.WriteLine($"发送请求到: {url}");
|
||||
|
||||
// 发送HTTP请求
|
||||
using (var httpClient = new HttpClient())
|
||||
{
|
||||
// 设置请求头
|
||||
httpClient.DefaultRequestHeaders.Add("Access-Id", AccessId);
|
||||
httpClient.DefaultRequestHeaders.Add("Content-Type", "application/json");
|
||||
|
||||
// 设置超时时间
|
||||
httpClient.Timeout = TimeSpan.FromMilliseconds(RequestTimeout);
|
||||
|
||||
var content = new StringContent(requestBody, Encoding.UTF8, "application/json");
|
||||
var response = await httpClient.PostAsync(url, content);
|
||||
|
||||
Console.WriteLine($"响应状态码: {response.StatusCode}");
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
string responseBody = await response.Content.ReadAsStringAsync();
|
||||
Console.WriteLine($"API响应: {responseBody}");
|
||||
|
||||
// 解析响应
|
||||
try
|
||||
{
|
||||
var responseData = JsonSerializer.Deserialize<JsonElement>(responseBody);
|
||||
|
||||
if (responseData.TryGetProperty("code", out var codeElement))
|
||||
{
|
||||
int code = codeElement.GetInt32();
|
||||
string message = responseData.TryGetProperty("message", out var msgElement) ? msgElement.GetString() : "";
|
||||
string encryptedResponse = responseData.TryGetProperty("data", out var dataElement) ? dataElement.GetString() : "";
|
||||
|
||||
Console.WriteLine($"API响应码: {code}");
|
||||
Console.WriteLine($"API消息: {message}");
|
||||
|
||||
if (code == 0 && !string.IsNullOrEmpty(encryptedResponse))
|
||||
{
|
||||
// 解密响应数据
|
||||
try
|
||||
{
|
||||
string decryptedResponse = Decrypt(encryptedResponse);
|
||||
Console.WriteLine($"解密后的响应: {decryptedResponse}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"解密响应数据失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"解析响应失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"请求失败: {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"API调用异常: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
305
public/examples/go/demo.go
Normal file
@@ -0,0 +1,305 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ==================== 配置区域 ====================
|
||||
// 请根据实际情况修改以下配置参数
|
||||
|
||||
const (
|
||||
// API接口配置
|
||||
InterfaceName = "XXXXXXXX" // 接口编号
|
||||
AccessID = "XXXXXXXXXXX"
|
||||
EncryptionKey = "XXXXXXXXXXXXXXXXXXXXX"
|
||||
BaseURL = "https://api.haiyudata.com"
|
||||
|
||||
// 测试数据配置
|
||||
TestName = "XXXXXXXX"
|
||||
TestIDCard = "XXXXXXXXXXXXX"
|
||||
TestMobileNo = "XXXXXXXXXXXXXXXXXXXX"
|
||||
TestAuthDate = "20250318-20270318"
|
||||
|
||||
// HTTP请求配置
|
||||
RequestTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
// API响应结构体
|
||||
type APIResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
// 请求参数结构体
|
||||
type RequestParams struct {
|
||||
MobileNo string `json:"mobile_no"`
|
||||
IDCard string `json:"id_card"`
|
||||
AuthDate string `json:"auth_date"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// 请求载荷结构体
|
||||
type RequestPayload struct {
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
// 结果结构体
|
||||
type Result struct {
|
||||
Code int `json:"code"`
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
EncryptedResponse string `json:"encrypted_response"`
|
||||
DecryptedResponse interface{} `json:"decrypted_response"`
|
||||
}
|
||||
|
||||
// ==================== AES加密解密方法 ====================
|
||||
|
||||
// AES CBC 加密函数,返回 Base64
|
||||
func aesEncrypt(plaintext, key string) (string, error) {
|
||||
keyBytes, err := hex.DecodeString(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(keyBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 生成随机IV
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 填充数据
|
||||
paddedData := pkcs7Pad([]byte(plaintext), aes.BlockSize)
|
||||
|
||||
// 加密
|
||||
ciphertext := make([]byte, len(iv)+len(paddedData))
|
||||
copy(ciphertext, iv)
|
||||
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
mode.CryptBlocks(ciphertext[len(iv):], paddedData)
|
||||
|
||||
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||
}
|
||||
|
||||
// AES CBC 解密函数,返回解密后的明文
|
||||
func aesDecrypt(encryptedText, key string) (string, error) {
|
||||
keyBytes, err := hex.DecodeString(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ciphertext, err := base64.StdEncoding.DecodeString(encryptedText)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(keyBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(ciphertext) < aes.BlockSize {
|
||||
return "", fmt.Errorf("密文太短")
|
||||
}
|
||||
|
||||
iv := ciphertext[:aes.BlockSize]
|
||||
ciphertext = ciphertext[aes.BlockSize:]
|
||||
|
||||
if len(ciphertext)%aes.BlockSize != 0 {
|
||||
return "", fmt.Errorf("密文长度不是块大小的倍数")
|
||||
}
|
||||
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
mode.CryptBlocks(ciphertext, ciphertext)
|
||||
|
||||
// 去除填充
|
||||
unpaddedData, err := pkcs7Unpad(ciphertext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(unpaddedData), nil
|
||||
}
|
||||
|
||||
// PKCS7填充
|
||||
func pkcs7Pad(data []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(data)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(data, padtext...)
|
||||
}
|
||||
|
||||
// PKCS7去除填充
|
||||
func pkcs7Unpad(data []byte) ([]byte, error) {
|
||||
length := len(data)
|
||||
if length == 0 {
|
||||
return nil, fmt.Errorf("数据为空")
|
||||
}
|
||||
unpadding := int(data[length-1])
|
||||
if unpadding > length {
|
||||
return nil, fmt.Errorf("无效的填充")
|
||||
}
|
||||
return data[:length-unpadding], nil
|
||||
}
|
||||
|
||||
// ==================== API调用方法 ====================
|
||||
|
||||
// 调用API函数
|
||||
func callAPI(name, idCard, mobileNo, authDate string) *Result {
|
||||
// 构建完整的API URL
|
||||
url := fmt.Sprintf("%s/api/v1/%s", BaseURL, InterfaceName)
|
||||
|
||||
// 构建请求参数
|
||||
params := RequestParams{
|
||||
MobileNo: mobileNo,
|
||||
IDCard: idCard,
|
||||
AuthDate: authDate,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
// 将参数转换为JSON字符串并加密
|
||||
jsonStr, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return &Result{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("JSON序列化失败: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("请求参数: %s\n", string(jsonStr))
|
||||
|
||||
encryptedData, err := aesEncrypt(string(jsonStr), EncryptionKey)
|
||||
if err != nil {
|
||||
return &Result{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("加密失败: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("加密后的数据: %s\n", encryptedData)
|
||||
|
||||
// 构建请求载荷
|
||||
payload := RequestPayload{
|
||||
Data: encryptedData,
|
||||
}
|
||||
|
||||
// 序列化请求载荷
|
||||
requestBody, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return &Result{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("请求载荷序列化失败: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("发送请求到: %s\n", url)
|
||||
|
||||
// 发送HTTP请求
|
||||
client := &http.Client{
|
||||
Timeout: RequestTimeout,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(requestBody))
|
||||
if err != nil {
|
||||
return &Result{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("创建请求失败: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Access-Id", AccessID)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return &Result{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("请求失败: %v", err),
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return &Result{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("读取响应失败: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
var apiResp APIResponse
|
||||
if err := json.Unmarshal(respBody, &apiResp); err != nil {
|
||||
return &Result{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("解析响应失败: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("API响应: %s\n", string(respBody))
|
||||
|
||||
// 处理响应
|
||||
result := &Result{
|
||||
Code: apiResp.Code,
|
||||
Success: apiResp.Code == 0,
|
||||
Message: apiResp.Message,
|
||||
EncryptedResponse: apiResp.Data,
|
||||
}
|
||||
|
||||
// 如果有返回data,尝试解密
|
||||
if apiResp.Data != "" {
|
||||
decryptedData, err := aesDecrypt(apiResp.Data, EncryptionKey)
|
||||
if err != nil {
|
||||
fmt.Printf("解密响应数据失败: %v\n", err)
|
||||
result.DecryptedResponse = nil
|
||||
} else {
|
||||
var decryptedJSON interface{}
|
||||
if err := json.Unmarshal([]byte(decryptedData), &decryptedJSON); err != nil {
|
||||
fmt.Printf("解析解密后的JSON失败: %v\n", err)
|
||||
result.DecryptedResponse = decryptedData
|
||||
} else {
|
||||
result.DecryptedResponse = decryptedJSON
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ==================== 主程序 ====================
|
||||
|
||||
func main() {
|
||||
fmt.Println("===== 个人涉诉详版 =====")
|
||||
|
||||
// 调用API
|
||||
result := callAPI(TestName, TestIDCard, TestMobileNo, TestAuthDate)
|
||||
|
||||
fmt.Println("\n===== 结果 =====")
|
||||
if result.Success {
|
||||
fmt.Println("请求成功!")
|
||||
if result.DecryptedResponse != nil {
|
||||
decryptedJSON, _ := json.MarshalIndent(result.DecryptedResponse, "", " ")
|
||||
fmt.Printf("解密后的响应: %s\n", string(decryptedJSON))
|
||||
} else {
|
||||
fmt.Println("未能获取或解密响应数据")
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("请求失败: %s\n", result.Message)
|
||||
}
|
||||
}
|
||||
129
public/examples/java/demo.java
Normal file
@@ -0,0 +1,129 @@
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Random;
|
||||
import java.io.OutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class Demo {
|
||||
|
||||
// 加密
|
||||
public static String aesEncrypt(String plainText, String hexKey) throws Exception {
|
||||
byte[] keyBytes = hexStringToByteArray(hexKey);
|
||||
byte[] iv = new byte[16];
|
||||
new SecureRandom().nextBytes(iv);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
|
||||
|
||||
byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// 拼接 IV 和密文
|
||||
byte[] encryptedData = new byte[iv.length + encrypted.length];
|
||||
System.arraycopy(iv, 0, encryptedData, 0, iv.length);
|
||||
System.arraycopy(encrypted, 0, encryptedData, iv.length, encrypted.length);
|
||||
|
||||
return Base64.getEncoder().encodeToString(encryptedData);
|
||||
}
|
||||
|
||||
// 解密
|
||||
public static String aesDecrypt(String encryptedText, String hexKey) throws Exception {
|
||||
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText);
|
||||
byte[] keyBytes = hexStringToByteArray(hexKey);
|
||||
|
||||
byte[] iv = new byte[16];
|
||||
System.arraycopy(encryptedBytes, 0, iv, 0, 16);
|
||||
|
||||
byte[] encryptedData = new byte[encryptedBytes.length - 16];
|
||||
System.arraycopy(encryptedBytes, 16, encryptedData, 0, encryptedData.length);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
|
||||
|
||||
byte[] decrypted = cipher.doFinal(encryptedData);
|
||||
return new String(decrypted, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
// Helper 方法,将 16 进制字符串转为字节数组
|
||||
public static byte[] hexStringToByteArray(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// 发送 HTTP POST 请求
|
||||
public static String sendPostRequest(String urlString, String data, String accessId) throws Exception {
|
||||
URL url = new URL(urlString);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setRequestProperty("Access-Id", accessId);
|
||||
connection.setDoOutput(true);
|
||||
|
||||
// 构造请求体
|
||||
String jsonInputString = "{\"data\":\"" + data + "\"}";
|
||||
try (OutputStream os = connection.getOutputStream()) {
|
||||
byte[] input = jsonInputString.getBytes("utf-8");
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
|
||||
// 读取响应
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
|
||||
StringBuilder response = new StringBuilder();
|
||||
String responseLine;
|
||||
while ((responseLine = br.readLine()) != null) {
|
||||
response.append(responseLine.trim());
|
||||
}
|
||||
|
||||
return response.toString();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 设置 URL、密钥和请求参数
|
||||
String url = "https://api.haiyudata.com/api/v1/IVYZ5733";
|
||||
String accessId = "XXXXXXXXXXXXX";
|
||||
String key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
|
||||
|
||||
// 请求参数
|
||||
JSONObject requestParams = new JSONObject();
|
||||
requestParams.put("name", "李四");
|
||||
requestParams.put("id_card", "110101199003076534");
|
||||
|
||||
// 将请求参数转为 JSON 字符串
|
||||
String jsonStr = requestParams.toString();
|
||||
|
||||
// 加密请求数据
|
||||
String encryptedData = aesEncrypt(jsonStr, key);
|
||||
|
||||
// 发送 HTTP POST 请求并获取响应
|
||||
String response = sendPostRequest(url, encryptedData, accessId);
|
||||
|
||||
// 解析响应数据
|
||||
JSONObject responseData = new JSONObject(response);
|
||||
String encryptedResponseData = responseData.optString("data");
|
||||
|
||||
// 解密返回的加密数据
|
||||
if (encryptedResponseData != null) {
|
||||
String decryptedResponseData = aesDecrypt(encryptedResponseData, key);
|
||||
System.out.println("Decrypted Response Data: " + decryptedResponseData);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
266
public/examples/javascript/sms_signature_demo.js
Normal file
@@ -0,0 +1,266 @@
|
||||
/**
|
||||
* 短信发送接口签名示例(浏览器版本)
|
||||
*
|
||||
* 本示例演示如何在浏览器中为短信发送接口生成HMAC-SHA256签名
|
||||
*
|
||||
* 安全提示:
|
||||
* 1. 密钥应该通过代码混淆、字符串拆分等方式隐藏
|
||||
* 2. 不要在前端代码中直接暴露完整密钥
|
||||
* 3. 建议使用构建工具进行代码混淆和压缩
|
||||
* 4. 可以考虑将签名逻辑放在后端代理接口中
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取签名密钥(通过多种方式混淆,增加破解难度)
|
||||
* 注意:这只是示例,实际使用时应该进一步混淆
|
||||
*/
|
||||
function getSecretKey() {
|
||||
// 方式1: 字符串拆分和拼接
|
||||
const part1 = 'HyApi2026'
|
||||
const part2 = 'SMSSecret'
|
||||
const part3 = 'Key!@#$%^'
|
||||
const part4 = '&*()_+QWERTY'
|
||||
const part5 = 'UIOP'
|
||||
|
||||
// 方式2: 使用数组和join(增加混淆)
|
||||
const arr = [part1, part2, part3, part4, part5]
|
||||
return arr.join('')
|
||||
|
||||
// 方式3: 字符数组拼接(更复杂的方式)
|
||||
// const chars = ['T', 'y', 'A', 'p', 'i', '2', '0', '2', '4', ...];
|
||||
// return chars.join('');
|
||||
|
||||
// 方式4: 使用atob解码(如果密钥经过base64编码)
|
||||
// const encoded = 'base64_encoded_string';
|
||||
// return atob(encoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机字符串(用于nonce)
|
||||
*/
|
||||
function generateNonce(length = 16) {
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
let result = ''
|
||||
const array = new Uint8Array(length)
|
||||
crypto.getRandomValues(array)
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars[array[i] % chars.length]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用Web Crypto API生成HMAC-SHA256签名
|
||||
*
|
||||
* @param {Object} params - 请求参数对象
|
||||
* @param {string} secretKey - 签名密钥
|
||||
* @param {number} timestamp - 时间戳(秒)
|
||||
* @param {string} nonce - 随机字符串
|
||||
* @returns {Promise<string>} 签名字符串(hex编码)
|
||||
*/
|
||||
async function generateSignature(params, secretKey, timestamp, nonce) {
|
||||
// 1. 构建待签名字符串:按key排序,拼接成 key1=value1&key2=value2 格式
|
||||
const keys = Object.keys(params)
|
||||
.filter((k) => k !== 'signature') // 排除签名字段
|
||||
.sort()
|
||||
|
||||
const parts = keys.map((k) => `${k}=${params[k]}`)
|
||||
|
||||
// 2. 添加时间戳和随机数
|
||||
parts.push(`timestamp=${timestamp}`)
|
||||
parts.push(`nonce=${nonce}`)
|
||||
|
||||
// 3. 拼接成待签名字符串
|
||||
const signString = parts.join('&')
|
||||
|
||||
// 4. 使用Web Crypto API计算HMAC-SHA256签名
|
||||
const encoder = new TextEncoder()
|
||||
const keyData = encoder.encode(secretKey)
|
||||
const messageData = encoder.encode(signString)
|
||||
|
||||
// 导入密钥
|
||||
const cryptoKey = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
keyData,
|
||||
{ name: 'HMAC', hash: 'SHA-256' },
|
||||
false,
|
||||
['sign'],
|
||||
)
|
||||
|
||||
// 计算签名
|
||||
const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData)
|
||||
|
||||
// 转换为hex字符串
|
||||
const hashArray = Array.from(new Uint8Array(signature))
|
||||
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
|
||||
|
||||
return hashHex
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义编码字符集(与后端保持一致)
|
||||
*/
|
||||
const CUSTOM_ENCODE_CHARSET =
|
||||
'0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz!@#$%^&*()_+-=[]{}|;:,.<>?'
|
||||
|
||||
/**
|
||||
* 自定义Base64编码(使用自定义字符集)
|
||||
*/
|
||||
function customBase64Encode(data) {
|
||||
if (data.length === 0) return ''
|
||||
|
||||
// 将字符串转换为UTF-8字节数组
|
||||
const encoder = new TextEncoder()
|
||||
const bytes = encoder.encode(data)
|
||||
const charset = CUSTOM_ENCODE_CHARSET
|
||||
let result = ''
|
||||
|
||||
// 将3个字节(24位)编码为4个字符
|
||||
for (let i = 0; i < bytes.length; i += 3) {
|
||||
const b1 = bytes[i]
|
||||
const b2 = i + 1 < bytes.length ? bytes[i + 1] : 0
|
||||
const b3 = i + 2 < bytes.length ? bytes[i + 2] : 0
|
||||
|
||||
// 组合成24位
|
||||
const combined = (b1 << 16) | (b2 << 8) | b3
|
||||
|
||||
// 分成4个6位段
|
||||
result += charset[(combined >> 18) & 0x3f]
|
||||
result += charset[(combined >> 12) & 0x3f]
|
||||
|
||||
if (i + 1 < bytes.length) {
|
||||
result += charset[(combined >> 6) & 0x3f]
|
||||
} else {
|
||||
result += '=' // 填充字符
|
||||
}
|
||||
|
||||
if (i + 2 < bytes.length) {
|
||||
result += charset[combined & 0x3f]
|
||||
} else {
|
||||
result += '=' // 填充字符
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用字符偏移混淆
|
||||
*/
|
||||
function applyCharShift(data, shift) {
|
||||
const charset = CUSTOM_ENCODE_CHARSET
|
||||
const charsetLen = charset.length
|
||||
let result = ''
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const c = data[i]
|
||||
if (c === '=') {
|
||||
result += c // 填充字符不变
|
||||
continue
|
||||
}
|
||||
|
||||
const idx = charset.indexOf(c)
|
||||
if (idx === -1) {
|
||||
result += c // 不在字符集中,保持不变
|
||||
} else {
|
||||
// 应用偏移
|
||||
const newIdx = (idx + shift) % charsetLen
|
||||
result += charset[newIdx]
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义编码请求数据
|
||||
*/
|
||||
function encodeRequest(data) {
|
||||
// 1. 使用自定义Base64编码
|
||||
const encoded = customBase64Encode(data)
|
||||
|
||||
// 2. 应用字符偏移混淆(偏移7个位置)
|
||||
const confused = applyCharShift(encoded, 7)
|
||||
|
||||
return confused
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信验证码(带签名)- 方式2:编码后传输(推荐,更安全)
|
||||
* 将所有参数(包括签名)使用自定义编码方案编码后传输,隐藏参数结构
|
||||
*
|
||||
* @param {string} phone - 手机号
|
||||
* @param {string} scene - 场景(register/login/change_password/reset_password等)
|
||||
* @param {string} apiBaseUrl - API基础URL
|
||||
* @returns {Promise<Object>} 响应结果
|
||||
*/
|
||||
async function sendSMSWithEncodedSignature(phone, scene, apiBaseUrl = 'http://localhost:8080') {
|
||||
// 1. 准备参数
|
||||
const timestamp = Math.floor(Date.now() / 1000) // 当前时间戳(秒)
|
||||
const nonce = generateNonce(16) // 生成随机字符串
|
||||
|
||||
const params = {
|
||||
phone: phone,
|
||||
scene: scene,
|
||||
}
|
||||
|
||||
// 2. 生成签名
|
||||
const secretKey = getSecretKey()
|
||||
const signature = await generateSignature(params, secretKey, timestamp, nonce)
|
||||
|
||||
// 3. 构建包含所有参数的JSON对象
|
||||
const allParams = {
|
||||
phone: phone,
|
||||
scene: scene,
|
||||
timestamp: timestamp,
|
||||
nonce: nonce,
|
||||
signature: signature,
|
||||
}
|
||||
|
||||
// 4. 将JSON对象转换为字符串,然后使用自定义编码方案编码
|
||||
const jsonString = JSON.stringify(allParams)
|
||||
const encodedData = encodeRequest(jsonString)
|
||||
|
||||
// 5. 构建请求体(只包含编码后的data字段)
|
||||
const requestBody = {
|
||||
data: encodedData,
|
||||
}
|
||||
|
||||
// 6. 发送请求
|
||||
try {
|
||||
const response = await fetch(`${apiBaseUrl}/api/v1/users/send-code`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('发送短信失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 如果在浏览器环境中使用,可以导出到全局
|
||||
if (typeof window !== 'undefined') {
|
||||
window.SMSSignature = {
|
||||
sendSMSWithEncodedSignature,
|
||||
generateSignature,
|
||||
generateNonce,
|
||||
encodeRequest,
|
||||
}
|
||||
}
|
||||
|
||||
// 如果在Node.js环境中使用(需要安装crypto-js等库)
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = {
|
||||
sendSMSWithEncodedSignature,
|
||||
generateSignature,
|
||||
generateNonce,
|
||||
getSecretKey,
|
||||
encodeRequest,
|
||||
}
|
||||
}
|
||||
11
public/examples/nodejs/.eslintignore
Normal file
@@ -0,0 +1,11 @@
|
||||
# ESLint 忽略配置 - 忽略示例代码目录下的所有语法错误
|
||||
# 这些是示例代码文件,不需要进行 ESLint 语法检查
|
||||
|
||||
# 忽略当前目录下的所有 .js 文件
|
||||
*.js
|
||||
|
||||
# 忽略当前目录下的所有文件
|
||||
*
|
||||
|
||||
# 忽略子目录
|
||||
*/
|
||||
131
public/examples/nodejs/demo.js
Normal file
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const crypto = require('crypto')
|
||||
|
||||
// ==================== API配置 ====================
|
||||
const ACCESS_ID = 'XXXXXXXXX' // 替换为您的ACCESS_ID
|
||||
const APP_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXX' // 替换为您的app_secret
|
||||
const BASE_URL = 'https://api.haiyudata.com'
|
||||
|
||||
// ==================== 测试参数 ====================
|
||||
const API_CODE = 'FLXG0V4B' // 替换为您的API编号
|
||||
|
||||
const PARAMS = {
|
||||
name: 'XXXX',
|
||||
id_card: 'XXXXXXXXXXXXXXX',
|
||||
auth_date: '20250722-20250923',
|
||||
}
|
||||
|
||||
// ==================== 加密解密函数 ====================
|
||||
function encrypt_data(data) {
|
||||
const key = Buffer.from(APP_SECRET, 'hex')
|
||||
const iv = crypto.randomBytes(16)
|
||||
const cipher = crypto.createCipheriv('aes-128-cbc', key, iv)
|
||||
cipher.setAutoPadding(true)
|
||||
let encrypted = cipher.update(data, 'utf8')
|
||||
encrypted = Buffer.concat([iv, encrypted, cipher.final()])
|
||||
return encrypted.toString('base64')
|
||||
}
|
||||
|
||||
function decrypt_data(encrypted_data) {
|
||||
const key = Buffer.from(APP_SECRET, 'hex')
|
||||
const encryptedBuffer = Buffer.from(encrypted_data, 'base64')
|
||||
const iv = encryptedBuffer.slice(0, 16)
|
||||
const ciphertext = encryptedBuffer.slice(16)
|
||||
const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv)
|
||||
decipher.setAutoPadding(true)
|
||||
let decrypted = decipher.update(ciphertext)
|
||||
decrypted = Buffer.concat([decrypted, decipher.final()])
|
||||
return decrypted.toString('utf8')
|
||||
}
|
||||
|
||||
// ==================== 主测试函数 ====================
|
||||
async function test_api() {
|
||||
try {
|
||||
console.log(`=== 测试API: ${API_CODE} ===`)
|
||||
console.log(`请求参数: ${JSON.stringify(PARAMS, null, 2)}`)
|
||||
|
||||
// 加密参数
|
||||
const params_json = JSON.stringify(PARAMS)
|
||||
const encrypted_data = encrypt_data(params_json)
|
||||
|
||||
// 构建请求
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Id': ACCESS_ID,
|
||||
}
|
||||
|
||||
const url = `${BASE_URL}/api/v1/${API_CODE}`
|
||||
const request_data = { data: encrypted_data, options: { json: true } }
|
||||
|
||||
console.log(`请求URL: ${url}`)
|
||||
console.log(`请求头: ${JSON.stringify(headers, null, 2)}`)
|
||||
|
||||
// 发送请求
|
||||
const start_time = Date.now()
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(request_data),
|
||||
signal: AbortSignal.timeout(30000), // 30秒超时
|
||||
})
|
||||
const elapsed_time = Date.now() - start_time
|
||||
|
||||
console.log(`\n=== 响应信息 ===`)
|
||||
console.log(`状态码: ${response.status}`)
|
||||
console.log(`耗时: ${elapsed_time}ms`)
|
||||
|
||||
if (response.status !== 200) {
|
||||
console.log(`请求失败: ${response.status} ${response.statusText}`)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
try {
|
||||
const response_json = await response.json()
|
||||
console.log(`原始响应: ${JSON.stringify(response_json, null, 2)}`)
|
||||
|
||||
// 检查响应格式
|
||||
if (!('code' in response_json)) {
|
||||
console.log('直接返回业务数据')
|
||||
return
|
||||
}
|
||||
|
||||
// 标准格式处理
|
||||
const api_code = response_json.code
|
||||
const api_message = response_json.message || ''
|
||||
const encrypted_response = response_json.data || ''
|
||||
|
||||
console.log(`API响应码: ${api_code}`)
|
||||
console.log(`API消息: ${api_message}`)
|
||||
|
||||
if (api_code !== 0) {
|
||||
console.log(`API错误: ${api_message}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (!encrypted_response) {
|
||||
console.log('无加密数据返回')
|
||||
return
|
||||
}
|
||||
|
||||
// 解密数据
|
||||
try {
|
||||
const decrypted_data = decrypt_data(encrypted_response)
|
||||
const result_data = JSON.parse(decrypted_data)
|
||||
|
||||
console.log(`\n=== 解密后的数据 ===`)
|
||||
console.log(JSON.stringify(result_data, null, 2))
|
||||
} catch (e) {
|
||||
console.log(`解密失败: ${e.message}`)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`响应不是JSON格式: ${e.message}`)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`测试异常: ${e.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
test_api()
|
||||
287
public/examples/nodejs/sms_signature_demo.js
Normal file
@@ -0,0 +1,287 @@
|
||||
/**
|
||||
* 短信发送接口签名示例
|
||||
*
|
||||
* 本示例演示如何为短信发送接口生成HMAC-SHA256签名
|
||||
*
|
||||
* 安全提示:
|
||||
* 1. 密钥应该通过代码混淆、字符串拆分等方式隐藏
|
||||
* 2. 不要在前端代码中直接暴露完整密钥
|
||||
* 3. 建议使用构建工具进行代码混淆
|
||||
*/
|
||||
|
||||
const crypto = require('crypto')
|
||||
|
||||
/**
|
||||
* 获取签名密钥(通过多种方式混淆,增加破解难度)
|
||||
* 注意:这只是示例,实际使用时应该进一步混淆
|
||||
*/
|
||||
function getSecretKey() {
|
||||
// 方式1: 字符串拆分和拼接
|
||||
const part1 = 'HyApi2026'
|
||||
const part2 = 'SMSSecret'
|
||||
const part3 = 'Key!@#$%^'
|
||||
const part4 = '&*()_+QWERTY'
|
||||
const part5 = 'UIOP'
|
||||
|
||||
// 方式2: Base64解码(可选,增加一层混淆)
|
||||
// const encoded = Buffer.from('some_base64_string', 'base64').toString();
|
||||
|
||||
// 方式3: 字符数组拼接
|
||||
const chars = [
|
||||
'T',
|
||||
'y',
|
||||
'A',
|
||||
'p',
|
||||
'i',
|
||||
'2',
|
||||
'0',
|
||||
'2',
|
||||
'4',
|
||||
'S',
|
||||
'M',
|
||||
'S',
|
||||
'S',
|
||||
'e',
|
||||
'c',
|
||||
'r',
|
||||
'e',
|
||||
't',
|
||||
'K',
|
||||
'e',
|
||||
'y',
|
||||
'!',
|
||||
'@',
|
||||
'#',
|
||||
'$',
|
||||
'%',
|
||||
'^',
|
||||
'&',
|
||||
'*',
|
||||
'(',
|
||||
')',
|
||||
'_',
|
||||
'+',
|
||||
'Q',
|
||||
'W',
|
||||
'E',
|
||||
'R',
|
||||
'T',
|
||||
'Y',
|
||||
'U',
|
||||
'I',
|
||||
'O',
|
||||
'P',
|
||||
]
|
||||
const fromChars = chars.join('')
|
||||
|
||||
// 组合多种方式(实际密钥:HyApi2026SMSSecretKey!@#$%^&*()_+QWERTYUIOP)
|
||||
return part1 + part2 + part3 + part4 + part5
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机字符串(用于nonce)
|
||||
*/
|
||||
function generateNonce(length = 16) {
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
let result = ''
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成HMAC-SHA256签名
|
||||
*
|
||||
* @param {Object} params - 请求参数对象
|
||||
* @param {string} secretKey - 签名密钥
|
||||
* @param {number} timestamp - 时间戳(秒)
|
||||
* @param {string} nonce - 随机字符串
|
||||
* @returns {string} 签名字符串(hex编码)
|
||||
*/
|
||||
function generateSignature(params, secretKey, timestamp, nonce) {
|
||||
// 1. 构建待签名字符串:按key排序,拼接成 key1=value1&key2=value2 格式
|
||||
const keys = Object.keys(params)
|
||||
.filter((k) => k !== 'signature') // 排除签名字段
|
||||
.sort()
|
||||
|
||||
const parts = keys.map((k) => `${k}=${params[k]}`)
|
||||
|
||||
// 2. 添加时间戳和随机数
|
||||
parts.push(`timestamp=${timestamp}`)
|
||||
parts.push(`nonce=${nonce}`)
|
||||
|
||||
// 3. 拼接成待签名字符串
|
||||
const signString = parts.join('&')
|
||||
|
||||
// 4. 使用HMAC-SHA256计算签名
|
||||
const signature = crypto.createHmac('sha256', secretKey).update(signString).digest('hex')
|
||||
|
||||
return signature
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义编码字符集(与后端保持一致)
|
||||
*/
|
||||
const CUSTOM_ENCODE_CHARSET =
|
||||
'0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz!@#$%^&*()_+-=[]{}|;:,.<>?'
|
||||
|
||||
/**
|
||||
* 自定义Base64编码(使用自定义字符集)
|
||||
*/
|
||||
function customBase64Encode(data) {
|
||||
if (data.length === 0) return ''
|
||||
|
||||
const bytes = Buffer.from(data, 'utf8')
|
||||
const charset = CUSTOM_ENCODE_CHARSET
|
||||
let result = ''
|
||||
|
||||
// 将3个字节(24位)编码为4个字符
|
||||
for (let i = 0; i < bytes.length; i += 3) {
|
||||
const b1 = bytes[i]
|
||||
const b2 = i + 1 < bytes.length ? bytes[i + 1] : 0
|
||||
const b3 = i + 2 < bytes.length ? bytes[i + 2] : 0
|
||||
|
||||
// 组合成24位
|
||||
const combined = (b1 << 16) | (b2 << 8) | b3
|
||||
|
||||
// 分成4个6位段
|
||||
result += charset[(combined >> 18) & 0x3f]
|
||||
result += charset[(combined >> 12) & 0x3f]
|
||||
|
||||
if (i + 1 < bytes.length) {
|
||||
result += charset[(combined >> 6) & 0x3f]
|
||||
} else {
|
||||
result += '=' // 填充字符
|
||||
}
|
||||
|
||||
if (i + 2 < bytes.length) {
|
||||
result += charset[combined & 0x3f]
|
||||
} else {
|
||||
result += '=' // 填充字符
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用字符偏移混淆
|
||||
*/
|
||||
function applyCharShift(data, shift) {
|
||||
const charset = CUSTOM_ENCODE_CHARSET
|
||||
const charsetLen = charset.length
|
||||
let result = ''
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const c = data[i]
|
||||
if (c === '=') {
|
||||
result += c // 填充字符不变
|
||||
continue
|
||||
}
|
||||
|
||||
const idx = charset.indexOf(c)
|
||||
if (idx === -1) {
|
||||
result += c // 不在字符集中,保持不变
|
||||
} else {
|
||||
// 应用偏移
|
||||
const newIdx = (idx + shift) % charsetLen
|
||||
result += charset[newIdx]
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义编码请求数据
|
||||
*/
|
||||
function encodeRequest(data) {
|
||||
// 1. 使用自定义Base64编码
|
||||
const encoded = customBase64Encode(data)
|
||||
|
||||
// 2. 应用字符偏移混淆(偏移7个位置)
|
||||
const confused = applyCharShift(encoded, 7)
|
||||
|
||||
return confused
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信验证码(带签名)- 方式2:编码后传输(推荐,更安全)
|
||||
* 将所有参数(包括签名)使用自定义编码方案编码后传输,隐藏参数结构
|
||||
*
|
||||
* @param {string} phone - 手机号
|
||||
* @param {string} scene - 场景(register/login/change_password/reset_password等)
|
||||
* @param {string} apiBaseUrl - API基础URL
|
||||
* @returns {Promise<Object>} 响应结果
|
||||
*/
|
||||
async function sendSMSWithEncodedSignature(phone, scene, apiBaseUrl = 'http://localhost:8080') {
|
||||
// 1. 准备参数
|
||||
const timestamp = Math.floor(Date.now() / 1000) // 当前时间戳(秒)
|
||||
const nonce = generateNonce(16) // 生成随机字符串
|
||||
|
||||
const params = {
|
||||
phone: phone,
|
||||
scene: scene,
|
||||
}
|
||||
|
||||
// 2. 生成签名
|
||||
const secretKey = getSecretKey()
|
||||
const signature = generateSignature(params, secretKey, timestamp, nonce)
|
||||
|
||||
// 3. 构建包含所有参数的JSON对象
|
||||
const allParams = {
|
||||
phone: phone,
|
||||
scene: scene,
|
||||
timestamp: timestamp,
|
||||
nonce: nonce,
|
||||
signature: signature,
|
||||
}
|
||||
|
||||
// 4. 将JSON对象转换为字符串,然后使用自定义编码方案编码
|
||||
const jsonString = JSON.stringify(allParams)
|
||||
const encodedData = encodeRequest(jsonString)
|
||||
|
||||
// 5. 构建请求体(只包含编码后的data字段)
|
||||
const requestBody = {
|
||||
data: encodedData,
|
||||
}
|
||||
|
||||
// 6. 发送请求
|
||||
try {
|
||||
const response = await fetch(`${apiBaseUrl}/api/v1/users/send-code`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('发送短信失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
if (require.main === module) {
|
||||
console.log('=== 发送短信验证码(使用自定义编码) ===')
|
||||
// 示例:发送注册验证码(使用自定义编码方案,只传递data字段)
|
||||
sendSMSWithEncodedSignature('13800138000', 'register', 'http://localhost:8080')
|
||||
.then((result) => {
|
||||
console.log('发送成功:', result)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('发送失败:', error)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendSMSWithEncodedSignature,
|
||||
generateSignature,
|
||||
generateNonce,
|
||||
getSecretKey,
|
||||
encodeRequest,
|
||||
}
|
||||
176
public/examples/php/demo.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AES CBC 加密函数,返回 Base64
|
||||
* @param string $plainText 要加密的明文
|
||||
* @param string $key 16进制密钥
|
||||
* @return string 加密后的Base64字符串
|
||||
*/
|
||||
function aesEncrypt($plainText, $key) {
|
||||
// 将16进制的密钥转换为二进制
|
||||
$keyBin = hex2bin($key);
|
||||
|
||||
// 生成随机IV
|
||||
$blockSize = 16; // AES 块大小
|
||||
$iv = openssl_random_pseudo_bytes($blockSize);
|
||||
|
||||
// 加密
|
||||
$encrypted = openssl_encrypt(
|
||||
$plainText,
|
||||
'AES-128-CBC',
|
||||
$keyBin,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv
|
||||
);
|
||||
|
||||
// 将IV和加密数据拼接后进行Base64编码
|
||||
return base64_encode($iv . $encrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES CBC 解密函数,返回解密后的明文
|
||||
* @param string $encryptedText Base64编码的加密文本
|
||||
* @param string $key 16进制密钥
|
||||
* @return string 解密后的明文
|
||||
*/
|
||||
function aesDecrypt($encryptedText, $key) {
|
||||
// 将16进制的密钥转换为二进制
|
||||
$keyBin = hex2bin($key);
|
||||
|
||||
// 解码Base64
|
||||
$encryptedBin = base64_decode($encryptedText);
|
||||
|
||||
// 提取IV和加密数据
|
||||
$blockSize = 16;
|
||||
$iv = substr($encryptedBin, 0, $blockSize);
|
||||
$encryptedData = substr($encryptedBin, $blockSize);
|
||||
|
||||
// 解密
|
||||
$decrypted = openssl_decrypt(
|
||||
$encryptedData,
|
||||
'AES-128-CBC',
|
||||
$keyBin,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv
|
||||
);
|
||||
|
||||
return $decrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用API函数
|
||||
*/
|
||||
function callApi($name, $id_card, $mobile_no, $auth_date) {
|
||||
// API相关配置
|
||||
$interface_name = "XXXXXXXX"; // 接口编号
|
||||
$access_id = "XXXXXXXXXXX";
|
||||
$key = "XXXXXXXXXXXXXXXXXXXXX";
|
||||
$url = "https://api.haiyudata.com/api/v1/{$interface_name}";
|
||||
|
||||
// 构建请求参数
|
||||
$params = array(
|
||||
"mobile_no" => $mobile_no,
|
||||
"id_card" => $id_card,
|
||||
"auth_date" => $auth_date,
|
||||
"name" => $name
|
||||
);
|
||||
|
||||
// 将参数转换为JSON字符串并加密
|
||||
$json_str = json_encode($params, JSON_UNESCAPED_UNICODE);
|
||||
echo "请求参数: {$json_str}\n";
|
||||
$encrypted_data = aesEncrypt($json_str, $key);
|
||||
echo "加密后的数据: {$encrypted_data}\n";
|
||||
|
||||
// 发送请求
|
||||
$headers = array(
|
||||
"Access-Id: {$access_id}",
|
||||
"Content-Type: application/json"
|
||||
);
|
||||
|
||||
$payload = array(
|
||||
"data" => $encrypted_data
|
||||
);
|
||||
|
||||
echo "发送请求到: {$url}\n";
|
||||
|
||||
try {
|
||||
// 使用PHP原生HTTP请求方式
|
||||
$context = stream_context_create(array(
|
||||
'http' => array(
|
||||
'method' => 'POST',
|
||||
'header' => implode("\r\n", $headers),
|
||||
'content' => json_encode($payload),
|
||||
'timeout' => 30
|
||||
)
|
||||
));
|
||||
|
||||
$response = file_get_contents($url, false, $context);
|
||||
|
||||
if ($response === false) {
|
||||
throw new Exception("HTTP请求失败");
|
||||
}
|
||||
|
||||
$response_data = json_decode($response, true);
|
||||
echo "API响应: " . json_encode($response_data, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
|
||||
// 处理响应
|
||||
$code = $response_data['code'] ?? null;
|
||||
$message = $response_data['message'] ?? '';
|
||||
$encrypted_response_data = $response_data['data'] ?? '';
|
||||
|
||||
$result = array(
|
||||
"code" => $code,
|
||||
"success" => $code == 0,
|
||||
"message" => $message,
|
||||
"encrypted_response" => $encrypted_response_data
|
||||
);
|
||||
|
||||
// 如果有返回data,尝试解密
|
||||
if ($encrypted_response_data) {
|
||||
try {
|
||||
$decrypted_data = aesDecrypt($encrypted_response_data, $key);
|
||||
$result["decrypted_response"] = json_decode($decrypted_data, true);
|
||||
} catch (Exception $e) {
|
||||
echo "解密响应数据失败: {$e->getMessage()}\n";
|
||||
$result["decrypted_response"] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "请求失败: {$e->getMessage()}\n";
|
||||
return array("success" => false, "message" => "请求失败: {$e->getMessage()}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数
|
||||
*/
|
||||
function main() {
|
||||
echo "===== 个人涉诉详版 =====\n";
|
||||
|
||||
// 直接设置手机号和姓名
|
||||
$name = "XXXXXXXX";
|
||||
$id_card = "XXXXXXXXXXXXX";
|
||||
$mobile_no = "XXXXXXXXXXXXXXXXXXXX";
|
||||
$auth_date = "20250318-20270318";
|
||||
|
||||
$result = callApi($name, $id_card, $mobile_no, $auth_date);
|
||||
|
||||
echo "\n===== 结果 =====\n";
|
||||
if ($result["success"]) {
|
||||
echo "请求成功!\n";
|
||||
if (isset($result["decrypted_response"])) {
|
||||
echo "解密后的响应: " . json_encode($result['decrypted_response'], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
|
||||
} else {
|
||||
echo "未能获取或解密响应数据\n";
|
||||
}
|
||||
} else {
|
||||
echo "请求失败: " . ($result['message'] ?? '未知错误') . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// 运行主函数
|
||||
main();
|
||||
?>
|
||||
124
public/examples/python/demo.py
Normal file
@@ -0,0 +1,124 @@
|
||||
import requests
|
||||
import json
|
||||
import base64
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
import os
|
||||
|
||||
# AES CBC 加密函数,返回 Base64
|
||||
def aes_encrypt(plaintext, key):
|
||||
# 将16进制密钥转换为字节
|
||||
key_bytes = bytes.fromhex(key)
|
||||
# 生成随机IV
|
||||
iv = os.urandom(16)
|
||||
# 创建加密器
|
||||
cipher = AES.new(key_bytes, AES.MODE_CBC, iv)
|
||||
# 对明文进行填充并加密
|
||||
padded_data = pad(plaintext.encode('utf-8'), AES.block_size)
|
||||
encrypted_data = cipher.encrypt(padded_data)
|
||||
# 连接IV和加密后的数据
|
||||
result = iv + encrypted_data
|
||||
# 转换为Base64编码
|
||||
return base64.b64encode(result).decode('utf-8')
|
||||
|
||||
# AES CBC 解密函数,返回解密后的明文
|
||||
def aes_decrypt(encrypted_text, key):
|
||||
# 将Base64编码的加密数据转换为字节
|
||||
encrypted_bytes = base64.b64decode(encrypted_text)
|
||||
# 将16进制密钥转换为字节
|
||||
key_bytes = bytes.fromhex(key)
|
||||
# 从加密数据中提取IV和加密数据
|
||||
iv = encrypted_bytes[:16]
|
||||
encrypted_data = encrypted_bytes[16:]
|
||||
# 创建解密器
|
||||
decipher = AES.new(key_bytes, AES.MODE_CBC, iv)
|
||||
# 解密并去除填充
|
||||
padded_data = decipher.decrypt(encrypted_data)
|
||||
return unpad(padded_data, AES.block_size).decode('utf-8')
|
||||
|
||||
def CallApi(name,id_card,mobile_no,auth_date):
|
||||
# API相关配置
|
||||
interface_name = "XXXXXXXX" #接口编号
|
||||
access_id = "XXXXXXXXXXX"
|
||||
key = "XXXXXXXXXXXXXXXXXXXXX"
|
||||
url = f"https://api.haiyudata.com/api/v1/{interface_name}"
|
||||
|
||||
# 构建请求参数
|
||||
params = {
|
||||
"mobile_no": mobile_no,
|
||||
"id_card": id_card,
|
||||
"auth_date": auth_date,
|
||||
"name": name,
|
||||
}
|
||||
|
||||
# 将参数转换为JSON字符串并加密
|
||||
json_str = json.dumps(params)
|
||||
print(f"请求参数: {json_str}")
|
||||
encrypted_data = aes_encrypt(json_str, key)
|
||||
print(f"加密后的数据: {encrypted_data}")
|
||||
|
||||
# 发送请求
|
||||
headers = {
|
||||
"Access-Id": access_id,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"data": encrypted_data
|
||||
}
|
||||
|
||||
print(f"发送请求到: {url}")
|
||||
try:
|
||||
response = requests.post(url, json=payload, headers=headers)
|
||||
response_data = response.json()
|
||||
print(f"API响应: {response_data}")
|
||||
|
||||
# 处理响应
|
||||
code = response_data.get("code")
|
||||
message = response_data.get("message")
|
||||
encrypted_response_data = response_data.get("data")
|
||||
|
||||
result = {
|
||||
"code": code,
|
||||
"success": code == 0,
|
||||
"message": message,
|
||||
"encrypted_response": encrypted_response_data
|
||||
}
|
||||
|
||||
# 如果有返回data,尝试解密
|
||||
if encrypted_response_data:
|
||||
try:
|
||||
decrypted_data = aes_decrypt(encrypted_response_data, key)
|
||||
result["decrypted_response"] = json.loads(decrypted_data)
|
||||
except Exception as e:
|
||||
print(f"解密响应数据失败: {e}")
|
||||
result["decrypted_response"] = None
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
print(f"请求失败: {e}")
|
||||
return {"success": False, "message": f"请求失败: {e}"}
|
||||
|
||||
def main():
|
||||
print("===== 个人涉诉详版 =====")
|
||||
|
||||
# 直接设置手机号和姓名
|
||||
name = "XXXXXXXX"
|
||||
id_card = "XXXXXXXXXXXXX"
|
||||
mobile_no = "XXXXXXXXXXXXXXXXXXXX"
|
||||
auth_date = "20250318-20270318"
|
||||
|
||||
result = CallApi(name,id_card,mobile_no,auth_date)
|
||||
|
||||
print("\n===== 结果 =====")
|
||||
if result["success"]:
|
||||
print("请求成功!")
|
||||
if result.get("decrypted_response"):
|
||||
print(f"解密后的响应: {json.dumps(result['decrypted_response'], ensure_ascii=False, indent=2)}")
|
||||
else:
|
||||
print("未能获取或解密响应数据")
|
||||
else:
|
||||
print(f"请求失败: {result.get('message', '未知错误')}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
BIN
public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
61
public/legal/yhxy.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# 海宇数据用户协议
|
||||
|
||||
## 欢迎与提示
|
||||
|
||||
欢迎访问海宇数据网站并使用我们提供的产品和服务。在完成注册程序或以任何方式使用海宇数据网站服务前,请您务必仔细阅读并透彻理解本服务条款,在确认充分理解后选择接受或不接受本服务条款。一旦您完成「同意条款并注册」或开始以其他方式使用海宇数据服务,即表明您已阅读并同意受本服务条款的约束。如您不同意本服务条款或其中任何条款约定,您应不再进行下一步或停止注册程序。
|
||||
|
||||
海宇数据再次提示您审慎阅读、充分理解各条款内容,特别是**限制或免除责任**的相应条款。限制或免除责任条款将以加粗或其他醒目形式提示您注意。如果您未满 18 周岁,请在法定监护人的陪同下阅读本服务条款。海宇数据致力于为用户提供优质的服务,同时严格遵守相关的法律法规,保护您的合法权益,确保您在使用我们的服务时能够享受到安全、便捷、高效的体验。我们始终将用户的需求和体验放在首位,通过不断优化和升级我们的服务,力求为用户提供全方位的支持和保障。我们注重用户的反馈和建议,积极进行服务的改进与创新,力求打造行业领先的数据服务平台。
|
||||
|
||||
## 一、签约主体及协议范围
|
||||
|
||||
### 1.1 协议主体
|
||||
|
||||
本服务协议是您与**海宇数科(广东横琴)科技有限公司**(以下简称「海宇数据」或「我们」)就使用海宇数据网站服务(包括提供的网页浏览、账户注册、数据查询、数据分析、API 接口调用等服务)所达成的有效合约。您通过访问和使用海宇数据的服务,表示您同意与海宇数科(广东横琴)科技有限公司订立此具有法律约束力的协议。我们致力于为用户提供高质量的数据服务,并以合规和透明的方式进行运营,确保用户的使用体验。
|
||||
|
||||
### 1.2 服务载体与具体服务
|
||||
|
||||
海宇数据网站包含域名为 [www.haiyudata.com](https://www.haiyudata.com) 的网站以及相关移动客户端(例如 APP)。如您使用或购买海宇数据网站上的某项具体服务,您可能仍需确认具体服务对应的服务条款。每项具体服务可能会包含不同的使用条款、收费标准和服务时间,建议您在使用前仔细查阅相关的协议条款。我们可能根据实际情况对服务内容进行变更,并将尽力以电子邮件、网站公告等形式通知您。若您未能在通知的期限内明确表示异议,您将被视为同意相关变更。我们将尽一切可能确保变更后的条款不影响您的基本权利和服务体验,并提供合理的过渡期以减少对用户的影响。
|
||||
|
||||
### 1.3 与单项协议的关系
|
||||
|
||||
本协议包括但不限于服务条款、隐私政策、使用说明、用户行为规范等,所有这些条款共同构成您与海宇数据之间的完整协议。若您与海宇数据之间存在其他书面协议或在线确认的条款,除非这些条款与本协议存在直接冲突,否则应共同适用于您对海宇数据服务的使用。我们建议用户在使用不同服务前,仔细阅读与其对应的具体条款,以全面了解各项服务的要求和规范,避免在服务使用中产生误解或纠纷。同时,我们承诺不断完善服务协议内容,以更好地保护用户的合法权益。
|
||||
|
||||
## 二、账户注册、使用及安全
|
||||
|
||||
### 2.1 注册资格
|
||||
|
||||
#### 2.1.1 行为能力及未成年人
|
||||
|
||||
您确认,您应具备完全民事行为能力的自然人、法人或其他组织。如您是未成年人或限制民事行为能力人,您的监护人应承担因您的不当注册行为而导致的一切后果。若您不具备前述条件,您应立即停止账户注册程序或停止使用海宇数据提供的服务。为了保障用户的合法权益,我们可能要求您提供相关的身份证明材料,以验证您的注册资格。在用户资格验证过程中,我们承诺对用户的隐私信息进行严格保护,确保不会泄露或滥用您的身份信息。
|
||||
|
||||
#### 2.1.2 出口管制与制裁
|
||||
|
||||
您还需确保自己不是任何国家、国际组织或地域实施的贸易限制、制裁或其他法律、规则限制的对象,否则可能无法正常注册和使用海宇数据服务。在此情况下,海宇数据保留取消您的注册和服务使用权限的权利,以保障公司及其他用户的合法权益不受损害。我们承诺将根据相关法律法规的要求严格保密您的个人信息,绝不将其用于与注册资格验证无关的用途。如因违反此条款导致的任何法律后果,您需自行承担全部责任。
|
||||
|
||||
### 2.2 账户注册
|
||||
|
||||
#### 2.2.1 注册成为用户
|
||||
|
||||
当您按照注册页面提示填写信息、阅读并同意本协议且完成注册程序后,您即可获得海宇数据账户并成为海宇数据的用户。我们将对您提供的注册信息予以核实,确保其真实性和有效性,并保留进一步联系以验证信息的权利。对于提供虚假信息的用户,海宇数据有权随时终止服务,并保留追究其法律责任的权利。我们建议用户提供真实、准确、完整的个人信息,以便更好地享受我们的服务。
|
||||
|
||||
#### 2.2.2 账户名称与密码
|
||||
|
||||
您在注册时设置的账户名称及密码,将用于后续登录和使用服务,请务必妥善保管。您应对您的账户信息保密,不得泄露给任何其他人,以防账户被他人恶意使用。因账户信息泄露或因您的疏忽导致账户被盗用所产生的后果由您自行承担。为了提高账户安全性,我们建议您定期更换密码,并避免使用过于简单或容易猜测的密码。同时,您可以启用双重身份验证功能以增加账户的安全性,进一步防范账户被盗风险。
|
||||
|
||||
#### 2.2.3 信息真实、准确与更新
|
||||
|
||||
您需确保账户信息的真实性和准确性,并及时更新。如您提供的信息不准确、不真实或未能及时更新,海宇数据有权采取措施限制您的使用权限,包括但不限于暂停或注销您的账户。若因您提供虚假信息导致纠纷,您应承担全部责任,同时海宇数据有权拒绝为您提供后续的服务。为了确保账户信息的真实性和安全性,我们将定期对账户信息进行核验,并可能要求您在特定情况下重新验证您的身份。对于多次提供虚假信息或拒绝验证的用户,我们将保留终止其账户服务的权利。
|
||||
|
||||
#### 2.2.4 进一步身份核验
|
||||
|
||||
海宇数据可能要求您提供更多身份信息以使用特定服务,账户在验证通过后方可获得相关使用资格。我们将根据相关法律法规的规定对您的信息进行严格保护,不会用于协议目的之外的用途。对于涉及特定敏感数据的服务,我们可能采取更严格的验证措施以确保数据安全与合规。例如,涉及金融数据或涉及高风险业务的服务,我们将要求用户提供额外的身份证明,以确保服务的合法性和安全性。我们还可能会对某些高敏感度的数据进行双重验证,以确保数据的绝对安全性和服务的合规性。对于未能通过验证的用户,我们将无法提供相关服务,敬请谅解。
|
||||
|
||||
### 2.3 账户安全
|
||||
|
||||
#### 2.3.1 保管与退出
|
||||
|
||||
您的账户和密码由您自行设置并由您保管,您应对因账户保管不善导致的任何损失负责。您需确保在每次上网时段结束时正确退出,确保账户安全,避免因公共网络或设备使用不当导致账户泄露。为保护账户安全,我们建议您不要在公共网络环境中使用海宇数据服务,并定期检查账户的登录记录。若发现任何异常登录活动,应及时更改密码并联系海宇数据的客服人员以获得帮助。
|
||||
|
||||
#### 2.3.2 通知与配合
|
||||
|
||||
若您发现账户被未经授权使用或存在其他安全问题,请立即通过海宇数据官方公布的联系方式通知我们,并配合我们采取为核实身份及保护账户安全所合理必需的措施。
|
||||
57
public/legal/yszc.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# 海宇数据隐私政策
|
||||
|
||||
**海宇数科(广东横琴)科技有限公司**(以下简称「海宇数据」或「我们」)非常重视您的隐私保护。您在使用我们的服务时,我们可能会收集和使用您的个人信息。本隐私政策旨在向您说明,我们如何收集、使用、储存和共享这些信息,以及如何为您提供访问、更正、控制和保护这些信息的方式。我们致力于维护您的个人信息的隐私、安全与保密性。请您仔细阅读本政策,确保您在充分理解的基础上使用我们的服务。使用或继续使用我们的服务,即表示您已充分理解并同意本政策的全部内容。
|
||||
|
||||
我们深知个人信息对于您的重要性,因此会采取一切合理、可行的措施来保护您的个人信息安全。我们秉承透明、合法、必要的原则进行信息的收集和处理,以最大限度地保障您的合法权益。此外,我们不断更新和改进我们的安全技术和管理措施,以应对不断变化的信息安全威胁,确保您的信息始终处于安全状态。
|
||||
|
||||
## 目录
|
||||
|
||||
1. 我们收集哪些信息及如何使用这些信息
|
||||
2. 我们如何使用 Cookie 和类似技术
|
||||
3. 我们如何共享、转让及公开披露您的个人信息
|
||||
4. 我们如何保存和保护您的个人信息
|
||||
5. 您的权利
|
||||
6. 未成年人的信息保护
|
||||
7. 个人信息的跨境传输
|
||||
8. 本政策的更新
|
||||
9. 如何联系我们
|
||||
|
||||
## 一、我们收集哪些信息及如何使用这些信息
|
||||
|
||||
### 1.1 个人信息的定义
|
||||
|
||||
「个人信息」是指以电子或其他方式记录的、能够单独或与其他信息结合识别您身份的各种信息。
|
||||
这些信息可能包括但不限于:姓名、手机号码、身份证号码、地址、位置信息、支付信息、电子邮箱、账户信息、设备信息以及其他能够识别您身份的相关数据。我们收集这些信息的目的是为了为您提供更为个性化、高效和便捷的服务。
|
||||
|
||||
### 1.2 信息的收集方式
|
||||
|
||||
#### (1)您主动提供的信息
|
||||
|
||||
在您使用我们的服务时,我们会收集您主动提供的个人信息。具体包括:
|
||||
|
||||
- **注册和身份验证信息**:当您注册成为我们的用户时,您可能需要提供您的账号名称、手机号码、电子邮箱、登录密码、真实姓名、身份证号码等,以便我们为您提供更为个性化的服务。
|
||||
- **支付信息**:在使用我们的服务过程中,您可能会提供支付相关信息,如银行卡信息、交易记录、开票信息等。这些信息将用于处理支付、结算以及可能的退款。我们会确保这些信息通过加密手段进行传输和存储,以保障您的财产安全。
|
||||
- **位置信息**:当您开启设备的定位功能后,我们会通过 GPS、Wi-Fi 等技术获取您的位置信息,以便为您提供与地理位置相关的个性化服务,如附近的服务推荐、路线规划等。我们尊重您的选择,您可以通过设备设置管理是否授权我们获取您的位置信息。
|
||||
- **用户提供的内容**:您可以在我们的平台上发布评论、上传图片及其他内容,我们将收集这些您主动发布的信息,以便更好地满足您与其他用户的互动需求。我们也会分析这些信息以优化平台的用户体验。
|
||||
|
||||
#### (2)我们自动采集的信息
|
||||
|
||||
在您使用我们的服务过程中,我们会通过技术手段自动采集某些信息。具体包括:
|
||||
|
||||
- **设备信息**:为了优化我们的服务体验,我们会收集设备相关的信息,包括但不限于设备型号、操作系统、设备唯一标识符(如 IMEI)、IP 地址、浏览器类型等。这些信息有助于我们更好地适配我们的服务,并确保不同设备上的用户都能够获得一致的使用体验。
|
||||
- **日志信息**:每当您访问我们的服务时,我们会自动记录访问的时间、浏览页面、点击行为、操作记录、崩溃日志等,以帮助我们更好地分析用户行为并改进服务质量。我们会将这些信息用于统计分析,以便不断改进我们的产品和服务,使其更符合用户的需求。
|
||||
|
||||
#### (3)第三方信息
|
||||
|
||||
- **第三方授权登录**:当您通过第三方账号(如微信、QQ)登录时,我们会根据您的授权从第三方获取必要的个人信息,包括昵称、头像、绑定的电话号码等,以便我们为您提供快速登录和便捷的服务体验。我们会确保获取的信息仅用于您授权的用途,并严格遵守第三方平台的相关规定。
|
||||
- **关联公司和合作伙伴提供的信息**:在获得您的明确授权后,我们可能从合作伙伴处获取相关信息,用于帮助我们更好地理解您的需求并为您提供更全面的服务。例如,我们可能从合作伙伴处获取您在其他平台的相关服务数据,以便为您提供整合的服务和用户体验。
|
||||
|
||||
#### (4)敏感个人信息
|
||||
|
||||
敏感个人信息是指一旦泄露、非法提供或滥用,可能对您的人身安全、财产安全、个人名誉、身体健康或其他方面造成损害的个人信息。常见的敏感信息包括身份证号、银行账户信息、健康数据、位置信息等。我们在收集和使用这些敏感信息时会特别谨慎,并采取严格的保护措施,确保此类信息的安全性。例如,我们在收集您的身份信息时,会使用安全的加密技术,防止未经授权的访问和使用。
|
||||
|
||||
## 二、我们如何使用 Cookie 和类似技术
|
||||
|
||||
为了能够更好地为您提供个性化的服务体验,我们和第三方合作伙伴会通过 Cookies、标签(Pixel Tags)等技术收集和存储您的信息,具体用途包括:
|
||||
|
||||
- **身份验证**:帮助您在使用我们的服务时保持登录状态,以便您无需重复输入账户和密码,从而节省您的时间并提升您的使用体验。
|
||||
BIN
public/logo.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
public/qrcode.jpg
Normal file
|
After Width: | Height: | Size: 142 KiB |
1
public/site.webmanifest
Normal file
@@ -0,0 +1 @@
|
||||
{"background_color":"#ffffff","display":"standalone","icons":[{"sizes":"192x192","src":"/android-chrome-192x192.png","type":"image/png"},{"sizes":"512x512","src":"/android-chrome-512x512.png","type":"image/png"}],"name":"","short_name":"","theme_color":"#ffffff"}
|
||||
93
public/zh_CN.js
Normal file
@@ -0,0 +1,93 @@
|
||||
/*!
|
||||
* TinyMCE
|
||||
*
|
||||
* Copyright (c) 2025 Ephox Corporation DBA Tiny Technologies, Inc.
|
||||
* Licensed under the Tiny commercial license. See https://www.tiny.cloud/legal/
|
||||
*/
|
||||
tinymce.Resource.add('tinymce.html-i18n.help-keynav.zh_CN',
|
||||
'<h1>开始键盘导航</h1>\n' +
|
||||
'\n' +
|
||||
'<dl>\n' +
|
||||
' <dt>使菜单栏处于焦点</dt>\n' +
|
||||
' <dd>Windows 或 Linux:Alt+F9</dd>\n' +
|
||||
' <dd>macOS:⌥F9</dd>\n' +
|
||||
' <dt>使工具栏处于焦点</dt>\n' +
|
||||
' <dd>Windows 或 Linux:Alt+F10</dd>\n' +
|
||||
' <dd>macOS:⌥F10</dd>\n' +
|
||||
' <dt>使页脚处于焦点</dt>\n' +
|
||||
' <dd>Windows 或 Linux:Alt+F11</dd>\n' +
|
||||
' <dd>macOS:⌥F11</dd>\n' +
|
||||
' <dt>使通知处于焦点</dt>\n' +
|
||||
' <dd>Windows 或 Linux:Alt+F12</dd>\n' +
|
||||
' <dd>macOS:⌥F12</dd>\n' +
|
||||
' <dt>使上下文工具栏处于焦点</dt>\n' +
|
||||
' <dd>Windows、Linux 或 macOS:Ctrl+F9</dd>\n' +
|
||||
'</dl>\n' +
|
||||
'\n' +
|
||||
'<p>导航将在第一个 UI 项上开始,其中突出显示该项,或者对于页脚元素路径中的第一项,将为其添加下划线。</p>\n' +
|
||||
'\n' +
|
||||
'<h1>在 UI 部分之间导航</h1>\n' +
|
||||
'\n' +
|
||||
'<p>要从一个 UI 部分移至下一个,请按 <strong>Tab</strong>。</p>\n' +
|
||||
'\n' +
|
||||
'<p>要从一个 UI 部分移至上一个,请按 <strong>Shift+Tab</strong>。</p>\n' +
|
||||
'\n' +
|
||||
'<p>这些 UI 部分的 <strong>Tab</strong> 顺序为:</p>\n' +
|
||||
'\n' +
|
||||
'<ol>\n' +
|
||||
' <li>菜单栏</li>\n' +
|
||||
' <li>每个工具栏组</li>\n' +
|
||||
' <li>边栏</li>\n' +
|
||||
' <li>页脚中的元素路径</li>\n' +
|
||||
' <li>页脚中的字数切换按钮</li>\n' +
|
||||
' <li>页脚中的品牌链接</li>\n' +
|
||||
' <li>页脚中的编辑器调整大小图柄</li>\n' +
|
||||
'</ol>\n' +
|
||||
'\n' +
|
||||
'<p>如果不存在某个 UI 部分,则跳过它。</p>\n' +
|
||||
'\n' +
|
||||
'<p>如果键盘导航焦点在页脚,并且没有可见的边栏,则按 <strong>Shift+Tab</strong> 将焦点移至第一个工具栏组而非最后一个。</p>\n' +
|
||||
'\n' +
|
||||
'<h1>在 UI 部分内导航</h1>\n' +
|
||||
'\n' +
|
||||
'<p>要从一个 UI 元素移至下一个,请按相应的<strong>箭头</strong>键。</p>\n' +
|
||||
'\n' +
|
||||
'<p><strong>左</strong>和<strong>右</strong>箭头键</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>在菜单栏中的菜单之间移动。</li>\n' +
|
||||
' <li>打开菜单中的子菜单。</li>\n' +
|
||||
' <li>在工具栏组中的按钮之间移动。</li>\n' +
|
||||
' <li>在页脚的元素路径中的各项之间移动。</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p><strong>下</strong>和<strong>上</strong>箭头键</p>\n' +
|
||||
'\n' +
|
||||
'<ul>\n' +
|
||||
' <li>在菜单中的菜单项之间移动。</li>\n' +
|
||||
' <li>在工具栏弹出菜单中的各项之间移动。</li>\n' +
|
||||
'</ul>\n' +
|
||||
'\n' +
|
||||
'<p><strong>箭头</strong>键在具有焦点的 UI 部分内循环。</p>\n' +
|
||||
'\n' +
|
||||
'<p>要关闭打开的菜单、打开的子菜单或打开的弹出菜单,请按 <strong>Esc</strong> 键。</p>\n' +
|
||||
'\n' +
|
||||
'<p>如果当前的焦点在特定 UI 部分的“顶部”,则按 <strong>Esc</strong> 键还将完全退出键盘导航。</p>\n' +
|
||||
'\n' +
|
||||
'<h1>执行菜单项或工具栏按钮</h1>\n' +
|
||||
'\n' +
|
||||
'<p>当突出显示所需的菜单项或工具栏按钮时,按 <strong>Return</strong>、<strong>Enter</strong> 或<strong>空格</strong>以执行该项。</p>\n' +
|
||||
'\n' +
|
||||
'<h1>在非标签页式对话框中导航</h1>\n' +
|
||||
'\n' +
|
||||
'<p>在非标签页式对话框中,当对话框打开时,第一个交互组件获得焦点。</p>\n' +
|
||||
'\n' +
|
||||
'<p>通过按 <strong>Tab</strong> 或 <strong>Shift+Tab</strong>,在交互对话框组件之间导航。</p>\n' +
|
||||
'\n' +
|
||||
'<h1>在标签页式对话框中导航</h1>\n' +
|
||||
'\n' +
|
||||
'<p>在标签页式对话框中,当对话框打开时,标签页菜单中的第一个按钮获得焦点。</p>\n' +
|
||||
'\n' +
|
||||
'<p>通过按 <strong>Tab</strong> 或 <strong>Shift+Tab</strong>,在此对话框的交互组件之间导航。</p>\n' +
|
||||
'\n' +
|
||||
'<p>通过将焦点移至另一对话框标签页的菜单,然后按相应的<strong>箭头</strong>键以在可用的标签页间循环,从而切换到该对话框标签页。</p>\n');
|
||||