如何用PHP项目搭建简历筛选?

wen java案例 4

我来帮您设计一个基于PHP的简历筛选系统架构和实现方案。

如何用PHP项目搭建简历筛选?

系统架构设计

数据库设计

-- 简历表
CREATE TABLE `resumes` (
    `id` INT PRIMARY KEY AUTO_INCREMENT,
    `name` VARCHAR(50) NOT NULL,
    `email` VARCHAR(100),
    `phone` VARCHAR(20),
    `education` TEXT, -- JSON格式存储教育经历
    `experience` TEXT, -- JSON格式存储工作经历
    `skills` TEXT, -- JSON格式存储技能
    `file_path` VARCHAR(255),
    `content` TEXT, -- 解析后的文本内容
    `status` TINYINT DEFAULT 0, -- 0:未筛选 1:通过 2:未通过
    `score` INT DEFAULT 0,
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 关键词规则表
CREATE TABLE `keywords` (
    `id` INT PRIMARY KEY AUTO_INCREMENT,
    `category` VARCHAR(50), -- 技能/学历/经验
    `word` VARCHAR(100),
    `weight` INT DEFAULT 1, -- 权重
    `is_required` TINYINT DEFAULT 0 -- 是否必须
);

核心PHP实现

<?php
// ResumeFilter.php
class ResumeFilter {
    private $db;
    private $keywords = [];
    private $threshold = 60; // 通过阈值
    public function __construct($db) {
        $this->db = $db;
        $this->loadKeywords();
    }
    // 加载筛选规则
    private function loadKeywords() {
        $result = $this->db->query("SELECT * FROM keywords");
        while($row = $result->fetch_assoc()) {
            $this->keywords[$row['category']][] = $row;
        }
    }
    // 简历解析
    public function parseResume($file) {
        $content = '';
        $ext = pathinfo($file['name'], PATHINFO_EXTENSION);
        switch(strtolower($ext)) {
            case 'pdf':
                $content = $this->parsePDF($file['tmp_name']);
                break;
            case 'doc':
            case 'docx':
                $content = $this->parseWord($file['tmp_name']);
                break;
            case 'txt':
                $content = file_get_contents($file['tmp_name']);
                break;
        }
        return $this->extractInfo($content);
    }
    // 解析PDF (需要安装pdfparser)
    private function parsePDF($filePath) {
        require_once 'vendor/autoload.php';
        $parser = new \Smalot\PdfParser\Parser();
        $pdf = $parser->parseFile($filePath);
        return $pdf->getText();
    }
    // 解析Word文档
    private function parseWord($filePath) {
        $phpWord = \PhpOffice\PhpWord\IOFactory::load($filePath);
        $content = '';
        foreach($phpWord->getSections() as $section) {
            foreach($section->getElements() as $element) {
                if(method_exists($element, 'getText')) {
                    $content .= $element->getText();
                }
            }
        }
        return $content;
    }
    // 提取关键信息
    private function extractInfo($content) {
        $info = [
            'skills' => [],
            'education' => [],
            'experience' => [],
            'content' => $content
        ];
        // 提取技能
        $skillKeywords = ['PHP', 'JavaScript', 'Python', 'Java', 'MySQL', 'Linux', 'Docker'];
        foreach($skillKeywords as $skill) {
            if(stripos($content, $skill) !== false) {
                $info['skills'][] = $skill;
            }
        }
        // 提取教育信息
        preg_match('/(?:本科|硕士|博士|大专)?\s*(?:毕业于)?\s*([\x{4e00}-\x{9fa5}]+(?:大学|学院))/u', $content, $matches);
        if(!empty($matches[1])) {
            $info['education'][] = $matches[1];
        }
        // 提取工作年限
        preg_match('/(\d+)\s*年\s*(?:工作经验|从业经验|工作经历)/', $content, $matches);
        if(!empty($matches[1])) {
            $info['experience'][] = $matches[1] . '年';
        }
        return $info;
    }
    // 评分算法
    public function calculateScore($resumeInfo) {
        $score = 0;
        $maxScore = 0;
        foreach($this->keywords as $category => $keywords) {
            foreach($keywords as $keyword) {
                $maxScore += $keyword['weight'];
                if($this->matchKeyword($resumeInfo['content'], $keyword['word'])) {
                    $score += $keyword['weight'];
                }
            }
        }
        return $maxScore > 0 ? round(($score / $maxScore) * 100) : 0;
    }
    // 关键词匹配
    private function matchKeyword($content, $word) {
        return stripos($content, $word) !== false;
    }
    // 执行筛选
    public function filter($resumeId) {
        $resume = $this->db->query("SELECT * FROM resumes WHERE id = $resumeId")->fetch_assoc();
        if(!$resume) return false;
        $resumeInfo = json_decode($resume['content'], true);
        $score = $this->calculateScore($resumeInfo);
        $status = $score >= $this->threshold ? 1 : 2;
        $this->db->query("UPDATE resumes SET score = $score, status = $status WHERE id = $resumeId");
        return [
            'id' => $resumeId,
            'score' => $score,
            'status' => $status,
            'passed' => $status == 1
        ];
    }
    // 批量筛选
    public function batchFilter($limit = 100) {
        $results = [];
        $resumes = $this->db->query("SELECT id FROM resumes WHERE status = 0 LIMIT $limit");
        while($resume = $resumes->fetch_assoc()) {
            $results[] = $this->filter($resume['id']);
        }
        return $results;
    }
}

RESTful API接口

<?php
// api/resume.php
class ResumeAPI {
    private $filter;
    public function __construct($filter) {
        $this->filter = $filter;
    }
    // 上传简历
    public function upload() {
        if(!isset($_FILES['resume'])) {
            return ['error' => 'No file uploaded'];
        }
        $file = $_FILES['resume'];
        $allowedTypes = ['pdf', 'doc', 'docx', 'txt'];
        $ext = pathinfo($file['name'], PATHINFO_EXTENSION);
        if(!in_array($ext, $allowedTypes)) {
            return ['error' => 'Invalid file type'];
        }
        // 解析简历
        $resumeInfo = $this->filter->parseResume($file);
        // 保存到数据库
        $name = $resumeInfo['name'] ?? pathinfo($file['name'], PATHINFO_FILENAME);
        $content = json_encode($resumeInfo);
        $stmt = $this->filter->db->prepare("INSERT INTO resumes (name, content, file_path) VALUES (?, ?, ?)");
        $stmt->bind_param("sss", $name, $content, $file['name']);
        $stmt->execute();
        return ['id' => $stmt->insert_id, 'message' => 'Upload successful'];
    }
    // 查看筛选结果
    public function status($id) {
        $resume = $this->filter->db->query("SELECT * FROM resumes WHERE id = $id")->fetch_assoc();
        if(!$resume) {
            return ['error' => 'Resume not found'];
        }
        return [
            'id' => $resume['id'],
            'name' => $resume['name'],
            'score' => $resume['score'],
            'status' => $resume['status'] == 1 ? '通过' : ($resume['status'] == 2 ? '未通过' : '待筛选'),
            'created_at' => $resume['created_at']
        ];
    }
}

前端展示示例

<!-- upload.html -->
<!DOCTYPE html>
<html>
<head>简历筛选系统</title>
    <style>
        .container { max-width: 800px; margin: 0 auto; padding: 20px; }
        .result { margin-top: 20px; border: 1px solid #ddd; padding: 10px; }
        .passed { border-color: green; background: #f0fff0; }
        .failed { border-color: red; background: #fff0f0; }
    </style>
</head>
<body>
    <div class="container">
        <h1>简历筛选系统</h1>
        <form action="api/upload.php" method="post" enctype="multipart/form-data">
            <input type="file" name="resume" accept=".pdf,.doc,.docx,.txt" required>
            <button type="submit">上传并筛选</button>
        </form>
        <div id="result"></div>
    </div>
    <script>
        document.querySelector('form').addEventListener('submit', function(e) {
            e.preventDefault();
            const formData = new FormData(this);
            fetch('/api/resume.php?action=upload', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                if(data.error) {
                    alert(data.error);
                    return;
                }
                // 轮询筛选结果
                checkStatus(data.id);
            });
        });
        function checkStatus(id) {
            fetch(`/api/resume.php?action=status&id=${id}`)
            .then(response => response.json())
            .then(data => {
                const resultDiv = document.getElementById('result');
                resultDiv.innerHTML = `
                    <div class="result ${data.status === '通过' ? 'passed' : 'failed'}">
                        <h3>${data.name}</h3>
                        <p>得分:${data.score}</p>
                        <p>状态:${data.status}</p>
                        <p>上传时间:${data.created_at}</p>
                    </div>
                `;
            });
        }
    </script>
</body>
</html>

配置与运行

composer.json:

{
    "require": {
        "smalot/pdfparser": "^2.0",
        "phpoffice/phpword": "^0.18"
    }
}

启动命令:

# 安装依赖
composer install
# 启动服务 (PHP内置服务器)
php -S localhost:8000

核心特性

  1. 多格式支持: PDF、Word、TXT文件解析
  2. 智能评分: 基于关键词权重计算
  3. 规则可配置: 通过数据库管理筛选规则
  4. 批量处理: 支持大量简历同时筛选
  5. API接口: 便于与其他系统集成

这个系统可以根据您的具体需求进行扩展,比如增加机器学习模型、添加更多解析规则、优化评分算法等。

抱歉,评论功能暂时关闭!