签名步骤:byte2HexStr (HMAC_SHA256(post-content +RequestURI, key), RequestURI 以”/” 开头,不包括域名;post-content ...

文章展示了在PHP7.4中使用Sodium扩展进行密码处理的方法,包括生成和验证哈希(crypto_pwhash_str)以及实现HMACSHA256。还提供了将字节转换为十六进制字符串的辅助函数。

以下是PHP 7.4版本的实现,使用了相同的依赖库apache/commons-codec来进行十六进制编码。

<?php

use function Sodium\crypto_generichash;
use function Sodium\crypto_pwhash_str;
use function Sodium\crypto_pwhash_str_verify;

require_once __DIR__ . '/vendor/autoload.php';

function hmacsha256($plainStr, $key)
{
    $secretKey = new \SecretKeySpec(
        utf8_encode($key),
        'HmacSHA256'
    );

    $mac = hash_init('sha256', HASH_HMAC, $secretKey);
    hash_update($mac, utf8_encode($plainStr));
    $digest = hash_final($mac, true);

    return byte2HexStr($digest);
}

function byte2HexStr($array)
{
    return $array !== null ? \strval(Hex::encode($array)) : null;
}

function generate_password($password, $salt)
{
    return crypto_pwhash_str($password, \SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE, \SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE);
}

function verify_password($hash, $password)
{
    return crypto_pwhash_str_verify($hash, $password);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ nginx配置文件 user zceo; worker_processes auto; error_log /data/openresty/logs/error.log; pid /apps/openresty/run/nginx.pid; events { worker_connections 10240; } http { server_tokens off; include mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" | $http_accept | "$http_x_forwarded_for" | $host | ' '$request_time | $request_length | $upstream_addr | $upstream_status | $upstream_response_time | ' '$connection | $connection_requests '; access_log /data/openresty/logs/access.log main; sendfile on; tcp_nopush on; keepalive_timeout 65; gzip on; gzip_min_length 1k; gzip_buffers 4 16k; #gzip_http_version 1.0; gzip_comp_level 3; gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript; gzip_vary on; gzip_disable "MSIE [1-6]\."; gzip_proxied any; # 指定服务器名称hash表的框大小 server_names_hash_bucket_size 512; # header头缓冲区大小,cookie内容较大时可加大 client_header_buffer_size 3072k; large_client_header_buffers 8 3072k; #设置允许发布内容为8M client_max_body_size 500M; client_body_buffer_size 3072k; # websocket配置 map $http_upgrade $connection_upgrade { default upgrade; '' close; } #获取真实请求IP map $http_x_forwarded_for $clientRealIp { "" $remote_addr; ~^(?P<firstAddr>[0-9\.]+),?.*$ $firstAddr; } map $http_user_agent $root_path { default /data/vue/bsp-workbenches-web; # 默认是 PC "~*Mobile" /data/vue/bsp-workbenches-h5; # 如果是移动设备,设置为 h5 路径 "~*Android" /data/vue/bsp-workbenches-h5; "~*iPhone" /data/vue/bsp-workbenches-h5; "~*iPad" /data/vue/bsp-workbenches-h5; "~*Windows Phone" /data/vue/bsp-workbenches-h5; } map $http_origin $cors_origin { default ""; ~^http?://(localhost|127\.0\.0\.1)(:\d+)?$ $http_origin; ~^https?://(.*\.)?gboss\.tech(?::\d+)?$ $http_origin; ~^https?://(.*\.)?ceboss\.cn(?::\d+)?$ $http_origin; ~^https?://(.*\.)?gmarketing\.cn(?::\d+)?$ $http_origin; } include vhosts/upstream.config; include vhosts/server.config; include vhosts/localhost.config; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ vhosts/upstream.config内容 upstream tsdasdaweb { server xxx.xxx.xxx.xxx:8080; keepalive 2000; } upstream csadasdweb { server xxx.xx.xxx.xxx:80; keepalive 2000; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ vhosts/server.config内容 多个下面类似配置 server { listen 80; server_name test-resource.gmarketing.tech; access_log /data/openresty/logs/access-test-resource.log main; error_log /data/openresty/logs/error-test-resource.log error; include /apps/openresty/nginx/conf/vhosts/test-resource-location.conf; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ vhosts/localhost.config内容: server { listen 80; server_name 127.0.0.1; location /ssadas { stub_status on; } location ^~ /csasdasdweb/ { proxy_pass http://asdasdadweb; include /apps/openresty/nginx/conf/vhosts/common-proxy.conf; } } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ test-resource-location.conf内容 location ^~ /file-api/ { proxy_pass http://file-api/; include /apps/openresty/nginx/conf/vhosts/common-x-form.conf; include /apps/openresty/nginx/conf/vhosts/common-proxy.conf; } location /file { include /apps/openresty/nginx/conf/vhosts/file-from.conf; alias /data/share/files/; } location /robots.txt { alias /data/vue/robots/robots.txt; add_header Content-Type text/plain; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /apps/openresty/nginx/conf/vhosts/common-x-form.conf内容 add_header Content-Security-Policy "frame-ancestors https://*.ceboss.cn https://*.gmarketing.tech https://*.gboss.tech;"; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /apps/openresty/nginx/conf/vhosts/common-proxy.conf内容 proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_connect_timeout 6000; proxy_read_timeout 6000; proxy_send_timeout 6000; proxy_buffer_size 3072k; proxy_buffers 16 3072k; proxy_busy_buffers_size 3072k; proxy_http_version 1.1; proxy_set_header Connection "Keep-Alive"; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /apps/openresty/nginx/conf/vhosts/file-from.conf内容 # CORS 配置 if ($http_origin ~* (https?://([^/]+\.)?(gmarketing\.tech|gboss\.tech|ceboss\.cn)(:\d+)?$)) { set $cors "true"; } if ($http_origin ~* (http?://([^/]+\.)?(gmarketing\.tech|gboss\.tech|ceboss\.cn|localhost:5173|localhost|localhost:5177)(:\d+)?$)) { set $cors "true"; } # 处理 .tiff 文件的 CORS 头 location ~* \.tiff$ { if ($cors = "true") { add_header 'Access-Control-Allow-Origin' "$http_origin" always; add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; add_header 'Access-Control-Allow-Credentials' 'true' always; } try_files $uri =404; } # 核心 CORS 头 add_header 'Access-Control-Allow-Origin' "$http_origin" always; add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; add_header 'Access-Control-Allow-Credentials' 'true' always; # 预检请求处理 if ($request_method = 'OPTIONS') { add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 后端页面文件代码: public class SecureUrlGenerator { /** * ??? Nginx secure_link ????? URL. * * @param uri ????????? (??: "/images/secret/document.jpg") * @param secretKey ? Nginx ?????????? * @param durationInSeconds ???????(?) * @return ?????? URL (????) */ public static String generate(String uri, String secretKey, int durationInSeconds) { try { // 1. ??????? (Unix apoch time in seconds) long expires = System.currentTimeMillis() / 1000 + durationInSeconds; // 2. ????????? // ????? Nginx ? secure_link_md5 ?????????? // ??: "$secure_link_expires$uri secret_key" String stringToSign = expires + uri + " " + secretKey; // 3. ?? MD5 ?? MessageDigest md = MessageDigest.getInstance("MD5"); byte[] md5Bytes = md.digest(stringToSign.getBytes(StandardCharsets.UTF_8)); // 4. ? MD5 ???? URL ??? Base64 ?? // Nginx ? secure_link ????????: // - ?? '-' ?? '+' // - ?? '_' ?? '/' // - ????? padding '=' // Java 8 ? Base64.getUrlEncoder().withoutPadding() ?????????? String signature = Base64.getUrlEncoder().withoutPadding().encodeToString(md5Bytes); // 5. ????? URL // ??: /path/to/resource?md5=<signature>&expires=<timestamp> return String.format("%s?sign=%s&t=%d", uri, signature, expires); } catch (NoSuchAlgorithmException e) { // ??????,?????????????????? e.printStackTrace(); throw new RuntimeException("MD5 apoch_time is not available.", e); } } /** * ???,??????? */ public static void main(String[] args) { // --- ???? --- https://pre-file.gmarketing.tech/file/sm9pS0sF6oK/20250611/2b2e90fca62e41b4a048b424dfb75234.jpg String resourceUri = "24Gy8U0OlRbQ/20250616/04fa62de6149b4d11e79fce3ac7638b4.jpg"; String mySecretKey = "q8xyMiuJJAomkrk"; // ????????,??Nginx???? int validDuration = 43200; // ?????12?? (43200?) // --- ???? URL --- String signedUrl = generate(resourceUri, mySecretKey, validDuration); // --- ???? --- System.out.println("?? URI: " + resourceUri); System.out.println("????? URL: " + signedUrl); } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 请结合提供内容,修改nginx配置完成如下需求 测试环境Nginx 新的文件服务https://test-resource.gmarketing.tech 只允许通过含有码(sm9pS0sF6oK|24Gy8U0OlRbQ)的链接,并且需要对Referer进行验证,只允许通过含有以下Referer的链接访问【test-wb.gboss.tech test-console.gmarketing.tech test-sw.ceboss.cn】 测试环境Nginx 新的文件服务https://test-resource.gmarketing.tech 需要对sign和时间戳进行校验,加密Key是(q8xyMiuJJAomkrk 请勿泄露),加密方式见截图,加密后的链接示例(https://test-resource.gmarketing.tech/file/24Gy8U0OlRbQ/20250616/04fa62de6149b4d11e79fce3ac7638b4.jpg?sign=ei6aaK9I3H-9ph88m4mrpQ&t=1750103291)
06-20
#!/bin/bash # ============= 自动后台运行检测 ============= if [[ "$1" != "background" ]]; then echo "首次运行,将在后台继续执行..." nohup $0 background > /var/log/ai-gateway-install.log 2>&1 & echo "安装日志已记录到 /var/log/ai-gateway-install.log" echo "你可以安全地关闭 SSH 连接" exit 0 fi # ============= 配置变量 ============= DOMAIN="vps2.zxm20.top" ADMIN_KEY="admin_$(openssl rand -hex 8)" ENCRYPTION_KEY="encrypt_$(openssl rand -hex 8)" DB_PASSWORD="dbpass_$(openssl rand -hex 8)" INSTALL_DIR="/var/www/ai-gateway" # ============= 创建日志目录 ============= mkdir -p /var/log/ exec > >(tee -a /var/log/ai-gateway-install.log) 2>&1 # ============= 1. 检查是否为 root ============= if [ "$EUID" -ne 0 ]; then echo "请以 root 权限运行此脚本" exit 1 fi # ============= 2. 安装系统依赖 ============= echo "安装系统依赖..." apt update && apt upgrade -y apt install -y git python3 python3-pip python3-venv nginx postgresql postgresql-contrib redis-server certbot curl build-essential net-tools # ============= 3. 安装 Node.js ============= echo "安装 Node.js..." curl -fsSL https://deb.nodesource.com/setup_18.x | bash - apt install -y nodejs # ============= 4. 创建项目目录 ============= echo "创建项目目录..." mkdir -p $INSTALL_DIR/{backend,frontend,admin,ssl} chown -R $USER:$USER $INSTALL_DIR cd $INSTALL_DIR # ============= 5. 生成后端代码 ============= cat > backend/app.py <<'EOL' import os import logging import random import time import ssl import psycopg2 import redis import requests from flask import Flask, request, jsonify from flask_cors import CORS from functools import wraps from cryptography.fernet import Fernet from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization import base64 import json from datetime import datetime, timedelta, timezone import psutil import traceback # 日志配置 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger('AI-Gateway') app = Flask(__name__) CORS(app) # 环境变量 DB_HOST = os.getenv('DB_HOST', 'localhost') DB_PORT = os.getenv('DB_PORT', '5432') DB_NAME = os.getenv('DB_NAME', 'ai_gateway') DB_USER = os.getenv('DB_USER', 'ai_gateway') DB_PASSWORD = os.getenv('DB_PASSWORD', 'your_db_password') REDIS_URL = os.getenv('REDIS_URL', 'redis://localhost:6379/0') ADMIN_KEY = os.getenv('ADMIN_KEY', 'your_admin_key') ENCRYPTION_KEY = os.getenv('ENCRYPTION_KEY', 'your_encryption_key') DOMAIN = os.getenv('DOMAIN', 'your-domain.com') # 数据库连接池 db_pool = psycopg2.pool.ThreadedConnectionPool(1, 10, host=DB_HOST, port=DB_PORT, database=DB_NAME, user=DB_USER, password=DB_PASSWORD ) # Redis连接池 redis_client = redis.Redis.from_url(REDIS_URL) def get_db(): if 'db' not in g: g.db = db_pool.getconn() return g.db @app.teardown_appcontext def close_db(e=None): db = g.pop('db', None) if db is not None: db_pool.putconn(db) # 加密工具 def get_cipher_suite(): salt = b'salt_' kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000, backend=default_backend()) key = base64.urlsafe_b64encode(kdf.derive(ENCRYPTION_KEY.encode())) return Fernet(key) def encrypt_data(data): return get_cipher_suite().encrypt(data.encode()).decode() def decrypt_data(encrypted_data): return get_cipher_suite().decrypt(encrypted_data.encode()).decode() # 管理员认证装饰器 def admin_required(f): @wraps(f) def decorated_function(*args, **kwargs): auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return jsonify({"error": "未提供认证信息"}), 401 token = auth_header.split(' ')[1] if token != ADMIN_KEY: return jsonify({"error": "无效的管理员密钥"}), 403 return f(*args, **kwargs) return decorated_function # 获取 API 密钥 def get_api_key(service_name): cache_key = f"api_key:{service_name}" cached_key = redis_client.get(cache_key) if cached_key: return cached_key.decode() db = get_db() cur = db.cursor() cur.execute("SELECT id, api_key FROM api_keys WHERE service_name = %s AND is_active = TRUE", (service_name,)) keys = cur.fetchall() cur.close() if not keys: raise Exception(f"没有可用的{service_name} API密钥") key_id, api_key = random.choice(keys) try: decrypted_key = decrypt_data(api_key) except: decrypted_key = api_key redis_client.setex(cache_key, 3600, decrypted_key) return decrypted_key # 调用AI服务 def call_ai_service(service_name, query, history=None): api_key = get_api_key(service_name) if service_name == 'openai': return call_openai(api_key, query, history) elif service_name == 'deepseek': return call_deepseek(api_key, query, history) elif service_name == 'doubao': return call_doubao(api_key, query, history) elif service_name == 'claude': return call_claude(api_key, query, history) elif service_name == 'llama': return call_llama(api_key, query, history) else: raise Exception(f"支持的服务: {service_name}") # 具体服务调用实现... # (此处省略具体服务实现,保持完整脚本结构) # 用户查询接口 @app.route('/api/query', methods=['POST']) def handle_query(): data = request.json service_name = data.get('service') query = data.get('query') history = data.get('history', []) if not service_name or not query: return jsonify({"error": "无效的请求参数"}), 400 start_time = time.time() client_ip = request.remote_addr status = 'success' error_message = None try: response = call_ai_service(service_name, query, history) elapsed = time.time() - start_time log_usage(service_name, query, response, elapsed, client_ip, status, error_message) return jsonify({ "success": True, "service": service_name, "response": response, "time": elapsed }) except Exception as e: status = 'error' error_message = str(e) elapsed = time.time() - start_time logger.error(f"AI调用失败: {str(e)}\n{traceback.format_exc()}") log_usage(service_name, query, None, elapsed, client_ip, status, error_message) return jsonify({"error": str(e)}), 500 # 其他路由... # (省略其余路由) if __name__ == '__main__': try: ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ssl_context.load_cert_chain('ssl/cert.pem', 'ssl/key.pem') app.run(host='0.0.0.0', port=5000, ssl_context=ssl_context, threaded=True, debug=False) except Exception as e: logger.error(f"启动失败: {str(e)}") exit(1) EOL # ============= 6. 创建数据库初始化脚本 ============= cat > backend/sql/init.sql <<'EOL' CREATE TABLE services ( id SERIAL PRIMARY KEY, name VARCHAR(50) NOT NULL UNIQUE, display_name VARCHAR(100) NOT NULL, description TEXT, endpoint VARCHAR(255), model VARCHAR(100), is_available BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE api_keys ( id SERIAL PRIMARY KEY, service_name VARCHAR(50) NOT NULL REFERENCES services(name), api_key TEXT NOT NULL, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 初始化服务 INSERT INTO services (name, display_name, description, endpoint, model) VALUES ('openai', 'OpenAI', 'OpenAI的GPT系列模型', 'https://api.openai.com/v1/chat/completions', 'gpt-3.5-turbo'), ('deepseek', 'DeepSeek', '深度求索的大模型服务', 'https://api.deepseek.com/v1/chat/completions', 'deepseek-chat'), ('doubao', '豆包', '字节跳动的豆包大模型', 'https://dubao.baidu.com/api/v1/chat', 'Doubao-Pro'), ('claude', 'Claude', 'Anthropic的安全对齐模型', 'https://api.anthropic.com/v1/messages', 'claude-3-opus'), ('llama', 'Llama', 'Meta的开源大语言模型', 'https://api.replicate.com/v1/predictions', 'llama-2-70b-chat'); EOL # ============= 7. 创建前端页面 ============= cat > frontend/index.html <<'EOL' <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>AI服务聚合平台</title> </head> <body> <h1>欢迎使用 AI 服务聚合平台</h1> <p>访问地址: https://vps2.zxm20.top</p> </body> </html> EOL # ============= 8. 申请SSL证书 ============= echo "申请SSL证书..." certbot certonly --standalone -d $DOMAIN --non-interactive --agree-tos -m admin@$DOMAIN --no-eff-email || true cp /etc/letsencrypt/live/$DOMAIN/fullchain.pem ssl/cert.pem cp /etc/letsencrypt/live/$DOMAIN/privkey.pem ssl/key.pem # ============= 9. 设置 PostgreSQL ============= echo "设置 PostgreSQL..." sudo -u postgres psql -c "CREATE USER ai_gateway WITH PASSWORD '$DB_PASSWORD';" sudo -u postgres psql -c "CREATE DATABASE ai_gateway;" sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE ai_gateway TO ai_gateway;" # ============= 10. 初始化数据库 ============= sudo -u postgres psql -d ai_gateway -f backend/sql/init.sql # ============= 11. 设置后端环境变量 ============= cd backend python3 -m venv venv source venv/bin/activate pip install cryptography flask flask-cors requests psycopg2-binary redis gunicorn cat > .env <<EOF ADMIN_KEY=$ADMIN_KEY ENCRYPTION_KEY=$ENCRYPTION_KEY DB_HOST=localhost DB_PORT=5432 DB_NAME=ai_gateway DB_USER=ai_gateway DB_PASSWORD=$DB_PASSWORD REDIS_URL=redis://localhost:6379/0 DOMAIN=$DOMAIN EOF # ============= 12. 创建 Gunicorn 服务 ============= cat > /etc/systemd/system/ai-gateway.service <<'EOF' [Unit] Description=AI Gateway Backend After=network.target [Service] User=$USER Group=$USER WorkingDirectory=/var/www/ai-gateway/backend Environment="PATH=/var/www/ai-gateway/backend/venv/bin" ExecStart=/var/www/ai-gateway/backend/venv/bin/gunicorn -w 4 -b 127.0.0.1:5000 app:app Restart=always [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable ai-gateway systemctl start ai-gateway # ============= 13. 配置 Nginx ============= cat > /etc/nginx/sites-available/ai-gateway <<'EOL' server { listen 80; server_name vps2.zxm20.top; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name vps2.zxm20.top; ssl_certificate /var/www/ai-gateway/ssl/cert.pem; ssl_certificate_key /var/www/ai-gateway/ssl/key.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers EECDH+AESGCM:EDH+AESGCM; ssl_prefer_server_ciphers on; location / { root /var/www/html; index index.html; try_files $uri $uri/ =404; } location /api { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } EOL ln -s /etc/nginx/sites-available/ai-gateway /etc/nginx/sites-enabled/ rm /etc/nginx/sites-enabled/default nginx -t && systemctl restart nginx # ============= 14. 配置防火墙 ============= ufw allow OpenSSH ufw allow 'Nginx Full' ufw --force enable # ============= 15. 输出部署信息 ============= echo "✅ 部署完成!" echo "管理员密钥: $ADMIN_KEY" echo "数据库密码: $DB_PASSWORD" echo "访问地址: https://vps2.zxm20.top" echo "日志文件: /var/log/ai-gateway-install.log" 改成OpenRouter的免费ai,记住要真的调用
07-31
如何实现 post请求通过params传递参数时参与签名。这是请求方法:export function getTask(data) { return request.post('/earnify-customer/customer/userTask/receive', data) }这是调用:const res = await getTask({ taskId:1 })这是request.ts文件:import type { AxiosInstance } from 'axios' import axios from 'axios' import Cookies from 'js-cookie' import { useUserStore } from '@/store/hooks/userStore' const userStore = useUserStore() import { useOutsideRouter } from '@/store/hooks/useOutsideRouter' import { signRequest } from './sign' export interface RespData<T> { success?: boolean errorCode?: number error?: number | string | null msg?: string data?: T [key: string]: any } // redirect error function errorRedirect(url: string) { const { router } = useOutsideRouter() router.push(url) } // code Message const codeMessage: { [key: number]: string } = { 200: '服务器成功返回请求的数据。', 201: '新建或修改数据成功。', 202: '一个请求已经进入后台排队(异步任务)。', 204: '删除数据成功。', 206: '进行范围请求成功。', 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。', 401: '用户没有权限(令牌、用户名、密码错误)。', 403: '用户得到授权,但是访问是被禁止的。', 404: '发出的请求针对的是存在的记录,服务器没有进行操作。', 405: '请求允许。', 406: '请求的格式可得。', 410: '请求的资源被永久删除,且会再得到的。', 422: '当创建一个对象时,发生一个验证错误。', 500: '服务器发生错误,请检查服务器。', 502: '网关错误。', 503: '服务可用,服务器暂时过载或维护。', 504: '网关超时。' } // 创建axios实例 const service: AxiosInstance = axios.create({ // api 的 base_url baseURL: 'https://tapi.shenyantuling.com:10000', // 请求超时时间 timeout: 6000000 }) // request拦截器 service.interceptors.request.use( request => { const token: string | undefined = userStore.userInfo.token // Conversion of hump nomenclature /** * 让每个请求携带自定义 token * 请根据实际情况自行修改 */ if (request.url === '/login') { return request } request.headers!.token = token as string // request.headers!.extendData = // TODO: 临时使用 // 确保 FormData 请求的 Content-Type 在签名计算之前设置 if (request.data instanceof FormData) { request.headers['Content-Type'] = 'multipart/form-data' } signRequest(request) request.headers['ngrok-skip-browser-warning'] = '69420' return request }, error => { return Promise.reject(error) } ) // respone拦截器 service.interceptors.response.use( response => { /** * response data * { * data: {}, * msg: "", * error: 0 0 success | 1 error | 5000 failed | HTTP code * } */ const data: any = response.data const msg: string = data.msg || '' if (msg.indexOf('user not log in') !== -1 && data.error === -1) { // TODO 写死的 之后要根据语言跳转 errorRedirect('login') return } if (response.config.autoDownLoadFile === undefined || response.config.autoDownLoadFile) { Promise.resolve().then(() => { useResHeadersAPI(response.headers, data) }) } if ( response.request.responseType === 'blob' && /json$/gi.test(response.headers['content-type']) ) { return new Promise(resolve => { const reader = new FileReader() reader.readAsText(<Blob>response.data) reader.onload = () => { if (!reader.result || typeof reader.result !== 'string') return resolve(response.data) response.data = JSON.parse(reader.result) resolve(response.data) } }) } else if (data instanceof Blob) { return { data, msg: '', error: 0 } } if (data.msg === null) { data.msg = 'Unknown error' } return data }, error => { /** * 某些特定的接口 404 500 需要跳转 * 在需要重定向的接口中传入 redirect字段 值为要跳转的路由 * redirect之后 调用接口的地方会继续执行 * 因为此时 response error * 所以需要前端返回一个前端构造好的数据结构 避免前端业务部分逻辑出错 * 重定向的接口则需要传 */ if (error.config?.redirect) { errorRedirect(error.config.redirect) } if (error.response) { return { data: {}, error: error.response.status, msg: codeMessage[error.response.status] || error.response.data.message } } else { // 某些特定的接口 failed 需要跳转 return { data: {}, error: 5000, msg: '服务请求可用,请重试或检查您的网络。' } } } ) export function sleep(time = 0) { return new Promise((resolve) => { setTimeout(() => { resolve({}) }, time) }) } function extractFileNameFromContentDispositionHeader(value: string) { const patterns = [ /filename\*=[^']+'\w*'"([^"]+)";?/i, /filename\*=[^']+'\w*'([^;]+);?/i, /filename="([^;]*);?"/i, /filename=([^;]*);?/i ] let responseFilename: any = null patterns.some(regex => { responseFilename = regex.exec(value) return responseFilename !== null }) if (responseFilename !== null && responseFilename.length > 1) { try { return decodeURIComponent(responseFilename[1]) } catch (e) { console.error(e) } } return null } export function downloadFile(boldData: BlobPart, filename = 'shinewing', type: any) { const blob = boldData instanceof Blob ? boldData : new Blob([boldData], { type }) const url = window.URL.createObjectURL(blob) const link = document.createElement('a') link.style.display = 'none' link.href = url link.download = filename document.body.appendChild(link) link.click() document.body.removeChild(link) } export function useResHeadersAPI(headers: any, resData: any) { const disposition = headers['content-disposition'] if (disposition) { let filename: string | null = '' filename = extractFileNameFromContentDispositionHeader(disposition) if (filename) { downloadFile(resData, filename, headers['content-type']) } } } const requestSuite: IRequestSuite = { get(uri, params, config) { return service.get(uri, { params, ...config }) }, post(uri, data, config) { if (config && config.params) { return service.post(uri, data, config) } return service.post(uri, data, config) }, put(uri, data, config) { return service.put(uri, data, config) }, patch(uri, data, config) { return service.patch(uri, data, config) }, delete(uri, config) { return service.delete(uri, config) } } export default requestSuite 这是sign.ts文件:import CryptoJS from 'crypto-js' // import { localStg } from '@/utils/storage'; // import { useUserStore } from '@/store/hooks/userStore' // const userStore = useUserStore() export function signRequest(config: any) { // const path = config.url; const timestamps = Date.now().toString() const key = 'shDOUArrDhpeAMw9FGY79Zmy3MLWwNWy' // 添加 timestamps 到 headers config.headers.timestamps = timestamps // const test = loadParam(path, config.data); const test = loadParam(config) // const token = userStore.userInfo.token // 添加token // config.headers.token = token || '' config.headers.os = 'web' config.headers.businessType = 'earnify_customer' // AES 加密 timestamps const aesTimestamp = CryptoJS.AES.encrypt(timestamps, CryptoJS.enc.Utf8.parse('4d5bc50346c22dde12be2c3b1b89ada6'), { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.ZeroPadding }) config.headers.envTimestamps = aesTimestamp.toString() // 计算签名 config.headers.sign = CryptoJS.MD5(test + timestamps + key) .toString(CryptoJS.enc.Hex) .toLowerCase() console.log(test) console.log('生成的签名:', config.headers.sign) return config } function loadParam(config: any): string { let str = '' if (config.headers['Content-Type'] !== 'multipart/form-data') { if (config.data || config.params) { if (config.method === 'get') { if (config.params) { const params = config.params const keyList = Object.keys(config.params) // 解析 GET 请求的查询参数 for (const key of keyList) { str += keyList[keyList.length - 1] !== key ? `${ key }=${ encodeURI(<string>params[key]) }&` : `${ key }=${ encodeURI(<string>params[key]) }` } console.log( str, 'gett') } } else if(config.method === 'post') { if(config.data) { const data = config.data const keyList = Object.keys(config.data) // 解析 GET 请求的查询参数 for (const key of keyList) { str += keyList[keyList.length - 1] !== key ? `${ key }=${ encodeURI(<string>data[key]) }&` : `${ key }=${ encodeURI(<string>data[key]) }` } console.log( str, 'post') } } else if(config.data) { str = JSON.stringify(config.data) } } } else { const url = config.url if (url.includes('?')) { str += url.substring(url.indexOf('?') + 1, url.length) } } return str } 指出问题 并解决
08-01
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值