v1.0.10
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* Session 鉴权中间件
|
||||
* 所有接口(登录/找回密码除外)均需通过 requireLogin() / checkPermission() 校验。
|
||||
* 写类接口还必须通过 checkAjax()(要求 X-Requested-With: XMLHttpRequest)。
|
||||
*/
|
||||
require_once __DIR__ . '/db.php';
|
||||
require_once __DIR__ . '/response.php';
|
||||
|
||||
/** 启动 Session */
|
||||
function startSession()
|
||||
{
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
}
|
||||
|
||||
/** 校验写类接口的 Ajax 头 */
|
||||
function checkAjax()
|
||||
{
|
||||
if (($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') !== 'XMLHttpRequest') {
|
||||
Response::error('非法请求', 400);
|
||||
}
|
||||
}
|
||||
|
||||
/** 校验是否已登录,未登录返回 401 */
|
||||
function requireLogin()
|
||||
{
|
||||
startSession();
|
||||
if (empty($_SESSION['user_id'])) {
|
||||
Response::error('未登录或会话已过期', 401);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验当前用户是否拥有某模块权限(同时校验登录态)
|
||||
* @param string $module 菜单标识:dashboard/company/person/media/need/marketing/channel/document/preliminary/system/log
|
||||
*/
|
||||
function checkPermission($module)
|
||||
{
|
||||
requireLogin();
|
||||
$perms = $_SESSION['permissions'] ?? [];
|
||||
if (!in_array($module, $perms, true)) {
|
||||
Response::error('无权操作', 403);
|
||||
}
|
||||
}
|
||||
|
||||
/** 仅超级管理员可操作(如操作日志查询) */
|
||||
function requireSuperAdmin()
|
||||
{
|
||||
requireLogin();
|
||||
if (($_SESSION['role_name'] ?? '') !== '超级管理员') {
|
||||
Response::error('仅超级管理员可操作', 403);
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取客户端 IP */
|
||||
function clientIp()
|
||||
{
|
||||
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||
}
|
||||
|
||||
/** 分页参数解析:page / limit */
|
||||
function pageParams($defaultLimit = 20)
|
||||
{
|
||||
$page = max(1, (int)($_REQUEST['page'] ?? 1));
|
||||
$limit = min(200, max(1, (int)($_REQUEST['limit'] ?? $defaultLimit)));
|
||||
return [$page, $limit];
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* PDO 数据库连接类(单例模式)
|
||||
* 用法:$pdo = DB::getInstance()->getPdo();
|
||||
*/
|
||||
class DB
|
||||
{
|
||||
private static $instance = null;
|
||||
private $pdo;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
$cfg = require __DIR__ . '/../../config/database.php';
|
||||
$dsn = sprintf(
|
||||
'mysql:host=%s;port=%d;dbname=%s;charset=%s',
|
||||
$cfg['host'],
|
||||
$cfg['port'],
|
||||
$cfg['dbname'],
|
||||
$cfg['charset']
|
||||
);
|
||||
$this->pdo = new PDO($dsn, $cfg['username'], $cfg['password'], [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getInstance()
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function getPdo()
|
||||
{
|
||||
return $this->pdo;
|
||||
}
|
||||
|
||||
// 禁止克隆
|
||||
private function __clone() {}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* 通用辅助函数与字段白名单(供各模块 add/update/import 复用)
|
||||
*/
|
||||
require_once __DIR__ . '/db.php';
|
||||
require_once __DIR__ . '/response.php';
|
||||
|
||||
/** 企业表可写入字段白名单(key => 类型标记:s字符串/i整数/d日期/n数字) */
|
||||
const COMPANY_FIELDS = [
|
||||
'name_zh' => 's', 'name_en' => 's', 'display_name' => 's',
|
||||
'business_role' => 's', 'country' => 's', 'registration_number' => 's',
|
||||
'address' => 's', 'legal_form' => 's', 'legal_representative' => 's',
|
||||
'business_scope' => 's', 'established_date' => 'd', 'registered_capital' => 's',
|
||||
'industry' => 's', 'industry_subdivision' => 's',
|
||||
'latest_employee_count' => 'i', 'latest_annual_revenue' => 's',
|
||||
'is_listed' => 'i', 'stock_code' => 's', 'website' => 's',
|
||||
];
|
||||
|
||||
/** 人员表可写入字段白名单 */
|
||||
const PERSON_FIELDS = [
|
||||
'union_id' => 's', 'full_name' => 's', 'gender' => 's', 'nationality' => 's',
|
||||
'id_type' => 's', 'id_number' => 's', 'education' => 's', 'graduated_from' => 's',
|
||||
'hometown' => 's', 'work_location' => 's',
|
||||
];
|
||||
|
||||
/** 媒体(社交账号)表可写入字段白名单 */
|
||||
const MEDIA_FIELDS = [
|
||||
'owner_type' => 's', 'owner_id' => 'i', 'platform' => 's', 'account_id' => 's',
|
||||
'profile_url' => 's', 'remark' => 's', 'is_primary' => 'i',
|
||||
];
|
||||
|
||||
/** 媒体商业属性表可写入字段白名单 */
|
||||
const MEDIA_ATTR_FIELDS = [
|
||||
'account_level' => 's', 'content_categories' => 's', 'follower_count' => 'i',
|
||||
'avg_read_count' => 'i', 'certification_type' => 's',
|
||||
'special_requirements' => 's', 'media_remark' => 's',
|
||||
];
|
||||
|
||||
/** 需求表可写入字段白名单 */
|
||||
const NEED_FIELDS = [
|
||||
'company_id' => 'i', 'contact_person' => 's', 'need_category' => 's',
|
||||
'target_product_category' => 's', 'application_scenario' => 's', 'description' => 's',
|
||||
];
|
||||
|
||||
/**
|
||||
* 从 POST 中按白名单提取并清洗数据
|
||||
* @param array $allowlist
|
||||
* @return array
|
||||
*/
|
||||
function extractFields($allowlist)
|
||||
{
|
||||
$data = [];
|
||||
foreach ($allowlist as $key => $type) {
|
||||
$val = $_POST[$key] ?? null;
|
||||
if ($val === null) {
|
||||
continue;
|
||||
}
|
||||
$val = trim((string)$val);
|
||||
if ($val === '') {
|
||||
continue;
|
||||
}
|
||||
switch ($type) {
|
||||
case 'i':
|
||||
$data[$key] = (int)$val;
|
||||
break;
|
||||
case 'd':
|
||||
$data[$key] = (strtotime($val) !== false) ? date('Y-m-d', strtotime($val)) : null;
|
||||
break;
|
||||
default:
|
||||
$data[$key] = $val;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/** 生成 INSERT 语句片段:字段名列表 + 占位符 */
|
||||
function buildInsert($data)
|
||||
{
|
||||
$cols = array_keys($data);
|
||||
$sql = '(`' . implode('`,`', $cols) . '`) VALUES (' . rtrim(str_repeat('?,', count($cols)), ',') . ')';
|
||||
return [$sql, array_values($data)];
|
||||
}
|
||||
|
||||
/** 生成 UPDATE SET 片段 */
|
||||
function buildUpdate($data)
|
||||
{
|
||||
$sets = [];
|
||||
foreach (array_keys($data) as $col) {
|
||||
$sets[] = "`$col` = ?";
|
||||
}
|
||||
return [implode(',', $sets), array_values($data)];
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* 操作日志写入函数
|
||||
* 所有写类接口(add/update/delete/import/export/audit/convert/login/logout)必须调用 logAction()。
|
||||
*/
|
||||
require_once __DIR__ . '/db.php';
|
||||
|
||||
/**
|
||||
* 写入操作日志
|
||||
* @param int|null $userId 操作人ID
|
||||
* @param string|null $username 操作人账号
|
||||
* @param string $action 操作类型
|
||||
* @param string $module 操作模块
|
||||
* @param string|null $targetTable 操作对象表名
|
||||
* @param int|null $targetId 操作对象记录ID
|
||||
* @param mixed $content 操作内容(数组,自动JSON编码)
|
||||
* @return bool
|
||||
*/
|
||||
function logAction($userId, $username, $action, $module, $targetTable = null, $targetId = null, $content = null)
|
||||
{
|
||||
try {
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||
$pdo = DB::getInstance()->getPdo();
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO system_logs (user_id, username, action, module, target_table, target_id, content, ip)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
$contentJson = ($content !== null && $content !== '')
|
||||
? json_encode($content, JSON_UNESCAPED_UNICODE)
|
||||
: null;
|
||||
return $stmt->execute([$userId, $username, $action, $module, $targetTable, $targetId, $contentJson, $ip]);
|
||||
} catch (Exception $e) {
|
||||
// 日志失败不影响主流程
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于当前 Session 用户写入日志(便捷函数)
|
||||
* @param string $action
|
||||
* @param string $module
|
||||
* @param string|null $targetTable
|
||||
* @param int|null $targetId
|
||||
* @param mixed $content
|
||||
*/
|
||||
function logCurrent($action, $module, $targetTable = null, $targetId = null, $content = null)
|
||||
{
|
||||
return logAction(
|
||||
$_SESSION['user_id'] ?? null,
|
||||
$_SESSION['username'] ?? null,
|
||||
$action,
|
||||
$module,
|
||||
$targetTable,
|
||||
$targetId,
|
||||
$content
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* 统一 JSON 返回类
|
||||
* 用法:Response::success($data, $msg) / Response::error($msg, $code)
|
||||
*/
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* 成功返回
|
||||
* @param mixed $data
|
||||
* @param string $msg
|
||||
*/
|
||||
public static function success($data = null, $msg = '操作成功')
|
||||
{
|
||||
self::json(['code' => 0, 'msg' => $msg, 'data' => $data]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回
|
||||
* @param string $msg
|
||||
* @param int $code 非0错误码(401未登录/403无权限/400参数错误/1业务错误)
|
||||
*/
|
||||
public static function error($msg = '操作失败', $code = 1)
|
||||
{
|
||||
self::json(['code' => $code, 'msg' => $msg]);
|
||||
}
|
||||
|
||||
private static function json($payload)
|
||||
{
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode($payload, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user