v1.0.51: 批次1架构筑基 - 1)版本迁移(common/migrate.php+schema_versions+natsort迁移文件);2)登录安全(bcrypt平滑迁移login/user_add/user_update旧sha1命中自动重写+system_login_attempts限流+session加固httponly/samesite/secure);3)CSRF Token(服务端checkCsrf+auth/csrf.php登录页取token+前端common.js/login.js统一携带X-CSRF-Token);4)统一基座common/Api.php并存量强制迁移全部70个endpoint(Api::boot按public/super/module/permissions分流,写接口强制checkAjax+checkCsrf,全局异常处理);5)前端收敛(common.js新增esc转义别名);6)REV-4硬数据来源渠道可编辑(hard_update+补全弹窗下拉);7)prepared卫生(soft_update.php修复+analysis.php表名白名单REV-9)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
qianqiuwanzi
2026-09-17 14:48:17 +08:00
parent 69fd4246c2
commit c86f08c325
91 changed files with 1072 additions and 463 deletions
+10
View File
@@ -0,0 +1,10 @@
<?php
/**
* 获取 CSRF Token GET /api/auth/csrf.php
* 供登录页等未登录场景在发起 POST 前先取会话绑定的 Token。
* 登录后 session.php 会随用户信息一并返回 csrf_token(common.js 自动缓存)。
*/
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['public' => true]);
Response::success(['csrf_token' => csrfToken()]);
+3 -6
View File
@@ -3,14 +3,11 @@
* 找回密码接口 POST /api/auth/forgot.php
* 入参:username / contact_email
* 说明:占位实现 —— 校验账号存在后返回成功(后续可接入 SMTP 发送重置邮件/短信验证码)。
* 批次1:经 Api::boot(['public' => true]) 免登录,但写请求仍校验 Ajax + CSRF。
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
Response::error('请求方式错误', 400);
}
Api::boot(['public' => true]);
$username = trim($_POST['username'] ?? '');
$contactEmail = trim($_POST['contact_email'] ?? '');
+32 -13
View File
@@ -3,18 +3,21 @@
* 登录接口 POST /api/auth/login.php
* 入参:username / password / slider_token(滑块验证通过后的token)
* 出参(成功):{ code:0, msg:'登录成功', data:{ real_name, role_name, permissions } }
*
* 批次1 安全加固(REV-1/REV-8):
* - 密码 bcrypt(password_hash)优先校验;存量 sha1 用户登录命中后平滑升级
* - 登录限流(15分钟内同 IP+账号失败≥5 次锁定)
* - 登录成功后 session_regenerate_id 防会话固定
* - 经 Api::boot 统一走 CSRF + 安全 Session
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
Response::error('请求方式错误', 400);
}
$pdo = Api::boot(['public' => true]);
$username = trim($_POST['username'] ?? '');
$password = (string)($_POST['password'] ?? '');
$sliderToken = trim($_POST['slider_token'] ?? '');
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
// 滑块验证:前端 slider.js 拖动通过后生成的 token,服务端校验非空且格式合法
if ($sliderToken === '' || strlen($sliderToken) < 10) {
@@ -24,7 +27,9 @@ if ($username === '' || $password === '') {
Response::error('请输入账号和密码');
}
$pdo = DB::getInstance()->getPdo();
// 登录限流(fail-open)
checkLoginThrottle($username, $ip);
$stmt = $pdo->prepare(
"SELECT u.*, r.role_name, r.permissions
FROM system_users u
@@ -34,22 +39,35 @@ $stmt = $pdo->prepare(
$stmt->execute([$username]);
$user = $stmt->fetch();
if (!$user || !hash_equals($user['password'], sha1($password))) {
// 密码校验:bcrypt 优先;旧版无盐 sha1 校验通过后即时平滑升级为 bcrypt
$authOk = false;
if ($user) {
if (password_verify($password, (string)$user['password'])) {
$authOk = true;
} elseif (hash_equals((string)$user['password'], sha1($password))) {
$authOk = true;
$pdo->prepare("UPDATE system_users SET password = ? WHERE id = ?")
->execute([password_hash($password, PASSWORD_DEFAULT), (int)$user['id']]);
}
}
if (!$authOk) {
recordLoginAttempt($username, $ip, false);
Response::error('账号或密码错误');
}
if ((int)$user['is_active'] !== 1) {
recordLoginAttempt($username, $ip, false);
Response::error('账号已被禁用,请联系管理员');
}
recordLoginAttempt($username, $ip, true);
// 更新最后登录时间/IP
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
$upd = $pdo->prepare("UPDATE system_users SET last_login_time = NOW(), last_login_ip = ? WHERE id = ?");
$upd->execute([$ip, $user['id']]);
// 写入 Session
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// 会话固定防护:登录成功后重新生成 session id(保留已有 CSRF Token)
session_regenerate_id(true);
$_SESSION['user_id'] = (int)$user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['real_name'] = $user['real_name'] ?? $user['username'];
@@ -64,4 +82,5 @@ Response::success([
'real_name' => $_SESSION['real_name'],
'role_name' => $_SESSION['role_name'],
'permissions' => $_SESSION['permissions'],
'csrf_token' => csrfToken(),
], '登录成功');
+3 -7
View File
@@ -1,15 +1,11 @@
<?php
/**
* 登出接口 POST /api/auth/logout.php
* 批次1 起经 Api::boot 统一校验登录态 + Ajax + CSRF。
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
Api::boot();
if (!empty($_SESSION['user_id'])) {
logAction((int)$_SESSION['user_id'], $_SESSION['username'] ?? '', 'logout', 'auth', 'system_users', (int)$_SESSION['user_id']);
+4 -4
View File
@@ -1,12 +1,11 @@
<?php
/**
* 会话校验接口 GET /api/auth/session.php
* 前端页面加载时调用:未登录返回 401(前端跳转登录页),已登录返回当前用户信息。
* 前端页面加载时调用:未登录返回 401(前端跳转登录页),已登录返回当前用户信息 + CSRF Token。
*/
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
requireLogin();
Api::boot(); // 仅要求登录
$pdo = DB::getInstance()->getPdo();
$stmt = $pdo->prepare("SELECT last_login_time, last_login_ip FROM system_users WHERE id = ?");
@@ -19,6 +18,7 @@ Response::success([
'real_name' => $_SESSION['real_name'],
'role_name' => $_SESSION['role_name'],
'permissions' => $_SESSION['permissions'],
'csrf_token' => csrfToken(),
'last_login_time' => $loginInfo['last_login_time'] ?? null,
'last_login_ip' => $loginInfo['last_login_ip'] ?? null,
]);
+6 -4
View File
@@ -7,11 +7,9 @@
* - year 为空 = 全部年份;否则按创建年份过滤
* 返回:{ years, company: [{channel, count, percent, details:[{source,count}]}], person: [...] }
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
checkPermission('channel');
Api::boot(['module' => 'channel']);
$year = trim($_REQUEST['year'] ?? '');
if ($year !== '' && (!ctype_digit($year) || (int)$year < 2000 || (int)$year > 2100)) {
@@ -32,6 +30,10 @@ $years = $pdo->query(
/** 单维度渠道分析 */
function channelAnalysis($pdo, $table, $year)
{
// REV-9:表名白名单,杜绝 SQL 插值
if (!in_array($table, ['companies', 'persons'], true)) {
Response::error('参数错误', 400);
}
$where = 'is_active = 1 AND source_channel IS NOT NULL AND source_channel <> \'\'';
$params = [];
if ($year !== '') {
+2 -6
View File
@@ -3,13 +3,9 @@
* 渠道新增计划删除接口 POST /api/channel/plan_delete.php
* 入参:id(必填);软删除(is_active = 0)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'channel']);
checkAjax();
checkPermission('channel');
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
+2 -4
View File
@@ -5,11 +5,9 @@
* 参数:page / limit / channel_type / status / keyword
* 返回:{ list, total, page, limit }(list 含 remaining_days 剩余天数)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'channel']);
checkPermission('channel');
[$page, $limit] = pageParams();
$channelType = trim($_REQUEST['channel_type'] ?? '');
+2 -7
View File
@@ -5,14 +5,9 @@
* source_detail / occurrence_address(发生地址)/ industry / start_date / end_date / remark / status(待启动|已执行|错过)/ cost(费用(元))
* 说明:编辑(传 id)时仅更新提交的字段,便于列表行内实时改状态/费用。
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/helpers.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'channel']);
checkAjax();
checkPermission('channel');
$id = (int)($_POST['id'] ?? 0);
+85
View File
@@ -0,0 +1,85 @@
<?php
/**
* 统一接口基座 v1(批次1「架构筑基」REV-ARCH-2)
*
* 所有业务接口统一走 Api::boot(),收敛「require 一串公共文件 + checkAjax +
* checkPermission + DB 连接」的重复样板,并提供统一的异常→JSON 兜底。
*
* 用法(endpoint 顶部):
* require_once __DIR__ . '/../common/Api.php';
* $pdo = Api::boot(['module' => 'preliminary']); // 登录 + 模块权限(写接口另含 Ajax/CSRF)
* $pdo = Api::boot(); // 仅要求登录(如 session.php)
* $pdo = Api::boot(['super' => true]); // 仅超级管理员
* $pdo = Api::boot(['permissions' => [...]]); // 任一权限
* $pdo = Api::boot(['public' => true]); // 免登录(仅 login/forgot/csrf)
*
* boot() 统一执行:
* - 安全 Session(httponly/samesite)
* - POST/PUT/PATCH/DELETE:checkAjax(X-Requested-With)+ checkCsrf(Token 头)
* - 登录态 + 权限
* - 返回 PDO(后续代码可直接用 $pdo;原 `$pdo = DB::getInstance()->getPdo();`
* 属冗余无害,可保留)
*
* 本文件另注册全局异常处理器:未捕获异常统一转 JSON 500 + 技术日志。
*/
require_once __DIR__ . '/db.php';
require_once __DIR__ . '/response.php';
require_once __DIR__ . '/logger.php';
require_once __DIR__ . '/security.php';
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/helpers.php';
require_once __DIR__ . '/validate.php';
require_once __DIR__ . '/duplicate_check.php';
require_once __DIR__ . '/completeness.php';
/** 统一异常处理:未捕获异常 → JSON 500 + 技术日志(REV-ARCH-3) */
set_exception_handler(function (\Throwable $e) {
logError('未捕获异常', [
'msg' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'uri' => $_SERVER['REQUEST_URI'] ?? '',
]);
if (!headers_sent()) {
http_response_code(500);
header('Content-Type: application/json; charset=utf-8');
}
echo json_encode(['code' => 500, 'msg' => '服务器内部错误,请稍后重试'], JSON_UNESCAPED_UNICODE);
exit;
});
class Api
{
/**
* 统一引导
* @param array $opts 见文件头注释
* @return PDO
*/
public static function boot(array $opts = [])
{
startSessionSecure();
$isWrite = in_array($_SERVER['REQUEST_METHOD'] ?? 'GET', ['POST', 'PUT', 'PATCH', 'DELETE'], true);
if ($isWrite) {
checkAjax();
}
if (empty($opts['public'])) {
if (!empty($opts['super'])) {
requireSuperAdmin();
} elseif (!empty($opts['module'])) {
checkPermission($opts['module']);
} elseif (!empty($opts['permissions'])) {
checkAnyPermission($opts['permissions']);
} else {
requireLogin();
}
}
if ($isWrite) {
checkCsrf();
}
return DB::getInstance()->getPdo();
}
}
+6 -4
View File
@@ -6,13 +6,15 @@
*/
require_once __DIR__ . '/db.php';
require_once __DIR__ . '/response.php';
require_once __DIR__ . '/security.php';
/** 启动 Session */
/**
* 启动 Session(批次1 起经 security.php 加固:httponly + samesite=Lax + HTTPS secure)
* 兼容既有调用方式,行为不变,仅 cookie 属性增强(REV-SEC-4/REV-8)
*/
function startSession()
{
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
startSessionSecure();
}
/** 校验写类接口的 Ajax 头 */
+2 -4
View File
@@ -5,11 +5,9 @@
* 返回各字典表启用中的数据,供前端下拉/联动使用。
* 数据来源:官方 Excel 生成的种子数据(见 sql/dict_seed.sql),不依赖任何第三方接口。
*/
require_once __DIR__ . '/db.php';
require_once __DIR__ . '/response.php';
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/Api.php';
$pdo = Api::boot();
requireLogin();
$type = trim($_REQUEST['type'] ?? 'all');
$pdo = DB::getInstance()->getPdo();
+18
View File
@@ -55,3 +55,21 @@ function logCurrent($action, $module, $targetTable = null, $targetId = null, $co
$content
);
}
/**
* 技术异常日志(批次1 REV-ARCH-3):写入 runtime/logs/error-YYYYMM.log
* 业务审计走 system_logs(logAction/logCurrent),技术异常走此文件。
* @param string $message
* @param mixed $context 上下文数组,自动 JSON 编码
*/
function logError($message, $context = null)
{
$dir = __DIR__ . '/../../runtime/logs';
if (!is_dir($dir)) {
@mkdir($dir, 0775, true);
}
$line = '[' . date('Y-m-d H:i:s') . '] ' . $message
. ($context !== null ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '')
. PHP_EOL;
@file_put_contents($dir . '/error-' . date('Ym') . '.log', $line, FILE_APPEND);
}
+2 -4
View File
@@ -11,15 +11,13 @@
* 返回:{ country, province, city, label, found }
* label 示例:"中国-广东省-深圳市" / "中国香港" / "美国"
*/
require_once __DIR__ . '/db.php';
require_once __DIR__ . '/response.php';
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/Api.php';
$pdo = Api::boot();
/**
* 接口入口:仅当作为 HTTP 接口直接访问时执行(便于函数被 require 复用/测试)
*/
if (isset($_SERVER['SCRIPT_FILENAME']) && realpath($_SERVER['SCRIPT_FILENAME']) === __FILE__) {
requireLogin();
$phone = trim($_REQUEST['phone'] ?? '');
if ($phone === '') {
+84
View File
@@ -0,0 +1,84 @@
<?php
/**
* 安全中间件(批次1):Session 加固 + CSRF Token + 登录限流
* 依赖:db.php / response.php / logger.php(均无回环依赖)
*/
require_once __DIR__ . '/db.php';
require_once __DIR__ . '/response.php';
require_once __DIR__ . '/logger.php';
/** 安全启动 Session:httponly + samesite=Lax + HTTPS 下 secure */
function startSessionSecure()
{
if (session_status() === PHP_SESSION_NONE) {
$secure = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => '',
'secure' => $secure,
'httponly' => true,
'samesite' => 'Lax',
]);
session_start();
}
}
/** 获取(或创建)当前会话绑定的 CSRF Token */
function csrfToken()
{
startSessionSecure();
if (empty($_SESSION['csrf_token']) || strlen($_SESSION['csrf_token']) !== 64) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
/**
* 校验写接口的 CSRF Token(要求 X-CSRF-Token 请求头与会话一致)
* 配合前端 common.js httpPost 统一附带;防跨站请求伪造(REV-2/REV-SEC-2)
*/
function checkCsrf()
{
startSessionSecure();
$token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if ($token === '' || empty($_SESSION['csrf_token'])
|| !hash_equals($_SESSION['csrf_token'], $token)) {
Response::error('安全校验失败(CSRF),请刷新页面后重试', 403);
}
}
/**
* 登录限流:同一 IP+账号 15 分钟内失败 ≥5 次则锁定(429)
* fail-open:DB 异常时跳过限流,保证登录可用(REV-8)
*/
function checkLoginThrottle($username, $ip)
{
try {
$pdo = DB::getInstance()->getPdo();
$pdo->prepare("DELETE FROM system_login_attempts WHERE attempt_time < NOW() - INTERVAL 30 MINUTE")->execute();
$stmt = $pdo->prepare(
"SELECT COUNT(*) FROM system_login_attempts
WHERE ip = ? AND username = ? AND success = 0 AND attempt_time > NOW() - INTERVAL 15 MINUTE"
);
$stmt->execute([$ip, $username]);
if ((int)$stmt->fetchColumn() >= 5) {
Response::error('尝试次数过多,请15分钟后再试', 429);
}
} catch (Exception $e) {
logError('登录限流检查失败(已跳过限流)', ['msg' => $e->getMessage()]);
}
}
/** 记录一次登录尝试结果(供限流统计) */
function recordLoginAttempt($username, $ip, $success)
{
try {
$pdo = DB::getInstance()->getPdo();
$pdo->prepare(
"INSERT INTO system_login_attempts (username, ip, success, attempt_time) VALUES (?, ?, ?, NOW())"
)->execute([$username, $ip, $success ? 1 : 0]);
} catch (Exception $e) {
logError('登录尝试记录失败', ['msg' => $e->getMessage()]);
}
}
+2 -9
View File
@@ -3,16 +3,9 @@
* 企业新增接口 POST /api/company/add.php
* 必填:display_name;其余字段见 COMPANY_FIELDS 白名单
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/helpers.php';
require_once __DIR__ . '/../common/completeness.php';
require_once __DIR__ . '/../common/duplicate_check.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'company']);
checkAjax();
checkPermission('company');
$data = extractFields(COMPANY_FIELDS);
// 显示名称不再单独录入:取中文名称(数据库 display_name 为 NOT NULL)
+2 -4
View File
@@ -4,11 +4,9 @@
* 返回启用中的认证类型(如:瞪羚企业、国家高新技术企业等),供编辑弹窗下拉使用
* 数据源:data_dict_certificate(原 certification_types,v1.0.16 改名,v1.0.18 再改名 data_dict_certificate)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'company']);
checkPermission('company');
$pdo = DB::getInstance()->getPdo();
$types = $pdo->query("SELECT id, name FROM data_dict_certificate WHERE is_active = 1 ORDER BY sort_order, id")->fetchAll();
+2 -6
View File
@@ -3,13 +3,9 @@
* 企业删除接口(软删除) POST /api/company/delete.php
* 入参:id(或 ids 逗号分隔批量)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'company']);
checkAjax();
checkPermission('company');
$id = (int)($_POST['id'] ?? 0);
$ids = trim($_POST['ids'] ?? '');
+2 -4
View File
@@ -3,11 +3,9 @@
* 企业详情接口 GET /api/company/detail.php?id=1
* 返回企业主表信息 + 关联数据(产品品类/文档/财务/资质/联系方式)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'company']);
checkPermission('company');
$id = (int)($_REQUEST['id'] ?? 0);
if ($id <= 0) {
+2 -4
View File
@@ -4,11 +4,9 @@
* 入参:company_id(必填)/ page / limit / keyword(姓名)/ job_level(职级)
* 数据源:persons JOIN person_work_experiences(按公司),联系方式取 social_accounts
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'company']);
checkPermission('company');
$companyId = (int)($_REQUEST['company_id'] ?? 0);
if ($companyId <= 0) {
+2 -5
View File
@@ -3,12 +3,9 @@
* 企业导出接口(CSV) GET/POST /api/company/export.php
* 入参:ids(必填,勾选的企业ID,逗号分隔)——只导出勾选的企业
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'company']);
checkPermission('company');
// 仅导出勾选的企业
$idsRaw = trim($_REQUEST['ids'] ?? '');
+2 -7
View File
@@ -6,14 +6,9 @@
* registered_capital,established_date,is_listed,stock_code
* 仅 display_name 为必填。
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/completeness.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'company']);
checkAjax();
checkPermission('company');
if (empty($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
Response::error('请选择要上传的CSV文件', 400);
+2 -4
View File
@@ -8,11 +8,9 @@
* 参数:page / limit / keyword / field / industry / company_type / is_active
* 返回:{ list, total, page, limit }
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'company']);
checkPermission('company');
[$page, $limit] = pageParams();
$keyword = trim($_REQUEST['keyword'] ?? '');
+2 -9
View File
@@ -5,16 +5,9 @@
* 入参:company_id(必填)/ platform(平台)/ account_id(账号名称)/ profile_url(主页链接)/
* remark / is_active(1启用 0停用)/ is_defult(1常用)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/helpers.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/completeness.php';
require_once __DIR__ . '/../common/duplicate_check.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'company']);
checkAjax();
checkPermission('company');
$companyId = (int)($_POST['company_id'] ?? 0);
$platform = trim($_POST['platform'] ?? '');
+2 -4
View File
@@ -5,11 +5,9 @@
* 字段:平台/账号名称/ID/主页链接/状态/更新日期
* 入参:company_id(必填)/ page / limit
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'company']);
checkPermission('company');
$companyId = (int)($_REQUEST['company_id'] ?? 0);
if ($companyId <= 0) {
+2 -7
View File
@@ -5,14 +5,9 @@
* is_active(1在产 0停产)/ attrs(可选 JSON:[{attr_id, value}],EAV 参数值)
* 说明:attr_id 来自 company_products_attr(按企业所属行业定义的参数属性)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/helpers.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'company']);
checkAjax();
checkPermission('company');
$companyId = (int)($_POST['company_id'] ?? 0);
$categoryName = trim($_POST['category_name'] ?? '');
+2 -4
View File
@@ -3,11 +3,9 @@
* 产品参数属性定义接口 GET /api/company/product_attrs.php?industry=汽车制造
* 返回指定行业的参数属性定义(EAV),供新增产品弹窗动态生成参数输入项
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'company']);
checkPermission('company');
$industry = trim($_REQUEST['industry'] ?? '');
if ($industry === '') {
+2 -4
View File
@@ -4,11 +4,9 @@
* v1.0.18:返回产品基础字段(产品品类/基本定位/包含系列/状态/更新日期)+ EAV 参数值(JSON)
* 入参:company_id(必填)/ page / limit
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'company']);
checkPermission('company');
$companyId = (int)($_REQUEST['company_id'] ?? 0);
if ($companyId <= 0) {
+2 -9
View File
@@ -3,16 +3,9 @@
* 企业编辑接口 POST /api/company/update.php
* 入参:id + 需要更新的白名单字段
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/helpers.php';
require_once __DIR__ . '/../common/completeness.php';
require_once __DIR__ . '/../common/duplicate_check.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'company']);
checkAjax();
checkPermission('company');
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
+2 -4
View File
@@ -4,11 +4,9 @@
* 返回:8 个指标卡 + 区域分布(国外=世界地图按国家 / 国内=中国地图按省份,企业/人员双维度)
* + 行业分布(企业/人员)+ 需求关键词(简化)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'dashboard']);
checkPermission('dashboard');
$pdo = DB::getInstance()->getPdo();
+2 -6
View File
@@ -3,13 +3,9 @@
* 文件删除接口(同时删除物理文件) POST /api/document/delete.php
* 入参:id(或 ids 逗号分隔批量)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['permissions' => ['document', 'competitor_data']]);
checkAjax();
checkAnyPermission(['document', 'competitor_data']);
$id = (int)($_POST['id'] ?? 0);
$ids = trim($_POST['ids'] ?? '');
+2 -4
View File
@@ -4,11 +4,9 @@
* 参数:page / limit / keyword / file_type / is_active
* 数据源:company_documents 表
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['permissions' => ['document', 'competitor_data']]);
checkAnyPermission(['document', 'competitor_data']);
[$page, $limit] = pageParams();
$keyword = trim($_REQUEST['keyword'] ?? '');
+2 -6
View File
@@ -5,13 +5,9 @@
* 存储:static/uploads/documents/年/月/随机文件名
* 返回:相对路径 storage_path
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['permissions' => ['document', 'competitor_data']]);
checkAjax();
checkAnyPermission(['document', 'competitor_data']);
if (empty($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
Response::error('请选择要上传的文件', 400);
+2 -8
View File
@@ -6,15 +6,9 @@
* target_product_category / description / source_channel
* 写入 preliminary_data,recorded_by=当前登录用户(v1.0.50 起无 status 字段)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/validate.php';
require_once __DIR__ . '/../common/duplicate_check.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'preliminary']);
checkAjax();
checkPermission('preliminary');
$sourceType = trim($_POST['source_type'] ?? '');
if (!in_array($sourceType, ['company', 'person', 'product', 'need', 'mixed'], true)) {
+2 -9
View File
@@ -6,16 +6,9 @@
* description, industry, need_category, application_scenario, contact_person
* 逻辑:按目标类型写入主表(product/need 缺公司时自动建公司)→ 标记碎片已转换(converted_type/id, processed_at)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/completeness.php';
require_once __DIR__ . '/../common/duplicate_check.php';
require_once __DIR__ . '/../common/validate.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'preliminary']);
checkAjax();
checkPermission('preliminary');
$id = (int)($_POST['id'] ?? 0);
$targetType = trim($_POST['target_type'] ?? '');
+2 -4
View File
@@ -2,12 +2,10 @@
/**
* 硬碎片详情接口 GET /api/fragment/hard_detail.php?id=1
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'preliminary']);
require_once __DIR__ . '/hard_common.php';
checkPermission('preliminary');
$id = (int)($_REQUEST['id'] ?? 0);
if ($id <= 0) {
+2 -6
View File
@@ -4,13 +4,9 @@
* 入参:id
* v1.0.50:废弃改为从 preliminary_data 彻底硬删除(不再保留记录)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'preliminary']);
checkAjax();
checkPermission('preliminary');
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
+2 -4
View File
@@ -5,12 +5,10 @@
* 返回:{ list, total, page, limit },每行含完整度 score、缺失层级、内容摘要
* 说明:v1.0.50 起移除 status 字段(查看弹窗/数据表均不再展示状态),列表仅返回未转换(converted_id IS NULL)记录
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'preliminary']);
require_once __DIR__ . '/hard_common.php';
checkPermission('preliminary');
[$page, $limit] = pageParams();
$keyword = trim($_REQUEST['keyword'] ?? '');
+7 -11
View File
@@ -4,22 +4,14 @@
* 仅更新硬碎片字段(企业名/联系人/手机/邮箱/身份证/社媒账号/主页链接),不执行转换。
* 转换请走 hard_convert.php(前端「转软碎片」按钮)。
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/validate.php';
require_once __DIR__ . '/../common/duplicate_check.php';
require_once __DIR__ . '/../common/Api.php';
checkAjax();
checkPermission('preliminary');
$pdo = Api::boot(['module' => 'preliminary']);
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
Response::error('参数错误', 400);
}
$pdo = DB::getInstance()->getPdo();
$stmt = $pdo->prepare("SELECT * FROM preliminary_data WHERE id = ? AND is_active = 1");
$stmt->execute([$id]);
$frag = $stmt->fetch();
@@ -45,6 +37,7 @@ $idNumber = trim($_POST['id_number'] ?? '');
$platform = trim($_POST['platform'] ?? '');
$accountId = trim($_POST['account_id'] ?? '');
$profileUrl = trim($_POST['profile_url'] ?? '');
$sourceChannel = trim($_POST['source_channel'] ?? ($frag['source_channel'] ?? '')); // REV-4 来源渠道可编辑
if (!in_array($sourceType, ['company', 'person', 'product', 'need', 'mixed'], true)) {
Response::error('碎片类型不合法', 400);
}
@@ -89,7 +82,8 @@ if (!empty($dups)) Response::duplicates($dups);
$pdo->prepare(
"UPDATE preliminary_data
SET source_type = ?, company_name = ?, industry = ?, registration_number = ?, business_role = ?, country = ?, address = ?,
person_name = ?, contact_phone = ?, contact_email = ?, work_location = ?, id_number = ?, platform = ?, account_id = ?, profile_url = ?
person_name = ?, contact_phone = ?, contact_email = ?, work_location = ?, id_number = ?, platform = ?, account_id = ?, profile_url = ?,
source_channel = ?
WHERE id = ?"
)->execute([
$sourceType,
@@ -107,6 +101,7 @@ $pdo->prepare(
$platform !== '' ? $platform : null,
$accountId !== '' ? $accountId : null,
$profileUrl !== '' ? $profileUrl : null,
$sourceChannel !== '' ? $sourceChannel : null,
$id,
]);
@@ -117,6 +112,7 @@ logCurrent('update', 'fragment', 'preliminary_data', $id, [
'phone' => $phone,
'email' => $email,
'id_number' => $idNumber,
'source_channel' => $sourceChannel,
]);
Response::success(null, '已保存,点击「转软碎片」完成转换');
+2 -5
View File
@@ -4,12 +4,9 @@
* 返回:{ table, table_label, id, name, overall, level, layers, missing_core, missing_important,
* missing_optional, missing_labels, row }
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/completeness.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'preliminary']);
checkPermission('preliminary');
$table = trim($_REQUEST['table'] ?? '');
$id = (int)($_REQUEST['id'] ?? 0);
+2 -5
View File
@@ -6,12 +6,9 @@
* 每行:{ table, table_label, id, name, overall 整体完整度, level 缺失层级,
* layer_rates {core,important,optional}, missing_fields 缺失字段(按核心→重要→非必要优先级), created_at }
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/completeness.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'preliminary']);
checkPermission('preliminary');
[$page, $limit] = pageParams();
$table = trim($_REQUEST['table'] ?? '');
+5 -13
View File
@@ -5,17 +5,9 @@
* 逻辑:更新主表字段 → 重新计算完整度 → 达标则 is_incomplete=0
* 特殊:persons 的 phone/email 写 social_accounts(platform=phone/email)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/helpers.php';
require_once __DIR__ . '/../common/completeness.php';
require_once __DIR__ . '/../common/duplicate_check.php';
require_once __DIR__ . '/../common/validate.php';
require_once __DIR__ . '/../common/Api.php';
checkAjax();
checkPermission('preliminary');
$pdo = Api::boot(['module' => 'preliminary']);
$table = trim($_POST['table'] ?? '');
$id = (int)($_POST['id'] ?? 0);
@@ -23,8 +15,6 @@ if (!in_array($table, ['persons', 'companies', 'social_accounts', 'media'], true
Response::error('参数错误', 400);
}
$pdo = DB::getInstance()->getPdo();
/** 按白名单收集 POST 字段 */
$fields = [];
$collect = function ($whitelist) use (&$fields) {
@@ -74,7 +64,9 @@ switch ($table) {
$dup = findDuplicate($pdo, 'id_number', $fields['id_number'], [$id]);
if ($dup) $dups[] = $dup;
}
$ownAccountIds = $pdo->query("SELECT id FROM social_accounts WHERE owner_type = 'person' AND owner_id = $id")->fetchAll(PDO::FETCH_COLUMN);
$ownStmt = $pdo->prepare("SELECT id FROM social_accounts WHERE owner_type = 'person' AND owner_id = ?");
$ownStmt->execute([$id]);
$ownAccountIds = $ownStmt->fetchAll(PDO::FETCH_COLUMN);
if (isset($_POST['phone'])) {
$dups = array_merge($dups, checkSingleAccountDuplicate($pdo, $_POST['phone'] !== '' ? 'phone' : '', $_POST['phone'], '', $ownAccountIds));
}
+2 -4
View File
@@ -10,11 +10,9 @@
* - pending 待处理碎片(硬待处理 + 软待处理)
* 说明:v1.0.50 起 preliminary_data 无 status 字段,已转换以 converted_id 非空标识
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'preliminary']);
checkPermission('preliminary');
$pdo = DB::getInstance()->getPdo();
$monthStart = "DATE_FORMAT(CURDATE(), '%Y-%m-01')";
+2 -6
View File
@@ -3,16 +3,12 @@
* 数据定制接口(占位) GET/POST /api/marketing/data_custom.php
* 后续扩展:定制数据需求提交、报价、交付等。
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'marketing']);
checkPermission('marketing');
$action = $_REQUEST['action'] ?? 'info';
if ($action === 'submit' && $_SERVER['REQUEST_METHOD'] === 'POST') {
checkAjax();
$content = [
'contact_name' => trim($_POST['contact_name'] ?? ''),
'contact_phone' => trim($_POST['contact_phone'] ?? ''),
+2 -4
View File
@@ -7,11 +7,9 @@
* filter 按 行业 + 区域 + 关键词 筛选邮箱列表(persons.email + companies 关联联系人邮箱)
* 入参(filter):industry / province / city / keyword / page / limit
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'marketing']);
checkPermission('marketing');
$action = $_REQUEST['action'] ?? 'filter';
$pdo = DB::getInstance()->getPdo();
+2 -5
View File
@@ -3,12 +3,9 @@
* EDM 邮箱列表导出接口(CSV) GET/POST /api/marketing/edm_export.php
* 入参同 edm.php filter:industry / province / city / keyword
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'marketing']);
checkPermission('marketing');
// 仅导出勾选的邮箱(emails 逗号分隔)
$emailsRaw = trim($_REQUEST['emails'] ?? '');
+2 -6
View File
@@ -3,16 +3,12 @@
* 媒体批发接口(占位) GET/POST /api/marketing/media_wholesale.php
* 后续扩展:媒体资源批量询价、下单等。
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'marketing']);
checkPermission('marketing');
$action = $_REQUEST['action'] ?? 'info';
if ($action === 'inquiry' && $_SERVER['REQUEST_METHOD'] === 'POST') {
checkAjax();
$content = [
'media_ids' => trim($_POST['media_ids'] ?? ''),
'remark' => trim($_POST['remark'] ?? ''),
+2 -9
View File
@@ -4,16 +4,9 @@
* 必填:owner_type / owner_id / platform / account_id
* 可选:profile_url / remark / is_primary + 商业属性(account_level/certification_type/follower_count 等)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/helpers.php';
require_once __DIR__ . '/../common/completeness.php';
require_once __DIR__ . '/../common/duplicate_check.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'media']);
checkAjax();
checkPermission('media');
$data = extractFields(MEDIA_FIELDS);
if (!in_array($data['owner_type'] ?? '', ['person', 'company'], true)) {
+2 -6
View File
@@ -3,13 +3,9 @@
* 媒体数据删除接口(软删除) POST /api/media/delete.php
* 入参:id(或 ids 逗号分隔批量)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'media']);
checkAjax();
checkPermission('media');
$id = (int)($_POST['id'] ?? 0);
$ids = trim($_POST['ids'] ?? '');
+2 -4
View File
@@ -2,11 +2,9 @@
/**
* 媒体数据详情接口 GET /api/media/detail.php?id=1
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'media']);
checkPermission('media');
$id = (int)($_REQUEST['id'] ?? 0);
if ($id <= 0) {
+2 -5
View File
@@ -3,12 +3,9 @@
* 媒体数据导出接口(CSV) GET/POST /api/media/export.php
* 参数同 list.php
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'media']);
checkPermission('media');
// 仅导出勾选的媒体账号
$idsRaw = trim($_REQUEST['ids'] ?? '');
+2 -7
View File
@@ -5,14 +5,9 @@
* account_level,content_categories,follower_count,avg_read_count,certification_type
* owner_type 为 person/company 时 owner_id 也可填对应名称(display_name/full_name),自动解析为ID。
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/completeness.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'media']);
checkAjax();
checkPermission('media');
if (empty($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
Response::error('请选择要上传的CSV文件', 400);
+2 -4
View File
@@ -4,11 +4,9 @@
* 参数:page / limit / keyword / platform / certification_type / account_level / owner_type / is_active
* 数据源:social_accounts JOIN media_commercial_attributes
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'media']);
checkPermission('media');
[$page, $limit] = pageParams();
$keyword = trim($_REQUEST['keyword'] ?? '');
+2 -9
View File
@@ -3,16 +3,9 @@
* 媒体数据编辑接口 POST /api/media/update.php
* 入参:id + 白名单字段(含商业属性)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/helpers.php';
require_once __DIR__ . '/../common/completeness.php';
require_once __DIR__ . '/../common/duplicate_check.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'media']);
checkAjax();
checkPermission('media');
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
+2 -7
View File
@@ -3,14 +3,9 @@
* 需求新增接口 POST /api/need/add.php
* 必填:company_id;可选白名单字段见 NEED_FIELDS
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/helpers.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'need']);
checkAjax();
checkPermission('need');
$data = extractFields(NEED_FIELDS);
if (empty($data['company_id'])) {
+2 -6
View File
@@ -3,13 +3,9 @@
* 需求删除接口(作废) POST /api/need/delete.php
* 入参:id(或 ids 逗号分隔批量)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'need']);
checkAjax();
checkPermission('need');
$id = (int)($_POST['id'] ?? 0);
$ids = trim($_POST['ids'] ?? '');
+2 -4
View File
@@ -2,11 +2,9 @@
/**
* 需求详情接口 GET /api/need/detail.php?id=1
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'need']);
checkPermission('need');
$id = (int)($_REQUEST['id'] ?? 0);
if ($id <= 0) {
+2 -4
View File
@@ -3,11 +3,9 @@
* 需求转盘列表接口 GET/POST /api/need/list.php
* 参数:page / limit / keyword / start_date / end_date / company_name / industry / need_category / is_valid
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'need']);
checkPermission('need');
[$page, $limit] = pageParams();
$keyword = trim($_REQUEST['keyword'] ?? '');
+2 -6
View File
@@ -5,13 +5,9 @@
* 说明:当前为占位实现,仅写日志并返回成功;
* 后续可扩展为调用第三方API推送JSON数据 / 通过SMTP发送邮件。
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'need']);
checkAjax();
checkPermission('need');
$id = (int)($_POST['id'] ?? 0);
$ids = trim($_POST['ids'] ?? '');
+2 -7
View File
@@ -3,14 +3,9 @@
* 需求编辑接口 POST /api/need/update.php
* 入参:id + 白名单字段(可含 is_valid 作废/恢复)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/helpers.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'need']);
checkAjax();
checkPermission('need');
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
+2 -10
View File
@@ -4,17 +4,9 @@
* 必填:full_name;union_id 为空时自动生成。
* 可选:contacts(JSON 数组,如 [{"platform":"email","account_id":"a@b.com","is_primary":1}])
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/helpers.php';
require_once __DIR__ . '/../common/completeness.php';
require_once __DIR__ . '/../common/duplicate_check.php';
require_once __DIR__ . '/../common/validate.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'person']);
checkAjax();
checkPermission('person');
$data = extractFields(PERSON_FIELDS);
if (empty($data['full_name'])) {
+2 -4
View File
@@ -4,11 +4,9 @@
* 入参:person_id(必填)/ page / limit
* 返回:该人员任职的公司列表(公司名称/部门/岗位/职级/入职日期/是否在岗)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'person']);
checkPermission('person');
$personId = (int)($_REQUEST['person_id'] ?? 0);
if ($personId <= 0) {
+2 -4
View File
@@ -4,11 +4,9 @@
* 返回启用中的企业(id + 显示名称),供人员「工作履历」行内公司下拉使用
* 入参:keyword(可选,模糊匹配企业名称)/ limit(默认 500)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'person']);
checkPermission('person');
$keyword = trim($_REQUEST['keyword'] ?? '');
$limit = min(1000, max(1, (int)($_REQUEST['limit'] ?? 500)));
+2 -4
View File
@@ -12,11 +12,9 @@
* 入参:person_id(必填)/ limit(默认 50)
* 返回:{ list: [{id, full_name, phone, tags:[老乡,校友,同事], intimacy}] }
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'person']);
checkPermission('person');
$personId = (int)($_REQUEST['person_id'] ?? 0);
if ($personId <= 0) {
+2 -6
View File
@@ -3,13 +3,9 @@
* 人员删除接口(软删除) POST /api/person/delete.php
* 入参:id(或 ids 逗号分隔批量)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'person']);
checkAjax();
checkPermission('person');
$id = (int)($_POST['id'] ?? 0);
$ids = trim($_POST['ids'] ?? '');
+2 -4
View File
@@ -3,11 +3,9 @@
* 人员详情接口 GET /api/person/detail.php?id=1
* v1.0.16:联系方式改为“手机”“邮箱”两字段(仅常用);另有全部账号列表 accounts 供编辑回显
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'person']);
checkPermission('person');
$id = (int)($_REQUEST['id'] ?? 0);
if ($id <= 0) {
+2 -5
View File
@@ -3,12 +3,9 @@
* 人员导出接口(CSV) GET/POST /api/person/export.php
* 参数同 list.php
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'person']);
checkPermission('person');
// 仅导出勾选的人员
$idsRaw = trim($_REQUEST['ids'] ?? '');
+2 -7
View File
@@ -5,14 +5,9 @@
* graduated_from,hometown,work_location,phone,email,source_channel,source_detail
* 仅 full_name 必填;union_id 留空自动生成;phone/email 写入 social_accounts(默认常用)。
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/completeness.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'person']);
checkAjax();
checkPermission('person');
if (empty($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
Response::error('请选择要上传的CSV文件', 400);
+2 -4
View File
@@ -5,11 +5,9 @@
* 返回:{ list, total, page, limit }
* v1.0.16:联系方式列改为“手机”“邮箱”两个字段(仅返回常用:is_defult=1 优先,其次 is_primary=1)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'person']);
checkPermission('person');
[$page, $limit] = pageParams();
$keyword = trim($_REQUEST['keyword'] ?? '');
+2 -4
View File
@@ -4,11 +4,9 @@
* 入参:person_id(必填)/ page / limit
* 数据源:该人员任职公司的产品品类(去重)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'person']);
checkPermission('person');
$personId = (int)($_REQUEST['person_id'] ?? 0);
if ($personId <= 0) {
+2 -10
View File
@@ -3,17 +3,9 @@
* 人员编辑接口 POST /api/person/update.php
* 入参:id + 白名单字段;可选 contacts(JSON 数组,整体替换联系方式)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/helpers.php';
require_once __DIR__ . '/../common/completeness.php';
require_once __DIR__ . '/../common/duplicate_check.php';
require_once __DIR__ . '/../common/validate.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'person']);
checkAjax();
checkPermission('person');
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
+2 -4
View File
@@ -3,11 +3,9 @@
* 碎片处理列表接口(待定逻辑,暂做基础列表) GET/POST /api/preliminary/list.php
* 参数:page / limit / keyword / source_type / start_date / end_date
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'preliminary']);
checkPermission('preliminary');
[$page, $limit] = pageParams();
$keyword = trim($_REQUEST['keyword'] ?? '');
+2 -4
View File
@@ -3,11 +3,9 @@
* 碎片处理统计接口 GET /api/preliminary/stats.php
* 返回:总数 / 今日新增
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'preliminary']);
checkPermission('preliminary');
$pdo = DB::getInstance()->getPdo();
+2 -4
View File
@@ -4,11 +4,9 @@
* 仅超级管理员可见,默认查询近1个月
* 参数:page / limit / keyword / action / module / username / start_date / end_date
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['super' => true]);
requireSuperAdmin();
[$page, $limit] = pageParams();
$keyword = trim($_REQUEST['keyword'] ?? '');
+2 -6
View File
@@ -3,13 +3,9 @@
* 新增角色接口(含权限分配) POST /api/system/role_add.php
* 入参:role_name / permissions(JSON数组,菜单标识)/ is_active
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'system']);
checkAjax();
checkPermission('system');
$roleName = trim($_POST['role_name'] ?? '');
$perms = json_decode($_POST['permissions'] ?? '[]', true);
+2 -6
View File
@@ -4,13 +4,9 @@
* 入参:id(或 ids 逗号分隔批量)
* 保护:不允许删除内置超级管理员角色(id=1)及仍被用户使用的角色
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'system']);
checkAjax();
checkPermission('system');
$id = (int)($_POST['id'] ?? 0);
$ids = trim($_POST['ids'] ?? '');
+2 -4
View File
@@ -3,11 +3,9 @@
* 角色列表接口 GET/POST /api/system/role_list.php
* 参数:page / limit / keyword / is_active
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'system']);
checkPermission('system');
[$page, $limit] = pageParams();
$keyword = trim($_REQUEST['keyword'] ?? '');
+2 -6
View File
@@ -3,13 +3,9 @@
* 编辑角色接口(含权限分配) POST /api/system/role_update.php
* 入参:id / role_name / permissions(JSON数组)/ is_active
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'system']);
checkAjax();
checkPermission('system');
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
+5 -11
View File
@@ -3,13 +3,9 @@
* 新增用户接口 POST /api/system/user_add.php
* 入参:username / password / real_name / role_id / is_active
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
checkAjax();
checkPermission('system');
$pdo = Api::boot(['module' => 'system']);
$username = trim($_POST['username'] ?? '');
$password = (string)($_POST['password'] ?? '');
@@ -20,15 +16,13 @@ $isActive = isset($_POST['is_active']) ? ((int)$_POST['is_active'] ? 1 : 0) : 1;
if ($username === '' || !preg_match('/^[a-zA-Z0-9_]{3,50}$/', $username)) {
Response::error('账号需为3-50位字母/数字/下划线', 400);
}
if (strlen($password) < 6) {
Response::error('密码长度不能少于6位', 400);
if (strlen($password) < 8 || !preg_match('/[a-zA-Z]/', $password) || !preg_match('/\d/', $password)) {
Response::error('密码需不少于8位,且同时包含字母和数字', 400);
}
if ($roleId <= 0) {
Response::error('请选择角色', 400);
}
$pdo = DB::getInstance()->getPdo();
$chk = $pdo->prepare("SELECT COUNT(*) FROM system_users WHERE username = ?");
$chk->execute([$username]);
if ((int)$chk->fetchColumn() > 0) {
@@ -45,7 +39,7 @@ $stmt = $pdo->prepare(
"INSERT INTO system_users (username, password, real_name, role_id, is_active)
VALUES (?, ?, ?, ?, ?)"
);
$stmt->execute([$username, sha1($password), $realName !== '' ? $realName : null, $roleId, $isActive]);
$stmt->execute([$username, password_hash($password, PASSWORD_DEFAULT), $realName !== '' ? $realName : null, $roleId, $isActive]);
$newId = (int)$pdo->lastInsertId();
logCurrent('add', 'system', 'system_users', $newId, ['username' => $username, 'role_id' => $roleId, 'is_active' => $isActive]);
+2 -6
View File
@@ -4,13 +4,9 @@
* 入参:id(或 ids 逗号分隔批量)
* 保护:不允许删除内置管理员(id=1)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'system']);
checkAjax();
checkPermission('system');
$id = (int)($_POST['id'] ?? 0);
$ids = trim($_POST['ids'] ?? '');
+2 -4
View File
@@ -3,11 +3,9 @@
* 用户列表接口 GET/POST /api/system/user_list.php
* 参数:page / limit / keyword / role_id / is_active
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/Api.php';
$pdo = Api::boot(['module' => 'system']);
checkPermission('system');
[$page, $limit] = pageParams();
$keyword = trim($_REQUEST['keyword'] ?? '');
+5 -11
View File
@@ -3,20 +3,14 @@
* 编辑用户接口(含角色分配) POST /api/system/user_update.php
* 入参:id / real_name / role_id / is_active / password(可选,留空不改密码)
*/
require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php';
require_once __DIR__ . '/../common/auth.php';
require_once __DIR__ . '/../common/logger.php';
require_once __DIR__ . '/../common/Api.php';
checkAjax();
checkPermission('system');
$pdo = Api::boot(['module' => 'system']);
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
Response::error('参数错误', 400);
}
$pdo = DB::getInstance()->getPdo();
$check = $pdo->prepare("SELECT * FROM system_users WHERE id = ?");
$check->execute([$id]);
$old = $check->fetch();
@@ -52,11 +46,11 @@ if (isset($_POST['is_active'])) {
}
$password = (string)($_POST['password'] ?? '');
if ($password !== '') {
if (strlen($password) < 6) {
Response::error('密码长度不能少于6位', 400);
if (strlen($password) < 8 || !preg_match('/[a-zA-Z]/', $password) || !preg_match('/\d/', $password)) {
Response::error('密码需不少于8位,且同时包含字母和数字', 400);
}
$sets[] = 'password = ?';
$params[] = sha1($password);
$params[] = password_hash($password, PASSWORD_DEFAULT);
}
if (!$sets) {