🔐 通用 - 签名接口
ThinkAdmin 提供完善的签名接口机制,支持安全的 API 接口调用和数据验证。
🚀 主要功能
- 安全签名: 基于 MD5 的接口数据签名机制
- 时间验证: 支持时间戳验证,防止重放攻击
- 随机字符串: 使用随机字符串增强安全性
- 调试模式: 支持调试模式,方便开发测试
- 分页支持: 支持列表分页功能
- 文件传输: 支持 base64 文件传输
📋 请求字段说明
必需字段
- appid: 接口请求认证账号,需要保持两端一致
- time: 接口请求时间(时差不能超过30秒),使用时间戳,单位为秒
- nostr: 接口请求随机字符串,可以使用随机函数生成
- data: 接口请求数据(数据需转为JSON格式),需以原格式传输
- sign: 接口数据签名,按特定规则生成
签名规则
- 字段顺序: 依次为
appid
、data
、time
、appkey
、nostr
- 连接方式: 五个字段以
#
连接 - 加密方式: 进行 MD5 编码获取签名
- 私钥保护:
appkey
为接口私钥,仅用于签名且不参与接口数据请求
⚙️ 特殊说明
文件传输
- 传输方式: 使用 base64 传输文件
- 数据格式: 需要去掉数据头部
- 大文件处理: 建议直接对接云存储
- 性能考虑: 大文件传输可能影响性能
调试模式
- 调试开关: 当接口的
debug
为true
时 - 简化请求: 不需要传签名内容
- 普通表单: 只需要以普通表单传原接口的
data
内容 - 格式要求: 非JSON格式,方便调试编写文档
- 开发便利: 简化开发阶段的接口调试
列表分页
- 分页参数: 使用
get
方式传page
参数 - 签名排除:
page
不需要参与签名 - 数据格式: 传在
data
的 JSON 中会无效 - 参数不符: 因为数据格式与 Page 参数不符
- 性能优化: 分页参数独立处理提升性能
安全机制
- 时间验证: 时间戳验证防止重放攻击
- 随机字符串: 使用随机字符串增强安全性
- 签名验证: 严格的签名验证机制
- 私钥保护: 私钥不参与接口数据请求
🔧 实际应用案例
前端客户端请求案例
基础请求示例
// 1. 加载 MD5 插件
require(['md5'], function(md5) {
// 2. 配置接口参数
const config = {
appid: 'your_app_id', // 接口账号
appkey: 'your_app_key', // 接口密钥
apiUrl: 'https://your-domain.com/api/data/getUserInfo' // 接口地址
};
// 3. 准备请求数据
const requestData = {
user_id: 123,
page: 1,
limit: 10
};
// 4. 生成签名
function generateSign(appid, data, appkey) {
const nostr = Math.random().toString(36).substr(2, 15);
const time = Math.ceil(Date.now() / 1000);
const json = JSON.stringify(data);
const signString = [appid, json, time, appkey, nostr].join('#');
const sign = md5(signString);
return {
appid: appid,
time: time,
nostr: nostr,
data: json,
sign: sign
};
}
// 5. 发起请求
const signData = generateSign(config.appid, requestData, config.appkey);
$.ajax({
url: config.apiUrl,
type: 'POST',
data: signData,
dataType: 'json',
success: function(response) {
if (response.code === 1) {
console.log('请求成功:', response.data);
// 处理成功响应
} else {
console.error('请求失败:', response.info);
// 处理错误响应
}
},
error: function(xhr, status, error) {
console.error('请求异常:', error);
}
});
});
封装成通用方法
// 封装签名接口请求方法
class ApiClient {
constructor(appid, appkey, baseUrl) {
this.appid = appid;
this.appkey = appkey;
this.baseUrl = baseUrl;
}
// 生成签名
generateSign(data) {
const nostr = Math.random().toString(36).substr(2, 15);
const time = Math.ceil(Date.now() / 1000);
const json = JSON.stringify(data);
const signString = [this.appid, json, time, this.appkey, nostr].join('#');
const sign = md5(signString);
return {
appid: this.appid,
time: time,
nostr: nostr,
data: json,
sign: sign
};
}
// 发起请求
request(endpoint, data = {}) {
return new Promise((resolve, reject) => {
const signData = this.generateSign(data);
$.ajax({
url: this.baseUrl + endpoint,
type: 'POST',
data: signData,
dataType: 'json',
success: function(response) {
if (response.code === 1) {
resolve(response.data);
} else {
reject(new Error(response.info));
}
},
error: function(xhr, status, error) {
reject(new Error(error));
}
});
});
}
}
// 使用示例
const api = new ApiClient('your_app_id', 'your_app_key', 'https://your-domain.com/api/');
// 获取用户列表
api.request('/data/getUserList', { page: 1, limit: 10 })
.then(data => {
console.log('用户列表:', data);
})
.catch(error => {
console.error('请求失败:', error.message);
});
// 获取用户详情
api.request('/data/getUserInfo', { user_id: 123 })
.then(data => {
console.log('用户详情:', data);
})
.catch(error => {
console.error('请求失败:', error.message);
});
调试模式示例
// 调试模式下的简化请求
function debugRequest(endpoint, data) {
// 调试模式下直接发送数据,不需要签名
$.ajax({
url: 'https://your-domain.com/api' + endpoint,
type: 'POST',
data: data, // 直接发送原始数据
dataType: 'json',
success: function(response) {
console.log('调试请求成功:', response);
},
error: function(xhr, status, error) {
console.error('调试请求失败:', error);
}
});
}
// 使用调试模式
debugRequest('/data/getUserList', {
page: 1,
limit: 10,
debug: true // 开启调试模式
});
后端客户端请求案例:
use think\admin\service\InterfaceService;
// 实例接口服务接口
$service = InterfaceService::instance();
$service->getway(GETWAY); // 设置接口请求网关
$service->setAuth(APPID, APPKEY); // 设置接口认证账号
$result = $service->doRequest('API_URL', DATA); // 发起接口请求
print_r($result);
服务端接口控制器案例:
namespace app\data\controller\api;
use think\admin\Controller;
use think\admin\service\InterfaceService;
/**
* 接口授权基础控制器
* Class Auth
* @package app\data\controller\api
*/
class Auth extends Controller
{
/**
* 当前请求数据
* @var array
*/
protected $data;
/**
* 接口ID
* @var string
*/
protected $appid = '接口账号';
/**
* 接口密钥
* @var string
*/
protected $appkey = '接口密钥';
/**
* 接口服务对象
* @var InterfaceService
*/
protected $interface;
/**
* 接口授权初始化
*/
protected function initialize()
{
// 实例接口对象
$this->interface = InterfaceService::instance();
// 获取接口数据
$this->interface->setAuth($this->appid, $this->appkey);
$this->data = $this->interface->getData();
}
/**
* 响应错误消息
* @param mixed $info
* @param mixed $data
* @param mixed $code
*/
public function error($info, $data = '{-null-}', $code = 0): void
{
$this->interface->error($info, $data, $code);
}
/**
* 响应成功消息
* @param mixed $info
* @param mixed $data
* @param mixed $code
*/
public function success($info, $data = '{-null-}', $code = 1): void
{
$this->interface->success($info, $data, $code);
}
/**
* 响应高级数据
* @param array $data
*/
protected function response(array $data = []): void
{
$this->interface->baseSuccess('接口调用成功!', $data);
}
}
如果需要使用数据库来管理接口账号,需要调整下初始化方法
// 接口实例初始化
$this->interface = InterfaceService::instance();
// 从数据库中账号检查 ( 用自己的数据表 )
$map = $this->_vali(['appid.require' => '参数APPID不能为空!']);
$this->user = $this->app->db->name('AppUser')->where($map)->find();
if (empty($this->user)) $this->error('接口账号不存在!');
if (empty($this->user['status'])) $this->error('接口账号已被禁用!');
if (!empty($this->user['is_deleted'])) $this->error('接口账号已被移除!');
// 接口数据初始化
$this->interface->setAuth($this->user['appid'], $this->user['appkey']);
[$this->data, $this->appid] = [$this->interface->getData(), $this->interface->getAppid()];
处理业务时,可以对接口提交过来的数据进行二次验证(需要继承基础控制器)
// 接收并验证参数是否符合要求,失败返回业务错误内容
$data = $this->_vali([...], $this->data, [$this,'error']);
// 使用 $this->_query 动态传参数查询,与后台控制器操作无差别(只需要指定数据)
$query = $this->_query($this->table, $this->data);
$query->like(...)->equal(...)->in(...); // 绑定查询条件
$this->success("操作成功", $query->page(true,false));