Php+JavaScript-ICP备案查询API
·
重写了下,理清了思路。顺便也是因为其验证换了,所以原来的处理就失效了
一、实现思路
工信部 ICP 备案查询的核心流程分为两步:
- 获取验证图片:向工信部接口请求滑块验证所需的大图 / 小图资源,同时获取鉴权所需的 Token、UUID 等参数;
- 滑块验证 + 查询备案:用户完成滑块验证后,将验证坐标提交后端,后端携带验证信息调用备案查询接口,最终返回备案数据。
系统整体架构分为:
- 前端:负责用户输入、滑块验证交互、请求发送与结果展示(HTML+JavaScript);
- 后端:负责对接工信部接口、处理鉴权逻辑、封装查询方法(PHP)。
非核心的页面样式、布局(如index.html中的 UI 排版)仅作为 Demo 验证,本文不做过多赘述,重点讲解核心业务逻辑。
二、后端核心逻辑(PHP)
后端核心代码集中在icpbeian.php(核心类)和api.php(接口层),前者封装了对接工信部接口的所有逻辑,后者作为前端与核心类的桥梁。
2.1 核心类Webquery
该类封装了 Cookie 获取、Token 生成、验证图片获取、滑块验证、备案查询等核心方法,是整个系统的核心。
2.1.1 初始化与基础配置
class Webquery
{
// 备案查询的参数模板(不同serviceType对应不同查询类型)
private $typj = [
0 => '{"pageNum": "", "pageSize": "", "unitName": "", "serviceType": 1}',
1 => '{"pageNum": "", "pageSize": "", "unitName": "", "serviceType": 6}',
2 => '{"pageNum": "", "pageSize": "", "unitName": "", "serviceType": 7}',
3 => '{"pageNum": "", "pageSize": "", "unitName": "", "serviceType": 8}'
];
// 基础请求头(模拟浏览器)
private $cookieHeaders = [
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...'
];
// 工信部核心接口地址
private $home = 'https://beian.miit.gov.cn/';
private $url = 'https://hlwicpfwc.miit.gov.cn/icpproject_query/api/auth';
private $getCheckImage = 'https://hlwicpfwc.miit.gov.cn/icpproject_query/api/image/getCheckImagePoint';
private $checkImage = 'https://hlwicpfwc.miit.gov.cn/icpproject_query/api/image/checkImage';
private $queryByCondition = 'https://hlwicpfwc.miit.gov.cn/icpproject_query/api/icpAbbreviateInfo/queryByCondition';
// 构造方法初始化curl(忽略SSL验证,适配工信部接口)
public function __construct()
{
$this->session = curl_init();
curl_setopt($this->session, CURLOPT_RETURNTRANSFER, true);
curl_setopt($this->session, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($this->session, CURLOPT_SSL_VERIFYHOST, false);
}
}
2.1.2 鉴权前置:Cookie 与 Token 获取
工信部接口需要先获取__jsluid_s Cookie 和 Token 才能正常请求,这是对接的第一个关键节点:
// 获取__jsluid_s Cookie
private function getCookie()
{
curl_setopt($this->session, CURLOPT_URL, $this->home);
curl_setopt($this->session, CURLOPT_HTTPHEADER, $this->formatHeaders($this->cookieHeaders));
curl_setopt($this->session, CURLOPT_HEADER, true);
curl_setopt($this->session, CURLOPT_NOBODY, true);
$response = curl_exec($this->session);
preg_match('/__jsluid_s=([0-9a-z]{32})/', $response, $matches);
return $matches[1] ?? '';
}
// 获取Token(基于时间戳+md5生成authKey)
private function getToken()
{
$timeStamp = round(microtime(true) * 1000);
$authSecret = 'testtest' . $timeStamp;
$authKey = md5($authSecret);
$cookie = $this->getCookie();
$this->baseHeader['Cookie'] = '__jsluid_s=' . $cookie;
curl_setopt($this->session, CURLOPT_URL, $this->url);
curl_setopt($this->session, CURLOPT_POST, true);
curl_setopt($this->session, CURLOPT_POSTFIELDS, ['authKey' => $authKey, 'timeStamp' => $timeStamp]);
$response = curl_exec($this->session);
$data = json_decode($response, true);
return $data['params']['bussiness'] ?? '';
}
2.1.3 获取滑块验证图片
Token 获取成功后,请求工信部接口获取滑块验证的大图 / 小图,并返回前端所需的鉴权参数:
public function checkImg()
{
$this->token = $this->getToken();
try {
// 生成客户端唯一标识clientUid
$data = $this->getClientUid();
$clientUid = json_decode($data, true)['clientUid'];
$headers = $this->baseHeader;
$headers['Content-Length'] = strlen($data);
$headers['Token'] = $this->token;
$headers['Content-Type'] = 'application/json';
// 请求验证图片接口
curl_setopt($this->session, CURLOPT_URL, $this->getCheckImage);
curl_setopt($this->session, CURLOPT_POST, true);
curl_setopt($this->session, CURLOPT_POSTFIELDS, $data);
curl_setopt($this->session, CURLOPT_HTTPHEADER, $this->formatHeaders($headers));
$response = curl_exec($this->session);
$res = json_decode($response, true);
// 提取核心参数(返回给前端)
return [
"big_image" => $res['params']['bigImage'] ?? '',
"small_image" => $res['params']['smallImage'] ?? '',
"p_uuid" => $res['params']['uuid'] ?? '',
"token" => $this->token,
"secretKey" => $res['params']['secretKey'] ?? '',
"clientUid" => $clientUid,
"height" => $res['params']['height'] ?? 0,
'cookie' => $this->baseHeader2['Cookie'] ?? '',
'success' => true
];
} catch (\Exception $e) {
return false;
}
}
2.1.4 滑块验证 + 备案查询
用户完成滑块拖动后,前端提交验证坐标,后端携带坐标调用验证接口,验证通过后查询备案信息:
public function icp_auth($offsetX, $p_uuid, $token, $secretKey, $clientUid, $domain, $cookie_c)
{
$client = new Client([
'verify' => false,
'headers' => [
'User-Agent' => 'Mozilla/5.0 ...',
'Cookie' => $cookie_c,
'Token' => $token
]
]);
try {
// 1. 提交滑块验证坐标
$verifyData = [
"key" => $p_uuid,
"value" => (string)intval($offsetX)
];
$verifyResponse = $client->post($this->checkImage, ['json' => $verifyData]);
$resData = json_decode($verifyResponse->getBody(), true);
if (!$resData['success']) {
return ['code' => 201, 'error' => '验证码验证失败'];
}
// 2. 验证通过后,调用备案查询接口
$sign = $resData["params"] ?? '';
$info = json_decode($this->typj[0], true);
$info['unitName'] = $domain;
$infoJson = json_encode($info, JSON_UNESCAPED_UNICODE);
$queryResponse = $client->post($this->queryByCondition, [
'body' => $infoJson,
'headers' => [
'Content-Length' => strlen($infoJson),
'Sign' => $sign,
'Token' => $token,
'Uuid' => $p_uuid
]
]);
return json_decode($queryResponse->getBody(), true);
} catch (GuzzleException $e) {
return ["code" => 122, "msg" => "查询失败: " . $e->getMessage()];
}
}
2.2 接口层
作为前端与核心类的桥梁,接收前端请求并分发处理(分 step1/step2):
require_once '../vendor/autoload.php';
require('icpbeian.php');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$step = $_POST['step'] ?? 1;
$domain = $_POST['domain'] ?? '';
$myicp = new Webquery();
// step1:获取验证图片
if ($step == 1) {
if (empty($domain)) {
echo json_encode(["code" => 101, "msg" => "参数错误,请指定domain参数"], JSON_UNESCAPED_UNICODE);
exit;
}
$result = $myicp->icp_search($domain); // 内部调用checkImg()
echo json_encode($result, JSON_UNESCAPED_UNICODE);
}
// step2:滑块验证+查询备案
elseif ($step == 2) {
$sliderOffset = $_POST['sliderOffset'] ?? null;
$p_uuid = $_POST['p_uuid'] ?? null;
$token = $_POST['token'] ?? null;
// 校验参数后调用icp_auth()
$result = $myicp->icp_auth($sliderOffset, $p_uuid, $token, $secretKey, $clientUid, $domain, $cookie_c);
echo json_encode($result, JSON_UNESCAPED_UNICODE);
}
}
三、前端核心交互(JavaScript)
前端核心逻辑在icpget.js,重点是滑块验证的实现与请求流程的封装。
3.1 初始化查询流程
用户输入域名后,发起第一步请求,获取验证图片并展示滑块验证界面:
function queryDomain() {
const domain = document.getElementById('domain').value.trim();
if (!domain) {
showError('请输入要查询的域名');
return;
}
// 展示加载状态,隐藏其他状态
document.getElementById('resultContainer').classList.remove('hidden');
document.getElementById('loadingState').classList.remove('hidden');
// 调用step1接口,获取验证图片
fetch('yhicp/api.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `domain=${encodeURIComponent(domain)}&step=1`
})
.then(response => response.json())
.then(data => {
if (data.success && data.big_image && data.small_image) {
showImages(data); // 初始化验证图片和滑块
} else {
showError(data.msg || '未查询到该域名的备案信息');
}
})
.catch(error => {
showError('查询失败,请稍后再试');
});
}
3.2 滑块验证实现
这是前端最核心的交互逻辑,实现滑块拖动、坐标计算、验证提交:
function initSlider() {
var slideBtn = document.getElementById('slideBtn');
var slideTrack = document.getElementById('slideTrack');
var smallImage = document.getElementById('smallImage');
var isDragging = false;
var startClientX = 0;
var startLeft = 0;
var currentLeft = 0;
// 计算最大可拖动距离(大图宽度-小图宽度)
var maxOffset = bigImage.naturalWidth - smallImage.naturalWidth;
var trackMaxOffset = slideTrack.offsetWidth - slideBtn.offsetWidth;
// 鼠标按下/触摸开始事件
slideBtn.addEventListener('mousedown', onStart);
slideBtn.addEventListener('touchstart', onStart, { passive: false });
// 鼠标移动/触摸移动事件
document.addEventListener('mousemove', onMove);
document.addEventListener('touchmove', onMove, { passive: false });
// 鼠标松开/触摸结束事件
document.addEventListener('mouseup', onEnd);
document.addEventListener('touchend', onEnd);
function onStart(e) {
e.preventDefault();
isDragging = true;
startClientX = e.type.indexOf('touch') >= 0 ? e.touches[0].clientX : e.clientX;
startLeft = currentLeft;
}
function onMove(e) {
if (!isDragging) return;
e.preventDefault();
var clientX = e.type.indexOf('touch') >= 0 ? e.touches[0].clientX : e.clientX;
var dx = clientX - startClientX;
var newTrackLeft = startLeft + dx;
// 限制拖动范围(0 ~ trackMaxOffset)
newTrackLeft = Math.max(0, Math.min(newTrackLeft, trackMaxOffset));
// 计算小图的偏移量(滑块拖动距离与验证图偏移的比例转换)
var ratio = maxOffset / trackMaxOffset;
var imageLeft = Math.round(newTrackLeft * ratio);
// 更新滑块、填充条、小图的位置
currentLeft = newTrackLeft;
slideBtn.style.left = newTrackLeft + 'px';
slideFill.style.width = (newTrackLeft + btnWidth / 2) + 'px';
smallImage.style.left = imageLeft + 'px';
}
function onEnd(e) {
if (!isDragging) return;
isDragging = false;
// 提交滑块偏移量进行验证
document.getElementById('sliderOffset').value = parseInt(smallImage.style.left) || 0;
submitSlide(); // 调用step2接口
}
}
3.3 提交滑块验证结果
将滑块偏移量、鉴权参数提交后端,验证通过后展示备案信息:
function submitSlide() {
var formData = new FormData(document.getElementById('coordinateForm'));
formData.append('step', 2);
document.getElementById('loadingState').classList.remove('hidden');
fetch('yhicp/api.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.params && data.params.list && data.params.list.length > 0) {
showResult(data.params.list[0]); // 展示备案信息
} else {
showError(data.msg || '验证失败,请刷新页面重试');
}
})
.catch(error => {
showError('查询失败,请稍后再试');
});
}
四、总结
- 接口鉴权适配:工信部接口依赖
__jsluid_sCookie、Token、UUID 等参数,需严格模拟浏览器请求头和参数生成规则; - 滑块验证逻辑:前端需实现拖动范围限制、滑块与验证图的坐标比例转换,确保偏移量精准;
- 跨端兼容:滑块验证同时支持鼠标和触摸事件,适配移动端与 PC 端;
- 异常处理:前后端均需处理网络异常、验证失败、参数缺失等场景,提升用户体验
五、项目仓库
完整代码已开源至 Gitee,可直接下载部署验证Gitee仓库
https://gitee.com/bei-mio/miit-icp-query
六、扩展与优化方向
- 增加验证码自动识别(如接入第三方打码平台),实现免手动滑块验证;
- 优化前端交互,增加加载动画、错误提示的精细化展示;
- 增加备案信息缓存,减少重复请求工信部接口;
- 适配更多工信部接口参数,支持企业 / 个人等多类型备案查询
更多推荐



所有评论(0)