diff --git a/.example.env b/.example.env new file mode 100644 index 0000000..c457fe5 --- /dev/null +++ b/.example.env @@ -0,0 +1,11 @@ +APP_DEBUG = true + +DB_TYPE = mysql +DB_HOST = 127.0.0.1 +DB_NAME = test +DB_USER = username +DB_PASS = password +DB_PORT = 3306 +DB_CHARSET = utf8 + +DEFAULT_LANG = zh-cn diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5054a67 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.log +.env +composer.phar +composer.lock +.DS_Store +Thumbs.db +/.idea +/.vscode +/vendor +/.settings +/.buildpath +/.project \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..36f7b6f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,42 @@ +sudo: false + +language: php + +branches: + only: + - stable + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - composer self-update + +install: + - composer install --no-dev --no-interaction --ignore-platform-reqs + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip . + - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0" + - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0" + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip . + +script: + - php think unit + +deploy: + provider: releases + api_key: + secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw= + file: + - ThinkPHP_Core.zip + - ThinkPHP_Full.zip + skip_cleanup: true + on: + tags: true diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..8d94897 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2025 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index c87caa4..0be2cb2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,77 @@ -# work_dhd_back_end - +![](https://www.thinkphp.cn/uploads/images/20230630/300c856765af4d8ae758c503185f8739.png) + +ThinkPHP 8 +=============== + +## 特性 + +* 基于PHP`8.0+`重构 +* 升级`PSR`依赖 +* 依赖`think-orm`3.0+版本 +* 全新的`think-dumper`服务,支持远程调试 +* 支持`6.0`/`6.1`无缝升级 + +> ThinkPHP8的运行环境要求PHP8.0+ + +现在开始,你可以使用官方提供的[ThinkChat](https://chat.topthink.com/),让你在学习ThinkPHP的旅途中享受私人AI助理服务! + +![](https://www.topthink.com/uploads/assistant/20230630/4d1a3f0ad2958b49bb8189b7ef824cb0.png) + +ThinkPHP生态服务由[顶想云](https://www.topthink.com)(TOPThink Cloud)提供,为生态提供专业的开发者服务和价值之选。 + +## 文档 + +[完全开发手册](https://doc.thinkphp.cn) + + +## 赞助 + +全新的[赞助计划](https://www.thinkphp.cn/sponsor)可以让你通过我们的网站、手册、欢迎页及GIT仓库获得巨大曝光,同时提升企业的品牌声誉,也更好保障ThinkPHP的可持续发展。 + +[![](https://www.thinkphp.cn/sponsor/special.svg)](https://www.thinkphp.cn/sponsor/special) + +[![](https://www.thinkphp.cn/sponsor.svg)](https://www.thinkphp.cn/sponsor) + +## 安装 + +~~~ +composer create-project topthink/think tp +~~~ + +启动服务 + +~~~ +cd tp +php think run +~~~ + +然后就可以在浏览器中访问 + +~~~ +http://localhost:8000 +~~~ + +如果需要更新框架使用 +~~~ +composer update topthink/framework +~~~ + +## 命名规范 + +`ThinkPHP`遵循PSR-2命名规范和PSR-4自动加载规范。 + +## 参与开发 + +直接提交PR或者Issue即可 + +## 版权信息 + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 + +本项目包含的第三方源码和二进制文件之版权信息另行标注。 + +版权所有Copyright © 2006-2024 by ThinkPHP (http://thinkphp.cn) All rights reserved。 + +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +更多细节参阅 [LICENSE.txt](LICENSE.txt) diff --git a/app/.htaccess b/app/.htaccess new file mode 100644 index 0000000..3418e55 --- /dev/null +++ b/app/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/app/AppService.php b/app/AppService.php new file mode 100644 index 0000000..96556e8 --- /dev/null +++ b/app/AppService.php @@ -0,0 +1,22 @@ +app = $app; + $this->request = $this->app->request; + + // 控制器初始化 + $this->initialize(); + } + + // 初始化 + protected function initialize() + {} + + /** + * 验证数据 + * @access protected + * @param array $data 数据 + * @param string|array $validate 验证器名或者验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @return array|string|true + * @throws ValidateException + */ + protected function validate(array $data, string|array $validate, array $message = [], bool $batch = false) + { + if (is_array($validate)) { + $v = new Validate(); + $v->rule($validate); + } else { + if (strpos($validate, '.')) { + // 支持场景 + [$validate, $scene] = explode('.', $validate); + } + $class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate); + $v = new $class(); + if (!empty($scene)) { + $v->scene($scene); + } + } + + $v->message($message); + + // 是否批量验证 + if ($batch || $this->batchValidate) { + $v->batch(true); + } + + return $v->failException(true)->check($data); + } + +} diff --git a/app/ExceptionHandle.php b/app/ExceptionHandle.php new file mode 100644 index 0000000..453d126 --- /dev/null +++ b/app/ExceptionHandle.php @@ -0,0 +1,58 @@ +*{ padding: 0; margin: 0; }'; + } + + public function hello($name = 'ThinkPHP8') + { + return 'hello,' . $name; + } +} diff --git a/app/controller/MatchController.php b/app/controller/MatchController.php new file mode 100644 index 0000000..15fa6c9 --- /dev/null +++ b/app/controller/MatchController.php @@ -0,0 +1,66 @@ +request->param(); + $position = $input['position'] ?? []; + $resume = $input['resume'] ?? []; + + // 如果是JSON请求,尝试从JSON中获取 + if (empty($position) && empty($resume)) { + $jsonData = json_decode($this->request->getContent(), true); + if (is_array($jsonData)) { + $position = $jsonData['position'] ?? []; + $resume = $jsonData['resume'] ?? []; + } + } + + // 参数验证 + if (empty($position) || empty($resume)) { + return json([ + 'code' => 400, + 'msg' => '参数错误:岗位信息和简历信息不能为空', + 'data' => null + ]); + } + + // 计算匹配度 + $matchService = new MatchService(); + $score = $matchService->calculateMatchScore($position, $resume); + + return json([ + 'code' => 200, + 'msg' => '计算成功', + 'data' => [ + 'match_score' => $score + ] + ]); + + } catch (\Exception $e) { + return json([ + 'code' => 500, + 'msg' => '计算失败:' . $e->getMessage(), + 'data' => null + ]); + } + } +} + diff --git a/app/event.php b/app/event.php new file mode 100644 index 0000000..e9851bb --- /dev/null +++ b/app/event.php @@ -0,0 +1,17 @@ + [ + ], + + 'listen' => [ + 'AppInit' => [], + 'HttpRun' => [], + 'HttpEnd' => [], + 'LogLevel' => [], + 'LogWrite' => [], + ], + + 'subscribe' => [ + ], +]; diff --git a/app/middleware.php b/app/middleware.php new file mode 100644 index 0000000..d2c3fda --- /dev/null +++ b/app/middleware.php @@ -0,0 +1,10 @@ + Request::class, + 'think\exception\Handle' => ExceptionHandle::class, +]; diff --git a/app/service.php b/app/service.php new file mode 100644 index 0000000..db1ee6a --- /dev/null +++ b/app/service.php @@ -0,0 +1,9 @@ +checkHardRequirements($position, $resume); + + if (!$hardCheck['passed']) { + return 0; // 硬性条件不满足,直接返回0分 + } + + // 第二步:计算软性条件匹配度(100分制) + $softCheck = $this->calculateSoftRequirements($position, $resume); + + return $softCheck['score']; + } + + /** + * 检查硬性条件(一票否决机制) + * @param array $position 岗位信息 + * @param array $resume 简历信息 + * @return array + */ + private function checkHardRequirements(array $position, array $resume): array + { + $positionRequire = $position['position_require'] ?? []; + $result = [ + 'passed' => true, + 'rejection_reasons' => [], + 'details' => [] + ]; + + // 1. 学历要求(硬性) + if (!empty($positionRequire['学历要求'])) { + $educationCheck = $this->checkEducation($positionRequire['学历要求'], $resume); + $result['details']['学历要求'] = $educationCheck; + if (!$educationCheck['passed']) { + $result['passed'] = false; + $result['rejection_reasons'][] = $educationCheck['reason']; + } + } + + // 2. 学位要求(硬性) + if (!empty($positionRequire['学位要求'])) { + $degreeCheck = $this->checkDegree($positionRequire['学位要求'], $resume); + $result['details']['学位要求'] = $degreeCheck; + if (!$degreeCheck['passed']) { + $result['passed'] = false; + $result['rejection_reasons'][] = $degreeCheck['reason']; + } + } + + // 3. 年龄要求(硬性) + if (!empty($positionRequire['年龄要求'])) { + $ageCheck = $this->checkAge($positionRequire['年龄要求'], $resume); + $result['details']['年龄要求'] = $ageCheck; + if (!$ageCheck['passed']) { + $result['passed'] = false; + $result['rejection_reasons'][] = $ageCheck['reason']; + } + } + + // 4. 专业要求(硬性) + if (!empty($positionRequire['专业(学科)类别'])) { + $majorCheck = $this->checkMajor($positionRequire['专业(学科)类别'], $resume); + $result['details']['专业要求'] = $majorCheck; + if (!$majorCheck['passed']) { + $result['passed'] = false; + $result['rejection_reasons'][] = $majorCheck['reason']; + } + } + + // 5. 性别要求(硬性,如果明确要求) + $otherConditions = $positionRequire['其他资格条件'] ?? ''; + if (preg_match('/适合(男|女)性/u', $otherConditions, $matches)) { + $genderCheck = $this->checkGender($matches[1], $resume); + $result['details']['性别要求'] = $genderCheck; + if (!$genderCheck['passed']) { + $result['passed'] = false; + $result['rejection_reasons'][] = $genderCheck['reason']; + } + } + + return $result; + } + + /** + * 计算软性条件匹配度(100分制) + * @param array $position 岗位信息 + * @param array $resume 简历信息 + * @return array + */ + private function calculateSoftRequirements(array $position, array $resume): array + { + $positionRequire = $position['position_require'] ?? []; + $score = 0; + $details = []; + $maxScore = 100; + + // 1. 专业匹配度(40分)- 即使专业类别符合,也可以根据专业相关性打分 + $majorScore = $this->scoreMajorMatch($positionRequire['专业(学科)类别'] ?? '', $resume); + $score += $majorScore['score']; + $details['专业匹配度'] = $majorScore; + + // 2. 学历层次匹配度(20分)- 超过要求的学历可以加分 + $educationScore = $this->scoreEducationLevel($positionRequire['学历要求'] ?? '', $resume); + $score += $educationScore['score']; + $details['学历层次匹配度'] = $educationScore; + + // 3. 专业资格条件(20分)- 竞赛获奖等 + $qualificationScore = $this->scoreQualification($positionRequire['专业资格条件'] ?? '', $resume); + $score += $qualificationScore['score']; + $details['专业资格条件'] = $qualificationScore; + + // 4. 基层工作经历(10分) + $workExpScore = $this->scoreWorkExperience($resume); + $score += $workExpScore['score']; + $details['基层工作经历'] = $workExpScore; + + // 5. 其他条件匹配(10分)- 如政治面貌、特殊身份等 + $otherScore = $this->scoreOtherConditions($positionRequire['其他资格条件'] ?? '', $resume); + $score += $otherScore['score']; + $details['其他条件'] = $otherScore; + + return [ + 'score' => min(100, $score), + 'max_score' => $maxScore, + 'details' => $details + ]; + } + + // ==================== 硬性条件检查方法 ==================== + + /** + * 检查学历要求(硬性) + */ + private function checkEducation(string $requirement, array $resume): array + { + $educations = $resume['education'] ?? []; + if (empty($educations)) { + return [ + 'passed' => false, + 'reason' => '未提供学历信息', + 'required' => $requirement, + 'actual' => null + ]; + } + + $highestEducation = $this->getHighestEducation($educations); + if (empty($highestEducation)) { + return [ + 'passed' => false, + 'reason' => '无法识别最高学历', + 'required' => $requirement, + 'actual' => null + ]; + } + + $educationLevel = $highestEducation['education_level'] ?? ''; + $educationLevels = [ + '普通本科' => 3, + '硕士研究生' => 4, + '博士研究生' => 5, + ]; + + $requireLevel = $this->parseEducationRequire($requirement); + $actualLevel = $educationLevels[$educationLevel] ?? 0; + + if ($actualLevel >= $requireLevel) { + return [ + 'passed' => true, + 'required' => $requirement, + 'actual' => $educationLevel + ]; + } + + return [ + 'passed' => false, + 'reason' => "学历不符合要求:需要{$requirement},实际为{$educationLevel}", + 'required' => $requirement, + 'actual' => $educationLevel + ]; + } + + /** + * 检查学位要求(硬性) + */ + private function checkDegree(string $requirement, array $resume): array + { + $educations = $resume['education'] ?? []; + if (empty($educations)) { + return [ + 'passed' => false, + 'reason' => '未提供学位信息', + 'required' => $requirement, + 'actual' => null + ]; + } + + $highestEducation = $this->getHighestEducation($educations); + $degree = $highestEducation['degree'] ?? ''; + + $degreeLevels = [ + '学士' => 1, + '硕士' => 2, + '博士' => 3, + ]; + + $requireLevel = $this->parseDegreeRequire($requirement); + $actualLevel = $degreeLevels[$degree] ?? 0; + + if ($actualLevel >= $requireLevel) { + return [ + 'passed' => true, + 'required' => $requirement, + 'actual' => $degree + ]; + } + + return [ + 'passed' => false, + 'reason' => "学位不符合要求:需要{$requirement},实际为{$degree}", + 'required' => $requirement, + 'actual' => $degree + ]; + } + + /** + * 检查年龄要求(硬性) + */ + private function checkAge(string $requirement, array $resume): array + { + $birthDate = $resume['birth_date'] ?? ''; + if (empty($birthDate)) { + return [ + 'passed' => false, + 'reason' => '未提供出生日期', + 'required' => $requirement, + 'actual' => null + ]; + } + + $age = $this->calculateAge($birthDate); + + if (preg_match('/(\d+)周岁以上.*?(\d+)周岁以下/u', $requirement, $matches)) { + $minAge = (int)$matches[1]; + $maxAge = (int)$matches[2]; + + if ($age >= $minAge && $age <= $maxAge) { + return [ + 'passed' => true, + 'required' => $requirement, + 'actual' => "{$age}岁" + ]; + } + + return [ + 'passed' => false, + 'reason' => "年龄不符合要求:需要{$minAge}-{$maxAge}岁,实际为{$age}岁", + 'required' => $requirement, + 'actual' => "{$age}岁" + ]; + } + + return [ + 'passed' => false, + 'reason' => '无法解析年龄要求格式', + 'required' => $requirement, + 'actual' => "{$age}岁" + ]; + } + + /** + * 检查专业要求(硬性) + */ + private function checkMajor(string $requirement, array $resume): array + { + $educations = $resume['education'] ?? []; + if (empty($educations)) { + return [ + 'passed' => false, + 'reason' => '未提供专业信息', + 'required' => $requirement, + 'actual' => null + ]; + } + + $matchedMajors = []; + foreach ($educations as $education) { + $majorName = $education['majors_name'] ?? ''; + if ($this->isMajorCategoryMatch($majorName, $requirement)) { + $matchedMajors[] = $majorName; + } + } + + if (!empty($matchedMajors)) { + return [ + 'passed' => true, + 'required' => $requirement, + 'actual' => implode('、', $matchedMajors) + ]; + } + + $actualMajors = array_map(function($edu) { + return $edu['majors_name'] ?? ''; + }, $educations); + $actualMajors = array_filter($actualMajors); + + return [ + 'passed' => false, + 'reason' => "专业不符合要求:需要{$requirement},实际为" . implode('、', $actualMajors), + 'required' => $requirement, + 'actual' => implode('、', $actualMajors) + ]; + } + + /** + * 检查性别要求(硬性) + */ + private function checkGender(string $requireGender, array $resume): array + { + $gender = $resume['gender'] ?? ''; + if ($gender === $requireGender) { + return [ + 'passed' => true, + 'required' => $requireGender, + 'actual' => $gender + ]; + } + + return [ + 'passed' => false, + 'reason' => "性别不符合要求:需要{$requireGender},实际为{$gender}", + 'required' => $requireGender, + 'actual' => $gender + ]; + } + + // ==================== 软性条件评分方法 ==================== + + /** + * 专业匹配度评分(40分) + */ + private function scoreMajorMatch(string $requirement, array $resume): array + { + if (empty($requirement)) { + return ['score' => 40, 'reason' => '无专业要求']; + } + + $educations = $resume['education'] ?? []; + if (empty($educations)) { + return ['score' => 0, 'reason' => '无专业信息']; + } + + $maxScore = 0; + foreach ($educations as $education) { + $majorName = $education['majors_name'] ?? ''; + $score = $this->calculateMajorRelevanceScore($majorName, $requirement); + $maxScore = max($maxScore, $score); + } + + return [ + 'score' => min(40, $maxScore), + 'max_score' => 40, + 'reason' => $maxScore >= 40 ? '专业高度匹配' : '专业部分匹配' + ]; + } + + /** + * 学历层次匹配度评分(20分) + */ + private function scoreEducationLevel(string $requirement, array $resume): array + { + $educations = $resume['education'] ?? []; + if (empty($educations)) { + return ['score' => 0, 'reason' => '无学历信息']; + } + + $highestEducation = $this->getHighestEducation($educations); + $educationLevel = $highestEducation['education_level'] ?? ''; + + $educationLevels = [ + '普通本科' => 3, + '硕士研究生' => 4, + '博士研究生' => 5, + ]; + + $requireLevel = $this->parseEducationRequire($requirement); + $actualLevel = $educationLevels[$educationLevel] ?? 0; + + // 刚好满足要求:15分,超过一级:20分,超过两级及以上:20分 + if ($actualLevel == $requireLevel) { + return ['score' => 15, 'max_score' => 20, 'reason' => '刚好满足要求']; + } elseif ($actualLevel > $requireLevel) { + $exceed = $actualLevel - $requireLevel; + return [ + 'score' => 15 + min(5, $exceed * 5), + 'max_score' => 20, + 'reason' => "超过要求{$exceed}级" + ]; + } + + return ['score' => 0, 'reason' => '未达到要求']; + } + + /** + * 专业资格条件评分(20分) + */ + private function scoreQualification(string $requirement, array $resume): array + { + if (empty($requirement)) { + return [ + 'score' => 20, + 'max_score' => 20, + 'reason' => '无专业资格要求' + ]; + } + + // 这里可以根据简历中的证书、获奖等信息来评分 + // 由于当前简历数据结构中没有这些字段,暂时返回基础分数 + // 实际应用中需要扩展简历数据结构 + + return [ + 'score' => 0, + 'max_score' => 20, + 'reason' => '未提供专业资格证明材料(需扩展简历数据结构)' + ]; + } + + /** + * 基层工作经历评分(10分) + */ + private function scoreWorkExperience(array $resume): array + { + $workExperience = $resume['work_experience'] ?? ''; + + if (empty($workExperience) || strpos($workExperience, '无') !== false) { + return [ + 'score' => 0, + 'max_score' => 10, + 'reason' => '无基层工作经历' + ]; + } + + // 可以根据工作年限进一步细化评分 + return [ + 'score' => 10, + 'max_score' => 10, + 'reason' => '有基层工作经历' + ]; + } + + /** + * 其他条件评分(10分) + */ + private function scoreOtherConditions(string $requirement, array $resume): array + { + if (empty($requirement)) { + return ['score' => 10, 'reason' => '无其他条件要求']; + } + + $score = 0; + $maxScore = 10; + + // 政治面貌等可以根据岗位要求评分 + // 当前暂不实现,返回基础分数 + + return [ + 'score' => 10, + 'max_score' => $maxScore, + 'reason' => '其他条件匹配(需根据具体岗位要求细化)' + ]; + } + + // ==================== 辅助方法 ==================== + + /** + * 计算年龄 + */ + private function calculateAge(string $birthDate): int + { + $birthTimestamp = strtotime($birthDate); + $currentTimestamp = time(); + $age = date('Y', $currentTimestamp) - date('Y', $birthTimestamp); + if (date('md', $currentTimestamp) < date('md', $birthTimestamp)) { + $age--; + } + return $age; + } + + /** + * 获取最高学历 + */ + private function getHighestEducation(array $educations): ?array + { + if (empty($educations)) { + return null; + } + + $educationLevels = [ + '普通本科' => 3, + '硕士研究生' => 4, + '博士研究生' => 5, + ]; + + $highest = null; + $highestLevel = 0; + + foreach ($educations as $education) { + $level = $educationLevels[$education['education_level'] ?? ''] ?? 0; + if ($level > $highestLevel) { + $highestLevel = $level; + $highest = $education; + } + } + + return $highest; + } + + /** + * 解析学历要求 + */ + private function parseEducationRequire(string $require): int + { + if (strpos($require, '本科') !== false) { + return 3; + } + if (strpos($require, '硕士') !== false) { + return 4; + } + if (strpos($require, '博士') !== false) { + return 5; + } + return 0; + } + + /** + * 解析学位要求 + */ + private function parseDegreeRequire(string $require): int + { + if (strpos($require, '学士') !== false) { + return 1; + } + if (strpos($require, '硕士') !== false) { + return 2; + } + if (strpos($require, '博士') !== false) { + return 3; + } + return 0; + } + + /** + * 判断专业类别是否匹配(硬性检查) + */ + private function isMajorCategoryMatch(string $majorName, string $majorRequire): bool + { + if (empty($majorName) || empty($majorRequire)) { + return false; + } + + $requireCategories = explode(',', $majorRequire); + + foreach ($requireCategories as $category) { + $category = trim($category); + + // 计算机科学与技术类 + if (strpos($category, '计算机') !== false) { + $computerKeywords = ['计算机', '软件', '网络', '信息', '数据', '人工智能', '大数据', '网络安全', '信息安全']; + foreach ($computerKeywords as $keyword) { + if (strpos($majorName, $keyword) !== false) { + return true; + } + } + } + + // 电气、电子及自动化类 + if (strpos($category, '电气') !== false || strpos($category, '电子') !== false || strpos($category, '自动化') !== false) { + $electronicsKeywords = ['电气', '电子', '自动化', '通信', '电信', '电子信息']; + foreach ($electronicsKeywords as $keyword) { + if (strpos($majorName, $keyword) !== false) { + return true; + } + } + } + } + + return false; + } + + /** + * 计算专业相关性分数(软性评分) + */ + private function calculateMajorRelevanceScore(string $majorName, string $majorRequire): int + { + if ($this->isMajorCategoryMatch($majorName, $majorRequire)) { + return 40; // 完全匹配 + } + + // 可以根据专业相似度进一步细化评分 + // 这里简单返回0分 + return 0; + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..22d37b8 --- /dev/null +++ b/composer.json @@ -0,0 +1,49 @@ +{ + "name": "topthink/think", + "description": "the new thinkphp framework", + "type": "project", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "https://www.thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "require": { + "php": ">=8.0.0", + "topthink/framework": "^8.0", + "topthink/think-orm": "^3.0|^4.0", + "topthink/think-filesystem": "^2.0|^3.0" + }, + "require-dev": { + "topthink/think-dumper": "^1.0", + "topthink/think-trace": "^2.0" + }, + "autoload": { + "psr-4": { + "app\\": "app" + }, + "psr-0": { + "": "extend/" + } + }, + "config": { + "preferred-install": "dist" + }, + "scripts": { + "post-autoload-dump": [ + "@php think service:discover", + "@php think vendor:publish" + ] + } +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..63285d2 --- /dev/null +++ b/config/app.php @@ -0,0 +1,30 @@ + '', + // 是否启用路由 + 'with_route' => true, + // 默认应用 + 'default_app' => 'index', + // 默认时区 + 'default_timezone' => 'Asia/Shanghai', + + // 应用映射(自动多应用模式有效) + 'app_map' => [], + // 域名绑定(自动多应用模式有效) + 'domain_bind' => [], + // 禁止URL访问的应用列表(自动多应用模式有效) + 'deny_app_list' => [], + + // 异常页面的模板文件 + 'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl', + + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 0000000..6b72dc8 --- /dev/null +++ b/config/cache.php @@ -0,0 +1,29 @@ + 'file', + + // 缓存连接方式配置 + 'stores' => [ + 'file' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + 'path' => '', + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + // 缓存标签前缀 + 'tag_prefix' => 'tag:', + // 序列化机制 例如 ['serialize', 'unserialize'] + 'serialize' => [], + ], + // 更多的缓存连接 + ], +]; diff --git a/config/console.php b/config/console.php new file mode 100644 index 0000000..a818a98 --- /dev/null +++ b/config/console.php @@ -0,0 +1,9 @@ + [ + ], +]; diff --git a/config/cookie.php b/config/cookie.php new file mode 100644 index 0000000..d3b3aab --- /dev/null +++ b/config/cookie.php @@ -0,0 +1,20 @@ + 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => false, + // 是否使用 setcookie + 'setcookie' => true, + // samesite 设置,支持 'strict' 'lax' + 'samesite' => '', +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..9d3f0d0 --- /dev/null +++ b/config/database.php @@ -0,0 +1,63 @@ + env('DB_DRIVER', 'mysql'), + + // 自定义时间查询规则 + 'time_query_rule' => [], + + // 自动写入时间戳字段 + // true为自动识别类型 false关闭 + // 字符串则明确指定时间字段类型 支持 int timestamp datetime date + 'auto_timestamp' => true, + + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + + // 时间字段配置 配置格式:create_time,update_time + 'datetime_field' => '', + + // 数据库连接配置信息 + 'connections' => [ + 'mysql' => [ + // 数据库类型 + 'type' => env('DB_TYPE', 'mysql'), + // 服务器地址 + 'hostname' => env('DB_HOST', '127.0.0.1'), + // 数据库名 + 'database' => env('DB_NAME', ''), + // 用户名 + 'username' => env('DB_USER', 'root'), + // 密码 + 'password' => env('DB_PASS', ''), + // 端口 + 'hostport' => env('DB_PORT', '3306'), + // 数据库连接参数 + 'params' => [], + // 数据库编码 + 'charset' => env('DB_CHARSET', 'utf8mb4'), + // 数据库表前缀 + 'prefix' => env('DB_PREFIX', ''), + + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 是否需要断线重连 + 'break_reconnect' => false, + // 监听SQL + 'trigger_sql' => env('APP_DEBUG', true), + // 开启字段缓存 + 'fields_cache' => false, + ], + + // 更多的数据库配置信息 + ], +]; diff --git a/config/filesystem.php b/config/filesystem.php new file mode 100644 index 0000000..582a8f8 --- /dev/null +++ b/config/filesystem.php @@ -0,0 +1,24 @@ + 'local', + // 磁盘列表 + 'disks' => [ + 'local' => [ + 'type' => 'local', + 'root' => app()->getRuntimePath() . 'storage', + ], + 'public' => [ + // 磁盘类型 + 'type' => 'local', + // 磁盘路径 + 'root' => app()->getRootPath() . 'public/storage', + // 磁盘路径对应的外部URL路径 + 'url' => '/storage', + // 可见性 + 'visibility' => 'public', + ], + // 更多的磁盘配置信息 + ], +]; diff --git a/config/lang.php b/config/lang.php new file mode 100644 index 0000000..ccad14a --- /dev/null +++ b/config/lang.php @@ -0,0 +1,29 @@ + env('DEFAULT_LANG', 'zh-cn'), + // 自动侦测浏览器语言 + 'auto_detect_browser' => true, + // 允许的语言列表 + 'allow_lang_list' => [], + // 多语言自动侦测变量名 + 'detect_var' => 'lang', + // 是否使用Cookie记录 + 'use_cookie' => true, + // 多语言cookie变量 + 'cookie_var' => 'think_lang', + // 多语言header变量 + 'header_var' => 'think-lang', + // 扩展语言包 + 'extend_list' => [], + // Accept-Language转义为对应语言包名称 + 'accept_language' => [ + 'zh-hans-cn' => 'zh-cn', + ], + // 是否支持语言分组 + 'allow_group' => false, +]; diff --git a/config/log.php b/config/log.php new file mode 100644 index 0000000..0d406f8 --- /dev/null +++ b/config/log.php @@ -0,0 +1,45 @@ + 'file', + // 日志记录级别 + 'level' => [], + // 日志类型记录的通道 ['error'=>'email',...] + 'type_channel' => [], + // 关闭全局日志写入 + 'close' => false, + // 全局日志处理 支持闭包 + 'processor' => null, + + // 日志通道列表 + 'channels' => [ + 'file' => [ + // 日志记录方式 + 'type' => 'File', + // 日志保存目录 + 'path' => '', + // 单文件日志写入 + 'single' => false, + // 独立日志级别 + 'apart_level' => [], + // 最大日志文件数量 + 'max_files' => 0, + // 使用JSON格式记录 + 'json' => false, + // 日志处理 + 'processor' => null, + // 关闭通道日志写入 + 'close' => false, + // 日志输出格式化 + 'format' => '[%s][%s] %s', + // 是否实时写入 + 'realtime_write' => false, + ], + // 其它日志通道配置 + ], + +]; diff --git a/config/middleware.php b/config/middleware.php new file mode 100644 index 0000000..7e1972f --- /dev/null +++ b/config/middleware.php @@ -0,0 +1,8 @@ + [], + // 优先级设置,此数组中的中间件会按照数组中的顺序优先执行 + 'priority' => [], +]; diff --git a/config/route.php b/config/route.php new file mode 100644 index 0000000..729d7be --- /dev/null +++ b/config/route.php @@ -0,0 +1,55 @@ + '/', + // 是否开启路由延迟解析 + 'url_lazy_route' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 是否区分大小写 + 'url_case_sensitive' => false, + // 自动扫描子目录分组 + 'route_auto_group' => false, + // 合并路由规则 + 'route_rule_merge' => false, + // 路由是否完全匹配 + 'route_complete_match' => false, + // 去除斜杠 + 'remove_slash' => false, + // 默认的路由变量规则 + 'default_route_pattern' => '[\w\.]+', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // 访问控制器层名称 + 'controller_layer' => 'controller', + // 空控制器名 + 'empty_controller' => 'Error', + // 是否使用控制器后缀 + 'controller_suffix' => false, + // 默认模块名(开启自动多模块有效) + 'default_module' => 'index', + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 操作方法后缀 + 'action_suffix' => '', + // 非路由变量是否使用普通参数方式(用于URL生成) + 'url_common_param' => true, + // 操作方法的参数绑定方式 route get param + 'action_bind_param' => 'get', + // 请求缓存规则 true为自动规则 + 'request_cache_key' => true, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + // 请求缓存的Tag + 'request_cache_tag' => '', + // API版本header变量 + 'api_version' => 'Api-Version', +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..c1ef6e1 --- /dev/null +++ b/config/session.php @@ -0,0 +1,19 @@ + 'PHPSESSID', + // SESSION_ID的提交变量,解决flash上传跨域 + 'var_session_id' => '', + // 驱动方式 支持file cache + 'type' => 'file', + // 存储连接标识 当type使用cache的时候有效 + 'store' => null, + // 过期时间 + 'expire' => 1440, + // 前缀 + 'prefix' => '', +]; diff --git a/config/trace.php b/config/trace.php new file mode 100644 index 0000000..fad2392 --- /dev/null +++ b/config/trace.php @@ -0,0 +1,10 @@ + 'Html', + // 读取的日志通道名 + 'channel' => '', +]; diff --git a/config/view.php b/config/view.php new file mode 100644 index 0000000..01259a0 --- /dev/null +++ b/config/view.php @@ -0,0 +1,25 @@ + 'Think', + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法 + 'auto_rule' => 1, + // 模板目录名 + 'view_dir_name' => 'view', + // 模板后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', +]; diff --git a/doc/岗位简历匹配度接口说明.md b/doc/岗位简历匹配度接口说明.md new file mode 100644 index 0000000..c6b4c43 --- /dev/null +++ b/doc/岗位简历匹配度接口说明.md @@ -0,0 +1,241 @@ +# 岗位简历匹配度接口说明 + +## 接口地址 + +``` +POST /match/calculate +``` + +## 接口说明 + +该接口用于计算岗位信息和简历信息的匹配度,采用**硬性条件一票否决 + 软性条件加分**的双层匹配机制,符合公考职位匹配的实际需求。 + +**返回结果**:仅返回匹配度分数(0-100分),硬性条件不满足时返回0分。 + +## 匹配机制说明 + +### 匹配流程 + +1. **硬性条件检查(一票否决)** + - 首先检查硬性条件,如学历、学位、年龄、专业、性别等 + - 任一硬性条件不满足,直接返回0分,不进行软性条件评分 + - 所有硬性条件满足后,进入软性条件评分 + +2. **软性条件评分(100分制)** + - 在通过硬性条件筛选的基础上,对软性条件进行评分 + - 评分维度包括:专业匹配度、学历层次匹配度、专业资格条件、基层工作经历、其他条件等 + - 最终得分范围为 0-100 分 + +## 请求参数 + +### 请求方式 +- **方法**: POST +- **Content-Type**: application/json 或 application/x-www-form-urlencoded + +### 请求参数说明 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| position | object | 是 | 岗位信息对象 | +| resume | object | 是 | 简历信息对象 | + +### 请求示例 + +#### JSON格式请求 + +```json +{ + "position": { + "id": 1, + "base_info": { + "岗位名称": "人工智能与大数据侦察职位一", + "招考单位": "市级公安机关", + "招录人数": "10", + "岗位代码": "45150001" + }, + "position_info": { + "岗位名称": "人工智能与大数据侦察职位一", + "招考单位": "市级公安机关", + "招录人数": "10", + "岗位代码": "45150001" + }, + "position_require": { + "学历要求": "本科及以上", + "学位要求": "学士及以上", + "年龄要求": "18周岁以上、35周岁以下。", + "专业(学科)类别": "计算机科学与技术类,电气、电子及自动化类", + "其他资格条件": "适合男性。符合人民警察录用条件。", + "专业资格条件": "曾参加人工智能、大数据、计算机领域竞赛,获个人三等奖或团体三等奖及以上。" + } + }, + "resume": { + "user_id": 527, + "birth_date": "1995-03-01", + "gender": "男", + "work_experience": "3年基层工作年限", + "education": [ + { + "education_level": "硕士研究生", + "degree": "硕士", + "majors_name": "计算机科学与技术" + } + ] + } +} +``` + +## 响应参数 + +### 成功响应 + +```json +{ + "code": 200, + "msg": "计算成功", + "data": { + "match_score": 90 + } +} +``` + +### 错误响应 + +```json +{ + "code": 400, + "msg": "参数错误:岗位信息和简历信息不能为空", + "data": null +} +``` + +## 匹配度分数说明 + +### 分数含义 + +- **0分**:硬性条件不满足(如年龄超限、专业不匹配、学历不达标等),一票否决 +- **1-100分**:通过硬性条件筛选后的软性条件匹配度 + +### 硬性条件(一票否决) + +以下条件不满足时,直接返回0分: + +1. **学历要求**:必须达到或超过岗位要求的学历 +2. **学位要求**:必须达到或超过岗位要求的学位 +3. **年龄要求**:必须在岗位要求的年龄范围内 +4. **专业要求**:专业必须属于岗位要求的专业类别 +5. **性别要求**:如果岗位明确要求性别,必须匹配 + +### 软性条件评分规则(100分制) + +在通过硬性条件筛选后,进行软性条件评分: + +1. **专业匹配度(40分)** + - 专业完全匹配:40分 + - 专业部分匹配:根据匹配程度评分 + +2. **学历层次匹配度(20分)** + - 刚好满足要求:15分 + - 超过要求一级:20分 + - 超过要求两级及以上:20分 + +3. **专业资格条件(20分)** + - 根据专业竞赛获奖、证书等情况评分 + - 无要求时给满分 + +4. **基层工作经历(10分)** + - 有基层工作经历:10分 + - 无基层工作经历:0分 + +5. **其他条件(10分)** + - 根据政治面貌、特殊身份等其他条件评分 + - 无要求时给满分 + +## 使用示例 + +### 示例1:硬性条件不满足 + +**请求:** +```json +{ + "position": { + "position_require": { + "年龄要求": "18周岁以上、35周岁以下。", + "专业(学科)类别": "计算机科学与技术类" + } + }, + "resume": { + "birth_date": "1989-03-01", + "education": [ + { + "majors_name": "逻辑学" + } + ] + } +} +``` + +**响应:** +```json +{ + "code": 200, + "msg": "计算成功", + "data": { + "match_score": 0 + } +} +``` + +**说明**:年龄36岁超过35岁上限,或专业不匹配,硬性条件不满足,返回0分。 + +### 示例2:通过硬性条件 + +**请求:** +```json +{ + "position": { + "position_require": { + "学历要求": "本科及以上", + "年龄要求": "18周岁以上、35周岁以下。", + "专业(学科)类别": "计算机科学与技术类" + } + }, + "resume": { + "birth_date": "1995-03-01", + "work_experience": "3年基层工作年限", + "education": [ + { + "education_level": "硕士研究生", + "degree": "硕士", + "majors_name": "计算机科学与技术" + } + ] + } +} +``` + +**响应:** +```json +{ + "code": 200, + "msg": "计算成功", + "data": { + "match_score": 100 + } +} +``` + +**说明**:所有硬性条件满足,软性条件评分100分。 + +## 注意事项 + +1. **硬性条件优先**:硬性条件不满足时,直接返回0分,不进行软性条件评分 +2. **匹配度分数**:只有在通过硬性条件筛选后,才会计算匹配度分数(0-100分) +3. **数据结构**:专业资格条件评分需要简历中包含相关证书、获奖等信息,当前数据结构暂不支持,需要扩展 +4. **专业匹配**:专业匹配基于关键词匹配,实际应用中可能需要更专业的专业分类映射表 + +## 使用场景 + +该接口适用于: +- 公考职位与考生简历匹配 +- 事业单位招聘匹配 +- 其他需要硬性条件筛选的招聘场景 diff --git a/extend/.gitignore b/extend/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/extend/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..cbc7868 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,8 @@ + + Options +FollowSymlinks -Multiviews + RewriteEngine On + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] + diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..3f2817f Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..c3b7842 --- /dev/null +++ b/public/index.php @@ -0,0 +1,25 @@ + +// +---------------------------------------------------------------------- + +use think\App; + +// [ 应用入口文件 ] + +require __DIR__ . '/../vendor/autoload.php'; + +// 执行HTTP应用并响应 +$http = (new App())->http; + +$response = $http->run(); + +$response->send(); + +$http->end($response); diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..eb05362 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/public/router.php b/public/router.php new file mode 100644 index 0000000..9b39a62 --- /dev/null +++ b/public/router.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) { + return false; +} else { + $_SERVER["SCRIPT_FILENAME"] = __DIR__ . '/index.php'; + + require __DIR__ . "/index.php"; +} diff --git a/public/static/.gitignore b/public/static/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/public/static/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/route/app.php b/route/app.php new file mode 100644 index 0000000..ae03670 --- /dev/null +++ b/route/app.php @@ -0,0 +1,20 @@ + +// +---------------------------------------------------------------------- +use think\facade\Route; + +Route::get('think', function () { + return 'hello,ThinkPHP8!'; +}); + +Route::get('hello/:name', 'index/hello'); + +// 岗位简历匹配度计算接口 +Route::post('match/calculate', 'match/calculate'); diff --git a/runtime/.gitignore b/runtime/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/runtime/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/test_match_new.php b/test_match_new.php new file mode 100644 index 0000000..e077044 --- /dev/null +++ b/test_match_new.php @@ -0,0 +1,123 @@ + 1, + "base_info" => [ + "岗位名称" => "人工智能与大数据侦察职位一", + "招考单位" => "市级公安机关", + "招录人数" => "10", + "岗位代码" => "45150001" + ], + "position_info" => [ + "岗位名称" => "人工智能与大数据侦察职位一", + "招考单位" => "市级公安机关", + "招录人数" => "10", + "岗位代码" => "45150001", + "备注" => "考生按照总成绩由高到低的顺序依次在对应的职位计划表中选择具体工作岗位。", + "招录机关" => "自治区公安厅", + "职位序号" => "1", + "职位简介" => "从事公安机关人工智能研发、大数据系统建设管理、数据信息挖掘及分析研判等专业技术工作。" + ], + "position_require" => [ + "学历要求" => "本科及以上", + "学位要求" => "学士及以上", + "年龄要求" => "18周岁以上、35周岁以下。", + "回避要求" => "报考人员不得报考与市公安局领导班子成员、内设机构领导班子成员(如刑侦支队支队长、副支队长等)存在规定回避情形的职位(回避情形含夫妻关系、直系血亲关系、三代以内旁系血亲关系以及近姻亲关系)。", + "专业资格条件" => "曾参加人工智能、大数据、计算机领域竞赛,获个人三等奖或团体三等奖及以上,提供官方证明文件。", + "其他资格条件" => "适合男性。符合人民警察录用条件。单侧矫正视力低于5.0不合格。", + "专业(学科)类别" => "计算机科学与技术类,电气、电子及自动化类" + ] +]; + +// 简历信息 +$resume = [ + "user_id" => 527, + "birth_date" => "1989-03-01", + "gender" => "男", + "ethnicity" => "汉族", + "work_experience" => "无基层工作年限", + "education" => [ + [ + "id" => 1, + "education_level" => "普通本科", + "degree" => "学士", + "majors_name" => "逻辑学" + ], + [ + "id" => 2, + "education_level" => "硕士研究生", + "degree" => "硕士", + "majors_name" => "伦理学" + ] + ] +]; + +// 加载ThinkPHP框架 +$app = new think\App(); +$app->initialize(); + +// 创建匹配服务实例 +$matchService = new app\service\MatchService(); + +// 计算匹配度 +$result = $matchService->calculateMatchScore($position, $resume); + +// 输出结果 +echo "========================================\n"; +echo "岗位简历匹配度测试结果(新机制)\n"; +echo "========================================\n\n"; + +echo "【硬性条件检查结果】\n"; +echo "----------------------------------------\n"; +if ($result['qualified']) { + echo "✓ 通过硬性条件筛选\n\n"; + foreach ($result['hard_requirements']['details'] as $key => $detail) { + if (isset($detail['passed'])) { + $status = $detail['passed'] ? '✓' : '✗'; + echo "{$status} {$key}: "; + if ($detail['passed']) { + echo "通过 (要求: {$detail['required']}, 实际: {$detail['actual']})\n"; + } else { + echo "不通过 - {$detail['reason']}\n"; + } + } + } +} else { + echo "✗ 未通过硬性条件筛选\n\n"; + echo "不通过原因:\n"; + foreach ($result['rejection_reasons'] as $reason) { + echo " - {$reason}\n"; + } + echo "\n详细检查结果:\n"; + foreach ($result['hard_requirements']['details'] as $key => $detail) { + if (isset($detail['passed'])) { + $status = $detail['passed'] ? '✓' : '✗'; + echo "{$status} {$key}: "; + if ($detail['passed']) { + echo "通过 (要求: {$detail['required']}, 实际: {$detail['actual']})\n"; + } else { + echo "不通过 - {$detail['reason']}\n"; + } + } + } +} + +echo "\n【软性条件评分结果】\n"; +echo "----------------------------------------\n"; +if ($result['qualified']) { + echo "总分: {$result['score']}/{$result['soft_requirements']['max_score']}分\n\n"; + foreach ($result['soft_requirements']['details'] as $key => $detail) { + echo "{$key}: {$detail['score']}/{$detail['max_score']}分 - {$detail['reason']}\n"; + } +} else { + echo "(未通过硬性条件筛选,不进行软性条件评分)\n"; +} + +echo "\n========================================\n"; +echo "最终结果: " . ($result['qualified'] ? "✓ 符合条件,匹配度 {$result['score']}/100分" : "✗ 不符合条件") . "\n"; +echo "========================================\n"; + diff --git a/test_match_pass.php b/test_match_pass.php new file mode 100644 index 0000000..5450750 --- /dev/null +++ b/test_match_pass.php @@ -0,0 +1,82 @@ + [ + "学历要求" => "本科及以上", + "学位要求" => "学士及以上", + "年龄要求" => "18周岁以上、35周岁以下。", + "其他资格条件" => "适合男性。", + "专业(学科)类别" => "计算机科学与技术类,电气、电子及自动化类" + ] +]; + +// 简历信息(符合硬性条件) +$resume = [ + "birth_date" => "1995-03-01", // 29岁,符合年龄要求 + "gender" => "男", + "work_experience" => "3年基层工作年限", + "education" => [ + [ + "education_level" => "硕士研究生", + "degree" => "硕士", + "majors_name" => "计算机科学与技术" + ] + ] +]; + +// 加载ThinkPHP框架 +$app = new think\App(); +$app->initialize(); + +// 创建匹配服务实例 +$matchService = new app\service\MatchService(); + +// 计算匹配度 +$result = $matchService->calculateMatchScore($position, $resume); + +// 输出结果 +echo "========================================\n"; +echo "岗位简历匹配度测试(通过硬性条件案例)\n"; +echo "========================================\n\n"; + +echo "【硬性条件检查结果】\n"; +echo "----------------------------------------\n"; +if ($result['qualified']) { + echo "✓ 通过硬性条件筛选\n\n"; + foreach ($result['hard_requirements']['details'] as $key => $detail) { + if (isset($detail['passed'])) { + $status = $detail['passed'] ? '✓' : '✗'; + echo "{$status} {$key}: "; + if ($detail['passed']) { + echo "通过 (要求: {$detail['required']}, 实际: {$detail['actual']})\n"; + } else { + echo "不通过 - {$detail['reason']}\n"; + } + } + } +} else { + echo "✗ 未通过硬性条件筛选\n"; + foreach ($result['rejection_reasons'] as $reason) { + echo " - {$reason}\n"; + } +} + +echo "\n【软性条件评分结果】\n"; +echo "----------------------------------------\n"; +if ($result['qualified']) { + echo "总分: {$result['score']}/{$result['soft_requirements']['max_score']}分\n\n"; + foreach ($result['soft_requirements']['details'] as $key => $detail) { + echo "{$key}: {$detail['score']}/{$detail['max_score']}分 - {$detail['reason']}\n"; + } +} else { + echo "(未通过硬性条件筛选,不进行软性条件评分)\n"; +} + +echo "\n========================================\n"; +echo "最终结果: " . ($result['qualified'] ? "✓ 符合条件,匹配度 {$result['score']}/100分" : "✗ 不符合条件") . "\n"; +echo "========================================\n"; + diff --git a/test_match_real.php b/test_match_real.php new file mode 100644 index 0000000..df6811c --- /dev/null +++ b/test_match_real.php @@ -0,0 +1,198 @@ + 1, + "base_info" => [ + "岗位名称" => "人工智能与大数据侦察职位一", + "招考单位" => "市级公安机关", + "招录人数" => "10", + "岗位代码" => "45150001" + ], + "position_info" => [ + "岗位名称" => "人工智能与大数据侦察职位一", + "招考单位" => "市级公安机关", + "招录人数" => "10", + "岗位代码" => "45150001", + "备注" => "考生按照总成绩由高到低的顺序依次在对应的职位计划表中选择具体工作岗位。", + "招录机关" => "自治区公安厅", + "职位序号" => "1", + "职位简介" => "从事公安机关人工智能研发、大数据系统建设管理、数据信息挖掘及分析研判等专业技术工作。" + ], + "position_require" => [ + "学历要求" => "本科及以上", + "学位要求" => "学士及以上", + "年龄要求" => "18周岁以上、35周岁以下。", + "回避要求" => "报考人员不得报考与市公安局领导班子成员、内设机构领导班子成员(如刑侦支队支队长、副支队长等)存在规定回避情形的职位(回避情形含夫妻关系、直系血亲关系、三代以内旁系血亲关系以及近姻亲关系)。", + "专业资格条件" => "曾参加人工智能、大数据、计算机领域竞赛,获个人三等奖或团体三等奖及以上,提供官方证明文件。", + "其他资格条件" => "适合男性。符合人民警察录用条件。单侧矫正视力低于5.0不合格。", + "专业(学科)类别" => "计算机科学与技术类,电气、电子及自动化类" + ] +]; + +// 简历信息(从简历-岗位.md) +$resume = [ + "user_id" => 527, + "exam_city" => [ + [ + "name" => "贵州省", + "child" => [ + [ + "name" => "贵阳市", + "city_id" => 604132 + ], + [ + "name" => "六盘水市", + "city_id" => 605834 + ], + [ + "name" => "遵义市", + "city_id" => 607029 + ] + ], + "city_id" => 604131 + ] + ], + "exam_type_id" => [3, 8], + "birth_date" => "1989-03-01", + "gender" => "男", + "ethnicity" => "汉族", + "city_id" => 609311, + "origin_city_id" => 609311, + "political_status" => "群众", + "special_identity" => "无特殊身份/其他特特身份", + "is_young" => "否", + "grassroots_program" => "无", + "work_experience" => "无基层工作年限", + "exam_type_name" => ["事业单位", "公务员"], + "city_name" => "贵州,安顺,西秀", + "origin_city_name" => "贵州,安顺,西秀", + "education" => [ + [ + "id" => 1, + "user_id" => 527, + "education_level" => "普通本科", + "degree" => "学士", + "majors_id" => 1016, + "education_type" => "全日制", + "graduate_date" => "2012-07-01", + "majors_name" => "逻辑学" + ], + [ + "id" => 2, + "user_id" => 527, + "education_level" => "硕士研究生", + "degree" => "硕士", + "majors_id" => 513, + "education_type" => "全日制", + "graduate_date" => "2015-07-01", + "majors_name" => "伦理学" + ] + ] +]; + +// 加载ThinkPHP框架 +$app = new think\App(); +$app->initialize(); + +// 创建匹配服务实例 +$matchService = new app\service\MatchService(); + +// 计算匹配度 +$result = $matchService->calculateMatchScore($position, $resume); + +// 输出结果 +echo "========================================\n"; +echo "岗位简历匹配度测试(真实数据)\n"; +echo "========================================\n\n"; + +echo "【岗位信息】\n"; +echo "----------------------------------------\n"; +echo "岗位名称: " . $position['base_info']['岗位名称'] . "\n"; +echo "招考单位: " . $position['base_info']['招考单位'] . "\n"; +echo "学历要求: " . $position['position_require']['学历要求'] . "\n"; +echo "学位要求: " . $position['position_require']['学位要求'] . "\n"; +echo "年龄要求: " . $position['position_require']['年龄要求'] . "\n"; +echo "专业要求: " . $position['position_require']['专业(学科)类别'] . "\n"; +echo "其他条件: " . $position['position_require']['其他资格条件'] . "\n\n"; + +echo "【简历信息】\n"; +echo "----------------------------------------\n"; +$highestEdu = null; +$highestLevel = 0; +$educationLevels = ['普通本科' => 3, '硕士研究生' => 4, '博士研究生' => 5]; +foreach ($resume['education'] as $edu) { + $level = $educationLevels[$edu['education_level']] ?? 0; + if ($level > $highestLevel) { + $highestLevel = $level; + $highestEdu = $edu; + } +} +echo "最高学历: " . ($highestEdu['education_level'] ?? '未知') . "\n"; +echo "最高学位: " . ($highestEdu['degree'] ?? '未知') . "\n"; +echo "专业: "; +foreach ($resume['education'] as $edu) { + echo $edu['majors_name'] . " "; +} +echo "\n"; +$age = date('Y') - date('Y', strtotime($resume['birth_date'])); +if (date('md') < date('md', strtotime($resume['birth_date']))) { + $age--; +} +echo "年龄: {$age}岁 (出生日期: {$resume['birth_date']})\n"; +echo "性别: " . $resume['gender'] . "\n"; +echo "基层工作经历: " . $resume['work_experience'] . "\n\n"; + +echo "【硬性条件检查结果】\n"; +echo "----------------------------------------\n"; +if ($result['qualified']) { + echo "✓ 通过硬性条件筛选\n\n"; + foreach ($result['hard_requirements']['details'] as $key => $detail) { + if (isset($detail['passed'])) { + $status = $detail['passed'] ? '✓' : '✗'; + echo "{$status} {$key}: "; + if ($detail['passed']) { + echo "通过 (要求: {$detail['required']}, 实际: {$detail['actual']})\n"; + } else { + echo "不通过 - {$detail['reason']}\n"; + } + } + } +} else { + echo "✗ 未通过硬性条件筛选\n\n"; + echo "不通过原因:\n"; + foreach ($result['rejection_reasons'] as $reason) { + echo " - {$reason}\n"; + } + echo "\n详细检查结果:\n"; + foreach ($result['hard_requirements']['details'] as $key => $detail) { + if (isset($detail['passed'])) { + $status = $detail['passed'] ? '✓' : '✗'; + echo "{$status} {$key}: "; + if ($detail['passed']) { + echo "通过 (要求: {$detail['required']}, 实际: {$detail['actual']})\n"; + } else { + echo "不通过 - {$detail['reason']}\n"; + } + } + } +} + +echo "\n【软性条件评分结果】\n"; +echo "----------------------------------------\n"; +if ($result['qualified']) { + echo "总分: {$result['score']}/{$result['soft_requirements']['max_score']}分\n\n"; + foreach ($result['soft_requirements']['details'] as $key => $detail) { + echo "{$key}: {$detail['score']}/{$detail['max_score']}分 - {$detail['reason']}\n"; + } +} else { + echo "(未通过硬性条件筛选,不进行软性条件评分)\n"; +} + +echo "\n========================================\n"; +echo "最终结果: " . ($result['qualified'] ? "✓ 符合条件,匹配度 {$result['score']}/100分" : "✗ 不符合条件") . "\n"; +echo "========================================\n"; + diff --git a/test_match_simple.php b/test_match_simple.php new file mode 100644 index 0000000..6ac0808 --- /dev/null +++ b/test_match_simple.php @@ -0,0 +1,80 @@ + [ + "学历要求" => "本科及以上", + "学位要求" => "学士及以上", + "年龄要求" => "18周岁以上、35周岁以下。", + "其他资格条件" => "适合男性。", + "专业(学科)类别" => "计算机科学与技术类,电气、电子及自动化类" + ] +]; + +// 简历信息1:不通过硬性条件(年龄超限、专业不匹配) +$resume1 = [ + "birth_date" => "1989-03-01", // 36岁 + "gender" => "男", + "work_experience" => "无基层工作年限", + "education" => [ + [ + "education_level" => "硕士研究生", + "degree" => "硕士", + "majors_name" => "逻辑学" + ], + [ + "education_level" => "硕士研究生", + "degree" => "硕士", + "majors_name" => "伦理学" + ] + ] +]; + +// 简历信息2:通过硬性条件 +$resume2 = [ + "birth_date" => "1995-03-01", // 30岁 + "gender" => "男", + "work_experience" => "3年基层工作年限", + "education" => [ + [ + "education_level" => "硕士研究生", + "degree" => "硕士", + "majors_name" => "计算机科学与技术" + ] + ] +]; + +// 加载ThinkPHP框架 +$app = new think\App(); +$app->initialize(); + +// 创建匹配服务实例 +$matchService = new app\service\MatchService(); + +echo "========================================\n"; +echo "岗位简历匹配度测试(简化版 - 只返回分数)\n"; +echo "========================================\n\n"; + +// 测试案例1:不通过硬性条件 +echo "【测试案例1:不通过硬性条件】\n"; +echo "----------------------------------------\n"; +echo "简历信息:36岁,专业:逻辑学、伦理学\n"; +$score1 = $matchService->calculateMatchScore($position, $resume1); +echo "匹配度分数: {$score1}/100分\n"; +echo "说明: 硬性条件不满足(年龄超限、专业不匹配),返回0分\n\n"; + +// 测试案例2:通过硬性条件 +echo "【测试案例2:通过硬性条件】\n"; +echo "----------------------------------------\n"; +echo "简历信息:30岁,专业:计算机科学与技术,有基层工作经历\n"; +$score2 = $matchService->calculateMatchScore($position, $resume2); +echo "匹配度分数: {$score2}/100分\n"; +echo "说明: 通过硬性条件筛选,进行软性条件评分\n\n"; + +echo "========================================\n"; +echo "测试完成\n"; +echo "========================================\n"; + diff --git a/think b/think new file mode 100644 index 0000000..979ac35 --- /dev/null +++ b/think @@ -0,0 +1,11 @@ +#!/usr/bin/env php +console->run(); diff --git a/view/README.md b/view/README.md new file mode 100644 index 0000000..360eb24 --- /dev/null +++ b/view/README.md @@ -0,0 +1 @@ +如果不使用模板,可以删除该目录 \ No newline at end of file diff --git a/简历-岗位.md b/简历-岗位.md new file mode 100644 index 0000000..93d5ce7 --- /dev/null +++ b/简历-岗位.md @@ -0,0 +1,100 @@ +岗位信息如下: +```json + { + "id": 1, + "base_info": { + "岗位名称": "人工智能与大数据侦察职位一", + "招考单位": "市级公安机关", + "招录人数": "10", + "岗位代码": "45150001" + }, + "position_info": { + "岗位名称": "人工智能与大数据侦察职位一", + "招考单位": "市级公安机关", + "招录人数": "10", + "岗位代码": "45150001", + "备注": "考生按照总成绩由高到低的顺序依次在对应的职位计划表中选择具体工作岗位。", + "招录机关": "自治区公安厅", + "职位序号": "1", + "职位简介": "从事公安机关人工智能研发、大数据系统建设管理、数据信息挖掘及分析研判等专业技术工作。" + }, + "position_require": { + "学历要求": "本科及以上", + "学位要求": "学士及以上", + "年龄要求": "18周岁以上、35周岁以下。", + "回避要求": "报考人员不得报考与市公安局领导班子成员、内设机构领导班子成员(如刑侦支队支队长、副支队长等)存在规定回避情形的职位(回避情形含夫妻关系、直系血亲关系、三代以内旁系血亲关系以及近姻亲关系)。", + "专业资格条件": "曾参加人工智能、大数据、计算机领域竞赛,获个人三等奖或团体三等奖及以上,提供官方证明文件。大赛赛项包括但不限于以下类型:\n一是由省级及以上党委、政府及其组成部门(如网信办、教育厅、公安厅、工信厅、总工会等)主办或指导举办的大赛,如全国人工智能大赛(NAIC)、“兴智杯”全国人工智能创新应用大赛、中国人工智能大赛、“数境杯”数据智能创新应用大赛、中国国际大学生创新大赛、“挑战杯”全国大学生课外学术科技作品竞赛、省级职业技能大赛等;\n二是由科研机构主办或指导举办的大赛,如全国大学生大数据分析技术技能大赛、中国高校计算机大赛-人工智能创意赛、中国研究生人工智能创新大赛、“美亚杯”中国电子数据取证大赛、“数证杯”电子数据取证分析大赛、“智警杯”大数据技能竞赛、全国大学生开源情报数据采集与分析大赛等;\n三是由大学联合主办的大赛,如中国高校计算机大赛-大数据挑战赛、IKCEST“一带一路”国际大数据竞赛、CCF大数据与计算智能大赛(简称CCF BDCI)等;\n四是由企业主办类,如阿里云天池大数据竞赛、百度AI开发者大会——百度人工智能安全对抗赛、腾讯广告算法大赛等。", + "其他资格条件": "适合男性。符合人民警察录用条件。单侧矫正视力低于5.0不合格。", + "专业(学科)类别": "计算机科学与技术类,电气、电子及自动化类" + } + } +``` + +简历信息如下: +```json +{ + "user_id": 527, + "exam_city": [ + { + "name": "贵州省", + "child": [ + { + "name": "贵阳市", + "city_id": 604132 + }, + { + "name": "六盘水市", + "city_id": 605834 + }, + { + "name": "遵义市", + "city_id": 607029 + } + ], + "city_id": 604131 + } + ], + "exam_type_id": [ + 3, + 8 + ], + "birth_date": "1989-03-01", + "gender": "男", + "ethnicity": "汉族", + "city_id": 609311, + "origin_city_id": 609311, + "political_status": "群众", + "special_identity": "无特殊身份/其他特特身份", + "is_young": "否", + "grassroots_program": "无", + "work_experience": "无基层工作年限", + "exam_type_name": [ + "事业单位", + "公务员" + ], + "city_name": "贵州,安顺,西秀", + "origin_city_name": "贵州,安顺,西秀", + "education": [ + { + "id": 1, + "user_id": 527, + "education_level": "普通本科", + "degree": "学士", + "majors_id": 1016, + "education_type": "全日制", + "graduate_date": "2012-07-01", + "majors_name": "逻辑学" + }, + { + "id": 2, + "user_id": 527, + "education_level": "硕士研究生", + "degree": "硕士", + "majors_id": 513, + "education_type": "全日制", + "graduate_date": "2015-07-01", + "majors_name": "伦理学" + } + ] + } +``` \ No newline at end of file