diff --git a/.gitignore b/.gitignore index e80ee5cf..0e7797d9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ target/ !**/src/main/**/target/ !**/src/test/**/target/ config.yaml -.git-rewrite ### IntelliJ IDEA ### *.iws *.iml @@ -12,13 +11,6 @@ config.yaml *.sh CLAUDE.md .claude -nul - -# Playwright -.playwright/ -ms-playwright/ -playwright-* - ### Eclipse ### .apt_generated .classpath @@ -39,7 +31,7 @@ build/ ### VS Code ### .vscode/ data.json -src/main/java/boss/data.json +src/main/java/getjobs/boss/data.json merge_prs.log ### Mac OS ### .DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..ac75eccb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,54 @@ +# Changelog +所有重大变更都将记录在此文件中。 + +## [2.1.5] - 2025-10-01 +### Fixed +- 岗位匹配度提示词调整 + - 补充:忽略并且不要考虑:学历、年限、管理经验、行业领域、具体技术深度或软技能等要求;不要向我提问。 + - 规避响应 + ```text + 我需要先了解您的背景信息才能进行匹配判断。 + 请提供以下信息: + 1. 您的学历和专业背景 + 2. Java开发工作年限 + 3. 技术栈掌握情况(Spring框架、数据库、分布式系统等) + 4. 是否有架构设计经验 + 5. 是否有团队管理经验 + 6. 是否有高并发项目经验 请补充这些信息,我就能准确判断该职位是否匹配您的背景。 + ``` + +## [2.1.4] - 2025-09-27 +### Added +- 新增51job检索投递功能 +- 新增智联招聘检索投递功能 + - 📢(智联有极验证js需要输入完整手机号激活验证码,否则无法登录) +- 新增猎聘板块(待开发) + +## [2.1.3] - 2025-09-23 +### Added +- 新增51job配置板块 + +### Fixed +- BOSS、51JOB字典值回显问题 +- BOSS期望席子过滤问题 +- 临时新增一个手动标记已登录按钮(修复刷新后需要点击执行登录逻辑) + +## [2.1.2] - 2025-09-20 + +### Fixed +- 根据简历投递状态过滤已投递的岗位 +- BOSS风控返回异常中断作业 + +## [2.1.1] - 2025-09-17 +### Added +- 新增神仙外企模块 + + +### Changed +- BOSS岗位查询条件字典值不再使用枚举硬编码,通过接口获取全量字典值 +- 移除系统参数配置(减少用户理解难度,默认配好即可) +- 移除关键词岗匹配 (后期使用期望职位与招聘jd进行智能匹配) + +### Fixed +- 修复重复打招呼 + diff --git a/LICENSE b/LICENSE index 68ed3d19..c9a34582 100644 --- a/LICENSE +++ b/LICENSE @@ -22,3 +22,58 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------- +Third-Party Components and Licenses Notice +------------------------------------------------------------------------------- + +This project includes third-party software components. + +H2 Database Engine +------------------- +This product bundles the H2 Database Engine (https://h2database.com/). + +The H2 Database Engine is multi-licensed under the following licenses: + - Eclipse Public License 1.0 (EPL-1.0) + - Mozilla Public License 2.0 (MPL-2.0) + - BSD 3-Clause License + - GNU Lesser General Public License v3 or later (LGPL-3.0-or-later) + +For the purpose of this project, we have elected to use and distribute +the H2 Database Engine under the **BSD 3-Clause License** only. + +A copy of the BSD 3-Clause License is provided below. + +------------------------------------------------------------------------------- +BSD 3-Clause License (for H2 Database Engine) +------------------------------------------------------------------------------- + +Copyright (c) 2004-2024 H2 Group + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of the H2 Group nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +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. + +------------------------------------------------------------------------------- +End of Third-Party License Notices +------------------------------------------------------------------------------- diff --git a/README.md b/README.md index cb3b3ecf..6c4ddd87 100644 --- a/README.md +++ b/README.md @@ -1,385 +1,27 @@ -

🍀 Get Jobs【工作无忧】

-
- -[![Stars](https://img.shields.io/github/stars/loks666/get_jobs?style=flat&label=%F0%9F%8C%9Fstars&labelColor=ff4f4f&color=ff8383)](https://github.com/loks666/get_jobs) -[![QQ交流群](https://img.shields.io/badge/🐧QQ交流群-get_jobs-0FB5EB?labelColor=235389&logoColor=white&style=flat)][qq-link] -[![License](https://img.shields.io/badge/📑licenses-MIT-34D058?labelColor=22863A&style=flat)](https://github.com/loks666/get_jobs/blob/master/LICENSE) -![Issues closed](https://img.shields.io/github/issues-search?query=repo%3Aloks666/get_jobs+is%3Aclosed&label=%F0%9F%A4%8F%F0%9F%8F%BBFissues%20closed&labelColor=008B8B&color=00CCCC) -[![Forks](https://img.shields.io/github/forks/loks666/get_jobs?style=flat&label=%F0%9F%8F%85Forks&labelColor=800080&color=912CEE)](https://github.com/loks666/get_jobs/forks) - -
-
-

黑暗无论多么长,光明迟早总是会来的

-

我知道你心中有煎熬,有焦虑,像一柄长剑悬在头顶,随时可能落下。
- -长夜再长,也终见天光
-再深的寒冬,也将逢迎春阳
-请记得只要不放弃,天总会亮
-黎明破晓之时,苦难都将化作勋章

- -

- - GitHub Trending - -

-
- -- [如何使用本程序寻找程序兼职岗位?](doc/part_job.md) -- [你找不到工作,是因为大环境不好吗?【很重要】](doc/doc.md) -- [少侠,请立即开始,你精彩的人生吧!【源自L站】](doc/just_do_it.md) -- [许愿墙](https://fcv1y6gslc.feishu.cn/sheets/JS45sElqAhKhawtTzsYcftymnFe) - - - 如果你有你心仪的工作,请在我这里许下愿望,如果实现了,请记得回来更新状态 - - 许愿墙为飞书文档,你可以编辑好以后,导出为xlsx文档,然后覆盖本项目中resources文件夹中的"**许愿墙.xlsx**"文档 - - 接下来,你就可以把xlsx文档,提交到main分支,然后……你就是本项目的开发者之一了。 - - 你可以在你的简历中加上一条,Github,热门开源项目开发者之一。 - - 但是你需要尽可能的,熟悉本项目的逻辑,去想象某一块功能就是你做的,这样可以更从容,更优雅的"吹牛逼”。 - - 只要你相信你自己,本项目就会帮你,你要记住,是你自己拯救的你自己。 - -> 需要注意的是,提交pr的commit:请固定使用"✨I can do it!“,然后提交pr即可,剩下的,我全都搞定啦! - -- 📌 **目前该项目存在的问题** - - 当前招聘市场,有效的软件仅有 Boss 和 猎聘(有少部分岗位)。 - - 如果 Boss 出现掉线等问题,请注意两点: - 1. 当天停止投递,第二天接着投,否则可能会封号。 - - **最重要的事情:不要依赖程序投递 Boss!!!** - 手机上的 Boss,比本程序网页端靠谱得多。当你手机投的很累,又没有投够 100 个,请再使用本程序的 Boss 投递! - - 本项目为 GitHub 热门开源项目,目前已申请 Intelli 的开源支持计划。加入开发组意味着你可以获得 Intelli 编辑器官方的* - *免费全家桶永久使用权**,欢迎联系我! - - 本项目遵循 MIT 协议。是的,你可以商业化,但是——**真心希望你能帮助更多人,团结起来!** - - [【重要】跳转到文末更新日志](#-更新日志) - -### 🌴源码地址 - -- Github(国外访问):https://github.com/loks666/get_jobs -- Gitee·码云(中国大陆):https://gitee.com/lok666/get_jobs -- GitCode(中国大陆):https://gitcode.com/super_journey/get_jobs - -### AI代理购买 - -- https://api.ruyun.fun/ [**支持市面全部大模型!折扣比例2比1!1刀也可充,详情请联系站内客服**] - -## 🌟 特色功能 - -- **💥 AI 智能匹配**:AI检测岗位匹配度,并根据JD自动撰写个性化的打招呼语(仅限 Boss 直聘)。 -- **📷️ 图片简历**:Boos直聘可在发送打招呼语后自动发送图片简历,无须等待HR索要简历,有效提高回复率。 -- **⏰ 定时投递**:一键投递所有平台,可设置定时投递,第二天自动重新投递,省时省力。 -- **🔎 智能过滤**:自动过滤 **不活跃 HR**、**猎头岗位**、**目标薪资**,让你的简历投递更精准。 -- **📢 实时通知**:通过企业微信消息推送,实时掌握简历投递情况,不错过任何机会。 -- **🚫 黑名单功能**:自动更新黑名单企业,避免重复投递不合适的公司,提高投递效率。 -- **🛠️ 易于配置**:集中化配置,只需修改配置文件即可自定义筛选条件,轻松上手。 -- **🔄 持久登录**:支持超长 Cookie 登录,大部分平台每周仅需扫码一次,减少重复操作。 - -### 🔞️ 注意事项 - -- ❌必须要关闭墙外代理,由于主要针对的国内平台,墙外代理会导致页面加载缓慢 -- 💪🏻如你有“折腾精神”希望自己配置,QQ群内提供免费答疑,如你不想麻烦,可进入群聊查看群公告 -- 📰由于不同系统的页面不一样,导致可能不兼容,文末会给出文档,尽可能让大家能自定义修改 -- 🚩如您不方便访问github,可使用码云镜像(中国大陆)版本:[gitee/getjobs](https://gitee.com/loks666/get_jobs) - -> 已经有人在交流群里 **发广告** 等与本项目无关的信息 -> 如果带着不同目的或者没想清楚就进群的 -> 一经发现群主会对您的家人及朋友进行亲切(**没有素质**)的问候 -> 并将您请出群聊,请珍惜交流的机会,谢谢! - -## 🚀 如何使用? - -### 1️⃣ 使用git拉取代码 - -``` -git clone https://github.com/loks666/get_jobs.git -cd get_jobs +## 运行界面 +![image](src/main/resources/images/img.png) +![image](src/main/resources/images/img2.png) + +## 运行准备 +### jdk21 + +### 补全`application-gpt.yml`配置 +api-key: ${OPENAI_API_KEY} -> api-key: ${OPENAI_API_KEY:xxx} +不用AI功能默认值随便填一个xxx,否则填自己申请的key +```yaml +spring: + ai: + openai: + api-key: ${OPENAI_API_KEY:xxx} ``` -### 2️⃣ 环境配置:JDK21、Maven、Chrome - -- 目前程序自动判断系统环境,自动下载对应的chromedriver,并进行浏览器操作,需解决网络问题。 - -更多环境配置详情请点击:📚 [环境配置](https://github.com/loks666/get_jobs/wiki/环境配置) - -### 3️⃣ 修改配置文件(一般默认即可,需要修改自己的地区和岗位) - -- 🔩 通用配置 - - - 日志文件在 **target/logs** 目录下,所有日志都会输出在以运行日期结尾的日志文件中 - - **Constant.WAIT_TIME**:超时等待时间,单位秒,用于等待页面加载 - - **cookie登录**: 扫码后会自动保存**cookie.json**文件在代码运行目录下,换号直接删除**cookie.json**即可 - - 每个平台的配置转换码都在平台文件夹下的Enum类里,找到相应的代码添加到类中即可 - -- 📢 企业微信消息推送设置 - - - 把[.env_template](src/main/resources/.env_template)文件重命名为 `.env` - - 在企业微信中创建一个群聊,然后添加机器人,获取到机器人URL,复制到 `.env`文件中的 `HOOK_URL`即可 - - 保持[config.yaml](src/main/resources/config.yaml)文件中 `bot.is_send`为true - - 企业微信推送示例 - 企业微信推送示例 - - > 完成以上配置,在每个平台投递结束简历后,便会在企业微信的群聊内,推送岗位的投递情况,无须改动其他代码 - -- 🤖 AI配置 - - - `.env`配制如下: - ``` - HOOK_URL=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your_key_here - BASE_URL=https://api.openai.com - API_KEY=sk-xxx - MODEL=gpt-5-nano - ``` - - `HOOK_URL`:企业微信机器人推送的链接 - - `BASE_URL`:直连或中转链接地址 - - `API_KEY`:调用的API KEY - - `MODEL`:需要使用的模型名称 - - > 根据测试,boss直聘在每天所有的岗位投递结束后消耗的额度(gpt-5-nano)大约在0.06美元(6美分) - > 左右,代理除了在本项目中可用,也可使用客户端(https://github.com/knowlimit/ChatGPT-NextWeb)进行使用 - > 在日常生活中使用,所以不会浪费,充值额度1刀起,随用随充 - > 💥注意!AI代理地址:如云API:https://api.ruyun.fun/ - ,该网站可自主充值需要的金额,无任何捆绑消费,支持市面上全部大模型,2人民币=1美元,base_url默认使用"https://api.ruyun.fun/" - 即可 - - - AI生成的打招呼语示例 - AI生成的打招呼语示例 - -- ⚙️ **最重要的配置文件**([💥config.yaml💥](src/main/resources/config.yaml)) - > 因为配置文主要改动较多,所以不放在自述文件中,请自己根据需要修改 - - -- boss直聘([Boss.java](src/main/java/boss/Boss.java))【喜大普奔,目前Boss打招呼上限已修改为每日150次】 - - > 注意:设置配置文件的sayHi为你的打招呼语,否则会投递失败 - > 投递结束后会自动更新黑名单企业,发送过不合适等消息的HR的公司会加入黑名单,不会在投递该公司 - > 现在找工作是很难,但也别做舔狗,打工人不是牛马! - -- 发送图片简历 - - > 在resources文件夹下,将自己的pdf简历转换为resume.jpg,同时配置项sendImgResume为ture,即可自动发送图片简历 - > pdf转图片需要wps会员,如果找不到相关工具,可联系群主帮忙转换,5r/次 - -- 目标薪资设置:expectedSalary: [ 25,35 ] - - 单位为K,第一个数字为最低薪资,第二个数字为最高薪资,只填一个数字默认为只要求最低薪水,不要求最高薪水 - - ``` - data.json //黑名单数据,在投递结束后会查询聊天记录寻找不合适的公司添加进去 - ├── blackCompanies: List.of("复深蓝"); // 公司黑名单,多个用逗号分隔 - ├── blackRecruiters: List.of("猎头"); // 排除招聘人员,比如猎头 - └── blackJobs: List.of("外包", "外派"); // 排除岗位,比如外包,外派 - ``` -- 猎聘([Liepin.java](src/main/java/liepin/Liepin.java))【默认打招呼无上限,主动发消息有上限,成功率不高,好在量大,较为推荐】 - - > 注意:需要在猎聘App最新版设置打招呼语(默或者自定义皆可),即可自动发送消息,不会被限制 - > 只可微信扫码,请绑定微信账号 - > 需要使用最新版猎聘手机app设置打招呼文本,只要不主动发消息,可以无限制对猎头打招呼,程序默认为该配置。 - -- 51job([Job.java](src/main/java/job51/Job51.java))【投递有上限,且限制搜索到的岗位数量,没什么活人】 - - > 51job现在已经烂掉了,不建议使用 - > 现在投递一段时间后会出现投递上限 - > 目前的解决方式是投一页暂停10秒,先这么着吧 - -- 拉勾([Lagou.java](src/main/java/lagou/Lagou.java))【拉钩已被51job收购,目前属于僵尸公司,此平台本项目已放弃更新】 - - > 默认使用微信扫码,请绑定微信账号 - > 拉勾需要指定默认投递简历(在线简历 or 附件简历),否则会投递失败 - > 拉勾直接使用的是微信扫码登录,运行后直接扫码即可,开箱通用 - > 但是拉勾由于反爬机制较为严重,代码中嵌套了大量的sleep,导致效率较慢 - > 这边建议拉勾的脚本运行一段时间后差不多就行了,配合手动在app或者微信小程序投递简历效果更佳! - -- 智联招聘([ZhiLian.java](src/main/java/zhilian/ZhiLian.java))【投递上限100左右,烂掉了,不要用】 - - > 智联招聘需要指定默认投递简历(在线简历 or 附件简历),否则会投递失败 - > 只可微信扫码,请绑定微信账号 - -### 4️⃣ 运行代码 - -- 🏃🏻‍♂️‍➡️ 直接运行你想要投递平台的下的代码即可 - ![运行图片](src/main/resources/images/run1.png) - -### 5️⃣ 定时投递 - -- 目前默认Boss会定时投递两次,可以修改相关代码修改时间 -- 每个包下的Scheduled文件,即使单独针对平台的定时投递,例:[BossScheduled.java](src/main/java/boss/BossScheduled.java) - ,就是boss平台每天定时投递 -- 定时投递第一次运行时会立即投递一次,到了第二天设定的时间,会再次投递,时间可以自行在代码中修改 - ---- - -### ️ 6️⃣ 批量投递 - -- win平台下,配置任务计划,执行run_startall.bat脚本即可,时间可以自己设定 -- [StartAll.java](src/main/java/StartAll.java)[BossScheduled.java](src/main/java/boss/BossScheduled.java) - 脚本可以一键启动所有平台,需要哪些平台可以自行进行修改编辑 - -### ✍🏼 例:Boss投递日志 - -Boss投递日志 - -### ✍🏼 猎聘投递日志 +## 运行 +`getjobs.GetJobsApplication` 直接运行main函数 -猎聘投递日志 - -### ✍🏼 寻找城市码 - -猎聘投递日志 - -## 📧 联系方式 - -- V2VDaGF0OkFpckVsaWF1azk1Mjcs6K+35aSH5rOo77ya5pq06aOO6Zuo5bCx6KaB5p2l5LqG - -## 👨🏻‍🔧 QQ群 - -- 扫码添加:QQ加群答案为本项目仓库名【get_jobs】 - -
- qq群 -
- -> 点击下面的链接可直接加群,微信群由于没有活跃度,所以停止了 - -## 🚩 环境部署问题 - -> 本项目文档已相对完善,如有运行仍有问题,请添加QQ群联系群主或在群内沟通 - -- 请注意: - 1. 本项目不支持服务器部署,无须尝试,如招聘网站发现访问者为服务器IP,不会返回任何网站数据。 - 2. 在开发与部署过程有任何问题都可在群内沟通,但群内的同学没有义务必须要解决您的问题,请保持礼貌提问的态度。 - -> 注:本项目为免费开源项目,非Saas类出售商品,不会考虑任何兼容的设备以及他人的需求,如多位同学有相同的需求可以提出issue,具有一定需求性会考虑开发,其他的问题有能力就自己修改,否则请联系群主,非诚勿扰。 - ---- - -## 📑 更新日志 - ---- - -* 2025-10-18 14:30:00 - 1. ✨ **内置1494+城市代码和2137+行业代码**:重构城市与行业代码加载方式,从硬编码改为JSON文件动态加载,支持全国所有地区。 - 2. 🗑️ **彻底移除chromedriver依赖**:从Git历史中完全删除chromedriver.exe,项目体积从200MB优化至49MB,减少75%空间占用。 - 3. 🔧 **修复企业微信推送功能**:优化Bot推送配置,支持从resources目录读取.env文件,简化配置流程。 - 4. 🧹 **代码优化与清理**:删除Bark推送冗余代码,移除硬编码的城市枚举,代码更简洁易维护。 - 5. 📋 **行业代码待完善**:city-industry-code.json已包含完整行业数据,后续将支持动态加载(择日更新)。 - 6. 🐛 **修复nul文件问题**:优化bat脚本,解决Windows下产生nul文件的问题。 - ---- - -* 2025-08-08 04:33:56 - 1. Boss直聘逻辑修正,现已能完整运行所有流程。 - 2. 图片简历不能发送,AI打招呼功能正常。 - 3. 目前boss驱动已从Selenium改为playwright。 - 4. 新增gui分支,该分支为本项目的gui版本,主要为群管理:【凯】提供。 - 5. 砍掉了bark推送,用户地区码,手机端等杂七杂八不重要的功能,返璞归真。 - ---- - -* 2024-08-12 22:56:20 - 1. 添加企业微信消息推送功能 -* 2024-08-12 22:56:20 - 1. 添加企业微信消息推送功能 -* 2024-08-11 18:39:56 - 1. 修复智联,猎聘等不能投递的问题。 - 2. 添加定时投递功能 -* 2024-06-06 17:41:20 - 1. boss支持多城市投递。 -* 2024-06-06 01:49:38 - 1. boos:若公司名小于2个字或4个字母则不会添加进黑名单 - 2. 添加linux系统支持。 -* 2024-04-28 15:20:06 - 1. boos:自动更新黑名单公司 -* 2024-04-15 01:52:18 - 1. 新增config.yaml,目前仅需修改配置文件即可,已全平台支持 - 2. cookie有效期延长,保持至少一周(拉勾平台除外)【安慰剂】 - ---- - -## 🤝 参与贡献 - -我们欢迎一切形式的贡献,你可以先查看我们的 [Issues](https://github.com/loks666/get_jobs/issues) -和 [Discussions](https://github.com/loks666/get_jobs/discussions),那里藏着无数等待你大展身手的机会! - -我们对代码质量有很高的要求,但不要担心 —— 你完全可以使用 GPT 等工具进行风格打磨,只要最终输出优雅且可靠的成果! - -如果你希望进入开发组,却一时不知道从哪里开始? -没关系,**观察,思考,提出你的见解,与大家讨论,去发现真正有价值的功能!** - -不要怕失败,不要畏惧修改。 -**每一次讨论,每一次提交,每一次调整,都是在为你的成长积蓄力量!** -当你的 PR 被成功合并的那一刻,你会明白 —— 所有努力,所有坚持,他们都值得! - -待开发列表: - -- 51job修改为PlayRight支持 -- 清除所有关于Selenium的依赖 - ---- - -## 🚀 PR 提交流程(非常重要!) - -1. Fork 本项目 -2. 从 `main` 分支新建你的个人开发分支 -3. 开发完成后,提交 Pull Request 到 **loks666/get_jobs 的 `dev` 分支** - (❗ **注意:不是 main,是 dev!**) -4. 提交 Commit 时,请在信息前加上一个符合提交内容的 **Emoji 表情 - **([emoji网站](https://www.emojiall.com/zh-hans/all-emojis))自由发挥! -5. 等待管理员审核,验证无误后,代码将合并到 `main` 分支 -6. 表现优秀者,可提前申请加入开发组,与我们并肩作战! - ---- - -# ✨ 相信自己! - -> **"每一个伟大,都有一个平凡的开始"** - - ---- - -### 📰 开源协议 - -

📝 License

- -[![FOSSA Status](https://app.fossa.com/projects/git%2Bgithub.com%2Floks666%2Fget_jobs/refs/branch/master/f1c1fbec331aec96694d8ab331fc720bb2aa84b4)](https://app.fossa.com/projects/git%2Bgithub.com%2Floks666%2Fget_jobs/refs/branch/master/f1c1fbec331aec96694d8ab331fc720bb2aa84b4) - -
- ---- - -### 🙅🏻‍♂️ 谨防受骗 - -- 近日已经有人反馈,有人拿着本项目免费开源的代码,在闲鱼等小红书各处售卖 -- 本项目代码完全开源免费,请勿上当受骗,请大家擦亮眼睛 -- 这是一个将本项目免费源码的网站 - 骗子网站 - 骗子1 - 骗子2 - 骗子3 - 骗子4 - ---- - -### 🐦‍🔥 一点碎碎念 - -- 此项目之初本是我自己用的工具,可不知不觉间,它已经有了茁壮的生命力和影响力。 -- 曾经我也希望我自己的项目能够被更多人使用,可是现在 -- 我更希望,愿终有一天,不会有任何人在使用本项目 -- 愿你我有朝一日,都可以不用再为生计奔波,去追求自己真正热爱的事物 -- 我也努力一点,希望你也可以努力一点,让这一天或许可以早点实现…… - ---- - -### ☕️ Github Star历史 - -[![Stargazers over time](https://starchart.cc/loks666/get_jobs.svg?background=%23ffffff&axis=%23101010&line=%23e86161)](https://starchart.cc/loks666/get_jobs) - - - - - -[qq-link]: https://qm.qq.com/q/qJwmIrqPU - -[qq-shield-badge]: https://img.shields.io/badge/QQ交流群-get_jobs-0FB5EB?labelColor=235389&logo=tencent-qq&logoColor=white&style=flat - -[pr-welcome-link]: https://github.com/loks666/get_jobs/pulls - -[pr-welcome-shield]: https://img.shields.io/badge/🤯_pr_welcome-%E2%86%92-ffcb47?labelColor=black&style=for-the-badge - -[fossa-license-shield]: https://app.fossa.com/api/projects/git/Bgithub.com/Floks666/Fget_jobs.svg?type=shield - -[fossa-license-link]: https://app.fossa.com/projects/git/Bgithub.com/Floks666/Fget_jobs?ref=badge_shield +```java +public static void main(String[] args) { + SpringApplication.run(GetJobsApplication.class, args); + } +``` +### 访问 +浏览器访问 http://localhost:8080 \ No newline at end of file diff --git a/REFACTORING.md b/REFACTORING.md new file mode 100644 index 00000000..31ba91de --- /dev/null +++ b/REFACTORING.md @@ -0,0 +1,60 @@ +# Refactoring +所有代码结构变更都将记录在此文件中。 + +## [2025-09-29] 新增任务状态管理模块 +### 背景 +- 各招聘平台任务(登录、采集、过滤、投递)的执行状态分散在各自的 Service 中,缺乏统一的管理和查询机制。 +- 前端或外部服务难以实时获取任务进度和结果。 + +### 变更内容 +- **引入 Spring 事件机制**: + - 创建了 `TaskUpdateEvent` 事件,用于在任务状态变更时发布通知。 + - 各平台 `TaskService`(`BossTaskService`, `Job51TaskService` 等)在关键节点(如开始、进行中、成功、失败)发布 `TaskUpdateEvent`。 +- **新增 `task` 模块**: + - `enums`:定义了 `TaskStage` (LOGIN, COLLECT, FILTER, DELIVER) 和 `TaskStatus` (STARTED, SUCCESS, FAILURE, IN_PROGRESS)。 + - `dto.TaskUpdatePayload`:作为事件的载体,封装了平台、阶段、状态、数量和消息等信息。 + - `service.TaskStatusService`:一个单例服务,使用 `ConcurrentHashMap` 存储各平台各阶段的最新状态,实现状态的集中管理。 + - `listener.TaskStatusListener`:监听 `TaskUpdateEvent` 事件,并调用 `TaskStatusService` 更新状态。 + - `controller.TaskStatusController`:提供 `/api/tasks/status` RESTful API,用于实时查询所有任务的当前状态。 +- **重构各平台 `TaskService`**: + - 移除了原有的本地状态管理逻辑(如 `taskStatusMap`)。 + - 注入 `ApplicationEventPublisher`,在 `login`, `collectJobs`, `filterJobs`, `deliverJobs` 等方法中发布状态更新事件。 + +### 影响 +- **统一状态管理**:实现了所有平台任务状态的集中化、事件驱动式管理。 +- **提高可观测性**:通过 RESTful API,可以方便地监控所有任务的实时进度。 +- **解耦**:将状态管理逻辑与业务逻辑解耦,提高了代码的可维护性和扩展性。 +- 外部接口保持不变,对现有业务逻辑无直接影响。 + +## [2025-09-29] 优化 PlaywrightService Context 管理 +### 背景 +- `PlaywrightService` 为每个招聘平台创建独立的 `BrowserContext`,导致启动时打开多个浏览器窗口,占用过多系统资源。 +- 经过分析,各平台间的 `Context` 信息(如 Cookies、LocalStorage)并无干扰,无需严格隔离。 + +### 变更内容 +- 将 `PlaywrightService` 的多 `BrowserContext` 实例重构为单一共享的 `BrowserContext`。 +- 所有 `Page` 均在同一个 `BrowserContext` 中创建,减少了浏览器窗口的数量。 +- 相应调整了 `init` 和 `close` 方法的逻辑,以适应单 `Context` 的管理模式。 + +### 影响 +- 显著减少了应用启动时的资源消耗和浏览器窗口数量。 +- 外部接口保持不变,对上层业务无影响。 + + + +## [2025-09-29] 将 PlaywrightUtil 重构为 PlaywrightService +### 背景 +- `getjobs.utils.PlaywrightUtil` 工具类中逐渐包含了复杂的业务逻辑 +- 难以进行单元测试,也不符合依赖注入与扩展的要求 + +### 变更内容 +- 创建 `getjobs.common.service.PlaywrightService`,替代 `getjobs.utils.PlaywrightUtil` +- 支持按平台构建独立 Page 与 Context + - 通过平台参数动态创建 BrowserContext 和 Page,实现多平台会话隔离、Cookies 独立管理和个性化配置。 + - 保证各平台投递流程的稳定性,避免静态 util 时代多平台共享单一上下文带来的冲突和数据污染。 +- 原有静态方法改为实例方法,并注入到需要的模块 +- 迁移调用点,并删除过时的工具类 + +### 影响 +- 无数据库结构变化 +- 原有外部接口不受影响 diff --git a/doc/Detail.md b/doc/Detail.md deleted file mode 100644 index 024412d1..00000000 --- a/doc/Detail.md +++ /dev/null @@ -1,79 +0,0 @@ -### 驱动配置 - -driver目前路径:[src/main/resources/chromedriver.exe](../src/main/resources/chromedriver.exe) -,Chrome需要更新到最新版本。 -如启动报错,则是因为版本升级导致,需要更新版本 -driver 下载链接:https://googlechromelabs.github.io/chrome-for-testing - -driver下载页面 - -### JDK17 - -下载链接:https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html - -jdk17下载页面 - -安装好后,请保存自己的安装路径,可以在IDEA内配置Java,无须配置环境变量 - -### IDEA - -下载链接:https://www.jetbrains.com/idea/download/#section=windows - -在 IntelliJ IDEA 中配置 JDK 的步骤如下: - -打开 IntelliJ IDEA。 - -在菜单栏中,选择 "File" -> "Project Structure"。 - -在弹出的对话框中,选择 "Project" 选项。 - -在 "Project SDK" 部分,点击新建按钮(如果你还没有配置过 JDK),或者从下拉菜单中选择一个已经配置过的 JDK。 - -如果你点击了新建按钮,会弹出一个文件选择对话框。在这个对话框中,找到你的 JDK 安装目录,然后点击 "OK"。 - -点击 "Apply",然后点击 "OK",以保存你的更改。 - -注意:你的 JDK 安装目录应该包含 bin、lib 等子目录,以及 src.zip、LICENSE 等文件。例如,如果你在 Windows 上安装了 JDK,那么你的 -JDK 安装目录可能是 C:\Program Files\Java\jdk1.8.0_231(这个路径可能会因为你的 JDK 版本和安装位置不同而不同)。 - -### Maven - -下载链接:https://maven.apache.org/download.cgi - -选择 `apache-maven-3.9.6-bin.zip` 下载即可 - -在 IntelliJ IDEA 中配置 Maven 的步骤如下: - -1. 打开 IntelliJ IDEA。 - -2. 在菜单栏中,选择 "File" -> "Settings" (Windows/Linux) 或 "IntelliJ IDEA" -> "Preferences" (macOS)。 - -3. 在弹出的对话框中,从左侧导航栏选择 "Build, Execution, Deployment" -> "Build Tools" -> "Maven"。 - -4. 在 "Maven home directory" 文本框中,输入你的 Maven 安装目录的路径,或者点击右侧的文件夹图标,从文件选择对话框中选择你的 - Maven 安装目录。 - -5. 在 "User settings file" 文本框中,输入你的 `settings.xml` - 文件的路径,或者点击右侧的文件夹图标,从文件选择对话框中选择你的 `settings.xml` 文件。这个文件通常位于你的 Maven - 安装目录的 `conf` 子目录下,或者在你的用户目录下的 `.m2` 子目录下。 - -6. 在 "Local repository" 文本框中,输入你的本地 Maven 仓库的路径,或者点击右侧的文件夹图标,从文件选择对话框中选择你的本地 - Maven 仓库。这个仓库通常位于你的用户目录下的 `.m2` 子目录下。 - -7. 点击 "Apply",然后点击 "OK",以保存你的更改。 - -注意:你的 Maven 安装目录应该包含 `bin`、`boot`、`conf` 等子目录,以及 `LICENSE`、`NOTICE`、`README.txt` 等文件。例如,如果你在 -Windows 上安装了 Maven,那么你的 Maven 安装目录可能是 `C:\Program Files\Apache\maven`(这个路径可能会因为你的 Maven -版本和安装位置不同而不同)。 - -### 下载依赖 - -配置好maven后,IDEA会开始自动下载依赖,等待下载完成且代码没有报红即可运行代码 -如下图,标记位置为手动刷新: -![img.png](../src/main/resources/images/maven.png) - -### 运行代码 - -运行代码前,需要修改代码中的配置,如投递页数、投递关键词等,详情请参阅[配置说明](README) - -![img.png](../src/main/resources/images/run.png) \ No newline at end of file diff --git a/doc/doc.md b/doc/doc.md deleted file mode 100644 index d9c89ddf..00000000 --- a/doc/doc.md +++ /dev/null @@ -1,111 +0,0 @@ -# 请你不要做影响大环境的人 - -某岗位的上限是 25k,告知我只能给 24k。谈薪的时候直接拒绝了,我甚至还给了他们项目合作的台阶下。不知是否是 HR 的 "努力"?最终将岗位上限击穿到了 30k。 - -这让我想到了一件事: - -朋友,一旦你接受 "大环境" 的设定,那么你将永远掉在这个圈子里出不来,你将会绝无涨薪之日。 - -你只管报价,剩下的,交给公司。 - -薪资更多的不是公司愿意给你多少钱,而是你觉得你自己值多少钱。 - -它并非物化自己,而是代表着你对自己能力的认可。 - ---- - -## 关于我的技术栈 - -**我,GitHub 热门开源项目作者。** - -- 机器学习、深度学习、强化学习 -- Python 所有 Web 框架全精 -- AI 大模型全精 -- Java 所有技术栈全精 -- Golang 框架 Gin、Zero、Beego 熟练 -- 前端 Vue、React 熟练 - -**Next.js?这玩意太恶心了,不会。TS 也懒得学,我甚至想抽空学 Rust,但实在没时间。** - ---- - -## 关于谈薪 - -说实话,面试期间如果面试官问题较多,我都会不耐烦。 - -比如 HR 常说的: - -> *"XX 薪资我们这里给不到。"* - -薪资给不到,那**是公司的问题,不是你的问题。** - -请你不要怀疑自己,保持写代码的手感,以及一颗谦卑的心(这个看心情)。 - -请记住,该来的,总会来的,好饭不怕晚。 - -> "他日你入关之时,总有大儒为你辩经。" - ---- - -## 关于创业经历 - -现在我总算知道为什么会有很多公司怕我有创业经历。 - -以前我总觉得很好奇,为什么不能创业? - -现在我明白了: - -- 他们怕的是我识破了他们的小伎俩。 -- 他们怕的是他们不能再压榨我。 -- 他们怕的,是我知道了所有事情真正的真相。 - ---- - -## 在 get jobs 看到的众生相 - -在 get jobs 的两个群中,我看到了众生相: - -- 有迷茫的应届毕业生 -- 有功成身就的上市总裁 -- 也有裁员焦虑的普通🐮🐴(比如我) - -在 get jobs 创立之初,源自于 L 站,我尝试在 L 站推广项目,吐槽了自己找不到工作,被管理员秦始皇以及所有管理将帖子置顶 3 天。 - -get jobs 项目 star 瞬间冲上 100,AI 真的很火。当我加入了 AI 功能,每天 star 的人数翻倍的往上涨,最终冲到 GitHub 榜首,甚至目前项目已经被豆包、GPT 等大模型收录。 - -> *"你要信 1+1=2?还是信我是秦始皇?"* - -说实话,这可能是这辈子唯一一个能让我相信他是秦始皇的人了。赛博菩萨普度众生,让 L 站涌现了一个又一个菩萨。 - -现在,我也加入了赛博菩萨的行列。 - -秦始皇是照亮我心中的一道光。 - -现在,我也希望做照亮世人的那道光。 - -如今,我已完成了一半的蜕变。 - -我仍希望可以照亮其他人远方的路。 - -更多的,我希望如果我照亮了你,请你也可以努力去照亮别人。 - -> *当工人罢了工,他才成为了人。* - -我愿你也是。 - ---- - -## 最后 - -> *"天不生秦始皇,AI 万古如长夜!"* - -**真诚、友善、团结、专业** - -共建你我引以为荣之社区。 - -[L 站](https://linux.do) - -[GitHub](https://github.com/loks666) - -*2025.3.5 4:58* - diff --git a/doc/just_do_it.md b/doc/just_do_it.md deleted file mode 100644 index a9a2f8cf..00000000 --- a/doc/just_do_it.md +++ /dev/null @@ -1,212 +0,0 @@ -# 这里是帮助你找工作的一篇帖子,你觉得你找不到工作是因为大环境不好吗? - -看到帮助了很多佬友,我很开心 -我终于回报大家了 - -现在,我的开源项目: -[**GitHub - loks666/get_jobs: 💼【AI找工作助手】全平台自动投简历脚本:(boss、前程无忧、猎聘、拉勾、智联招聘)**](https://github.com/loks666/get_jobs) -自动投简历脚本,目前已经 1.6k star,很感谢大家的支持 - -所以,我在今天,正式开放了 PR,项目 resource 文件夹下,有一个“许愿墙.xlsx”文件 -需要你打开飞书链接,写下你的愿望 -大家可以在文件里写下你心仪的岗位,以及目前,投递中?已找到? -填任何你想要的状态都可以 -下载为 xlsx 文件,覆盖掉项目 resource 下的文件 -你就可以把这条 PR,提到项目里 - -这样,你就进入了项目的开发组,你可以在 GitHub 项目的主页上 -看到你自己的头像,然后就可以在简历上写上: -你是“著名”开源项目的开发者之一 - -你现在就可以想想这件事,如果你去面试的时候 -有没有可能你的心态就会不一样? -或者可能感觉会好一点点 - -就是这一点点! -我要的就是你这一点点 -它可能,会改变非常多的事情 - -> “每一个伟大,都源自一个平凡的开始。” - -但必须要提醒的是,请尽可能地了解项目逻辑,这样面试官在问你的时候 -你对某块逻辑够熟悉,你就可以说,这个功能,就是我做的,怎样实现的 - ---- - -## 我的故事 - -我自我介绍一下: -13 年到上海,大专,学的是出版印刷 -毕业一年,做的租房销售,为的就是我可以租到便宜的房子,我做到了,我在上海 -可以找到任何意想不到的便宜的房子,现在我的房租就是 710 元,20 平带独卫,可能比较偏 -但是通勤一个小时,我觉得,还是可以接受的 - -然后,我培训 IT,达内,我跟我父亲唯一一次真心的对话: -“爸,我想学 IT” -“你数学那么垃圾,英语那么差劲,赶紧算了” -“你今天最好给我记住这句话,我一定要狠狠地打你的脸” - -即使在第二年,他给了我两万,让我去培训 IT -可在当年过年回家的时候,吵架的时候他又说: -给了我两万,我没有给家里搬过一块砖 - -所以我彻底地与他,断绝关系了 -我的人生,从那一刻开始,变得明亮了 -就像形容刘晓庆的那样,“浑身上下,没有一块骨头是软的” - -过年回来,我憋着一口气,我的信用卡有 20 万的额度 -没有钱就去刷信用卡,如果刷完了信用卡的 20 万,仍然找不到工作 -我就去死,挑个风和日丽的日子,悄悄地离去 - -> “置之死地而后生” - -然后我发现了我项目中最严重的一个问题: -Boss 的网页端的机会,真的比 App 端少太多了 -所以我每天手动投递,每天强行忍着投递的痛苦,一定要投满 100 -然后我发现,每周我的面试邀约不断 -优先投 AI,其次是 Java,偶尔投投 Python 和 Golang -其实只用了两周,五个面试,五面,五杀 - ---- - -## 面试经验 - -这里面有一个面试,杭州的 RAG,岗位上限 27k,我给面试官说我期望 30 -面试官说他立即要去申请,我说 OK,那可以,那咱们就可以合作 -然后他的领导跟我谈薪的时候我发现了一件事情: -他的领导好像在竭尽全力地打击我,他并不是明目张胆的否定 -而是要让我自我怀疑式地说出:你到底哪里好呢? -我过去一年,机器学习、深度学习、强化学习这三个框架的项目 -都要写烂了,我手上还有一个 900 star 的 get_jobs -我从来不会怀疑自己这些事情,面试官知道压不住我 -说后面通知我,然而我清楚地知道,是他们给不起! - -再来说说我现在的这个 offer,我的领导也在忽悠我 -说他们的系统有一些问题,他们已经解决了 -我就把过去一年,我做 AI 的过程中的一些技术栈 -分享了一些,比如向量数据库、本地微型大模型、API 这些都不用说了 -反正就是,我的每一场面试,我都会给自己一个心理暗示: - -> 我来你们公司,是给你们教东西的 - -每一场面试,都要成为我的单方面屠杀 -因为我学历不够,去不了大公司 -所以我接到的面试,都是小公司 -小公司,能找真正跑过大模型的又有几个人? - ---- - -## 关于简历和心态 - -自信,是一种“无”,是“我就站在这里” -浑身上下好像都充满了破绽,又都不是破绽 -这里有一个误区,并不是我告诉自己我有多厉害,我多牛逼 -自卑,往往是用自负表现出来的 - -面试官的打压,我知道,你们想压我的工资罢了 -可是我当过老板,你们该不会真的以为 -我不知道你们在想些什么吧? -压掉你的工资,说你做得不好 -让你玩命地干活,拿少少的钱 - -这就是那些老板最怕的东西 -只要你知道了这件事,就没有什么东西能压得住你 - -其次是简历,我的建议是:**尽可能多地写技术栈** -RAG 的机会,仅仅是我在项目写了一句话: -我给我上一家公司搭建了 RAG 知识库 -真的,在我看来这个东西太简单了,而且我没有足够的信心面试专业大模型工程师 -但还是写上去了,面试官看到了我这一个,立马联系我 -因为他们公司就在做这方面的事情 - ---- - -## 对抗偏见与突破焦虑 - -他们会说,你技术栈广而不精 -AI,就是这个时代带给我最大的礼物 -现在有了 AI,我可以立即吼回去:“你们公司连 AI 都不会用是吧?” - -其实很少有面试官这样问,但是我必须要这样想 -我要调整我自己的心态,面试可以输 -但是我的气势一定要压住你,只不过尽量不咄咄逼人 - -有一次一个面试,副总领导问我:“你做的这些项目都是学生的作业,你的优势在哪里?” -那一刻,这个公司,给我多少钱我都不会来 -我立刻反问:“那么公司给我的优势又在哪里?” -面试官的态度瞬间就变得和善了,虽然他们又让我使用 Rust 的 Tauri 打包他们的前端 -我做了,因为那时候我知道这个技术,还是我没用过 -即使他们是想占我便宜,但没关系,我学到了我自己的技术 -我用 GPT 指导我,就花了一个小时 -随便玩玩,你看,我又学会了一个技术 -技术,就是这样积累的 - ---- - -## 心理与自我认同 - -你需要记住你每一个做得很棒的瞬间 -肯定自己,认可自己 -这就是心理学,自我身份价值认同 -你的身份,其实永远都是自己给的 - -简历,要尽可能地事实求是 -在以前,我喜欢玩各种技术,我哪怕只写了两天的爬虫 -我也会写上去,重点是——可能有些面试官不熟悉爬虫 -即使我写了两天的三脚猫的爬虫水平 -对于面试官来说,也是他不会的东西 -我会你不会的东西,这就是技术 -我用技术帮你做你做不了的东西,这就是价值 - -我给你工作的价值,你给我工资的回报 -天经地义,两不相欠 -多一分我不要,少一分你免谈 - -请尽可能地去扩大自己的机会 -告诉自己,你想象的东西,真的没有多少会真实发生的 -只要你现在去观察,只要你现在去发现 - ---- - -## 个人介绍 & 后续计划 - -暂时先写到这里吧,目前在写一个机器学习雷达扫描的项目,后面开源,敬请期待 - -我简单介绍一下自己: -上海 9 年 Java / 1 年 AI 工程师 -5 年视觉中国摄影师 -自学心理学 12 年的双相症患者 -目前已完整自愈,并不是我找到工作才自愈,是我自愈,才找到了工作 -熟读心理学、周易、哲学、博弈论、社会学,略懂一些戏剧 -其实上面这些东西,都是通的 -你只要找到自己喜欢的,啃透,啃到底,其他的稍微用用 GPT -瞬间就学会了,真的 - ---- - -还有想对有些渣子说: -你们该不会真的以为 -我的收入,只有这 30k 的工资吧? - -我目前手上有的资源: -- 全上海所有的医院医生,只要我想联系,没有我联系不到 -- 高尔夫球场的项目,可以合作,也可以来玩,300/次 -- 上海的部分律师资源 -- 2021 年随心飞,全国除了台湾,我都去了 -- 全世界,其实都有我的朋友 - -我的每一位朋友,都是真朋友 -只要你认真地对待生活,**你的生活,就会认真地对待你** - -我的这些资源,如果你有想法,或者想合作,都可以联系我 -如果你真的需要帮助,也可以找我,只要我能做到的,我都会尽力去做 - ---- - -## 最后祝愿 - -最后我再重申,我只讨厌一种人: -就是脑子里只有钱的臭虫 - -**愿每一位在这篇文章里获取到能量的朋友:** -> “少侠,请立即开始你精彩的人生吧!” diff --git a/doc/part_job.md b/doc/part_job.md deleted file mode 100644 index a370cc8f..00000000 --- a/doc/part_job.md +++ /dev/null @@ -1,35 +0,0 @@ -# 如何使用本程序寻找兼职岗位 - -## 项目背景 - -在找兼职的过程中,我发现尽管在平台上勾选了兼职选项,但实际推荐的岗位中却依旧以全职工程师为主。针对这一点,我开发了这款程序——只需设置自己的期望岗位和求职类型为“兼职”,程序就能自动帮你筛选并投递真正的兼职岗位。 - -## 程序功能 - -- **自动筛选兼职岗位** - 修改配置文件后,程序会自动筛选出符合你期望的兼职岗位,避免投递那些全职岗位。 - -- **自动投递简历** - 程序会主动帮你投递简历,之后只需等待那些兼职岗位的回复即可。 - -## 使用方法 - -1. **修改配置文件** - 打开项目中的 `config.yaml` 文件,将 `jobType` 的值修改为 `"兼职"`: - ```yaml - jobType: "兼职" - ``` - 同时填写你的期望岗位信息。 - -2. **运行程序** - 程序会根据配置自动筛选和投递符合条件的兼职岗位。 - -## 项目链接与交流群 - -欢迎大家访问我的项目主页:[https://github.com/loks666/get_jobs](https://github.com/loks666/get_jobs) -项目中也提供了交流群链接,是否加入完全根据个人意愿。 - -## 致谢 - -我希望我的程序能够帮助大家光明正大地找到心仪的兼职工作,同时也期待项目能获得更多的 star 与支持。请放心,我不会利用此程序进行任何坑蒙拐骗或恶意引流行为。 -让我们携手前行,用技术实现梦想,用努力换取未来! \ No newline at end of file diff --git a/html/DroidSansMonoDotted.ttf b/html/DroidSansMonoDotted.ttf deleted file mode 100644 index e56a5ca7..00000000 Binary files a/html/DroidSansMonoDotted.ttf and /dev/null differ diff --git a/html/bootstrap.min.css b/html/bootstrap.min.css deleted file mode 100644 index 92791740..00000000 --- a/html/bootstrap.min.css +++ /dev/null @@ -1,12 +0,0 @@ -@charset "UTF-8";/*! - * Bootswatch v5.3.3 (https://bootswatch.com) - * Theme: sketchy - * Copyright 2012-2024 Thomas Park - * Licensed under MIT - * Based on Bootstrap -*//*! - * Bootstrap v5.3.3 (https://getbootstrap.com/) - * Copyright 2011-2024 The Bootstrap Authors - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */@import url(https://fonts.googleapis.com/css?family=Neucha|Cabin+Sketch&display=swap);:root,[data-bs-theme=light]{--bs-blue:#007bff;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#e83e8c;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#28a745;--bs-teal:#20c997;--bs-cyan:#17a2b8;--bs-black:#000;--bs-white:#fff;--bs-gray:#868e96;--bs-gray-dark:#333;--bs-gray-100:#f8f9fa;--bs-gray-200:#f7f7f9;--bs-gray-300:#dee2e6;--bs-gray-400:#ccc;--bs-gray-500:#aaa;--bs-gray-600:#868e96;--bs-gray-700:#555;--bs-gray-800:#333;--bs-gray-900:#212529;--bs-primary:#333;--bs-secondary:#555;--bs-success:#28a745;--bs-info:#17a2b8;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#fff;--bs-dark:#555;--bs-primary-rgb:51,51,51;--bs-secondary-rgb:85,85,85;--bs-success-rgb:40,167,69;--bs-info-rgb:23,162,184;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:255,255,255;--bs-dark-rgb:85,85,85;--bs-primary-text-emphasis:#141414;--bs-secondary-text-emphasis:#222222;--bs-success-text-emphasis:#10431c;--bs-info-text-emphasis:#09414a;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#555;--bs-dark-text-emphasis:#555;--bs-primary-bg-subtle:#d6d6d6;--bs-secondary-bg-subtle:#dddddd;--bs-success-bg-subtle:#d4edda;--bs-info-bg-subtle:#d1ecf1;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ccc;--bs-primary-border-subtle:#adadad;--bs-secondary-border-subtle:#bbbbbb;--bs-success-border-subtle:#a9dcb5;--bs-info-border-subtle:#a2dae3;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--bs-light-border-subtle:#f7f7f9;--bs-dark-border-subtle:#aaa;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:Neucha,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:700;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33,37,41;--bs-body-bg:#fff;--bs-body-bg-rgb:255,255,255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0,0,0;--bs-secondary-color:rgba(33, 37, 41, 0.75);--bs-secondary-color-rgb:33,37,41;--bs-secondary-bg:#f7f7f9;--bs-secondary-bg-rgb:247,247,249;--bs-tertiary-color:rgba(33, 37, 41, 0.5);--bs-tertiary-color-rgb:33,37,41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:inherit;--bs-link-color:#333;--bs-link-color-rgb:51,51,51;--bs-link-decoration:underline;--bs-link-hover-color:#292929;--bs-link-hover-color-rgb:41,41,41;--bs-code-color:#e83e8c;--bs-highlight-color:#212529;--bs-highlight-bg:#fff3cd;--bs-border-width:2px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:25px;--bs-border-radius-sm:15px;--bs-border-radius-lg:35px;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg:0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset:inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width:0.25rem;--bs-focus-ring-opacity:0.25;--bs-focus-ring-color:rgba(51, 51, 51, 0.25);--bs-form-valid-color:#28a745;--bs-form-valid-border-color:#28a745;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222,226,230;--bs-body-bg:#212529;--bs-body-bg-rgb:33,37,41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255,255,255;--bs-secondary-color:rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb:222,226,230;--bs-secondary-bg:#333;--bs-secondary-bg-rgb:51,51,51;--bs-tertiary-color:rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb:222,226,230;--bs-tertiary-bg:#2a2c2e;--bs-tertiary-bg-rgb:42,44,46;--bs-primary-text-emphasis:#858585;--bs-secondary-text-emphasis:#999999;--bs-success-text-emphasis:#7eca8f;--bs-info-text-emphasis:#74c7d4;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#0a0a0a;--bs-secondary-bg-subtle:#111111;--bs-success-bg-subtle:#08210e;--bs-info-bg-subtle:#052025;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#333;--bs-dark-bg-subtle:#1a1a1a;--bs-primary-border-subtle:#1f1f1f;--bs-secondary-border-subtle:#333333;--bs-success-border-subtle:#186429;--bs-info-border-subtle:#0e616e;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#555;--bs-dark-border-subtle:#333;--bs-heading-color:inherit;--bs-link-color:#858585;--bs-link-hover-color:#9d9d9d;--bs-link-color-rgb:133,133,133;--bs-link-hover-color-rgb:157,157,157;--bs-code-color:#f18bba;--bs-highlight-color:#dee2e6;--bs-highlight-bg:#664d03;--bs-border-color:#555;--bs-border-color-translucent:rgba(255, 255, 255, 0.15);--bs-form-valid-color:#7eca8f;--bs-form-valid-border-color:#7eca8f;--bs-form-invalid-color:#ea868f;--bs-form-invalid-border-color:#ea868f}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;border:0;border-top:var(--bs-border-width) solid;opacity:.25}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-family:"Cabin Sketch",cursive;font-weight:500;line-height:1.2;color:var(--bs-heading-color)}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.1875em;color:var(--bs-highlight-color);background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1));text-decoration:underline}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.1875rem .375rem;font-size:.875em;color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:15px}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-secondary-color);text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#868e96}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:var(--bs-secondary-color)}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{--bs-gutter-x:1.5rem;--bs-gutter-y:0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}:root{--bs-breakpoint-xs:0;--bs-breakpoint-sm:576px;--bs-breakpoint-md:768px;--bs-breakpoint-lg:992px;--bs-breakpoint-xl:1200px;--bs-breakpoint-xxl:1400px}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.66666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.66666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.66666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.66666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.66666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.66666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-color-type:initial;--bs-table-bg-type:initial;--bs-table-color-state:initial;--bs-table-bg-state:initial;--bs-table-color:var(--bs-emphasis-color);--bs-table-bg:var(--bs-body-bg);--bs-table-border-color:#333;--bs-table-accent-bg:transparent;--bs-table-striped-color:var(--bs-emphasis-color);--bs-table-striped-bg:rgba(var(--bs-emphasis-color-rgb), 0.05);--bs-table-active-color:var(--bs-emphasis-color);--bs-table-active-bg:rgba(var(--bs-emphasis-color-rgb), 0.1);--bs-table-hover-color:var(--bs-emphasis-color);--bs-table-hover-bg:#fff;width:100%;margin-bottom:1rem;vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem .5rem;color:var(--bs-table-color-state,var(--bs-table-color-type,var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:2px;box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state,var(--bs-table-bg-type,var(--bs-table-accent-bg)))}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(2px * 2) solid currentcolor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:2px 0}.table-bordered>:not(caption)>*>*{border-width:0 2px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-striped-columns>:not(caption)>tr>:nth-child(2n){--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-active{--bs-table-color-state:var(--bs-table-active-color);--bs-table-bg-state:var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state:var(--bs-table-hover-color);--bs-table-bg-state:var(--bs-table-hover-bg)}.table-primary{--bs-table-color:#000;--bs-table-bg:#d6d6d6;--bs-table-border-color:#ababab;--bs-table-striped-bg:#cbcbcb;--bs-table-striped-color:#fff;--bs-table-active-bg:#c1c1c1;--bs-table-active-color:#fff;--bs-table-hover-bg:#c6c6c6;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color:#000;--bs-table-bg:#dddddd;--bs-table-border-color:#b1b1b1;--bs-table-striped-bg:#d2d2d2;--bs-table-striped-color:#000;--bs-table-active-bg:#c7c7c7;--bs-table-active-color:#fff;--bs-table-hover-bg:#cccccc;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color:#000;--bs-table-bg:#d4edda;--bs-table-border-color:#aabeae;--bs-table-striped-bg:#c9e1cf;--bs-table-striped-color:#000;--bs-table-active-bg:#bfd5c4;--bs-table-active-color:#000;--bs-table-hover-bg:#c4dbca;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color:#000;--bs-table-bg:#d1ecf1;--bs-table-border-color:#a7bdc1;--bs-table-striped-bg:#c7e0e5;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd4d9;--bs-table-active-color:#000;--bs-table-hover-bg:#c1dadf;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color:#000;--bs-table-bg:#fff3cd;--bs-table-border-color:#ccc2a4;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color:#000;--bs-table-bg:#f8d7da;--bs-table-border-color:#c6acae;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#fff;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color:#000;--bs-table-bg:#fff;--bs-table-border-color:#cccccc;--bs-table-striped-bg:#f2f2f2;--bs-table-striped-color:#000;--bs-table-active-bg:#e6e6e6;--bs-table-active-color:#000;--bs-table-hover-bg:#ececec;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color:#fff;--bs-table-bg:#555;--bs-table-border-color:#777777;--bs-table-striped-bg:#5e5e5e;--bs-table-striped-color:#fff;--bs-table-active-bg:#666666;--bs-table-active-color:#fff;--bs-table-hover-bg:#626262;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + var(--bs-border-width));padding-bottom:calc(.375rem + var(--bs-border-width));margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + var(--bs-border-width));padding-bottom:calc(.5rem + var(--bs-border-width));font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + var(--bs-border-width));padding-bottom:calc(.25rem + var(--bs-border-width));font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:var(--bs-secondary-color)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:700;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-body-bg);background-clip:padding-box;border:var(--bs-border-width) solid #333;border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:var(--bs-body-bg);border-color:#333;outline:0;box-shadow:0 0 0 .25rem rgba(51,51,51,.25)}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::-moz-placeholder{color:var(--bs-secondary-color);opacity:1}.form-control::placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:disabled{background-color:var(--bs-secondary-bg);border-color:#868e96;opacity:1}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:var(--bs-secondary-bg)}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:var(--bs-body-color);background-color:transparent;border:solid transparent;border-width:var(--bs-border-width) 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2));padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2))}textarea.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-control-color{width:3rem;height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color::-webkit-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23333' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:700;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-body-bg);background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon,none);background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:var(--bs-border-width) solid #333;border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#333;outline:0;box-shadow:0 0 0 .25rem rgba(51,51,51,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{color:#868e96;background-color:var(--bs-secondary-bg);border-color:#868e96}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{padding-right:1.5em;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:-1.5em;margin-left:0}.form-check-input{--bs-form-check-bg:var(--bs-body-bg);flex-shrink:0;width:1em;height:1em;margin-top:.25em;vertical-align:top;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:center;background-size:contain;border:var(--bs-border-width) solid var(--bs-border-color);-webkit-print-color-adjust:exact;color-adjust:exact;print-color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#333;outline:0;box-shadow:0 0 0 .25rem rgba(51,51,51,.25)}.form-check-input:checked{background-color:#333;border-color:#333}.form-check-input:checked[type=checkbox]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#333;border-color:#333;--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23333'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.5rem;padding:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(51,51,51,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(51,51,51,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;-webkit-appearance:none;appearance:none;background-color:#333;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#c2c2c2}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;-moz-appearance:none;appearance:none;background-color:#333;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#c2c2c2}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(var(--bs-border-width) * 2));min-height:calc(3.5rem + calc(var(--bs-border-width) * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;height:100%;padding:1rem .75rem;overflow:hidden;text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:var(--bs-border-width) solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control-plaintext::-moz-placeholder,.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control-plaintext::placeholder,.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control-plaintext:not(:-moz-placeholder-shown),.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:-webkit-autofill,.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label,.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:not(:-moz-placeholder-shown)~label::after{position:absolute;inset:1rem 0.375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control-plaintext~label::after,.form-floating>.form-control:focus~label::after,.form-floating>.form-control:not(:placeholder-shown)~label::after,.form-floating>.form-select~label::after{position:absolute;inset:1rem 0.375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control:-webkit-autofill~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label{border-width:var(--bs-border-width) 0}.form-floating>.form-control:disabled~label,.form-floating>:disabled~label{color:#868e96}.form-floating>.form-control:disabled~label::after,.form-floating>:disabled~label::after{background-color:var(--bs-secondary-bg)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-floating,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-floating:focus-within,.input-group>.form-select:focus{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:700;line-height:1.5;color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:var(--bs-tertiary-bg);border:var(--bs-border-width) solid #333;border-radius:var(--bs-border-radius)}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select,.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select,.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(var(--bs-border-width) * -1);border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-valid-color)}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-success);border-radius:var(--bs-border-radius)}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:var(--bs-form-valid-border-color);padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:var(--bs-form-valid-border-color)}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-control-color.is-valid,.was-validated .form-control-color:valid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:var(--bs-form-valid-border-color)}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:var(--bs-form-valid-color)}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-valid,.input-group>.form-floating:not(:focus-within).is-valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-control:not(:focus):valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.was-validated .input-group>.form-select:not(:focus):valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-invalid-color)}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-danger);border-radius:var(--bs-border-radius)}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:var(--bs-form-invalid-border-color);padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:var(--bs-form-invalid-border-color)}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-control-color.is-invalid,.was-validated .form-control-color:invalid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:var(--bs-form-invalid-border-color)}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:var(--bs-form-invalid-color)}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-invalid,.input-group>.form-floating:not(:focus-within).is-invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-control:not(:focus):invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.was-validated .input-group>.form-select:not(:focus):invalid{z-index:4}.btn{--bs-btn-padding-x:0.75rem;--bs-btn-padding-y:0.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight:400;--bs-btn-line-height:1.5;--bs-btn-color:var(--bs-body-color);--bs-btn-bg:transparent;--bs-btn-border-width:var(--bs-border-width);--bs-btn-border-color:transparent;--bs-btn-border-radius:var(--bs-border-radius);--bs-btn-hover-border-color:transparent;--bs-btn-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.15),0 1px 1px rgba(0, 0, 0, 0.075);--bs-btn-disabled-opacity:0.65;--bs-btn-focus-box-shadow:0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,.btn.active,.btn.show,.btn:first-child:active,:not(.btn-check)+.btn:active{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible,.btn:first-child:active:focus-visible,:not(.btn-check)+.btn:active:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked:focus-visible+.btn{box-shadow:var(--bs-btn-focus-box-shadow)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#333;--bs-btn-border-color:#333;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#2b2b2b;--bs-btn-hover-border-color:#292929;--bs-btn-focus-shadow-rgb:82,82,82;--bs-btn-active-color:#fff;--bs-btn-active-bg:#292929;--bs-btn-active-border-color:#262626;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#333;--bs-btn-disabled-border-color:#333}.btn-secondary{--bs-btn-color:#fff;--bs-btn-bg:#555;--bs-btn-border-color:#555;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#484848;--bs-btn-hover-border-color:#444444;--bs-btn-focus-shadow-rgb:111,111,111;--bs-btn-active-color:#fff;--bs-btn-active-bg:#444444;--bs-btn-active-border-color:#404040;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#555;--bs-btn-disabled-border-color:#555}.btn-success{--bs-btn-color:#fff;--bs-btn-bg:#28a745;--bs-btn-border-color:#28a745;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#228e3b;--bs-btn-hover-border-color:#208637;--bs-btn-focus-shadow-rgb:72,180,97;--bs-btn-active-color:#fff;--bs-btn-active-bg:#208637;--bs-btn-active-border-color:#1e7d34;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#28a745;--bs-btn-disabled-border-color:#28a745}.btn-info{--bs-btn-color:#fff;--bs-btn-bg:#17a2b8;--bs-btn-border-color:#17a2b8;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#148a9c;--bs-btn-hover-border-color:#128293;--bs-btn-focus-shadow-rgb:58,176,195;--bs-btn-active-color:#fff;--bs-btn-active-bg:#128293;--bs-btn-active-border-color:#117a8a;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#17a2b8;--bs-btn-disabled-border-color:#17a2b8}.btn-warning{--bs-btn-color:#fff;--bs-btn-bg:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#d9a406;--bs-btn-hover-border-color:#cc9a06;--bs-btn-focus-shadow-rgb:255,202,44;--bs-btn-active-color:#fff;--bs-btn-active-bg:#cc9a06;--bs-btn-active-border-color:#bf9105;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#ffc107;--bs-btn-disabled-border-color:#ffc107}.btn-danger{--bs-btn-color:#fff;--bs-btn-bg:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#bb2d3b;--bs-btn-hover-border-color:#b02a37;--bs-btn-focus-shadow-rgb:225,83,97;--bs-btn-active-color:#fff;--bs-btn-active-bg:#b02a37;--bs-btn-active-border-color:#a52834;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#dc3545;--bs-btn-disabled-border-color:#dc3545}.btn-light{--bs-btn-color:#000;--bs-btn-bg:#fff;--bs-btn-border-color:#fff;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#d9d9d9;--bs-btn-hover-border-color:#cccccc;--bs-btn-focus-shadow-rgb:217,217,217;--bs-btn-active-color:#fff;--bs-btn-active-bg:#cccccc;--bs-btn-active-border-color:#bfbfbf;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#fff;--bs-btn-disabled-border-color:#fff}.btn-dark{--bs-btn-color:#fff;--bs-btn-bg:#555;--bs-btn-border-color:#555;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6f6f6f;--bs-btn-hover-border-color:#666666;--bs-btn-focus-shadow-rgb:111,111,111;--bs-btn-active-color:#fff;--bs-btn-active-bg:#777777;--bs-btn-active-border-color:#666666;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#555;--bs-btn-disabled-border-color:#555}.btn-outline-primary{--bs-btn-color:#333;--bs-btn-border-color:#333;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#333;--bs-btn-hover-border-color:#333;--bs-btn-focus-shadow-rgb:51,51,51;--bs-btn-active-color:#fff;--bs-btn-active-bg:#333;--bs-btn-active-border-color:#333;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#333;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#333;--bs-gradient:none}.btn-outline-secondary{--bs-btn-color:#555;--bs-btn-border-color:#555;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#555;--bs-btn-hover-border-color:#555;--bs-btn-focus-shadow-rgb:85,85,85;--bs-btn-active-color:#fff;--bs-btn-active-bg:#555;--bs-btn-active-border-color:#555;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#555;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#555;--bs-gradient:none}.btn-outline-success{--bs-btn-color:#28a745;--bs-btn-border-color:#28a745;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#28a745;--bs-btn-hover-border-color:#28a745;--bs-btn-focus-shadow-rgb:40,167,69;--bs-btn-active-color:#fff;--bs-btn-active-bg:#28a745;--bs-btn-active-border-color:#28a745;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#28a745;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#28a745;--bs-gradient:none}.btn-outline-info{--bs-btn-color:#17a2b8;--bs-btn-border-color:#17a2b8;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#17a2b8;--bs-btn-hover-border-color:#17a2b8;--bs-btn-focus-shadow-rgb:23,162,184;--bs-btn-active-color:#fff;--bs-btn-active-bg:#17a2b8;--bs-btn-active-border-color:#17a2b8;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#17a2b8;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#17a2b8;--bs-gradient:none}.btn-outline-warning{--bs-btn-color:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#ffc107;--bs-btn-hover-border-color:#ffc107;--bs-btn-focus-shadow-rgb:255,193,7;--bs-btn-active-color:#fff;--bs-btn-active-bg:#ffc107;--bs-btn-active-border-color:#ffc107;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#ffc107;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#ffc107;--bs-gradient:none}.btn-outline-danger{--bs-btn-color:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#dc3545;--bs-btn-hover-border-color:#dc3545;--bs-btn-focus-shadow-rgb:220,53,69;--bs-btn-active-color:#fff;--bs-btn-active-bg:#dc3545;--bs-btn-active-border-color:#dc3545;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#dc3545;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#dc3545;--bs-gradient:none}.btn-outline-light{--bs-btn-color:#fff;--bs-btn-border-color:#fff;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#fff;--bs-btn-hover-border-color:#fff;--bs-btn-focus-shadow-rgb:255,255,255;--bs-btn-active-color:#000;--bs-btn-active-bg:#fff;--bs-btn-active-border-color:#fff;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#fff;--bs-gradient:none}.btn-outline-dark{--bs-btn-color:#555;--bs-btn-border-color:#555;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#555;--bs-btn-hover-border-color:#555;--bs-btn-focus-shadow-rgb:85,85,85;--bs-btn-active-color:#fff;--bs-btn-active-bg:#555;--bs-btn-active-border-color:#555;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#555;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#555;--bs-gradient:none}.btn-link{--bs-btn-font-weight:400;--bs-btn-color:var(--bs-link-color);--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-link-hover-color);--bs-btn-hover-border-color:transparent;--bs-btn-active-color:var(--bs-link-hover-color);--bs-btn-active-border-color:transparent;--bs-btn-disabled-color:#868e96;--bs-btn-disabled-border-color:transparent;--bs-btn-box-shadow:0 0 0 #000;--bs-btn-focus-shadow-rgb:82,82,82;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-group-lg>.btn,.btn-lg{--bs-btn-padding-y:0.5rem;--bs-btn-padding-x:1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius:var(--bs-border-radius-lg)}.btn-group-sm>.btn,.btn-sm{--bs-btn-padding-y:0.25rem;--bs-btn-padding-x:0.5rem;--bs-btn-font-size:0.875rem;--bs-btn-border-radius:var(--bs-border-radius-sm)}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropdown-center,.dropend,.dropstart,.dropup,.dropup-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex:1000;--bs-dropdown-min-width:10rem;--bs-dropdown-padding-x:0;--bs-dropdown-padding-y:0.5rem;--bs-dropdown-spacer:0.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color:var(--bs-body-color);--bs-dropdown-bg:var(--bs-body-bg);--bs-dropdown-border-color:#333;--bs-dropdown-border-radius:var(--bs-border-radius);--bs-dropdown-border-width:var(--bs-border-width);--bs-dropdown-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg:#333;--bs-dropdown-divider-margin-y:0.5rem;--bs-dropdown-box-shadow:var(--bs-box-shadow);--bs-dropdown-link-color:var(--bs-body-color);--bs-dropdown-link-hover-color:#fff;--bs-dropdown-link-hover-bg:#333;--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#333;--bs-dropdown-link-disabled-color:var(--bs-tertiary-color);--bs-dropdown-item-padding-x:1rem;--bs-dropdown-item-padding-y:0.25rem;--bs-dropdown-header-color:#868e96;--bs-dropdown-header-padding-x:1rem;--bs-dropdown-header-padding-y:0.5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0;border-radius:var(--bs-dropdown-item-border-radius,0)}.dropdown-item:focus,.dropdown-item:hover{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color:#dee2e6;--bs-dropdown-bg:#333;--bs-dropdown-border-color:#333;--bs-dropdown-box-shadow: ;--bs-dropdown-link-color:#dee2e6;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-divider-bg:#333;--bs-dropdown-link-hover-bg:rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#333;--bs-dropdown-link-disabled-color:#aaa;--bs-dropdown-header-color:#aaa}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:var(--bs-border-radius)}.btn-group>.btn-group:not(:first-child),.btn-group>:not(.btn-check:first-child)+.btn{margin-left:calc(var(--bs-border-width) * -1)}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:calc(var(--bs-border-width) * -1)}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x:1rem;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-link-color);--bs-nav-link-hover-color:var(--bs-link-hover-color);--bs-nav-link-disabled-color:var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);text-decoration:none;background:0 0;border:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem rgba(51,51,51,.25)}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width:var(--bs-border-width);--bs-nav-tabs-border-color:#333;--bs-nav-tabs-border-radius:var(--bs-border-radius);--bs-nav-tabs-link-hover-border-color:#333;--bs-nav-tabs-link-active-color:#333;--bs-nav-tabs-link-active-bg:var(--bs-body-bg);--bs-nav-tabs-link-active-border-color:#333;border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius:var(--bs-border-radius);--bs-nav-pills-link-active-color:#fff;--bs-nav-pills-link-active-bg:#333}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap:1rem;--bs-nav-underline-border-width:0.125rem;--bs-nav-underline-link-active-color:var(--bs-emphasis-color);gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{padding-right:0;padding-left:0;border-bottom:var(--bs-nav-underline-border-width) solid transparent}.nav-underline .nav-link:focus,.nav-underline .nav-link:hover{border-bottom-color:currentcolor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{font-weight:700;color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentcolor}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x:0;--bs-navbar-padding-y:0.5rem;--bs-navbar-color:rgba(var(--bs-emphasis-color-rgb), 0.65);--bs-navbar-hover-color:rgba(var(--bs-emphasis-color-rgb), 0.8);--bs-navbar-disabled-color:rgba(var(--bs-emphasis-color-rgb), 0.3);--bs-navbar-active-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-padding-y:0.3125rem;--bs-navbar-brand-margin-end:1rem;--bs-navbar-brand-font-size:1.25rem;--bs-navbar-brand-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-hover-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-nav-link-padding-x:0.5rem;--bs-navbar-toggler-padding-y:0.25rem;--bs-navbar-toggler-padding-x:0.75rem;--bs-navbar-toggler-font-size:1.25rem;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color:rgba(var(--bs-emphasis-color-rgb), 0.15);--bs-navbar-toggler-border-radius:var(--bs-border-radius);--bs-navbar-toggler-focus-width:0.25rem;--bs-navbar-toggler-transition:box-shadow 0.15s ease-in-out;position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);text-decoration:none;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-navbar-color);--bs-nav-link-hover-color:var(--bs-navbar-hover-color);--bs-nav-link-disabled-color:var(--bs-navbar-disabled-color);display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:focus,.navbar-text a:hover{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:transparent;border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition)}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color:rgba(255, 255, 255, 0.55);--bs-navbar-hover-color:rgba(255, 255, 255, 0.75);--bs-navbar-disabled-color:rgba(255, 255, 255, 0.25);--bs-navbar-active-color:#fff;--bs-navbar-brand-color:#fff;--bs-navbar-brand-hover-color:#fff;--bs-navbar-toggler-border-color:rgba(255, 255, 255, 0.1);--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y:1rem;--bs-card-spacer-x:1rem;--bs-card-title-spacer-y:0.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width:2px;--bs-card-border-color:#333;--bs-card-border-radius:var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius:calc(var(--bs-border-radius) - 2px);--bs-card-cap-padding-y:0.5rem;--bs-card-cap-padding-x:1rem;--bs-card-cap-bg:rgba(var(--bs-body-color-rgb), 0.03);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg:var(--bs-body-bg);--bs-card-img-overlay-padding:1rem;--bs-card-group-margin:0.75rem;position:relative;display:flex;flex-direction:column;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width) solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));margin-bottom:0;color:var(--bs-card-subtitle-color)}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion{--bs-accordion-color:var(--bs-body-color);--bs-accordion-bg:var(--bs-body-bg);--bs-accordion-transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out,border-radius 0.15s ease;--bs-accordion-border-color:var(--bs-border-color);--bs-accordion-border-width:var(--bs-border-width);--bs-accordion-border-radius:var(--bs-border-radius);--bs-accordion-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-accordion-btn-padding-x:1.25rem;--bs-accordion-btn-padding-y:1rem;--bs-accordion-btn-color:var(--bs-body-color);--bs-accordion-btn-bg:var(--bs-accordion-bg);--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width:1.25rem;--bs-accordion-btn-icon-transform:rotate(-180deg);--bs-accordion-btn-icon-transition:transform 0.2s ease-in-out;--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23141414' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");--bs-accordion-btn-focus-box-shadow:0 0 0 0.25rem rgba(51, 51, 51, 0.25);--bs-accordion-body-padding-x:1.25rem;--bs-accordion-body-padding-y:1rem;--bs-accordion-active-color:var(--bs-primary-text-emphasis);--bs-accordion-active-bg:var(--bs-primary-bg-subtle)}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);font-size:1rem;color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);border:0;border-radius:0;overflow-anchor:none;transition:var(--bs-accordion-transition)}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed)::after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button::after{flex-shrink:0;width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);margin-left:auto;content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition)}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;outline:0;box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:first-of-type{border-top-left-radius:var(--bs-accordion-border-radius);border-top-right-radius:var(--bs-accordion-border-radius)}.accordion-item:first-of-type>.accordion-header .accordion-button{border-top-left-radius:var(--bs-accordion-inner-border-radius);border-top-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-item:last-of-type>.accordion-header .accordion-button.collapsed{border-bottom-right-radius:var(--bs-accordion-inner-border-radius);border-bottom-left-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:last-of-type>.accordion-collapse{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush>.accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush>.accordion-item:first-child{border-top:0}.accordion-flush>.accordion-item:last-child{border-bottom:0}.accordion-flush>.accordion-item>.accordion-header .accordion-button,.accordion-flush>.accordion-item>.accordion-header .accordion-button.collapsed{border-radius:0}.accordion-flush>.accordion-item>.accordion-collapse{border-radius:0}[data-bs-theme=dark] .accordion-button::after{--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23858585'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23858585'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x:0.75rem;--bs-breadcrumb-padding-y:0.375rem;--bs-breadcrumb-margin-bottom:1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius:0.25rem;--bs-breadcrumb-divider-color:#333;--bs-breadcrumb-item-padding-x:0.5rem;--bs-breadcrumb-item-active-color:#333;display:flex;flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x:0.75rem;--bs-pagination-padding-y:0.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color:var(--bs-link-color);--bs-pagination-bg:var(--bs-body-bg);--bs-pagination-border-width:var(--bs-border-width);--bs-pagination-border-color:#333;--bs-pagination-border-radius:var(--bs-border-radius);--bs-pagination-hover-color:#fff;--bs-pagination-hover-bg:#333;--bs-pagination-hover-border-color:#333;--bs-pagination-focus-color:var(--bs-link-hover-color);--bs-pagination-focus-bg:var(--bs-secondary-bg);--bs-pagination-focus-box-shadow:0 0 0 0.25rem rgba(51, 51, 51, 0.25);--bs-pagination-active-color:#fff;--bs-pagination-active-bg:#333;--bs-pagination-active-border-color:#333;--bs-pagination-disabled-color:#ccc;--bs-pagination-disabled-bg:var(--bs-secondary-bg);--bs-pagination-disabled-border-color:#333;display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);text-decoration:none;background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.active>.page-link,.page-link.active{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.disabled>.page-link,.page-link.disabled{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(var(--bs-border-width) * -1)}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x:1.5rem;--bs-pagination-padding-y:0.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius:var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x:0.5rem;--bs-pagination-padding-y:0.25rem;--bs-pagination-font-size:0.875rem;--bs-pagination-border-radius:var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x:1.2em;--bs-badge-padding-y:0.5em;--bs-badge-font-size:0.75em;--bs-badge-font-weight:700;--bs-badge-color:#fff;--bs-badge-border-radius:var(--bs-border-radius);display:inline-block;padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius)}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg:transparent;--bs-alert-padding-x:1rem;--bs-alert-padding-y:1rem;--bs-alert-margin-bottom:1rem;--bs-alert-color:inherit;--bs-alert-border-color:transparent;--bs-alert-border:var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius:var(--bs-border-radius);--bs-alert-link-color:inherit;position:relative;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius)}.alert-heading{color:inherit}.alert-link{font-weight:700;color:var(--bs-alert-link-color)}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{--bs-alert-color:var(--bs-primary-text-emphasis);--bs-alert-bg:var(--bs-primary-bg-subtle);--bs-alert-border-color:var(--bs-primary-border-subtle);--bs-alert-link-color:var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color:var(--bs-secondary-text-emphasis);--bs-alert-bg:var(--bs-secondary-bg-subtle);--bs-alert-border-color:var(--bs-secondary-border-subtle);--bs-alert-link-color:var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color:var(--bs-success-text-emphasis);--bs-alert-bg:var(--bs-success-bg-subtle);--bs-alert-border-color:var(--bs-success-border-subtle);--bs-alert-link-color:var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color:var(--bs-info-text-emphasis);--bs-alert-bg:var(--bs-info-bg-subtle);--bs-alert-border-color:var(--bs-info-border-subtle);--bs-alert-link-color:var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color:var(--bs-warning-text-emphasis);--bs-alert-bg:var(--bs-warning-bg-subtle);--bs-alert-border-color:var(--bs-warning-border-subtle);--bs-alert-link-color:var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color:var(--bs-danger-text-emphasis);--bs-alert-bg:var(--bs-danger-bg-subtle);--bs-alert-border-color:var(--bs-danger-border-subtle);--bs-alert-link-color:var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color:var(--bs-light-text-emphasis);--bs-alert-bg:var(--bs-light-bg-subtle);--bs-alert-border-color:var(--bs-light-border-subtle);--bs-alert-link-color:var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color:var(--bs-dark-text-emphasis);--bs-alert-bg:var(--bs-dark-bg-subtle);--bs-alert-border-color:var(--bs-dark-border-subtle);--bs-alert-link-color:var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress,.progress-stacked{--bs-progress-height:1rem;--bs-progress-font-size:0.75rem;--bs-progress-bg:#fff;--bs-progress-border-radius:var(--bs-border-radius);--bs-progress-box-shadow:var(--bs-box-shadow-inset);--bs-progress-bar-color:#fff;--bs-progress-bar-bg:#ccc;--bs-progress-bar-transition:width 0.6s ease;display:flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius)}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color:var(--bs-body-color);--bs-list-group-bg:var(--bs-body-bg);--bs-list-group-border-color:#333;--bs-list-group-border-width:var(--bs-border-width);--bs-list-group-border-radius:var(--bs-border-radius);--bs-list-group-item-padding-x:1rem;--bs-list-group-item-padding-y:0.5rem;--bs-list-group-action-color:#333;--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:#dee2e6;--bs-list-group-action-active-color:var(--bs-body-color);--bs-list-group-action-active-bg:var(--bs-secondary-bg);--bs-list-group-disabled-color:var(--bs-secondary-color);--bs-list-group-disabled-bg:var(--bs-body-bg);--bs-list-group-active-color:#fff;--bs-list-group-active-bg:#333;--bs-list-group-active-border-color:#333;display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:var(--bs-list-group-border-radius)}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);text-decoration:none;background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{--bs-list-group-color:var(--bs-primary-text-emphasis);--bs-list-group-bg:var(--bs-primary-bg-subtle);--bs-list-group-border-color:var(--bs-primary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-primary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-primary-border-subtle);--bs-list-group-active-color:var(--bs-primary-bg-subtle);--bs-list-group-active-bg:var(--bs-primary-text-emphasis);--bs-list-group-active-border-color:var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color:var(--bs-secondary-text-emphasis);--bs-list-group-bg:var(--bs-secondary-bg-subtle);--bs-list-group-border-color:var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-secondary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-secondary-border-subtle);--bs-list-group-active-color:var(--bs-secondary-bg-subtle);--bs-list-group-active-bg:var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color:var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color:var(--bs-success-text-emphasis);--bs-list-group-bg:var(--bs-success-bg-subtle);--bs-list-group-border-color:var(--bs-success-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-success-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-success-border-subtle);--bs-list-group-active-color:var(--bs-success-bg-subtle);--bs-list-group-active-bg:var(--bs-success-text-emphasis);--bs-list-group-active-border-color:var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color:var(--bs-info-text-emphasis);--bs-list-group-bg:var(--bs-info-bg-subtle);--bs-list-group-border-color:var(--bs-info-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-info-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-info-border-subtle);--bs-list-group-active-color:var(--bs-info-bg-subtle);--bs-list-group-active-bg:var(--bs-info-text-emphasis);--bs-list-group-active-border-color:var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color:var(--bs-warning-text-emphasis);--bs-list-group-bg:var(--bs-warning-bg-subtle);--bs-list-group-border-color:var(--bs-warning-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-warning-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-warning-border-subtle);--bs-list-group-active-color:var(--bs-warning-bg-subtle);--bs-list-group-active-bg:var(--bs-warning-text-emphasis);--bs-list-group-active-border-color:var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color:var(--bs-danger-text-emphasis);--bs-list-group-bg:var(--bs-danger-bg-subtle);--bs-list-group-border-color:var(--bs-danger-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-danger-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-danger-border-subtle);--bs-list-group-active-color:var(--bs-danger-bg-subtle);--bs-list-group-active-bg:var(--bs-danger-text-emphasis);--bs-list-group-active-border-color:var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color:var(--bs-light-text-emphasis);--bs-list-group-bg:var(--bs-light-bg-subtle);--bs-list-group-border-color:var(--bs-light-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-light-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-light-border-subtle);--bs-list-group-active-color:var(--bs-light-bg-subtle);--bs-list-group-active-bg:var(--bs-light-text-emphasis);--bs-list-group-active-border-color:var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color:var(--bs-dark-text-emphasis);--bs-list-group-bg:var(--bs-dark-bg-subtle);--bs-list-group-border-color:var(--bs-dark-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-dark-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-dark-border-subtle);--bs-list-group-active-color:var(--bs-dark-bg-subtle);--bs-list-group-active-bg:var(--bs-dark-text-emphasis);--bs-list-group-active-border-color:var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color:inherit;--bs-btn-close-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='inherit'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");--bs-btn-close-opacity:1;--bs-btn-close-hover-opacity:1;--bs-btn-close-focus-shadow:none;--bs-btn-close-focus-opacity:1;--bs-btn-close-disabled-opacity:0.25;--bs-btn-close-white-filter:invert(1) grayscale(100%) brightness(200%);box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:var(--bs-btn-close-color);background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;border:0;border-radius:25px;opacity:var(--bs-btn-close-opacity)}.btn-close:hover{color:var(--bs-btn-close-color);text-decoration:none;opacity:var(--bs-btn-close-hover-opacity)}.btn-close:focus{outline:0;box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity)}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{filter:var(--bs-btn-close-white-filter)}[data-bs-theme=dark] .btn-close{filter:var(--bs-btn-close-white-filter)}.toast{--bs-toast-zindex:1090;--bs-toast-padding-x:0.75rem;--bs-toast-padding-y:0.5rem;--bs-toast-spacing:1.5rem;--bs-toast-max-width:350px;--bs-toast-font-size:0.875rem;--bs-toast-color: ;--bs-toast-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-border-width:2px;--bs-toast-border-color:#333;--bs-toast-border-radius:var(--bs-border-radius);--bs-toast-box-shadow:var(--bs-box-shadow);--bs-toast-header-color:#333;--bs-toast-header-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-header-border-color:#333;width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex:1090;position:absolute;z-index:var(--bs-toast-zindex);width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;align-items:center;padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width))}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex:1055;--bs-modal-width:500px;--bs-modal-padding:1rem;--bs-modal-margin:0.5rem;--bs-modal-color: ;--bs-modal-bg:var(--bs-body-bg);--bs-modal-border-color:#333;--bs-modal-border-width:var(--bs-border-width);--bs-modal-border-radius:var(--bs-border-radius-lg);--bs-modal-box-shadow:var(--bs-box-shadow-sm);--bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));--bs-modal-header-padding-x:1rem;--bs-modal-header-padding-y:1rem;--bs-modal-header-padding:1rem 1rem;--bs-modal-header-border-color:#333;--bs-modal-header-border-width:var(--bs-border-width);--bs-modal-title-line-height:1.5;--bs-modal-footer-gap:0.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color:#333;--bs-modal-footer-border-width:var(--bs-border-width);position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - var(--bs-modal-margin) * 2)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);outline:0}.modal-backdrop{--bs-backdrop-zindex:1050;--bs-backdrop-bg:#000;--bs-backdrop-opacity:0.5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;flex-shrink:0;align-items:center;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5) calc(var(--bs-modal-header-padding-x) * .5);margin:calc(-.5 * var(--bs-modal-header-padding-y)) calc(-.5 * var(--bs-modal-header-padding-x)) calc(-.5 * var(--bs-modal-header-padding-y)) auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:1 1 auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media (min-width:576px){.modal{--bs-modal-margin:1.75rem;--bs-modal-box-shadow:var(--bs-box-shadow)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{--bs-modal-width:800px}}@media (min-width:1200px){.modal-xl{--bs-modal-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-footer,.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-footer,.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-footer,.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-footer,.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-footer,.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-footer,.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex:1080;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:0.5rem;--bs-tooltip-padding-y:0.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:0.875rem;--bs-tooltip-color:var(--bs-body-bg);--bs-tooltip-bg:var(--bs-emphasis-color);--bs-tooltip-border-radius:var(--bs-border-radius);--bs-tooltip-opacity:0.9;--bs-tooltip-arrow-width:0.8rem;--bs-tooltip-arrow-height:0.4rem;z-index:var(--bs-tooltip-zindex);display:block;margin:var(--bs-tooltip-margin);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{display:block;width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height)}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-top-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-right-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) 0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg)}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg);border-radius:var(--bs-tooltip-border-radius)}.popover{--bs-popover-zindex:1070;--bs-popover-max-width:276px;--bs-popover-font-size:0.875rem;--bs-popover-bg:var(--bs-body-bg);--bs-popover-border-width:var(--bs-border-width);--bs-popover-border-color:#333;--bs-popover-border-radius:var(--bs-border-radius-lg);--bs-popover-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow:var(--bs-box-shadow);--bs-popover-header-padding-x:1rem;--bs-popover-header-padding-y:0.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color:inherit;--bs-popover-header-bg:var(--bs-secondary-bg);--bs-popover-body-padding-x:1rem;--bs-popover-body-padding-y:1rem;--bs-popover-body-color:var(--bs-body-color);--bs-popover-arrow-width:1rem;--bs-popover-arrow-height:0.5rem;--bs-popover-arrow-border:var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid;border-width:0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-top>.popover-arrow::before{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-end>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::before{border-width:0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-start>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}[data-bs-theme=dark] .carousel .carousel-control-next-icon,[data-bs-theme=dark] .carousel .carousel-control-prev-icon,[data-bs-theme=dark].carousel .carousel-control-next-icon,[data-bs-theme=dark].carousel .carousel-control-prev-icon{filter:invert(1) grayscale(100)}[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target],[data-bs-theme=dark].carousel .carousel-indicators [data-bs-target]{background-color:#000}[data-bs-theme=dark] .carousel .carousel-caption,[data-bs-theme=dark].carousel .carousel-caption{color:#000}.spinner-border,.spinner-grow{display:inline-block;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name)}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-border-width:0.25em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:transparent}.spinner-border-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem;--bs-spinner-border-width:0.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed:1.5s}}.offcanvas,.offcanvas-lg,.offcanvas-md,.offcanvas-sm,.offcanvas-xl,.offcanvas-xxl{--bs-offcanvas-zindex:1045;--bs-offcanvas-width:400px;--bs-offcanvas-height:30vh;--bs-offcanvas-padding-x:1rem;--bs-offcanvas-padding-y:1rem;--bs-offcanvas-color:var(--bs-body-color);--bs-offcanvas-bg:var(--bs-body-bg);--bs-offcanvas-border-width:var(--bs-border-width);--bs-offcanvas-border-color:#333;--bs-offcanvas-box-shadow:var(--bs-box-shadow-sm);--bs-offcanvas-transition:transform 0.3s ease-in-out;--bs-offcanvas-title-line-height:1.5}@media (max-width:575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:575.98px) and (prefers-reduced-motion:reduce){.offcanvas-sm{transition:none}}@media (max-width:575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-sm.show:not(.hiding),.offcanvas-sm.showing{transform:none}.offcanvas-sm.hiding,.offcanvas-sm.show,.offcanvas-sm.showing{visibility:visible}}@media (min-width:576px){.offcanvas-sm{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:767.98px) and (prefers-reduced-motion:reduce){.offcanvas-md{transition:none}}@media (max-width:767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-md.show:not(.hiding),.offcanvas-md.showing{transform:none}.offcanvas-md.hiding,.offcanvas-md.show,.offcanvas-md.showing{visibility:visible}}@media (min-width:768px){.offcanvas-md{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:991.98px) and (prefers-reduced-motion:reduce){.offcanvas-lg{transition:none}}@media (max-width:991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-lg.show:not(.hiding),.offcanvas-lg.showing{transform:none}.offcanvas-lg.hiding,.offcanvas-lg.show,.offcanvas-lg.showing{visibility:visible}}@media (min-width:992px){.offcanvas-lg{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:1199.98px) and (prefers-reduced-motion:reduce){.offcanvas-xl{transition:none}}@media (max-width:1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xl.show:not(.hiding),.offcanvas-xl.showing{transform:none}.offcanvas-xl.hiding,.offcanvas-xl.show,.offcanvas-xl.showing{visibility:visible}}@media (min-width:1200px){.offcanvas-xl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:1399.98px) and (prefers-reduced-motion:reduce){.offcanvas-xxl{transition:none}}@media (max-width:1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xxl.show:not(.hiding),.offcanvas-xxl.showing{transform:none}.offcanvas-xxl.hiding,.offcanvas-xxl.show,.offcanvas-xxl.showing{visibility:visible}}@media (min-width:1400px){.offcanvas-xxl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.show:not(.hiding),.offcanvas.showing{transform:none}.offcanvas.hiding,.offcanvas.show,.offcanvas.showing{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5) calc(var(--bs-offcanvas-padding-x) * .5);margin:calc(-.5 * var(--bs-offcanvas-padding-y)) calc(-.5 * var(--bs-offcanvas-padding-x)) calc(-.5 * var(--bs-offcanvas-padding-y)) auto}.offcanvas-title{margin-bottom:0;line-height:var(--bs-offcanvas-title-line-height)}.offcanvas-body{flex-grow:1;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);overflow-y:auto}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentcolor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.text-bg-primary{color:#fff!important;background-color:RGBA(var(--bs-primary-rgb),var(--bs-bg-opacity,1))!important}.text-bg-secondary{color:#fff!important;background-color:RGBA(var(--bs-secondary-rgb),var(--bs-bg-opacity,1))!important}.text-bg-success{color:#fff!important;background-color:RGBA(var(--bs-success-rgb),var(--bs-bg-opacity,1))!important}.text-bg-info{color:#fff!important;background-color:RGBA(var(--bs-info-rgb),var(--bs-bg-opacity,1))!important}.text-bg-warning{color:#fff!important;background-color:RGBA(var(--bs-warning-rgb),var(--bs-bg-opacity,1))!important}.text-bg-danger{color:#fff!important;background-color:RGBA(var(--bs-danger-rgb),var(--bs-bg-opacity,1))!important}.text-bg-light{color:#000!important;background-color:RGBA(var(--bs-light-rgb),var(--bs-bg-opacity,1))!important}.text-bg-dark{color:#fff!important;background-color:RGBA(var(--bs-dark-rgb),var(--bs-bg-opacity,1))!important}.link-primary{color:RGBA(var(--bs-primary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important}.link-primary:focus,.link-primary:hover{color:RGBA(41,41,41,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(41,41,41,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(41,41,41,var(--bs-link-underline-opacity,1))!important}.link-secondary{color:RGBA(var(--bs-secondary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important}.link-secondary:focus,.link-secondary:hover{color:RGBA(68,68,68,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(68,68,68,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(68,68,68,var(--bs-link-underline-opacity,1))!important}.link-success{color:RGBA(var(--bs-success-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important}.link-success:focus,.link-success:hover{color:RGBA(32,134,55,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(32,134,55,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(32,134,55,var(--bs-link-underline-opacity,1))!important}.link-info{color:RGBA(var(--bs-info-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important}.link-info:focus,.link-info:hover{color:RGBA(18,130,147,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(18,130,147,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(18,130,147,var(--bs-link-underline-opacity,1))!important}.link-warning{color:RGBA(var(--bs-warning-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important}.link-warning:focus,.link-warning:hover{color:RGBA(204,154,6,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(204,154,6,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(204,154,6,var(--bs-link-underline-opacity,1))!important}.link-danger{color:RGBA(var(--bs-danger-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important}.link-danger:focus,.link-danger:hover{color:RGBA(176,42,55,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(176,42,55,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(176,42,55,var(--bs-link-underline-opacity,1))!important}.link-light{color:RGBA(var(--bs-light-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important}.link-light:focus,.link-light:hover{color:RGBA(255,255,255,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(255,255,255,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(255,255,255,var(--bs-link-underline-opacity,1))!important}.link-dark{color:RGBA(var(--bs-dark-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important}.link-dark:focus,.link-dark:hover{color:RGBA(68,68,68,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(68,68,68,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(68,68,68,var(--bs-link-underline-opacity,1))!important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-body-emphasis:focus,.link-body-emphasis:hover{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,.75))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,0.75))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,0.75))!important}.focus-ring:focus{outline:0;box-shadow:var(--bs-focus-ring-x,0) var(--bs-focus-ring-y,0) var(--bs-focus-ring-blur,0) var(--bs-focus-ring-width) var(--bs-focus-ring-color)}.icon-link{display:inline-flex;gap:.375rem;align-items:center;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,0.5));text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,0.5));text-underline-offset:0.25em;-webkit-backface-visibility:hidden;backface-visibility:hidden}.icon-link>.bi{flex-shrink:0;width:1em;height:1em;fill:currentcolor;transition:.2s ease-in-out transform}@media (prefers-reduced-motion:reduce){.icon-link>.bi{transition:none}}.icon-link-hover:focus-visible>.bi,.icon-link-hover:hover>.bi{transform:var(--bs-icon-link-transform,translate3d(.25em,0,0))}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption),.visually-hidden:not(caption){position:absolute!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:var(--bs-border-width);min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.object-fit-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-none{-o-object-fit:none!important;object-fit:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.overflow-x-auto{overflow-x:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.overflow-x-visible{overflow-x:visible!important}.overflow-x-scroll{overflow-x:scroll!important}.overflow-y-auto{overflow-y:auto!important}.overflow-y-hidden{overflow-y:hidden!important}.overflow-y-visible{overflow-y:visible!important}.overflow-y-scroll{overflow-y:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-inline-grid{display:inline-grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:var(--bs-box-shadow)!important}.shadow-sm{box-shadow:var(--bs-box-shadow-sm)!important}.shadow-lg{box-shadow:var(--bs-box-shadow-lg)!important}.shadow-none{box-shadow:none!important}.focus-ring-primary{--bs-focus-ring-color:rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color:rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color:rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color:rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color:rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color:rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color:rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color:rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-0{border:0!important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-top-0{border-top:0!important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-primary{--bs-border-opacity:1;border-color:rgba(var(--bs-primary-rgb),var(--bs-border-opacity))!important}.border-secondary{--bs-border-opacity:1;border-color:rgba(var(--bs-secondary-rgb),var(--bs-border-opacity))!important}.border-success{--bs-border-opacity:1;border-color:rgba(var(--bs-success-rgb),var(--bs-border-opacity))!important}.border-info{--bs-border-opacity:1;border-color:rgba(var(--bs-info-rgb),var(--bs-border-opacity))!important}.border-warning{--bs-border-opacity:1;border-color:rgba(var(--bs-warning-rgb),var(--bs-border-opacity))!important}.border-danger{--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb),var(--bs-border-opacity))!important}.border-light{--bs-border-opacity:1;border-color:rgba(var(--bs-light-rgb),var(--bs-border-opacity))!important}.border-dark{--bs-border-opacity:1;border-color:rgba(var(--bs-dark-rgb),var(--bs-border-opacity))!important}.border-black{--bs-border-opacity:1;border-color:rgba(var(--bs-black-rgb),var(--bs-border-opacity))!important}.border-white{--bs-border-opacity:1;border-color:rgba(var(--bs-white-rgb),var(--bs-border-opacity))!important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle)!important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle)!important}.border-success-subtle{border-color:var(--bs-success-border-subtle)!important}.border-info-subtle{border-color:var(--bs-info-border-subtle)!important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle)!important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle)!important}.border-light-subtle{border-color:var(--bs-light-border-subtle)!important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle)!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.border-opacity-10{--bs-border-opacity:0.1}.border-opacity-25{--bs-border-opacity:0.25}.border-opacity-50{--bs-border-opacity:0.5}.border-opacity-75{--bs-border-opacity:0.75}.border-opacity-100{--bs-border-opacity:1}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.row-gap-0{row-gap:0!important}.row-gap-1{row-gap:.25rem!important}.row-gap-2{row-gap:.5rem!important}.row-gap-3{row-gap:1rem!important}.row-gap-4{row-gap:1.5rem!important}.row-gap-5{row-gap:3rem!important}.column-gap-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-lighter{font-weight:lighter!important}.fw-light{font-weight:300!important}.fw-normal{font-weight:400!important}.fw-medium{font-weight:500!important}.fw-semibold{font-weight:600!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-body-secondary{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-body-tertiary{--bs-text-opacity:1;color:var(--bs-tertiary-color)!important}.text-body-emphasis{--bs-text-opacity:1;color:var(--bs-emphasis-color)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis)!important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis)!important}.text-success-emphasis{color:var(--bs-success-text-emphasis)!important}.text-info-emphasis{color:var(--bs-info-text-emphasis)!important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis)!important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis)!important}.text-light-emphasis{color:var(--bs-light-text-emphasis)!important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis)!important}.link-opacity-10{--bs-link-opacity:0.1}.link-opacity-10-hover:hover{--bs-link-opacity:0.1}.link-opacity-25{--bs-link-opacity:0.25}.link-opacity-25-hover:hover{--bs-link-opacity:0.25}.link-opacity-50{--bs-link-opacity:0.5}.link-opacity-50-hover:hover{--bs-link-opacity:0.5}.link-opacity-75{--bs-link-opacity:0.75}.link-opacity-75-hover:hover{--bs-link-opacity:0.75}.link-opacity-100{--bs-link-opacity:1}.link-opacity-100-hover:hover{--bs-link-opacity:1}.link-offset-1{text-underline-offset:0.125em!important}.link-offset-1-hover:hover{text-underline-offset:0.125em!important}.link-offset-2{text-underline-offset:0.25em!important}.link-offset-2-hover:hover{text-underline-offset:0.25em!important}.link-offset-3{text-underline-offset:0.375em!important}.link-offset-3-hover:hover{text-underline-offset:0.375em!important}.link-underline-primary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-secondary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-success{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important}.link-underline-info{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important}.link-underline-warning{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important}.link-underline-danger{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important}.link-underline-light{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important}.link-underline-dark{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important}.link-underline{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-underline-opacity-0{--bs-link-underline-opacity:0}.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity:0}.link-underline-opacity-10{--bs-link-underline-opacity:0.1}.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity:0.1}.link-underline-opacity-25{--bs-link-underline-opacity:0.25}.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity:0.25}.link-underline-opacity-50{--bs-link-underline-opacity:0.5}.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity:0.5}.link-underline-opacity-75{--bs-link-underline-opacity:0.75}.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity:0.75}.link-underline-opacity-100{--bs-link-underline-opacity:1}.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-body-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-bg-rgb),var(--bs-bg-opacity))!important}.bg-body-tertiary{--bs-bg-opacity:1;background-color:rgba(var(--bs-tertiary-bg-rgb),var(--bs-bg-opacity))!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle)!important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle)!important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle)!important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle)!important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle)!important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle)!important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle)!important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle)!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:var(--bs-border-radius)!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:var(--bs-border-radius-sm)!important}.rounded-2{border-radius:var(--bs-border-radius)!important}.rounded-3{border-radius:var(--bs-border-radius-lg)!important}.rounded-4{border-radius:var(--bs-border-radius-xl)!important}.rounded-5{border-radius:var(--bs-border-radius-xxl)!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:var(--bs-border-radius-pill)!important}.rounded-top{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-0{border-top-left-radius:0!important;border-top-right-radius:0!important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm)!important;border-top-right-radius:var(--bs-border-radius-sm)!important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg)!important;border-top-right-radius:var(--bs-border-radius-lg)!important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl)!important;border-top-right-radius:var(--bs-border-radius-xl)!important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl)!important;border-top-right-radius:var(--bs-border-radius-xxl)!important}.rounded-top-circle{border-top-left-radius:50%!important;border-top-right-radius:50%!important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill)!important;border-top-right-radius:var(--bs-border-radius-pill)!important}.rounded-end{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-0{border-top-right-radius:0!important;border-bottom-right-radius:0!important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm)!important;border-bottom-right-radius:var(--bs-border-radius-sm)!important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg)!important;border-bottom-right-radius:var(--bs-border-radius-lg)!important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl)!important;border-bottom-right-radius:var(--bs-border-radius-xl)!important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-right-radius:var(--bs-border-radius-xxl)!important}.rounded-end-circle{border-top-right-radius:50%!important;border-bottom-right-radius:50%!important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill)!important;border-bottom-right-radius:var(--bs-border-radius-pill)!important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-0{border-bottom-right-radius:0!important;border-bottom-left-radius:0!important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm)!important;border-bottom-left-radius:var(--bs-border-radius-sm)!important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg)!important;border-bottom-left-radius:var(--bs-border-radius-lg)!important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl)!important;border-bottom-left-radius:var(--bs-border-radius-xl)!important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-left-radius:var(--bs-border-radius-xxl)!important}.rounded-bottom-circle{border-bottom-right-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill)!important;border-bottom-left-radius:var(--bs-border-radius-pill)!important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-0{border-bottom-left-radius:0!important;border-top-left-radius:0!important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm)!important;border-top-left-radius:var(--bs-border-radius-sm)!important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg)!important;border-top-left-radius:var(--bs-border-radius-lg)!important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl)!important;border-top-left-radius:var(--bs-border-radius-xl)!important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl)!important;border-top-left-radius:var(--bs-border-radius-xxl)!important}.rounded-start-circle{border-bottom-left-radius:50%!important;border-top-left-radius:50%!important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill)!important;border-top-left-radius:var(--bs-border-radius-pill)!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.z-n1{z-index:-1!important}.z-0{z-index:0!important}.z-1{z-index:1!important}.z-2{z-index:2!important}.z-3{z-index:3!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.object-fit-sm-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-sm-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-sm-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-sm-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-sm-none{-o-object-fit:none!important;object-fit:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-inline-grid{display:inline-grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.row-gap-sm-0{row-gap:0!important}.row-gap-sm-1{row-gap:.25rem!important}.row-gap-sm-2{row-gap:.5rem!important}.row-gap-sm-3{row-gap:1rem!important}.row-gap-sm-4{row-gap:1.5rem!important}.row-gap-sm-5{row-gap:3rem!important}.column-gap-sm-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-sm-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-sm-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-sm-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-sm-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-sm-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.object-fit-md-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-md-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-md-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-md-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-md-none{-o-object-fit:none!important;object-fit:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-inline-grid{display:inline-grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.row-gap-md-0{row-gap:0!important}.row-gap-md-1{row-gap:.25rem!important}.row-gap-md-2{row-gap:.5rem!important}.row-gap-md-3{row-gap:1rem!important}.row-gap-md-4{row-gap:1.5rem!important}.row-gap-md-5{row-gap:3rem!important}.column-gap-md-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-md-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-md-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-md-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-md-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-md-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.object-fit-lg-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-lg-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-lg-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-lg-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-lg-none{-o-object-fit:none!important;object-fit:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-inline-grid{display:inline-grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.row-gap-lg-0{row-gap:0!important}.row-gap-lg-1{row-gap:.25rem!important}.row-gap-lg-2{row-gap:.5rem!important}.row-gap-lg-3{row-gap:1rem!important}.row-gap-lg-4{row-gap:1.5rem!important}.row-gap-lg-5{row-gap:3rem!important}.column-gap-lg-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-lg-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-lg-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-lg-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-lg-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-lg-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.object-fit-xl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xl-none{-o-object-fit:none!important;object-fit:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-inline-grid{display:inline-grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.row-gap-xl-0{row-gap:0!important}.row-gap-xl-1{row-gap:.25rem!important}.row-gap-xl-2{row-gap:.5rem!important}.row-gap-xl-3{row-gap:1rem!important}.row-gap-xl-4{row-gap:1.5rem!important}.row-gap-xl-5{row-gap:3rem!important}.column-gap-xl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xl-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-xl-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-xl-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xl-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xl-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.object-fit-xxl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xxl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xxl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xxl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xxl-none{-o-object-fit:none!important;object-fit:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-inline-grid{display:inline-grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.row-gap-xxl-0{row-gap:0!important}.row-gap-xxl-1{row-gap:.25rem!important}.row-gap-xxl-2{row-gap:.5rem!important}.row-gap-xxl-3{row-gap:1rem!important}.row-gap-xxl-4{row-gap:1.5rem!important}.row-gap-xxl-5{row-gap:3rem!important}.column-gap-xxl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xxl-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-xxl-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-xxl-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xxl-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xxl-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-inline-grid{display:inline-grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}.navbar{border-style:solid;border-width:2px;border-radius:25px 25px 55px 5px/5px 55px 25px 25px}.navbar.bg-light{border-color:#333}.navbar.fixed-top{border-width:0 0 2px;border-radius:0 25px 225px 0/25px 0 25px 255px}.navbar.fixed-bottom{border-width:2px 0 0;border-radius:255px 25px 0 25px/25px 225px 25px 0}.navbar-brand{font-family:"Cabin Sketch",cursive;font-weight:400;text-decoration:none}.btn{text-decoration:none;border-radius:255px 25px 225px 25px/25px 225px 25px 255px}.btn-group-lg>.btn,.btn-lg{border-radius:55px 225px 15px 25px/25px 25px 35px 355px}.btn-group-sm>.btn,.btn-sm{border-radius:255px 25px 225px 25px/25px 225px 25px 255px}.btn-check{display:inline-block;opacity:0}button,input,optgroup,select,textarea{font-family:Neucha,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif}b,strong{font-family:"Cabin Sketch",cursive}blockquote{border-radius:15px 27px 25px 25px/25px 25px 305px 635px}table td,table th{background-color:#fff}.table-bordered{overflow:hidden;border-spacing:0;border-collapse:separate;background-color:#333;border-radius:5px 25px 5px 25px/25px 5px 25px 5px}.table-bordered td,.table-bordered th{border-radius:5px 5px 25px 4px/5px 4px 3px 5px}.table-bordered .table-success td,.table-bordered .table-success th,.table-bordered .table-success:hover td,.table-bordered .table-success:hover th{color:#fff;background-color:#28a745}.table-bordered .table-info td,.table-bordered .table-info th,.table-bordered .table-info:hover td,.table-bordered .table-info:hover th{color:#fff;background-color:#17a2b8}.table-bordered .table-warning td,.table-bordered .table-warning th,.table-bordered .table-warning:hover td,.table-bordered .table-warning:hover th{color:#fff;background-color:#ffc107}.table-bordered .table-danger td,.table-bordered .table-danger th,.table-bordered .table-danger:hover td,.table-bordered .table-danger:hover th{color:#fff;background-color:#dc3545}.table-dark td,.table-dark th,.table-dark.table-hover .table-active:hover>td,.table-dark.table-hover .table-active:hover>th{background-color:#333}.form-control,.input-group-text,input{border-radius:255px 25px 225px 25px/25px 225px 25px 255px}select,select.form-control,textarea,textarea.form-control{border-radius:55px 225px 15px 25px/25px 25px 35px 355px!important}[type=checkbox]{position:relative;width:0;height:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer;border:none}[type=checkbox]::before{position:absolute;top:-.1em;left:0;display:inline-block;width:15px;height:16px;content:"";border:2px solid #333;border-radius:2px 8px 2px 4px/5px 3px 5px 3px}[type=checkbox]:focus::before{box-shadow:0 0 0 .25rem rgba(51,51,51,.25)}[type=checkbox]:checked::after,[type=checkbox]:indeterminate::after{position:absolute;top:0;left:.1em;font-size:1.5rem;line-height:.5;color:#333}[type=checkbox]:checked::after{content:"x"}[type=checkbox]:indeterminate::after{top:.1em;content:"-"}[type=checkbox]:disabled::before{border:2px solid #aaa}[type=radio]{position:relative;width:0;height:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer;border:none}[type=radio]::before{position:absolute;top:-.1em;left:0;display:inline-block;width:16px;height:16px;content:"";border:2px solid #333;border-radius:50% 45% 40% 50%/40% 50% 50% 45%}[type=radio]:focus::before{box-shadow:0 0 0 .25rem rgba(51,51,51,.25)}[type=radio]:checked::before{background-color:#333}[type=radio]:disabled::before{border:2px solid #aaa}.form-check-input:focus{box-shadow:none}.form-switch{padding-left:0}.form-switch .form-check-input{position:relative;margin-left:0}.form-switch .form-check-input::before{width:32px;border-radius:30% 35% 30% 30%/30% 50% 30% 45%}.form-switch .form-check-input::after{position:absolute;top:0;left:0;display:inline-block;width:12px;height:12px;content:"";background-color:#fff;border:2px solid #333;border-radius:50% 45% 40% 50%/40% 50% 50% 45%;transition:left .15s ease-in-out}.form-switch .form-check-input:checked::after{top:0;left:18px;background-color:#333}.form-switch .form-check-label{margin-left:.5em}.dropdown-menu{overflow:hidden;border-radius:555px 25px 25px 25px/25px 25px 25px 555px}.dropdown-divider{border-top-width:2px}.list-group{overflow:hidden;background-color:#333;border:2px solid #333;border-radius:45px 15px 35px 5px/15px 5px 15px 65px}.list-group-item{border-top:2px solid #333;border-right:none;border-left:none;border-radius:255px 5px 225px 5px/25px 225px 25px 255px}.list-group-item:first-child{border-top:none}.list-group-item:last-child{border-bottom:none}.nav-pills .nav-link{border-radius:255px 25px 225px 25px/25px 225px 25px 255px}.dropdown-item,.list-group-item,.nav-link,.page-link{text-decoration:none}.nav-tabs .nav-link{border-radius:45px 15px 225px 5px/25px 225px 25px 255px}.breadcrumb{border:2px solid #333;border-radius:255px 25px 225px 25px/25px 225px 25px 255px}.pagination .page-link{border-radius:425px 255px 25px 25px/25px 25px 5px 25px}.badge{border-radius:255px 25px 225px 25px/25px 225px 25px 255px}.badge-pill{border-radius:7rem 8rem 8rem 8rem/4rem 5rem 6rem 6rem}.badge.bg-light{color:#555}.alert{border-radius:255px 25px 225px 25px/25px 225px 25px 255px}.alert .btn-close::before{color:inherit}.progress{border:2px solid #333;border-radius:255px 25px 225px 25px/25px 225px 25px 255px}.card{border-radius:5px 5px 5px 5px/25px 25px 25px 5px}.card-outline-danger,.card-outline-info,.card-outline-primary,.card-outline-success,.card-outline-warning{border-width:2px}.card-header{border-color:inherit;border-bottom-width:2px}.card-header:first-child{border-radius:3px 3px 0 0/23px 23px 0 0}.card-footer{border-top-width:2px}.toast{border-radius:10px 10px 15px 5px/5px 15px 5px 15px}.toast-header{font-family:"Cabin Sketch",cursive}.modal-content{border-radius:15px 5px 5px 25px/5px 25px 25px 5px}.popover{padding:0;border-radius:45px 85px 15px 25px/15px 10px 35px 555px}.popover-title{border-bottom:2px solid #333}.popover.bs-popover-auto[data-popper-placement^=left]::before,.popover.bs-popover-start::before,.popover.bs-tether-element-attached-right::before{right:-13px}.popover.bs-popover-auto[data-popper-placement^=top]::before,.popover.bs-popover-top::before,.popover.bs-tether-element-attached-bottom::before{bottom:-13px}.popover.bs-popover-auto[data-popper-placement^=bottom]::before,.popover.bs-popover-bottom::before,.popover.bs-tether-element-attached-top::before{top:-13px}.popover.bs-popover-auto[data-popper-placement^=right]::before,.popover.bs-popover-end::before,.popover.bs-tether-element-attached-left::before{left:-13px}.tooltip-inner{border-radius:255px 25px 225px 25px/25px 225px 25px 255px}pre{border:2px solid #333;border-radius:15px 5px 5px 25px/5px 25px 25px 5px}.btn-close{background-image:none}.btn-close::before{position:absolute;top:.8rem;right:1rem;content:"X"}.img-thumbnail{border-radius:255px 25px 225px 25px/25px 225px 25px 255px} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/html/index.html b/html/index.html deleted file mode 100644 index 084a3a51..00000000 --- a/html/index.html +++ /dev/null @@ -1,290 +0,0 @@ - - - - get_jobs - - - Title - - - - -

🍀Get Jobs【工作无忧】

-
- - Stars - QQ交流群 - License Issues closed - Forks -
-
-

🌴源码地址

- - -

🌞 特色功能

- - - -

🔞️ 注意事项

- - - -
-

已经有人在交流群里 发广告 等与本项目无关的信息

-

如果带着不同目的或者没想清楚就进群的

-

一经发现群主会对您的家人及朋友进行亲切(没有素质)的问候

-

并将您请出群聊,请珍惜交流的机会,谢谢!

-
-
-

🚀 如何使用?


-

1️⃣ 使用git拉取代码

- -
-    git clone https://github.com/loks666/get_jobs.git
-    cd get_jobs
-    
- -

2️⃣ 环境配置:JDK17+、Maven、Chrome、ChromeDriver

- -
-

目前driver版本号:123.0.6312.122

-

chrome需要版本为:124.0.6367.61及以上(默认最新即可)

- - - - 更多环境配置详情请点击:📚 环境配置 -
- -

3️⃣ 修改配置文件(一般默认即可,需要修改自己的地区和岗位)

- -

4️⃣ 最后一步:运行代码

- -
-

✍🏼 例:Boss投递日志

- Boss投递日志 -

✍🏼 猎聘投递日志

- 猎聘投递日志 -

✍🏼 获取城市码

- 获取城市码 -

📧 联系方式

- -

👨🏻‍🔧 QQ群

-

扫码添加:加群答案为本项目仓库名【get_jobs】

- qq群 - 微信群 -

点击下面的链接可直接加群

- QQ交流群 - -

🚩 付费部署

-

本项目文档已相对完善,如仍需付费部署,请添加QQ群或微信联系群主

- - -

📑 更新日志

-

2024-4-15 01:52:18

-
    -
  1. 新增config.yaml,目前仅需修改配置文件即可,已全平台支持
  2. -
  3. cookie有效期延长,保持至少一周(拉勾平台除外)
  4. -
- -

🤝 参与贡献

-

我们非常欢迎各种形式的贡献

-

如果你对贡献代码感兴趣

-

可以查看我们的 Issuesdiscussions

-

期待你的大展身手,向我们展示你的奇思妙想。

- -

📰 开源协议

-
-

📝 License

- FOSSA Status -
-

☕️ 请我喝杯咖啡

- 支付宝付款码 - 微信付款码 -
- - \ No newline at end of file diff --git a/img.png b/img.png new file mode 100644 index 00000000..dd13f94e Binary files /dev/null and b/img.png differ diff --git a/pom.xml b/pom.xml index 68e121c3..45235c9f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,161 +5,161 @@ 4.0.0 com.superxiang - get_jobs - v2.0.1 + npe_get_jobs + v2.1.4 get_jobs org.springframework.boot spring-boot-starter-parent - 2.5.0 + 3.2.12 + 1.0.0-M6 UTF-8 21 21 + + + + + - org.json - json - 20231013 + org.springframework.boot + spring-boot-starter - + org.springframework.boot spring-boot-starter-web - org.projectlombok - lombok - 1.18.30 - - - - org.slf4j - slf4j-api - 1.7.30 + io.projectreactor.netty + reactor-netty-http + - ch.qos.logback - logback-classic - 1.2.13 + org.springframework.boot + spring-boot-starter-tomcat - + - com.microsoft.playwright - playwright - 1.51.0 + org.springframework.boot + spring-boot-starter-data-jpa + + + + - org.seleniumhq.selenium - selenium-java - - 4.31.0 - - - org.seleniumhq.selenium - selenium-api - - - org.seleniumhq.selenium - selenium-remote-driver - - - org.seleniumhq.selenium - selenium-support - - - org.seleniumhq.selenium - selenium-devtools-v133 - - - org.seleniumhq.selenium - selenium-devtools-v134 - - - org.seleniumhq.selenium - selenium-chrome-driver - - - org.seleniumhq.selenium - selenium-chromium-driver - - - org.seleniumhq.selenium - selenium-edge-driver - - - org.seleniumhq.selenium - selenium-firefox-driver - - - org.seleniumhq.selenium - selenium-ie-driver - - - org.seleniumhq.selenium - selenium-safari-driver - - + org.xerial + sqlite-jdbc + 3.45.0.0 - - + - org.seleniumhq.selenium - selenium-api - 4.31.0 + org.hibernate.orm + hibernate-community-dialects + + + + - org.seleniumhq.selenium - selenium-devtools-v135 - 4.31.0 + com.fasterxml.jackson.core + jackson-databind - - org.seleniumhq.selenium - selenium-chrome-driver - 4.31.0 + com.fasterxml.jackson.core + jackson-core - + - org.seleniumhq.selenium - selenium-remote-driver - 4.31.0 + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml - + - org.seleniumhq.selenium - selenium-support - 4.31.0 + org.yaml + snakeyaml - + - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - 2.12.3 + com.github.openjson + openjson + 1.0.12 - + + + + org.apache.httpcomponents.client5 httpclient5-fluent 5.1 + + + com.squareup.okhttp3 + okhttp + 4.12.0 + + + + + + + + org.springframework.ai + spring-ai-openai-spring-boot-starter + ${spring-ai.version} + - + + + + + com.samskivert + jmustache + 1.16 + + io.github.cdimascio dotenv-java 2.2.0 + + + org.projectlombok + lombok + 1.18.30 + provided + + + + + + + + + com.microsoft.playwright + playwright + 1.51.0 + + @@ -171,6 +171,7 @@ + src/main/java org.apache.maven.plugins @@ -179,6 +180,7 @@ 21 21 + 21 @@ -187,8 +189,30 @@ exec-maven-plugin 3.0.0 - StartAll + getjobs.GetJobsApplication + + + + + org.springframework.boot + spring-boot-maven-plugin + 3.2.12 + + getjobs.GetJobsApplication + + + org.projectlombok + lombok + + + + + + repackage + + + diff --git a/src/main/java/StartAll.java b/src/main/java/StartAll.java deleted file mode 100644 index 6a41b39d..00000000 --- a/src/main/java/StartAll.java +++ /dev/null @@ -1,119 +0,0 @@ -import lombok.extern.slf4j.Slf4j; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.ArrayList; -import java.util.List; - -@Slf4j -public class StartAll { - // 存储所有子进程的引用 - private static final List childProcesses = new ArrayList<>(); - - public static void main(String[] args) { - - // Create a ScheduledExecutorService for Boss - ScheduledExecutorService bossScheduler = Executors.newSingleThreadScheduledExecutor(); - - // 定义Boss任务 - Runnable bossTask = () -> { - try { - log.info("正在执行 Boss 任务,线程名称: {}", Thread.currentThread().getName()); - executeTask("boss.Boss"); - log.info("Boss 任务已完成,完成时间: {}", java.time.LocalDateTime.now()); - } catch (Exception e) { - log.error("Boss 任务执行过程中发生错误: {}", e.getMessage(), e); - } - }; - - // 创建一个统一的线程池来执行所有任务 - ExecutorService executorService = Executors.newFixedThreadPool(2); - - // 定义Liepin任务 - Runnable liepinTask = () -> { - try { - log.info("正在执行 Liepin 任务,线程名称: {}", Thread.currentThread().getName()); - executeTask("liepin.Liepin"); - log.info("Liepin 任务已完成,完成时间: {}", java.time.LocalDateTime.now()); - } catch (Exception e) { - log.error("Liepin 任务执行过程中发生错误: {}", e.getMessage(), e); - } - }; - - // 定义Job51任务 - Runnable job51Task = () -> { - try { - log.info("正在执行 Job51 任务,线程名称: {}", Thread.currentThread().getName()); - executeTask("job51.Job51"); - log.info("Job51 任务已完成,完成时间: {}", java.time.LocalDateTime.now()); - } catch (Exception e) { - log.error("Job51 任务执行过程中发生错误: {}", e.getMessage(), e); - } - }; - - // 提交所有任务到线程池执行 -// executorService.submit(liepinTask); -// executorService.submit(job51Task); - - // 添加关闭钩子,优雅地关闭线程池和子进程 - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - log.info("正在关闭线程池和子进程..."); - - // 关闭所有子进程 - synchronized (childProcesses) { - for (Process process : childProcesses) { - if (process != null && process.isAlive()) { - process.destroyForcibly(); - } - } - childProcesses.clear(); - } - - executorService.shutdown(); - bossScheduler.shutdown(); - try { - if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) { - log.warn("强制关闭线程池..."); - executorService.shutdownNow(); - bossScheduler.shutdownNow(); - } - } catch (InterruptedException e) { - log.error("关闭线程池时发生错误: {}", e.getMessage(), e); - executorService.shutdownNow(); - bossScheduler.shutdownNow(); - } - })); - } - - /** - * 使用独立进程运行指定的类 - * - * @param className 要执行的类名 - * @throws Exception 如果发生错误 - */ - private static void executeTask(String className) throws Exception { - ProcessBuilder processBuilder = new ProcessBuilder( - "java", "-cp", System.getProperty("java.class.path"), className - ); - processBuilder.inheritIO(); // 将子进程的输入/输出重定向到当前进程 - Process process = processBuilder.start(); - - // 将进程添加到管理列表中 - synchronized (childProcesses) { - childProcesses.add(process); - } - - int exitCode = process.waitFor(); - - // 进程结束后从列表中移除 - synchronized (childProcesses) { - childProcesses.remove(process); - } - - if (exitCode != 0) { - throw new RuntimeException(className + " 执行失败,退出代码: " + exitCode); - } - } -} diff --git a/src/main/java/ai/AiConfig.java b/src/main/java/ai/AiConfig.java deleted file mode 100644 index 0aac284f..00000000 --- a/src/main/java/ai/AiConfig.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai; - -import lombok.Data; -import utils.JobUtils; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -@Data -public class AiConfig { - - /** - * 介绍语 - */ - private String introduce; - - /** - * 提示词 - */ - private String prompt; - - public AiConfig() { - } - - public AiConfig(String introduce, String prompt) { - this.introduce = introduce; - this.prompt = prompt; - } - - public static AiConfig init() { - AiConfig config = JobUtils.getConfig(AiConfig.class); - return new AiConfig(config.introduce, config.prompt); - } - -} diff --git a/src/main/java/ai/AiFilter.java b/src/main/java/ai/AiFilter.java deleted file mode 100644 index 729354d6..00000000 --- a/src/main/java/ai/AiFilter.java +++ /dev/null @@ -1,31 +0,0 @@ -package ai; - -import lombok.Data; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -@Data -public class AiFilter { - - /** - * ai检测结果 - */ - private Boolean result; - - /** - * 如果匹配,则返回的打招呼语 - */ - private String message; - - public AiFilter(Boolean result) { - this.result = result; - } - - public AiFilter(Boolean result, String message) { - this.result = result; - this.message = message; - } - -} diff --git a/src/main/java/ai/AiService.java b/src/main/java/ai/AiService.java deleted file mode 100644 index 58fb469c..00000000 --- a/src/main/java/ai/AiService.java +++ /dev/null @@ -1,170 +0,0 @@ -package ai; - -import io.github.cdimascio.dotenv.Dotenv; -import lombok.extern.slf4j.Slf4j; -import org.json.JSONArray; -import org.json.JSONObject; - -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.concurrent.*; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -@Slf4j -public class AiService { - - private static final Dotenv dotenv = Dotenv.load(); - private static final String BASE_URL = dotenv.get("BASE_URL") + "/v1/chat/completions"; - private static final String API_KEY = dotenv.get("API_KEY"); - private static final String MODEL = dotenv.get("MODEL"); - - - public static String sendRequest(String content) { - // 设置超时时间,单位:秒 - int timeoutInSeconds = 60; // 你可以修改这个变量来设置超时时间 - - // 创建 HttpClient 实例并设置超时 - HttpClient client = HttpClient.newBuilder() - .connectTimeout(Duration.ofSeconds(timeoutInSeconds)) // 设置连接超时 - .build(); - - // 构建 JSON 请求体 - JSONObject requestData = new JSONObject(); - requestData.put("model", MODEL); - requestData.put("temperature", 0.5); - - // 添加消息内容 - JSONArray messages = new JSONArray(); - JSONObject message = new JSONObject(); - message.put("role", "user"); - message.put("content", content); - messages.put(message); - - requestData.put("messages", messages); - - // 构建 HTTP 请求 - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(BASE_URL)) - .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + API_KEY) - .POST(HttpRequest.BodyPublishers.ofString(requestData.toString())) - .build(); - - // 创建线程池用于执行请求 - ExecutorService executor = Executors.newSingleThreadExecutor(); - Callable> task = () -> client.send(request, HttpResponse.BodyHandlers.ofString()); - - // 提交请求并控制超时 - Future> future = executor.submit(task); - try { - // 使用 future.get 设置超时 - HttpResponse response = future.get(timeoutInSeconds, TimeUnit.SECONDS); - - if (response.statusCode() == 200) { - // 解析响应体 - log.info(response.body()); - JSONObject responseObject = new JSONObject(response.body()); - String requestId = responseObject.getString("id"); - long created = responseObject.getLong("created"); - String model = responseObject.getString("model"); - - // 解析返回的内容 - JSONObject messageObject = responseObject.getJSONArray("choices") - .getJSONObject(0) - .getJSONObject("message"); - String responseContent = messageObject.getString("content"); - - // 解析 usage 部分 - JSONObject usageObject = responseObject.getJSONObject("usage"); - int promptTokens = usageObject.getInt("prompt_tokens"); - int completionTokens = usageObject.getInt("completion_tokens"); - int totalTokens = usageObject.getInt("total_tokens"); - - // 格式化时间 - LocalDateTime createdTime = Instant.ofEpochSecond(created) - .atZone(ZoneId.systemDefault()) - .toLocalDateTime(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - String formattedTime = createdTime.format(formatter); - - log.info("请求ID: {}, 创建时间: {}, 模型名: {}, 提示词: {}, 补全: {}, 总用量: {}", requestId, formattedTime, model, promptTokens, completionTokens, totalTokens); - return responseContent; - } else { - log.error("AI请求失败!状态码: {}", response.statusCode()); - } - } catch (TimeoutException e) { - log.error("请求超时!超时设置为 {} 秒", timeoutInSeconds); - } catch (Exception e) { - log.error("AI请求异常!", e); - } finally { - executor.shutdownNow(); // 关闭线程池 - } - return ""; - } - - - public static void main(String[] args) { - System.out.println(cleanBossDesc(".EwyXFHpFfseN{display:inline-block;width:0.1px;height:0.1px;overflow:hidden;visibility: hidden;}.FxpRjMznwNS{display:inline-block;font-size:0!important;width:1em;height:1em;visibility:hidden;line-height:0;}.QTsRdnap{display:inline-block;font-size:0!important;width:1em;height:1em;visibility:hidden;line-height:0;}.spBzTCGii{display:inline-block;font-size:0!important;width:1em;height:1em;visibility:hidden;line-height:0;}.DXpfskbRdfn{display:inline-block;width:0.1px;height:0.1px;overflow:hidden;visibility: hidden;}.snNcSPFFs{font-style:normal;font-weight:normal}.zjziXGAdnjK{font-style:normal;font-weight:normal}.CjmzfkfTmx{font-style:normal;font-weight:normal}.YYTWRZHhrm{font-style:normal;font-weight:normal}.cfAzXEKs{font-style:normal;font-weight:normal}岗位职责:\n" + - "kanzhun一、boss产品+AI的实BOSS直聘施与落地能力\n" + - "1、负责公司产品+AI的实施与落地,熟悉各基础大模型性能,熟练应用大模型关键技术,面向隧道股份各需求,协同规划产品落地路径,具备实施能力;\n" + - "2、负责聚焦场景和产品的大模型相关的训练工作,包括:需求分析、功能设计、数据集构建、模型训练、评估及优化等;\n" + - "3、熟悉包括RAG、指令数据构建、Prompt工程、模型Fine-tuning、Prompt Engineering等环节,实现大模型技术在领域内垂直场景的落地应用;\n" + - "4、熟悉NLP、CV、多模态等领域大模型结构、算法,具备追踪大模型领域内前沿的技术研究成果,包括但不局限预训练、强化学习、知识增强、分布式训练等,同时提出创新思路来推动升级的能力;\n" + - "5、具备优化计算、存储和网络性能的能力,以满足业务系统的资源需求,并设定具体的性能优化目标,如延迟、吞吐量、资源利用率等;在满足性能需求的前提下,优化计算、存储和网络资源的使用,降低总成本;\n" + - "6、对业务系统的效果进行持续调优,通过数据分析和系统改进,提升系统的性能和用户体验。\n" + - "二、项目管理与协作\n" + - "1、参与制定核心业务项目计划和需求分析,确保项目按时交付和达到高质量标准。\n" + - "带领项目成员进行端对端开发,制定项目计划、分配任务并指导项目成员完成开发工作。\n" + - "2、与跨部门团队紧密合作,包括开发人员、测试人员、产品经理等,共同推动项目的顺利进行。\n" + - "四、技术能力提升\n" + - "1、负责相关技术文档的撰写与整理。\n" + - "2、协同团队成员进行技术分享,促进***学习于经验交流。\n" + - "3、建立公司知识库,沉淀技术文档、***实践、案例分享等,方便企业内部日常学习与参考。\n" + - "\n" + - "任职资格:\n" + - "一、教育背景\n" + - "本科及以上学历,电子工程、计算机科学、人工智能等相关领域专业。\n" + - "二、工作经验\n" + - "1、具备3年以上AI相关开发经验或5年以上软件开发经验(优秀者可适当放宽工作年限要求)。\n" + - "2、具备Rerank、Embedding、Langchain、RAG等服务开发及部署经验者优先。\n" + - "具备大模型应用开发经验,在智能问答、代码review、代码续写、测试用例生成等方向有成功经验者优先。\n" + - "4、有大型互联网公司大规模机器学习平台相关研发落地经验者优先。\n" + - "三、专业知识\n" + - "1、熟悉主流大模型如GPT、Gemini、LLaMA、ChatGLM等及其原理,并能进行针对性模型开发工作;\n" + - "2、了解深度学习等技术,熟悉大模型训练、推理、量化和部署者优先;\n" + - "3、了解主流AI应用框架者(如TensorFlow、PyTorch、longchain等)优先;\n" + - "4、熟悉JAVA/C++/Go/Python任一语言,有完整的项目开发经验,具备核心模块设计和维护经验。有一定的算法工程化能力,能够实现算法/模型的工程化与应用部署;具有NLP相关技术经验者更佳;\n" + - "熟悉Agent框架,有优化能力,包括planing、action、tools use、memory等核心Agent能力的提升者优先;、了解深度学习等技术,熟悉大模型训练、推理、量化和部署者优先;\n" + - "四、能力要求\n" + - "1、具备良好的问题解决能力和逻辑思维,能独立分析并解决技术难题。\n" + - "2、具备良好的团队合作精神和沟通能力,能在跨部门协作中发挥积极作用。\n" + - "3、具备良好的学习能力和适应能力,能够快速掌握新技术和新知识。\n" + - "4、具备较强的抗压能力,能够适应一定频率的出差与加班,满足工作中的紧急任务需求。")); - try { - // 示例:发送请求 - String content = "你好"; - String response = sendRequest(content); - System.out.println("AI回复: " + response); - } catch (Exception e) { - log.error("AI异常!"); - } - } - - public static String cleanBossDesc(String raw) { - return raw.replaceAll("kanzhun|BOSS直聘|来自BOSS直聘", "") - .replaceAll("[\\u200b-\\u200d\\uFEFF]", "") - .replaceAll("<[^>]+>", "") // 如果有HTML标签就用 - .replaceAll("\\s+", " ") - .trim(); - } -} diff --git a/src/main/java/boss/Boss.java b/src/main/java/boss/Boss.java deleted file mode 100644 index 8e5eb515..00000000 --- a/src/main/java/boss/Boss.java +++ /dev/null @@ -1,935 +0,0 @@ -package boss; - -import ai.AiConfig; -import ai.AiFilter; -import ai.AiService; -import com.microsoft.playwright.Locator; -import com.microsoft.playwright.Page; -import lombok.SneakyThrows; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import utils.*; - -import java.io.File; -import java.io.IOException; -import java.math.BigDecimal; -import java.math.RoundingMode; - -import java.net.URL; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.*; -import java.util.stream.Collectors; -import java.util.Scanner; - -import static boss.Locators.*; -import static utils.Bot.sendMessageByTime; -import static utils.JobUtils.formatDuration; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - * Boss直聘自动投递 - */ -public class Boss { - static { - // 在类加载时就设置日志文件名,确保Logger初始化时能获取到正确的属性 - System.setProperty("log.name", "boss"); - } - - private static final Logger log = LoggerFactory.getLogger(Boss.class); - static String homeUrl = "https://www.zhipin.com/"; - static String baseUrl = "https://www.zhipin.com/web/geek/job?"; - static Set blackCompanies; - static Set blackRecruiters; - static Set blackJobs; - static List resultList = new ArrayList<>(); - static String dataPath = "src/main/java/boss/data.json"; - static String cookiePath = "src/main/java/boss/cookie.json"; - static Date startDate; - static BossConfig config = BossConfig.init(); - - static { - try { - // 检查dataPath文件是否存在,不存在则创建 - File dataFile = new File(dataPath); - if (!dataFile.exists()) { - // 确保父目录存在 - if (!dataFile.getParentFile().exists()) { - dataFile.getParentFile().mkdirs(); - } - // 创建文件并写入初始JSON结构 - Map> initialData = new HashMap<>(); - initialData.put("blackCompanies", new HashSet<>()); - initialData.put("blackRecruiters", new HashSet<>()); - initialData.put("blackJobs", new HashSet<>()); - String initialJson = customJsonFormat(initialData); - Files.write(Paths.get(dataPath), initialJson.getBytes()); - log.info("创建数据文件: {}", dataPath); - } - - // 检查cookiePath文件是否存在,不存在则创建 - File cookieFile = new File(cookiePath); - if (!cookieFile.exists()) { - // 确保父目录存在 - if (!cookieFile.getParentFile().exists()) { - cookieFile.getParentFile().mkdirs(); - } - // 创建空的cookie文件 - Files.write(Paths.get(cookiePath), "[]".getBytes()); - log.info("创建cookie文件: {}", cookiePath); - } - } catch (IOException e) { - log.error("创建文件时发生异常: {}", e.getMessage()); - } - } - - public static void main(String[] args) { - loadData(dataPath); - // 使用 PlayWright 获取岗位 - PlaywrightUtil.init(); - startDate = new Date(); - login(); - config.getCityCode().forEach(Boss::postJobByCity); - log.info(resultList.isEmpty() ? "未发起新的聊天..." : "新发起聊天公司如下:\n{}", - resultList.stream().map(Object::toString).collect(Collectors.joining("\n"))); - if (!config.getDebugger()) { - printResult(); - } - } - - private static void printResult() { - String message = String.format("\nBoss投递完成,共发起%d个聊天,用时%s", resultList.size(), - formatDuration(startDate, new Date())); - log.info(message); - sendMessageByTime(message); - saveData(dataPath); - resultList.clear(); - if (!config.getDebugger()) { - PlaywrightUtil.close(); - } - - // 确保所有日志都被刷新到文件 - try { - Thread.sleep(1000); // 等待1秒确保日志写入完成 - // 强制刷新日志 - 使用正确的方法 - ch.qos.logback.classic.LoggerContext loggerContext = (ch.qos.logback.classic.LoggerContext) org.slf4j.LoggerFactory.getILoggerFactory(); - loggerContext.stop(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - private static void postJobByCity(String cityCode) { - String searchUrl = getSearchUrl(cityCode); - for (String keyword : config.getKeywords()) { - int postCount = 0; - // 使用 URLEncoder 对关键词进行编码 - String encodedKeyword = URLEncoder.encode(keyword, StandardCharsets.UTF_8); - - String url = searchUrl + "&query=" + encodedKeyword; - log.info("投递地址:{}", searchUrl + "&query=" + keyword); - com.microsoft.playwright.Page page = PlaywrightUtil.getPageObject(); - page.navigate(url); - - // 1. 滚动到底部,加载所有岗位卡片 - int lastCount = -1; - while (true) { - // 滑动到底部 - page.evaluate("window.scrollTo(0, document.body.scrollHeight);"); - PlaywrightUtil.sleep(1); // 等待加载(可根据速度调整) - - // 获取所有卡片数 - Locator cards = page.locator("//ul[contains(@class, 'rec-job-list')]//li[contains(@class, 'job-card-box')]"); - int currentCount = cards.count(); - - // 判断是否继续滑动 - if (currentCount == lastCount) { - break; // 没有新内容,跳出循环 - } - lastCount = currentCount; - } - log.info("【{}】岗位已全部加载,总数:{}", keyword, lastCount); - - // 2. 回到页面顶部 - page.evaluate("window.scrollTo(0, 0);"); - PlaywrightUtil.sleep(1); - - // 3. 逐个遍历所有岗位 - Locator cards = page.locator("//ul[contains(@class, 'rec-job-list')]//li[contains(@class, 'job-card-box')]"); - int count = cards.count(); - for (int i = 0; i < count; i++) { - // 重新获取卡片,避免元素过期 - cards = page.locator("//ul[contains(@class, 'rec-job-list')]//li[contains(@class, 'job-card-box')]"); - cards.nth(i).click(); - PlaywrightUtil.sleep(1); - - // 等待详情内容加载 - page.waitForSelector("div[class*='job-detail-box']", new Page.WaitForSelectorOptions().setTimeout(4000)); - Locator detailBox = page.locator("div[class*='job-detail-box']"); - - // 岗位名称 - String jobName = safeText(detailBox, "span[class*='job-name']"); - if (blackJobs.stream().anyMatch(jobName::contains)) continue; - // 薪资(原始) - String jobSalaryRaw = safeText(detailBox, "span.job-salary"); - String jobSalary = decodeSalary(jobSalaryRaw); - // 城市/经验/学历 - List tags = safeAllText(detailBox, "ul[class*='tag-list'] > li"); - // 标签 (暂时不使用) - // List jobLabels = safeAllText(detailBox, "ul[class*='job-label-list'] > li"); - // 岗位描述 - String jobDesc = safeText(detailBox, "p.desc"); - // Boss姓名、活跃 - String bossNameRaw = safeText(detailBox, "h2[class*='name']"); - String[] bossInfo = splitBossName(bossNameRaw); - String bossName = bossInfo[0]; - String bossActive = bossInfo[1]; - if (config.getDeadStatus().stream().anyMatch(bossActive::contains)) continue; - // Boss公司/职位 - String bossTitleRaw = safeText(detailBox, "div[class*='boss-info-attr']"); - String[] bossTitleInfo = splitBossTitle(bossTitleRaw); - String bossCompany = bossTitleInfo[0]; - if (blackCompanies.stream().anyMatch(bossCompany::contains)) continue; - String bossJobTitle = bossTitleInfo[1]; - if (blackRecruiters.stream().anyMatch(bossJobTitle::contains)) continue; - - // 创建Job对象 - Job job = new Job(); - job.setJobName(jobName); - job.setSalary(jobSalary); - job.setJobArea(String.join(", ", tags)); - job.setCompanyName(bossCompany); - job.setRecruiter(bossName); - job.setJobInfo(jobDesc); - - // 输出 -// log.info("正在投递:第{}条 | 岗位名称:{} | 薪资:{} | 城市/经验/学历:{} | Boss姓名:{} | 活跃状态:{} | 公司:{} | 职位:{}", (i + 1), jobName, jobSalary, tags, bossName, bossActive, bossCompany, bossJobTitle); - resumeSubmission(page, keyword, job); - postCount++; - } - log.info("【{}】岗位已投递完毕!已投递岗位数量:{}", keyword, postCount); - } - } - - public static String decodeSalary(String text) { - Map fontMap = new HashMap<>(); - fontMap.put('', '0'); - fontMap.put('', '1'); - fontMap.put('', '2'); - fontMap.put('', '3'); - fontMap.put('', '4'); - fontMap.put('', '5'); - fontMap.put('', '6'); - fontMap.put('', '7'); - fontMap.put('', '8'); - fontMap.put('', '9'); - StringBuilder result = new StringBuilder(); - for (char c : text.toCharArray()) { - result.append(fontMap.getOrDefault(c, c)); - } - return result.toString(); - } - - // 安全获取单个文本内容 - public static String safeText(Locator root, String selector) { - Locator node = root.locator(selector); - try { - if (node.count() > 0 && node.innerText() != null) { - return node.innerText().trim(); - } - } catch (Exception e) { - // ignore - } - return ""; - } - - // 安全获取多个文本内容 - public static List safeAllText(Locator root, String selector) { - try { - return root.locator(selector).allInnerTexts(); - } catch (Exception e) { - return new ArrayList<>(); - } - } - - // Boss姓名+活跃状态拆分 - public static String[] splitBossName(String raw) { - String[] bossParts = raw.trim().split("\\s+"); - String bossName = bossParts[0]; - String bossActive = bossParts.length > 1 ? String.join(" ", Arrays.copyOfRange(bossParts, 1, bossParts.length)) : ""; - return new String[]{bossName, bossActive}; - } - - // Boss公司+职位拆分 - public static String[] splitBossTitle(String raw) { - String[] parts = raw.trim().split(" · "); - String company = parts[0]; - String job = parts.length > 1 ? parts[1] : ""; - return new String[]{company, job}; - } - - private static boolean isJobsPresent() { - try { - // 判断页面是否存在岗位的元素 - PlaywrightUtil.waitForElement(JOB_LIST_CONTAINER); - return true; - } catch (Exception e) { - log.error("加载岗位区块失败:{}", e.getMessage()); - return false; - } - } - - private static String getSearchUrl(String cityCode) { - return baseUrl + JobUtils.appendParam("city", cityCode) + - JobUtils.appendParam("jobType", config.getJobType()) + - JobUtils.appendParam("salary", config.getSalary()) + - JobUtils.appendListParam("experience", config.getExperience()) + - JobUtils.appendListParam("degree", config.getDegree()) + - JobUtils.appendListParam("scale", config.getScale()) + - JobUtils.appendListParam("industry", config.getIndustry()) + - JobUtils.appendListParam("stage", config.getStage()); - } - - private static void saveData(String path) { - try { - updateListData(); - Map> data = new HashMap<>(); - data.put("blackCompanies", blackCompanies); - data.put("blackRecruiters", blackRecruiters); - data.put("blackJobs", blackJobs); - String json = customJsonFormat(data); - Files.write(Paths.get(path), json.getBytes()); - } catch (IOException e) { - log.error("保存【{}】数据失败!", path); - } - } - - private static void updateListData() { - com.microsoft.playwright.Page page = PlaywrightUtil.getPageObject(); - page.navigate("https://www.zhipin.com/web/geek/chat"); - PlaywrightUtil.sleep(3); - - boolean shouldBreak = false; - while (!shouldBreak) { - try { - Locator bottomLocator = page.locator(FINISHED_TEXT); - if (bottomLocator.count() > 0 && "没有更多了".equals(bottomLocator.textContent())) { - shouldBreak = true; - } - } catch (Exception ignore) { - } - - Locator items = page.locator(CHAT_LIST_ITEM); - int itemCount = items.count(); - - for (int i = 0; i < itemCount; i++) { - try { - Locator companyElements = page.locator(COMPANY_NAME_IN_CHAT); - Locator messageElements = page.locator(LAST_MESSAGE); - - if (i >= companyElements.count() || i >= messageElements.count()) { - break; - } - - String companyName = null; - String message = null; - int retryCount = 0; - - while (retryCount < 2) { - try { - companyName = companyElements.nth(i).textContent(); - message = messageElements.nth(i).textContent(); - break; - } catch (Exception e) { - retryCount++; - if (retryCount >= 2) { - log.info("尝试获取元素文本2次失败,放弃本次获取"); - break; - } - log.info("页面元素已变更,正在重试第{}次获取元素文本...", retryCount); - PlaywrightUtil.sleep(1); - } - } - - if (companyName != null && message != null) { - boolean match = message.contains("不") || message.contains("感谢") || message.contains("但") - || message.contains("遗憾") || message.contains("需要本") || message.contains("对不"); - boolean nomatch = message.contains("不是") || message.contains("不生"); - if (match && !nomatch) { - log.info("黑名单公司:【{}】,信息:【{}】", companyName, message); - if (blackCompanies.stream().anyMatch(companyName::contains)) { - continue; - } - companyName = companyName.replaceAll("\\.{3}", ""); - if (companyName.matches(".*(\\p{IsHan}{2,}|[a-zA-Z]{4,}).*")) { - blackCompanies.add(companyName); - } - } - } - } catch (Exception e) { - log.error("寻找黑名单公司异常...", e); - } - } - - try { - Locator scrollElement = page.locator(SCROLL_LOAD_MORE); - if (scrollElement.count() > 0) { - scrollElement.scrollIntoViewIfNeeded(); - } else { - page.evaluate("window.scrollTo(0, document.body.scrollHeight);"); - } - } catch (Exception e) { - log.error("滚动元素出错", e); - break; - } - } - log.info("黑名单公司数量:{}", blackCompanies.size()); - } - - private static String customJsonFormat(Map> data) { - StringBuilder sb = new StringBuilder(); - sb.append("{\n"); - for (Map.Entry> entry : data.entrySet()) { - sb.append(" \"").append(entry.getKey()).append("\": [\n"); - sb.append(entry.getValue().stream().map(s -> " \"" + s + "\"").collect(Collectors.joining(",\n"))); - - sb.append("\n ],\n"); - } - sb.delete(sb.length() - 2, sb.length()); - sb.append("\n}"); - return sb.toString(); - } - - private static void loadData(String path) { - try { - String json = new String(Files.readAllBytes(Paths.get(path))); - parseJson(json); - } catch (IOException e) { - log.error("读取【{}】数据失败!", path); - } - } - - private static void parseJson(String json) { - JSONObject jsonObject = new JSONObject(json); - blackCompanies = jsonObject.getJSONArray("blackCompanies").toList().stream().map(Object::toString) - .collect(Collectors.toSet()); - blackRecruiters = jsonObject.getJSONArray("blackRecruiters").toList().stream().map(Object::toString) - .collect(Collectors.toSet()); - blackJobs = jsonObject.getJSONArray("blackJobs").toList().stream().map(Object::toString) - .collect(Collectors.toSet()); - } - - @SneakyThrows - private static void resumeSubmission(com.microsoft.playwright.Page page, String keyword, Job job) { - PlaywrightUtil.sleep(1); - - // 1. 查找“查看更多信息”按钮(必须存在且新开页) - Locator moreInfoBtn = page.locator("a.more-job-btn"); - if (moreInfoBtn.count() == 0) { - log.warn("未找到“查看更多信息”按钮,跳过..."); - return; - } - // 强制用js新开tab - String href = moreInfoBtn.first().getAttribute("href"); - if (href == null || !href.startsWith("/job_detail/")) { - log.warn("未获取到岗位详情链接,跳过..."); - return; - } - String detailUrl = "https://www.zhipin.com/" + href; - - // 2. 新开详情页 - com.microsoft.playwright.Page detailPage = page.context().newPage(); - detailPage.navigate(detailUrl); - PlaywrightUtil.sleep(1); // 页面加载 - - // 3. 查找“立即沟通”按钮 - Locator chatBtn = detailPage.locator("a.btn-startchat, a.op-btn-chat"); - boolean foundChatBtn = false; - for (int i = 0; i < 5; i++) { - if (chatBtn.count() > 0 && (chatBtn.first().textContent().contains("立即沟通"))) { - foundChatBtn = true; - break; - } - PlaywrightUtil.sleep(1); - } - if (!foundChatBtn) { - log.warn("未找到立即沟通按钮,跳过岗位: {}", job.getJobName()); - detailPage.close(); - return; - } - chatBtn.first().click(); - PlaywrightUtil.sleep(1); - - // 4. 等待聊天输入框 - Locator inputLocator = detailPage.locator("div#chat-input.chat-input[contenteditable='true'], textarea.input-area"); - boolean inputReady = false; - for (int i = 0; i < 10; i++) { - if (inputLocator.count() > 0 && inputLocator.first().isVisible()) { - inputReady = true; - break; - } - PlaywrightUtil.sleep(1); - } - if (!inputReady) { - log.warn("聊天输入框未出现,跳过: {}", job.getJobName()); - detailPage.close(); - return; - } - - // 5. AI智能生成打招呼语 - AiFilter aiResult = null; - if (config.getEnableAI()) { - String jd = job.getJobInfo(); - if (jd != null && !jd.isEmpty()) { - aiResult = checkJob(keyword, job.getJobName(), jd); - } - } - String sayHi = config.getSayHi().replaceAll("[\\r\\n]", ""); - String message = (aiResult != null && aiResult.getResult() && isValidString(aiResult.getMessage())) - ? aiResult.getMessage() : sayHi; - - // 6. 输入打招呼语 - Locator input = inputLocator.first(); - input.click(); - if (input.evaluate("el => el.tagName.toLowerCase()") instanceof String tag && tag.equals("textarea")) { - input.fill(message); - } else { - input.evaluate("(el, msg) => el.innerText = msg", message); - } - - // 7. 发送图片简历(可选) - boolean imgResume = false; - if (config.getSendImgResume()) { - try { - URL resourceUrl = Boss.class.getResource("/resume.jpg"); - if (resourceUrl != null) { - File imageFile = new File(resourceUrl.toURI()); - Locator fileInput = detailPage.locator("//div[@aria-label='发送图片']//input[@type='file']"); - if (fileInput.count() > 0) { - fileInput.setInputFiles(imageFile.toPath()); - imgResume = true; - } - } - } catch (Exception e) { - log.error("发送图片简历失败: {}", e.getMessage()); - } - } - - // 8. 点击发送按钮(div.send-message 或 button.btn-send) - Locator sendBtn = detailPage.locator("div.send-message, button[type='send'].btn-send, button.btn-send"); - boolean sendSuccess = false; - if (sendBtn.count() > 0) { - sendBtn.first().click(); - PlaywrightUtil.sleep(1); - sendSuccess = true; - } else { - log.warn("未找到发送按钮,自动跳过!岗位:{}", job.getJobName()); - } - - log.info("投递完成 | 岗位:{} | 招呼语:{} | 图片简历:{}", job.getJobName(), message, imgResume ? "已发送" : "未发送"); - - // 9. 关闭详情页,回到主页面 - detailPage.close(); - PlaywrightUtil.sleep(1); - - // 10. 成功投递加入结果 - if (sendSuccess) { - resultList.add(job); - } - } - - public static boolean isValidString(String str) { - return str != null && !str.isEmpty(); - } - - public static Boolean sendResume(String company) { - log.warn("sendResume方法已废弃,请直接在主逻辑中使用playwright实现文件上传"); - return false; - } - - /** - * 检查岗位薪资是否符合预期 - * - * @return boolean - * true 不符合预期 - * false 符合预期 - * 期望的最低薪资如果比岗位最高薪资还小,则不符合(薪资给的太少) - * 期望的最高薪资如果比岗位最低薪资还小,则不符合(要求太高满足不了) - */ - private static boolean isSalaryNotExpected(String salary) { - try { - // 1. 如果没有期望薪资范围,直接返回 false,表示"薪资并非不符合预期" - List expectedSalary = config.getExpectedSalary(); - if (!hasExpectedSalary(expectedSalary)) { - return false; - } - - // 2. 清理薪资文本(比如去掉 "·15薪") - salary = removeYearBonusText(salary); - - // 3. 如果薪资格式不符合预期(如缺少 "K" / "k"),直接返回 true,表示"薪资不符合预期" - if (!isSalaryInExpectedFormat(salary)) { - return true; - } - - // 4. 进一步清理薪资文本,比如去除 "K"、"k"、"·" 等 - salary = cleanSalaryText(salary); - - // 5. 判断是 "月薪" 还是 "日薪" - String jobType = detectJobType(salary); - salary = removeDayUnitIfNeeded(salary); // 如果是按天,则去除 "元/天" - - // 6. 解析薪资范围并检查是否超出预期 - Integer[] jobSalaryRange = parseSalaryRange(salary); - return isSalaryOutOfRange(jobSalaryRange, - getMinimumSalary(expectedSalary), - getMaximumSalary(expectedSalary), - jobType); - - } catch (Exception e) { - log.error("岗位薪资获取异常!薪资文本【{}】,异常信息【{}】", salary, e.getMessage(), e); - // 出错时,您可根据业务需求决定返回 true 或 false - // 这里假设出错时无法判断,视为不满足预期 => 返回 true - return true; - } - } - - /** - * 是否存在有效的期望薪资范围 - */ - private static boolean hasExpectedSalary(List expectedSalary) { - return expectedSalary != null && !expectedSalary.isEmpty(); - } - - /** - * 去掉年终奖信息,如 "·15薪"、"·13薪"。 - */ - private static String removeYearBonusText(String salary) { - if (salary.contains("薪")) { - // 使用正则去除 "·任意数字薪" - return salary.replaceAll("·\\d+薪", ""); - } - return salary; - } - - /** - * 判断是否是按天计薪,如发现 "元/天" 则认为是日薪 - */ - private static String detectJobType(String salary) { - if (salary.contains("元/天")) { - return "day"; - } - return "mouth"; - } - - /** - * 如果是日薪,则去除 "元/天" - */ - private static String removeDayUnitIfNeeded(String salary) { - if (salary.contains("元/天")) { - return salary.replaceAll("元/天", ""); - } - return salary; - } - - private static Integer getMinimumSalary(List expectedSalary) { - return expectedSalary != null && !expectedSalary.isEmpty() ? expectedSalary.get(0) : null; - } - - private static Integer getMaximumSalary(List expectedSalary) { - return expectedSalary != null && expectedSalary.size() > 1 ? expectedSalary.get(1) : null; - } - - private static boolean isSalaryInExpectedFormat(String salaryText) { - return salaryText.contains("K") || salaryText.contains("k") || salaryText.contains("元/天"); - } - - private static String cleanSalaryText(String salaryText) { - salaryText = salaryText.replace("K", "").replace("k", ""); - int dotIndex = salaryText.indexOf('·'); - if (dotIndex != -1) { - salaryText = salaryText.substring(0, dotIndex); - } - return salaryText; - } - - private static boolean isSalaryOutOfRange(Integer[] jobSalary, Integer miniSalary, Integer maxSalary, - String jobType) { - if (jobSalary == null) { - return true; - } - if (miniSalary == null) { - return false; - } - if (Objects.equals("day", jobType)) { - // 期望薪资转为平均每日的工资 - maxSalary = BigDecimal.valueOf(maxSalary).multiply(BigDecimal.valueOf(1000)) - .divide(BigDecimal.valueOf(21.75), 0, RoundingMode.HALF_UP).intValue(); - miniSalary = BigDecimal.valueOf(miniSalary).multiply(BigDecimal.valueOf(1000)) - .divide(BigDecimal.valueOf(21.75), 0, RoundingMode.HALF_UP).intValue(); - } - // 如果职位薪资下限低于期望的最低薪资,返回不符合 - if (jobSalary[1] < miniSalary) { - return true; - } - // 如果职位薪资上限高于期望的最高薪资,返回不符合 - return maxSalary != null && jobSalary[0] > maxSalary; - } - - private static void RandomWait() { - PlaywrightUtil.sleep(JobUtils.getRandomNumberInRange(3, 20)); - } - - private static void simulateWait() { - com.microsoft.playwright.Page page = PlaywrightUtil.getPageObject(); - for (int i = 0; i < 3; i++) { - page.keyboard().press(" "); - PlaywrightUtil.sleep(1); - } - page.keyboard().press("Control+Home"); - PlaywrightUtil.sleep(1); - } - - private static boolean isDeadHR(com.microsoft.playwright.Page page) { - if (!config.getFilterDeadHR()) { - return false; - } - try { - // 尝试获取 HR 的活跃时间 - Locator activeTimeLocator = page.locator(HR_ACTIVE_TIME); - if (activeTimeLocator.count() > 0) { - String activeTimeText = activeTimeLocator.textContent(); - log.info("{}:{}", getCompanyAndHR(page), activeTimeText); - // 如果 HR 活跃状态符合预期,则返回 true - return containsDeadStatus(activeTimeText, config.getDeadStatus()); - } - } catch (Exception e) { - log.info("没有找到【{}】的活跃状态, 默认此岗位将会投递...", getCompanyAndHR(page)); - } - return false; - } - - public static boolean containsDeadStatus(String activeTimeText, List deadStatus) { - for (String status : deadStatus) { - if (activeTimeText.contains(status)) { - return true;// 一旦找到包含的值,立即返回 true - } - } - return false;// 如果没有找到,返回 false - } - - private static String getCompanyAndHR(com.microsoft.playwright.Page page) { - Locator recruiterLocator = page.locator(RECRUITER_INFO); - if (recruiterLocator.count() > 0) { - return recruiterLocator.textContent().replaceAll("\n", ""); - } - return "未知公司和HR"; - } - - private static void closeWindow(ArrayList tabs) { - log.warn("closeWindow方法已废弃,请使用playwright的page.close()方法"); - // 该方法已废弃,在playwright中直接使用page.close() - } - - private static AiFilter checkJob(String keyword, String jobName, String jd) { - AiConfig aiConfig = AiConfig.init(); - String requestMessage = String.format(aiConfig.getPrompt(), aiConfig.getIntroduce(), keyword, jobName, jd, - config.getSayHi()); - String result = AiService.sendRequest(requestMessage); - return result.contains("false") ? new AiFilter(false) : new AiFilter(true, result); - } - - private static Integer[] parseSalaryRange(String salaryText) { - try { - return Arrays.stream(salaryText.split("-")).map(s -> s.replaceAll("[^0-9]", "")) // 去除非数字字符 - .map(Integer::parseInt) // 转换为Integer - .toArray(Integer[]::new); // 转换为Integer数组 - } catch (Exception e) { - log.error("薪资解析异常!{}", e.getMessage(), e); - } - return null; - } - - private static boolean isLimit(com.microsoft.playwright.Page page) { - try { - PlaywrightUtil.sleep(1); - Locator dialogLocator = page.locator(DIALOG_CON); - if (dialogLocator.count() > 0) { - String text = dialogLocator.textContent(); - return text.contains("已达上限"); - } - return false; - } catch (Exception e) { - return false; - } - } - - @SneakyThrows - private static void login() { - log.info("打开Boss直聘网站中..."); - - com.microsoft.playwright.Page page = PlaywrightUtil.getPageObject(); - page.navigate(homeUrl); - PlaywrightUtil.sleep(1); - // 检查滑块验证 - waitForSliderVerify(page); - - if (PlaywrightUtil.isCookieValid(cookiePath)) { - PlaywrightUtil.loadCookies(cookiePath); - page.reload(); - PlaywrightUtil.sleep(1); - waitForSliderVerify(page); - // 启用反检测模式 - PlaywrightUtil.initStealth(); - } - - if (isLoginRequired()) { - log.error("cookie失效,尝试扫码登录..."); - scanLogin(); - } - } - - private static void waitForSliderVerify(com.microsoft.playwright.Page page) { - String SLIDER_URL = "https://www.zhipin.com/web/user/safe/verify-slider"; - // 最多等待5分钟(防呆,防止死循环) - long start = System.currentTimeMillis(); - while (true) { - String url = page.url(); - if (url != null && url.startsWith(SLIDER_URL)) { - System.out.println("\n【滑块验证】请手动完成Boss直聘滑块验证,通过后在控制台回车继续…"); - try { - System.in.read(); - } catch (Exception e) { - log.error("等待滑块验证输入异常: {}", e.getMessage()); - } - PlaywrightUtil.sleep(1); - // 验证通过后页面url会变,循环再检测一次 - continue; - } - if ((System.currentTimeMillis() - start) > 5 * 60 * 1000) { - throw new RuntimeException("滑块验证超时!"); - } - break; - } - } - - - private static boolean isLoginRequired() { - try { - com.microsoft.playwright.Page page = PlaywrightUtil.getPageObject(); - Locator buttonLocator = page.locator(LOGIN_BTNS); - if (buttonLocator.count() > 0 && buttonLocator.textContent().contains("登录")) { - return true; - } - } catch (Exception e) { - try { - com.microsoft.playwright.Page page = PlaywrightUtil.getPageObject(); - page.locator(PAGE_HEADER).waitFor(); - Locator errorLoginLocator = page.locator(ERROR_PAGE_LOGIN); - if (errorLoginLocator.count() > 0) { - errorLoginLocator.click(); - } - return true; - } catch (Exception ex) { - log.info("没有出现403访问异常"); - } - log.info("cookie有效,已登录..."); - return false; - } - return false; - } - - @SneakyThrows - private static void scanLogin() { - // 访问登录页面 - com.microsoft.playwright.Page page = PlaywrightUtil.getPageObject(); - page.navigate(homeUrl + "/web/user/?ka=header-login"); - PlaywrightUtil.sleep(1); - - // 1. 如果已经登录,则直接返回 - try { - Locator loginBtnLocator = page.locator(LOGIN_BTN); - if (loginBtnLocator.count() > 0 && !Objects.equals(loginBtnLocator.textContent(), "登录")) { - log.info("已经登录,直接开始投递..."); - return; - } - } catch (Exception ignored) { - } - - log.info("等待登录..."); - - // 2. 定位二维码登录的切换按钮 - try { - Locator scanButton = page.locator(LOGIN_SCAN_SWITCH); - scanButton.click(); - - // 3. 登录逻辑 - boolean login = false; - - // 4. 记录开始时间,用于判断10分钟超时 - long startTime = System.currentTimeMillis(); - final long TIMEOUT = 10 * 60 * 1000; // 10分钟 - - while (!login) { - // 判断是否超时 - long elapsed = System.currentTimeMillis() - startTime; - if (elapsed >= TIMEOUT) { - log.error("超过10分钟未完成登录,程序退出..."); - System.exit(1); - } - - try { - // 判断页面上是否出现职位列表容器 - Locator jobList = page.locator("div.job-list-container"); - if (jobList.isVisible()) { - login = true; - log.info("用户已登录!"); - // 登录成功,保存Cookie - PlaywrightUtil.saveCookies(cookiePath); - break; - } - } catch (Exception e) { - log.error("检测元素时异常: {}", e.getMessage()); - } - // 每2秒检查一次 - Thread.sleep(2000); - } - - - } catch (Exception e) { - log.error("未找到二维码登录按钮,登录失败", e); - } - } - - /** - * 在指定的毫秒数内等待用户输入回车;若在等待时间内用户按回车则返回 true,否则返回 false。 - * - * @param scanner 用于读取控制台输入 - * @return 用户是否在指定时间内按回车 - */ - private static boolean waitForUserInputOrTimeout(Scanner scanner) { - long end = System.currentTimeMillis() + 2000; - while (System.currentTimeMillis() < end) { - try { - // 判断输入流中是否有可用字节 - if (System.in.available() > 0) { - // 读取一行(用户输入) - scanner.nextLine(); - return true; - } - } catch (IOException e) { - // 读取输入流异常,直接忽略 - } - - // 小睡一下,避免 CPU 空转 - PlaywrightUtil.sleep(1); - } - return false; - } - -} diff --git a/src/main/java/boss/BossConfig.java b/src/main/java/boss/BossConfig.java deleted file mode 100644 index 85a5cef0..00000000 --- a/src/main/java/boss/BossConfig.java +++ /dev/null @@ -1,211 +0,0 @@ -package boss; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Data; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import utils.JobUtils; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -@Data -@Slf4j -public class BossConfig { - /** - * 用于打招呼的语句 - */ - private String sayHi; - - /** - * 开发者模式 - */ - private Boolean debugger; - - /** - * 搜索关键词列表 - */ - private List keywords; - - /** - * 城市编码 - */ - private List cityCode; - - /** - * 自定义城市编码映射 - */ - private Map customCityCode; - - /** - * 行业列表 - */ - private List industry; - - /** - * 工作经验要求 - */ - private List experience; - - /** - * 工作类型 - */ - private String jobType; - - /** - * 薪资范围 - */ - private String salary; - - /** - * 学历要求列表 - */ - private List degree; - - /** - * 公司规模列表 - */ - private List scale; - - /** - * 公司融资阶段列表 - */ - private List stage; - - /** - * 是否开放AI检测 - */ - private Boolean enableAI; - - /** - * 是否过滤不活跃hr - */ - private Boolean filterDeadHR; - - /** - * 是否发送图片简历 - */ - private Boolean sendImgResume; - - /** - * 目标薪资 - */ - private List expectedSalary; - - /** - * 等待时间 - */ - private String waitTime; - - /** - * HR未上线状态 - */ - private List deadStatus; - - /** - * 城市代码映射缓存 - */ - private static Map cityCodeMap = new HashMap<>(); - - /** - * 从JSON文件加载城市代码 - */ - @SuppressWarnings("unchecked") - private static void loadCityCodeFromJson() { - if (!cityCodeMap.isEmpty()) { - return; - } - - try { - ObjectMapper mapper = new ObjectMapper(); - File jsonFile = new File("src/main/java/boss/city-industry-code.json"); - - if (!jsonFile.exists()) { - log.error("城市代码JSON文件不存在: {}", jsonFile.getAbsolutePath()); - return; - } - - // 读取JSON文件 - Map data = mapper.readValue(jsonFile, new TypeReference<>() {}); - List> cityList = (List>) data.get("city"); - - if (cityList != null) { - for (Map city : cityList) { - String name = (String) city.get("name"); - Object codeObj = city.get("code"); - String code = codeObj != null ? codeObj.toString() : ""; - cityCodeMap.put(name, code); - } - } - } catch (IOException e) { - log.error("加载城市代码失败: {}", e.getMessage(), e); - } - } - - /** - * 根据城市名称获取城市代码 - * @param cityName 城市名称 - * @return 城市代码,如果未找到返回null - */ - private static String getCityCodeFromJson(String cityName) { - return cityCodeMap.get(cityName); - } - - @SneakyThrows - public static BossConfig init() { - BossConfig config = JobUtils.getConfig(BossConfig.class); - - // 加载城市代码JSON数据 - loadCityCodeFromJson(); - - // 转换工作类型 - config.setJobType(BossEnum.JobType.forValue(config.getJobType()).getCode()); - // 转换薪资范围 - config.setSalary(BossEnum.Salary.forValue(config.getSalary()).getCode()); - // 转换城市编码 - List convertedCityCodes = config.getCityCode().stream() - .map(city -> { - // 优先从自定义映射中获取 - if (config.getCustomCityCode() != null && config.getCustomCityCode().containsKey(city)) { - return config.getCustomCityCode().get(city); - } - // 尝试从枚举中获取(不限、全国) - BossEnum.CityCode enumCity = BossEnum.CityCode.forValue(city); - if (enumCity != BossEnum.CityCode.NULL || "不限".equals(city) || "全国".equals(city)) { - return enumCity.getCode(); - } - // 从JSON文件中获取 - String codeFromJson = getCityCodeFromJson(city); - if (codeFromJson != null) { - return codeFromJson; - } - // 如果都找不到,返回"不限"的代码 - log.warn("未找到城市【{}】的代码,使用默认值", city); - return "0"; - }) - .collect(Collectors.toList()); - config.setCityCode(convertedCityCodes); - // 转换工作经验要求 - config.setExperience(config.getExperience().stream().map(value -> BossEnum.Experience.forValue(value).getCode()).collect(Collectors.toList())); - // 转换学历要求 - config.setDegree(config.getDegree().stream().map(value -> BossEnum.Degree.forValue(value).getCode()).collect(Collectors.toList())); - // 转换公司规模 - config.setScale(config.getScale().stream().map(value -> BossEnum.Scale.forValue(value).getCode()).collect(Collectors.toList())); - // 转换公司融资阶段 - config.setStage(config.getStage().stream().map(value -> BossEnum.Financing.forValue(value).getCode()).collect(Collectors.toList())); - // 转换行业 - config.setIndustry(config.getIndustry().stream().map(value -> BossEnum.Industry.forValue(value).getCode()).collect(Collectors.toList())); - - return config; - } - -} diff --git a/src/main/java/boss/BossEnum.java b/src/main/java/boss/BossEnum.java deleted file mode 100644 index 9d1ae686..00000000 --- a/src/main/java/boss/BossEnum.java +++ /dev/null @@ -1,243 +0,0 @@ -package boss; - -import com.fasterxml.jackson.annotation.JsonCreator; -import lombok.Getter; - -import java.util.Arrays; -import java.util.Optional; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -public class BossEnum { - @Getter - public enum Experience { - NULL("不限", "0"), - STUDENT("在校生", "108"), - GRADUATE("应届毕业生", "102"), - UNLIMITED("经验不限", "101"), - LESS_THAN_ONE_YEAR("1年以下", "103"), - ONE_TO_THREE_YEARS("1-3年", "104"), - THREE_TO_FIVE_YEARS("3-5年", "105"), - FIVE_TO_TEN_YEARS("5-10年", "106"), - MORE_THAN_TEN_YEARS("10年以上", "107"); - - private final String name; - private final String code; - - Experience(String name, String code) { - this.name = name; - this.code = code; - } - - public static Optional getCode(String name) { - return Arrays.stream(Experience.values()).filter(experience -> experience.name.equals(name)).findFirst().map(experience -> experience.code); - } - - @JsonCreator - public static Experience forValue(String value) { - for (Experience experience : Experience.values()) { - if (experience.name.equals(value)) { - return experience; - } - } - return NULL; - } - } - - @Getter - public enum CityCode { - NULL("不限", "0"), - ALL("全国", "100010000"); - - private final String name; - private final String code; - - CityCode(String name, String code) { - this.name = name; - this.code = code; - } - - @JsonCreator - public static CityCode forValue(String value) { - for (CityCode cityCode : CityCode.values()) { - if (cityCode.name.equals(value)) { - return cityCode; - } - } - return NULL; - } - - } - - @Getter - public enum JobType { - NULL("不限", "0"), - FULL_TIME("全职", "1901"), - PART_TIME("兼职", "1903"); - - private final String name; - private final String code; - - JobType(String name, String code) { - this.name = name; - this.code = code; - } - - @JsonCreator - public static JobType forValue(String value) { - for (JobType jobType : JobType.values()) { - if (jobType.name.equals(value)) { - return jobType; - } - } - return NULL; - } - } - - @Getter - public enum Salary { - NULL("不限", "0"), - BELOW_3K("3K以下", "402"), - FROM_3K_TO_5K("3-5K", "403"), - FROM_5K_TO_10K("5-10K", "404"), - FROM_10K_TO_20K("10-20K", "405"), - FROM_20K_TO_50K("20-50K", "406"), - ABOVE_50K("50K以上", "407"); - - private final String name; - private final String code; - - Salary(String name, String code) { - this.name = name; - this.code = code; - } - - @JsonCreator - public static Salary forValue(String value) { - for (Salary salary : Salary.values()) { - if (salary.name.equals(value)) { - return salary; - } - } - return NULL; - } - } - - @Getter - public enum Degree { - NULL("不限", "0"), - BELOW_JUNIOR_HIGH_SCHOOL("初中及以下", "209"), - SECONDARY_VOCATIONAL("中专/中技", "208"), - HIGH_SCHOOL("高中", "206"), - JUNIOR_COLLEGE("大专", "202"), - BACHELOR("本科", "203"), - MASTER("硕士", "204"), - DOCTOR("博士", "205"); - - private final String name; - private final String code; - - Degree(String name, String code) { - this.name = name; - this.code = code; - } - - @JsonCreator - public static Degree forValue(String value) { - for (Degree degree : Degree.values()) { - if (degree.name.equals(value)) { - return degree; - } - } - return NULL; - } - } - - @Getter - public enum Scale { - NULL("不限", "0"), - ZERO_TO_TWENTY("0-20人", "301"), - TWENTY_TO_NINETY_NINE("20-99人", "302"), - ONE_HUNDRED_TO_FOUR_NINETY_NINE("100-499人", "303"), - FIVE_HUNDRED_TO_NINE_NINETY_NINE("500-999人", "304"), - ONE_THOUSAND_TO_NINE_NINE_NINE_NINE("1000-9999人", "305"), - TEN_THOUSAND_ABOVE("10000人以上", "306"); - - private final String name; - private final String code; - - Scale(String name, String code) { - this.name = name; - this.code = code; - } - - @JsonCreator - public static Scale forValue(String value) { - for (Scale scale : Scale.values()) { - if (scale.name.equals(value)) { - return scale; - } - } - return NULL; - } - } - - @Getter - public enum Financing { - NULL("不限", "0"), - UNFUNDED("未融资", "801"), - ANGEL_ROUND("天使轮", "802"), - A_ROUND("A轮", "803"), - B_ROUND("B轮", "804"), - C_ROUND("C轮", "805"), - D_AND_ABOVE("D轮及以上", "806"), - LISTED("已上市", "807"), - NO_NEED("不需要融资", "808"); - - private final String name; - private final String code; - - Financing(String name, String code) { - this.name = name; - this.code = code; - } - - @JsonCreator - public static Financing forValue(String value) { - for (Financing financing : Financing.values()) { - if (financing.name.equals(value)) { - return financing; - } - } - return NULL; - } - } - - @Getter - public enum Industry { - NULL("不限", "0"), - INTERNET("互联网", "100020"), - COMPUTER_SOFTWARE("计算机软件", "100021"), - CLOUD_COMPUTING("云计算", "100029"); - - private final String name; - private final String code; - - Industry(String name, String code) { - this.name = name; - this.code = code; - } - - @JsonCreator - public static Industry forValue(String value) { - for (Industry industry : Industry.values()) { - if (industry.name.equals(value)) { - return industry; - } - } - return NULL; - } - } -} diff --git a/src/main/java/boss/BossScheduled.java b/src/main/java/boss/BossScheduled.java deleted file mode 100644 index f2defb64..00000000 --- a/src/main/java/boss/BossScheduled.java +++ /dev/null @@ -1,30 +0,0 @@ -package boss; - -import lombok.extern.slf4j.Slf4j; -import utils.JobUtils; -import utils.Platform; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -@Slf4j -public class BossScheduled { - - public static void main(String[] args) { - JobUtils.runScheduled(Platform.BOSS); - } - - public static void postJobs() { - safeRun(() -> Boss.main(null)); - } - - // 任务执行的安全包装,防止异常 - private static void safeRun(Runnable task) { - try { - task.run(); - } catch (Exception e) { - log.error("safeRun异常:{}", e.getMessage(), e); - } - } -} diff --git a/src/main/java/boss/Locators.java b/src/main/java/boss/Locators.java deleted file mode 100644 index 3bfdeb63..00000000 --- a/src/main/java/boss/Locators.java +++ /dev/null @@ -1,63 +0,0 @@ -package boss; - -/** - * Boss直聘网页元素定位器 - * 集中管理所有页面元素的定位表达式 - */ -public class Locators { - // 主页相关元素 - public static final String LOGIN_BTN = "//li[@class='nav-figure']"; - public static final String LOGIN_SCAN_SWITCH = "//div[@class='btn-sign-switch ewm-switch']"; - - /** - * 搜索结果页相关元素 - */ - // 用于判断岗位列表区块是否加载完成 - public static final String JOB_LIST_CONTAINER = "//div[@class='job-list-container']"; - // 定位一个岗位卡 - public static final String JOB_CARD_BOX = "li.job-card-box"; - - /** - * 岗位列表 - */ - // 定位所有岗位卡片,用于获取当前获取到的岗位总数 - public static final String JOB_LIST_SELECTOR = "ul.rec-job-list li.job-card-box"; - // 岗位名称 - public static final String JOB_NAME = "a.job-name"; - // 公司名称 - public static final String COMPANY_NAME = "span.boss-name"; - // 公司区域 - public static final String JOB_AREA = "span.company-location"; - // 岗位标签 - public static final String TAG_LIST = "ul.tag-list li"; - - // 职位详情页元素 - public static final String CHAT_BUTTON = "[class*='btn btn-startchat']"; - public static final String ERROR_CONTENT = "//div[@class='error-content']"; - public static final String JOB_DETAIL_SALARY = "//div[@class='info-primary']//span[@class='salary']"; - public static final String RECRUITER_INFO = "//div[@class='boss-info-attr']"; - public static final String HR_ACTIVE_TIME = "//span[@class='boss-active-time']"; - public static final String JOB_DESCRIPTION = "//div[@class='job-sec-text']"; - - // 聊天相关元素 - public static final String DIALOG_TITLE = "//div[@class='dialog-title']"; - public static final String DIALOG_CLOSE = "//i[@class='icon-close']"; - public static final String CHAT_INPUT = "//div[@id='chat-input']"; - public static final String DIALOG_CONTAINER = "//div[@class='dialog-container']"; - public static final String SEND_BUTTON = "//button[@type='send']"; - public static final String IMAGE_UPLOAD = "//div[@aria-label='发送图片']//input[@type='file']"; - public static final String DIALOG_CONTENT = "//div[@class='dialog-con']"; - public static final String SCROLL_LOAD_MORE = "//div[contains(text(), '滚动加载更多')]"; - - // 消息列表页元素 - public static final String CHAT_LIST_ITEM = "//li[@role='listitem']"; - public static final String COMPANY_NAME_IN_CHAT = "//div[@class='title-box']/span[@class='name-box']//span[2]"; - public static final String LAST_MESSAGE = "//div[@class='gray last-msg']/span[@class='last-msg-text']"; - public static final String FINISHED_TEXT = "//div[@class='finished']"; - - public static final String DIALOG_CON = ".dialog-con"; - public static final String LOGIN_BTNS = "//div[@class='btns']"; - public static final String PAGE_HEADER = "//h1"; - public static final String ERROR_PAGE_LOGIN = "//a[@ka='403_login']"; - -} \ No newline at end of file diff --git a/src/main/java/boss/city-industry-code.json b/src/main/java/boss/city-industry-code.json deleted file mode 100644 index a229709f..00000000 --- a/src/main/java/boss/city-industry-code.json +++ /dev/null @@ -1,2138 +0,0 @@ -{ - "city": [ - { - "name": "鞍山", - "code": 101070300 - }, - { - "name": "阿拉善盟", - "code": 101081200 - }, - { - "name": "安康", - "code": 101110700 - }, - { - "name": "阿克苏地区", - "code": 101131000 - }, - { - "name": "阿勒泰地区", - "code": 101131500 - }, - { - "name": "阿拉尔", - "code": 101131700 - }, - { - "name": "阿里地区", - "code": 101140700 - }, - { - "name": "安阳", - "code": 101180200 - }, - { - "name": "安庆", - "code": 101220600 - }, - { - "name": "安顺", - "code": 101260300 - }, - { - "name": "阿坝藏族羌族自治州", - "code": 101271900 - }, - { - "name": "澳门", - "code": 101330100 - }, - { - "name": "北京", - "code": 101010100 - }, - { - "name": "白城", - "code": 101060500 - }, - { - "name": "白山", - "code": 101060800 - }, - { - "name": "本溪", - "code": 101070500 - }, - { - "name": "包头", - "code": 101080200 - }, - { - "name": "巴彦淖尔", - "code": 101080800 - }, - { - "name": "保定", - "code": 101090200 - }, - { - "name": "宝鸡", - "code": 101110900 - }, - { - "name": "滨州", - "code": 101121100 - }, - { - "name": "巴音郭楞蒙古自治州", - "code": 101130400 - }, - { - "name": "博尔塔拉蒙古自治州", - "code": 101130500 - }, - { - "name": "北屯市", - "code": 101132100 - }, - { - "name": "白杨市", - "code": 101132700 - }, - { - "name": "白银", - "code": 101161000 - }, - { - "name": "蚌埠", - "code": 101220200 - }, - { - "name": "亳州", - "code": 101220900 - }, - { - "name": "毕节", - "code": 101260500 - }, - { - "name": "巴中", - "code": 101270900 - }, - { - "name": "保山", - "code": 101290300 - }, - { - "name": "百色", - "code": 101301000 - }, - { - "name": "北海", - "code": 101301300 - }, - { - "name": "白沙黎族自治县", - "code": 101311400 - }, - { - "name": "保亭黎族苗族自治县", - "code": 101311800 - }, - { - "name": "重庆", - "code": 101040100 - }, - { - "name": "长春", - "code": 101060100 - }, - { - "name": "朝阳", - "code": 101071200 - }, - { - "name": "赤峰", - "code": 101080500 - }, - { - "name": "承德", - "code": 101090400 - }, - { - "name": "沧州", - "code": 101090700 - }, - { - "name": "长治", - "code": 101100500 - }, - { - "name": "昌吉回族自治州", - "code": 101130300 - }, - { - "name": "昌都", - "code": 101140300 - }, - { - "name": "常州", - "code": 101191100 - }, - { - "name": "滁州", - "code": 101221000 - }, - { - "name": "池州", - "code": 101221500 - }, - { - "name": "长沙", - "code": 101250100 - }, - { - "name": "郴州", - "code": 101250500 - }, - { - "name": "常德", - "code": 101250600 - }, - { - "name": "成都", - "code": 101270100 - }, - { - "name": "潮州", - "code": 101281500 - }, - { - "name": "楚雄彝族自治州", - "code": 101291700 - }, - { - "name": "崇左", - "code": 101300200 - }, - { - "name": "澄迈", - "code": 101311200 - }, - { - "name": "昌江黎族自治县", - "code": 101311500 - }, - { - "name": "大庆", - "code": 101050800 - }, - { - "name": "大兴安岭地区", - "code": 101051300 - }, - { - "name": "大连", - "code": 101070200 - }, - { - "name": "丹东", - "code": 101070600 - }, - { - "name": "大同", - "code": 101100200 - }, - { - "name": "德州", - "code": 101120400 - }, - { - "name": "东营", - "code": 101121200 - }, - { - "name": "定西", - "code": 101160200 - }, - { - "name": "达州", - "code": 101270600 - }, - { - "name": "德阳", - "code": 101271700 - }, - { - "name": "东莞", - "code": 101281600 - }, - { - "name": "东沙群岛", - "code": 101282200 - }, - { - "name": "德宏傣族景颇族自治州", - "code": 101291300 - }, - { - "name": "迪庆藏族自治州", - "code": 101291500 - }, - { - "name": "大理白族自治州", - "code": 101291600 - }, - { - "name": "儋州", - "code": 101310400 - }, - { - "name": "东方", - "code": 101310900 - }, - { - "name": "定安", - "code": 101311000 - }, - { - "name": "鄂尔多斯", - "code": 101080600 - }, - { - "name": "鄂州", - "code": 101200300 - }, - { - "name": "恩施土家族苗族自治州", - "code": 101201300 - }, - { - "name": "抚顺", - "code": 101070400 - }, - { - "name": "阜新", - "code": 101070900 - }, - { - "name": "阜阳", - "code": 101220800 - }, - { - "name": "福州", - "code": 101230100 - }, - { - "name": "抚州", - "code": 101240400 - }, - { - "name": "佛山", - "code": 101280800 - }, - { - "name": "防城港", - "code": 101301400 - }, - { - "name": "果洛藏族自治州", - "code": 101150600 - }, - { - "name": "甘南藏族自治州", - "code": 101161400 - }, - { - "name": "固原", - "code": 101170400 - }, - { - "name": "赣州", - "code": 101240700 - }, - { - "name": "贵阳", - "code": 101260100 - }, - { - "name": "广安", - "code": 101270800 - }, - { - "name": "广元", - "code": 101271800 - }, - { - "name": "甘孜藏族自治州", - "code": 101272100 - }, - { - "name": "广州", - "code": 101280100 - }, - { - "name": "桂林", - "code": 101300500 - }, - { - "name": "贵港", - "code": 101300800 - }, - { - "name": "哈尔滨", - "code": 101050100 - }, - { - "name": "黑河", - "code": 101050600 - }, - { - "name": "鹤岗", - "code": 101051100 - }, - { - "name": "葫芦岛", - "code": 101071400 - }, - { - "name": "呼和浩特", - "code": 101080100 - }, - { - "name": "呼伦贝尔", - "code": 101080700 - }, - { - "name": "衡水", - "code": 101090800 - }, - { - "name": "邯郸", - "code": 101091000 - }, - { - "name": "汉中", - "code": 101110800 - }, - { - "name": "菏泽", - "code": 101121000 - }, - { - "name": "哈密", - "code": 101130900 - }, - { - "name": "和田地区", - "code": 101131300 - }, - { - "name": "胡杨河市", - "code": 101132600 - }, - { - "name": "海东", - "code": 101150200 - }, - { - "name": "海北藏族自治州", - "code": 101150300 - }, - { - "name": "黄南藏族自治州", - "code": 101150400 - }, - { - "name": "海南藏族自治州", - "code": 101150500 - }, - { - "name": "海西蒙古族藏族自治州", - "code": 101150800 - }, - { - "name": "鹤壁", - "code": 101181200 - }, - { - "name": "淮安", - "code": 101190900 - }, - { - "name": "黄冈", - "code": 101200500 - }, - { - "name": "黄石", - "code": 101200600 - }, - { - "name": "杭州", - "code": 101210100 - }, - { - "name": "湖州", - "code": 101210200 - }, - { - "name": "合肥", - "code": 101220100 - }, - { - "name": "淮南", - "code": 101220400 - }, - { - "name": "淮北", - "code": 101221100 - }, - { - "name": "黄山", - "code": 101221600 - }, - { - "name": "衡阳", - "code": 101250400 - }, - { - "name": "怀化", - "code": 101251200 - }, - { - "name": "惠州", - "code": 101280300 - }, - { - "name": "河源", - "code": 101281200 - }, - { - "name": "红河哈尼族彝族自治州", - "code": 101291200 - }, - { - "name": "贺州", - "code": 101300700 - }, - { - "name": "河池", - "code": 101301200 - }, - { - "name": "海口", - "code": 101310100 - }, - { - "name": "佳木斯", - "code": 101050400 - }, - { - "name": "鸡西", - "code": 101051000 - }, - { - "name": "吉林", - "code": 101060200 - }, - { - "name": "锦州", - "code": 101070700 - }, - { - "name": "晋中", - "code": 101100400 - }, - { - "name": "晋城", - "code": 101100600 - }, - { - "name": "济南", - "code": 101120100 - }, - { - "name": "济宁", - "code": 101120700 - }, - { - "name": "金昌", - "code": 101160600 - }, - { - "name": "酒泉", - "code": 101160800 - }, - { - "name": "嘉峪关", - "code": 101161200 - }, - { - "name": "焦作", - "code": 101181100 - }, - { - "name": "济源", - "code": 101181800 - }, - { - "name": "荆州", - "code": 101200800 - }, - { - "name": "荆门", - "code": 101201200 - }, - { - "name": "嘉兴", - "code": 101210300 - }, - { - "name": "金华", - "code": 101210900 - }, - { - "name": "九江", - "code": 101240200 - }, - { - "name": "吉安", - "code": 101240600 - }, - { - "name": "景德镇", - "code": 101240800 - }, - { - "name": "江门", - "code": 101281100 - }, - { - "name": "揭阳", - "code": 101281900 - }, - { - "name": "克拉玛依", - "code": 101130200 - }, - { - "name": "克孜勒苏柯尔克孜自治州", - "code": 101131100 - }, - { - "name": "喀什地区", - "code": 101131200 - }, - { - "name": "可克达拉市", - "code": 101132200 - }, - { - "name": "昆玉市", - "code": 101132300 - }, - { - "name": "开封", - "code": 101180800 - }, - { - "name": "昆明", - "code": 101290100 - }, - { - "name": "辽源", - "code": 101060600 - }, - { - "name": "辽阳", - "code": 101071000 - }, - { - "name": "廊坊", - "code": 101090600 - }, - { - "name": "临汾", - "code": 101100700 - }, - { - "name": "吕梁", - "code": 101101100 - }, - { - "name": "临沂", - "code": 101120900 - }, - { - "name": "聊城", - "code": 101121700 - }, - { - "name": "拉萨", - "code": 101140100 - }, - { - "name": "林芝", - "code": 101140400 - }, - { - "name": "兰州", - "code": 101160100 - }, - { - "name": "陇南", - "code": 101161100 - }, - { - "name": "临夏回族自治州", - "code": 101161300 - }, - { - "name": "洛阳", - "code": 101180900 - }, - { - "name": "漯河", - "code": 101181500 - }, - { - "name": "连云港", - "code": 101191000 - }, - { - "name": "丽水", - "code": 101210800 - }, - { - "name": "六安", - "code": 101221400 - }, - { - "name": "龙岩", - "code": 101230700 - }, - { - "name": "娄底", - "code": 101250800 - }, - { - "name": "六盘水", - "code": 101260600 - }, - { - "name": "泸州", - "code": 101271000 - }, - { - "name": "乐山", - "code": 101271400 - }, - { - "name": "凉山彝族自治州", - "code": 101272000 - }, - { - "name": "临沧", - "code": 101290800 - }, - { - "name": "丽江", - "code": 101290900 - }, - { - "name": "柳州", - "code": 101300300 - }, - { - "name": "来宾", - "code": 101300400 - }, - { - "name": "临高", - "code": 101311300 - }, - { - "name": "乐东黎族自治县", - "code": 101311600 - }, - { - "name": "陵水黎族自治县", - "code": 101311700 - }, - { - "name": "牡丹江", - "code": 101050300 - }, - { - "name": "马鞍山", - "code": 101220500 - }, - { - "name": "绵阳", - "code": 101270400 - }, - { - "name": "眉山", - "code": 101271500 - }, - { - "name": "梅州", - "code": 101280400 - }, - { - "name": "茂名", - "code": 101282000 - }, - { - "name": "那曲", - "code": 101140600 - }, - { - "name": "南阳", - "code": 101180700 - }, - { - "name": "南京", - "code": 101190100 - }, - { - "name": "南通", - "code": 101190500 - }, - { - "name": "宁波", - "code": 101210400 - }, - { - "name": "宁德", - "code": 101230300 - }, - { - "name": "南平", - "code": 101230900 - }, - { - "name": "南昌", - "code": 101240100 - }, - { - "name": "南充", - "code": 101270500 - }, - { - "name": "内江", - "code": 101271200 - }, - { - "name": "怒江傈僳族自治州", - "code": 101291400 - }, - { - "name": "南宁", - "code": 101300100 - }, - { - "name": "盘锦", - "code": 101071300 - }, - { - "name": "平凉", - "code": 101160300 - }, - { - "name": "平顶山", - "code": 101180500 - }, - { - "name": "濮阳", - "code": 101181300 - }, - { - "name": "莆田", - "code": 101230400 - }, - { - "name": "萍乡", - "code": 101240900 - }, - { - "name": "攀枝花", - "code": 101270200 - }, - { - "name": "普洱", - "code": 101290500 - }, - { - "name": "齐齐哈尔", - "code": 101050200 - }, - { - "name": "七台河", - "code": 101050900 - }, - { - "name": "秦皇岛", - "code": 101091100 - }, - { - "name": "青岛", - "code": 101120200 - }, - { - "name": "庆阳", - "code": 101160400 - }, - { - "name": "潜江", - "code": 101201500 - }, - { - "name": "衢州", - "code": 101211000 - }, - { - "name": "泉州", - "code": 101230500 - }, - { - "name": "黔东南苗族侗族自治州", - "code": 101260700 - }, - { - "name": "黔南布依族苗族自治州", - "code": 101260800 - }, - { - "name": "黔西南布依族苗族自治州", - "code": 101260900 - }, - { - "name": "清远", - "code": 101281300 - }, - { - "name": "曲靖", - "code": 101290200 - }, - { - "name": "钦州", - "code": 101301100 - }, - { - "name": "琼海", - "code": 101310600 - }, - { - "name": "琼中黎族苗族自治县", - "code": 101311900 - }, - { - "name": "日照", - "code": 101121500 - }, - { - "name": "日喀则", - "code": 101140200 - }, - { - "name": "上海", - "code": 101020100 - }, - { - "name": "绥化", - "code": 101050500 - }, - { - "name": "双鸭山", - "code": 101051200 - }, - { - "name": "四平", - "code": 101060300 - }, - { - "name": "松原", - "code": 101060700 - }, - { - "name": "沈阳", - "code": 101070100 - }, - { - "name": "石家庄", - "code": 101090100 - }, - { - "name": "朔州", - "code": 101100900 - }, - { - "name": "商洛", - "code": 101110600 - }, - { - "name": "石河子", - "code": 101131600 - }, - { - "name": "双河市", - "code": 101132400 - }, - { - "name": "山南", - "code": 101140500 - }, - { - "name": "石嘴山", - "code": 101170200 - }, - { - "name": "商丘", - "code": 101181000 - }, - { - "name": "三门峡", - "code": 101181700 - }, - { - "name": "苏州", - "code": 101190400 - }, - { - "name": "宿迁", - "code": 101191300 - }, - { - "name": "十堰", - "code": 101201000 - }, - { - "name": "随州", - "code": 101201100 - }, - { - "name": "神农架", - "code": 101201700 - }, - { - "name": "绍兴", - "code": 101210500 - }, - { - "name": "宿州", - "code": 101220700 - }, - { - "name": "三明", - "code": 101230800 - }, - { - "name": "上饶", - "code": 101240300 - }, - { - "name": "邵阳", - "code": 101250900 - }, - { - "name": "遂宁", - "code": 101270700 - }, - { - "name": "韶关", - "code": 101280200 - }, - { - "name": "汕头", - "code": 101280500 - }, - { - "name": "深圳", - "code": 101280600 - }, - { - "name": "汕尾", - "code": 101282100 - }, - { - "name": "三亚", - "code": 101310200 - }, - { - "name": "三沙", - "code": 101310300 - }, - { - "name": "天津", - "code": 101030100 - }, - { - "name": "通化", - "code": 101060400 - }, - { - "name": "铁岭", - "code": 101071100 - }, - { - "name": "通辽", - "code": 101080400 - }, - { - "name": "唐山", - "code": 101090500 - }, - { - "name": "太原", - "code": 101100100 - }, - { - "name": "铜川", - "code": 101111000 - }, - { - "name": "泰安", - "code": 101120800 - }, - { - "name": "吐鲁番", - "code": 101130800 - }, - { - "name": "塔城地区", - "code": 101131400 - }, - { - "name": "图木舒克", - "code": 101131800 - }, - { - "name": "铁门关", - "code": 101132000 - }, - { - "name": "天水", - "code": 101160900 - }, - { - "name": "泰州", - "code": 101191200 - }, - { - "name": "天门", - "code": 101201600 - }, - { - "name": "台州", - "code": 101210600 - }, - { - "name": "铜陵", - "code": 101221200 - }, - { - "name": "铜仁", - "code": 101260400 - }, - { - "name": "屯昌", - "code": 101311100 - }, - { - "name": "台湾", - "code": 101341100 - }, - { - "name": "乌海", - "code": 101080300 - }, - { - "name": "乌兰察布", - "code": 101080900 - }, - { - "name": "渭南", - "code": 101110500 - }, - { - "name": "潍坊", - "code": 101120600 - }, - { - "name": "威海", - "code": 101121300 - }, - { - "name": "乌鲁木齐", - "code": 101130100 - }, - { - "name": "五家渠", - "code": 101131900 - }, - { - "name": "武威", - "code": 101160500 - }, - { - "name": "吴忠", - "code": 101170300 - }, - { - "name": "无锡", - "code": 101190200 - }, - { - "name": "武汉", - "code": 101200100 - }, - { - "name": "温州", - "code": 101210700 - }, - { - "name": "芜湖", - "code": 101220300 - }, - { - "name": "文山壮族苗族自治州", - "code": 101291100 - }, - { - "name": "梧州", - "code": 101300600 - }, - { - "name": "五指山", - "code": 101310500 - }, - { - "name": "文昌", - "code": 101310700 - }, - { - "name": "万宁", - "code": 101310800 - }, - { - "name": "锡林郭勒盟", - "code": 101081000 - }, - { - "name": "兴安盟", - "code": 101081100 - }, - { - "name": "邢台", - "code": 101090900 - }, - { - "name": "忻州", - "code": 101101000 - }, - { - "name": "西安", - "code": 101110100 - }, - { - "name": "咸阳", - "code": 101110200 - }, - { - "name": "新星市", - "code": 101132500 - }, - { - "name": "西宁", - "code": 101150100 - }, - { - "name": "新乡", - "code": 101180300 - }, - { - "name": "许昌", - "code": 101180400 - }, - { - "name": "信阳", - "code": 101180600 - }, - { - "name": "徐州", - "code": 101190800 - }, - { - "name": "襄阳", - "code": 101200200 - }, - { - "name": "孝感", - "code": 101200400 - }, - { - "name": "咸宁", - "code": 101200700 - }, - { - "name": "仙桃", - "code": 101201400 - }, - { - "name": "宣城", - "code": 101221300 - }, - { - "name": "厦门", - "code": 101230200 - }, - { - "name": "新余", - "code": 101241000 - }, - { - "name": "湘潭", - "code": 101250200 - }, - { - "name": "湘西土家族苗族自治州", - "code": 101251400 - }, - { - "name": "西双版纳傣族自治州", - "code": 101291000 - }, - { - "name": "香港", - "code": 101320300 - }, - { - "name": "伊春", - "code": 101050700 - }, - { - "name": "延边朝鲜族自治州", - "code": 101060900 - }, - { - "name": "营口", - "code": 101070800 - }, - { - "name": "阳泉", - "code": 101100300 - }, - { - "name": "运城", - "code": 101100800 - }, - { - "name": "延安", - "code": 101110300 - }, - { - "name": "榆林", - "code": 101110400 - }, - { - "name": "烟台", - "code": 101120500 - }, - { - "name": "伊犁哈萨克自治州", - "code": 101130600 - }, - { - "name": "玉树藏族自治州", - "code": 101150700 - }, - { - "name": "银川", - "code": 101170100 - }, - { - "name": "扬州", - "code": 101190600 - }, - { - "name": "盐城", - "code": 101190700 - }, - { - "name": "宜昌", - "code": 101200900 - }, - { - "name": "宜春", - "code": 101240500 - }, - { - "name": "鹰潭", - "code": 101241100 - }, - { - "name": "益阳", - "code": 101250700 - }, - { - "name": "岳阳", - "code": 101251000 - }, - { - "name": "永州", - "code": 101251300 - }, - { - "name": "宜宾", - "code": 101271100 - }, - { - "name": "雅安", - "code": 101271600 - }, - { - "name": "云浮", - "code": 101281400 - }, - { - "name": "阳江", - "code": 101281800 - }, - { - "name": "玉溪", - "code": 101290400 - }, - { - "name": "玉林", - "code": 101300900 - }, - { - "name": "张家口", - "code": 101090300 - }, - { - "name": "淄博", - "code": 101120300 - }, - { - "name": "枣庄", - "code": 101121400 - }, - { - "name": "张掖", - "code": 101160700 - }, - { - "name": "中卫", - "code": 101170500 - }, - { - "name": "郑州", - "code": 101180100 - }, - { - "name": "周口", - "code": 101181400 - }, - { - "name": "驻马店", - "code": 101181600 - }, - { - "name": "镇江", - "code": 101190300 - }, - { - "name": "舟山", - "code": 101211100 - }, - { - "name": "漳州", - "code": 101230600 - }, - { - "name": "株洲", - "code": 101250300 - }, - { - "name": "张家界", - "code": 101251100 - }, - { - "name": "遵义", - "code": 101260200 - }, - { - "name": "自贡", - "code": 101270300 - }, - { - "name": "资阳", - "code": 101271300 - }, - { - "name": "珠海", - "code": 101280700 - }, - { - "name": "肇庆", - "code": 101280900 - }, - { - "name": "湛江", - "code": 101281000 - }, - { - "name": "中山", - "code": 101281700 - }, - { - "name": "昭通", - "code": 101290700 - } - ], - "industry": [ - { - "name": "互联网/AI", - "code": 100000 - }, - { - "name": "互联网", - "code": 100020 - }, - { - "name": "电子商务", - "code": 100001 - }, - { - "name": "计算机软件", - "code": 100021 - }, - { - "name": "生活服务(O2O)", - "code": 100007 - }, - { - "name": "企业服务", - "code": 100015 - }, - { - "name": "医疗健康", - "code": 100006 - }, - { - "name": "游戏", - "code": 100002 - }, - { - "name": "社交网络与媒体", - "code": 100003 - }, - { - "name": "人工智能", - "code": 100028 - }, - { - "name": "云计算", - "code": 100029 - }, - { - "name": "在线教育", - "code": 100012 - }, - { - "name": "计算机服务", - "code": 100023 - }, - { - "name": "大数据", - "code": 100005 - }, - { - "name": "广告营销", - "code": 100004 - }, - { - "name": "物联网", - "code": 100030 - }, - { - "name": "新零售", - "code": 100017 - }, - { - "name": "信息安全", - "code": 100016 - }, - { - "name": "电子/通信/半导体", - "code": 101400 - }, - { - "name": "半导体/芯片", - "code": 101405 - }, - { - "name": "电子/硬件开发", - "code": 101406 - }, - { - "name": "通信/网络设备", - "code": 101402 - }, - { - "name": "智能硬件/消费电子", - "code": 101401 - }, - { - "name": "运营商/增值服务", - "code": 101403 - }, - { - "name": "计算机硬件", - "code": 101404 - }, - { - "name": "电子/半导体/集成电路", - "code": 101407 - }, - { - "name": "服务业", - "code": 101100 - }, - { - "name": "餐饮", - "code": 101101 - }, - { - "name": "美容", - "code": 101111 - }, - { - "name": "美发", - "code": 101112 - }, - { - "name": "酒店/民宿", - "code": 101102 - }, - { - "name": "休闲/娱乐", - "code": 101107 - }, - { - "name": "运动/健身", - "code": 101113 - }, - { - "name": "保健/养生", - "code": 101114 - }, - { - "name": "家政服务", - "code": 101109 - }, - { - "name": "旅游/景区", - "code": 101103 - }, - { - "name": "婚庆/摄影", - "code": 101105 - }, - { - "name": "宠物服务", - "code": 101110 - }, - { - "name": "回收/维修", - "code": 101108 - }, - { - "name": "美容/美发", - "code": 101104 - }, - { - "name": "其他生活服务", - "code": 101106 - }, - { - "name": "消费品/批发/零售", - "code": 101000 - }, - { - "name": "批发/零售", - "code": 101011 - }, - { - "name": "进出口贸易", - "code": 101012 - }, - { - "name": "食品/饮料/烟酒", - "code": 101001 - }, - { - "name": "服装/纺织", - "code": 101003 - }, - { - "name": "家具/家居", - "code": 101009 - }, - { - "name": "家用电器", - "code": 101010 - }, - { - "name": "日化", - "code": 101002 - }, - { - "name": "珠宝/首饰", - "code": 101006 - }, - { - "name": "家具/家电/家居", - "code": 101004 - }, - { - "name": "其他消费品", - "code": 101013 - }, - { - "name": "房地产/建筑", - "code": 100700 - }, - { - "name": "装修装饰", - "code": 100704 - }, - { - "name": "房屋建筑工程", - "code": 100708 - }, - { - "name": "土木工程", - "code": 100709 - }, - { - "name": "机电工程", - "code": 100710 - }, - { - "name": "物业管理", - "code": 100707 - }, - { - "name": "房地产中介/租赁", - "code": 100706 - }, - { - "name": "建筑材料", - "code": 100705 - }, - { - "name": "房地产开发经营", - "code": 100701 - }, - { - "name": "建筑设计", - "code": 100703 - }, - { - "name": "建筑工程咨询服务", - "code": 100711 - }, - { - "name": "土地与公共设施管理", - "code": 100712 - }, - { - "name": "工程施工", - "code": 100702 - }, - { - "name": "教育培训", - "code": 100300 - }, - { - "name": "培训/辅导机构", - "code": 100303 - }, - { - "name": "职业培训", - "code": 100305 - }, - { - "name": "学前教育", - "code": 100301 - }, - { - "name": "学校/学历教育", - "code": 100302 - }, - { - "name": "学术/科研", - "code": 100304 - }, - { - "name": "广告/传媒/文化/体育", - "code": 100100 - }, - { - "name": "文化艺术/娱乐", - "code": 100104 - }, - { - "name": "体育", - "code": 100105 - }, - { - "name": "广告/公关/会展", - "code": 100101 - }, - { - "name": "广播/影视", - "code": 100103 - }, - { - "name": "新闻/出版", - "code": 100102 - }, - { - "name": "制造业", - "code": 100900 - }, - { - "name": "通用设备", - "code": 100906 - }, - { - "name": "专用设备", - "code": 100907 - }, - { - "name": "电气机械/器材", - "code": 100908 - }, - { - "name": "金属制品", - "code": 100909 - }, - { - "name": "非金属矿物制品", - "code": 100910 - }, - { - "name": "橡胶/塑料制品", - "code": 100911 - }, - { - "name": "化学原料/化学制品", - "code": 100912 - }, - { - "name": "仪器仪表", - "code": 100913 - }, - { - "name": "自动化设备", - "code": 100914 - }, - { - "name": "印刷/包装/造纸", - "code": 100904 - }, - { - "name": "铁路/船舶/航空/航天制造", - "code": 100905 - }, - { - "name": "计算机/通信/其他电子设备", - "code": 100915 - }, - { - "name": "新材料", - "code": 100916 - }, - { - "name": "机械设备/机电/重工", - "code": 100901 - }, - { - "name": "仪器仪表/工业自动化", - "code": 100902 - }, - { - "name": "原材料及加工/模具", - "code": 100903 - }, - { - "name": "其他制造业", - "code": 100917 - }, - { - "name": "专业服务", - "code": 100600 - }, - { - "name": "咨询", - "code": 100601 - }, - { - "name": "财务/审计/税务", - "code": 100605 - }, - { - "name": "人力资源服务", - "code": 100604 - }, - { - "name": "法律", - "code": 100602 - }, - { - "name": "检测/认证/知识产权", - "code": 100609 - }, - { - "name": "翻译", - "code": 100603 - }, - { - "name": "其他专业服务", - "code": 100608 - }, - { - "name": "制药/医疗", - "code": 100400 - }, - { - "name": "医疗服务", - "code": 100402 - }, - { - "name": "医美服务", - "code": 100404 - }, - { - "name": "医疗器械", - "code": 100403 - }, - { - "name": "IVD", - "code": 100405 - }, - { - "name": "生物/制药", - "code": 100401 - }, - { - "name": "医药批发零售", - "code": 100406 - }, - { - "name": "医疗研发外包", - "code": 100407 - }, - { - "name": "汽车", - "code": 100800 - }, - { - "name": "新能源汽车", - "code": 100804 - }, - { - "name": "汽车智能网联", - "code": 100805 - }, - { - "name": "汽车经销商", - "code": 100806 - }, - { - "name": "汽车后市场", - "code": 100807 - }, - { - "name": "汽车研发/制造", - "code": 100801 - }, - { - "name": "汽车零部件", - "code": 100802 - }, - { - "name": "摩托车/自行车制造", - "code": 100808 - }, - { - "name": "4S店/后市场", - "code": 100803 - }, - { - "name": "交通运输/物流", - "code": 100500 - }, - { - "name": "即时配送", - "code": 100505 - }, - { - "name": "快递", - "code": 100506 - }, - { - "name": "公路物流", - "code": 100507 - }, - { - "name": "同城货运", - "code": 100508 - }, - { - "name": "跨境物流", - "code": 100509 - }, - { - "name": "装卸搬运和仓储业", - "code": 100510 - }, - { - "name": "客运服务", - "code": 100511 - }, - { - "name": "港口/铁路/公路/机场", - "code": 100512 - }, - { - "name": "交通/运输", - "code": 100501 - }, - { - "name": "物流/仓储", - "code": 100502 - }, - { - "name": "能源/化工/环保", - "code": 101200 - }, - { - "name": "光伏", - "code": 101208 - }, - { - "name": "储能", - "code": 101209 - }, - { - "name": "动力电池", - "code": 101210 - }, - { - "name": "风电", - "code": 101211 - }, - { - "name": "其他新能源", - "code": 101212 - }, - { - "name": "环保", - "code": 101207 - }, - { - "name": "化工", - "code": 101202 - }, - { - "name": "电力/热力/燃气/水利", - "code": 101205 - }, - { - "name": "石油/石化", - "code": 101201 - }, - { - "name": "矿产/地质", - "code": 101203 - }, - { - "name": "采掘/冶炼", - "code": 101204 - }, - { - "name": "新能源", - "code": 101206 - }, - { - "name": "金融", - "code": 100200 - }, - { - "name": "互联网金融", - "code": 100206 - }, - { - "name": "银行", - "code": 100201 - }, - { - "name": "投资/融资", - "code": 100207 - }, - { - "name": "证券/期货", - "code": 100203 - }, - { - "name": "基金", - "code": 100204 - }, - { - "name": "保险", - "code": 100202 - }, - { - "name": "租赁/拍卖/典当/担保", - "code": 100208 - }, - { - "name": "信托", - "code": 100205 - }, - { - "name": "财富管理", - "code": 100209 - }, - { - "name": "其他金融业", - "code": 100210 - }, - { - "name": "政府/非盈利机构/其他", - "code": 101300 - }, - { - "name": "农/林/牧/渔", - "code": 101303 - }, - { - "name": "非盈利机构", - "code": 101302 - }, - { - "name": "政府/公共事业", - "code": 101301 - }, - { - "name": "其他行业", - "code": 101304 - } - ] -} \ No newline at end of file diff --git a/src/main/java/boss/data.json b/src/main/java/boss/data.json deleted file mode 100644 index d443e6c4..00000000 --- a/src/main/java/boss/data.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "blackCompanies": [ - "境开科技", - "法本信息", - "正佳科技", - "广州市千兔科技", - "智和瑞成", - "羁绊科技", - "阿里巴巴智能信息事业群", - "海南钦诚", - "头文科技", - "法本", - "三七互娱" - ], - "blackJobs": [ - "视觉", - "设计", - "外包", - "现场", - "驻场" - ], - "blackRecruiters": [ - "猎头" - ] -} \ No newline at end of file diff --git a/src/main/java/getjobs/GetJobsApplication.java b/src/main/java/getjobs/GetJobsApplication.java new file mode 100644 index 00000000..6a3a4697 --- /dev/null +++ b/src/main/java/getjobs/GetJobsApplication.java @@ -0,0 +1,17 @@ +package getjobs; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Slf4j +@EnableScheduling +@SpringBootApplication +@EntityScan("getjobs.repository") +public class GetJobsApplication { + public static void main(String[] args) { + SpringApplication.run(GetJobsApplication.class, args); + } +} diff --git a/src/main/java/getjobs/common/dto/ConfigDTO.java b/src/main/java/getjobs/common/dto/ConfigDTO.java new file mode 100644 index 00000000..5b955b6e --- /dev/null +++ b/src/main/java/getjobs/common/dto/ConfigDTO.java @@ -0,0 +1,250 @@ +package getjobs.common.dto; + +import getjobs.repository.entity.ConfigEntity; +import getjobs.repository.ConfigRepository; +import getjobs.utils.SpringContextUtil; +import getjobs.common.enums.RecruitmentPlatformEnum; +import lombok.Data; +import lombok.SneakyThrows; + +import java.util.*; +import java.util.stream.Collectors; + +@Data +public class ConfigDTO { + + // 文本与布尔 + private String sayHi; + + // 逗号分隔原始输入(来自表单/配置) + private String keywords; + private String cityCode; + private String industry; + private String publishTime; + + // 单/多选原始字符串(来自表单/配置) + private String experience; + private String jobType; + private String salary; + private String degree; + private String scale; + private String stage; + private String companyType; // 新增:公司类型字段,用于51job的companyType参数 + private String expectedPosition; + + // 可选:自定义城市编码映射 + private Map customCityCode; + + // 功能开关与AI + private Boolean enableAIJobMatchDetection; + private Boolean enableAIGreeting; + private Boolean filterDeadHR; + private Boolean sendImgResume; + private Boolean keyFilter; + private Boolean recommendJobs; + private Boolean checkStateOwned; + + // 简历配置 + private String resumeImagePath; + private String resumeContent; + + // 期望薪资(min/max) + private Integer minSalary; + private Integer maxSalary; + + // 系统 + private String waitTime; + + // 平台类型 + private String platformType; + + // 其他列表型配置 + private List deadStatus; + + // ------------ 单例加载 ------------ + private static volatile ConfigDTO instance; + + private ConfigDTO() { + } + + public static ConfigDTO getInstance() { + if (instance == null) { + synchronized (ConfigDTO.class) { + if (instance == null) { + instance = init(); + } + } + } + return instance; + } + + @SneakyThrows + private static ConfigDTO init() { + // 从数据库查询ConfigEntity + ConfigRepository configRepository = SpringContextUtil.getBean(ConfigRepository.class); + Optional configOpt = configRepository.getDefaultConfig(); + + if (configOpt.isEmpty()) { + // 如果数据库中没有配置,返回默认配置 + return new ConfigDTO(); + } + + // 转换为BossConfigDTO + return convertFromEntity(configOpt.get()); + } + + @SneakyThrows + public static synchronized void reload() { + instance = init(); + } + + /** + * 将ConfigEntity转换为BossConfigDTO + */ + private static ConfigDTO convertFromEntity(ConfigEntity entity) { + ConfigDTO dto = new ConfigDTO(); + + // 基础字段映射 + dto.setSayHi(entity.getSayHi()); + dto.setEnableAIJobMatchDetection(entity.getEnableAIJobMatchDetection()); + dto.setEnableAIGreeting(entity.getEnableAIGreeting()); + dto.setFilterDeadHR(entity.getFilterDeadHR()); + dto.setSendImgResume(entity.getSendImgResume()); + dto.setKeyFilter(entity.getKeyFilter()); + dto.setRecommendJobs(entity.getRecommendJobs()); + dto.setCheckStateOwned(entity.getCheckStateOwned()); + dto.setResumeImagePath(entity.getResumeImagePath()); + dto.setResumeContent(entity.getResumeContent()); + dto.setWaitTime(entity.getWaitTime()); + dto.setPlatformType(entity.getPlatformType()); + + // 列表字段转换为逗号分隔的字符串 + if (entity.getKeywords() != null) { + dto.setKeywords(String.join(",", entity.getKeywords())); + } + if (entity.getCityCode() != null) { + dto.setCityCode(String.join(",", entity.getCityCode())); + } + if (entity.getIndustry() != null) { + dto.setIndustry(String.join(",", entity.getIndustry())); + } + if (entity.getExperience() != null) { + dto.setExperience(String.join(",", entity.getExperience())); + } + if (entity.getDegree() != null) { + dto.setDegree(String.join(",", entity.getDegree())); + } + if (entity.getScale() != null) { + dto.setScale(String.join(",", entity.getScale())); + } + if (entity.getStage() != null) { + dto.setStage(String.join(",", entity.getStage())); + } + dto.setPublishTime(entity.getPublishTime()); + // 注意:需要在ConfigEntity中添加companyType字段 + // if (entity.getCompanyType() != null) { + // dto.setCompanyType(String.join(",", entity.getCompanyType())); + // } + if (entity.getDeadStatus() != null) { + dto.setDeadStatus(entity.getDeadStatus()); + } + + // 期望薪资处理 + if (entity.getExpectedSalary() != null && entity.getExpectedSalary().size() >= 2) { + dto.setMinSalary(entity.getExpectedSalary().get(0)); + dto.setMaxSalary(entity.getExpectedSalary().get(1)); + } + + // 其他字段 + dto.setCustomCityCode(entity.getCustomCityCode()); + dto.setJobType(entity.getJobType()); + dto.setSalary(entity.getSalary()); + dto.setExpectedPosition(entity.getExpectedPosition()); + + return dto; + } + + // ------------ 包装/转换访问器(供业务方使用)------------ + + public List getKeywordsList() { + return splitToList(keywords); + } + + public List getCityCodeCodes() { + List cities = splitToList(cityCode); + if (cities == null) + return Collections.emptyList(); + return cities.stream() + .map(city -> { + if (customCityCode != null && customCityCode.containsKey(city)) { + return customCityCode.get(city); + } + return city; + }) + .collect(Collectors.toList()); + } + + public List getIndustryCodes() { + return mapToCodes(splitToList(industry), v -> v); + } + + public List getExperienceCodes() { + return mapToCodes(splitToList(experience), v -> v); + } + + + public List getDegreeCodes() { + return mapToCodes(splitToList(degree), v -> degree); + } + + public List getScaleCodes() { + return mapToCodes(splitToList(scale), v -> scale); + } + + public List getStageCodes() { + return mapToCodes(splitToList(stage), v -> stage); + } + + public List getCompanyTypeCodes() { + return mapToCodes(splitToList(companyType), v -> companyType); + } + + public List getExpectedSalary() { + List list = new ArrayList<>(); + if (minSalary != null) + list.add(minSalary); + if (maxSalary != null) + list.add(maxSalary); + return list; + } + + /** + * 获取平台类型对应的枚举 + * @return 招聘平台枚举,如果platformType为空或无效则返回null + */ + public RecruitmentPlatformEnum getPlatformTypeEnum() { + if (platformType == null || platformType.trim().isEmpty()) { + return null; + } + return RecruitmentPlatformEnum.getByCode(platformType.trim()); + } + + // ------------ 工具方法 ------------ + private List splitToList(String text) { + if (text == null || text.trim().isEmpty()) { + return new ArrayList<>(); + } + // 支持中文/英文逗号、竖线、空格分隔 + String[] arr = text.split("[,,|\\s]+"); + return Arrays.stream(arr) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + } + + private List mapToCodes(List src, java.util.function.Function mapper) { + if (src == null) + return new ArrayList<>(); + return src.stream().map(mapper).collect(Collectors.toList()); + } +} diff --git a/src/main/java/getjobs/common/enums/JobStatusEnum.java b/src/main/java/getjobs/common/enums/JobStatusEnum.java new file mode 100644 index 00000000..a6e54012 --- /dev/null +++ b/src/main/java/getjobs/common/enums/JobStatusEnum.java @@ -0,0 +1,23 @@ +package getjobs.common.enums; + +import lombok.Getter; + +/** + * 职位状态枚举 + * 1 待处理 2 已过滤 3 投递成功 4 投递失败 + */ +@Getter +public enum JobStatusEnum { + PENDING(0, "待处理"), + FILTERED(2, "已过滤"), + DELIVERED_SUCCESS(3, "投递成功"), + DELIVERED_FAILED(4, "投递失败"); + + private final int code; + private final String desc; + + JobStatusEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } +} diff --git a/src/main/java/getjobs/common/enums/RecruitmentPlatformEnum.java b/src/main/java/getjobs/common/enums/RecruitmentPlatformEnum.java new file mode 100644 index 00000000..f615d68d --- /dev/null +++ b/src/main/java/getjobs/common/enums/RecruitmentPlatformEnum.java @@ -0,0 +1,69 @@ +package getjobs.common.enums; + +import lombok.Getter; + +/** + * 招聘平台枚举 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Getter +public enum RecruitmentPlatformEnum { + BOSS_ZHIPIN("Boss直聘", "boss", "https://www.zhipin.com/"), + ZHILIAN_ZHAOPIN("智联招聘", "zhilian", "https://www.zhaopin.com/"), + JOB_51("51job", "51job", "https://51job.com/"), + LIEPIN("猎聘", "liepin", "https://www.liepin.com/"); + + /** + * 平台名称 + */ + private final String platformName; + + /** + * 平台代码 + */ + private final String platformCode; + + /** + * 平台主页URL + */ + private final String homeUrl; + + RecruitmentPlatformEnum(String platformName, String platformCode, String homeUrl) { + this.platformName = platformName; + this.platformCode = platformCode; + this.homeUrl = homeUrl; + } + + /** + * 根据平台代码获取枚举 + * + * @param platformCode 平台代码 + * @return 招聘平台枚举 + */ + public static RecruitmentPlatformEnum getByCode(String platformCode) { + for (RecruitmentPlatformEnum platform : values()) { + if (platform.getPlatformCode().equals(platformCode)) { + return platform; + } + } + return null; + } + + /** + * 根据平台名称获取枚举 + * + * @param platformName 平台名称 + * @return 招聘平台枚举 + */ + public static RecruitmentPlatformEnum getByName(String platformName) { + for (RecruitmentPlatformEnum platform : values()) { + if (platform.getPlatformName().equals(platformName)) { + return platform; + } + } + return null; + } +} diff --git a/src/main/java/getjobs/common/service/PlaywrightService.java b/src/main/java/getjobs/common/service/PlaywrightService.java new file mode 100644 index 00000000..d98c90e5 --- /dev/null +++ b/src/main/java/getjobs/common/service/PlaywrightService.java @@ -0,0 +1,141 @@ +package getjobs.common.service; + +import com.microsoft.playwright.*; +import com.microsoft.playwright.options.Cookie; +import getjobs.common.enums.RecruitmentPlatformEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Playwright服务,统一管理Playwright实例、浏览器、上下文和页面。 + * 为每个招聘平台提供独立的BrowserContext和Page。 + */ +@Slf4j +@Service +public class PlaywrightService { + + private Playwright playwright; + private Browser browser; + + private BrowserContext context; + private final Map pageMap = new ConcurrentHashMap<>(); + + private static final int DEFAULT_TIMEOUT = 30000; + + private static final String[] USER_AGENTS = { + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36" + }; + + @PostConstruct + public void init() { + try { + log.info("Initializing Playwright service..."); + playwright = Playwright.create(); + + browser = playwright.chromium().launch(new BrowserType.LaunchOptions() + .setHeadless(false) + .setSlowMo(50) + .setArgs(List.of( + "--disable-blink-features=AutomationControlled", + "--disable-web-security", + "--disable-features=VizDisplayCompositor", + "--disable-dev-shm-usage", + "--no-sandbox", + "--disable-extensions", + "--disable-plugins", + "--disable-default-apps", + "--disable-background-timer-throttling", + "--disable-renderer-backgrounding", + "--disable-backgrounding-occluded-windows", + "--disable-ipc-flooding-protection" + ))); + + context = createNewContext(); + for (RecruitmentPlatformEnum platform : RecruitmentPlatformEnum.values()) { + Page page = createNewPage(context); + page.navigate(platform.getHomeUrl()); + pageMap.put(platform, page); + log.info("Initialized page for platform: {}, url: {}", platform.getPlatformName(), platform.getHomeUrl()); + } + + log.info("Playwright service initialized successfully."); + } catch (Exception e) { + log.error("Failed to initialize Playwright service", e); + // Ensure cleanup is called on initialization failure + close(); + throw new RuntimeException("Failed to initialize Playwright service", e); + } + } + + private BrowserContext createNewContext() { + String randomUserAgent = getRandomUserAgent(); + BrowserContext context = browser.newContext(new Browser.NewContextOptions() + .setUserAgent(randomUserAgent) + .setJavaScriptEnabled(true) + .setBypassCSP(true) + .setPermissions(List.of("geolocation", "notifications")) + .setLocale("zh-CN") + .setTimezoneId("Asia/Shanghai")); + return context; + } + + private Page createNewPage(BrowserContext context) { + Page page = context.newPage(); + page.setDefaultTimeout(DEFAULT_TIMEOUT); + page.onConsoleMessage(message -> { + if ("error".equals(message.type())) { + log.error("Browser console error: {}", message.text()); + } + }); + return page; + } + + @PreDestroy + public void close() { + log.info("Closing Playwright service..."); + pageMap.values().forEach(Page::close); + + if (context != null) { + context.close(); + } + + if (browser != null) { + browser.close(); + } + if (playwright != null) { + playwright.close(); + } + log.info("Playwright service closed."); + } + + public Page getPage(RecruitmentPlatformEnum platform) { + return pageMap.get(platform); + } + + public BrowserContext getContext(RecruitmentPlatformEnum platform) { + return context; + } + + public void addCookies(RecruitmentPlatformEnum platform, List cookies) { + if (context != null) { + context.addCookies(cookies); + log.info("Added cookies for platform: {}", platform.getPlatformName()); + } else { + log.warn("BrowserContext not initialized"); + } + } + + private String getRandomUserAgent() { + Random random = new Random(); + return USER_AGENTS[random.nextInt(USER_AGENTS.length)]; + } +} diff --git a/src/main/java/getjobs/config/ai/DeepseekGptConfig.java b/src/main/java/getjobs/config/ai/DeepseekGptConfig.java new file mode 100644 index 00000000..344478f4 --- /dev/null +++ b/src/main/java/getjobs/config/ai/DeepseekGptConfig.java @@ -0,0 +1,73 @@ +package getjobs.config.ai; + +import getjobs.modules.ai.common.enums.AiPlatform; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.StreamingChatModel; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * GPT配置类 + */ +@Configuration +public class DeepseekGptConfig { + + @Value("${spring.ai.deepseek.api-key}") + private String apiKey; + + @Value("${spring.ai.deepseek.base-url:https://api.deepseek.com}") + private String baseUrl; + + @Value("${spring.ai.deepseek.chat.options.model}") + private String model; + + @Value("${spring.ai.deepseek.chat.options.temperature:0.7}") + private Double temperature; + + @Value("${spring.ai.deepseek.chat.options.max-tokens:2000}") + private Integer maxTokens; + + /** + * 配置OpenAiApi + */ + @Bean + public OpenAiApi deepseekAiApi() { + return OpenAiApi.builder() + .apiKey(apiKey) + .baseUrl(baseUrl) + .build(); + } + + /** + * 配置ChatModel + */ + @Bean(AiPlatform.DEEPSEEK_BEAN_NAME) + public ChatModel deepseekChatModel(@Qualifier("deepseekAiApi") OpenAiApi deepseekAiApi) { + OpenAiChatOptions options = OpenAiChatOptions.builder() + .model(model) + .temperature(temperature) + .maxTokens(maxTokens) + .build(); + + return new OpenAiChatModel(deepseekAiApi, options); + } + + /** + * 配置StreamingChatModel + */ + @Bean("deepseekStreamingChatModel") + public StreamingChatModel deepseekStreamingChatModel(@Qualifier("deepseekAiApi") OpenAiApi deepseekAiApi) { + OpenAiChatOptions options = OpenAiChatOptions.builder() + .model(model) + .temperature(temperature) + .maxTokens(maxTokens) + .build(); + + return new OpenAiChatModel(deepseekAiApi, options); + } +} diff --git a/src/main/java/getjobs/config/ai/GptConfig.java b/src/main/java/getjobs/config/ai/GptConfig.java new file mode 100644 index 00000000..ab616486 --- /dev/null +++ b/src/main/java/getjobs/config/ai/GptConfig.java @@ -0,0 +1,177 @@ +package getjobs.config.ai; + +import getjobs.modules.ai.common.enums.AiPlatform; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.StreamingChatModel; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.netty.http.client.HttpClient; +import reactor.netty.transport.ProxyProvider; + +import java.time.Duration; + +/** + * GPT配置类 + */ +@Slf4j +@Configuration +public class GptConfig { + + @Value("${spring.ai.openai.api-key}") + private String apiKey; + + @Value("${spring.ai.openai.base-url:https://api.openai.com}") + private String baseUrl; + + @Value("${spring.ai.openai.chat.options.model}") + private String model; + + @Value("${spring.ai.openai.chat.options.temperature:0.7}") + private Double temperature; + + @Value("${spring.ai.openai.chat.options.max-tokens:2000}") + private Integer maxTokens; + + @Value("${proxy.host:}") + private String proxyHost; + + @Value("${proxy.port:0}") + private int proxyPort; + + @Value("${spring.ai.openai.connect-timeout:60000}") + private int connectTimeout; + + @Value("${spring.ai.openai.response-timeout:120000}") + private int responseTimeout; + + @PostConstruct + public void init() { + log.info("OpenAI配置信息:"); + log.info("API基础URL: {}", baseUrl); + log.info("默认模型: {}", model); + log.info("连接超时: {}ms, 响应超时: {}ms", connectTimeout, responseTimeout); + + if (proxyHost != null && !proxyHost.isEmpty() && proxyPort > 0) { + log.info("代理已配置 - 主机: {}, 端口: {}", proxyHost, proxyPort); + + // 调用非流式响应默认使用的实现是 jdk.internal.net.http.HttpClientImpl + // 设置全局HTTP代理 + if (isProxyAvailable(proxyHost, proxyPort)) { + log.info("全局HTTP代理 http.proxyHost: {}", proxyHost); + log.info("全局HTTP代理 http.proxyPort: {}", String.valueOf(proxyPort)); + log.info("全局HTTP代理 https.proxyHost: {}", proxyHost); + log.info("全局HTTP代理 https.proxyPort: {}", String.valueOf(proxyPort)); + log.info("已设置全局HTTP代理"); + } else { + log.warn("无法连接到代理 - 主机: {}, 端口: {},全局HTTP代理设置已跳过", proxyHost, proxyPort); + } + } else { + log.warn("未配置代理或代理配置不完整。如果您在中国大陆,可能需要配置代理才能访问OpenAI API。"); + log.warn("当前代理配置: 主机: {}, 端口: {}", proxyHost, proxyPort); + } + } + + /** + * 配置OpenAiApi + */ + @Bean + public OpenAiApi openAiApi() { + WebClient.Builder webClientBuilder = WebClient.builder(); + + if (proxyHost != null && !proxyHost.isEmpty() && proxyPort > 0) { + if (isProxyAvailable(proxyHost, proxyPort)) { + log.info("使用代理创建WebClient - 主机: {}, 端口: {}", proxyHost, proxyPort); + HttpClient httpClient = HttpClient.create() + .proxy(proxy -> proxy + .type(ProxyProvider.Proxy.HTTP) + .host(proxyHost) + .port(proxyPort)) + .responseTimeout(Duration.ofMillis(responseTimeout)) + .option(io.netty.channel.ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout) + .option(io.netty.channel.ChannelOption.SO_KEEPALIVE, true); + + webClientBuilder = webClientBuilder.clone() + .clientConnector(new ReactorClientHttpConnector(httpClient)); + } else { + log.warn("无法连接到代理 - 主机: {}, 端口: {},将直接连接OpenAI API", proxyHost, proxyPort); + } + } else { + log.warn("创建WebClient时未使用代理,这可能导致在中国大陆无法连接到OpenAI API"); + } + + // 即使不使用代理,也添加超时配置 + HttpClient httpClient = HttpClient.create() + .responseTimeout(Duration.ofMillis(responseTimeout)) + .option(io.netty.channel.ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout) + .option(io.netty.channel.ChannelOption.SO_KEEPALIVE, true); + + webClientBuilder = webClientBuilder.clone() + .clientConnector(new ReactorClientHttpConnector(httpClient)); + + log.info("创建OpenAiApi,基础URL: {}", baseUrl); + return OpenAiApi.builder() + .apiKey(apiKey) + .baseUrl(baseUrl) + .webClientBuilder(webClientBuilder) + .build(); + } + + /** + * 测试代理是否可用 + * @param host 代理主机 + * @param port 代理端口 + * @return 代理是否可用 + */ + private boolean isProxyAvailable(String host, int port) { + try (java.net.Socket socket = new java.net.Socket()) { + socket.connect(new java.net.InetSocketAddress(host, port), 3000); // 3秒超时 + return true; + } catch (Exception e) { + log.error("代理连接测试失败: {}", e.getMessage()); + return false; + } + } + + /** + * 配置ChatModel + * + */ + @Bean(AiPlatform.OPENAI_BEAN_NAME) + public ChatModel openAiChatModel(OpenAiApi openAiApi) { + OpenAiChatOptions options = OpenAiChatOptions.builder() + .model(model) + .temperature(temperature) + .maxTokens(maxTokens) + .build(); + + log.info("创建ChatModel,模型: {}, 温度: {}, 最大Token: {}", model, temperature, maxTokens); + ChatModel chatModel = new OpenAiChatModel(openAiApi, options); + return chatModel; + } + + /** + * 配置StreamingChatModel + */ + @Bean + public StreamingChatModel openAiStreamingChatModel(OpenAiApi openAiApi) { + OpenAiChatOptions options = OpenAiChatOptions.builder() + .model(model) + .temperature(temperature) + .maxTokens(maxTokens) + .build(); + + log.info("创建StreamingChatModel,模型: {}, 温度: {}, 最大Token: {}", model, temperature, maxTokens); + return new OpenAiChatModel(openAiApi, options); + } +} diff --git a/src/main/java/getjobs/config/webclient/WebClientConfig.java b/src/main/java/getjobs/config/webclient/WebClientConfig.java new file mode 100644 index 00000000..3937886d --- /dev/null +++ b/src/main/java/getjobs/config/webclient/WebClientConfig.java @@ -0,0 +1,73 @@ +package getjobs.config.webclient; + +import io.netty.channel.ChannelOption; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.handler.timeout.WriteTimeoutHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.ExchangeStrategies; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import reactor.netty.http.client.HttpClient; +import reactor.netty.resources.ConnectionProvider; + +import java.time.Duration; + +@Configuration +public class WebClientConfig { + + @Bean + public WebClient defaultWebClient(WebClient.Builder builder) { + // 连接池(Reactor Netty) + ConnectionProvider provider = ConnectionProvider.builder("default-pool") + .maxConnections(500) + .pendingAcquireMaxCount(1000) + .pendingAcquireTimeout(Duration.ofSeconds(2)) + .maxIdleTime(Duration.ofSeconds(30)) + .maxLifeTime(Duration.ofMinutes(5)) + .lifo() // 或 fifo() + .build(); + + HttpClient httpClient = HttpClient.create(provider) + .compress(true) // 开启 gzip + .responseTimeout(Duration.ofSeconds(5)) // 整体响应超时(含首包) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000) // 连接超时 + .doOnConnected(conn -> conn + .addHandlerLast(new ReadTimeoutHandler(5)) + .addHandlerLast(new WriteTimeoutHandler(5))) + // .proxy(typeSpec -> + // typeSpec.type(ProxyProvider.Proxy.HTTP).host("proxy").port(8080)) + // .secure(sslSpec -> ...) // 如需自签证书、信任库 + ; + + // 统一日志 / 追踪 / Header / 错误映射 + ExchangeFilterFunction logFilter = ExchangeFilterFunction.ofRequestProcessor(req -> { + // 简易日志,生产建议使用自定义 logger + 脱敏 + return Mono.just(ClientRequest.from(req).build()); + }); + + ExchangeStrategies strategies = ExchangeStrategies.builder() + .codecs(c -> { + // 提升内存缓冲上限(默认 256KB) + c.defaultCodecs().maxInMemorySize(8 * 1024 * 1024); + // 如需自定义 ObjectMapper: + // c.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(customMapper)); + // c.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(customMapper)); + }) + .build(); + + return builder + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .exchangeStrategies(strategies) + .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .defaultHeader(HttpHeaders.USER_AGENT, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36") + .filter(logFilter) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/getjobs/controller/BossTaskController.java b/src/main/java/getjobs/controller/BossTaskController.java new file mode 100644 index 00000000..02585eda --- /dev/null +++ b/src/main/java/getjobs/controller/BossTaskController.java @@ -0,0 +1,230 @@ +package getjobs.controller; + +import getjobs.common.dto.ConfigDTO; +import getjobs.modules.boss.service.BossTaskService; +import getjobs.modules.boss.service.BossTaskService.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * Boss任务控制器 - 提供4个独立的API接口 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Slf4j +@RestController +@RequestMapping("/api/boss/task") +public class BossTaskController { + + private final BossTaskService bossTaskService; + + public BossTaskController(BossTaskService bossTaskService) { + this.bossTaskService = bossTaskService; + } + + /** + * 1. 登录接口 + * POST /api/boss/task/login + * + * @param config Boss配置信息 + * @return 登录结果 + */ + @PostMapping("/login") + public ResponseEntity> login(@RequestBody ConfigDTO config) { + log.info("接收到Boss登录请求"); + + try { + LoginResult result = bossTaskService.login(config); + + Map response = new HashMap<>(); + response.put("success", result.isSuccess()); + response.put("taskId", result.getTaskId()); + response.put("message", result.getMessage()); + response.put("timestamp", result.getTimestamp()); + + if (result.isSuccess()) { + log.info("Boss登录成功,任务ID: {}", result.getTaskId()); + return ResponseEntity.ok(response); + } else { + log.warn("Boss登录失败,任务ID: {}, 原因: {}", result.getTaskId(), result.getMessage()); + return ResponseEntity.badRequest().body(response); + } + + } catch (Exception e) { + log.error("Boss登录接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "登录接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 2. 采集岗位接口 + * POST /api/boss/task/collect + * + * @param config Boss配置信息 + * @return 采集结果 + */ + @PostMapping("/collect") + public ResponseEntity> collectJobs(@RequestBody ConfigDTO config) { + log.info("接收到Boss岗位采集请求"); + + try { + CollectResult result = bossTaskService.collectJobs(config); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("taskId", result.getTaskId()); + response.put("message", result.getMessage()); + response.put("jobCount", result.getJobCount()); + response.put("timestamp", result.getTimestamp()); + + // 如果需要返回详细岗位信息,可以添加以下行 + // response.put("jobs", result.getJobs()); + + log.info("Boss岗位采集完成,任务ID: {}, 采集到 {} 个岗位", result.getTaskId(), result.getJobCount()); + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("Boss岗位采集接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "采集接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 3. 过滤岗位接口 + * POST /api/boss/task/filter + * + * @param request 过滤请求 + * @return 过滤结果 + */ + @PostMapping("/filter") + public ResponseEntity> filterJobs(@RequestBody FilterRequest request) { + log.info("接收到Boss岗位过滤请求"); + + try { + FilterResult result = bossTaskService.filterJobs(request.getConfig()); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", result.getMessage()); + response.put("originalCount", result.getOriginalCount()); + response.put("filteredCount", result.getFilteredCount()); + response.put("timestamp", result.getTimestamp()); + + // 如果需要返回详细岗位信息,可以添加以下行 + // response.put("jobs", result.getJobs()); + + log.info("Boss岗位过滤完成,原始 {} 个,过滤后 {} 个", + result.getOriginalCount(), result.getFilteredCount()); + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("Boss岗位过滤接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "过滤接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 4. 投递岗位接口 + * POST /api/boss/task/deliver + * + * @param request 投递请求(包含配置和是否实际投递) + * @return 投递结果 + */ + @PostMapping("/deliver") + public ResponseEntity> deliverJobs(@RequestBody DeliveryRequest request) { + log.info("接收到Boss岗位投递请求,实际投递: {}", + request.isEnableActualDelivery()); + + try { + DeliveryResult result = bossTaskService.deliverJobs( + request.getConfig(), + request.isEnableActualDelivery()); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("taskId", result.getTaskId()); + response.put("message", result.getMessage()); + response.put("totalCount", result.getTotalCount()); + response.put("deliveredCount", result.getDeliveredCount()); + response.put("actualDelivery", result.isActualDelivery()); + response.put("timestamp", result.getTimestamp()); + + // 如果有岗位详情,添加到响应中 + if (result.getJobDetails() != null && !result.getJobDetails().isEmpty()) { + response.put("jobDetails", result.getJobDetails()); + } + + log.info("Boss岗位投递完成,任务ID: {}, 处理 {} 个岗位", + result.getTaskId(), result.getDeliveredCount()); + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("Boss岗位投递接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "投递接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + + + // 请求DTO类 + public static class FilterRequest { + private String collectTaskId; + private ConfigDTO config; + + public String getCollectTaskId() { + return collectTaskId; + } + + public void setCollectTaskId(String collectTaskId) { + this.collectTaskId = collectTaskId; + } + + public ConfigDTO getConfig() { + return config; + } + + public void setConfig(ConfigDTO config) { + this.config = config; + } + } + + public static class DeliveryRequest { + private ConfigDTO config; + private boolean enableActualDelivery = false; // 默认为模拟投递 + + public ConfigDTO getConfig() { + return config; + } + + public void setConfig(ConfigDTO config) { + this.config = config; + } + + public boolean isEnableActualDelivery() { + return enableActualDelivery; + } + + public void setEnableActualDelivery(boolean enableActualDelivery) { + this.enableActualDelivery = enableActualDelivery; + } + } +} diff --git a/src/main/java/getjobs/controller/CommonConfigController.java b/src/main/java/getjobs/controller/CommonConfigController.java new file mode 100644 index 00000000..80d06c72 --- /dev/null +++ b/src/main/java/getjobs/controller/CommonConfigController.java @@ -0,0 +1,86 @@ +package getjobs.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import getjobs.repository.UserProfileRepository; +import getjobs.repository.entity.UserProfile; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 公共配置控制器 + * 用于管理系统级别的公共配置 + */ +@RestController +@RequestMapping("/api/common/config") +@RequiredArgsConstructor +public class CommonConfigController { + + private final UserProfileRepository userProfileRepository; + private final ObjectMapper objectMapper; + + /** + * 新增或更新公共配置 + * @param configData 配置数据(键值对形式) + * @return 保存结果 + */ + @PostMapping("/save") + @Transactional + public ResponseEntity> saveCommonConfig(@RequestBody Map configData) { + Map response = new HashMap<>(); + + try { + // 验证配置数据 + if (configData == null || configData.isEmpty()) { + response.put("success", false); + response.put("message", "配置数据不能为空"); + return ResponseEntity.badRequest().body(response); + } + + // 获取或创建 UserProfile(假设系统中只有一个配置记录) + UserProfile userProfile = userProfileRepository.findAll().stream() + .findFirst() + .orElse(new UserProfile()); + + // 从 configData 中提取并更新字段 + if (configData.containsKey("jobBlacklistKeywords")) { + userProfile.setPositionBlacklist(convertToList(configData.get("jobBlacklistKeywords"))); + } + if (configData.containsKey("companyBlacklistKeywords")) { + userProfile.setCompanyBlacklist(convertToList(configData.get("companyBlacklistKeywords"))); + } + + // 保存到数据库 + UserProfile saved = userProfileRepository.save(userProfile); + + response.put("success", true); + response.put("message", "公共配置保存成功"); + response.put("timestamp", LocalDateTime.now()); + response.put("profileId", saved.getId()); + + return ResponseEntity.ok(response); + + } catch (Exception e) { + response.put("success", false); + response.put("message", "保存配置失败: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 转换为字符串列表 + */ + @SuppressWarnings("unchecked") + private List convertToList(Object value) { + if (value == null) return null; + if (value instanceof List) return (List) value; + return objectMapper.convertValue(value, List.class); + } +} + diff --git a/src/main/java/getjobs/controller/ConfigController.java b/src/main/java/getjobs/controller/ConfigController.java new file mode 100644 index 00000000..49d70f48 --- /dev/null +++ b/src/main/java/getjobs/controller/ConfigController.java @@ -0,0 +1,160 @@ +package getjobs.controller; + +import getjobs.common.dto.ConfigDTO; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.repository.entity.ConfigEntity; +import getjobs.service.ConfigService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/config") +public class ConfigController { + + private final ConfigService configService; + + public ConfigController(ConfigService configService) { + this.configService = configService; + } + + @PostMapping("/boss") + public ResponseEntity> saveBossConfig(@RequestBody ConfigDTO dto) { + ConfigEntity entity = toEntity(dto); + // 自动绑定平台类型为boss + entity.setPlatformType(RecruitmentPlatformEnum.BOSS_ZHIPIN.getPlatformCode()); + entity = configService.save(entity); + ConfigDTO.reload(); + + Map resp = new HashMap<>(); + resp.put("success", true); + resp.put("id", entity.getId()); + resp.put("platformType", entity.getPlatformType()); + return ResponseEntity.ok(resp); + } + + @GetMapping("/boss") + public ResponseEntity loadBossConfig() { + ConfigEntity entity = configService.loadByPlatformType(RecruitmentPlatformEnum.BOSS_ZHIPIN.getPlatformCode()); + return ResponseEntity.ok(entity); + } + + @PostMapping("/job51") + public ResponseEntity> save51JobConfig(@RequestBody ConfigDTO dto) { + ConfigEntity entity = toEntity(dto); + // 自动绑定平台类型为51job + entity.setPlatformType(RecruitmentPlatformEnum.JOB_51.getPlatformCode()); + entity = configService.save(entity); + ConfigDTO.reload(); + + Map resp = new HashMap<>(); + resp.put("success", true); + resp.put("id", entity.getId()); + resp.put("platformType", entity.getPlatformType()); + return ResponseEntity.ok(resp); + } + + @GetMapping("/job51") + public ResponseEntity load51JobConfig() { + ConfigEntity entity = configService.loadByPlatformType(RecruitmentPlatformEnum.JOB_51.getPlatformCode()); + return ResponseEntity.ok(entity); + } + + @PostMapping("/zhilian") + public ResponseEntity> saveZhilianConfig(@RequestBody ConfigDTO dto) { + ConfigEntity entity = toEntity(dto); + // 自动绑定平台类型为智联招聘 + entity.setPlatformType(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN.getPlatformCode()); + entity = configService.save(entity); + ConfigDTO.reload(); + + Map resp = new HashMap<>(); + resp.put("success", true); + resp.put("id", entity.getId()); + resp.put("platformType", entity.getPlatformType()); + return ResponseEntity.ok(resp); + } + + @GetMapping("/zhilian") + public ResponseEntity loadZhilianConfig() { + ConfigEntity entity = configService + .loadByPlatformType(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN.getPlatformCode()); + return ResponseEntity.ok(entity); + } + + @PostMapping("/liepin") + public ResponseEntity> saveLiepinConfig(@RequestBody ConfigDTO dto) { + ConfigEntity entity = toEntity(dto); + // 自动绑定平台类型为猎聘 + entity.setPlatformType(RecruitmentPlatformEnum.LIEPIN.getPlatformCode()); + entity = configService.save(entity); + ConfigDTO.reload(); + + Map resp = new HashMap<>(); + resp.put("success", true); + resp.put("id", entity.getId()); + resp.put("platformType", entity.getPlatformType()); + return ResponseEntity.ok(resp); + } + + @GetMapping("/liepin") + public ResponseEntity loadLiepinConfig() { + ConfigEntity entity = configService.loadByPlatformType(RecruitmentPlatformEnum.LIEPIN.getPlatformCode()); + return ResponseEntity.ok(entity); + } + + private ConfigEntity toEntity(ConfigDTO dto) { + ConfigEntity e = new ConfigEntity(); + e.setSayHi(dto.getSayHi()); + e.setKeywords(split(dto.getKeywords())); + e.setCityCode(split(dto.getCityCode())); + e.setIndustry(split(dto.getIndustry())); + e.setExperience(wrap(dto.getExperience())); + e.setJobType(dto.getJobType()); + e.setSalary(dto.getSalary()); + e.setExpectedPosition(dto.getExpectedPosition()); + e.setDegree(wrap(dto.getDegree())); + e.setScale(wrap(dto.getScale())); + e.setStage(wrap(dto.getStage())); + e.setEnableAIJobMatchDetection(Boolean.TRUE.equals(dto.getEnableAIJobMatchDetection())); + e.setEnableAIGreeting(Boolean.TRUE.equals(dto.getEnableAIGreeting())); + e.setFilterDeadHR(Boolean.TRUE.equals(dto.getFilterDeadHR())); + e.setSendImgResume(Boolean.TRUE.equals(dto.getSendImgResume())); + e.setResumeImagePath(dto.getResumeImagePath()); + e.setResumeContent(dto.getResumeContent()); + if (dto.getMinSalary() != null && dto.getMaxSalary() != null && dto.getMinSalary() > 0 + && dto.getMaxSalary() >= dto.getMinSalary()) { + e.setExpectedSalary(Arrays.asList(dto.getMinSalary(), dto.getMaxSalary())); + } + e.setWaitTime(dto.getWaitTime()); + e.setKeyFilter(Boolean.TRUE.equals(dto.getKeyFilter())); + e.setRecommendJobs(Boolean.TRUE.equals(dto.getRecommendJobs())); + e.setCheckStateOwned(Boolean.TRUE.equals(dto.getCheckStateOwned())); + e.setCustomCityCode(dto.getCustomCityCode()); + e.setDeadStatus(dto.getDeadStatus()); + return e; + } + + private java.util.List split(String csv) { + if (csv == null || csv.trim().isEmpty()) + return java.util.Collections.emptyList(); + String[] arr = csv.split(","); + java.util.List list = new java.util.ArrayList<>(); + for (String s : arr) { + String t = s.trim(); + if (!t.isEmpty()) + list.add(t); + } + return list; + } + + private java.util.List wrap(String v) { + if (v == null || v.trim().isEmpty()) + return java.util.Collections.emptyList(); + return java.util.Collections.singletonList(v.trim()); + } + +} diff --git a/src/main/java/getjobs/controller/DataBackupController.java b/src/main/java/getjobs/controller/DataBackupController.java new file mode 100644 index 00000000..bb71eec5 --- /dev/null +++ b/src/main/java/getjobs/controller/DataBackupController.java @@ -0,0 +1,134 @@ +package getjobs.controller; + +import getjobs.service.DataBackupService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * 数据备份和恢复控制器 + * 提供H2内存数据库的数据备份到用户目录和恢复功能 + * + * @author getjobs + * @since v2.0.1 + */ +@Slf4j +@RestController +@RequestMapping("/api/backup") +@RequiredArgsConstructor +public class DataBackupController { + + private final DataBackupService dataBackupService; + + /** + * 备份当前H2内存数据库中的所有数据到用户目录 + * + * @return 备份结果 + */ + @PostMapping("/export") + public ResponseEntity> exportData() { + Map response = new HashMap<>(); + + try { + String backupPath = dataBackupService.exportData(); + response.put("success", true); + response.put("message", "数据备份成功"); + response.put("backupPath", backupPath); + response.put("timestamp", System.currentTimeMillis()); + + log.info("数据备份成功,备份路径: {}", backupPath); + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("数据备份失败", e); + response.put("success", false); + response.put("message", "数据备份失败: " + e.getMessage()); + return ResponseEntity.status(500).body(response); + } + } + + /** + * 从用户目录恢复数据到H2内存数据库 + * + * @return 恢复结果 + */ + @PostMapping("/import") + public ResponseEntity> importData() { + Map response = new HashMap<>(); + + try { + boolean restored = dataBackupService.importData(); + if (restored) { + response.put("success", true); + response.put("message", "数据恢复成功"); + response.put("timestamp", System.currentTimeMillis()); + log.info("数据恢复成功"); + } else { + response.put("success", false); + response.put("message", "未找到备份文件或备份文件为空"); + log.warn("数据恢复失败:未找到备份文件"); + } + + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("数据恢复失败", e); + response.put("success", false); + response.put("message", "数据恢复失败: " + e.getMessage()); + return ResponseEntity.status(500).body(response); + } + } + + /** + * 获取备份文件信息 + * + * @return 备份文件信息 + */ + @GetMapping("/info") + public ResponseEntity> getBackupInfo() { + Map response = new HashMap<>(); + + try { + Map backupInfo = dataBackupService.getBackupInfo(); + response.put("success", true); + response.put("data", backupInfo); + + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("获取备份信息失败", e); + response.put("success", false); + response.put("message", "获取备份信息失败: " + e.getMessage()); + return ResponseEntity.status(500).body(response); + } + } + + /** + * 删除备份文件 + * + * @return 删除结果 + */ + @DeleteMapping("/clean") + public ResponseEntity> cleanBackup() { + Map response = new HashMap<>(); + + try { + boolean cleaned = dataBackupService.cleanBackup(); + response.put("success", cleaned); + response.put("message", cleaned ? "备份文件删除成功" : "备份文件不存在"); + + log.info("备份文件清理结果: {}", cleaned); + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("删除备份文件失败", e); + response.put("success", false); + response.put("message", "删除备份文件失败: " + e.getMessage()); + return ResponseEntity.status(500).body(response); + } + } +} diff --git a/src/main/java/getjobs/controller/HealthController.java b/src/main/java/getjobs/controller/HealthController.java new file mode 100644 index 00000000..3fcf801d --- /dev/null +++ b/src/main/java/getjobs/controller/HealthController.java @@ -0,0 +1,31 @@ +package getjobs.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api") +public class HealthController { + + @GetMapping("/health") + public Map health() { + Map response = new HashMap<>(); + response.put("status", "UP"); + response.put("timestamp", LocalDateTime.now()); + response.put("service", "npe-get-jobs"); + return response; + } + + @GetMapping("/") + public Map home() { + Map response = new HashMap<>(); + response.put("message", "NPE Get Jobs Service is running!"); + response.put("version", "v2.0.1"); + return response; + } +} diff --git a/src/main/java/getjobs/controller/Job51TaskController.java b/src/main/java/getjobs/controller/Job51TaskController.java new file mode 100644 index 00000000..5e20a13b --- /dev/null +++ b/src/main/java/getjobs/controller/Job51TaskController.java @@ -0,0 +1,229 @@ +package getjobs.controller; + +import getjobs.common.dto.ConfigDTO; +import getjobs.modules.job51.service.Job51TaskService; +import getjobs.modules.job51.service.Job51TaskService.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * 51job任务控制器 - 提供4个独立的API接口 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Slf4j +@RestController +@RequestMapping("/api/job51/task") +public class Job51TaskController { + + private final Job51TaskService job51TaskService; + + public Job51TaskController(Job51TaskService job51TaskService) { + this.job51TaskService = job51TaskService; + } + + /** + * 1. 登录接口 + * POST /api/job51/task/login + * + * @param config 51job配置信息 + * @return 登录结果 + */ + @PostMapping("/login") + public ResponseEntity> login(@RequestBody ConfigDTO config) { + log.info("接收到51job登录请求"); + + try { + LoginResult result = job51TaskService.login(config); + + Map response = new HashMap<>(); + response.put("success", result.isSuccess()); + response.put("taskId", result.getTaskId()); + response.put("message", result.getMessage()); + response.put("timestamp", result.getTimestamp()); + + if (result.isSuccess()) { + log.info("51job登录成功,任务ID: {}", result.getTaskId()); + return ResponseEntity.ok(response); + } else { + log.warn("51job登录失败,任务ID: {}, 原因: {}", result.getTaskId(), result.getMessage()); + return ResponseEntity.badRequest().body(response); + } + + } catch (Exception e) { + log.error("51job登录接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "登录接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 2. 采集岗位接口 + * POST /api/job51/task/collect + * + * @param config 51job配置信息 + * @return 采集结果 + */ + @PostMapping("/collect") + public ResponseEntity> collectJobs(@RequestBody ConfigDTO config) { + log.info("接收到51job岗位采集请求"); + + try { + CollectResult result = job51TaskService.collectJobs(config); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("taskId", result.getTaskId()); + response.put("message", result.getMessage()); + response.put("jobCount", result.getJobCount()); + response.put("timestamp", result.getTimestamp()); + + // 如果需要返回详细岗位信息,可以添加以下行 + // response.put("jobs", result.getJobs()); + + log.info("51job岗位采集完成,任务ID: {}, 采集到 {} 个岗位", result.getTaskId(), result.getJobCount()); + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("51job岗位采集接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "采集接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 3. 过滤岗位接口 + * POST /api/job51/task/filter + * + * @param request 过滤请求 + * @return 过滤结果 + */ + @PostMapping("/filter") + public ResponseEntity> filterJobs(@RequestBody FilterRequest request) { + log.info("接收到51job岗位过滤请求"); + + try { + FilterResult result = job51TaskService.filterJobs(request.getConfig()); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", result.getMessage()); + response.put("originalCount", result.getOriginalCount()); + response.put("filteredCount", result.getFilteredCount()); + response.put("timestamp", result.getTimestamp()); + + // 如果需要返回详细岗位信息,可以添加以下行 + // response.put("jobs", result.getJobs()); + + log.info("51job岗位过滤完成,原始 {} 个,过滤后 {} 个", + result.getOriginalCount(), result.getFilteredCount()); + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("51job岗位过滤接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "过滤接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 4. 投递岗位接口 + * POST /api/job51/task/deliver + * + * @param request 投递请求(包含配置和是否实际投递) + * @return 投递结果 + */ + @PostMapping("/deliver") + public ResponseEntity> deliverJobs(@RequestBody DeliveryRequest request) { + log.info("接收到51job岗位投递请求,实际投递: {}", + request.isEnableActualDelivery()); + + try { + DeliveryResult result = job51TaskService.deliverJobs( + request.getConfig(), + request.isEnableActualDelivery()); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("taskId", result.getTaskId()); + response.put("message", result.getMessage()); + response.put("totalCount", result.getTotalCount()); + response.put("deliveredCount", result.getDeliveredCount()); + response.put("actualDelivery", result.isActualDelivery()); + response.put("timestamp", result.getTimestamp()); + + // 如果有岗位详情,添加到响应中 + if (result.getJobDetails() != null && !result.getJobDetails().isEmpty()) { + response.put("jobDetails", result.getJobDetails()); + } + + log.info("51job岗位投递完成,任务ID: {}, 处理 {} 个岗位", + result.getTaskId(), result.getDeliveredCount()); + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("51job岗位投递接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "投递接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + + + // 请求DTO类 + public static class FilterRequest { + private String collectTaskId; + private ConfigDTO config; + + public String getCollectTaskId() { + return collectTaskId; + } + + public void setCollectTaskId(String collectTaskId) { + this.collectTaskId = collectTaskId; + } + + public ConfigDTO getConfig() { + return config; + } + + public void setConfig(ConfigDTO config) { + this.config = config; + } + } + + public static class DeliveryRequest { + private ConfigDTO config; + private boolean enableActualDelivery = false; // 默认为模拟投递 + + public ConfigDTO getConfig() { + return config; + } + + public void setConfig(ConfigDTO config) { + this.config = config; + } + + public boolean isEnableActualDelivery() { + return enableActualDelivery; + } + + public void setEnableActualDelivery(boolean enableActualDelivery) { + this.enableActualDelivery = enableActualDelivery; + } + } +} diff --git a/src/main/java/getjobs/controller/JobController.java b/src/main/java/getjobs/controller/JobController.java new file mode 100644 index 00000000..10b083f1 --- /dev/null +++ b/src/main/java/getjobs/controller/JobController.java @@ -0,0 +1,43 @@ +package getjobs.controller; + +import getjobs.repository.entity.JobEntity; +import getjobs.service.JobService; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/jobs") +public class JobController { + + private final JobService jobService; + + public JobController(JobService jobService) { + this.jobService = jobService; + } + + @GetMapping + public Page list( + @RequestParam(value = "platform", required = false) String platform, + @RequestParam(value = "keyword", required = false) String keyword, + @RequestParam(value = "page", defaultValue = "0") int page, + @RequestParam(value = "size", defaultValue = "10") int size) { + + return jobService.search(platform, keyword, page, size); + } + + @PostMapping("/reset-filter") + public int resetFilter(@RequestParam("platform") String platform) { + return jobService.resetFilterByPlatform(platform); + } + + @DeleteMapping("") + public void deleteAllJobs(@RequestParam("platform") String platform) { + jobService.deleteAllByPlatform(platform); + } + +} diff --git a/src/main/java/getjobs/listener/DataRestoreListener.java b/src/main/java/getjobs/listener/DataRestoreListener.java new file mode 100644 index 00000000..fba3ac7f --- /dev/null +++ b/src/main/java/getjobs/listener/DataRestoreListener.java @@ -0,0 +1,71 @@ +package getjobs.listener; + +import getjobs.service.DataBackupService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * 应用启动数据恢复监听器 + * 在Spring Boot应用启动完成后自动恢复备份数据 + * + * @author getjobs + * @since v2.0.1 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class DataRestoreListener { + + private final DataBackupService dataBackupService; + + /** + * 监听应用启动完成事件,自动恢复备份数据 + */ + @EventListener(ApplicationReadyEvent.class) + @Order(1000) // 确保在其他组件初始化完成后执行 + public void onApplicationReady() { + try { + log.info("应用启动完成,开始检查并恢复备份数据..."); + + // 检查备份文件是否存在 + var backupInfo = dataBackupService.getBackupInfo(); + boolean backupExists = (Boolean) backupInfo.get("exists"); + + if (backupExists) { + log.info("发现备份文件,开始恢复数据..."); + log.info("备份文件路径: {}", backupInfo.get("filePath")); + log.info("备份文件大小: {} bytes", backupInfo.get("fileSize")); + + if (backupInfo.containsKey("exportTime")) { + + log.info("备份时间: {}", backupInfo.get("exportTime")); + } + if (backupInfo.containsKey("configCount")) { + log.info("备份配置数量: {}", backupInfo.get("configCount")); + } + if (backupInfo.containsKey("jobCount")) { + log.info("备份职位数量: {}", backupInfo.get("jobCount")); + } + + // 执行数据恢复 + boolean restored = dataBackupService.importData(); + + if (restored) { + log.info("数据恢复成功!"); + } else { + log.warn("数据恢复失败或备份文件为空"); + } + } else { + log.info("未发现备份文件,使用空数据库启动"); + } + + } catch (Exception e) { + log.error("启动时数据恢复过程中发生错误", e); + // 不抛出异常,避免影响应用启动 + } + } +} diff --git a/src/main/java/getjobs/modules/ai/common/enums/AiPlatform.java b/src/main/java/getjobs/modules/ai/common/enums/AiPlatform.java new file mode 100644 index 00000000..8f265c40 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/common/enums/AiPlatform.java @@ -0,0 +1,31 @@ +package getjobs.modules.ai.common.enums; + +/** + * AI 平台枚举 + */ +public enum AiPlatform { + /** + * OpenAI + */ + OPENAI, + /** + * Deepseek + */ + DEEPSEEK; + + + public static final String OPENAI_BEAN_NAME = "chatgptAiChatModel"; + public static final String DEEPSEEK_BEAN_NAME = "deepseekChatModel"; + + public String getModelBeanName() { + switch (this) { + case OPENAI: + return OPENAI_BEAN_NAME; + case DEEPSEEK: + return DEEPSEEK_BEAN_NAME; + default: + // Or handle as per application's error handling strategy + throw new IllegalArgumentException("Unsupported AI platform: " + this); + } + } +} diff --git a/src/main/java/getjobs/modules/ai/common/factory/ChatModelFactory.java b/src/main/java/getjobs/modules/ai/common/factory/ChatModelFactory.java new file mode 100644 index 00000000..cadfca8d --- /dev/null +++ b/src/main/java/getjobs/modules/ai/common/factory/ChatModelFactory.java @@ -0,0 +1,28 @@ +package getjobs.modules.ai.common.factory; + +import getjobs.modules.ai.common.enums.AiPlatform; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class ChatModelFactory { + + private final Map chatModelMap = new ConcurrentHashMap<>(); + + public ChatModelFactory(Map chatModels) { + for (AiPlatform platform : AiPlatform.values()) { + ChatModel chatModel = chatModels.get(platform.getModelBeanName()); + if (chatModel != null) { + chatModelMap.put(platform, chatModel); + + } + } + } + + public ChatModel getChatModel(AiPlatform platform) { + return chatModelMap.get(platform); + } +} diff --git a/src/main/java/getjobs/modules/ai/config/AiPromptProperties.java b/src/main/java/getjobs/modules/ai/config/AiPromptProperties.java new file mode 100644 index 00000000..78b75a70 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/config/AiPromptProperties.java @@ -0,0 +1,118 @@ +package getjobs.modules.ai.config; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * AI 提示词属性配置类 + *

+ * 从 application.yml 文件中加载以 "getjobs.ai.prompts" 为前缀的配置项。 + * 这个类用于定义和管理不同场景下的 AI 提示词模板。 + *

+ */ +@Component +@ConfigurationProperties(prefix = "getjobs.ai.prompts") +public class AiPromptProperties { + + /** + * 存储与“职位”相关的提示词定义。 + *

+ * Map 的键是提示词的唯一标识符(例如 "job-match"),值是 {@link PromptDefinition} 对象, + * 包含了提示词的详细信息,如描述、占位符和模板内容。 + *

+ */ + private Map job = new HashMap<>(); + + public Map getJob() { + return job; + } + + public void setJob(Map job) { + this.job = job; + } + + /** + * 表示单个提示词的定义。 + *

+ * 包含提示词的描述、模板中使用的占位符列表以及模板本身。 + *

+ */ + public static class PromptDefinition { + + /** + * 提示词的描述信息,用于说明该提示词的用途。 + */ + private String description; + + /** + * 提示词模板中使用的占位符。 + *

+ * Map 的键是占位符的名称(例如 "my_jd"),值是 {@link Placeholder} 对象, + * 包含了占位符的详细信息,如是否必需和描述。 + *

+ */ + private Map placeholders = new HashMap<>(); + + /** + * 提示词的模板内容。 + *

+ * 模板中可以包含由 "{}" 包裹的占位符,这些占位符将在运行时被实际值替换。 + * 例如:"请根据以下简历 {resume} 和职位描述 {job_description},分析匹配度。" + *

+ */ + private String template; + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Map getPlaceholders() { + return placeholders; + } + + public void setPlaceholders(Map placeholders) { + this.placeholders = placeholders; + } + + public String getTemplate() { + return template; + } + + public void setTemplate(String template) { + this.template = template; + } + } + + /** + * 表示提示词模板中单个占位符的定义。 + */ + @Data + public static class Placeholder { + + /** + * 指示用户是否必须为此占位符提供值。 + */ + private boolean required; + + /** + * 占位符的描述,用于解释其预期的内容或用途。 + */ + private String description; + + + private String value; + + } +} diff --git a/src/main/java/getjobs/modules/ai/greeting/ab/ExperimentService.java b/src/main/java/getjobs/modules/ai/greeting/ab/ExperimentService.java new file mode 100644 index 00000000..509dc37d --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/ab/ExperimentService.java @@ -0,0 +1,13 @@ +package getjobs.modules.ai.greeting.ab; + +import getjobs.modules.ai.greeting.dto.GreetingParams; +import org.springframework.stereotype.Service; + +@Service +public class ExperimentService { + public String pickVariant(String expName, GreetingParams p) { + // 先按 tone 简单分配;真实环境可按用户ID hash 分桶 + if ("platform".equalsIgnoreCase(p.getTone())) return Variant.V2.templateId; + return Variant.V1.templateId; + } +} diff --git a/src/main/java/getjobs/modules/ai/greeting/ab/Variant.java b/src/main/java/getjobs/modules/ai/greeting/ab/Variant.java new file mode 100644 index 00000000..3b291063 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/ab/Variant.java @@ -0,0 +1,8 @@ +package getjobs.modules.ai.greeting.ab; + +public enum Variant { + V1("greeting-v1"), + V2("greeting-v1"); // 先共用一个模板,后续可切换到新模板 + public final String templateId; + Variant(String id){ this.templateId = id; } +} diff --git a/src/main/java/getjobs/modules/ai/greeting/assembler/PromptAssembler.java b/src/main/java/getjobs/modules/ai/greeting/assembler/PromptAssembler.java new file mode 100644 index 00000000..37e99709 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/assembler/PromptAssembler.java @@ -0,0 +1,65 @@ +package getjobs.modules.ai.greeting.assembler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import getjobs.modules.ai.greeting.assembler.PromptVariables; +import getjobs.modules.ai.greeting.dto.GreetingRequest; +import getjobs.modules.ai.greeting.extract.KeywordExtractor; +import getjobs.modules.ai.greeting.llm.LlmMessage; +import getjobs.modules.ai.greeting.template.PromptRenderer; +import getjobs.modules.ai.greeting.template.PromptTemplate; +import getjobs.modules.ai.greeting.template.TemplateRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class PromptAssembler { + private final TemplateRepository repo; + private final PromptRenderer renderer; + private final KeywordExtractor extractor; + + public List assemble(String templateId, GreetingRequest req) { + List keywords = req.getJdKeywords(); + if (keywords == null || keywords.isEmpty()) { + keywords = extractor.extractTop(req.getJdText(), 3); + } + Map vars = new HashMap<>(); + vars.put(PromptVariables.TONE, req.getParams().getTone()); + vars.put(PromptVariables.MAX_CHARS, req.getParams().getMaxChars()); + vars.put(PromptVariables.SHOW_WEAKNESS, req.getParams().isShowWeakness()); + vars.put(PromptVariables.JD_TEXT, req.getJdText()); + vars.put(PromptVariables.JD_KEYWORDS, keywords); + vars.put(PromptVariables.PROFILE_JSON, toJson(req.getProfile())); + + PromptTemplate tpl = repo.get(templateId); + List messages = new ArrayList<>(); + + for (PromptTemplate.Segment seg : tpl.getSegments()) { + String content = renderer.render(seg.getContent(), vars); + switch (seg.getType()) { + case SYSTEM -> messages.add(LlmMessage.system(content)); + case GUIDELINES -> messages.add(LlmMessage.system(content)); + case USER -> messages.add(LlmMessage.user(content)); + case FEW_SHOTS -> { + // FEW_SHOTS 类型的段落通常包含一或多个“用户输入-助手输出”的示例,用于指导模型的响应格式和风格。 + // 这里将其作为 user 消息类型,是为了将这些示例作为用户提供给模型的上下文信息。 + // 这种方法可以有效地向模型展示期望的交互模式,引导其生成符合预期的回复。 + // 尽管示例中可能包含助手角色的内容,但将它们统一作为用户消息的一部分, + // 是一种常见的 prompt 工程技巧,可以简化对话历史的结构,并有助于在某些模型上获得更稳定和可控的输出。 + messages.add(LlmMessage.user(content)); // 或 assistant 示例 + } + } + } + return messages; + } + + private String toJson(Object o) { + try { return new ObjectMapper().writeValueAsString(o); } + catch (Exception e) { return "{}"; } + } +} diff --git a/src/main/java/getjobs/modules/ai/greeting/assembler/PromptVariables.java b/src/main/java/getjobs/modules/ai/greeting/assembler/PromptVariables.java new file mode 100644 index 00000000..3baaee51 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/assembler/PromptVariables.java @@ -0,0 +1,15 @@ +package getjobs.modules.ai.greeting.assembler; + +public final class PromptVariables { + + private PromptVariables() { + // Prevent instantiation + } + + public static final String TONE = "tone"; + public static final String MAX_CHARS = "max_chars"; + public static final String SHOW_WEAKNESS = "show_weakness"; + public static final String JD_TEXT = "jd_text"; + public static final String JD_KEYWORDS = "jd_keywords"; + public static final String PROFILE_JSON = "profile_json"; +} diff --git a/src/main/java/getjobs/modules/ai/greeting/assembler/ToneStrategy.java b/src/main/java/getjobs/modules/ai/greeting/assembler/ToneStrategy.java new file mode 100644 index 00000000..dbf804a6 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/assembler/ToneStrategy.java @@ -0,0 +1,8 @@ +package getjobs.modules.ai.greeting.assembler; + + +import getjobs.modules.ai.greeting.dto.GreetingParams; + +public interface ToneStrategy { + GreetingParams adjust(GreetingParams in); +} diff --git a/src/main/java/getjobs/modules/ai/greeting/dto/GreetingParams.java b/src/main/java/getjobs/modules/ai/greeting/dto/GreetingParams.java new file mode 100644 index 00000000..7e8ca342 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/dto/GreetingParams.java @@ -0,0 +1,11 @@ +package getjobs.modules.ai.greeting.dto; + +import lombok.Data; + +@Data +public class GreetingParams { + private String tone; // formal | brief | platform | english + private int maxChars = 80; // 60~90 + private boolean showWeakness; // 是否带1个可控改进点 + private String outputMode = "text"; // text | json +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/ai/greeting/dto/GreetingRequest.java b/src/main/java/getjobs/modules/ai/greeting/dto/GreetingRequest.java new file mode 100644 index 00000000..6efb1baa --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/dto/GreetingRequest.java @@ -0,0 +1,13 @@ +package getjobs.modules.ai.greeting.dto; + +import lombok.Data; + +import java.util.List; + +@Data +public class GreetingRequest { + private ProfileDTO profile; + private String jdText; + private List jdKeywords; // 可为空 + private GreetingParams params; +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/ai/greeting/dto/GreetingResponse.java b/src/main/java/getjobs/modules/ai/greeting/dto/GreetingResponse.java new file mode 100644 index 00000000..4b011759 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/dto/GreetingResponse.java @@ -0,0 +1,16 @@ +package getjobs.modules.ai.greeting.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.List; + +@Data +@AllArgsConstructor +public class GreetingResponse { + private String greeting; // 产出的一句话 + private List usedKeywords; // 实际覆盖到的关键词 + private String fitReason; // 12字内贴合概括(可选) + private String tone; + private int length; +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/ai/greeting/dto/ProfileDTO.java b/src/main/java/getjobs/modules/ai/greeting/dto/ProfileDTO.java new file mode 100644 index 00000000..221c128e --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/dto/ProfileDTO.java @@ -0,0 +1,66 @@ +package getjobs.modules.ai.greeting.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class ProfileDTO { + /** + * 角色/职位 + * 示例: "Java 后端开发/高级" + */ + private String role; + /** + * 工作年限 + * 示例: 8 + */ + private int years; + /** + * 从事的领域 + * 示例: ["电商", "支付"] + */ + private List domains; + /** + * 核心技术栈 + * 示例: ["Spring Boot","Spring Cloud Gateway","Redis","MySQL","MQ(Artemis/Kafka)","Docker"] + */ + @JsonProperty("core_stack") + private List coreStack; + /** + * 项目规模 + * qps_peak: 峰值QPS + * sla: 服务等级协议 + * 示例: {"qps_peak": "3k+", "sla": "99.99%"} + */ + private Map scale; // qps_peak/sla... + /** + * 主要成就 + * 示例: ["订单超时处理从5分钟降至30秒,稳定性至99.99%", "重构网关限流与灰度发布,峰值3k QPS 稳定"] + */ + private List achievements; + /** + * 个人强项/优势 + * 示例: ["性能调优","微服务治理","疑难排障"] + */ + private List strengths; + /** + * 待提升方面 + * 示例: ["前端薄弱,已在补 Vue3"] + */ + private List improvements; + /** + * 到岗时间及地点期望 + * 示例: "两周到岗,广州/可远程" + */ + private String availability; + /** + * 个人链接 + * github: github地址 + * portfolio: 个人作品集 + * 示例: {"github": "", "portfolio": ""} + */ + private Map links; +} diff --git a/src/main/java/getjobs/modules/ai/greeting/extract/KeywordExtractor.java b/src/main/java/getjobs/modules/ai/greeting/extract/KeywordExtractor.java new file mode 100644 index 00000000..a46be7ed --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/extract/KeywordExtractor.java @@ -0,0 +1,32 @@ +package getjobs.modules.ai.greeting.extract; + +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Component +public class KeywordExtractor { + private static final Set STOP = Set.of( + "我们","负责","以及","需要","能力","熟悉","相关","能够","经验","工作","优先", + "具有","进行","参与","良好","沟通","协作","等","等。","和" + ); + + public List extractTop(String jd, int k) { + if (jd == null || jd.isBlank()) return List.of(); + Map cnt = new HashMap<>(); + String[] tokens = jd.replaceAll("[^\\p{IsHan}A-Za-z0-9]+", " ") + .toLowerCase().split("\\s+"); + for (String w : tokens) { + if (w.length() < 2 || STOP.contains(w)) continue; + cnt.merge(w, 1, Integer::sum); + } + return cnt.entrySet().stream() + .sorted((a, b) -> b.getValue() - a.getValue()) + .limit(k) + .map(Map.Entry::getKey) + .toList(); + } +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/ai/greeting/llm/LlmClient.java b/src/main/java/getjobs/modules/ai/greeting/llm/LlmClient.java new file mode 100644 index 00000000..0cbbf358 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/llm/LlmClient.java @@ -0,0 +1,7 @@ +package getjobs.modules.ai.greeting.llm; + +import java.util.List; + +public interface LlmClient { + String chat(List messages); +} diff --git a/src/main/java/getjobs/modules/ai/greeting/llm/LlmMessage.java b/src/main/java/getjobs/modules/ai/greeting/llm/LlmMessage.java new file mode 100644 index 00000000..ffedce00 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/llm/LlmMessage.java @@ -0,0 +1,7 @@ +package getjobs.modules.ai.greeting.llm; + +public record LlmMessage(String role, String content) { + public static LlmMessage system(String c){ return new LlmMessage("system", c); } + public static LlmMessage user(String c){ return new LlmMessage("user", c); } + public static LlmMessage assistant(String c){ return new LlmMessage("assistant", c); } +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/ai/greeting/llm/SpringAiLlmClient.java b/src/main/java/getjobs/modules/ai/greeting/llm/SpringAiLlmClient.java new file mode 100644 index 00000000..62c2a4b0 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/llm/SpringAiLlmClient.java @@ -0,0 +1,35 @@ +package getjobs.modules.ai.greeting.llm; + +import getjobs.modules.ai.common.enums.AiPlatform; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class SpringAiLlmClient implements LlmClient { + + private final ChatModel chatModel; + + public SpringAiLlmClient(@Qualifier(AiPlatform.DEEPSEEK_BEAN_NAME) ChatModel chatModel) { + this.chatModel = chatModel; + } + + @Override + public String chat(List messages) { + var springMsgs = messages.stream().map(m -> switch (m.role()) { + case "system" -> new SystemMessage(m.content()); + case "assistant" -> new AssistantMessage(m.content()); + default -> new UserMessage(m.content()); + }).toList(); + + var resp = chatModel.call(new Prompt(springMsgs)); + return resp.getResult().getOutput().getText(); + } +} diff --git a/src/main/java/getjobs/modules/ai/greeting/service/GreetingService.java b/src/main/java/getjobs/modules/ai/greeting/service/GreetingService.java new file mode 100644 index 00000000..67f9fe3b --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/service/GreetingService.java @@ -0,0 +1,44 @@ +package getjobs.modules.ai.greeting.service; + +import getjobs.modules.ai.greeting.ab.ExperimentService; +import getjobs.modules.ai.greeting.assembler.PromptAssembler; +import getjobs.modules.ai.greeting.dto.GreetingRequest; +import getjobs.modules.ai.greeting.dto.GreetingResponse; +import getjobs.modules.ai.greeting.extract.KeywordExtractor; +import getjobs.modules.ai.greeting.llm.LlmClient; +import getjobs.modules.ai.greeting.validate.KeywordCoverageValidator; +import getjobs.modules.ai.greeting.validate.LengthValidator; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class GreetingService { + + private final PromptAssembler assembler; + private final LlmClient llm; + private final LengthValidator lengthValidator; + private final KeywordCoverageValidator keywordValidator; + private final ExperimentService experimentService; + private final KeywordExtractor extractor; + + public GreetingResponse generate(GreetingRequest req) { + String templateId = experimentService.pickVariant("greeting", req.getParams()); + var messages = assembler.assemble(templateId, req); + + String raw = llm.chat(messages).trim(); + + int max = req.getParams().getMaxChars(); + lengthValidator.check(raw, max); + + List kws = (req.getJdKeywords() == null || req.getJdKeywords().isEmpty()) + ? extractor.extractTop(req.getJdText(), 3) + : req.getJdKeywords(); + List used = keywordValidator.covered(raw, kws); + + // 这里可按需加一次“轻重写”逻辑;示例先直接返回 + return new GreetingResponse(raw, used, "贴合JD关键词", req.getParams().getTone(), raw.length()); + } +} diff --git a/src/main/java/getjobs/modules/ai/greeting/template/PromptRenderer.java b/src/main/java/getjobs/modules/ai/greeting/template/PromptRenderer.java new file mode 100644 index 00000000..51b507bc --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/template/PromptRenderer.java @@ -0,0 +1,15 @@ +package getjobs.modules.ai.greeting.template; + +import com.samskivert.mustache.Mustache; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@Component +public class PromptRenderer { + private final Mustache.Compiler compiler = Mustache.compiler(); + + public String render(String raw, Map vars) { + return compiler.defaultValue("").compile(raw).execute(vars); + } +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/ai/greeting/template/PromptSegmentType.java b/src/main/java/getjobs/modules/ai/greeting/template/PromptSegmentType.java new file mode 100644 index 00000000..b97ddcdd --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/template/PromptSegmentType.java @@ -0,0 +1,5 @@ +package getjobs.modules.ai.greeting.template; + +public enum PromptSegmentType { + SYSTEM, GUIDELINES, USER, FEW_SHOTS +} diff --git a/src/main/java/getjobs/modules/ai/greeting/template/PromptTemplate.java b/src/main/java/getjobs/modules/ai/greeting/template/PromptTemplate.java new file mode 100644 index 00000000..d4d6ce07 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/template/PromptTemplate.java @@ -0,0 +1,18 @@ +package getjobs.modules.ai.greeting.template; + +import lombok.Data; + +import java.util.List; + +@Data +public class PromptTemplate { + private String id; + private String description; + private List segments; + + @Data + public static class Segment { + private PromptSegmentType type; + private String content; + } +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/ai/greeting/template/TemplateRepository.java b/src/main/java/getjobs/modules/ai/greeting/template/TemplateRepository.java new file mode 100644 index 00000000..775b1a0f --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/template/TemplateRepository.java @@ -0,0 +1,25 @@ +package getjobs.modules.ai.greeting.template; + +import jakarta.annotation.PostConstruct; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class TemplateRepository { + private final Map cache = new ConcurrentHashMap<>(); + + @PostConstruct + public void load() throws IOException { + // 读取classpath: prompts/*.yml -> 缓存 + // 你可用 SnakeYAML 映射到 PromptTemplate + } + + public PromptTemplate get(String id) { + return Optional.ofNullable(cache.get(id)) + .orElseThrow(() -> new IllegalArgumentException("Template not found: " + id)); + } +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/ai/greeting/validate/JsonSchemaValidator.java b/src/main/java/getjobs/modules/ai/greeting/validate/JsonSchemaValidator.java new file mode 100644 index 00000000..1fb1a3f4 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/validate/JsonSchemaValidator.java @@ -0,0 +1,24 @@ +package getjobs.modules.ai.greeting.validate; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.stereotype.Component; + +/** + * 极简占位:仅校验 JSON 可解析与包含关键字段。 + * 若要正式 Schema 校验,可引入 networknt/json-schema。 + */ +@Component +public class JsonSchemaValidator { + + private final ObjectMapper mapper = new ObjectMapper(); + + public void checkBasic(String json) { + try { + JsonNode n = mapper.readTree(json); + if (!n.has("greeting")) throw new IllegalArgumentException("missing 'greeting'"); + } catch (Exception e) { + throw new IllegalArgumentException("invalid json: " + e.getMessage(), e); + } + } +} diff --git a/src/main/java/getjobs/modules/ai/greeting/validate/KeywordCoverageValidator.java b/src/main/java/getjobs/modules/ai/greeting/validate/KeywordCoverageValidator.java new file mode 100644 index 00000000..9333de21 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/validate/KeywordCoverageValidator.java @@ -0,0 +1,17 @@ +package getjobs.modules.ai.greeting.validate; + +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class KeywordCoverageValidator { + public List covered(String text, List keywords) { + List used = new ArrayList<>(); + for (String k : keywords) { + if (k != null && !k.isBlank() && text.contains(k)) used.add(k); + } + return used; + } +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/ai/greeting/validate/LengthValidator.java b/src/main/java/getjobs/modules/ai/greeting/validate/LengthValidator.java new file mode 100644 index 00000000..c6d7b60f --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/validate/LengthValidator.java @@ -0,0 +1,12 @@ +package getjobs.modules.ai.greeting.validate; + +import org.springframework.stereotype.Component; + +@Component +public class LengthValidator { + public void check(String text, int maxChars) { + if (text.codePointCount(0, text.length()) > maxChars) { + throw new IllegalArgumentException("Exceed max chars"); + } + } +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/ai/greeting/validate/OutputContract.java b/src/main/java/getjobs/modules/ai/greeting/validate/OutputContract.java new file mode 100644 index 00000000..d7c83a7e --- /dev/null +++ b/src/main/java/getjobs/modules/ai/greeting/validate/OutputContract.java @@ -0,0 +1,13 @@ +package getjobs.modules.ai.greeting.validate; + +import lombok.Data; + +import java.util.List; + +@Data +public class OutputContract { + private int maxChars; + private List mustCoverKeywords; // 至少覆盖2个 + private boolean jsonMode; // 若需要返回JSON结构 + private String jsonSchema; // networknt/json-schema 可校验 +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/ai/job/dto/JobMatchRequest.java b/src/main/java/getjobs/modules/ai/job/dto/JobMatchRequest.java new file mode 100644 index 00000000..a6ace9a2 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/job/dto/JobMatchRequest.java @@ -0,0 +1,11 @@ +package getjobs.modules.ai.job.dto; + +import getjobs.modules.ai.common.enums.AiPlatform; +import lombok.Data; + +@Data +public class JobMatchRequest { + private String myJd; + private String jobDescription; + private AiPlatform platform; +} diff --git a/src/main/java/getjobs/modules/ai/job/dto/PromptRenderRequest.java b/src/main/java/getjobs/modules/ai/job/dto/PromptRenderRequest.java new file mode 100644 index 00000000..5ed1900a --- /dev/null +++ b/src/main/java/getjobs/modules/ai/job/dto/PromptRenderRequest.java @@ -0,0 +1,10 @@ +package getjobs.modules.ai.job.dto; + +import lombok.Data; +import java.util.Map; + +@Data +public class PromptRenderRequest { + private String name; + private Map variables; +} diff --git a/src/main/java/getjobs/modules/ai/job/dto/PromptTemplateDto.java b/src/main/java/getjobs/modules/ai/job/dto/PromptTemplateDto.java new file mode 100644 index 00000000..5e9950a1 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/job/dto/PromptTemplateDto.java @@ -0,0 +1,14 @@ +package getjobs.modules.ai.job.dto; + +import getjobs.modules.ai.config.AiPromptProperties; +import lombok.Data; + +import java.util.Map; + +@Data +public class PromptTemplateDto { + private String name; + private String template; + private String description; + private Map placeholders; +} diff --git a/src/main/java/getjobs/modules/ai/job/dto/UserProfileRequest.java b/src/main/java/getjobs/modules/ai/job/dto/UserProfileRequest.java new file mode 100644 index 00000000..6abfa8b2 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/job/dto/UserProfileRequest.java @@ -0,0 +1,76 @@ +package getjobs.modules.ai.job.dto; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * 用户求职信息请求DTO + * + * @author getjobs + */ +@Data +public class UserProfileRequest { + + /** + * 职位角色 + */ + private String role; + + /** + * 工作年限 + */ + private Integer years; + + /** + * 领域列表 + */ + private List domains; + + /** + * 核心技术栈列表 + */ + private List coreStack; + + /** + * 规模指标(包含qps_peak、sla等) + */ + private Map scale; + + /** + * 成就列表 + */ + private List achievements; + + /** + * 优势列表 + */ + private List strengths; + + /** + * 改进项列表 + */ + private List improvements; + + /** + * 到岗时间 + */ + private String availability; + + /** + * 链接信息(包含github、portfolio等) + */ + private Map links; + + /** + * 岗位黑名单 + */ + private List positionBlacklist; + + /** + * 公司黑名单 + */ + private List companyBlacklist; +} + diff --git a/src/main/java/getjobs/modules/ai/job/prompt/JobPromptCatalog.java b/src/main/java/getjobs/modules/ai/job/prompt/JobPromptCatalog.java new file mode 100644 index 00000000..55a64ee0 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/job/prompt/JobPromptCatalog.java @@ -0,0 +1,85 @@ +package getjobs.modules.ai.job.prompt; + +import getjobs.modules.ai.config.AiPromptProperties; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.chat.prompt.PromptTemplate; +import org.springframework.stereotype.Component; + +import java.util.EnumMap; +import java.util.Map; +import java.util.Objects; + +/** + * 职位相关的提示词目录 + *

+ * 这是一个中央注册表,用于管理所有与职位相关的 AI 提示词。 + * 它在应用启动时从 {@link AiPromptProperties} 加载配置,并将提示词模板组织起来, + * 以便在需要时可以方便地按类型 ({@link JobPromptType}) 获取和渲染。 + *

+ */ +@Component +public class JobPromptCatalog { + + private final Map registry; + + /** + * 构造函数,用于初始化职位提示词目录。 + *

+ * 它会遍历 {@link JobPromptType} 中的所有枚举值,并从 {@link AiPromptProperties} + * 中查找对应的提示词定义。如果找不到任何一个配置,将抛出 {@link IllegalStateException}。 + * 找到配置后,会创建一个 {@link PromptTemplateHolder} 并将其注册到目录中。 + *

+ * + * @param properties AI 提示词的配置属性,通过依赖注入获取。 + * @throws IllegalStateException 如果某个 {@link JobPromptType} 缺少对应的配置。 + */ + public JobPromptCatalog(AiPromptProperties properties) { + Objects.requireNonNull(properties, "properties must not be null"); + this.registry = new EnumMap<>(JobPromptType.class); + + Map jobPrompts = properties.getJob(); + for (JobPromptType type : JobPromptType.values()) { + AiPromptProperties.PromptDefinition definition = jobPrompts.get(type.getConfigKey()); + if (definition == null) { + throw new IllegalStateException("Prompt configuration missing for key: " + type.getConfigKey()); + } + PromptTemplate template = new PromptTemplate(definition.getTemplate()); + PromptTemplateHolder holder = new PromptTemplateHolder( + definition.getDescription(), + definition.getPlaceholders(), + template + ); + registry.put(type, holder); + } + } + + /** + * 根据指定的提示词类型获取 {@link PromptTemplateHolder}。 + * + * @param type 职位提示词的类型 ({@link JobPromptType})。 + * @return 对应的 {@link PromptTemplateHolder} 实例。 + * @throws IllegalArgumentException 如果指定的类型没有注册任何提示词。 + */ + public PromptTemplateHolder get(JobPromptType type) { + PromptTemplateHolder holder = registry.get(type); + if (holder == null) { + throw new IllegalArgumentException("Prompt not registered for type: " + type); + } + return holder; + } + + /** + * 渲染指定类型的提示词模板。 + *

+ * 这是一个便捷方法,它首先获取指定类型的 {@link PromptTemplateHolder}, + * 然后使用提供的变量来渲染提示词模板,最终返回一个 {@link Prompt} 对象。 + *

+ * + * @param type 职位提示词的类型 ({@link JobPromptType})。 + * @param variables 用于填充模板占位符的变量 Map。 + * @return 渲染完成的 {@link Prompt} 对象,可直接用于与 AI 模型交互。 + */ + public Prompt render(JobPromptType type, Map variables) { + return get(type).render(variables); + } +} diff --git a/src/main/java/getjobs/modules/ai/job/prompt/JobPromptType.java b/src/main/java/getjobs/modules/ai/job/prompt/JobPromptType.java new file mode 100644 index 00000000..c24240f3 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/job/prompt/JobPromptType.java @@ -0,0 +1,36 @@ +package getjobs.modules.ai.job.prompt; + +/** + * 职位相关 AI 提示词的类型枚举 + *

+ * 这个枚举定义了所有可用的、与职位相关的 AI 提示词类型。 + * 每个枚举常量都关联一个配置键,该键用于从 {@link getjobs.modules.ai.config.AiPromptProperties} + * 中查找对应的提示词模板配置。 + *

+ */ +public enum JobPromptType { + + /** + * 用于匹配职位与简历的提示词类型。 + *

+ * 对应的配置键是 "match-position"。 + * 这个提示词通常用于分析一份简历和一份职位描述,并评估它们的匹配度。 + *

+ */ + MATCH_POSITION("match-position"); + + private final String configKey; + + JobPromptType(String configKey) { + this.configKey = configKey; + } + + /** + * 获取与此提示词类型关联的配置键。 + * + * @return 配置键字符串。 + */ + public String getConfigKey() { + return configKey; + } +} diff --git a/src/main/java/getjobs/modules/ai/job/prompt/PromptTemplateHolder.java b/src/main/java/getjobs/modules/ai/job/prompt/PromptTemplateHolder.java new file mode 100644 index 00000000..66fdda4d --- /dev/null +++ b/src/main/java/getjobs/modules/ai/job/prompt/PromptTemplateHolder.java @@ -0,0 +1,104 @@ +package getjobs.modules.ai.job.prompt; + +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.chat.prompt.PromptTemplate; + +import java.util.Collections; +import java.util.HashMap; +import getjobs.modules.ai.config.AiPromptProperties; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 提示词模板的持有者类 + *

+ * 这个类封装了一个 {@link PromptTemplate} 实例及其元数据,如描述和必需的变量(占位符)。 + * 它还提供了渲染提示词的逻辑,并在渲染前验证所有必需的变量是否都已提供。 + *

+ */ +public class PromptTemplateHolder { + + private final String description; + private final Set requiredVariables; + private final PromptTemplate template; + + /** + * 构造函数,用于创建一个新的 {@link PromptTemplateHolder} 实例。 + * + * @param description 提示词的描述。 + * @param placeholders 提示词模板中所有必需的占位符(变量)的可迭代集合。 + * @param template 要封装的 {@link PromptTemplate} 实例。 + */ + public PromptTemplateHolder(String description, Map placeholders, PromptTemplate template) { + this.description = description; + this.template = Objects.requireNonNull(template, "template must not be null"); + Set variables = new HashSet<>(); + if (placeholders != null) { + placeholders.forEach((key, value) -> { + if (value.isRequired()) { + variables.add(key); + } + }); + } + this.requiredVariables = Collections.unmodifiableSet(variables); + } + + /** + * 获取提示词的描述。 + * + * @return 提示词描述字符串。 + */ + public String getDescription() { + return description; + } + + /** + * 获取此提示词模板所有必需的变量名集合。 + * + * @return 一个不可修改的、包含所有必需变量名的 Set。 + */ + public Set getRequiredVariables() { + return requiredVariables; + } + + /** + * 获取封装的 {@link PromptTemplate} 实例。 + * + * @return {@link PromptTemplate} 实例。 + */ + public PromptTemplate getTemplate() { + return template; + } + + /** + * 使用提供的变量渲染提示词模板。 + *

+ * 在渲染之前,此方法会检查提供的 {@code variables} Map 是否包含了所有 + * {@link #requiredVariables} 中定义的必需变量。如果缺少任何必需变量, + * 将抛出 {@link IllegalArgumentException}。 + *

+ * + * @param variables 包含用于填充模板占位符的键值对的 Map。 + * @return 渲染完成的 {@link Prompt} 对象。 + * @throws IllegalArgumentException 如果任何必需的变量未在 {@code variables} Map 中提供。 + */ + public Prompt render(Map variables) { + Map context = new HashMap<>(); + if (variables != null) { + variables.forEach((key, value) -> context.put(key, value)); + } + + Set missing = requiredVariables.stream() + .filter(key -> !context.containsKey(key) || context.get(key) == null) + .collect(Collectors.toCollection(HashSet::new)); + + if (!missing.isEmpty()) { + throw new IllegalArgumentException("Missing prompt variables: " + String.join(", ", missing)); + } + + return template.create(context); + } +} diff --git a/src/main/java/getjobs/modules/ai/job/service/JobMatchAiService.java b/src/main/java/getjobs/modules/ai/job/service/JobMatchAiService.java new file mode 100644 index 00000000..59d7bd92 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/job/service/JobMatchAiService.java @@ -0,0 +1,94 @@ +package getjobs.modules.ai.job.service; + +import getjobs.modules.ai.common.enums.AiPlatform; +import getjobs.modules.ai.common.factory.ChatModelFactory; +import getjobs.modules.ai.job.prompt.JobPromptCatalog; +import getjobs.modules.ai.job.prompt.JobPromptType; +import getjobs.modules.ai.job.prompt.PromptTemplateHolder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 职位匹配 AI 服务 + *

+ * 该服务使用 AI 模型来评估候选人的简历(或个人简介)与职位描述(JD)的匹配度。 + * 它通过 {@link JobPromptCatalog} 获取预定义的提示词模板,填充具体内容后, + * 调用 AI 模型并解析其返回结果,最终得出一个布尔值(匹配或不匹配)。 + *

+ */ +@Slf4j +@Service +public class JobMatchAiService { + + private static final Pattern BOOLEAN_PATTERN = Pattern.compile("\\b(true|false)\\b", Pattern.CASE_INSENSITIVE); + + private final ChatModelFactory chatModelFactory; + private final JobPromptCatalog jobPromptCatalog; + + /** + * 构造函数,注入所需的 {@link ChatModelFactory} 和 {@link JobPromptCatalog}。 + * + * @param chatModelFactory 用于获取 AI 模型实例的工厂。 + * @param jobPromptCatalog 职位相关的提示词目录。 + */ + public JobMatchAiService(ChatModelFactory chatModelFactory, JobPromptCatalog jobPromptCatalog) { + this.chatModelFactory = chatModelFactory; + this.jobPromptCatalog = jobPromptCatalog; + } + + /** + * 判断候选人的简历是否与职位描述匹配。 + * + * @param myJd 候选人的简历或个人简介文本。 + * @param jobDescription 招聘网站上的职位描述文本。 + * @param platform AI 平台。 + * @return 如果 AI 模型认为匹配,则返回 {@code true};否则返回 {@code false}。 + */ + public boolean isMatch(String myJd, String jobDescription, AiPlatform platform) { + Map variables = new HashMap<>(); + variables.put("my_jd", myJd); + variables.put("jd", jobDescription); + + PromptTemplateHolder holder = jobPromptCatalog.get(JobPromptType.MATCH_POSITION); + Prompt prompt = holder.render(variables); + ChatModel chatModel = chatModelFactory.getChatModel(platform); + ChatResponse response = chatModel.call(prompt); + String content = response.getResult().getOutput().getText(); + + boolean result = parseBoolean(content); + log.debug("Job match evaluation result={}, raw response={}", result, content); + return result; + } + + /** + * 从 AI 模型的响应内容中解析出布尔值。 + *

+ * AI 模型被期望返回 "true" 或 "false" 的文本。此方法会尝试从返回的文本中 + * 提取这个布尔值。如果无法解析,将抛出 {@link IllegalStateException}。 + *

+ * + * @param content AI 模型返回的原始文本内容。 + * @return 解析出的布尔值。 + * @throws IllegalStateException 如果响应内容为 null 或无法从中解析出布尔值。 + */ + private boolean parseBoolean(String content) { + if (content == null) { + throw new IllegalStateException("AI response content is null"); + } + String normalized = content.trim(); + Matcher matcher = BOOLEAN_PATTERN.matcher(normalized); + if (matcher.find()) { + return Boolean.parseBoolean(matcher.group(1).toLowerCase(Locale.ROOT)); + } + throw new IllegalStateException("Unable to parse boolean result from AI response: " + content); + } +} diff --git a/src/main/java/getjobs/modules/ai/job/web/JobMatchAiController.java b/src/main/java/getjobs/modules/ai/job/web/JobMatchAiController.java new file mode 100644 index 00000000..486eb2e3 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/job/web/JobMatchAiController.java @@ -0,0 +1,24 @@ +package getjobs.modules.ai.job.web; + +import getjobs.modules.ai.job.dto.JobMatchRequest; +import getjobs.modules.ai.job.service.JobMatchAiService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/ai/job") +public class JobMatchAiController { + + private final JobMatchAiService jobMatchAiService; + + public JobMatchAiController(JobMatchAiService jobMatchAiService) { + this.jobMatchAiService = jobMatchAiService; + } + + @PostMapping("/match") + public boolean isMatch(@RequestBody JobMatchRequest request) { + return jobMatchAiService.isMatch(request.getMyJd(), request.getJobDescription(), request.getPlatform()); + } +} diff --git a/src/main/java/getjobs/modules/ai/service/AiPromptService.java b/src/main/java/getjobs/modules/ai/service/AiPromptService.java new file mode 100644 index 00000000..2e5f0ec9 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/service/AiPromptService.java @@ -0,0 +1,82 @@ +package getjobs.modules.ai.service; + +import getjobs.modules.ai.config.AiPromptProperties; +import getjobs.modules.ai.job.dto.PromptTemplateDto; +import getjobs.repository.AiPromptTemplateRepository; +import getjobs.repository.entity.AiPromptTemplate; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class AiPromptService { + + private final AiPromptTemplateRepository repository; + private final AiPromptProperties promptProperties; + + public Optional getPromptTemplate(String name) { + return repository.findByName(name); + } + + public List getAllPromptTemplates() { + return repository.findAll().stream() + .map(this::convertToDto) + .collect(Collectors.toList()); + } + + @Transactional + public String renderPrompt(String name, Map variables) { + AiPromptTemplate templateEntity = repository.findByName(name) + .orElseThrow(() -> new IllegalArgumentException("Cannot find prompt template with name: " + name)); + + Map placeholders = new HashMap<>(); + if (templateEntity.getPlaceholders() != null) { + placeholders.putAll(templateEntity.getPlaceholders()); + } + if (variables != null) { + placeholders.putAll(variables); + } + templateEntity.setPlaceholders(placeholders); + repository.save(templateEntity); + + String template = templateEntity.getTemplate(); + for (Map.Entry entry : placeholders.entrySet()) { + template = template.replace("{" + entry.getKey() + "}", entry.getValue()); + } + return template; + } + + public PromptTemplateDto convertToDto(AiPromptTemplate entity) { + PromptTemplateDto dto = new PromptTemplateDto(); + dto.setName(entity.getName()); + dto.setTemplate(entity.getTemplate()); + dto.setDescription(entity.getDescription()); + + String name = entity.getName(); + if (name != null) { + String[] parts = name.split("\\.", 2); + if (parts.length == 2) { + String category = parts[0]; + String key = parts[1]; + if ("job".equals(category)) { + AiPromptProperties.PromptDefinition definition = promptProperties.getJob().get(key); + if (definition != null) { + definition.getPlaceholders().forEach((placeholderKey, value) -> { + value.setValue(entity.getPlaceholders().get(placeholderKey)); + }); + dto.setPlaceholders(definition.getPlaceholders()); + } + } + } + } + + return dto; + } +} diff --git a/src/main/java/getjobs/modules/ai/service/PromptTemplateInitializer.java b/src/main/java/getjobs/modules/ai/service/PromptTemplateInitializer.java new file mode 100644 index 00000000..4b52df38 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/service/PromptTemplateInitializer.java @@ -0,0 +1,46 @@ +package getjobs.modules.ai.service; + +import getjobs.modules.ai.config.AiPromptProperties; +import getjobs.repository.AiPromptTemplateRepository; +import getjobs.repository.entity.AiPromptTemplate; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Component +@RequiredArgsConstructor +public class PromptTemplateInitializer implements CommandLineRunner { + + private final AiPromptProperties promptProperties; + private final AiPromptTemplateRepository repository; + + @Override + public void run(String... args) { + log.info("Initializing AI prompt templates from properties..."); + Map jobPrompts = promptProperties.getJob(); + + jobPrompts.forEach((key, definition) -> { + String name = "job." + key; + repository.findByName(name).ifPresentOrElse( + template -> log.info("Prompt template '{}' already exists, skipping initialization.", name), + () -> { + log.info("Creating new prompt template: {}", name); + AiPromptTemplate newTemplate = new AiPromptTemplate(); + newTemplate.setName(name); + newTemplate.setDescription(definition.getDescription()); + newTemplate.setTemplate(definition.getTemplate()); + Map placeholders = definition.getPlaceholders().keySet().stream() + .collect(Collectors.toMap(p -> p, p -> "")); + newTemplate.setPlaceholders(placeholders); + repository.save(newTemplate); + } + ); + }); + log.info("AI prompt templates initialization complete."); + } +} diff --git a/src/main/java/getjobs/modules/ai/service/UserProfileService.java b/src/main/java/getjobs/modules/ai/service/UserProfileService.java new file mode 100644 index 00000000..d7b16fdc --- /dev/null +++ b/src/main/java/getjobs/modules/ai/service/UserProfileService.java @@ -0,0 +1,51 @@ +package getjobs.modules.ai.service; + +import getjobs.modules.ai.job.dto.UserProfileRequest; +import getjobs.repository.UserProfileRepository; +import getjobs.repository.entity.UserProfile; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 用户求职信息服务 + * + * @author getjobs + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class UserProfileService { + + private final UserProfileRepository userProfileRepository; + + /** + * 保存用户求职信息 + * + * @param request 用户求职信息请求 + * @return 保存后的用户求职信息实体 + */ + @Transactional + public UserProfile saveUserProfile(UserProfileRequest request) { + UserProfile userProfile = new UserProfile(); + userProfile.setRole(request.getRole()); + userProfile.setYears(request.getYears()); + userProfile.setDomains(request.getDomains()); + userProfile.setCoreStack(request.getCoreStack()); + userProfile.setScale(request.getScale()); + userProfile.setAchievements(request.getAchievements()); + userProfile.setStrengths(request.getStrengths()); + userProfile.setImprovements(request.getImprovements()); + userProfile.setAvailability(request.getAvailability()); + userProfile.setLinks(request.getLinks()); + userProfile.setPositionBlacklist(request.getPositionBlacklist()); + userProfile.setCompanyBlacklist(request.getCompanyBlacklist()); + + UserProfile saved = userProfileRepository.save(userProfile); + log.info("保存用户求职信息成功,ID: {}, 角色: {}", saved.getId(), saved.getRole()); + + return saved; + } +} + diff --git a/src/main/java/getjobs/modules/ai/web/AiPromptController.java b/src/main/java/getjobs/modules/ai/web/AiPromptController.java new file mode 100644 index 00000000..6f64f8c4 --- /dev/null +++ b/src/main/java/getjobs/modules/ai/web/AiPromptController.java @@ -0,0 +1,57 @@ +package getjobs.modules.ai.web; + +import getjobs.modules.ai.job.dto.PromptRenderRequest; +import getjobs.modules.ai.job.dto.PromptTemplateDto; +import getjobs.modules.ai.job.dto.UserProfileRequest; +import getjobs.modules.ai.service.AiPromptService; +import getjobs.modules.ai.service.UserProfileService; +import getjobs.repository.entity.UserProfile; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/ai") +@RequiredArgsConstructor +public class AiPromptController { + + private final AiPromptService aiPromptService; + private final UserProfileService userProfileService; + + @GetMapping + public ResponseEntity> getAllPromptTemplates() { + return ResponseEntity.ok(aiPromptService.getAllPromptTemplates()); + } + + @GetMapping("/prompt/{name}") + public ResponseEntity getPromptTemplate(@PathVariable String name) { + return aiPromptService.getPromptTemplate(name) + .map(aiPromptService::convertToDto) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + @PutMapping("/prompt/render") + public ResponseEntity renderPrompt(@RequestBody PromptRenderRequest request) { + try { + String renderedPrompt = aiPromptService.renderPrompt(request.getName(), request.getVariables()); + return ResponseEntity.ok(renderedPrompt); + } catch (IllegalArgumentException e) { + return ResponseEntity.notFound().build(); + } + } + + /** + * 保存用户求职基本信息 + * + * @param request 用户求职信息请求 + * @return 保存后的用户求职信息 + */ + @PostMapping("/profile") + public ResponseEntity saveUserProfile(@RequestBody UserProfileRequest request) { + UserProfile savedProfile = userProfileService.saveUserProfile(request); + return ResponseEntity.ok(savedProfile); + } +} diff --git a/src/main/java/getjobs/modules/boss/BossElementLocators.java b/src/main/java/getjobs/modules/boss/BossElementLocators.java new file mode 100644 index 00000000..5deedae6 --- /dev/null +++ b/src/main/java/getjobs/modules/boss/BossElementLocators.java @@ -0,0 +1,232 @@ +package getjobs.modules.boss; + +import com.microsoft.playwright.Locator; +import com.microsoft.playwright.Page; +import lombok.extern.slf4j.Slf4j; + +/** + * Boss直聘网页元素定位器 + * 集中管理所有页面元素的定位表达式 + */ +@Slf4j +public class BossElementLocators { + // 主页相关元素 + public static final String LOGIN_BTN = "//li[@class='nav-figure']"; + public static final String LOGIN_SUCCESS_HEADER = "//*[@id=\"header\"]/div[1]/div[@class='user-nav']/ul/li[@class='nav-figure']"; + + // 登录状态判断相关元素 + // 未登录状态:存在"登录/注册"按钮 + public static final String HEADER_LOGIN_BTN = "//a[@class='btn btn-outline header-login-btn']"; + public static final String HEADER_LOGIN_BTN_ALT = "a.header-login-btn"; // CSS选择器备用 + + // 已登录状态:存在消息、简历等链接 + public static final String HEADER_MESSAGE_LINK = "//a[@ka='header-message']"; // 消息链接 + public static final String HEADER_RESUME_LINK = "//a[@ka='header-resume']"; // 简历链接 + public static final String NAV_FIGURE = "//li[@class='nav-figure']"; // 用户头像区域 + + /** + * 搜索结果页相关元素 + */ + // 用于判断岗位列表区块是否加载完成 + public static final String JOB_LIST_CONTAINER = "//div[@class='job-list-container']"; + // 定位job-list-container下的所有岗位卡片(用于循环点击) + public static final String JOB_LIST_CONTAINER_CARDS = "//div[@class='job-list-container']//li[@class='job-card-box']"; + + /** + * 岗位列表 + */ + // 定位所有岗位卡片,用于获取当前获取到的岗位总数 + public static final String JOB_LIST_SELECTOR = "ul.rec-job-list li.job-card-box"; + + // 职位详情页元素 + public static final String CHAT_BUTTON = "a.btn.btn-startchat"; + public static final String ERROR_CONTENT = "//div[@class='error-content']"; + + // 聊天相关元素 + public static final String DIALOG_TITLE = "//div[@class='dialog-title']"; + public static final String DIALOG_CLOSE = "//i[@class='icon-close']"; + public static final String CHAT_INPUT = "//div[@id='chat-input']"; + public static final String DIALOG_CONTAINER = "//div[@class='dialog-container']"; + public static final String SEND_BUTTON = "//button[@type='send']"; + public static final String IMAGE_UPLOAD = "//div[@aria-label='发送图片']//input[@type='file']"; + public static final String SCROLL_LOAD_MORE = "//div[contains(text(), '滚动加载更多')]"; + + // 消息列表页元素 + public static final String CHAT_LIST_ITEM = "//li[@role='listitem']"; + public static final String COMPANY_NAME_IN_CHAT = "//div[@class='title-box']/span[@class='name-box']//span[2]"; + public static final String LAST_MESSAGE = "//div[@class='gray last-msg']/span[@class='last-msg-text']"; + public static final String FINISHED_TEXT = "//div[@class='finished']"; + + public static final String DIALOG_CON = ".dialog-con"; + public static final String LOGIN_BTNS = "//div[@class='btns']"; + public static final String PAGE_HEADER = "//h1"; + public static final String ERROR_PAGE_LOGIN = "//a[@ka='403_login']"; + + /** + * 等待元素出现(使用默认超时时间) + * + * @param page Playwright页面对象 + * @param selector 元素选择器 + * @return 元素定位器 + * @throws RuntimeException 如果元素未在超时时间内出现 + */ + public static Locator waitForElement(Page page, String selector) { + return waitForElement(page, selector, 30000); // 默认30秒超时 + } + + /** + * 等待元素出现(指定超时时间) + * + * @param page Playwright页面对象 + * @param selector 元素选择器 + * @param timeout 超时时间(毫秒) + * @return 元素定位器 + * @throws RuntimeException 如果元素未在超时时间内出现 + */ + public static Locator waitForElement(Page page, String selector, int timeout) { + if (page == null) { + log.error("Page对象为空,无法等待元素"); + throw new IllegalArgumentException("Page对象不能为空"); + } + + try { + Locator locator = page.locator(selector); + locator.waitFor(new Locator.WaitForOptions().setTimeout(timeout)); + log.debug("元素已出现: {}", selector); + return locator; + } catch (Exception e) { + log.error("等待元素超时: {} - {}", selector, e.getMessage()); + throw e; + } + } + + /** + * 获取所有job-card-box元素并逐个点击 + * + * @param page Playwright页面对象 + * @param delayMillis 每次点击之间的延迟时间(毫秒) + * @return 成功点击的岗位数量 + */ + public static int clickAllJobCards(Page page, int delayMillis) { + if (page == null) { + log.error("Page对象为空,无法执行点击操作"); + return 0; + } + + try { + // 使用JOB_LIST_CONTAINER_CARDS定位器获取所有岗位卡片 + Locator jobCards = page.locator(JOB_LIST_CONTAINER_CARDS); + int cardCount = jobCards.count(); + + if (cardCount == 0) { + log.warn("未找到任何job-card-box元素"); + return 0; + } + + log.info("找到 {} 个岗位卡片,开始逐个点击", cardCount); + int successCount = 0; + + // 遍历所有岗位卡片并点击 + for (int i = 0; i < cardCount; i++) { + try { + Locator currentCard = jobCards.nth(i); + + // 确保元素可见且可点击 + if (currentCard.isVisible()) { + log.debug("正在点击第 {} 个岗位卡片", i + 1); + currentCard.click(); + successCount++; + + // 添加延迟,避免点击过快 + if (delayMillis > 0) { + Thread.sleep(delayMillis); + } + } else { + log.warn("第 {} 个岗位卡片不可见,跳过", i + 1); + } + } catch (Exception e) { + log.error("点击第 {} 个岗位卡片时发生错误: {}", i + 1, e.getMessage()); + } + } + + log.info("岗位卡片点击完成,成功点击 {} 个,总共 {} 个", successCount, cardCount); + return successCount; + + } catch (Exception e) { + log.error("执行岗位卡片点击操作时发生异常: {}", e.getMessage(), e); + return 0; + } + } + + /** + * 判断用户是否已登录 + * + * 判断逻辑: + * 1. 如果页面存在"登录/注册"按钮 -> 未登录 + * 2. 如果页面存在"消息"或"简历"链接 -> 已登录 + * 3. 如果都不存在或出现异常 -> 默认未登录 + * + * @param page Playwright页面对象 + * @return true表示已登录,false表示未登录 + */ + public static boolean isUserLoggedIn(Page page) { + try { + if (page == null) { + log.error("Page对象为空,无法检查登录状态"); + return false; + } + + log.debug("检查Boss直聘用户登录状态..."); + + try { + // 等待页面加载完成 + page.waitForLoadState(); + page.waitForTimeout(1500); + + // 方法1:检查是否存在"登录/注册"按钮(未登录特征) + Locator loginButton = page.locator(HEADER_LOGIN_BTN); + if (loginButton.count() > 0 && loginButton.isVisible()) { + log.debug("检测到'登录/注册'按钮,用户未登录"); + return false; + } + + // 方法2:检查是否存在"消息"链接(已登录特征) + Locator messageLink = page.locator(HEADER_MESSAGE_LINK); + if (messageLink.count() > 0 && messageLink.isVisible()) { + log.debug("检测到'消息'链接,用户已登录"); + return true; + } + + // 方法3:检查是否存在"简历"链接(已登录特征) + Locator resumeLink = page.locator(HEADER_RESUME_LINK); + if (resumeLink.count() > 0 && resumeLink.isVisible()) { + log.debug("检测到'简历'链接,用户已登录"); + return true; + } + + // 方法4:检查是否存在用户头像区域(已登录特征) + Locator navFigure = page.locator(NAV_FIGURE); + if (navFigure.count() > 0) { + // 进一步检查nav-figure中是否包含用户名(而非登录按钮) + String navText = navFigure.first().textContent(); + if (navText != null && !navText.contains("登录")) { + log.debug("检测到用户头像区域,用户已登录"); + return true; + } + } + + log.warn("无法明确判断登录状态,默认为未登录"); + return false; + + } catch (Exception e) { + log.debug("检查Boss直聘登录状态时发生异常: {}", e.getMessage()); + return false; + } + + } catch (Exception e) { + log.error("检查Boss直聘用户登录状态时发生异常", e); + return false; // 出现异常时,默认未登录 + } + } + +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/boss/dto/BossApiResponse.java b/src/main/java/getjobs/modules/boss/dto/BossApiResponse.java new file mode 100644 index 00000000..f5debc69 --- /dev/null +++ b/src/main/java/getjobs/modules/boss/dto/BossApiResponse.java @@ -0,0 +1,54 @@ +package getjobs.modules.boss.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +/** + * BOSS直聘API响应数据结构 + * + * @author getjobs + * @since v2.0.1 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class BossApiResponse implements Serializable { + + /** + * 响应码 + */ + private Integer code; + + /** + * 响应消息 + */ + private String message; + + /** + * 职位数据 + */ + private ZpData zpData; + + @JsonIgnoreProperties(ignoreUnknown = true) + @Data + public static class ZpData implements Serializable { + + /** + * 是否有更多数据 + */ + private Boolean hasMore; + + /** + * 职位列表 + */ + private List> jobList; + + /** + * 类型 + */ + private Integer type; + } +} diff --git a/src/main/java/getjobs/modules/boss/dto/JobDTO.java b/src/main/java/getjobs/modules/boss/dto/JobDTO.java new file mode 100644 index 00000000..58a00781 --- /dev/null +++ b/src/main/java/getjobs/modules/boss/dto/JobDTO.java @@ -0,0 +1,362 @@ +package getjobs.modules.boss.dto; + +import getjobs.common.enums.RecruitmentPlatformEnum; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * 职位数据传输对象 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Data +public class JobDTO implements Serializable { + + // ==================== 基础职位信息 ==================== + + /** + * 岗位链接 + */ + private String href; + + /** + * 岗位名称 + */ + private String jobName; + + /** + * 岗位地区 + */ + private String jobArea; + + /** + * 岗位信息 + */ + private String jobInfo; + + /** + * 岗位薪水 + */ + private String salary; + + /** + * 薪资描述 + */ + private String salaryDesc; + + /** + * 工作经验要求 + */ + private String jobExperience; + + /** + * 学历要求 + */ + private String jobDegree; + + /** + * 职位标签 + */ + private List jobLabels; + + /** + * 技能要求 + */ + private List skills; + + /** + * 职位描述 + */ + private String jobDescription; + + /** + * 职位要求 + */ + private String jobRequirements; + + // ==================== 公司信息 ==================== + + /** + * 公司名字 + */ + private String companyName; + + /** + * 公司信息 + */ + private String companyInfo; + + /** + * 公司行业 + */ + private String companyIndustry; + + /** + * 公司融资阶段 + */ + private String companyStage; + + /** + * 公司规模 + */ + private String companyScale; + + /** + * 公司Logo + */ + private String companyLogo; + + /** + * 公司标签 + */ + private String companyTag; + + // ==================== 工作地点信息 ==================== + + /** + * 工作城市 + */ + private String workCity; + + /** + * 工作区域 + */ + private String workArea; + + /** + * 商圈 + */ + private String businessDistrict; + + /** + * 经度 + */ + private BigDecimal longitude; + + /** + * 纬度 + */ + private BigDecimal latitude; + + // ==================== HR信息 ==================== + + /** + * HR名称 + */ + private String recruiter; + + /** + * HR姓名 + */ + private String hrName; + + /** + * HR职位 + */ + private String hrTitle; + + /** + * HR头像 + */ + private String hrAvatar; + + /** + * HR是否在线 + */ + private Boolean hrOnline; + + /** + * HR认证等级 + */ + private Integer hrCertLevel; + + /** + * HR 活跃时间/状态原文 + */ + private String hrActiveTime; + + // ==================== 系统信息 ==================== + + /** + * 数据来源平台 + */ + private String platform; + + /** + * 加密职位ID + */ + private String encryptJobId; + + /** + * 加密HR ID + */ + private String encryptHrId; + + /** + * 加密公司ID + */ + private String encryptCompanyId; + + /** + * 安全ID + */ + private String securityId; + + /** + * 职位状态 (0: 待处理, 1: 已处理, 2: 已忽略, 3: 已过滤) + */ + private Integer status = 0; + + /** + * 过滤原因说明 + */ + private String filterReason; + + /** + * 是否收藏 + */ + private Boolean isFavorite = false; + + /** + * 是否最优职位 + */ + private Boolean isOptimal; + + /** + * 是否代理职位 + */ + private Boolean isProxyJob; + + /** + * 代理类型 + */ + private Integer proxyType; + + /** + * 是否金猎手 + */ + private Boolean isGoldHunter; + + /** + * 是否联系过 + */ + private Boolean isContacted; + + /** + * 是否屏蔽 + */ + private Boolean isShielded; + + /** + * 职位有效性状态 + */ + private Integer jobValidStatus; + + /** + * 福利列表 + */ + private List welfareList; + + /** + * 图标标志列表 + */ + private List iconFlagList; + + /** + * 名称前图标 + */ + private List beforeNameIcons; + + /** + * 名称后图标 + */ + private List afterNameIcons; + + /** + * 图标文字 + */ + private String iconWord; + + /** + * 最少工作月数描述 + */ + private String leastMonthDesc; + + /** + * 每周工作天数描述 + */ + private String daysPerWeekDesc; + + /** + * 是否显示顶部位置 + */ + private Boolean showTopPosition; + + /** + * 是否海外职位 + */ + private Boolean isOutland; + + /** + * 匿名状态 + */ + private Integer anonymousStatus; + + /** + * 项目ID + */ + private Integer itemId; + + /** + * 期望ID + */ + private Long expectId; + + /** + * 城市编码 + */ + private Long cityCode; + + /** + * 行业编码 + */ + private Long industryCode; + + /** + * 职位类型 + */ + private Integer jobType; + + /** + * 是否ATS直接投递 + */ + private Boolean atsDirectPost; + + /** + * 搜索ID + */ + private String searchId; + + @Override + public String toString() { + return String.format("【%s, %s, %s, %s, %s, %s】", companyName, jobName, jobArea, salary, companyTag, recruiter); + } + + public String toString(RecruitmentPlatformEnum platform) { + if (platform == RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN) { + return String.format("【%s, %s, %s, %s, %s, %s, %s】", companyName, jobName, jobArea, companyTag, salary, + recruiter, href); + } + if (platform == RecruitmentPlatformEnum.BOSS_ZHIPIN) { + return String.format("【%s, %s, %s, %s, %s, %s】", companyName, jobName, jobArea, salary, companyTag, + recruiter); + } + return String.format("【%s, %s, %s, %s, %s, %s, %s】", companyName, jobName, jobArea, salary, companyTag, + recruiter, href); + } +} diff --git a/src/main/java/getjobs/modules/boss/service/BossTaskService.java b/src/main/java/getjobs/modules/boss/service/BossTaskService.java new file mode 100644 index 00000000..1d4c94a0 --- /dev/null +++ b/src/main/java/getjobs/modules/boss/service/BossTaskService.java @@ -0,0 +1,607 @@ +package getjobs.modules.boss.service; + +import getjobs.common.dto.ConfigDTO; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.boss.dto.JobDTO; +import getjobs.common.enums.JobStatusEnum; +import getjobs.repository.entity.JobEntity; +import getjobs.repository.JobRepository; +import getjobs.service.*; +import getjobs.modules.task.dto.TaskUpdatePayload; +import getjobs.modules.task.enums.TaskStage; +import getjobs.modules.task.enums.TaskStatus; +import getjobs.modules.task.event.TaskUpdateEvent; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +import jakarta.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Boss任务服务 - 将4个核心操作分离为独立的服务方法 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Slf4j +@Service +public class BossTaskService { + + + private final RecruitmentServiceFactory serviceFactory; + + private final JobService jobService; + + private final JobRepository jobRepository; + private final ApplicationEventPublisher eventPublisher; + + // 数据目录路径 + private String dataPath; + + public BossTaskService(RecruitmentServiceFactory serviceFactory, + JobService jobService, JobRepository jobRepository, JobFilterService jobFilterService, ApplicationEventPublisher eventPublisher) { + this.serviceFactory = serviceFactory; + this.jobService = jobService; + this.jobRepository = jobRepository; + this.eventPublisher = eventPublisher; + } + + @PostConstruct + public void init() { + try { + initializeDataFiles(); + } catch (IOException e) { + log.error("数据文件初始化失败", e); + } + } + + /** + * 1. 登录操作 + * + * @param config 配置信息 + * @return 登录结果 + */ + public LoginResult login(ConfigDTO config) { + publishTaskUpdate(TaskStage.LOGIN, TaskStatus.STARTED, 0, "开始登录"); + try { + log.info("开始执行登录操作"); + + // 获取Boss直聘服务 + RecruitmentService bossService = serviceFactory.getService(RecruitmentPlatformEnum.BOSS_ZHIPIN); + + // 执行登录 + boolean success = bossService.login(config); + + LoginResult result = new LoginResult(); + result.setSuccess(success); + result.setMessage(success ? "登录成功" : "登录失败"); + result.setTimestamp(new Date()); + + publishTaskUpdate(TaskStage.LOGIN, success ? TaskStatus.SUCCESS : TaskStatus.FAILURE, 0, result.getMessage()); + + log.info("登录操作完成,结果: {}", success ? "成功" : "失败"); + return result; + + } catch (Exception e) { + log.error("登录操作执行失败", e); + publishTaskUpdate(TaskStage.LOGIN, TaskStatus.FAILURE, 0, "登录异常: " + e.getMessage()); + + LoginResult result = new LoginResult(); + result.setSuccess(false); + result.setMessage("登录异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + /** + * 2. 采集操作 + * + * @param config 配置信息 + * @return 采集结果 + */ + public CollectResult collectJobs(ConfigDTO config) { + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.STARTED, 0, "开始采集"); + try { + log.info("开始执行岗位采集操作"); + + // 获取Boss直聘服务 + RecruitmentService bossService = serviceFactory.getService(RecruitmentPlatformEnum.BOSS_ZHIPIN); + + // 采集岗位 + List allJobDTOS = new ArrayList<>(); + + // 采集搜索岗位 + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.IN_PROGRESS, 0, "正在采集搜索岗位"); + List searchJobDTOS = bossService.collectJobs(config); + allJobDTOS.addAll(searchJobDTOS); + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.IN_PROGRESS, allJobDTOS.size(), "已采集 " + allJobDTOS.size() + " 个搜索岗位"); + + // 采集推荐岗位(如果配置开启) + if (config.getRecommendJobs()) { + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.IN_PROGRESS, allJobDTOS.size(), "正在采集推荐岗位"); + List recommendJobDTOS = bossService.collectRecommendJobs(config); + allJobDTOS.addAll(recommendJobDTOS); + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.IN_PROGRESS, allJobDTOS.size(), "已采集 " + allJobDTOS.size() + " 个岗位"); + } + + // 保存到数据库 + int savedCount = 0; + if (!allJobDTOS.isEmpty()) { + try { + savedCount = jobService.saveJobs(allJobDTOS, RecruitmentPlatformEnum.BOSS_ZHIPIN.name()); + log.info("成功保存 {} 个岗位到数据库", savedCount); + } catch (Exception e) { + log.error("保存岗位到数据库失败", e); + // 即使数据库保存失败,也不影响采集结果的返回 + } + } + + CollectResult result = new CollectResult(); + result.setJobCount(allJobDTOS.size()); + result.setJobs(allJobDTOS); + String message = String.format("成功采集到 %d 个岗位,保存到数据库 %d 个", allJobDTOS.size(), savedCount); + result.setMessage(message); + result.setTimestamp(new Date()); + + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.SUCCESS, allJobDTOS.size(), message); + + log.info("岗位采集操作完成,采集到 {} 个岗位,保存到数据库 {} 个", allJobDTOS.size(), savedCount); + return result; + + } catch (Exception e) { + log.error("岗位采集操作执行失败", e); + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.FAILURE, 0, "采集异常: " + e.getMessage()); + + CollectResult result = new CollectResult(); + result.setJobCount(0); + result.setJobs(new ArrayList<>()); + result.setMessage("采集异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + /** + * 3. 过滤操作 + * + * @param config 配置信息 + * @return 过滤结果 + */ + public FilterResult filterJobs(ConfigDTO config) { + publishTaskUpdate(TaskStage.FILTER, TaskStatus.STARTED, 0, "开始过滤"); + try { + log.info("开始执行岗位过滤操作"); + + RecruitmentService bossService = serviceFactory.getService(RecruitmentPlatformEnum.BOSS_ZHIPIN); + + // 直接从数据库查询所有职位实体 + List allJobEntities = jobService.findAllJobEntitiesByPlatform("BOSS直聘"); + if (allJobEntities == null || allJobEntities.isEmpty()) { + throw new IllegalArgumentException("数据库中未找到职位数据或职位数据为空"); + } + + // 执行过滤逻辑,获取过滤原因 + List filteredJobDTOS = new ArrayList<>(); + List filteredJobIds = new ArrayList<>(); + List filterReasons = new ArrayList<>(); + + List jobDTOS = new ArrayList<>(); + for (JobEntity entity : allJobEntities) { + JobDTO job = jobService.convertToDTO(entity); + jobDTOS.add(job); + } + List filterJobs = bossService.filterJobs(jobDTOS, config); + filterJobs.forEach(job -> { + String filterReason = job.getFilterReason(); + if (filterReason == null) { + // 通过过滤 + filteredJobDTOS.add(job); + } else { + // 被过滤,记录原因 + filteredJobIds.add(job.getEncryptJobId()); + filterReasons.add(filterReason); + } + }); + + // 批量更新被过滤的职位状态 + if (!filteredJobIds.isEmpty()) { + // 按过滤原因分组更新 + Map> reasonGroups = new HashMap<>(); + for (int i = 0; i < filteredJobIds.size(); i++) { + String reason = filterReasons.get(i); + String encryptJobId = filteredJobIds.get(i); + reasonGroups.computeIfAbsent(reason, k -> new ArrayList<>()).add(encryptJobId); + } + + for (Map.Entry> entry : reasonGroups.entrySet()) { + jobService.updateJobStatus(entry.getValue(), JobStatusEnum.FILTERED.getCode(), entry.getKey()); + } + } + + FilterResult result = new FilterResult(); + result.setOriginalCount(allJobEntities.size()); + result.setFilteredCount(filteredJobDTOS.size()); + result.setJobs(filteredJobDTOS); + String message = String.format("原始岗位 %d 个,过滤后剩余 %d 个,已过滤 %d 个", + allJobEntities.size(), filteredJobDTOS.size(), filteredJobIds.size()); + result.setMessage(message); + result.setTimestamp(new Date()); + + publishTaskUpdate(TaskStage.FILTER, TaskStatus.SUCCESS, filteredJobDTOS.size(), message); + + log.info("岗位过滤操作完成,原始 {} 个,过滤后 {} 个,已过滤 {} 个", + allJobEntities.size(), filteredJobDTOS.size(), filteredJobIds.size()); + return result; + + } catch (Exception e) { + log.error("岗位过滤操作执行失败", e); + publishTaskUpdate(TaskStage.FILTER, TaskStatus.FAILURE, 0, "过滤异常: " + e.getMessage()); + + FilterResult result = new FilterResult(); + result.setOriginalCount(0); + result.setFilteredCount(0); + result.setJobs(new ArrayList<>()); + result.setMessage("过滤异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + /** + * 4. 投递操作 + * + * @param config 配置信息 + * @param enableActualDelivery 是否启用实际投递 + * @return 投递结果 + */ + public DeliveryResult deliverJobs(ConfigDTO config, boolean enableActualDelivery) { + publishTaskUpdate(TaskStage.DELIVER, TaskStatus.STARTED, 0, "开始投递"); + try { + log.info("开始执行BOSS直聘岗位投递操作,实际投递: {}", enableActualDelivery); + + // 从数据库获取待处理状态的BOSS直聘平台岗位记录 + List jobEntities = jobRepository.findByStatusAndPlatform( + JobStatusEnum.PENDING.getCode(), + "BOSS直聘"); + if (jobEntities == null || jobEntities.isEmpty()) { + throw new IllegalArgumentException("未找到可投递的BOSS直聘岗位记录,数据库中没有待处理状态的BOSS直聘岗位"); + } + + // 转换为JobDTO + List filteredJobDTOS = jobEntities.stream() + .map(jobService::convertToDTO) + .collect(Collectors.toList()); + + int deliveredCount = 0; + + if (enableActualDelivery) { + // 获取Boss直聘服务 + RecruitmentService bossService = serviceFactory.getService(RecruitmentPlatformEnum.BOSS_ZHIPIN); + + // 执行实际投递 + deliveredCount = bossService.deliverJobs(filteredJobDTOS, config); + + // 保存数据 + bossService.saveData(dataPath); + + log.info("实际投递完成,成功投递 {} 个岗位", deliveredCount); + } else { + // 仅模拟投递 + deliveredCount = filteredJobDTOS.size(); + log.info("模拟投递完成,可投递岗位 {} 个", deliveredCount); + } + + DeliveryResult result = new DeliveryResult(); + result.setTotalCount(filteredJobDTOS.size()); + result.setDeliveredCount(deliveredCount); + result.setActualDelivery(enableActualDelivery); + String message = String.format("%s完成,处理 %d 个岗位", + enableActualDelivery ? "实际投递" : "模拟投递", deliveredCount); + result.setMessage(message); + result.setTimestamp(new Date()); + + // 显示岗位详情 + if (filteredJobDTOS.size() <= 10) { + result.setJobDetails(buildJobDetails(filteredJobDTOS)); + } + + publishTaskUpdate(TaskStage.DELIVER, TaskStatus.SUCCESS, deliveredCount, message); + + log.info("BOSS直聘岗位投递操作完成,处理 {} 个岗位", deliveredCount); + return result; + + } catch (Exception e) { + log.error("BOSS直聘岗位投递操作执行失败", e); + publishTaskUpdate(TaskStage.DELIVER, TaskStatus.FAILURE, 0, "投递异常: " + e.getMessage()); + + DeliveryResult result = new DeliveryResult(); + result.setTotalCount(0); + result.setDeliveredCount(0); + result.setActualDelivery(enableActualDelivery); + result.setMessage("投递异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + private void publishTaskUpdate(TaskStage stage, TaskStatus status, Integer count, String message) { + TaskUpdatePayload payload = TaskUpdatePayload.builder() + .platform(RecruitmentPlatformEnum.BOSS_ZHIPIN) + .stage(stage) + .status(status) + .count(count) + .message(message) + .build(); + eventPublisher.publishEvent(new TaskUpdateEvent(this, payload)); + } + + private List buildJobDetails(List jobDTOS) { + return jobDTOS.stream() + .map(job -> String.format("%s - %s | %s | %s", + job.getCompanyName(), + job.getJobName(), + job.getSalary() != null ? job.getSalary() : "薪资未知", + job.getJobArea() != null ? job.getJobArea() : "地区未知")) + .collect(Collectors.toList()); + } + + private void initializeDataFiles() throws IOException { + // 初始化工作目录为用户home目录下的getjobs目录 + String userHome = System.getProperty("user.home"); + dataPath = userHome + File.separator + "getjobs"; + + log.info("初始化工作目录: {}", dataPath); + + // 检查getjobs目录是否存在,不存在则创建 + File getJobsDir = new File(dataPath); + if (!getJobsDir.exists()) { + boolean created = getJobsDir.mkdirs(); + if (created) { + log.info("成功创建getjobs目录: {}", dataPath); + } else { + log.error("创建getjobs目录失败: {}", dataPath); + throw new IOException("无法创建getjobs目录: " + dataPath); + } + } else { + log.info("getjobs目录已存在: {}", dataPath); + } + + // 检查并创建data子目录 + File dataDir = new File(dataPath, "data"); + if (!dataDir.exists()) { + boolean created = dataDir.mkdirs(); + if (created) { + log.info("成功创建data子目录: {}", dataDir.getAbsolutePath()); + } else { + log.error("创建data子目录失败: {}", dataDir.getAbsolutePath()); + throw new IOException("无法创建data子目录: " + dataDir.getAbsolutePath()); + } + } else { + log.info("data子目录已存在: {}", dataDir.getAbsolutePath()); + } + + // 更新dataPath为实际的data目录路径 + dataPath = dataDir.getAbsolutePath(); + log.info("数据文件目录设置为: {}", dataPath); + } + + public static class LoginResult { + private String taskId; + private boolean success; + private String message; + private Date timestamp; + + // getters and setters + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + } + + public static class CollectResult { + private String taskId; + private int jobCount; + private List jobDTOS; + private String message; + private Date timestamp; + + // getters and setters + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public int getJobCount() { + return jobCount; + } + + public void setJobCount(int jobCount) { + this.jobCount = jobCount; + } + + public List getJobs() { + return jobDTOS; + } + + public void setJobs(List jobDTOS) { + this.jobDTOS = jobDTOS; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + } + + public static class FilterResult { + private String taskId; + private int originalCount; + private int filteredCount; + private List jobDTOS; + private String message; + private Date timestamp; + + // getters and setters + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public int getOriginalCount() { + return originalCount; + } + + public void setOriginalCount(int originalCount) { + this.originalCount = originalCount; + } + + public int getFilteredCount() { + return filteredCount; + } + + public void setFilteredCount(int filteredCount) { + this.filteredCount = filteredCount; + } + + public List getJobs() { + return jobDTOS; + } + + public void setJobs(List jobDTOS) { + this.jobDTOS = jobDTOS; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + } + + public static class DeliveryResult { + private String taskId; + private int totalCount; + private int deliveredCount; + private boolean actualDelivery; + private String message; + private Date timestamp; + private List jobDetails; + + // getters and setters + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public int getTotalCount() { + return totalCount; + } + + public void setTotalCount(int totalCount) { + this.totalCount = totalCount; + } + + public int getDeliveredCount() { + return deliveredCount; + } + + public void setDeliveredCount(int deliveredCount) { + this.deliveredCount = deliveredCount; + } + + public boolean isActualDelivery() { + return actualDelivery; + } + + public void setActualDelivery(boolean actualDelivery) { + this.actualDelivery = actualDelivery; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public List getJobDetails() { + return jobDetails; + } + + public void setJobDetails(List jobDetails) { + this.jobDetails = jobDetails; + } + } +} diff --git a/src/main/java/getjobs/modules/boss/service/impl/BossRecruitmentServiceImpl.java b/src/main/java/getjobs/modules/boss/service/impl/BossRecruitmentServiceImpl.java new file mode 100644 index 00000000..2c4f6ed1 --- /dev/null +++ b/src/main/java/getjobs/modules/boss/service/impl/BossRecruitmentServiceImpl.java @@ -0,0 +1,1059 @@ +package getjobs.modules.boss.service.impl; + +import com.github.openjson.JSONArray; +import com.github.openjson.JSONObject; +import com.microsoft.playwright.ElementHandle; +import com.microsoft.playwright.Locator; +import com.microsoft.playwright.Page; +import com.microsoft.playwright.options.Cookie; +import com.microsoft.playwright.options.WaitForSelectorState; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.common.service.PlaywrightService; +import getjobs.modules.boss.BossElementLocators; +import getjobs.common.dto.ConfigDTO; +import getjobs.modules.boss.dto.JobDTO; +import getjobs.common.enums.JobStatusEnum; +import getjobs.service.JobFilterService; +import getjobs.modules.boss.service.playwright.BossApiMonitorService; +import getjobs.repository.JobRepository; +import getjobs.repository.entity.ConfigEntity; +import getjobs.repository.entity.JobEntity; +import getjobs.service.ConfigService; +import getjobs.service.RecruitmentService; +import getjobs.utils.JobUtils; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import jakarta.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static getjobs.modules.boss.BossElementLocators.*; + +/** + * Boss直聘招聘服务实现类 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Slf4j +@Service +public class BossRecruitmentServiceImpl implements RecruitmentService { + + private static final String HOME_URL = RecruitmentPlatformEnum.BOSS_ZHIPIN.getHomeUrl(); + private static final String GEEK_JOB_URL = HOME_URL + "/web/geek/job?"; + private static final String GEEK_CHAT_URL = HOME_URL + "/web/geek/chat"; + + private final ConfigService configService; + private final BossApiMonitorService bossApiMonitorService; + private final JobRepository jobRepository; + private final JobFilterService jobFilterService; + private final PlaywrightService playwrightService; + + private Page page; + + public BossRecruitmentServiceImpl(ConfigService configService, BossApiMonitorService bossApiMonitorService, + JobRepository jobRepository, JobFilterService jobFilterService, + PlaywrightService playwrightService) { + this.configService = configService; + this.bossApiMonitorService = bossApiMonitorService; + this.jobRepository = jobRepository; + this.jobFilterService = jobFilterService; + this.playwrightService = playwrightService; + } + + @PostConstruct + public void init() { + this.page = playwrightService.getPage(RecruitmentPlatformEnum.BOSS_ZHIPIN); + } + + @Override + public RecruitmentPlatformEnum getPlatform() { + return RecruitmentPlatformEnum.BOSS_ZHIPIN; + } + + @Override + public boolean login(ConfigDTO config) { + log.info("开始Boss直聘登录检查"); + + try { + // 使用Playwright打开网站 + page.navigate(HOME_URL); + + // 检查并加载Cookie + String cookieData = getCookieFromConfig(); + if (isCookieValid(cookieData)) { + loadCookiesFromString(cookieData); + page.reload(); + TimeUnit.SECONDS.sleep(2); + } + + // 检查是否需要登录 + if (isLoginRequired()) { + log.info("Cookie失效,开始扫码登录"); + return scanLogin(); + } else { + log.info("Boss直聘已登录"); + return true; + } + } catch (Exception e) { + log.error("Boss直聘登录失败", e); + return false; + } + } + + @Override + public List collectJobs(ConfigDTO config) { + log.info("开始Boss直聘岗位采集"); + List allJobDTOS = new ArrayList<>(); + + // 记录采集开始时间,用于统计新增岗位数量 + LocalDateTime collectionStartTime = LocalDateTime.now(); + + try { + // 按城市和关键词搜索岗位 + for (String cityCode : config.getCityCodeCodes()) { + for (String keyword : config.getKeywordsList()) { + collectJobsByCity(cityCode, keyword, config); + // 不再依赖返回的空集合,岗位数据由监控服务自动入库 + } + } + + // 等待一段时间确保所有API响应都被处理完毕 + try { + Thread.sleep(3000); // 等待3秒 + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + // 统计采集期间新增的岗位数量 + LocalDateTime collectionEndTime = LocalDateTime.now(); + long collectedJobCount = jobRepository.countByPlatformAndCreatedAtBetween( + "boss", collectionStartTime, collectionEndTime); + + log.info("Boss直聘岗位采集完成,共采集{}个岗位", collectedJobCount); + + // 返回空集合,实际岗位数据已通过监控服务入库 + return allJobDTOS; + } catch (Exception e) { + log.error("Boss直聘岗位采集失败", e); + return allJobDTOS; + } + } + + @Override + public List collectRecommendJobs(ConfigDTO config) { + log.info("开始Boss直聘推荐岗位采集"); + List recommendJobDTOS = new ArrayList<>(); + + // 记录采集开始时间,用于统计新增岗位数量 + LocalDateTime collectionStartTime = LocalDateTime.now(); + + try { + // 设置推荐岗位接口监听器 + bossApiMonitorService.startMonitoring(); + + String cookieData = getCookieFromConfig(); + if (isCookieValid(cookieData)) { + loadCookiesFromString(cookieData); + } + page.navigate(GEEK_JOB_URL); + + // 等待页面加载 + page.waitForLoadState(); + + // 等待元素出现,最多等待10秒 + page.waitForSelector("a.expect-item", new Page.WaitForSelectorOptions().setTimeout(10000.0)); + + // 获取a标签且class是expect-item的元素 + ElementHandle activeElement = page.querySelector("a.expect-item"); + + if (activeElement != null) { + log.debug("找到推荐岗位入口,准备点击"); + activeElement.click(); + page.waitForLoadState(); + + if (isJobsPresent()) { + // 滚动加载推荐岗位 + int totalJobs = loadJobsWithScroll(page, "推荐岗位"); + log.info("推荐岗位加载,总计: {}", totalJobs); + } + } + + // 等待一段时间确保所有API响应都被处理完毕 + try { + Thread.sleep(3000); // 等待3秒 + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + // 统计采集期间新增的岗位数量 + LocalDateTime collectionEndTime = LocalDateTime.now(); + long collectedJobCount = jobRepository.countByPlatformAndCreatedAtBetween( + "boss", collectionStartTime, collectionEndTime); + + log.info("Boss直聘推荐岗位采集完成,共采集{}个岗位", collectedJobCount); + + } catch (Exception e) { + log.error("Boss直聘推荐岗位采集失败", e); + } + + // 返回空集合,实际岗位数据已通过监控服务入库 + return recommendJobDTOS; + } + + @Override + public List filterJobs(List jobDTOS, ConfigDTO config) { + // 从数据库获取boss平台的配置,不使用前端传递的config + ConfigEntity configEntity = configService.loadByPlatformType(RecruitmentPlatformEnum.BOSS_ZHIPIN.getPlatformCode()); + if (configEntity == null) { + log.warn("数据库中未找到boss平台配置,跳过过滤"); + return jobDTOS; + } + + // 将ConfigEntity转换为ConfigDTO + ConfigDTO dbConfig = convertConfigEntityToDTO(configEntity); + + return jobFilterService.filterJobs(jobDTOS, dbConfig); + } + + @Override + public int deliverJobs(List jobDTOS, ConfigDTO config) { + log.info("开始Boss直聘岗位投递,待投递岗位数量: {}", jobDTOS.size()); + int successCount = 0; + + for (JobDTO jobDTO : jobDTOS) { + try { + if (isDeliveryLimitReached()) { + log.warn("达到投递上限,停止投递"); + break; + } + + boolean delivered = deliverSingleJob(jobDTO, config); + if (delivered) { + successCount++; + log.info("投递成功: {} - {}", jobDTO.getCompanyName(), jobDTO.getJobName()); + updateJobStatus(jobDTO, JobStatusEnum.DELIVERED_SUCCESS.getCode(), null); + } else { + log.warn("投递失败: {} - {}", jobDTO.getCompanyName(), jobDTO.getJobName()); + updateJobStatus(jobDTO, JobStatusEnum.DELIVERED_FAILED.getCode(), "自动投递失败"); + } + + // 投递间隔 + TimeUnit.SECONDS.sleep(15); + + } catch (Exception e) { + log.error("投递岗位失败: {} - {}", jobDTO.getCompanyName(), jobDTO.getJobName(), e); + try { + updateJobStatus(jobDTO, JobStatusEnum.DELIVERED_FAILED.getCode(), "异常投递失败"); + } catch (Exception ignore) { + } + } + } + + log.info("Boss直聘岗位投递完成,成功投递: {}", successCount); + return successCount; + } + + /** + * 根据加密职位ID更新职位状态与原因 + */ + private void updateJobStatus(JobDTO jobDTO, int status, String reason) { + try { + if (jobDTO == null || jobDTO.getEncryptJobId() == null) { + return; + } + JobEntity entity = jobRepository.findByEncryptJobId(jobDTO.getEncryptJobId()); + if (entity == null) { + return; + } + entity.setStatus(status); + if (reason != null && !reason.isEmpty()) { + entity.setFilterReason(reason); + } + jobRepository.save(entity); + } catch (Exception e) { + log.debug("更新职位状态失败: {} - {} - {}", jobDTO.getCompanyName(), jobDTO.getJobName(), e.getMessage()); + } + } + + @Override + public boolean isDeliveryLimitReached() { + try { + TimeUnit.SECONDS.sleep(1); + Locator dialogElement = page.locator(DIALOG_CON); + if (dialogElement.isVisible(new Locator.IsVisibleOptions().setTimeout(2000.0))) { + String text = dialogElement.textContent(); + boolean isLimit = text.contains("已达上限"); + if (isLimit) { + log.warn("投递限制:今日投递已达上限"); + } + return isLimit; + } + return false; + } catch (Exception e) { + return false; + } + } + + @Override + public void saveData(String dataPath) { + updateBlacklistFromChat(); + log.info("保存Boss直聘黑名单数据: {}", dataPath); + } + + // ==================== 私有辅助方法 ==================== + + /** + * 将ConfigEntity转换为ConfigDTO + * 使用反射创建ConfigDTO实例,因为构造函数是私有的 + */ + private ConfigDTO convertConfigEntityToDTO(ConfigEntity entity) { + try { + // 通过反射创建ConfigDTO实例 + java.lang.reflect.Constructor constructor = ConfigDTO.class.getDeclaredConstructor(); + constructor.setAccessible(true); + ConfigDTO dto = constructor.newInstance(); + + // 基础字段映射 + dto.setSayHi(entity.getSayHi()); + dto.setEnableAIJobMatchDetection(entity.getEnableAIJobMatchDetection()); + dto.setEnableAIGreeting(entity.getEnableAIGreeting()); + dto.setFilterDeadHR(entity.getFilterDeadHR()); + dto.setSendImgResume(entity.getSendImgResume()); + dto.setKeyFilter(entity.getKeyFilter()); + dto.setRecommendJobs(entity.getRecommendJobs()); + dto.setCheckStateOwned(entity.getCheckStateOwned()); + dto.setResumeImagePath(entity.getResumeImagePath()); + dto.setResumeContent(entity.getResumeContent()); + dto.setWaitTime(entity.getWaitTime()); + dto.setPlatformType(entity.getPlatformType()); + + // 列表字段转换为逗号分隔的字符串 + if (entity.getKeywords() != null) { + dto.setKeywords(String.join(",", entity.getKeywords())); + } + if (entity.getCityCode() != null) { + dto.setCityCode(String.join(",", entity.getCityCode())); + } + if (entity.getIndustry() != null) { + dto.setIndustry(String.join(",", entity.getIndustry())); + } + if (entity.getExperience() != null) { + dto.setExperience(String.join(",", entity.getExperience())); + } + if (entity.getDegree() != null) { + dto.setDegree(String.join(",", entity.getDegree())); + } + if (entity.getScale() != null) { + dto.setScale(String.join(",", entity.getScale())); + } + if (entity.getStage() != null) { + dto.setStage(String.join(",", entity.getStage())); + } + if (entity.getDeadStatus() != null) { + dto.setDeadStatus(entity.getDeadStatus()); + } + + // 期望薪资处理 + if (entity.getExpectedSalary() != null && entity.getExpectedSalary().size() >= 2) { + dto.setMinSalary(entity.getExpectedSalary().get(0)); + dto.setMaxSalary(entity.getExpectedSalary().get(1)); + } + + // 其他字段 + dto.setCustomCityCode(entity.getCustomCityCode()); + dto.setJobType(entity.getJobType()); + dto.setSalary(entity.getSalary()); + dto.setExpectedPosition(entity.getExpectedPosition()); + + return dto; + } catch (Exception e) { + log.error("ConfigEntity转换为ConfigDTO失败", e); + // 如果转换失败,返回ConfigDTO的单例实例作为备用 + return ConfigDTO.getInstance(); + } + } + + /** + * 按城市采集岗位 + */ + private void collectJobsByCity(String cityCode, String keyword, ConfigDTO config) { + String searchUrl = getSearchUrl(cityCode, config); + String encodedKeyword = URLEncoder.encode(keyword, StandardCharsets.UTF_8); + String url = searchUrl + "&query=" + encodedKeyword; + + log.info("开始采集,城市: {},关键词: {},URL: {}", cityCode, keyword, url); + + // 设置岗位搜索接口监听器 + bossApiMonitorService.startMonitoring(); + + String cookieData = getCookieFromConfig(); + if (isCookieValid(cookieData)) { + loadCookiesFromString(cookieData); + } + page.navigate(url); + + if (isJobsPresent()) { + try { + // 滚动加载更多岗位 + int totalJobs = loadJobsWithScroll(page, "搜索岗位"); + log.info("搜索岗位加载完成: {},关键词: {}", totalJobs, keyword); + } catch (Exception e) { + log.error("滚动加载岗位数据出错", e); + } + } + + // 点击所有岗位卡片以触发详情API调用,让监控服务获取更多岗位信息 + BossElementLocators.clickAllJobCards(page, 5000); + + log.info("城市: {},关键词: {} 的岗位采集操作完成,实际数据由监控服务自动入库", cityCode, keyword); + } + + /** + * 构建搜索URL + */ + private String getSearchUrl(String cityCode, ConfigDTO config) { + return GEEK_JOB_URL + + // 城市参数:指定搜索的城市代码 + JobUtils.appendParam("city", cityCode) + + // 职位类型参数:指定搜索的职位类型代码(如:全职、兼职、实习等) + JobUtils.appendParam("jobType", config.getJobType()) + + // 薪资参数:指定期望薪资范围代码 + JobUtils.appendParam("salary", config.getSalary()) + + // 工作经验参数:指定工作经验要求代码列表(如:1-3年、3-5年等) + JobUtils.appendListParam("experience", config.getExperienceCodes()) + + // 学历要求参数:指定学历要求代码列表(如:本科、硕士、博士等) + JobUtils.appendListParam("degree", config.getDegreeCodes()) + + // 公司规模参数:指定公司规模代码列表(如:20-99人、100-499人等) + JobUtils.appendListParam("scale", config.getScaleCodes()) + + // 行业参数:指定行业类型代码列表(如:互联网、金融、教育等) + JobUtils.appendListParam("industry", config.getIndustryCodes()) + + // 融资阶段参数:指定公司融资阶段代码列表(如:天使轮、A轮、B轮等) + JobUtils.appendListParam("stage", config.getStageCodes()); + } + + /** + * 检查是否存在岗位 + */ + private boolean isJobsPresent() { + try { + BossElementLocators.waitForElement(page, JOB_LIST_CONTAINER); + return true; + } catch (Exception e) { + log.warn("岗位页面检查:页面无岗位元素"); + return false; + } + } + + /** + * 滚动加载岗位数据 + */ + private int loadJobsWithScroll(Page page, String jobType) { + int previousJobCount = 0; + int currentJobCount = 0; + int unchangedCount = 0; + + while (unchangedCount < 2) { + List jobCards = page.querySelectorAll(JOB_LIST_SELECTOR); + currentJobCount = jobCards.size(); + + log.debug("滚动加载中,当前岗位数: {}", currentJobCount); + + if (currentJobCount > previousJobCount) { + previousJobCount = currentJobCount; + unchangedCount = 0; + + safeEvaluateJavaScript(page, "window.scrollTo(0, document.body.scrollHeight)"); + log.debug("{}下拉页面加载更多...", jobType); + page.waitForTimeout(2000); + } else { + unchangedCount++; + if (unchangedCount < 2) { + log.debug("{}下拉后岗位数量未增加,再次尝试...", jobType); + safeEvaluateJavaScript(page, "window.scrollTo(0, document.body.scrollHeight)"); + page.waitForTimeout(2000); + } + } + } + + log.debug("{}加载完成,总计岗位数: {}", jobType, currentJobCount); + return currentJobCount; + } + + /** + * 投递单个岗位 + */ + @SneakyThrows + private boolean deliverSingleJob(JobDTO jobDTO, ConfigDTO config) { + // 在新标签页中打开岗位详情 + Page jobPage = page.context().newPage(); + + try { + jobPage.navigate(jobDTO.getHref()); + + // 等待聊天按钮出现 + Locator chatButton = jobPage.locator(CHAT_BUTTON); + if (!chatButton.nth(0).isVisible(new Locator.IsVisibleOptions().setTimeout(5000.0))) { + Locator errorElement = jobPage.locator(ERROR_CONTENT); + if (errorElement.isVisible() && errorElement.textContent().contains("异常访问")) { + return false; + } + } + + // 模拟用户浏览行为 + simulateUserBrowsingBehavior(jobPage); + + // 执行投递 + return performDelivery(jobPage, jobDTO, config); + + } finally { + jobPage.close(); + } + } + + /** + * 执行具体的投递操作 + */ + private boolean performDelivery(Page jobPage, JobDTO jobDTO, ConfigDTO config) { + try { + Locator chatBtn = jobPage.locator(CHAT_BUTTON).nth(0); + + // 等待并点击沟通按钮 + String waitTime = config.getWaitTime(); + int sleepTime = 10; + if (waitTime != null) { + try { + sleepTime = Integer.parseInt(waitTime); + } catch (NumberFormatException e) { + log.warn("等待时间配置错误,使用默认值10秒"); + } + } + TimeUnit.SECONDS.sleep(sleepTime); + + chatBtn.click(); + + if (isDeliveryLimitReached()) { + return false; + } + + // 处理可能出现的弹框 + handlePossibleDialog(jobPage, chatBtn); + + // 处理输入框和发送消息 + return handleChatInput(jobPage, jobDTO, config); + + } catch (Exception e) { + log.error("执行投递操作失败", e); + return false; + } + } + + /** + * 处理可能出现的弹框 + */ + private void handlePossibleDialog(Page jobPage, Locator chatBtn) { + try { + Locator dialogTitle = jobPage.locator(DIALOG_TITLE); + if (dialogTitle.nth(0).isVisible()) { + Locator closeBtn = jobPage.locator(DIALOG_CLOSE); + if (closeBtn.nth(0).isVisible()) { + closeBtn.nth(0).click(); + chatBtn.nth(0).click(); + } + } + } catch (Exception ignore) { + // 忽略弹框处理异常 + } + } + + /** + * 处理聊天输入框 + */ + private boolean handleChatInput(Page jobPage, JobDTO jobDTO, ConfigDTO config) { + try { + Locator input = jobPage.locator(CHAT_INPUT).nth(0); + + input.waitFor(new Locator.WaitForOptions() + .setState(WaitForSelectorState.VISIBLE) + .setTimeout(10000.0)); + + if (input.isVisible(new Locator.IsVisibleOptions().setTimeout(10000.0))) { + input.click(); + + Locator dialogElement = jobPage.locator(DIALOG_CONTAINER).nth(0); + if (dialogElement.isVisible() && "不匹配".equals(dialogElement.textContent())) { + return false; + } + + // 准备打招呼内容 + String greetingMessage = config.getSayHi().replaceAll("\\r|\\n", ""); + input.fill(greetingMessage); + + Locator sendBtn = jobPage.locator(SEND_BUTTON).nth(0); + if (sendBtn.isVisible(new Locator.IsVisibleOptions().setTimeout(5000.0))) { + sendBtn.click(); + TimeUnit.SECONDS.sleep(3); + + // 发送简历图片 + if (config.getSendImgResume()) { + sendResumeImage(jobPage, config); + } + + TimeUnit.SECONDS.sleep(3); + return true; + } + } + return false; + } catch (Exception e) { + log.error("处理聊天输入框失败", e); + return false; + } + } + + /** + * 发送简历图片 + */ + private boolean sendResumeImage(Page jobPage, ConfigDTO config) { + try { + String resumePath = config.getResumeImagePath(); + if (resumePath != null && !resumePath.isEmpty()) { + File imageFile = new File(resumePath); + if (imageFile.exists() && imageFile.isFile()) { + Locator fileInput = jobPage.locator(IMAGE_UPLOAD); + if (fileInput.isVisible()) { + fileInput.setInputFiles(new Path[]{Paths.get(imageFile.getPath())}); + Locator imageSendBtn = jobPage.locator(".image-uploader-btn"); + if (imageSendBtn.isVisible(new Locator.IsVisibleOptions().setTimeout(2000.0))) { + imageSendBtn.click(); + return true; + } + } + } else { + log.warn("简历图片发送:文件不存在 -> {}", resumePath); + } + } + } catch (Exception e) { + log.error("简历图片发送出错", e); + } + return false; + } + + /** + * 模拟用户浏览行为 + */ + private void simulateUserBrowsingBehavior(Page jobPage) { + try { + if (!isPageValid(jobPage)) { + log.warn("页面浏览模拟:页面状态无效,跳过滚动操作"); + return; + } + + safeEvaluateJavaScript(jobPage, "window.scrollBy(0, 300)"); + TimeUnit.SECONDS.sleep(1); + + if (!isPageValid(jobPage)) { + return; + } + + safeEvaluateJavaScript(jobPage, "window.scrollBy(0, 300)"); + TimeUnit.SECONDS.sleep(1); + + if (!isPageValid(jobPage)) { + return; + } + + safeEvaluateJavaScript(jobPage, "window.scrollTo(0, 0)"); + TimeUnit.SECONDS.sleep(1); + + } catch (Exception e) { + log.error("页面浏览行为模拟出错", e); + } + } + + /** + * 安全地执行JavaScript代码 + */ + private void safeEvaluateJavaScript(Page page, String script) { + try { + if (!isPageValid(page)) { + log.debug("页面状态无效,跳过JavaScript执行: {}", script); + return; + } + page.evaluate(script); + } catch (Exception e) { + if (e.getMessage() != null && e.getMessage().contains("Execution context was destroyed")) { + log.warn("JavaScript执行:页面执行上下文被销毁,可能发生了页面跳转"); + } else { + log.debug("JavaScript执行失败: {} - {}", script, e.getMessage()); + } + } + } + + /** + * 检查页面是否仍然有效 + */ + private boolean isPageValid(Page page) { + try { + String url = page.url(); + return url != null && url.contains("zhipin.com") && !url.contains("error"); + } catch (Exception e) { + log.debug("页面状态检查失败: {}", e.getMessage()); + return false; + } + } + + /** + * 更新黑名单数据从聊天记录 + */ + private void updateBlacklistFromChat() { + page.navigate(GEEK_CHAT_URL); + try { + TimeUnit.SECONDS.sleep(3); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + boolean shouldBreak = false; + int processedItems = 0; + int newBlackCompanies = 0; + + while (!shouldBreak) { + try { + Locator bottomElement = page.locator(FINISHED_TEXT); + if (bottomElement.isVisible() && "没有更多了".equals(bottomElement.textContent())) { + shouldBreak = true; + } + } catch (Exception ignore) { + } + + Locator items = page.locator(CHAT_LIST_ITEM); + int itemCount = items.count(); + + for (int i = 0; i < itemCount; i++) { + processedItems++; + try { + Locator companyElements = page.locator(COMPANY_NAME_IN_CHAT); + Locator messageElements = page.locator(LAST_MESSAGE); + + String companyName = null; + String message = null; + int retryCount = 0; + + while (retryCount < 2) { + try { + if (i < companyElements.count() && i < messageElements.count()) { + companyName = companyElements.nth(i).textContent(); + message = messageElements.nth(i).textContent(); + break; + } else { + log.debug("聊天记录元素索引超出范围"); + break; + } + } catch (Exception e) { + retryCount++; + if (retryCount >= 2) { + log.debug("获取聊天记录文本失败,跳过"); + break; + } + log.debug("页面元素已变更,重试获取聊天记录文本..."); + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + } + + if (companyName != null && message != null) { + boolean match = message.contains("不") || message.contains("感谢") || message.contains("但") + || message.contains("遗憾") || message.contains("需要本") || message.contains("对不"); + boolean nomatch = message.contains("不是") || message.contains("不生"); + if (match && !nomatch) { + // TODO 数据库中查询获取 + // if (!blackCompanies.stream().anyMatch(companyName::contains)) { + // companyName = companyName.replaceAll("\\.{3}", ""); + // if (companyName.matches(".*(\\p{IsHan}{2,}|[a-zA-Z]{4,}).*")) { + // blackCompanies.add(companyName); + // newBlackCompanies++; + // log.debug("新增黑名单公司:{} - 拒绝信息:{}", companyName, message); + // } + // } + } + } + } catch (Exception e) { + log.debug("处理聊天记录异常:{}", e.getMessage()); + } + } + + try { + Locator loadMoreElement = page.locator(SCROLL_LOAD_MORE); + if (loadMoreElement.isVisible()) { + loadMoreElement.scrollIntoViewIfNeeded(); + TimeUnit.SECONDS.sleep(1); + } else { + safeEvaluateJavaScript(page, "window.scrollTo(0, document.body.scrollHeight)"); + TimeUnit.SECONDS.sleep(1); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.debug("聊天记录滚动加载完成"); + break; + } catch (Exception e) { + log.debug("聊天记录滚动加载完成"); + break; + } + } + + log.info("聊天记录分析:处理{}条,新增黑名单公司:{}", processedItems, newBlackCompanies); + } + + /** + * 自定义JSON格式化 + */ + private String customJsonFormat(Map> data) { + StringBuilder sb = new StringBuilder(); + sb.append("{\n"); + for (Map.Entry> entry : data.entrySet()) { + sb.append(" \"").append(entry.getKey()).append("\": [\n"); + sb.append(entry.getValue().stream().map(s -> " \"" + s + "\"").collect(Collectors.joining(",\n"))); + sb.append("\n ],\n"); + } + sb.delete(sb.length() - 2, sb.length()); + sb.append("\n}"); + return sb.toString(); + } + + /** + * 检查Cookie是否有效 + */ + private boolean isCookieValid(String cookieData) { + if (cookieData == null || cookieData.trim().isEmpty()) { + return false; + } + try { + return !cookieData.equals("[]") && cookieData.contains("name"); + } catch (Exception e) { + log.error("Cookie数据验证出错", e); + return false; + } + } + + /** + * 检查是否需要登录 + */ + private boolean isLoginRequired() { + try { + Locator loginButton = page.locator(LOGIN_BTNS); + if (loginButton.isVisible() && loginButton.textContent().contains("登录")) { + return true; + } + + try { + Locator pageHeader = page.locator(PAGE_HEADER); + if (pageHeader.isVisible()) { + Locator errorPageLogin = page.locator(ERROR_PAGE_LOGIN); + if (errorPageLogin.isVisible()) { + errorPageLogin.click(); + return true; + } + } + } catch (Exception ex) { + log.debug("无访问异常页面"); + } + + return false; + } catch (Exception e) { + log.error("登录状态检查出错", e); + return true; + } + } + + /** + * 扫码登录 + */ + @SneakyThrows + private boolean scanLogin() { + page.navigate(HOME_URL + "/web/user/?ka=header-login"); + TimeUnit.SECONDS.sleep(5); + + try { + Locator loginBtn = page.locator(LOGIN_BTN); + if (loginBtn.isVisible() && !loginBtn.textContent().equals("登录")) { + log.info("检测到已登录状态"); + return true; + } + } catch (Exception ignored) { + } + + boolean login = false; + + boolean loginSuccess; + + while (!login) { + try { + loginSuccess = page.locator(LOGIN_SUCCESS_HEADER) + .isVisible(new Locator.IsVisibleOptions().setTimeout(2000.0)); + + if (loginSuccess) { + login = true; + log.info("登录成功,保存Cookie"); + } + } catch (Exception e) { + loginSuccess = page.locator(LOGIN_SUCCESS_HEADER) + .isVisible(new Locator.IsVisibleOptions().setTimeout(2000.0)); + if (loginSuccess) { + login = true; + log.info("登录成功,保存Cookie"); + } + } + } + + saveCookieToConfig(); + return true; + } + + /** + * 等待用户输入或超时 + */ + private boolean waitForUserInputOrTimeout(Scanner scanner) { + long end = System.currentTimeMillis() + 2000; + while (System.currentTimeMillis() < end) { + try { + if (System.in.available() > 0) { + scanner.nextLine(); + return true; + } + TimeUnit.SECONDS.sleep(1); + } catch (IOException e) { + // 忽略异常 + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + return false; + } + + /** + * 从配置实体中获取Cookie数据 + */ + private String getCookieFromConfig() { + try { + ConfigEntity config = configService + .loadByPlatformType(RecruitmentPlatformEnum.BOSS_ZHIPIN.getPlatformCode()); + return config != null ? config.getCookieData() : null; + } catch (Exception e) { + log.error("从配置中获取Cookie失败", e); + return null; + } + } + + /** + * 保存Cookie到配置实体 + */ + private void saveCookieToConfig() { + try { + ConfigEntity config = configService + .loadByPlatformType(RecruitmentPlatformEnum.BOSS_ZHIPIN.getPlatformCode()); + if (config == null) { + config = new ConfigEntity(); + } + + // 获取当前浏览器的Cookie + String cookieJson = getCurrentCookiesAsJson(); + config.setCookieData(cookieJson); + + config.setPlatformType(RecruitmentPlatformEnum.BOSS_ZHIPIN.getPlatformCode()); + configService.save(config); + log.info("Cookie已保存到配置实体"); + } catch (Exception e) { + log.error("保存Cookie到配置失败", e); + } + } + + /** + * 获取当前浏览器Cookie并转换为JSON字符串 + */ + private String getCurrentCookiesAsJson() { + try { + List cookies = page.context().cookies(); + JSONArray jsonArray = new JSONArray(); + + for (Cookie cookie : cookies) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", cookie.name); + jsonObject.put("value", cookie.value); + jsonObject.put("domain", cookie.domain); + jsonObject.put("path", cookie.path); + if (cookie.expires != null) { + jsonObject.put("expires", cookie.expires); + } + jsonObject.put("secure", cookie.secure); + jsonObject.put("httpOnly", cookie.httpOnly); + jsonArray.put(jsonObject); + } + + return jsonArray.toString(); + } catch (Exception e) { + log.error("获取当前Cookie失败", e); + return "[]"; + } + } + + /** + * 从JSON字符串加载Cookie到浏览器 + */ + private void loadCookiesFromString(String cookieData) { + try { + JSONArray jsonArray = new JSONArray(cookieData); + List cookies = new ArrayList<>(); + + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject jsonObject = jsonArray.getJSONObject(i); + + com.microsoft.playwright.options.Cookie cookie = new com.microsoft.playwright.options.Cookie( + jsonObject.getString("name"), + jsonObject.getString("value")); + + if (!jsonObject.isNull("domain")) { + cookie.domain = jsonObject.getString("domain"); + } + + if (!jsonObject.isNull("path")) { + cookie.path = jsonObject.getString("path"); + } + + if (!jsonObject.isNull("expires")) { + cookie.expires = jsonObject.getDouble("expires"); + } + + if (!jsonObject.isNull("secure")) { + cookie.secure = jsonObject.getBoolean("secure"); + } + + if (!jsonObject.isNull("httpOnly")) { + cookie.httpOnly = jsonObject.getBoolean("httpOnly"); + } + + cookies.add(cookie); + } + + page.context().addCookies(cookies); + log.info("已从配置加载Cookie,共{}个", cookies.size()); + } catch (Exception e) { + log.error("从配置加载Cookie失败", e); + } + } +} + diff --git a/src/main/java/getjobs/modules/boss/service/playwright/BossApiMonitorService.java b/src/main/java/getjobs/modules/boss/service/playwright/BossApiMonitorService.java new file mode 100644 index 00000000..01cc95aa --- /dev/null +++ b/src/main/java/getjobs/modules/boss/service/playwright/BossApiMonitorService.java @@ -0,0 +1,669 @@ +package getjobs.modules.boss.service.playwright; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.openjson.JSONObject; +import com.microsoft.playwright.*; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.common.service.PlaywrightService; +import getjobs.modules.boss.dto.BossApiResponse; +import getjobs.repository.entity.JobEntity; +import getjobs.repository.JobRepository; +import getjobs.utils.BossJobDataConverter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.PostConstruct; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; + +/** + * Boss接口监控服务 + * 负责监听和记录Boss直聘相关的API请求和响应 + * + * @author system + * @since 2024-01-01 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class BossApiMonitorService { + + private final JobRepository jobRepository; + private final BossJobDataConverter dataConverter; + private final PlaywrightService playwrightService; + private final ObjectMapper objectMapper = new ObjectMapper(); + + // 全局接口调用频率限制:记录最后一次调用时间 + private static volatile long lastCallTime = 0L; + + // 接口调用间隔:10秒 + private static final long CALL_INTERVAL_MS = 10000L; + + // 全局锁,确保同一时间只有一个请求在执行 + private static final ReentrantLock globalLock = new ReentrantLock(); + + /** + * 初始化监控服务 + * 设置岗位搜索和推荐岗位接口监听器 + */ + @PostConstruct + public void init() { + setupJobApiMonitor(); + } + + /** + * 设置岗位搜索和推荐岗位接口监听器 + * 监听所有相关的API响应并打印完整报文 + */ + public void setupJobApiMonitor() { + try { + BrowserContext ctx = playwrightService.getContext(RecruitmentPlatformEnum.BOSS_ZHIPIN); + Page page = playwrightService.getPage(RecruitmentPlatformEnum.BOSS_ZHIPIN); + + // 监听岗位搜索接口 + // setupJobSearchMonitor(ctx); + + // 监听岗位推荐接口 + // setupRecommendJobMonitor(ctx); + + // 监听所有岗位相关接口的响应 + setupResponseMonitor(page); + + log.info("Boss API监控服务初始化完成"); + } catch (Exception e) { + log.error("Boss API监控服务初始化失败: {}", e.getMessage(), e); + } + } + + /** + * 设置岗位搜索接口监控 + */ + private void setupJobSearchMonitor(BrowserContext ctx) { + ctx.route("**/wapi/zpgeek/search/joblist.json**", route -> { + + try { + Request req = route.request(); + log.info("=== 岗位搜索请求拦截 ==="); + log.info("请求方法: {}", req.method()); + log.info("请求URL: {}", req.url()); + log.info("请求头: {}", req.headers()); + log.info("请求参数: {}", req.url()); + log.info("========================"); + } catch (Exception e) { + log.error("route error", e); + } finally { + // 继续请求 + route.resume(); + } + + }); + } + + /** + * 设置推荐岗位接口监控 + */ + private void setupRecommendJobMonitor(BrowserContext ctx) { + ctx.route("**/wapi/zpgeek/pc/recommend/job/list.json**", route -> { + try { + Request req = route.request(); + String url = req.url(); + log.info("=== 推荐岗位请求拦截 ==="); + log.info("请求方法: {}", req.method()); + log.info("请求URL: {}", url); + log.info("请求头: {}", req.headers()); + log.info("请求参数: {}", url); + log.info("========================"); + } catch (Exception e) { + log.error("route error", e); + } finally { + // 继续请求 + route.resume(); + } + }); + } + + /** + * 设置响应监控 + */ + private void setupResponseMonitor(Page page) { + page.onResponse(res -> { + String url = res.url(); + + // 监听岗位搜索接口响应 + if (url.contains("/wapi/zpgeek/search/joblist.json")) { + handleJobSearchResponse(res); + } + // 监听推荐岗位接口响应 + else if (url.contains("/wapi/zpgeek/pc/recommend/job/list.json")) { + handleRecommendJobResponse(res); + } + else if(url.contains("/wapi/zpgeek/job/detail.json")){ + handleJobDetailResponse(res); + } + }); + } + + private void handleJobDetailResponse(Response res) { + log.info("=== 岗位详情响应拦截 ==="); + log.info("响应状态: {}", res.status()); + log.info("响应URL: {}", res.url()); + log.info("响应头: {}", res.headers()); + + try { + String body = res.text(); + log.info("响应体长度: {} 字符", body.length()); + log.info("响应体内容: {}", body); + + JSONObject jsonResponse = new JSONObject(body); + // 解析并保存职位数据 + parseAndUpdateJobDetail(jsonResponse); + + } catch (PlaywrightException e) { + log.error("读取响应体失败: {}", e.getMessage()); + } + log.info("=========================="); + + } + + /** + * 处理岗位搜索响应 + */ + private void handleJobSearchResponse(Response res) { + log.info("=== 岗位搜索响应拦截 ==="); + log.info("响应状态: {}", res.status()); + log.info("响应URL: {}", res.url()); + log.info("响应头: {}", res.headers()); + + try { + String body = res.text(); + log.info("响应体长度: {} 字符", body.length()); + log.info("响应体内容: {}", body); + + // 尝试解析JSON并美化输出 + formatJsonResponse(body); + + // 解析并保存职位数据 + parseAndSaveJobData(body, "岗位搜索"); + + } catch (PlaywrightException e) { + log.error("读取响应体失败: {}", e.getMessage()); + } + log.info("=========================="); + } + + /** + * 处理推荐岗位响应 + */ + private void handleRecommendJobResponse(com.microsoft.playwright.Response res) { + log.info("=== 推荐岗位响应拦截 ==="); + log.info("响应状态: {}", res.status()); + log.info("响应URL: {}", res.url()); + log.info("响应头: {}", res.headers()); + + try { + String body = res.text(); + log.info("响应体长度: {} 字符", body.length()); + log.info("响应体内容: {}", body); + + // 尝试解析JSON并美化输出 + formatJsonResponse(body); + + // 解析并保存职位数据 + parseAndSaveJobData(body, "推荐岗位"); + + } catch (PlaywrightException e) { + log.error("读取响应体失败: {}", e.getMessage()); + } + log.info("=========================="); + } + + /** + * 格式化JSON响应 + */ + private void formatJsonResponse(String body) { + try { + JSONObject jsonResponse = new JSONObject(body); + log.debug("格式化JSON响应: {}", jsonResponse.toString(2)); + } catch (Exception e) { + log.debug("响应体不是有效的JSON格式"); + } + } + + /** + * 解析并保存职位数据 + * + * @param body 响应体JSON字符串 + * @param source 数据来源描述 + */ + @Transactional + public void parseAndSaveJobData(String body, String source) { + try { + // 解析BOSS直聘API响应 + BossApiResponse response = objectMapper.readValue(body, BossApiResponse.class); + + if (response.getCode() != 0) { + log.warn("BOSS直聘API响应错误,code: {}, message: {}", response.getCode(), response.getMessage()); + return; + } + + if (response.getZpData() == null || response.getZpData().getJobList() == null) { + log.warn("BOSS直聘API响应中没有职位数据"); + return; + } + + List> jobList = response.getZpData().getJobList(); + log.info("从{}获取到 {} 个职位数据", source, jobList.size()); + + // 转换为JobEntity并保存 + List jobEntities = jobList.stream() + .map(dataConverter::convertToJobEntity) + .filter(entity -> entity != null) + .collect(Collectors.toList()); + + if (!jobEntities.isEmpty()) { + // 检查是否已存在相同的职位(基于encryptJobId) + List newJobs = jobEntities.stream() + .filter(entity -> !isJobExists(entity.getEncryptJobId())) + .collect(Collectors.toList()); + + if (!newJobs.isEmpty()) { + jobRepository.saveAll(newJobs); + log.info("成功保存 {} 个新职位到数据库,来源: {}", newJobs.size(), source); + } else { + log.info("所有职位都已存在,跳过保存,来源: {}", source); + } + } else { + log.warn("没有有效的职位数据可以保存,来源: {}", source); + } + + } catch (Exception e) { + log.error("解析并保存职位数据失败,来源: {}", source, e); + } + } + + /** + * 检查职位是否已存在 + * + * @param encryptJobId 加密职位ID + * @return 是否存在 + */ + private boolean isJobExists(String encryptJobId) { + if (encryptJobId == null || encryptJobId.trim().isEmpty()) { + return false; + } + + try { + return jobRepository.existsByEncryptJobId(encryptJobId); + } catch (Exception e) { + log.warn("检查职位是否存在时发生错误: {}", e.getMessage()); + return false; + } + } + + /** + * 手动启动监控(如果需要重新启动) + */ + public void startMonitoring() { + log.info("手动启动Boss API监控服务"); + setupJobApiMonitor(); + } + + /** + * 检查监控服务状态 + */ + public boolean isMonitoringActive() { + try { + BrowserContext ctx = playwrightService.getContext(RecruitmentPlatformEnum.BOSS_ZHIPIN); + return ctx != null; + } catch (Exception e) { + log.warn("检查监控服务状态失败: {}", e.getMessage()); + return false; + } + } + + + /** + * 解析职位明细数据并更新到数据库 + * + * @param jsonResponse 职位明细JSON响应 + */ + @Transactional + protected void parseAndUpdateJobDetail(JSONObject jsonResponse) { + String encryptId = jsonResponse.optJSONObject("zpData") + .optJSONObject("jobInfo") + .optString("encryptId"); + parseAndUpdateJobDetail(jsonResponse, encryptId); + } + + /** + * 解析职位明细数据并更新到数据库 + * + * @param jsonResponse 职位明细JSON响应 + * @param encryptJobId 加密JobId + */ + @Transactional + protected void parseAndUpdateJobDetail(JSONObject jsonResponse, String encryptJobId) { + try { + // 根据encryptJobId查找对应的JobEntity + JobEntity jobEntity = jobRepository.findByEncryptJobId(encryptJobId); + if (jobEntity == null) { + log.warn("未找到encryptJobId为 {} 的职位记录", encryptJobId); + return; + } + + // 获取zpData节点 + JSONObject zpData = jsonResponse.optJSONObject("zpData"); + if (zpData == null) { + log.warn("职位明细响应中没有zpData节点,encryptJobId: {}", encryptJobId); + return; + } + + // 解析jobInfo节点 + JSONObject jobInfo = zpData.optJSONObject("jobInfo"); + if (jobInfo != null) { + updateJobEntityFromJobInfo(jobEntity, jobInfo); + } + + // 解析bossInfo节点 + JSONObject bossInfo = zpData.optJSONObject("bossInfo"); + if (bossInfo != null) { + updateJobEntityFromBossInfo(jobEntity, bossInfo); + } + + // 解析brandComInfo节点 + JSONObject brandComInfo = zpData.optJSONObject("brandComInfo"); + if (brandComInfo != null) { + updateJobEntityFromBrandComInfo(jobEntity, brandComInfo); + } + + // 保存更新后的实体 + jobRepository.save(jobEntity); + log.info("成功更新职位明细信息,encryptJobId: {}, 职位: {}", encryptJobId, jobEntity.getJobTitle()); + + } catch (Exception e) { + log.error("解析并更新职位明细数据失败,encryptJobId: {}", encryptJobId, e); + } + } + + /** + * 从jobInfo节点更新JobEntity + * + * @param jobEntity JobEntity实例 + * @param jobInfo jobInfo JSON对象 + */ + private void updateJobEntityFromJobInfo(JobEntity jobEntity, JSONObject jobInfo) { + try { + // 基础职位信息 + if (jobInfo.has("encryptId") && !jobInfo.isNull("encryptId")) { + jobEntity.setEncryptJobDetailId(jobInfo.getString("encryptId")); + } + if (jobInfo.has("encryptUserId") && !jobInfo.isNull("encryptUserId")) { + jobEntity.setEncryptJobUserId(jobInfo.getString("encryptUserId")); + } + if (jobInfo.has("invalidStatus")) { + jobEntity.setJobInvalidStatus(jobInfo.getBoolean("invalidStatus")); + } + if (jobInfo.has("jobName") && !jobInfo.isNull("jobName")) { + jobEntity.setJobTitle(jobInfo.getString("jobName")); + } + if (jobInfo.has("position")) { + jobEntity.setJobPositionCode(jobInfo.getLong("position")); + } + if (jobInfo.has("positionName") && !jobInfo.isNull("positionName")) { + jobEntity.setJobPositionName(jobInfo.getString("positionName")); + } + if (jobInfo.has("location")) { + jobEntity.setJobLocationCode(jobInfo.getLong("location")); + } + if (jobInfo.has("locationName") && !jobInfo.isNull("locationName")) { + jobEntity.setJobLocationName(jobInfo.getString("locationName")); + } + if (jobInfo.has("locationUrl") && !jobInfo.isNull("locationUrl")) { + jobEntity.setJobLocationUrl(jobInfo.getString("locationUrl")); + } + if (jobInfo.has("experienceName") && !jobInfo.isNull("experienceName")) { + jobEntity.setJobExperienceName(jobInfo.getString("experienceName")); + } + if (jobInfo.has("degreeName") && !jobInfo.isNull("degreeName")) { + jobEntity.setJobDegreeName(jobInfo.getString("degreeName")); + } + if (jobInfo.has("jobType")) { + jobEntity.setJobDetailType(jobInfo.getInt("jobType")); + } + if (jobInfo.has("proxyJob")) { + jobEntity.setJobProxyJob(jobInfo.getInt("proxyJob")); + } + if (jobInfo.has("proxyType")) { + jobEntity.setJobProxyType(jobInfo.getInt("proxyType")); + } + if (jobInfo.has("salaryDesc") && !jobInfo.isNull("salaryDesc")) { + jobEntity.setSalaryDesc(jobInfo.getString("salaryDesc")); + } + if (jobInfo.has("payTypeDesc") && !jobInfo.isNull("payTypeDesc")) { + jobEntity.setJobPayTypeDesc(jobInfo.getString("payTypeDesc")); + } + if (jobInfo.has("postDescription") && !jobInfo.isNull("postDescription")) { + jobEntity.setJobPostDescription(jobInfo.getString("postDescription")); + } + if (jobInfo.has("encryptAddressId") && !jobInfo.isNull("encryptAddressId")) { + jobEntity.setEncryptAddressId(jobInfo.getString("encryptAddressId")); + } + if (jobInfo.has("address") && !jobInfo.isNull("address")) { + jobEntity.setJobAddress(jobInfo.getString("address")); + } + if (jobInfo.has("longitude")) { + jobEntity.setJobLongitude(new BigDecimal(jobInfo.getString("longitude"))); + } + if (jobInfo.has("latitude")) { + jobEntity.setJobLatitude(new BigDecimal(jobInfo.getString("latitude"))); + } + if (jobInfo.has("staticMapUrl") && !jobInfo.isNull("staticMapUrl")) { + jobEntity.setJobStaticMapUrl(jobInfo.getString("staticMapUrl")); + } + if (jobInfo.has("pcStaticMapUrl") && !jobInfo.isNull("pcStaticMapUrl")) { + jobEntity.setJobPcStaticMapUrl(jobInfo.getString("pcStaticMapUrl")); + } + if (jobInfo.has("baiduStaticMapUrl") && !jobInfo.isNull("baiduStaticMapUrl")) { + jobEntity.setJobBaiduStaticMapUrl(jobInfo.getString("baiduStaticMapUrl")); + } + if (jobInfo.has("baiduPcStaticMapUrl") && !jobInfo.isNull("baiduPcStaticMapUrl")) { + jobEntity.setJobBaiduPcStaticMapUrl(jobInfo.getString("baiduPcStaticMapUrl")); + } + if (jobInfo.has("showSkills")) { + jobEntity.setJobShowSkills(jobInfo.getJSONArray("showSkills").toString()); + } + if (jobInfo.has("anonymous")) { + jobEntity.setJobAnonymous(jobInfo.getInt("anonymous")); + } + if (jobInfo.has("jobStatusDesc") && !jobInfo.isNull("jobStatusDesc")) { + jobEntity.setJobStatusDesc(jobInfo.getString("jobStatusDesc")); + } + + log.debug("成功更新jobInfo信息到JobEntity"); + } catch (Exception e) { + log.error("更新jobInfo信息失败: {}", e.getMessage(), e); + } + } + + /** + * 从bossInfo节点更新JobEntity + * + * @param jobEntity JobEntity实例 + * @param bossInfo bossInfo JSON对象 + */ + private void updateJobEntityFromBossInfo(JobEntity jobEntity, JSONObject bossInfo) { + try { + if (bossInfo.has("name") && !bossInfo.isNull("name")) { + jobEntity.setBossName(bossInfo.getString("name")); + } + if (bossInfo.has("title") && !bossInfo.isNull("title")) { + jobEntity.setBossTitle(bossInfo.getString("title")); + } + if (bossInfo.has("tiny") && !bossInfo.isNull("tiny")) { + jobEntity.setBossTiny(bossInfo.getString("tiny")); + } + if (bossInfo.has("large") && !bossInfo.isNull("large")) { + jobEntity.setBossLarge(bossInfo.getString("large")); + } + if (bossInfo.has("activeTimeDesc") && !bossInfo.isNull("activeTimeDesc")) { + jobEntity.setBossActiveTimeDesc(bossInfo.getString("activeTimeDesc")); + } + if (bossInfo.has("bossOnline")) { + jobEntity.setBossOnline(bossInfo.getBoolean("bossOnline")); + } + if (bossInfo.has("brandName") && !bossInfo.isNull("brandName")) { + jobEntity.setBossBrandName(bossInfo.getString("brandName")); + } + if (bossInfo.has("bossSource")) { + jobEntity.setBossSource(bossInfo.getInt("bossSource")); + } + if (bossInfo.has("certificated")) { + jobEntity.setBossCertificated(bossInfo.getBoolean("certificated")); + } + if (bossInfo.has("tagIconUrl") && !bossInfo.isNull("tagIconUrl")) { + jobEntity.setBossTagIconUrl(bossInfo.getString("tagIconUrl")); + } + if (bossInfo.has("avatarStickerUrl") && !bossInfo.isNull("avatarStickerUrl")) { + jobEntity.setBossAvatarStickerUrl(bossInfo.getString("avatarStickerUrl")); + } + + log.debug("成功更新bossInfo信息到JobEntity"); + } catch (Exception e) { + log.error("更新bossInfo信息失败: {}", e.getMessage(), e); + } + } + + /** + * 从brandComInfo节点更新JobEntity + * + * @param jobEntity JobEntity实例 + * @param brandComInfo brandComInfo JSON对象 + */ + private void updateJobEntityFromBrandComInfo(JobEntity jobEntity, JSONObject brandComInfo) { + try { + if (brandComInfo.has("encryptBrandId") && !brandComInfo.isNull("encryptBrandId")) { + jobEntity.setEncryptBrandId(brandComInfo.getString("encryptBrandId")); + } + if (brandComInfo.has("brandName") && !brandComInfo.isNull("brandName")) { + jobEntity.setBrandName(brandComInfo.getString("brandName")); + } + if (brandComInfo.has("logo") && !brandComInfo.isNull("logo")) { + jobEntity.setBrandLogo(brandComInfo.getString("logo")); + } + if (brandComInfo.has("stage")) { + jobEntity.setBrandStage(brandComInfo.getLong("stage")); + } + if (brandComInfo.has("stageName") && !brandComInfo.isNull("stageName")) { + jobEntity.setBrandStageName(brandComInfo.getString("stageName")); + } + if (brandComInfo.has("scale")) { + jobEntity.setBrandScale(brandComInfo.getLong("scale")); + } + if (brandComInfo.has("scaleName") && !brandComInfo.isNull("scaleName")) { + jobEntity.setBrandScaleName(brandComInfo.getString("scaleName")); + } + if (brandComInfo.has("industry")) { + jobEntity.setBrandIndustry(brandComInfo.getLong("industry")); + } + if (brandComInfo.has("industryName") && !brandComInfo.isNull("industryName")) { + jobEntity.setBrandIndustryName(brandComInfo.getString("industryName")); + } + if (brandComInfo.has("introduce") && !brandComInfo.isNull("introduce")) { + jobEntity.setBrandIntroduce(brandComInfo.getString("introduce")); + } + if (brandComInfo.has("labels")) { + jobEntity.setBrandLabels(brandComInfo.getJSONArray("labels").toString()); + } + if (brandComInfo.has("activeTime")) { + jobEntity.setBrandActiveTime(brandComInfo.getLong("activeTime")); + } + if (brandComInfo.has("visibleBrandInfo")) { + jobEntity.setVisibleBrandInfo(brandComInfo.getBoolean("visibleBrandInfo")); + } + if (brandComInfo.has("focusBrand")) { + jobEntity.setFocusBrand(brandComInfo.getBoolean("focusBrand")); + } + if (brandComInfo.has("customerBrandName") && !brandComInfo.isNull("customerBrandName")) { + jobEntity.setCustomerBrandName(brandComInfo.getString("customerBrandName")); + } + if (brandComInfo.has("customerBrandStageName") && !brandComInfo.isNull("customerBrandStageName")) { + jobEntity.setCustomerBrandStageName(brandComInfo.getString("customerBrandStageName")); + } + + log.debug("成功更新brandComInfo信息到JobEntity"); + } catch (Exception e) { + log.error("更新brandComInfo信息失败: {}", e.getMessage(), e); + } + } + + /** + * 通过访问zhipin.com主页来刷新token + * 模拟正常用户访问行为,获取新的临时token + * + * @return 是否成功刷新token + */ + private boolean refreshTokenByVisitingZhipin() { + try { + log.info("开始访问zhipin.com刷新token"); + + // 获取Playwright上下文 + BrowserContext context = playwrightService.getContext(RecruitmentPlatformEnum.BOSS_ZHIPIN); + if (context == null) { + log.error("无法获取Playwright上下文,token刷新失败"); + return false; + } + + // 创建新页面访问zhipin.com + Page refreshPage = context.newPage(); + + try { + // 设置用户代理,模拟真实浏览器访问 + refreshPage.setExtraHTTPHeaders(Map.of( + "User-Agent", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Accept", + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8", + "Accept-Encoding", "gzip, deflate, br", + "Cache-Control", "no-cache", + "Pragma", "no-cache")); + + // 访问zhipin.com主页 + log.info("正在访问 https://www.zhipin.com/"); + refreshPage.navigate("https://www.zhipin.com/"); + + // 等待页面加载完成 + refreshPage.waitForLoadState(); + refreshPage.waitForTimeout(3000); // 等待3秒确保页面完全加载 + + // 模拟用户行为:滚动页面 + refreshPage.evaluate("window.scrollTo(0, document.body.scrollHeight / 2)"); + refreshPage.waitForTimeout(1000); + + // 模拟点击页面(不实际点击任何元素,只是模拟用户活动) + refreshPage.hover("body"); + refreshPage.waitForTimeout(1000); + + log.info("成功访问zhipin.com,token刷新完成"); + return true; + + } finally { + // 关闭页面 + if (refreshPage != null) { + refreshPage.close(); + } + } + + } catch (Exception e) { + log.error("访问zhipin.com刷新token时发生异常: {}", e.getMessage(), e); + return false; + } + } + +} diff --git a/src/main/java/getjobs/modules/dict/api/DictBundle.java b/src/main/java/getjobs/modules/dict/api/DictBundle.java new file mode 100644 index 00000000..bc5efc28 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/api/DictBundle.java @@ -0,0 +1,20 @@ +package getjobs.modules.dict.api; + +import getjobs.common.enums.RecruitmentPlatformEnum; + +import java.util.List; +import java.util.Optional; + +/** + * 平台字典总包 + * + * @param platform + * @param groups + */ +public record DictBundle(RecruitmentPlatformEnum platform, List groups) { + + // 便捷取法:按key取组 + public Optional group(String key) { + return groups.stream().filter(g -> g.key().equalsIgnoreCase(key)).findFirst(); + } +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/dict/api/DictGroup.java b/src/main/java/getjobs/modules/dict/api/DictGroup.java new file mode 100644 index 00000000..284debf5 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/api/DictGroup.java @@ -0,0 +1,11 @@ +package getjobs.modules.dict.api; + +import java.util.List; + +/** + * 字典分组(如 payTypeList / experienceList) + * + * @param key + * @param items + */ +public record DictGroup(String key, List items) {} diff --git a/src/main/java/getjobs/modules/dict/api/DictGroupKey.java b/src/main/java/getjobs/modules/dict/api/DictGroupKey.java new file mode 100644 index 00000000..37ce6dd8 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/api/DictGroupKey.java @@ -0,0 +1,46 @@ +package getjobs.modules.dict.api; + +import java.util.Arrays; + +/** + * 所有平台统一的字典分组Key + */ +public enum DictGroupKey { + + CITY("cityList", "城市"), + PAY_TYPE("payTypeList", "结算方式"), + EXPERIENCE("experienceList", "工作经验"), + SALARY("salaryList", "薪资区间"), + STAGE("stageList", "融资阶段"), + COMPANY_NATURE("companyNatureList", "公司性质"), + SCALE("scaleList", "公司规模"), + PART_TIME("partTimeList", "兼职类型"), + DEGREE("degreeList", "学历"), + JOB_TYPE("jobTypeList", "工作类型"), + INDUSTRY("industryList", "行业类别"), + PUBTIMES("pubTimes","招聘活跃度"); + + private final String key; + private final String description; + + DictGroupKey(String key, String description) { + this.key = key; + this.description = description; + } + + public String key() { + return key; + } + + public String description() { + return description; + } + + public static DictGroupKey fromKey(String key) { + + return Arrays.stream(values()) + .filter(e -> e.key.equalsIgnoreCase(key)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("无效的字典key: " + key)); + } +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/dict/api/DictItem.java b/src/main/java/getjobs/modules/dict/api/DictItem.java new file mode 100644 index 00000000..56763828 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/api/DictItem.java @@ -0,0 +1,18 @@ +package getjobs.modules.dict.api; + +/** + * 字典项(如 薪资区间 3-5K) + * lowSalary, highSalary 仅 salaryList 有值 + * parentCode 父级字典代码,用于表示层级关系 + */ +public record DictItem(String code, String name, Integer lowSalary, Integer highSalary, String parentCode) { + // 向下兼容:两参数构造函数 + public DictItem(String code, String name) { + this(code, name, null, null, null); + } + + // 向下兼容:四参数构造函数(原主构造函数签名) + public DictItem(String code, String name, Integer lowSalary, Integer highSalary) { + this(code, name, lowSalary, highSalary, null); + } +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/dict/config/Job51DictConfig.java b/src/main/java/getjobs/modules/dict/config/Job51DictConfig.java new file mode 100644 index 00000000..febb7c0a --- /dev/null +++ b/src/main/java/getjobs/modules/dict/config/Job51DictConfig.java @@ -0,0 +1,19 @@ +package getjobs.modules.dict.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * Job51字典配置类,用于读取application.yml中的dict-json配置 + */ +@Data +@Component +@ConfigurationProperties(prefix = "json51") +public class Job51DictConfig { + + /** + * dict-json配置内容 + */ + private String dictJson; +} diff --git a/src/main/java/getjobs/modules/dict/config/LiepinDictConfig.java b/src/main/java/getjobs/modules/dict/config/LiepinDictConfig.java new file mode 100644 index 00000000..02be5267 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/config/LiepinDictConfig.java @@ -0,0 +1,24 @@ +package getjobs.modules.dict.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * Liepin字典配置类,用于读取application.yml中的dict-json配置 + */ +@Data +@Component +@ConfigurationProperties(prefix = "liepin") +public class LiepinDictConfig { + + /** + * dict-json配置内容 + */ + private String dictJson; + + /** + * dict-city-json配置内容 + */ + private String dictCityJson; +} diff --git a/src/main/java/getjobs/modules/dict/domain/DictProvider.java b/src/main/java/getjobs/modules/dict/domain/DictProvider.java new file mode 100644 index 00000000..2100af17 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/domain/DictProvider.java @@ -0,0 +1,20 @@ +package getjobs.modules.dict.domain; + +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.dict.api.DictBundle; +import getjobs.modules.dict.api.DictGroup; + +import java.util.Optional; + +public interface DictProvider { + /** 该实现对应的平台 */ + RecruitmentPlatformEnum platform(); + + /** 返回该平台“全部”字典集合(已映射为统一模型) */ + DictBundle fetchAll(); + + /** 可选:只取指定分组,平台可直连远端做最小化拉取 */ + default Optional fetchByKey(String key) { + return fetchAll().group(key); + } +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/dict/infrastructure/config/ZhilianDictConfig.java b/src/main/java/getjobs/modules/dict/infrastructure/config/ZhilianDictConfig.java new file mode 100644 index 00000000..62853ddd --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/config/ZhilianDictConfig.java @@ -0,0 +1,38 @@ +package getjobs.modules.dict.infrastructure.config; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import getjobs.modules.dict.infrastructure.provider.dto.zhilian.ZhilianDictItem; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import java.util.Collections; +import java.util.List; + +/** + * 智联招聘字典配置 + */ +@Slf4j +@Getter +@Configuration +public class ZhilianDictConfig { + + private final List industryList; + + public ZhilianDictConfig( + @Value("${zhilian.dict-industry-json}") String industryJson, + ObjectMapper objectMapper) { + List tempList; + try { + tempList = objectMapper.readValue(industryJson, new TypeReference>() {}); + log.info("成功加载智联招聘行业字典数据,共 {} 条", tempList.size()); + } catch (Exception e) { + log.error("解析智联招聘行业字典数据失败: {}", e.getMessage()); + tempList = Collections.emptyList(); + } + this.industryList = tempList; + } +} + diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/Job51DictProviderImpl.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/Job51DictProviderImpl.java new file mode 100644 index 00000000..4975b3f5 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/Job51DictProviderImpl.java @@ -0,0 +1,186 @@ +package getjobs.modules.dict.infrastructure.provider; + +import com.fasterxml.jackson.databind.ObjectMapper; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.dict.config.Job51DictConfig; +import getjobs.modules.dict.api.DictBundle; +import getjobs.modules.dict.api.DictGroup; +import getjobs.modules.dict.api.DictGroupKey; +import getjobs.modules.dict.api.DictItem; +import getjobs.modules.dict.domain.DictProvider; +import getjobs.modules.dict.infrastructure.provider.dto.job51.DictJsonResponse; +import getjobs.modules.dict.infrastructure.provider.dto.job51.Job51CityGroup; +import getjobs.modules.dict.infrastructure.provider.dto.job51.Job51Response; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Slf4j +@Service +public class Job51DictProviderImpl implements DictProvider { + + private final String cityJsonUrl = "https://js.51jobcdn.com/in/js/2023/dd/dd_city.json"; + + private final WebClient webClient; + private final ObjectMapper objectMapper; + private final Job51DictConfig job51DictConfig; + + public Job51DictProviderImpl(WebClient webClient, ObjectMapper objectMapper, Job51DictConfig job51DictConfig) { + this.webClient = webClient; + this.objectMapper = objectMapper; + this.job51DictConfig = job51DictConfig; + } + + @Override + public RecruitmentPlatformEnum platform() { + return RecruitmentPlatformEnum.JOB_51; + } + + @Override + public DictBundle fetchAll() { + List groups = new ArrayList<>(); + + try { + // 获取城市数据 + String cityResponse = webClient.get() + .uri(cityJsonUrl) + .retrieve() + .bodyToMono(String.class) + .block(); + + if (cityResponse != null) { + Job51Response cityData = objectMapper.readValue(cityResponse, Job51Response.class); + + if (cityData.items() != null) { + // 处理城市数据 - 使用Set去重,避免热门城市和其他分组中的城市重复 + Set cityItemsSet = new HashSet<>(); + + // 遍历所有城市分组 + for (Job51CityGroup group : cityData.items()) { + if (group.items() != null&&"0".equals(group.type())) { + // 添加城市项目 + cityItemsSet.addAll(group.items().stream() + .map(city -> new DictItem(city.code(), city.value())) + .toList()); + } + } + + groups.add(new DictGroup(DictGroupKey.CITY.key(), new ArrayList<>(cityItemsSet))); + } + } + + DictBundle dictBundle = fetchFromConfig(); + groups.addAll(dictBundle.groups()); + } catch (Exception e) { + // 记录错误日志,但不抛出异常,返回空的数据包 + log.warn("获取51job招聘字典数据失败: {}", e.getMessage()); + } + + return new DictBundle(RecruitmentPlatformEnum.JOB_51, groups); + } + + + /** + * 直接从application.yml配置中读取dict-json并解析字典数据 + * + * @return 字典数据包 + */ + public DictBundle fetchFromConfig() { + List groups = new ArrayList<>(); + + try { + // 从配置中读取dict-json字符串 + String dictJsonStr = job51DictConfig.getDictJson(); + if (dictJsonStr == null || dictJsonStr.trim().isEmpty()) { + log.warn("dict-json配置为空"); + return new DictBundle(platform(), groups); + } + + // 解析JSON + DictJsonResponse response = objectMapper.readValue(dictJsonStr, DictJsonResponse.class); + + if (response == null || response.getResultBody() == null) { + log.warn("解析dict-json失败:响应数据为空"); + return new DictBundle(platform(), groups); + } + + DictJsonResponse.ResultBody resultBody = response.getResultBody(); + + // 处理行业字典 + if (resultBody.getIndustry() != null) { + List industryItems = new ArrayList<>(); + for (var industry : resultBody.getIndustry()) { + // 添加父行业 + industryItems.add(new DictItem(industry.getId(), industry.getValue())); + // 添加子行业(带parentCode) + if (industry.getSub() != null) { + for (var sub : industry.getSub()) { + industryItems.add(new DictItem(sub.getId(), sub.getValue(), null, null, industry.getId())); + } + } + } + groups.add(new DictGroup(DictGroupKey.INDUSTRY.key(), industryItems)); + } + + // 处理公司类型字典 - 使用COMPANY_NATURE作为公司类型的key + if (resultBody.getCompanyType() != null) { + groups.add(new DictGroup(DictGroupKey.COMPANY_NATURE.key(), + resultBody.getCompanyType().stream() + .map(item -> new DictItem(item.getId(), item.getValue())) + .toList())); + } + + // 处理工作经验字典 + if (resultBody.getWorkYear() != null) { + groups.add(new DictGroup(DictGroupKey.EXPERIENCE.key(), + resultBody.getWorkYear().stream() + .map(item -> new DictItem(item.getId(), item.getValue())) + .toList())); + } + + // 处理薪资字典 + if (resultBody.getSalary() != null) { + groups.add(new DictGroup(DictGroupKey.SALARY.key(), + resultBody.getSalary().stream() + .map(item -> new DictItem(item.getId(), item.getValue())) + .toList())); + } + + // 处理公司规模字典 + if (resultBody.getCompanySize() != null) { + groups.add(new DictGroup(DictGroupKey.SCALE.key(), + resultBody.getCompanySize().stream() + .map(item -> new DictItem(item.getId(), item.getValue())) + .toList())); + } + + // 处理学历字典 + if (resultBody.getDegree() != null) { + groups.add(new DictGroup(DictGroupKey.DEGREE.key(), + resultBody.getDegree().stream() + .map(item -> new DictItem(item.getId(), item.getValue())) + .toList())); + } + + // 处理工作性质字典 + if (resultBody.getJobTerm() != null) { + groups.add(new DictGroup(DictGroupKey.JOB_TYPE.key(), + resultBody.getJobTerm().stream() + .map(item -> new DictItem(item.getId(), item.getValue())) + .toList())); + } + + log.info("成功从配置中解析出{}个字典组", groups.size()); + + } catch (Exception e) { + log.error("从配置解析字典数据失败", e); + } + + return new DictBundle(platform(), groups); + } +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/LiepinDictProviderImpl.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/LiepinDictProviderImpl.java new file mode 100644 index 00000000..9ee23471 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/LiepinDictProviderImpl.java @@ -0,0 +1,194 @@ +package getjobs.modules.dict.infrastructure.provider; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.type.TypeReference; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.dict.config.LiepinDictConfig; +import getjobs.modules.dict.api.DictBundle; +import getjobs.modules.dict.api.DictGroup; +import getjobs.modules.dict.api.DictGroupKey; +import getjobs.modules.dict.api.DictItem; +import getjobs.modules.dict.domain.DictProvider; +import getjobs.modules.dict.infrastructure.provider.dto.liepin.LiepinDictResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class LiepinDictProviderImpl implements DictProvider { + + /** + * 省份编码集合 + */ + private static final List PROVINCE_CODES = List.of( + "140", "260", "210", "190", "160", "060", "070", "080", "090", "200", + "250", "150", "170", "180", "010", "050", "020", "110", "130", "280", + "030", "120", "310", "270", "040", "100", "240", "290", "230", "300", + "220", "330", "340", "320" + ); + + private final ObjectMapper objectMapper; + private final LiepinDictConfig liepinDictConfig; + + public LiepinDictProviderImpl(ObjectMapper objectMapper, LiepinDictConfig liepinDictConfig) { + this.objectMapper = objectMapper; + this.liepinDictConfig = liepinDictConfig; + } + + @Override + public RecruitmentPlatformEnum platform() { + return RecruitmentPlatformEnum.LIEPIN; + } + + @Override + public DictBundle fetchAll() { + List groups = new ArrayList<>(); + try { + DictBundle dictBundle = fetchFromConfig(); + groups.addAll(dictBundle.groups()); + } catch (Exception e) { + log.warn("获取猎聘招聘字典数据失败: {}", e.getMessage()); + } + return new DictBundle(platform(), groups); + } + + public DictBundle fetchFromConfig() { + List groups = new ArrayList<>(); + + try { + String dictJsonStr = liepinDictConfig.getDictJson(); + if (dictJsonStr == null || dictJsonStr.trim().isEmpty()) { + log.warn("liepin.dict-json配置为空"); + return new DictBundle(platform(), groups); + } + + LiepinDictResponse response = objectMapper.readValue(dictJsonStr, LiepinDictResponse.class); + + if (response == null || response.getData() == null) { + log.warn("解析liepin.dict-json失败:响应数据为空"); + return new DictBundle(platform(), groups); + } + + LiepinDictResponse.LiepinDictData data = response.getData(); + + if (data.getWorkExperiences() != null) { + groups.add(new DictGroup(DictGroupKey.EXPERIENCE.key(), + data.getWorkExperiences().stream() + .map(item -> new DictItem(item.getCode(), item.getName())) + .collect(Collectors.toList()))); + } + + if (data.getSalaries() != null) { + groups.add(new DictGroup(DictGroupKey.SALARY.key(), + data.getSalaries().stream() + .map(item -> new DictItem(item.getCode(), item.getName())) + .collect(Collectors.toList()))); + } + + if (data.getCompScales() != null) { + groups.add(new DictGroup(DictGroupKey.SCALE.key(), + data.getCompScales().stream() + .map(item -> new DictItem(item.getCode(), item.getName())) + .collect(Collectors.toList()))); + } + + if (data.getEducations() != null) { + groups.add(new DictGroup(DictGroupKey.DEGREE.key(), + data.getEducations().stream() + .map(item -> new DictItem(item.getCode(), item.getName())) + .collect(Collectors.toList()))); + } + if (data.getCompNatures() != null) { + groups.add(new DictGroup(DictGroupKey.COMPANY_NATURE.key(), + data.getCompNatures().stream() + .map(item -> new DictItem(item.getCode(), item.getName())) + .collect(Collectors.toList()))); + } + + if (data.getIndustries() != null) { + List industryItems = new ArrayList<>(); + for (var industry : data.getIndustries()) { + // 添加父行业 + industryItems.add(new DictItem(industry.getCode(), industry.getName())); + // 添加子行业(带parentCode) + if (industry.getChildren() != null) { + for (var child : industry.getChildren()) { + industryItems.add(new DictItem(child.getCode(), child.getName(), null, null, industry.getCode())); + } + } + } + groups.add(new DictGroup(DictGroupKey.INDUSTRY.key(), industryItems)); + } + + + // 处理工作性质字典 + if (data.getJobKinds() != null) { + groups.add(new DictGroup(DictGroupKey.JOB_TYPE.key(), + data.getJobKinds().stream() + .map(item -> new DictItem(item.getCode(), item.getName())) + .toList())); + } + + // 融资阶段 + if (data.getFinanceStages() != null) { + groups.add(new DictGroup(DictGroupKey.STAGE.key(), + data.getFinanceStages().stream() + .map(item -> new DictItem(String.valueOf(item.getCode()), item.getName())) + .collect(Collectors.toList()))); + } + + // 招聘者活跃度 + if (data.getPubTimes() != null) { + groups.add(new DictGroup(DictGroupKey.PUBTIMES.key(), + data.getPubTimes().stream() + .map(item -> new DictItem(String.valueOf(item.getCode()), item.getName())) + .collect(Collectors.toList()))); + } + + // 处理城市字典 + String dictCityJsonStr = liepinDictConfig.getDictCityJson(); + if (dictCityJsonStr != null && !dictCityJsonStr.trim().isEmpty()) { + try { + Map> cityMap = objectMapper.readValue( + dictCityJsonStr, + new TypeReference>>() {} + ); + + List cityItems = cityMap.entrySet().stream() + .filter(entry -> { + Map cityInfo = entry.getValue(); + String provinceCode = (String) cityInfo.get("p"); + return provinceCode != null && PROVINCE_CODES.contains(provinceCode); + }) + .map(entry -> { + String cityCode = entry.getKey(); + Map cityInfo = entry.getValue(); + String cityName = (String) cityInfo.get("n"); + return new DictItem(cityCode, cityName); + }) + .collect(Collectors.toList()); + + if (!cityItems.isEmpty()) { + groups.add(new DictGroup(DictGroupKey.CITY.key(), cityItems)); + log.info("成功加载{}个城市字典项", cityItems.size()); + } + } catch (Exception e) { + log.error("解析城市字典数据失败", e); + } + } + + + log.info("成功从配置中解析出{}个猎聘字典组", groups.size()); + + } catch (Exception e) { + log.error("从配置解析猎聘字典数据失败", e); + } + + return new DictBundle(platform(), groups); + } +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/ZhilianDictProviderImpl.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/ZhilianDictProviderImpl.java new file mode 100644 index 00000000..70c53c3c --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/ZhilianDictProviderImpl.java @@ -0,0 +1,287 @@ +package getjobs.modules.dict.infrastructure.provider; + +import com.fasterxml.jackson.databind.ObjectMapper; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.dict.api.DictBundle; +import getjobs.modules.dict.api.DictGroup; +import getjobs.modules.dict.api.DictGroupKey; +import getjobs.modules.dict.api.DictItem; +import getjobs.modules.dict.domain.DictProvider; +import getjobs.modules.dict.infrastructure.config.ZhilianDictConfig; +import getjobs.modules.dict.infrastructure.provider.dto.zhilian.ZhilianBaseData; +import getjobs.modules.dict.infrastructure.provider.dto.zhilian.ZhilianDictItem; +import getjobs.modules.dict.infrastructure.provider.dto.zhilian.ZhilianResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class ZhilianDictProviderImpl implements DictProvider { + + private final String baseDataUrl = "https://fe-api.zhaopin.com/c/i/search/base/data"; + + private final WebClient webClient; + private final ObjectMapper objectMapper; + private final ZhilianDictConfig zhilianDictConfig; + + public ZhilianDictProviderImpl(WebClient webClient, ObjectMapper objectMapper, ZhilianDictConfig zhilianDictConfig) { + this.webClient = webClient; + this.objectMapper = objectMapper; + this.zhilianDictConfig = zhilianDictConfig; + } + + @Override + public RecruitmentPlatformEnum platform() { + return RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN; + } + + @Override + public DictBundle fetchAll() { + List groups = new ArrayList<>(); + + try { + String response = webClient.get() + .uri(baseDataUrl) + .retrieve() + .bodyToMono(String.class) + .block(); + + if (response != null) { + ZhilianResponse zhilianData = objectMapper.readValue(response, + objectMapper.getTypeFactory().constructParametricType(ZhilianResponse.class, + ZhilianBaseData.class)); + + if (zhilianData.code() == 200 && zhilianData.data() != null) { + ZhilianBaseData data = zhilianData.data(); + + // 处理城市数据 + processCityData(groups, data); + + // 处理公司类型 + if (data.companyType() != null) { + groups.add(new DictGroup(DictGroupKey.COMPANY_NATURE.key(), + data.companyType().stream() + .filter(item -> item.deleted() == null || !item.deleted()) + .filter(item -> item.code() != null) // 过滤掉"不限"选项 + .map(item -> new DictItem(item.code(), item.name())) + .collect(Collectors.toList()))); + } + + // 处理薪资类型 + if (data.salaryType() != null) { + groups.add(new DictGroup(DictGroupKey.SALARY.key(), + data.salaryType().stream() + .filter(item -> item.deleted() == null || !item.deleted()) + .filter(item -> item.code() != null) // 过滤掉"不限"选项 + .map(item -> parseSalaryItem(item)) + .collect(Collectors.toList()))); + } + + // 处理职位类别 + if (data.position() != null) { + groups.add(new DictGroup(DictGroupKey.PART_TIME.key(), + flattenPositionItems(data.position()))); + } + + // 处理工作经验 + if (data.workExpType() != null) { + groups.add(new DictGroup(DictGroupKey.EXPERIENCE.key(), + data.workExpType().stream() + .filter(item -> item.deleted() == null || !item.deleted()) + .filter(item -> item.code() != null && !item.code().equals("-1")) // 过滤掉"不限"选项 + .map(item -> new DictItem(item.code(), item.name())) + .collect(Collectors.toList()))); + } + + // 处理求职类型 + if (data.jobStatus() != null) { + groups.add(new DictGroup(DictGroupKey.JOB_TYPE.key(), + data.jobStatus().stream() + .filter(item -> item.deleted() == null || !item.deleted()) + .filter(item -> item.code() != null && !item.code().equals("-1")) // 过滤掉"不限"选项 + .map(item -> new DictItem(item.code(), item.name())) + .collect(Collectors.toList()))); + } + + // 处理学历要求 + if (data.educationType() != null) { + groups.add(new DictGroup(DictGroupKey.DEGREE.key(), + data.educationType().stream() + .filter(item -> item.deleted() == null || !item.deleted()) + .filter(item -> item.code() != null && !item.code().equals("-1")) // 过滤掉"不限"选项 + .map(item -> new DictItem(item.code(), item.name())) + .collect(Collectors.toList()))); + } + + // 处理公司规模 + if (data.companySize() != null) { + groups.add(new DictGroup(DictGroupKey.SCALE.key(), + data.companySize().stream() + .filter(item -> item.deleted() == null || !item.deleted()) + .filter(item -> item.code() != null && !item.code().equals("-1")) // 过滤掉"不限"选项 + .filter(item -> !item.name().trim().isEmpty()) // 过滤掉名称为空的选项(如"保密"选项) + .map(item -> new DictItem(item.code(), item.name())) + .collect(Collectors.toList()))); + } + } + } + } catch (Exception e) { + log.warn("获取智联招聘字典数据失败: {}", e.getMessage()); + } + + // 处理目标行业 + processIndustryData(groups); + + return new DictBundle(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN, groups); + } + + /** + * 处理城市数据,包含地铁和城市列表 + */ + private void processCityData(List groups, ZhilianBaseData data) { + Set cityItemsSet = new HashSet<>(); + + // 从地铁数据中提取城市信息 + if (data.subway() != null) { + cityItemsSet.addAll(data.subway().stream() + .filter(subway -> subway.deleted() == null || !subway.deleted()) + .filter(subway -> subway.code() != null) + .map(subway -> new DictItem(subway.code(), subway.name())) + .toList()); + } + + // 从城市列表中提取城市信息 + if (data.cityList() != null) { + cityItemsSet.addAll(data.cityList().stream() + .filter(city -> city.deleted() == null || !city.deleted()) + .filter(city -> city.code() != null) + .map(city -> new DictItem(city.code(), city.name())) + .toList()); + } + + if (!cityItemsSet.isEmpty()) { + groups.add(new DictGroup(DictGroupKey.CITY.key(), new ArrayList<>(cityItemsSet))); + } + } + + /** + * 解析薪资项,提取薪资范围 + */ + private DictItem parseSalaryItem(ZhilianDictItem item) { + String code = item.code(); + String name = item.name(); + + // 解析薪资范围,格式如 "4001,6000" 或 "0000,9999999" + Integer lowSalary = null; + Integer highSalary = null; + + if (code != null && code.contains(",")) { + try { + String[] parts = code.split(","); + if (parts.length == 2) { + int low = Integer.parseInt(parts[0]); + int high = Integer.parseInt(parts[1]); + + // 处理特殊情况:不限薪资 + if (low == 0 && high == 9999999) { + // 不限薪资不设置范围 + } else if (low == 0) { + // 如"4K以下",只设置高薪资 + highSalary = high; + } else if (high == 9999999) { + // 如"50K以上",只设置低薪资 + lowSalary = low; + } else { + // 正常范围 + lowSalary = low; + highSalary = high; + } + } + } catch (NumberFormatException e) { + log.debug("解析薪资范围失败: {}", code); + } + } + + return new DictItem(code, name, lowSalary, highSalary); + } + + /** + * 扁平化职位分类数据,包含子分类 + */ + private List flattenPositionItems(List positions) { + List result = new ArrayList<>(); + + for (ZhilianDictItem position : positions) { + if (position.deleted() == null || !position.deleted()) { + // 添加主分类 + if (position.code() != null) { + result.add(new DictItem(position.code(), position.name())); + } + + // 递归添加子分类 + if (position.sublist() != null && !position.sublist().isEmpty()) { + result.addAll(flattenPositionItems(position.sublist())); + } + } + } + + return result; + } + + /** + * 处理目标行业数据 + * 优先返回 sublist 不为空的大行业类目,再返回所有行业对象下的 sublist 集合中的子行业 + */ + private void processIndustryData(List groups) { + try { + List industryList = zhilianDictConfig.getIndustryList(); + if (industryList == null || industryList.isEmpty()) { + log.warn("智联招聘行业字典数据为空"); + return; + } + + List industryItems = new ArrayList<>(); + + for (ZhilianDictItem industry : industryList) { + // 过滤掉已删除和"不限"选项 + if (industry.deleted() != null && industry.deleted()) { + continue; + } + if (industry.code() == null || industry.code().equals("-1")) { + continue; + } + + // 优先添加 sublist 不为空的大行业类目 + if (industry.sublist() != null && !industry.sublist().isEmpty()) { + industryItems.add(new DictItem(industry.code(), industry.name())); + + // 添加所有子行业(过滤掉"不限"和已删除的) + for (ZhilianDictItem subIndustry : industry.sublist()) { + if (subIndustry.deleted() != null && subIndustry.deleted()) { + continue; + } + // if (subIndustry.code() == null || subIndustry.code().equals(industry.code())) { + // // 过滤掉 code 和父级相同的"不限"子项 + // continue; + // } + industryItems.add(new DictItem(subIndustry.code(), subIndustry.name(), null, null, industry.code())); + } + } + } + + if (!industryItems.isEmpty()) { + groups.add(new DictGroup(DictGroupKey.INDUSTRY.key(), industryItems)); + log.info("成功处理智联招聘行业字典数据,共 {} 条", industryItems.size()); + } + } catch (Exception e) { + log.warn("处理智联招聘行业字典数据失败: {}", e.getMessage()); + } + } +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/ZhipinDictProviderImpl.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/ZhipinDictProviderImpl.java new file mode 100644 index 00000000..2028ad03 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/ZhipinDictProviderImpl.java @@ -0,0 +1,207 @@ +package getjobs.modules.dict.infrastructure.provider; + +import com.fasterxml.jackson.databind.ObjectMapper; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.dict.api.DictBundle; +import getjobs.modules.dict.api.DictGroup; +import getjobs.modules.dict.api.DictGroupKey; +import getjobs.modules.dict.api.DictItem; +import getjobs.modules.dict.domain.DictProvider; +import getjobs.modules.dict.infrastructure.provider.dto.boss.CityGroupData; +import getjobs.modules.dict.infrastructure.provider.dto.ConditionsData; +import getjobs.modules.dict.infrastructure.provider.dto.boss.IndustryData; +import getjobs.modules.dict.infrastructure.provider.dto.boss.ZhipinResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class ZhipinDictProviderImpl implements DictProvider { + + private final String conditionsJsonUrl = "https://www.zhipin.com/wapi/zpgeek/pc/all/filter/conditions.json"; + + private final String cityGroupJsonUrl = "https://www.zhipin.com/wapi/zpCommon/data/cityGroup.json"; + + private final WebClient webClient; + private final ObjectMapper objectMapper; + + @Value("${boss.dict-industry-json}") + private String dictIndustryJson; + + public ZhipinDictProviderImpl(WebClient webClient, ObjectMapper objectMapper) { + this.webClient = webClient; + this.objectMapper = objectMapper; + } + + @Override + public RecruitmentPlatformEnum platform() { + return RecruitmentPlatformEnum.BOSS_ZHIPIN; + } + + @Override + public DictBundle fetchAll() { + List groups = new ArrayList<>(); + + try { + // 获取城市数据 + String cityResponse = webClient.get() + .uri(cityGroupJsonUrl) + .retrieve() + .bodyToMono(String.class) + .block(); + + if (cityResponse != null) { + ZhipinResponse cityData = objectMapper.readValue(cityResponse, + objectMapper.getTypeFactory().constructParametricType(ZhipinResponse.class, + CityGroupData.class)); + + if (cityData.code() == 0 && cityData.zpData() != null) { + // 处理城市数据 - 使用Set去重,避免热门城市和城市分组中的城市重复 + Set cityItemsSet = new HashSet<>(); + + // 添加热门城市 + if (cityData.zpData().hotCityList() != null) { + cityItemsSet.addAll(cityData.zpData().hotCityList().stream() + .map(city -> new DictItem(String.valueOf(city.code()), city.name())) + .toList()); + } + + // 添加所有城市分组中的城市 + if (cityData.zpData().cityGroup() != null) { + cityItemsSet.addAll(cityData.zpData().cityGroup().stream() + .flatMap(group -> group.cityList().stream()) + .map(city -> new DictItem(String.valueOf(city.code()), city.name())) + .toList()); + } + + groups.add(new DictGroup(DictGroupKey.CITY.key(), new ArrayList<>(cityItemsSet))); + } + } + + // 获取条件数据 + String conditionsResponse = webClient.get() + .uri(conditionsJsonUrl) + .retrieve() + .bodyToMono(String.class) + .block(); + + if (conditionsResponse != null) { + ZhipinResponse conditionsData = objectMapper.readValue(conditionsResponse, + objectMapper.getTypeFactory().constructParametricType(ZhipinResponse.class, + ConditionsData.class)); + + if (conditionsData.code() == 0 && conditionsData.zpData() != null) { + ConditionsData data = conditionsData.zpData(); + + // 处理各种条件列表 + if (data.payTypeList() != null) { + groups.add(new DictGroup(DictGroupKey.PAY_TYPE.key(), + data.payTypeList().stream() + .map(item -> new DictItem(String.valueOf(item.code()), item.name())) + .collect(Collectors.toList()))); + } + + if (data.experienceList() != null) { + groups.add(new DictGroup(DictGroupKey.EXPERIENCE.key(), + data.experienceList().stream() + .map(item -> new DictItem(String.valueOf(item.code()), item.name())) + .collect(Collectors.toList()))); + } + + if (data.salaryList() != null) { + groups.add(new DictGroup(DictGroupKey.SALARY.key(), + data.salaryList().stream() + .map(item -> new DictItem(String.valueOf(item.code()), item.name(), + item.lowSalary() == 0 ? null : item.lowSalary(), + item.highSalary() == 0 ? null : item.highSalary())) + .collect(Collectors.toList()))); + } + + if (data.stageList() != null) { + groups.add(new DictGroup(DictGroupKey.STAGE.key(), + data.stageList().stream() + .map(item -> new DictItem(String.valueOf(item.code()), item.name())) + .collect(Collectors.toList()))); + } + + if (data.companyNatureList() != null) { + groups.add(new DictGroup(DictGroupKey.COMPANY_NATURE.key(), + data.companyNatureList().stream() + .map(item -> new DictItem(String.valueOf(item.code()), item.name())) + .collect(Collectors.toList()))); + } + + if (data.scaleList() != null) { + groups.add(new DictGroup(DictGroupKey.SCALE.key(), + data.scaleList().stream() + .map(item -> new DictItem(String.valueOf(item.code()), item.name())) + .collect(Collectors.toList()))); + } + + if (data.partTimeList() != null) { + groups.add(new DictGroup(DictGroupKey.PART_TIME.key(), + data.partTimeList().stream() + .map(item -> new DictItem(String.valueOf(item.code()), item.name())) + .collect(Collectors.toList()))); + } + + if (data.degreeList() != null) { + groups.add(new DictGroup(DictGroupKey.DEGREE.key(), + data.degreeList().stream() + .map(item -> new DictItem(String.valueOf(item.code()), item.name())) + .collect(Collectors.toList()))); + } + + if (data.jobTypeList() != null) { + groups.add(new DictGroup(DictGroupKey.JOB_TYPE.key(), + data.jobTypeList().stream() + .map(item -> new DictItem(String.valueOf(item.code()), item.name())) + .collect(Collectors.toList()))); + } + } + } + + // 处理行业数据 + if (dictIndustryJson != null && !dictIndustryJson.isEmpty()) { + try { + ZhipinResponse> industryResponse = objectMapper.readValue(dictIndustryJson, + objectMapper.getTypeFactory().constructParametricType(ZhipinResponse.class, + objectMapper.getTypeFactory().constructCollectionType(List.class, IndustryData.class))); + + if (industryResponse.code() == 0 && industryResponse.zpData() != null) { + List industryItems = industryResponse.zpData().stream() + .filter(industryData -> industryData.subLevelModelList() != null) + .flatMap(industryData -> industryData.subLevelModelList().stream() + .map(item -> new DictItem( + String.valueOf(item.code()), + item.name(), + null, + null, + String.valueOf(industryData.code())))) + .collect(Collectors.toList()); + + if (!industryItems.isEmpty()) { + groups.add(new DictGroup(DictGroupKey.INDUSTRY.key(), industryItems)); + } + } + } catch (Exception e) { + log.warn("解析行业数据失败: {}", e.getMessage()); + } + } + } catch (Exception e) { + // 记录错误日志,但不抛出异常,返回空的数据包 + // 在实际项目中应该使用日志框架记录错误 + log.warn("获取BOSS直聘招聘字典数据失败: {}", e.getMessage()); + } + + return new DictBundle(RecruitmentPlatformEnum.BOSS_ZHIPIN, groups); + } +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/ConditionItem.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/ConditionItem.java new file mode 100644 index 00000000..b684f083 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/ConditionItem.java @@ -0,0 +1,11 @@ +package getjobs.modules.dict.infrastructure.provider.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * 条件项 + */ +public record ConditionItem( + @JsonProperty("code") String code, + @JsonProperty("name") String name) { +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/ConditionsData.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/ConditionsData.java new file mode 100644 index 00000000..b2393565 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/ConditionsData.java @@ -0,0 +1,28 @@ +package getjobs.modules.dict.infrastructure.provider.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * 条件数据 + */ +public record ConditionsData( + /** 结算方式列表 */ + @JsonProperty("payTypeList") List payTypeList, + /** 工作经验列表 */ + @JsonProperty("experienceList") List experienceList, + /** 薪资区间列表 */ + @JsonProperty("salaryList") List salaryList, + /** 融资阶段列表 */ + @JsonProperty("stageList") List stageList, + /** 公司性质列表 */ + @JsonProperty("companyNatureList") List companyNatureList, + /** 公司规模列表 */ + @JsonProperty("scaleList") List scaleList, + /** 兼职类型列表 */ + @JsonProperty("partTimeList") List partTimeList, + /** 学历要求列表 */ + @JsonProperty("degreeList") List degreeList, + /** 工作类型列表 */ + @JsonProperty("jobTypeList") List jobTypeList) { +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/SalaryItem.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/SalaryItem.java new file mode 100644 index 00000000..d4fecb3b --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/SalaryItem.java @@ -0,0 +1,13 @@ +package getjobs.modules.dict.infrastructure.provider.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * 薪资项 + */ +public record SalaryItem( + @JsonProperty("code") String code, + @JsonProperty("name") String name, + @JsonProperty("lowSalary") Integer lowSalary, + @JsonProperty("highSalary") Integer highSalary) { +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/City.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/City.java new file mode 100644 index 00000000..5fb0bc7b --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/City.java @@ -0,0 +1,26 @@ +package getjobs.modules.dict.infrastructure.provider.dto.boss; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * 城市信息 + */ +public record City( + @JsonProperty("code") int code, + @JsonProperty("name") String name, + @JsonProperty("tip") String tip, + @JsonProperty("subLevelModelList") Object subLevelModelList, + @JsonProperty("firstChar") String firstChar, + @JsonProperty("pinyin") String pinyin, + @JsonProperty("rank") int rank, + @JsonProperty("mark") int mark, + @JsonProperty("positionType") int positionType, + @JsonProperty("cityType") int cityType, + @JsonProperty("capital") int capital, + @JsonProperty("color") String color, + @JsonProperty("recruitmentType") String recruitmentType, + @JsonProperty("cityCode") String cityCode, + @JsonProperty("regionCode") int regionCode, + @JsonProperty("centerGeo") String centerGeo, + @JsonProperty("value") Object value) { +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/CityGroup.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/CityGroup.java new file mode 100644 index 00000000..1c5e9c14 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/CityGroup.java @@ -0,0 +1,12 @@ +package getjobs.modules.dict.infrastructure.provider.dto.boss; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * 城市分组 + */ +public record CityGroup( + @JsonProperty("firstChar") String firstChar, + @JsonProperty("cityList") List cityList) { +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/CityGroupData.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/CityGroupData.java new file mode 100644 index 00000000..49354dcc --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/CityGroupData.java @@ -0,0 +1,13 @@ +package getjobs.modules.dict.infrastructure.provider.dto.boss; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * 城市分组数据 + */ +public record CityGroupData( + @JsonProperty("cityGroup") List cityGroup, + @JsonProperty("hotCityList") List hotCityList, + @JsonProperty("locationCity") City locationCity) { +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/IndustryData.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/IndustryData.java new file mode 100644 index 00000000..b3234ddf --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/IndustryData.java @@ -0,0 +1,14 @@ +package getjobs.modules.dict.infrastructure.provider.dto.boss; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * 行业分类数据 + */ +public record IndustryData( + @JsonProperty("code") Integer code, + @JsonProperty("name") String name, + @JsonProperty("subLevelModelList") List subLevelModelList) { +} + diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/IndustryItem.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/IndustryItem.java new file mode 100644 index 00000000..eae6db72 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/IndustryItem.java @@ -0,0 +1,12 @@ +package getjobs.modules.dict.infrastructure.provider.dto.boss; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * 行业项 + */ +public record IndustryItem( + @JsonProperty("code") Integer code, + @JsonProperty("name") String name) { +} + diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/ZhipinResponse.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/ZhipinResponse.java new file mode 100644 index 00000000..26251e8a --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/boss/ZhipinResponse.java @@ -0,0 +1,12 @@ +package getjobs.modules.dict.infrastructure.provider.dto.boss; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * 智联招聘 API 响应基础结构 + */ +public record ZhipinResponse( + @JsonProperty("code") int code, + @JsonProperty("message") String message, + @JsonProperty("zpData") T zpData) { +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/job51/DictJsonResponse.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/job51/DictJsonResponse.java new file mode 100644 index 00000000..c8cccac8 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/job51/DictJsonResponse.java @@ -0,0 +1,72 @@ +package getjobs.modules.dict.infrastructure.provider.dto.job51; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 映射application.yml中json51.dict-json的响应结构 + */ +@Data +public class DictJsonResponse { + + @JsonProperty("status") + private String status; + + @JsonProperty("message") + private String message; + + @JsonProperty("resultbody") + private ResultBody resultBody; + + @Data + public static class ResultBody { + + @JsonProperty("d_industry") + private List industry; + + @JsonProperty("d_area") + private List area; + + @JsonProperty("d_search_cottype") + private List companyType; + + @JsonProperty("d_search_workyear") + private List workYear; + + @JsonProperty("d_search_providesalary") + private List salary; + + @JsonProperty("d_search_companysize") + private List companySize; + + @JsonProperty("d_search_degreefrom") + private List degree; + + @JsonProperty("d_search_jobterm") + private List jobTerm; + + @JsonProperty("d_search_issuedate") + private List issueDate; + + @JsonProperty("d_search_postchannel") + private List postChannel; + } + + @Data + public static class DictItem { + + @JsonProperty("id") + private String id; + + @JsonProperty("value") + private String value; + + @JsonProperty("sub") + private List sub; + + @JsonProperty("trace") + private List trace; + } +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/job51/Job51City.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/job51/Job51City.java new file mode 100644 index 00000000..b9755137 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/job51/Job51City.java @@ -0,0 +1,12 @@ +package getjobs.modules.dict.infrastructure.provider.dto.job51; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * 51job城市信息 + */ +public record Job51City( + @JsonProperty("code") String code, + @JsonProperty("value") String value, + @JsonProperty("hasSubArea") boolean hasSubArea) { +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/job51/Job51CityGroup.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/job51/Job51CityGroup.java new file mode 100644 index 00000000..0da47e96 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/job51/Job51CityGroup.java @@ -0,0 +1,14 @@ +package getjobs.modules.dict.infrastructure.provider.dto.job51; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * 51job城市分组 + */ +public record Job51CityGroup( + @JsonProperty("title") String title, + @JsonProperty("type") String type, + @JsonProperty("items") List items, + @JsonProperty("eItems") List eItems) { +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/job51/Job51Response.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/job51/Job51Response.java new file mode 100644 index 00000000..488b5677 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/job51/Job51Response.java @@ -0,0 +1,11 @@ +package getjobs.modules.dict.infrastructure.provider.dto.job51; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * 51job API响应 + */ +public record Job51Response( + @JsonProperty("items") List items) { +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/liepin/LiepinDictResponse.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/liepin/LiepinDictResponse.java new file mode 100644 index 00000000..273e91d6 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/liepin/LiepinDictResponse.java @@ -0,0 +1,42 @@ +package getjobs.modules.dict.infrastructure.provider.dto.liepin; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class LiepinDictResponse { + private int flag; + private LiepinDictData data; + + @Data + public static class LiepinDictData { + private List workExperiences; + private List yearSalaries; + private List jobKinds; + private List salaries; + private List compScales; + private List industries; + private List famousComps; + private List hotCities; + private List pubTimes; + private List educations; + private List financeStages; + @JsonProperty("compNatures") + private List compNatures; + } + + @Data + public static class LiepinDictItem { + private String code; + private String name; + } + + @Data + public static class LiepinIndustryItem { + private String code; + private String name; + private List children; + } +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/zhilian/ZhilianBaseData.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/zhilian/ZhilianBaseData.java new file mode 100644 index 00000000..4ac58415 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/zhilian/ZhilianBaseData.java @@ -0,0 +1,20 @@ +package getjobs.modules.dict.infrastructure.provider.dto.zhilian; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * 智联招聘基础数据响应 + */ +public record ZhilianBaseData( + @JsonProperty("companyType") List companyType, + @JsonProperty("salaryType") List salaryType, + @JsonProperty("subway") List subway, + @JsonProperty("cityList") List cityList, + @JsonProperty("position") List position, + @JsonProperty("workExpType") List workExpType, + @JsonProperty("jobStatus") List jobStatus, + @JsonProperty("educationType") List educationType, + @JsonProperty("companySize") List companySize) { +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/zhilian/ZhilianDictItem.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/zhilian/ZhilianDictItem.java new file mode 100644 index 00000000..17ba97ab --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/zhilian/ZhilianDictItem.java @@ -0,0 +1,20 @@ +package getjobs.modules.dict.infrastructure.provider.dto.zhilian; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * 智联招聘字典项 + */ +public record ZhilianDictItem( + @JsonProperty("code") String code, + @JsonProperty("parentCode") String parentCode, + @JsonProperty("name") String name, + @JsonProperty("en_name") String enName, + @JsonProperty("deleted") Boolean deleted, + @JsonProperty("sublist") List sublist, + @JsonProperty("longitude") Double longitude, + @JsonProperty("latitude") Double latitude +) { +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/zhilian/ZhilianResponse.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/zhilian/ZhilianResponse.java new file mode 100644 index 00000000..f3280398 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/zhilian/ZhilianResponse.java @@ -0,0 +1,13 @@ +package getjobs.modules.dict.infrastructure.provider.dto.zhilian; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * 智联招聘API响应数据包装类 + */ +public record ZhilianResponse( + @JsonProperty("code") int code, + @JsonProperty("message") String message, + @JsonProperty("data") T data +) { +} diff --git a/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/zhilian/ZhilianSubway.java b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/zhilian/ZhilianSubway.java new file mode 100644 index 00000000..e1ac00f7 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/infrastructure/provider/dto/zhilian/ZhilianSubway.java @@ -0,0 +1,20 @@ +package getjobs.modules.dict.infrastructure.provider.dto.zhilian; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * 智联招聘地铁/城市数据 + */ +public record ZhilianSubway( + @JsonProperty("code") String code, + @JsonProperty("parentCode") String parentCode, + @JsonProperty("name") String name, + @JsonProperty("en_name") String enName, + @JsonProperty("deleted") Boolean deleted, + @JsonProperty("longitude") Double longitude, + @JsonProperty("latitude") Double latitude, + @JsonProperty("sublist") List sublist +) { +} diff --git a/src/main/java/getjobs/modules/dict/service/DictFacade.java b/src/main/java/getjobs/modules/dict/service/DictFacade.java new file mode 100644 index 00000000..9c53d77d --- /dev/null +++ b/src/main/java/getjobs/modules/dict/service/DictFacade.java @@ -0,0 +1,27 @@ +package getjobs.modules.dict.service; + +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.dict.api.DictBundle; +import getjobs.modules.dict.api.DictGroup; +import getjobs.modules.dict.service.registry.DictProviderRegistry; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +public class DictFacade { + + private final DictProviderRegistry registry; + + public DictFacade(DictProviderRegistry registry) { + this.registry = registry; + } + + public DictBundle fetchAll(RecruitmentPlatformEnum platform) { + return registry.get(platform).fetchAll(); + } + + public Optional fetchByKey(RecruitmentPlatformEnum platform, String key) { + return registry.get(platform).fetchByKey(key); + } +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/dict/service/registry/DictProviderRegistry.java b/src/main/java/getjobs/modules/dict/service/registry/DictProviderRegistry.java new file mode 100644 index 00000000..7a4c135e --- /dev/null +++ b/src/main/java/getjobs/modules/dict/service/registry/DictProviderRegistry.java @@ -0,0 +1,26 @@ +package getjobs.modules.dict.service.registry; + +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.dict.domain.DictProvider; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +public class DictProviderRegistry { + + private final Map providerMap; + + public DictProviderRegistry(List providers) { + this.providerMap = providers.stream() + .collect(Collectors.toUnmodifiableMap(DictProvider::platform, p -> p)); + } + + public DictProvider get(RecruitmentPlatformEnum platform) { + var p = providerMap.get(platform); + if (p == null) throw new IllegalArgumentException("No DictProvider for platform: " + platform); + return p; + } +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/dict/web/DictController.java b/src/main/java/getjobs/modules/dict/web/DictController.java new file mode 100644 index 00000000..fd783aa4 --- /dev/null +++ b/src/main/java/getjobs/modules/dict/web/DictController.java @@ -0,0 +1,55 @@ +package getjobs.modules.dict.web; + +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.dict.api.DictBundle; +import getjobs.modules.dict.api.DictGroup; +import getjobs.modules.dict.service.DictFacade; +import getjobs.modules.dict.infrastructure.provider.Job51DictProviderImpl; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/dicts") +public class DictController { + + private final DictFacade dictFacade; + private final Job51DictProviderImpl job51DictProvider; + + public DictController(DictFacade dictFacade, Job51DictProviderImpl job51DictProvider) { + this.dictFacade = dictFacade; + this.job51DictProvider = job51DictProvider; + } + + /** + * 平台字典值 + * + * @param platform + * @return + */ + @GetMapping("/{platform}") + public DictBundle all(@PathVariable("platform") RecruitmentPlatformEnum platform) { + return dictFacade.fetchAll(platform); + } + + // 取某一分组(如 payTypeList) + @GetMapping("/{platform}/{key}") + public ResponseEntity group(@PathVariable("platform") RecruitmentPlatformEnum platform, + @PathVariable("key") String key) { + return dictFacade.fetchByKey(platform, key) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + /** + * 直接从配置中获取Job51字典数据 + * + * @return 字典数据包 + */ + @GetMapping("/job51/config") + public DictBundle job51FromConfig() { + return job51DictProvider.fetchFromConfig(); + } +} \ No newline at end of file diff --git a/src/main/java/getjobs/modules/job51/dto/Job51ApiResponse.java b/src/main/java/getjobs/modules/job51/dto/Job51ApiResponse.java new file mode 100644 index 00000000..1fdb3978 --- /dev/null +++ b/src/main/java/getjobs/modules/job51/dto/Job51ApiResponse.java @@ -0,0 +1,859 @@ +package getjobs.modules.job51.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Data; + +import java.util.List; + +/** + * 51Job API响应数据结构 + * + * @author getjobs + * @since v2.1.1 + */ +@Data +public class Job51ApiResponse { + + /** + * 响应状态 (1: 成功) + */ + @JsonProperty("status") + private String status; + + /** + * 响应消息 + */ + @JsonProperty("message") + private String message; + + /** + * 响应数据体 + */ + @JsonProperty("resultbody") + private Job51ResultBody resultbody; + + @Data + public static class Job51ResultBody { + + /** + * 搜索类型 + */ + @JsonProperty("searchType") + private Integer searchType; + + /** + * 引擎关键词类型 + */ + @JsonProperty("engineKeywordType") + private Integer engineKeywordType; + + /** + * 请求ID + */ + @JsonProperty("requestId") + private String requestId; + + /** + * 职位数据 + */ + @JsonProperty("job") + private Job51JobData job; + } + + @Data + public static class Job51JobData { + + /** + * 职位列表 + */ + @JsonProperty("items") + private List items; + + /** + * 总数量 + */ + @JsonProperty("totalCount") + private Integer totalCount; + + /** + * 请求ID + */ + @JsonProperty("requestId") + private String requestId; + + /** + * 策略ID + */ + @JsonProperty("policyId") + private String policyId; + + /** + * 策略类型 + */ + @JsonProperty("policyType") + private String policyType; + + /** + * 策略 + */ + @JsonProperty("policies") + private String policies; + + /** + * 总数量(备用字段) + */ + @JsonProperty("totalcount") + private Integer totalcount; + } + + @Data + public static class Job51JobItem { + + /** + * 属性信息 + */ + @JsonProperty("property") + @JsonDeserialize(using = Job51PropertyDeserializer.class) + private Job51Property property; + + /** + * 职位ID + */ + @JsonProperty("jobId") + private String jobId; + + /** + * 职位类型 + */ + @JsonProperty("jobType") + private String jobType; + + /** + * 职位名称 + */ + @JsonProperty("jobName") + private String jobName; + + /** + * 职位标签 + */ + @JsonProperty("jobTags") + private List jobTags; + + /** + * 职位数量字符串 + */ + @JsonProperty("jobNumString") + private String jobNumString; + + /** + * 工作区域代码 + */ + @JsonProperty("workAreaCode") + private String workAreaCode; + + /** + * 职位区域代码 + */ + @JsonProperty("jobAreaCode") + private String jobAreaCode; + + /** + * 职位区域字符串 + */ + @JsonProperty("jobAreaString") + private String jobAreaString; + + /** + * 区域拼音 + */ + @JsonProperty("hrefAreaPinYin") + private String hrefAreaPinYin; + + /** + * 职位区域级别详情 + */ + @JsonProperty("jobAreaLevelDetail") + private Job51AreaDetail jobAreaLevelDetail; + + /** + * 薪资字符串 + */ + @JsonProperty("provideSalaryString") + private String provideSalaryString; + + /** + * 发布日期字符串 + */ + @JsonProperty("issueDateString") + private String issueDateString; + + /** + * 确认日期字符串 + */ + @JsonProperty("confirmDateString") + private String confirmDateString; + + /** + * 工作年限 + */ + @JsonProperty("workYear") + private String workYear; + + /** + * 工作年限字符串 + */ + @JsonProperty("workYearString") + private String workYearString; + + /** + * 学历字符串 + */ + @JsonProperty("degreeString") + private String degreeString; + + /** + * 行业类型1 + */ + @JsonProperty("industryType1") + private String industryType1; + + /** + * 行业类型2 + */ + @JsonProperty("industryType2") + private String industryType2; + + /** + * 行业类型1字符串 + */ + @JsonProperty("industryType1Str") + private String industryType1Str; + + /** + * 行业类型2字符串 + */ + @JsonProperty("industryType2Str") + private String industryType2Str; + + /** + * 功能类型1代码 + */ + @JsonProperty("funcType1Code") + private String funcType1Code; + + /** + * 功能类型2代码 + */ + @JsonProperty("funcType2Code") + private String funcType2Code; + + /** + * 专业1字符串 + */ + @JsonProperty("major1Str") + private String major1Str; + + /** + * 专业2字符串 + */ + @JsonProperty("major2Str") + private String major2Str; + + /** + * 加密公司ID + */ + @JsonProperty("encCoId") + private String encCoId; + + /** + * 公司名称 + */ + @JsonProperty("companyName") + private String companyName; + + /** + * 完整公司名称 + */ + @JsonProperty("fullCompanyName") + private String fullCompanyName; + + /** + * 公司Logo + */ + @JsonProperty("companyLogo") + private String companyLogo; + + /** + * 公司类型字符串 + */ + @JsonProperty("companyTypeString") + private String companyTypeString; + + /** + * 公司规模字符串 + */ + @JsonProperty("companySizeString") + private String companySizeString; + + /** + * 公司规模代码 + */ + @JsonProperty("companySizeCode") + private String companySizeCode; + + /** + * 公司行业类型1字符串 + */ + @JsonProperty("companyIndustryType1Str") + private String companyIndustryType1Str; + + /** + * 公司行业类型2字符串 + */ + @JsonProperty("companyIndustryType2Str") + private String companyIndustryType2Str; + + /** + * HR用户ID + */ + @JsonProperty("hrUid") + private String hrUid; + + /** + * HR姓名 + */ + @JsonProperty("hrName") + private String hrName; + + /** + * HR小头像URL + */ + @JsonProperty("smallHrLogoUrl") + private String smallHrLogoUrl; + + /** + * HR职位 + */ + @JsonProperty("hrPosition") + private String hrPosition; + + /** + * HR活跃状态(绿色) + */ + @JsonProperty("hrActiveStatusGreen") + private String hrActiveStatusGreen; + + /** + * HR勋章标题 + */ + @JsonProperty("hrMedalTitle") + private String hrMedalTitle; + + /** + * HR勋章等级 + */ + @JsonProperty("hrMedalLevel") + private String hrMedalLevel; + + /** + * 是否显示HR勋章标题 + */ + @JsonProperty("showHrMedalTitle") + private Boolean showHrMedalTitle; + + /** + * HR是否在线 + */ + @JsonProperty("hrIsOnline") + private Boolean hrIsOnline; + + /** + * 是否在线 + */ + @JsonProperty("isOnline") + private Boolean isOnline; + + /** + * HR标签 + */ + @JsonProperty("hrLabels") + private List hrLabels; + + /** + * 更新日期时间 + */ + @JsonProperty("updateDateTime") + private String updateDateTime; + + /** + * 经度 + */ + @JsonProperty("lon") + private String lon; + + /** + * 纬度 + */ + @JsonProperty("lat") + private String lat; + + /** + * 是否沟通过 + */ + @JsonProperty("isCommunicate") + private Boolean isCommunicate; + + /** + * 是否来自学友汇 + */ + @JsonProperty("isFromXyx") + private Boolean isFromXyx; + + /** + * 是否实习 + */ + @JsonProperty("isIntern") + private Boolean isIntern; + + /** + * 是否模范雇主 + */ + @JsonProperty("isModelEmployer") + private Boolean isModelEmployer; + + /** + * 是否快速反馈 + */ + @JsonProperty("isQuickFeedback") + private Boolean isQuickFeedback; + + /** + * 是否推广 + */ + @JsonProperty("isPromotion") + private Boolean isPromotion; + + /** + * 是否申请 + */ + @JsonProperty("isApply") + private Boolean isApply; + + /** + * 是否过期 + */ + @JsonProperty("isExpire") + private Boolean isExpire; + + /** + * 职位链接 + */ + @JsonProperty("jobHref") + private String jobHref; + + /** + * 职位描述 + */ + @JsonProperty("jobDescribe") + private String jobDescribe; + + /** + * 公司链接 + */ + @JsonProperty("companyHref") + private String companyHref; + + /** + * 是否允许在线聊天 + */ + @JsonProperty("allowChatOnline") + private Boolean allowChatOnline; + + /** + * CTM ID + */ + @JsonProperty("ctmId") + private Long ctmId; + + /** + * 任期 + */ + @JsonProperty("term") + private String term; + + /** + * 任期字符串 + */ + @JsonProperty("termStr") + private String termStr; + + /** + * 地标ID + */ + @JsonProperty("landmarkId") + private String landmarkId; + + /** + * 地标字符串 + */ + @JsonProperty("landmarkString") + private String landmarkString; + + /** + * 检索器名称 + */ + @JsonProperty("retrieverName") + private String retrieverName; + + /** + * 扩展信息02 + */ + @JsonProperty("exrInfo02") + @JsonDeserialize(using = Job51ExrInfo02Deserializer.class) + private Job51ExrInfo02 exrInfo02; + + /** + * HR信息类型 + */ + @JsonProperty("hrInfoType") + private Integer hrInfoType; + + /** + * 是否远程工作 + */ + @JsonProperty("isRemoteWork") + private Boolean isRemoteWork; + + /** + * 是否允许联系 + */ + @JsonProperty("contactAllowed") + private String contactAllowed; + + /** + * 联系日期 + */ + @JsonProperty("contactDay") + private String contactDay; + + /** + * 联系时间 + */ + @JsonProperty("contactTime") + private String contactTime; + + /** + * 是否有HR手机号 + */ + @JsonProperty("hasHrMobile") + private Boolean hasHrMobile; + + /** + * 职位标签排序 + */ + @JsonProperty("jobTagsForOrder") + private List jobTagsForOrder; + + /** + * 职位标签列表 + */ + @JsonProperty("jobTagsList") + private List jobTagsList; + + /** + * 是否允许聊天 + */ + @JsonProperty("isAllowChat") + private Boolean isAllowChat; + + /** + * 芝麻标签列表 + */ + @JsonProperty("sesameLabelList") + private List sesameLabelList; + + /** + * 职位福利代码数据列表 + */ + @JsonProperty("jobWelfareCodeDataList") + private List jobWelfareCodeDataList; + + /** + * 职位最高薪资 + */ + @JsonProperty("jobSalaryMax") + private String jobSalaryMax; + + /** + * 职位最低薪资 + */ + @JsonProperty("jobSalaryMin") + private String jobSalaryMin; + + /** + * 是否转发职位 + */ + @JsonProperty("isReprintJob") + private String isReprintJob; + + /** + * 申请时间文本 + */ + @JsonProperty("applyTimeText") + private String applyTimeText; + + /** + * 触发批量投递 + */ + @JsonProperty("triggerBatchDeliver") + private Boolean triggerBatchDeliver; + + /** + * 职位发布类型 + */ + @JsonProperty("jobReleaseType") + private String jobReleaseType; + + /** + * 在线HR标签列表 + */ + @JsonProperty("onlineHrLabelList") + private List onlineHrLabelList; + + /** + * 显示类型URL + */ + @JsonProperty("showTypeUrl") + private String showTypeUrl; + + /** + * 职位方案 + */ + @JsonProperty("jobScheme") + private String jobScheme; + + /** + * 公司ID + */ + @JsonProperty("coId") + private String coId; + } + + @Data + public static class Job51AreaDetail { + + /** + * 省份代码 + */ + @JsonProperty("provinceCode") + private String provinceCode; + + /** + * 省份字符串 + */ + @JsonProperty("provinceString") + private String provinceString; + + /** + * 城市代码 + */ + @JsonProperty("cityCode") + private String cityCode; + + /** + * 城市字符串 + */ + @JsonProperty("cityString") + private String cityString; + + /** + * 区域字符串 + */ + @JsonProperty("districtString") + private String districtString; + + /** + * 地标字符串 + */ + @JsonProperty("landMarkString") + private String landMarkString; + } + + @Data + public static class Job51JobTag { + + /** + * 职位标签名称 + */ + @JsonProperty("jobTagName") + private String jobTagName; + } + + @Data + public static class Job51SesameLabel { + + /** + * 标签名称 + */ + @JsonProperty("labelName") + private String labelName; + + /** + * 标签翻译名称 + */ + @JsonProperty("labelTranslateName") + private String labelTranslateName; + + /** + * 标签代码 + */ + @JsonProperty("labelCode") + private String labelCode; + + /** + * 标签定义 + */ + @JsonProperty("labelDefinition") + private String labelDefinition; + } + + @Data + public static class Job51WelfareData { + + /** + * 代码 + */ + @JsonProperty("code") + private String code; + + /** + * 中文标题 + */ + @JsonProperty("chineseTitle") + private String chineseTitle; + + /** + * 英文标题 + */ + @JsonProperty("englishTitle") + private String englishTitle; + + /** + * 类型代码 + */ + @JsonProperty("typeCode") + private String typeCode; + + /** + * 类型标题 + */ + @JsonProperty("typeTitle") + private String typeTitle; + } + + @Data + public static class Job51Property { + + /** + * 是否主动 + */ + @JsonProperty("isInitiative") + private String isInitiative; + + /** + * 页面代码 + */ + @JsonProperty("pageCode") + private String pageCode; + + /** + * 短页面代码 + */ + @JsonProperty("shortPageCode") + private String shortPageCode; + + /** + * 搜索类型 + */ + @JsonProperty("searchType") + private String searchType; + + /** + * 职位排名 + */ + @JsonProperty("jobRank") + private String jobRank; + + /** + * 策略ID + */ + @JsonProperty("policyId") + private Object policyId; + + /** + * 关键词 + */ + @JsonProperty("keyword") + private String keyword; + + /** + * 页面编号 + */ + @JsonProperty("pageNum") + private String pageNum; + + /** + * 请求ID + */ + @JsonProperty("requestId") + private String requestId; + + /** + * 职位类型 + */ + @JsonProperty("jobType") + private String jobType; + } + + @Data + public static class Job51ExrInfo02 { + + /** + * 检索器名称 + */ + @JsonProperty("retrieverName") + private String retrieverName; + + /** + * 参考职位ID + */ + @JsonProperty("referJobId") + private String referJobId; + + /** + * 意图 + */ + @JsonProperty("intentions") + private String intentions; + + /** + * 广告扩展功能 + */ + @JsonProperty("adExtendFunc") + private String adExtendFunc; + + /** + * 广告扩展城市 + */ + @JsonProperty("adExtendCity") + private String adExtendCity; + + /** + * 工作功能混合标签结果扩展信息 + */ + @JsonProperty("workFuncMixedLabelResultExrInfo") + private String workFuncMixedLabelResultExrInfo; + } +} diff --git a/src/main/java/getjobs/modules/job51/dto/Job51ExrInfo02Deserializer.java b/src/main/java/getjobs/modules/job51/dto/Job51ExrInfo02Deserializer.java new file mode 100644 index 00000000..d4619df1 --- /dev/null +++ b/src/main/java/getjobs/modules/job51/dto/Job51ExrInfo02Deserializer.java @@ -0,0 +1,62 @@ +package getjobs.modules.job51.dto; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; + +/** + * Job51ExrInfo02自定义反序列化器 + * 用于处理exrInfo02字段可能是JSON字符串或对象的情况 + * + * @author getjobs + * @since v2.1.1 + */ +@Slf4j +public class Job51ExrInfo02Deserializer extends JsonDeserializer { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public Job51ApiResponse.Job51ExrInfo02 deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + + try { + // 检查当前token类型 + switch (p.getCurrentToken()) { + case START_OBJECT: + // 如果是对象,直接解析 + return objectMapper.readValue(p, Job51ApiResponse.Job51ExrInfo02.class); + + case VALUE_STRING: + // 如果是字符串,先获取字符串值,然后解析为对象 + String jsonString = p.getValueAsString(); + if (jsonString != null && !jsonString.trim().isEmpty()) { + try { + return objectMapper.readValue(jsonString, Job51ApiResponse.Job51ExrInfo02.class); + } catch (Exception e) { + log.warn("无法解析exrInfo02 JSON字符串: {}", jsonString, e); + return null; + } + } + break; + + case VALUE_NULL: + return null; + + default: + log.warn("exrInfo02字段的token类型不支持: {}", p.getCurrentToken()); + break; + } + + return null; + } catch (Exception e) { + log.error("反序列化Job51ExrInfo02时发生错误", e); + return null; + } + } +} diff --git a/src/main/java/getjobs/modules/job51/dto/Job51PropertyDeserializer.java b/src/main/java/getjobs/modules/job51/dto/Job51PropertyDeserializer.java new file mode 100644 index 00000000..7365e3a7 --- /dev/null +++ b/src/main/java/getjobs/modules/job51/dto/Job51PropertyDeserializer.java @@ -0,0 +1,62 @@ +package getjobs.modules.job51.dto; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; + +/** + * Job51Property自定义反序列化器 + * 用于处理property字段可能是JSON字符串或对象的情况 + * + * @author getjobs + * @since v2.1.1 + */ +@Slf4j +public class Job51PropertyDeserializer extends JsonDeserializer { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public Job51ApiResponse.Job51Property deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + + try { + // 检查当前token类型 + switch (p.getCurrentToken()) { + case START_OBJECT: + // 如果是对象,直接解析 + return objectMapper.readValue(p, Job51ApiResponse.Job51Property.class); + + case VALUE_STRING: + // 如果是字符串,先获取字符串值,然后解析为对象 + String jsonString = p.getValueAsString(); + if (jsonString != null && !jsonString.trim().isEmpty()) { + try { + return objectMapper.readValue(jsonString, Job51ApiResponse.Job51Property.class); + } catch (Exception e) { + log.warn("无法解析property JSON字符串: {}", jsonString, e); + return null; + } + } + break; + + case VALUE_NULL: + return null; + + default: + log.warn("property字段的token类型不支持: {}", p.getCurrentToken()); + break; + } + + return null; + } catch (Exception e) { + log.error("反序列化Job51Property时发生错误", e); + return null; + } + } +} diff --git a/src/main/java/getjobs/modules/job51/service/Job51ElementLocators.java b/src/main/java/getjobs/modules/job51/service/Job51ElementLocators.java new file mode 100644 index 00000000..e849b3b4 --- /dev/null +++ b/src/main/java/getjobs/modules/job51/service/Job51ElementLocators.java @@ -0,0 +1,569 @@ +package getjobs.modules.job51.service; + +import com.microsoft.playwright.Page; +import com.microsoft.playwright.Locator; +import lombok.extern.slf4j.Slf4j; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ArrayList; + +/** + * 51Job网站元素定位器 + * 用于定位51Job网站上的各种元素 + */ +@Slf4j +public class Job51ElementLocators { + /** + * 判断用户是否已登录 + * 通过检查header-right区块中是否存在"登录/注册"按钮来判断登录状态 + * 未登录状态:存在"登录/注册"按钮 + * 已登录状态:不存在"登录/注册"按钮 + * + * @param page Playwright页面对象 + * @return true表示已登录,false表示未登录 + */ + public static boolean isUserLoggedIn(Page page) { + try { + // 查找包含"登录/注册"文本的span元素 + var loginButton = page.locator("span.login.loginBtnClick:has-text('登录/注册')"); + + // 如果不存在"登录/注册"按钮,说明用户已登录 + return loginButton.count() == 0; + } catch (Exception e) { + // 如果出现异常,默认认为未登录 + return false; + } + } + + + /** + * 获取用户信息 + * 从class="user"块中提取用户名等信息 + * + * @param page Playwright页面对象 + * @return 用户名,如果未登录或获取失败则返回null + */ + public static String getUserName(Page page) { + try { + var userDiv = page.locator("div.user"); + var userNameElement = userDiv.locator("a.uname"); + + if (userNameElement.count() > 0) { + return userNameElement.textContent(); + } + return null; + } catch (Exception e) { + return null; + } + } + + /** + * 筛选条件信息类 + * 用于存储每个筛选条件的标签和对应的选项 + */ + public static class FilterOption { + private String label; // 筛选条件标签(如:工作地点、月薪范围等) + private String text; // span元素的文本内容 + private Locator spanElement; // span元素定位器 + private Locator clistElement; // clist容器元素定位器 + + public FilterOption(String label, String text, Locator spanElement, Locator clistElement) { + this.label = label; + this.text = text; + this.spanElement = spanElement; + this.clistElement = clistElement; + } + + // Getters + public String getLabel() { + return label; + } + + public String getText() { + return text; + } + + public Locator getSpanElement() { + return spanElement; + } + + public Locator getClistElement() { + return clistElement; + } + } + + /** + * 获取页面中class="j_filter"下所有筛选条件元素 + * 解析每个fbox中的label和clist,获取clist下所有span元素的文本内容 + * + * @param page Playwright页面对象 + * @return 筛选条件映射表,key为label文本,value为该label下的所有选项列表 + */ + public static Map> getFilterOptions(Page page) { + Map> filterOptionsMap = new HashMap<>(); + + try { + // 获取class="j_filter"下的所有div class="fbox"元素 + var fboxElements = page.locator("div.j_filter div.fbox"); + int fboxCount = fboxElements.count(); + + for (int i = 0; i < fboxCount; i++) { + var fbox = fboxElements.nth(i); + + // 获取fbox下的label元素 + var labelElement = fbox.locator("div.label"); + if (labelElement.count() > 0) { + String labelText = labelElement.textContent().trim(); + + // 获取fbox下的clist元素 + var clistElement = fbox.locator("div.clist"); + if (clistElement.count() > 0) { + List options = new ArrayList<>(); + + // 获取clist下的所有span元素 + var spanElements = clistElement.locator("span"); + int spanCount = spanElements.count(); + + for (int j = 0; j < spanCount; j++) { + var spanElement = spanElements.nth(j); + String spanText = spanElement.textContent().trim(); + + // 创建FilterOption对象 + FilterOption option = new FilterOption(labelText, spanText, spanElement, clistElement); + options.add(option); + } + + // 将选项列表添加到映射表中 + filterOptionsMap.put(labelText, options); + } + } + } + + } catch (Exception e) { + log.error("获取筛选条件时发生错误: {}", e.getMessage()); + } + + return filterOptionsMap; + } + + /** + * 根据标签和文本内容查找对应的span元素 + * + * @param page Playwright页面对象 + * @param labelText 筛选条件标签文本(如:"工作地点:") + * @param optionText 要查找的选项文本(如:"广州") + * @return 匹配的span元素定位器,如果未找到则返回null + */ + public static Locator findSpanByLabelAndText(Page page, String labelText, String optionText) { + try { + // 获取class="j_filter"下的所有div class="fbox"元素 + var fboxElements = page.locator("div.j_filter div.fbox"); + int fboxCount = fboxElements.count(); + + for (int i = 0; i < fboxCount; i++) { + var fbox = fboxElements.nth(i); + + // 检查label是否匹配 + var labelElement = fbox.locator("div.label"); + if (labelElement.count() > 0) { + String currentLabelText = labelElement.textContent().trim(); + + if (labelText.equals(currentLabelText)) { + // 找到匹配的label,在其clist中查找span + var clistElement = fbox.locator("div.clist"); + if (clistElement.count() > 0) { + var spanElements = clistElement.locator("span"); + int spanCount = spanElements.count(); + + for (int j = 0; j < spanCount; j++) { + var spanElement = spanElements.nth(j); + String spanText = spanElement.textContent().trim(); + + if (optionText.equals(spanText)) { + return spanElement; + } + } + } + } + } + } + + } catch (Exception e) { + log.error("查找span元素时发生错误: {}", e.getMessage()); + } + + return null; + } + + /** + * 获取指定ID的input元素并模拟用户输入文本 + * 输入完成后自动按回车键确认 + * + * @param page Playwright页面对象 + * @param inputId input元素的ID属性值 + * @param inputText 要输入的文本内容 + * @return true表示输入成功,false表示输入失败 + */ + public static boolean inputTextAndSubmit(Page page, String inputId, String inputText) { + try { + // 根据ID定位input元素 + var inputElement = page.locator("#" + inputId); + + // 检查元素是否存在 + if (inputElement.count() == 0) { + log.error("未找到ID为 '{}' 的input元素", inputId); + return false; + } + + // 清空输入框内容(如果有的话) + inputElement.clear(); + + // 模拟用户输入文本 + inputElement.fill(inputText); + + // 等待一小段时间确保输入完成 + page.waitForTimeout(500); + + // 按回车键提交 + inputElement.press("Enter"); + + log.info("成功在ID为 '{}' 的input元素中输入文本: {}", inputId, inputText); + return true; + + } catch (Exception e) { + log.error("输入文本时发生错误: {}", e.getMessage()); + return false; + } + } + + /** + * 专门用于keywordInput输入框的方法 + * 获取id="keywordInput"的input元素并模拟用户录入输入对应的文本并回车确定 + * + * @param page Playwright页面对象 + * @param keyword 要搜索的关键词 + * @return true表示输入成功,false表示输入失败 + */ + public static boolean inputKeywordAndSearch(Page page, String keyword) { + return inputTextAndSubmit(page, "keywordInput", keyword); + } + + /** + * 判断首页是否存在登录元素 + * 通过检查class="login loginBtnClick"的span元素来判断是否存在登录按钮 + * + * @param page Playwright页面对象 + * @return true表示存在登录元素,false表示不存在 + */ + public static boolean hasLoginElement(Page page) { + try { + // 查找class="login loginBtnClick"的span元素 + var loginElement = page.locator("span.login.loginBtnClick"); + + // 检查元素是否存在且文本内容为"登录/注册" + if (loginElement.count() > 0) { + String elementText = loginElement.textContent(); + return "登录/注册".equals(elementText); + } + + return false; + } catch (Exception e) { + log.error("检查登录元素时发生错误: {}", e.getMessage()); + return false; + } + } + + /** + * 根据页码点击分页元素 + * 在class="el-pager"的ul元素中查找指定页码的li元素并点击 + * + * @param page Playwright页面对象 + * @param pageNumber 要点击的页码 + * @return true表示点击成功,false表示未找到对应页码或点击失败 + */ + public static boolean clickPageNumber(Page page, int pageNumber) { + try { + // 首先等待分页元素出现 + try { + page.waitForSelector("ul.el-pager", new Page.WaitForSelectorOptions().setTimeout(8000)); + } catch (Exception e) { + log.error("等待分页元素出现超时: {}", e.getMessage()); + return false; + } + + // 查找class="el-pager"的ul元素 + var pagerElement = page.locator("ul.el-pager"); + + if (pagerElement.count() == 0) { + log.error("未找到分页元素 ul.el-pager"); + return false; + } + + // 查找包含指定页码文本的li元素 +// var pageElement = pagerElement.locator("li.number").filter( +// new Locator.FilterOptions().setHasText(String.valueOf(pageNumber)) +// ); + /** + * Error { + * message='Error: strict mode violation: locator("ul.el-pager").locator("li.number").filter(new Locator.FilterOptions().setHasText("2")) resolved to 2 elements: + * 1)
  • 2
  • aka getByText("2", new Page.GetByTextOptions().setExact(true)) + * 2)
  • 25
  • aka getByText("25", new Page.GetByTextOptions().setExact(true)) + */ + var pageElement = pagerElement.locator("li.number"). + getByText(String.valueOf(pageNumber), new Locator.GetByTextOptions().setExact(true)); + + + + if (pageElement.count() == 0) { + log.info("未找到页码为 {} 的分页元素,可能已到达最后一页", pageNumber); + return false; + } + + // 检查元素是否已经是当前激活状态 + var activePageElement = pagerElement.locator("li.number.active"); + if (activePageElement.count() > 0) { + String activePageText = activePageElement.textContent().trim(); + if (String.valueOf(pageNumber).equals(activePageText)) { + log.info("页码 {} 已经是当前激活状态,无需点击", pageNumber); + return true; + } + } + + // 确保元素可见且可点击 + pageElement.scrollIntoViewIfNeeded(); + + // 点击页码元素 + pageElement.click(); + + // 等待页面状态变化,确保点击生效 + try { + // 等待当前页码变为激活状态 + page.waitForSelector("ul.el-pager li.number.active:has-text('" + pageNumber + "')", + new Page.WaitForSelectorOptions().setTimeout(5000)); + log.info("成功点击页码: {},页面已切换", pageNumber); + } catch (Exception e) { + log.warn("等待页码 {} 激活状态超时,但点击操作已执行: {}", pageNumber, e.getMessage()); + } + + return true; + + } catch (Exception e) { + log.error("点击页码 {} 时发生错误: {}", pageNumber, e.getMessage()); + return false; + } + } + + /** + * 获取当前激活的页码 + * 查找class="el-pager"中带有"active"类的li元素,获取其页码值 + * + * @param page Playwright页面对象 + * @return 当前激活的页码,如果未找到则返回-1 + */ + public static int getCurrentPageNumber(Page page) { + try { + // 查找class="el-pager"的ul元素 + var pagerElement = page.locator("ul.el-pager"); + + if (pagerElement.count() == 0) { + log.error("未找到分页元素 ul.el-pager"); + return -1; + } + + // 查找当前激活的页码元素 + var activePageElement = pagerElement.locator("li.number.active"); + + if (activePageElement.count() > 0) { + String pageText = activePageElement.textContent().trim(); + try { + int currentPage = Integer.parseInt(pageText); + log.info("当前激活页码: {}", currentPage); + return currentPage; + } catch (NumberFormatException e) { + log.error("解析当前页码失败: {}", pageText); + return -1; + } + } + + log.error("未找到当前激活的页码元素"); + return -1; + + } catch (Exception e) { + log.error("获取当前页码时发生错误: {}", e.getMessage()); + return -1; + } + } + + /** + * 获取所有可见的页码列表 + * 从class="el-pager"的ul元素中获取所有li.number元素的页码值 + * + * @param page Playwright页面对象 + * @return 所有可见页码的列表,如果出错则返回空列表 + */ + public static List getVisiblePageNumbers(Page page) { + List pageNumbers = new ArrayList<>(); + + try { + // 查找class="el-pager"的ul元素 + var pagerElement = page.locator("ul.el-pager"); + + if (pagerElement.count() == 0) { + log.error("未找到分页元素 ul.el-pager"); + return pageNumbers; + } + + // 获取所有页码元素 + var pageElements = pagerElement.locator("li.number"); + int elementCount = pageElements.count(); + + for (int i = 0; i < elementCount; i++) { + var pageElement = pageElements.nth(i); + String pageText = pageElement.textContent().trim(); + + try { + int pageNumber = Integer.parseInt(pageText); + pageNumbers.add(pageNumber); + } catch (NumberFormatException e) { + log.warn("跳过非数字页码: {}", pageText); + } + } + + log.info("获取到可见页码列表: {}", pageNumbers); + + } catch (Exception e) { + log.error("获取可见页码列表时发生错误: {}", e.getMessage()); + } + + return pageNumbers; + } + + /** + * 判断登录页是否存在密码登录元素 + * 通过检查class="login_qr"的div元素来判断是否在登录页面 + * 存在表示未登录,需要进行登录操作 + * + * @param page Playwright页面对象 + * @return true表示存在密码登录元素(未登录状态),false表示不存在(已登录或非登录页) + */ + public static boolean hasPasswordLoginElement(Page page) { + try { + // 查找class="login_qr"的div元素 + var loginQrElement = page.locator("div.login_qr"); + + // 检查元素是否存在 + if (loginQrElement.count() > 0) { + // 进一步验证内部结构,确保这是登录页面的二维码登录区域 + var qrIniElement = loginQrElement.locator("div.qrini"); + var titleElement = qrIniElement.locator("p.tit"); + + if (qrIniElement.count() > 0 && titleElement.count() > 0) { + // 检查标题文本是否包含"微信扫码登录" + String titleText = titleElement.textContent(); + if (titleText != null && titleText.contains("微信扫码登录")) { + log.info("检测到登录页面的密码登录元素,用户未登录"); + return true; + } + } + } + + log.info("未检测到登录页面的密码登录元素"); + return false; + + } catch (Exception e) { + log.error("检查密码登录元素时发生错误: {}", e.getMessage()); + return false; + } + } + + /** + * 点击岗位详情页的申请职位按钮 + * 定位并点击class="but_sq"且id="app_ck"的申请职位链接 + * + * @param page Playwright页面对象 + * @return true表示点击成功,false表示未找到申请按钮或点击失败 + */ + public static boolean clickApplyJobButton(Page page) { + try { + // 等待页面加载完成,给申请职位按钮充分的加载时间 + try { + page.waitForSelector("a.but_sq#app_ck", new Page.WaitForSelectorOptions().setTimeout(5000)); + } catch (Exception e) { + log.warn("等待申请职位按钮加载超时(5秒),按钮可能不存在: {}", e.getMessage()); + return false; + } + + // 查找申请职位按钮:class="but_sq" 且 id="app_ck" + var applyButton = page.locator("a.but_sq#app_ck"); + + // 再次检查元素是否存在(经过等待后) + if (applyButton.count() == 0) { + log.error("等待后仍未找到申请职位按钮"); + return false; + } + + // 验证按钮文本内容是否包含"申请职位" + String buttonText = applyButton.textContent(); + if (buttonText == null || !buttonText.contains("申请职位")) { + log.error("找到元素但文本内容不匹配,实际文本: {}", buttonText); + return false; + } + + // 确保元素可见 + applyButton.scrollIntoViewIfNeeded(); + + // 点击申请职位按钮 + applyButton.click(); + + log.info("成功点击申请职位按钮"); + + // 等待一小段时间让页面响应点击事件 + page.waitForTimeout(1000); + + return true; + + } catch (Exception e) { + log.error("点击申请职位按钮时发生错误: {}", e.getMessage()); + return false; + } + } + + /** + * 检查岗位详情页是否存在申请职位按钮 + * 通过检查class="but_sq"且id="app_ck"的元素来判断是否在岗位详情页 + * + * @param page Playwright页面对象 + * @return true表示存在申请职位按钮,false表示不存在 + */ + public static boolean hasApplyJobButton(Page page) { + try { + // 等待页面加载完成,给申请职位按钮充分的加载时间 + try { + page.waitForSelector("a.but_sq#app_ck", new Page.WaitForSelectorOptions().setTimeout(5000)); + } catch (Exception e) { + log.info("等待申请职位按钮加载超时(5秒),按钮可能不存在: {}", e.getMessage()); + return false; + } + + // 查找申请职位按钮 + var applyButton = page.locator("a.but_sq#app_ck"); + + if (applyButton.count() > 0) { + // 验证按钮文本内容 + String buttonText = applyButton.textContent(); + boolean hasCorrectText = buttonText != null && buttonText.contains("申请职位"); + + log.info("检测到申请职位按钮,文本内容: {}, 文本匹配: {}", buttonText, hasCorrectText); + return hasCorrectText; + } + + log.info("未检测到申请职位按钮"); + return false; + + } catch (Exception e) { + log.error("检查申请职位按钮时发生错误: {}", e.getMessage()); + return false; + } + } +} diff --git a/src/main/java/getjobs/modules/job51/service/Job51TaskService.java b/src/main/java/getjobs/modules/job51/service/Job51TaskService.java new file mode 100644 index 00000000..4c9a396e --- /dev/null +++ b/src/main/java/getjobs/modules/job51/service/Job51TaskService.java @@ -0,0 +1,604 @@ +package getjobs.modules.job51.service; + +import getjobs.common.dto.ConfigDTO; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.boss.dto.JobDTO; +import getjobs.common.enums.JobStatusEnum; +import getjobs.repository.entity.JobEntity; +import getjobs.repository.JobRepository; +import getjobs.service.JobService; +import getjobs.service.PlaywrightManager; +import getjobs.modules.task.dto.TaskUpdatePayload; +import getjobs.modules.task.enums.TaskStage; +import getjobs.modules.task.enums.TaskStatus; +import getjobs.modules.task.event.TaskUpdateEvent; +import getjobs.service.RecruitmentService; +import getjobs.service.RecruitmentServiceFactory; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +import jakarta.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * 51job任务服务 - 将4个核心操作分离为独立的服务方法 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Slf4j +@Service +public class Job51TaskService { + + + private final RecruitmentServiceFactory serviceFactory; + + private final JobService jobService; + + private final JobRepository jobRepository; + private final ApplicationEventPublisher eventPublisher; + + // 数据目录路径 + private String dataPath; + + public Job51TaskService(RecruitmentServiceFactory serviceFactory, + JobService jobService, JobRepository jobRepository, ApplicationEventPublisher eventPublisher) { + this.serviceFactory = serviceFactory; + this.jobService = jobService; + this.jobRepository = jobRepository; + this.eventPublisher = eventPublisher; + } + + @PostConstruct + public void init() { + try { + initializeDataFiles(); + } catch (IOException e) { + log.error("数据文件初始化失败", e); + } + } + + /** + * 1. 登录操作 + * + * @param config 配置信息 + * @return 登录结果 + */ + public LoginResult login(ConfigDTO config) { + publishTaskUpdate(TaskStage.LOGIN, TaskStatus.STARTED, 0, "开始登录"); + try { + log.info("开始执行51job登录操作"); + + // 获取51job服务 + RecruitmentService job51Service = serviceFactory.getService(RecruitmentPlatformEnum.JOB_51); + + // 执行登录 + boolean success = job51Service.login(config); + + LoginResult result = new LoginResult(); + result.setSuccess(success); + result.setMessage(success ? "登录成功" : "登录失败"); + result.setTimestamp(new Date()); + + publishTaskUpdate(TaskStage.LOGIN, success ? TaskStatus.SUCCESS : TaskStatus.FAILURE, 0, result.getMessage()); + + log.info("51job登录操作完成,结果: {}", success ? "成功" : "失败"); + return result; + + } catch (Exception e) { + log.error("51job登录操作执行失败", e); + publishTaskUpdate(TaskStage.LOGIN, TaskStatus.FAILURE, 0, "登录异常: " + e.getMessage()); + + LoginResult result = new LoginResult(); + result.setSuccess(false); + result.setMessage("登录异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + /** + * 2. 采集操作 + * + * @param config 配置信息 + * @return 采集结果 + */ + public CollectResult collectJobs(ConfigDTO config) { + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.STARTED, 0, "开始采集"); + try { + log.info("开始执行51job岗位采集操作"); + + // 获取51job服务 + RecruitmentService job51Service = serviceFactory.getService(RecruitmentPlatformEnum.JOB_51); + + // 采集岗位 + List allJobDTOS = new ArrayList<>(); + + // 采集搜索岗位 + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.IN_PROGRESS, 0, "正在采集搜索岗位"); + List searchJobDTOS = job51Service.collectJobs(config); + allJobDTOS.addAll(searchJobDTOS); + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.IN_PROGRESS, allJobDTOS.size(), "已采集 " + allJobDTOS.size() + " 个岗位"); + + // 保存到数据库 + int savedCount = 0; + if (!allJobDTOS.isEmpty()) { + try { + savedCount = jobService.saveJobs(allJobDTOS, RecruitmentPlatformEnum.JOB_51.name()); + log.info("成功保存 {} 个岗位到数据库", savedCount); + } catch (Exception e) { + log.error("保存岗位到数据库失败", e); + // 即使数据库保存失败,也不影响采集结果的返回 + } + } + + CollectResult result = new CollectResult(); + result.setJobCount(allJobDTOS.size()); + result.setJobs(allJobDTOS); + String message = String.format("成功采集到 %d 个岗位,保存到数据库 %d 个", allJobDTOS.size(), savedCount); + result.setMessage(message); + result.setTimestamp(new Date()); + + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.SUCCESS, allJobDTOS.size(), message); + + log.info("51job岗位采集操作完成,采集到 {} 个岗位,保存到数据库 {} 个", allJobDTOS.size(), savedCount); + return result; + + } catch (Exception e) { + log.error("51job岗位采集操作执行失败", e); + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.FAILURE, 0, "采集异常: " + e.getMessage()); + + CollectResult result = new CollectResult(); + result.setJobCount(0); + result.setJobs(new ArrayList<>()); + result.setMessage("采集异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + /** + * 3. 过滤操作 + * + * @param config 配置信息 + * @return 过滤结果 + */ + public FilterResult filterJobs(ConfigDTO config) { + publishTaskUpdate(TaskStage.FILTER, TaskStatus.STARTED, 0, "开始过滤"); + try { + log.info("开始执行51job岗位过滤操作"); + + RecruitmentService job51Service = serviceFactory.getService(RecruitmentPlatformEnum.JOB_51); + + // 直接从数据库查询51job平台的所有职位实体 + List allJobEntities = jobService.findAllJobEntitiesByPlatform( + RecruitmentPlatformEnum.JOB_51.getPlatformCode()); + if (allJobEntities == null || allJobEntities.isEmpty()) { + throw new IllegalArgumentException("数据库中未找到职位数据或职位数据为空"); + } + + // 执行过滤逻辑,获取过滤原因 + List filteredJobDTOS = new ArrayList<>(); + List filteredJobIds = new ArrayList<>(); + List filterReasons = new ArrayList<>(); + + List jobDTOS = new ArrayList<>(); + for (JobEntity entity : allJobEntities) { + JobDTO job = jobService.convertToDTO(entity); + jobDTOS.add(job); + } + List filterJobs = job51Service.filterJobs(jobDTOS, config); + filterJobs.forEach(job -> { + String filterReason = job.getFilterReason(); + if (filterReason == null) { + // 通过过滤 + filteredJobDTOS.add(job); + } else { + // 被过滤,记录原因 + filteredJobIds.add(job.getEncryptJobId()); + filterReasons.add(filterReason); + } + }); + + // 批量更新被过滤的职位状态 + if (!filteredJobIds.isEmpty()) { + // 按过滤原因分组更新 + Map> reasonGroups = new HashMap<>(); + for (int i = 0; i < filteredJobIds.size(); i++) { + String reason = filterReasons.get(i); + String encryptJobId = filteredJobIds.get(i); + reasonGroups.computeIfAbsent(reason, k -> new ArrayList<>()).add(encryptJobId); + } + + for (Map.Entry> entry : reasonGroups.entrySet()) { + jobService.updateJobStatus(entry.getValue(), JobStatusEnum.FILTERED.getCode(), entry.getKey()); + } + } + + FilterResult result = new FilterResult(); + result.setOriginalCount(allJobEntities.size()); + result.setFilteredCount(filteredJobDTOS.size()); + result.setJobs(filteredJobDTOS); + String message = String.format("原始岗位 %d 个,过滤后剩余 %d 个,已过滤 %d 个", + allJobEntities.size(), filteredJobDTOS.size(), filteredJobIds.size()); + result.setMessage(message); + result.setTimestamp(new Date()); + + publishTaskUpdate(TaskStage.FILTER, TaskStatus.SUCCESS, filteredJobDTOS.size(), message); + + log.info("51job岗位过滤操作完成,原始 {} 个,过滤后 {} 个,已过滤 {} 个", + allJobEntities.size(), filteredJobDTOS.size(), filteredJobIds.size()); + return result; + + } catch (Exception e) { + log.error("51job岗位过滤操作执行失败", e); + publishTaskUpdate(TaskStage.FILTER, TaskStatus.FAILURE, 0, "过滤异常: " + e.getMessage()); + + FilterResult result = new FilterResult(); + result.setOriginalCount(0); + result.setFilteredCount(0); + result.setJobs(new ArrayList<>()); + result.setMessage("过滤异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + /** + * 4. 投递操作 + * + * @param config 配置信息 + * @param enableActualDelivery 是否启用实际投递 + * @return 投递结果 + */ + public DeliveryResult deliverJobs(ConfigDTO config, boolean enableActualDelivery) { + publishTaskUpdate(TaskStage.DELIVER, TaskStatus.STARTED, 0, "开始投递"); + try { + log.info("开始执行51job岗位投递操作,实际投递: {}", enableActualDelivery); + + // 从数据库获取待处理状态的51job平台岗位记录 + List jobEntities = jobRepository.findByStatusAndPlatform( + JobStatusEnum.PENDING.getCode(), + RecruitmentPlatformEnum.JOB_51.getPlatformCode()); + if (jobEntities == null || jobEntities.isEmpty()) { + throw new IllegalArgumentException("未找到可投递的51job岗位记录,数据库中没有待处理状态的51job岗位"); + } + + // 转换为JobDTO + List filteredJobDTOS = jobEntities.stream() + .map(jobService::convertToDTO) + .collect(Collectors.toList()); + + int deliveredCount = 0; + + if (enableActualDelivery) { + // 获取51job服务 + RecruitmentService job51Service = serviceFactory.getService(RecruitmentPlatformEnum.JOB_51); + + // 执行实际投递 + deliveredCount = job51Service.deliverJobs(filteredJobDTOS, config); + + // 保存数据 + job51Service.saveData(dataPath); + + log.info("实际投递完成,成功投递 {} 个岗位", deliveredCount); + } else { + // 仅模拟投递 + deliveredCount = filteredJobDTOS.size(); + log.info("模拟投递完成,可投递岗位 {} 个", deliveredCount); + } + + DeliveryResult result = new DeliveryResult(); + result.setTotalCount(filteredJobDTOS.size()); + result.setDeliveredCount(deliveredCount); + result.setActualDelivery(enableActualDelivery); + String message = String.format("%s完成,处理 %d 个岗位", + enableActualDelivery ? "实际投递" : "模拟投递", deliveredCount); + result.setMessage(message); + result.setTimestamp(new Date()); + + // 显示岗位详情 + if (filteredJobDTOS.size() <= 10) { + result.setJobDetails(buildJobDetails(filteredJobDTOS)); + } + + publishTaskUpdate(TaskStage.DELIVER, TaskStatus.SUCCESS, deliveredCount, message); + + log.info("51job岗位投递操作完成,处理 {} 个岗位", deliveredCount); + return result; + + } catch (Exception e) { + log.error("51job岗位投递操作执行失败", e); + publishTaskUpdate(TaskStage.DELIVER, TaskStatus.FAILURE, 0, "投递异常: " + e.getMessage()); + + DeliveryResult result = new DeliveryResult(); + result.setTotalCount(0); + result.setDeliveredCount(0); + result.setActualDelivery(enableActualDelivery); + result.setMessage("投递异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + private void publishTaskUpdate(TaskStage stage, TaskStatus status, Integer count, String message) { + TaskUpdatePayload payload = TaskUpdatePayload.builder() + .platform(RecruitmentPlatformEnum.JOB_51) + .stage(stage) + .status(status) + .count(count) + .message(message) + .build(); + eventPublisher.publishEvent(new TaskUpdateEvent(this, payload)); + } + + private List buildJobDetails(List jobDTOS) { + return jobDTOS.stream() + .map(job -> String.format("%s - %s | %s | %s", + job.getCompanyName(), + job.getJobName(), + job.getSalary() != null ? job.getSalary() : "薪资未知", + job.getJobArea() != null ? job.getJobArea() : "地区未知")) + .collect(Collectors.toList()); + } + + private void initializeDataFiles() throws IOException { + // 初始化工作目录为用户home目录下的getjobs目录 + String userHome = System.getProperty("user.home"); + dataPath = userHome + File.separator + "getjobs"; + + log.info("初始化工作目录: {}", dataPath); + + // 检查getjobs目录是否存在,不存在则创建 + File getJobsDir = new File(dataPath); + if (!getJobsDir.exists()) { + boolean created = getJobsDir.mkdirs(); + if (created) { + log.info("成功创建getjobs目录: {}", dataPath); + } else { + log.error("创建getjobs目录失败: {}", dataPath); + throw new IOException("无法创建getjobs目录: " + dataPath); + } + } else { + log.info("getjobs目录已存在: {}", dataPath); + } + + // 检查并创建data子目录 + File dataDir = new File(dataPath, "data"); + if (!dataDir.exists()) { + boolean created = dataDir.mkdirs(); + if (created) { + log.info("成功创建data子目录: {}", dataDir.getAbsolutePath()); + } else { + log.error("创建data子目录失败: {}", dataDir.getAbsolutePath()); + throw new IOException("无法创建data子目录: " + dataDir.getAbsolutePath()); + } + } else { + log.info("data子目录已存在: {}", dataDir.getAbsolutePath()); + } + + // 更新dataPath为实际的data目录路径 + dataPath = dataDir.getAbsolutePath(); + log.info("数据文件目录设置为: {}", dataPath); + } + + public static class LoginResult { + private String taskId; + private boolean success; + private String message; + private Date timestamp; + + // getters and setters + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + } + + public static class CollectResult { + private String taskId; + private int jobCount; + private List jobDTOS; + private String message; + private Date timestamp; + + // getters and setters + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public int getJobCount() { + return jobCount; + } + + public void setJobCount(int jobCount) { + this.jobCount = jobCount; + } + + public List getJobs() { + return jobDTOS; + } + + public void setJobs(List jobDTOS) { + this.jobDTOS = jobDTOS; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + } + + public static class FilterResult { + private String taskId; + private int originalCount; + private int filteredCount; + private List jobDTOS; + private String message; + private Date timestamp; + + // getters and setters + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public int getOriginalCount() { + return originalCount; + } + + public void setOriginalCount(int originalCount) { + this.originalCount = originalCount; + } + + public int getFilteredCount() { + return filteredCount; + } + + public void setFilteredCount(int filteredCount) { + this.filteredCount = filteredCount; + } + + public List getJobs() { + return jobDTOS; + } + + public void setJobs(List jobDTOS) { + this.jobDTOS = jobDTOS; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + } + + public static class DeliveryResult { + private String taskId; + private int totalCount; + private int deliveredCount; + private boolean actualDelivery; + private String message; + private Date timestamp; + private List jobDetails; + + // getters and setters + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public int getTotalCount() { + return totalCount; + } + + public void setTotalCount(int totalCount) { + this.totalCount = totalCount; + } + + public int getDeliveredCount() { + return deliveredCount; + } + + public void setDeliveredCount(int deliveredCount) { + this.deliveredCount = deliveredCount; + } + + public boolean isActualDelivery() { + return actualDelivery; + } + + public void setActualDelivery(boolean actualDelivery) { + this.actualDelivery = actualDelivery; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public List getJobDetails() { + return jobDetails; + } + + public void setJobDetails(List jobDetails) { + this.jobDetails = jobDetails; + } + } +} diff --git a/src/main/java/getjobs/modules/job51/service/impl/Job51RecruitmentServiceImpl.java b/src/main/java/getjobs/modules/job51/service/impl/Job51RecruitmentServiceImpl.java new file mode 100644 index 00000000..8a8bcad7 --- /dev/null +++ b/src/main/java/getjobs/modules/job51/service/impl/Job51RecruitmentServiceImpl.java @@ -0,0 +1,424 @@ +package getjobs.modules.job51.service.impl; + +import com.microsoft.playwright.Page; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.common.dto.ConfigDTO; +import getjobs.common.service.PlaywrightService; +import getjobs.modules.boss.dto.JobDTO; +import getjobs.modules.job51.service.Job51ElementLocators; +import getjobs.service.RecruitmentService; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import jakarta.annotation.PostConstruct; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Random; +import java.util.Scanner; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 51job招聘服务实现 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class Job51RecruitmentServiceImpl implements RecruitmentService { + + private static final String HOME_URL = RecruitmentPlatformEnum.JOB_51.getHomeUrl(); + private static final String LOGIN_URL = "https://login.51job.com/login.php"; + private static final String SEARCH_JOB_URL = "https://we.51job.com/pc/search?"; + + private final PlaywrightService playwrightService; + private Page page; + + @PostConstruct + public void init() { + this.page = playwrightService.getPage(RecruitmentPlatformEnum.JOB_51); + } + + @Override + public RecruitmentPlatformEnum getPlatform() { + return RecruitmentPlatformEnum.JOB_51; + } + + @Override + public boolean login(ConfigDTO config) { + log.info("开始51job登录检查"); + + try { + // 使用Playwright打开网站 + page.navigate(HOME_URL); + + // 检查是否需要登录 + if (isLoginRequired()) { + log.info("需要登录,开始登录流程"); + return login(); + } else { + log.info("51job已登录"); + return true; + } + } catch (Exception e) { + log.error("51job登录失败", e); + return false; + } + } + + @Override + public List collectJobs(ConfigDTO config) { + log.info("开始执行51job岗位采集操作"); + try { + config.getCityCodeCodes().forEach(cityCode -> { + // 构造完整的搜索条件 + String searchParams = buildSearchParams(cityCode, config); + String fullUrl = SEARCH_JOB_URL + searchParams; + log.info("访问搜索URL: {}", fullUrl); + page.navigate(fullUrl); + + // 等待页面完全加载,确保分页元素可用 + waitForPageLoad(page); + + int pageNumber = 1; + while (Job51ElementLocators.clickPageNumber(page, pageNumber)) { + log.info("正在处理第{}页数据", pageNumber); + + // 添加3-5秒随机延迟,避免过快点击分页 + try { + int randomSeconds = new Random().nextInt(3) + 3; // 3-5秒 + TimeUnit.SECONDS.sleep(randomSeconds); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + // 在点击分页后也需要等待页面加载 + waitForPageContentLoad(page); + pageNumber++; + } + }); + + log.info("51job岗位采集功能待实现"); + return List.of(); // 暂时返回空列表,等待具体实现 + + } catch (Exception e) { + log.error("51job岗位采集失败", e); + return List.of(); + } + } + + @Override + public List collectRecommendJobs(ConfigDTO config) { + // TODO: 实现51job推荐岗位采集逻辑 + return List.of(); + } + + @Override + public List filterJobs(List jobDTOS, ConfigDTO config) { + log.info("开始执行51job岗位过滤操作,待过滤岗位数量: {}", jobDTOS.size()); + try { + // TODO: 实现51job岗位过滤逻辑 + // 这里需要根据51job的岗位特点和过滤规则来实现过滤 + // 可以参考Boss直聘的过滤逻辑,但需要适配51job的岗位结构 + + log.info("51job岗位过滤功能待实现"); + return jobDTOS; // 暂时返回原始列表,等待具体实现 + + } catch (Exception e) { + log.error("51job岗位过滤失败", e); + return jobDTOS; + } + } + + @Override + public int deliverJobs(List jobDTOS, ConfigDTO config) { + log.info("开始执行51job岗位投递操作,待投递岗位数量: {}", jobDTOS.size()); + + // 在新标签页中打开岗位详情 + try (Page jobPage = page.context().newPage()) { + + AtomicInteger count = new AtomicInteger(); + + try { + jobDTOS.forEach(jobDTO -> { + + jobPage.navigate(jobDTO.getHref()); + // 执行投递 + Job51ElementLocators.clickApplyJobButton(jobPage); + count.getAndIncrement(); + + // 添加3-5秒随机延迟,避免投递过快 + try { + int randomSeconds = new Random().nextInt(3) + 3; // 3-5秒 + TimeUnit.SECONDS.sleep(randomSeconds); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + }); + + return count.get(); // 暂时返回0,等待具体实现 + + } catch (Exception e) { + log.error("51job岗位投递失败", e); + return count.get(); + } + } + } + + @Override + public boolean isDeliveryLimitReached() { + // TODO: 实现51job投递限制检查逻辑 + // 这里需要检查是否达到51job的投递限制 + log.info("51job投递限制检查功能待实现"); + return false; // 暂时返回false,等待具体实现 + } + + @Override + public void saveData(String dataPath) { + log.info("开始保存51job数据到路径: {}", dataPath); + try { + // TODO: 实现51job数据保存逻辑 + // 这里需要保存51job相关的数据,如登录状态、采集的岗位信息等 + + log.info("51job数据保存功能待实现"); + + } catch (Exception e) { + log.error("51job数据保存失败", e); + } + } + + // ==================== 私有辅助方法 ==================== + + /** + * 构建完整的搜索参数字符串 + * 基于URL: https://we.51job.com/pc/search?jobArea=030200&keyword=java&salary=16000-20000&workYear=05°ree=04&companySize=03&companyType=04 + * + * @param cityCode 城市代码 + * @param config 配置信息 + * @return 完整的搜索参数字符串 + */ + private String buildSearchParams(String cityCode, ConfigDTO config) { + StringBuilder params = new StringBuilder(); + + try { + // 必需参数 + params.append("jobArea=").append(cityCode); + + // 关键词需要URL编码 + String keywords = config.getKeywords() != null ? config.getKeywords().trim() : ""; + params.append("&keyword=").append(URLEncoder.encode(keywords, StandardCharsets.UTF_8)); + + // 可选参数 - 薪资范围 (如: 16000-20000) + appendParameterIfNotEmpty(params, "salary", config.getSalary()); + + // 可选参数 - 工作年限 (如: 05表示5年以上) + appendParameterIfNotEmpty(params, "workYear", config.getExperience()); + + // 可选参数 - 学历要求 (如: 04表示本科) + appendParameterIfNotEmpty(params, "degree", config.getDegree()); + + // 可选参数 - 公司规模 (如: 03表示100-499人) + appendParameterIfNotEmpty(params, "companySize", config.getScale()); + + // 可选参数 - 公司类型 (如: 04表示民营公司) + appendParameterIfNotEmpty(params, "companyType", config.getCompanyType()); + + // 其他可能的参数 + // industrytype - 行业类型 + appendParameterIfNotEmpty(params, "industrytype", config.getIndustry()); + + // jobtype - 职位类型 + appendParameterIfNotEmpty(params, "jobtype", config.getJobType()); + + } catch (Exception e) { + log.error("构建搜索参数失败", e); + // 返回基础参数 + return "jobArea=" + cityCode + "&keyword=" + + (config.getKeywords() != null ? config.getKeywords() : ""); + } + + log.debug("构建的搜索参数: {}", params.toString()); + return params.toString(); + } + + /** + * 添加参数到StringBuilder(如果参数值不为空) + * + * @param params 参数构建器 + * @param paramName 参数名 + * @param paramValue 参数值 + */ + private void appendParameterIfNotEmpty(StringBuilder params, String paramName, String paramValue) { + if (paramValue != null && !paramValue.trim().isEmpty()) { + try { + params.append("&").append(paramName).append("=") + .append(URLEncoder.encode(paramValue.trim(), StandardCharsets.UTF_8)); + } catch (Exception e) { + log.warn("编码参数失败: {}={}", paramName, paramValue, e); + // 如果编码失败,使用原始值 + params.append("&").append(paramName).append("=").append(paramValue.trim()); + } + } + } + + /** + * 检查是否需要登录 + */ + private boolean isLoginRequired() { + try { + // 检查是否存在登录按钮 + if (Job51ElementLocators.hasLoginElement(page)) { + log.debug("检测到登录按钮,需要登录"); + return true; // 需要登录 + } + + return false; + } catch (Exception e) { + log.error("登录状态检查出错", e); + return true; // 出错时默认需要登录 + } + } + + /** + * 登录检查 + */ + @SneakyThrows + private boolean login() { + page.navigate(LOGIN_URL); + TimeUnit.SECONDS.sleep(3); + + try { + // 检查是否已经登录 + if (Job51ElementLocators.isUserLoggedIn(page)) { + log.info("检测到已登录状态"); + return true; + } + } catch (Exception ignored) { + } + + log.info("等待用户手动登录..."); + + boolean login = false; + + while (!login) { + try { + // 判断登录页登录元素是否还存在 + if (Job51ElementLocators.hasPasswordLoginElement(page)) { + login = true; + log.info("登录成功"); + } + } catch (Exception e) { + // 登录检查异常,继续等待 + log.debug("登录状态检查异常,继续等待: {}", e.getMessage()); + } + + // 等待一段时间后再次检查 + TimeUnit.SECONDS.sleep(3); + } + + return true; + } + + /** + * 等待页面完全加载 + * 等待分页元素和职位列表元素出现,确保页面内容完全加载 + */ + private void waitForPageLoad(Page page) { + try { + log.info("等待页面加载完成..."); + + // 等待DOM加载完成 + page.waitForLoadState(); + + // 等待分页元素出现(最多等待10秒) + try { + page.waitForSelector("ul.el-pager", new Page.WaitForSelectorOptions().setTimeout(10000)); + log.info("分页元素已加载"); + } catch (Exception e) { + log.warn("分页元素加载超时,可能页面没有分页或加载失败: {}", e.getMessage()); + } + + + // 额外等待一段时间,确保JS完全执行完毕 + TimeUnit.SECONDS.sleep(2); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("等待页面加载被中断: {}", e.getMessage()); + } catch (Exception e) { + log.error("等待页面加载时发生错误: {}", e.getMessage()); + // 发生错误时也等待一段时间 + try { + TimeUnit.SECONDS.sleep(3); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * 等待页面内容加载 + * 在点击分页后等待新页面内容加载完成 + */ + private void waitForPageContentLoad(Page page) { + try { + log.debug("等待页面内容更新..."); + + // 等待网络空闲,确保数据加载完成 + page.waitForLoadState(); + + // 等待职位列表内容更新 + try { + page.waitForSelector("div.joblist .joblist-item", new Page.WaitForSelectorOptions().setTimeout(5000)); + log.debug("职位列表内容已更新"); + } catch (Exception e) { + log.warn("职位列表内容更新超时: {}", e.getMessage()); + } + + // 短暂等待确保页面稳定 + TimeUnit.SECONDS.sleep(1); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("等待页面内容加载被中断: {}", e.getMessage()); + } catch (Exception e) { + log.error("等待页面内容加载时发生错误: {}", e.getMessage()); + // 发生错误时也等待一段时间 + try { + TimeUnit.SECONDS.sleep(2); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * 等待用户输入或超时 + */ + private boolean waitForUserInputOrTimeout(Scanner scanner) { + long end = System.currentTimeMillis() + 2000; + while (System.currentTimeMillis() < end) { + try { + if (System.in.available() > 0) { + scanner.nextLine(); + return true; + } + TimeUnit.SECONDS.sleep(1); + } catch (IOException e) { + // 忽略异常 + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + return false; + } +} diff --git a/src/main/java/getjobs/modules/job51/service/playwright/Job51ApiMonitorService.java b/src/main/java/getjobs/modules/job51/service/playwright/Job51ApiMonitorService.java new file mode 100644 index 00000000..e1a69927 --- /dev/null +++ b/src/main/java/getjobs/modules/job51/service/playwright/Job51ApiMonitorService.java @@ -0,0 +1,315 @@ +package getjobs.modules.job51.service.playwright; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.playwright.*; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.common.service.PlaywrightService; +import getjobs.modules.job51.dto.Job51ApiResponse; +import getjobs.repository.entity.JobEntity; +import getjobs.repository.JobRepository; +import getjobs.utils.Job51DataConverter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.PostConstruct; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 51Job接口监控服务 + * 负责监听和记录51Job相关的API请求和响应 + * + * @author getjobs + * @since v2.1.1 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class Job51ApiMonitorService { + + private final JobRepository jobRepository; + private final Job51DataConverter dataConverter; + private final PlaywrightService playwrightService; + private final ObjectMapper objectMapper = new ObjectMapper(); + + // 全局接口调用频率限制:记录最后一次调用时间 + private static volatile long lastCallTime = 0L; + + /** + * 初始化监控服务 + * 设置51Job搜索接口监听器 + */ + @PostConstruct + public void init() { + setupJob51ApiMonitor(); + } + + /** + * 设置51Job接口监听器 + * 监听所有相关的API响应并处理职位数据 + */ + public void setupJob51ApiMonitor() { + try { + Page page = playwrightService.getPage(RecruitmentPlatformEnum.JOB_51); + + // 监听51Job职位搜索接口的响应 + setupResponseMonitor(page); + + log.info("51Job API监控服务初始化完成"); + } catch (Exception e) { + log.error("51Job API监控服务初始化失败: {}", e.getMessage(), e); + } + } + + /** + * 设置响应监控 + */ + private void setupResponseMonitor(Page page) { + page.onResponse(response -> { + String url = response.url(); + + // 监听51Job职位搜索接口响应 + if (url.contains("/api/job/search-pc")) { + handleJob51SearchResponse(response); + } + // 可以在此添加其他51Job相关接口的监听 + }); + } + + /** + * 处理51Job职位搜索响应 + */ + private void handleJob51SearchResponse(Response response) { + log.info("=== 51Job职位搜索响应拦截 ==="); + log.info("响应状态: {}", response.status()); + log.info("响应URL: {}", response.url()); + log.info("响应头: {}", response.headers()); + + try { + String body = response.text(); + log.info("响应体长度: {} 字符", body.length()); + log.debug("响应体内容: {}", body); + + // 尝试解析JSON并美化输出 + formatJsonResponse(body); + + // 解析并保存职位数据 + parseAndSaveJob51Data(body, "51Job职位搜索"); + + } catch (PlaywrightException e) { + log.error("读取51Job响应体失败: {}", e.getMessage()); + } + log.info("=========================="); + } + + /** + * 格式化JSON响应 + */ + private void formatJsonResponse(String body) { + try { + Object jsonObject = objectMapper.readValue(body, Object.class); + String formattedJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject); + log.debug("格式化JSON响应: {}", formattedJson); + } catch (Exception e) { + log.debug("响应体不是有效的JSON格式: {}", e.getMessage()); + } + } + + /** + * 解析并保存51Job职位数据 + * + * @param body 响应体JSON字符串 + * @param source 数据来源描述 + */ + @Transactional + public void parseAndSaveJob51Data(String body, String source) { + try { + // 解析51Job API响应 + Job51ApiResponse response = objectMapper.readValue(body, Job51ApiResponse.class); + + if (!"1".equals(response.getStatus())) { + log.warn("51Job API响应错误,status: {}, message: {}", response.getStatus(), response.getMessage()); + return; + } + + if (response.getResultbody() == null || + response.getResultbody().getJob() == null || + response.getResultbody().getJob().getItems() == null) { + log.warn("51Job API响应中没有职位数据"); + return; + } + + List jobItems = response.getResultbody().getJob().getItems(); + log.info("从{}获取到 {} 个职位数据", source, jobItems.size()); + + // 过滤有效的职位数据 + List validJobItems = jobItems.stream() + .filter(dataConverter::isValidJobData) + .toList(); + + if (validJobItems.isEmpty()) { + log.warn("没有有效的职位数据,来源: {}", source); + return; + } + + log.info("过滤后有效职位数据: {} 个", validJobItems.size()); + + // 转换为JobEntity并保存 + List jobEntities = validJobItems.stream() + .map(dataConverter::convertToJobEntity) + .filter(Objects::nonNull) + .toList(); + + if (!jobEntities.isEmpty()) { + // 检查是否已存在相同的职位(基于encryptJobId) + List newJobs = jobEntities.stream() + .filter(entity -> !isJobExists(entity.getEncryptJobId())) + .collect(Collectors.toList()); + + if (!newJobs.isEmpty()) { + jobRepository.saveAll(newJobs); + log.info("成功保存 {} 个新职位到数据库,来源: {}", newJobs.size(), source); + + // 打印保存的职位信息 + newJobs.forEach(job -> log.info("保存职位: {} - {} - {}", + job.getJobTitle(), job.getCompanyName(), job.getSalaryDesc())); + } else { + log.info("所有职位都已存在,跳过保存,来源: {}", source); + } + } else { + log.warn("没有有效的职位数据可以保存,来源: {}", source); + } + + } catch (Exception e) { + log.error("解析并保存51Job职位数据失败,来源: {}", source, e); + } + } + + /** + * 检查职位是否已存在 + * + * @param encryptJobId 职位ID + * @return 是否存在 + */ + private boolean isJobExists(String encryptJobId) { + if (encryptJobId == null || encryptJobId.trim().isEmpty()) { + return false; + } + + try { + return jobRepository.existsByEncryptJobId(encryptJobId); + } catch (Exception e) { + log.warn("检查职位是否存在时发生错误: {}", e.getMessage()); + return false; + } + } + + /** + * 手动启动监控(如果需要重新启动) + */ + public void startMonitoring() { + log.info("手动启动51Job API监控服务"); + setupJob51ApiMonitor(); + } + + /** + * 检查监控服务状态 + */ + public boolean isMonitoringActive() { + try { + BrowserContext ctx = playwrightService.getContext(RecruitmentPlatformEnum.JOB_51); + return ctx != null; + } catch (Exception e) { + log.warn("检查51Job监控服务状态失败: {}", e.getMessage()); + return false; + } + } + + /** + * 刷新51Job页面来获取新的session + */ + public boolean refreshSession() { + try { + log.info("开始刷新51Job session"); + + // 获取Playwright上下文 + BrowserContext context = playwrightService.getContext(RecruitmentPlatformEnum.JOB_51); + if (context == null) { + log.error("无法获取Playwright上下文,session刷新失败"); + return false; + } + + // 创建新页面访问51job.com + Page refreshPage = context.newPage(); + + try { + // 设置用户代理,模拟真实浏览器访问 + refreshPage.setExtraHTTPHeaders(java.util.Map.of( + "User-Agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Accept", + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8", + "Accept-Encoding", "gzip, deflate, br", + "Cache-Control", "no-cache", + "Pragma", "no-cache")); + + // 访问51job.com主页 + log.info("正在访问 https://www.51job.com/"); + refreshPage.navigate("https://www.51job.com/"); + + // 等待页面加载完成 + refreshPage.waitForLoadState(); + refreshPage.waitForTimeout(3000); // 等待3秒确保页面完全加载 + + // 模拟用户行为:滚动页面 + refreshPage.evaluate("window.scrollTo(0, document.body.scrollHeight / 2)"); + refreshPage.waitForTimeout(1000); + + // 模拟点击页面(不实际点击任何元素,只是模拟用户活动) + refreshPage.hover("body"); + refreshPage.waitForTimeout(1000); + + log.info("成功访问51job.com,session刷新完成"); + return true; + + } finally { + // 关闭页面 + if (refreshPage != null) { + refreshPage.close(); + } + } + + } catch (Exception e) { + log.error("访问51job.com刷新session时发生异常: {}", e.getMessage(), e); + return false; + } + } + + + /** + * 获取监控统计信息 + */ + public java.util.Map getMonitoringStats() { + java.util.Map stats = new java.util.HashMap<>(); + + try { + // 统计数据库中51job职位数量 + long totalJobs = jobRepository.countByPlatform("51job"); + stats.put("totalJobs", totalJobs); + stats.put("platform", "51job"); + stats.put("isActive", isMonitoringActive()); + stats.put("lastCallTime", lastCallTime); + + } catch (Exception e) { + log.error("获取监控统计信息失败: {}", e.getMessage(), e); + stats.put("error", e.getMessage()); + } + + return stats; + } +} diff --git a/src/main/java/getjobs/modules/job51/web/Job51MonitorController.java b/src/main/java/getjobs/modules/job51/web/Job51MonitorController.java new file mode 100644 index 00000000..e819803c --- /dev/null +++ b/src/main/java/getjobs/modules/job51/web/Job51MonitorController.java @@ -0,0 +1,83 @@ +package getjobs.modules.job51.web; + +import getjobs.modules.job51.service.playwright.Job51ApiMonitorService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * 51Job监控服务控制器 + * 提供51Job API监控服务的管理接口 + * + * @author getjobs + * @since v2.1.1 + */ +@Slf4j +@RestController +@RequestMapping("/api/job51/monitor") +@RequiredArgsConstructor +public class Job51MonitorController { + + private final Job51ApiMonitorService job51ApiMonitorService; + + /** + * 获取监控服务状态 + */ + @GetMapping("/status") + public ResponseEntity> getMonitorStatus() { + try { + Map stats = job51ApiMonitorService.getMonitoringStats(); + return ResponseEntity.ok(stats); + } catch (Exception e) { + log.error("获取51Job监控状态失败", e); + return ResponseEntity.internalServerError() + .body(Map.of("error", "获取监控状态失败: " + e.getMessage())); + } + } + + /** + * 启动监控服务 + */ + @PostMapping("/start") + public ResponseEntity> startMonitoring() { + try { + job51ApiMonitorService.startMonitoring(); + return ResponseEntity.ok(Map.of( + "success", true, + "message", "51Job监控服务已启动" + )); + } catch (Exception e) { + log.error("启动51Job监控服务失败", e); + return ResponseEntity.internalServerError() + .body(Map.of("error", "启动监控服务失败: " + e.getMessage())); + } + } + + /** + * 刷新session + */ + @PostMapping("/refresh-session") + public ResponseEntity> refreshSession() { + try { + boolean success = job51ApiMonitorService.refreshSession(); + if (success) { + return ResponseEntity.ok(Map.of( + "success", true, + "message", "51Job session刷新成功" + )); + } else { + return ResponseEntity.badRequest() + .body(Map.of("error", "51Job session刷新失败")); + } + } catch (Exception e) { + log.error("刷新51Job session失败", e); + return ResponseEntity.internalServerError() + .body(Map.of("error", "刷新session失败: " + e.getMessage())); + } + } + + +} diff --git a/src/main/java/getjobs/modules/liepin/dto/LiePinApiResponse.java b/src/main/java/getjobs/modules/liepin/dto/LiePinApiResponse.java new file mode 100644 index 00000000..c33ac432 --- /dev/null +++ b/src/main/java/getjobs/modules/liepin/dto/LiePinApiResponse.java @@ -0,0 +1,70 @@ +package getjobs.modules.liepin.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class LiePinApiResponse { + private int flag; + private DataWrapper data; + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class DataWrapper { + private DataInner data; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class DataInner { + private List jobCardList; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class JobCard { + private Comp comp; + private Job job; + private Recruiter recruiter; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Comp { + private String compIndustry; + private Integer compId; + private String compName; + private String compScale; + private String compStage; + private String compLogo; + private String link; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Job { + private List labels; + private String title; + private String refreshTime; + private String salary; + private String jobKind; + private String jobId; + private String dq; + private String link; + private String requireEduLevel; + private String requireWorkYears; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Recruiter { + private String recruiterId; + private String recruiterName; + private String recruiterTitle; + private String imShowText; + private String recruiterPhoto; + } +} diff --git a/src/main/java/getjobs/modules/liepin/service/LiepinElementLocators.java b/src/main/java/getjobs/modules/liepin/service/LiepinElementLocators.java new file mode 100644 index 00000000..f00d26e5 --- /dev/null +++ b/src/main/java/getjobs/modules/liepin/service/LiepinElementLocators.java @@ -0,0 +1,473 @@ +package getjobs.modules.liepin.service; + +import com.microsoft.playwright.Locator; +import com.microsoft.playwright.Page; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * 猎聘网站元素定位器 + * 用于定位猎聘网站上的各种元素 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Slf4j +public class LiepinElementLocators { + + /** 未登录状态的菜单列表 (ul 标签),使用 class 选择器 */ + public static final String NOT_LOGIN_MENU = "ul.header-quick-menu-not-login"; + + /** 已登录状态的菜单列表 (ul 标签),使用 class 选择器 */ + public static final String LOGGED_IN_MENU = "ul.header-quick-menu-login"; + + /** HR信息区块容器,使用 class 选择器 */ + public static final String RECRUITER_CONTAINER = "section.recruiter-container"; + + /** 聊一聊按钮,使用 class 选择器 */ + public static final String CHAT_BUTTON = "a.btn-chat"; + + /** 聊天对话框的文本输入框,使用 class 选择器 */ + public static final String CHAT_INPUT_TEXTAREA = "textarea.__im_basic__textarea"; + + /** 聊天对话框的发送按钮,使用 class 选择器 */ + public static final String CHAT_SEND_BUTTON = "button.__im_basic__basic-send-btn"; + + /** + * 检查是否需要登录 + * 通过检查不同登录状态下唯一的 ul 列表元素的 class 来判断 + * + * @param page Playwright页面对象 + * @return true表示需要登录,false表示已登录 + */ + public static boolean isLoginRequired(Page page) { + try { + // 等待页面加载完成,确保所有元素都已加载 + log.debug("等待页面加载完成..."); + page.waitForLoadState(); + page.waitForTimeout(3000); // 等待DOM稳定 + + log.debug("检查猎聘登录状态..."); + + // 检查是否存在未登录状态的菜单 + Locator notLoginMenu = page.locator(NOT_LOGIN_MENU); + if (notLoginMenu.isVisible()) { + log.info("找到'header-quick-menu-not-login' class,判定为未登录状态"); + return true; + } + + // 检查是否存在已登录状态的菜单 + Locator loggedInMenu = page.locator(LOGGED_IN_MENU); + if (loggedInMenu.isVisible()) { + log.info("找到'header-quick-menu-login' class,判定为已登录状态"); + return false; + } + + // 如果以上都没有立即找到,可能页面正在加载或结构有变,增加等待后重试 + log.warn("无法立即判断登录状态,将等待5秒后重试..."); + page.waitForTimeout(5000); + + if (page.locator(NOT_LOGIN_MENU).isVisible()) { + log.info("重试后找到'header-quick-menu-not-login' class,判定为未登录状态"); + return true; + } + + if (page.locator(LOGGED_IN_MENU).isVisible()) { + log.info("重试后找到'header-quick-menu-login' class,判定为已登录状态"); + return false; + } + + log.error("无法明确判断猎聘的登录状态,默认需要登录。请检查页面元素定位器是否需要更新。"); + return true; // 无法判断时,为安全起见,默认需要登录 + + } catch (Exception e) { + log.error("检查猎聘登录状态时发生异常", e); + return true; // 出现异常时,为安全起见,默认需要登录 + } + } + + /** + * 获取HR信息区块并点击"聊一聊"按钮 + * + * @param page Playwright页面对象 + * @return true表示点击成功,false表示点击失败 + */ + public static boolean clickChatWithRecruiter(Page page) { + try { + log.info("开始查找HR信息区块..."); + + // 等待HR信息区块加载 + Locator recruiterContainer = page.locator(RECRUITER_CONTAINER); + if (!recruiterContainer.isVisible()) { + log.warn("未找到HR信息区块,可能该职位没有HR信息"); + return false; + } + + log.info("找到HR信息区块,准备点击'聊一聊'按钮..."); + + // 定位"聊一聊"按钮 + Locator chatButton = page.locator(CHAT_BUTTON); + if (!chatButton.isVisible()) { + log.warn("'聊一聊'按钮不可见"); + return false; + } + + // 获取HR姓名(用于日志) + try { + String recruiterName = page.locator(RECRUITER_CONTAINER + " .name").textContent(); + log.info("准备与HR【{}】聊天", recruiterName); + } catch (Exception e) { + log.debug("无法获取HR姓名", e); + } + + // 点击按钮 + chatButton.click(); + log.info("成功点击'聊一聊'按钮"); + + // 等待页面响应 + page.waitForTimeout(2000); + + return true; + + } catch (Exception e) { + log.error("点击'聊一聊'按钮时发生异常", e); + return false; + } + } + + /** + * 检查HR是否在线 + * + * @param page Playwright页面对象 + * @return true表示在线,false表示离线或无法判断 + */ + public static boolean isRecruiterOnline(Page page) { + try { + Locator onlineStatus = page.locator(RECRUITER_CONTAINER + " .online"); + boolean isOnline = onlineStatus.isVisible(); + log.debug("HR在线状态: {}", isOnline ? "在线" : "离线"); + return isOnline; + } catch (Exception e) { + log.debug("无法判断HR在线状态", e); + return false; + } + } + + /** + * 在聊天对话框中输入文本 + * + * @param page Playwright页面对象 + * @param message 要输入的文本内容 + * @return true表示输入成功,false表示输入失败 + */ + public static boolean inputChatMessage(Page page, String message) { + return inputChatMessage(page, message, false); + } + + /** + * 在聊天对话框中输入文本 + * + * @param page Playwright页面对象 + * @param message 要输入的文本内容 + * @param autoSend 是否自动发送(按Enter键),true表示输入后自动发送,false表示只输入不发送 + * @return true表示操作成功,false表示操作失败 + */ + public static boolean inputChatMessage(Page page, String message, boolean autoSend) { + try { + if (message == null || message.trim().isEmpty()) { + log.warn("输入的消息内容为空"); + return false; + } + + log.info("开始在聊天对话框中输入文本: {}", message); + + // 等待聊天输入框加载 + Locator inputTextarea = page.locator(CHAT_INPUT_TEXTAREA); + if (!inputTextarea.isVisible()) { + log.warn("未找到聊天输入框,请确认是否已打开聊天窗口"); + return false; + } + + // 点击输入框获取焦点 + inputTextarea.click(); + page.waitForTimeout(500); + + // 清空输入框 + inputTextarea.fill(""); + + // 输入文本 + inputTextarea.fill(message); + log.info("成功输入文本到聊天框"); + + // 如果需要自动发送 + if (autoSend) { + log.info("准备发送消息..."); + + // 方式1:按Enter键发送 + inputTextarea.press("Enter"); + + // 等待消息发送 + page.waitForTimeout(1000); + + log.info("消息已发送"); + } + + return true; + + } catch (Exception e) { + log.error("在聊天对话框中输入文本时发生异常", e); + return false; + } + } + + /** + * 点击发送按钮发送消息 + * + * @param page Playwright页面对象 + * @return true表示点击成功,false表示点击失败 + */ + public static boolean clickSendButton(Page page) { + try { + log.info("准备点击发送按钮..."); + + Locator sendButton = page.locator(CHAT_SEND_BUTTON); + if (!sendButton.isVisible()) { + log.warn("发送按钮不可见"); + return false; + } + + // 检查按钮是否被禁用 + if (sendButton.isDisabled()) { + log.warn("发送按钮处于禁用状态,可能输入框为空"); + return false; + } + + sendButton.click(); + log.info("成功点击发送按钮"); + + // 等待消息发送 + page.waitForTimeout(1000); + + return true; + + } catch (Exception e) { + log.error("点击发送按钮时发生异常", e); + return false; + } + } + + /** + * 判断用户是否已登录 + * 通过检测页面中是否存在"登录/注册"关键字来判断 + * + * @param page Playwright页面对象 + * @return true表示已登录,false表示未登录 + */ + public static boolean isUserLoggedIn(Page page) { + try { + log.debug("检查猎聘用户登录状态..."); + + // 等待页面加载完成 + page.waitForLoadState(); + page.waitForTimeout(2000); // 等待DOM稳定 + + // 方式1: 通过检测"登录/注册"文本来判断登录状态 + // 如果页面中存在"登录/注册"文本,说明用户未登录 + Locator loginText = page.getByText("登录/注册"); + + if (loginText.count() > 0 && loginText.first().isVisible()) { + log.info("检测到'登录/注册'文本,判定为未登录状态"); + return false; // 未登录 + } + + // 方式2: 检查是否跳转到登录页面(通过登录框判断) + // 如果页面中存在 login-box 元素,说明在登录页面,用户未登录 + Locator loginBox = page.locator("div.login-box"); + if (loginBox.count() > 0 && loginBox.first().isVisible()) { + log.info("检测到登录页面容器(login-box),判定为未登录状态"); + return false; // 未登录 + } + + log.info("未检测到登录相关元素,判定为已登录状态"); + return true; // 已登录 + + } catch (Exception e) { + log.error("检查猎聘用户登录状态时发生异常", e); + return false; // 出现异常时,默认未登录 + } + } + + // ==================== 分页相关方法 ==================== + + /** + * 根据页码点击分页元素 + * 在class="list-pagination-box"的div元素中查找指定页码的li元素并点击 + * + * @param page Playwright页面对象 + * @param pageNumber 要点击的页码 + * @return true表示点击成功,false表示未找到对应页码或点击失败 + */ + public static boolean clickPageNumber(Page page, int pageNumber) { + try { + // 首先等待分页元素出现 + try { + page.waitForSelector("div.list-pagination-box", new Page.WaitForSelectorOptions().setTimeout(8000)); + } catch (Exception e) { + log.error("等待分页元素出现超时: {}", e.getMessage()); + return false; + } + + // 查找class="list-pagination-box"的div元素 + Locator pagerBox = page.locator("div.list-pagination-box"); + + if (pagerBox.count() == 0) { + log.error("未找到分页元素 div.list-pagination-box"); + return false; + } + + // 查找ant-pagination容器 + Locator paginationElement = pagerBox.locator("ul.ant-pagination"); + + if (paginationElement.count() == 0) { + log.error("未找到分页容器 ul.ant-pagination"); + return false; + } + + // 查找指定页码的元素 + String pageItemSelector = "li.ant-pagination-item-" + pageNumber; + Locator pageElement = paginationElement.locator(pageItemSelector); + + if (pageElement.count() == 0) { + log.info("未找到页码为 {} 的分页元素,可能已到达最后一页", pageNumber); + return false; + } + + // 检查元素是否已经是当前激活状态 + if (pageElement.getAttribute("class").contains("ant-pagination-item-active")) { + log.info("页码 {} 已经是当前激活状态,无需点击", pageNumber); + return true; + } + + // 确保元素可见且可点击 + pageElement.scrollIntoViewIfNeeded(); + + // 在点击之前添加随机延迟3-5秒,模拟真实用户行为 + Random random = new Random(); + int delay = 3000 + random.nextInt(2001); // 3000-5000毫秒之间的随机延迟 + log.info("准备点击页码 {},随机延迟 {} 毫秒", pageNumber, delay); + page.waitForTimeout(delay); + + // 点击页码元素 + pageElement.click(); + + // 等待页面状态变化,确保点击生效 + try { + // 等待当前页码变为激活状态 + String activePageSelector = "li.ant-pagination-item-" + pageNumber + ".ant-pagination-item-active"; + page.waitForSelector("div.list-pagination-box " + activePageSelector, + new Page.WaitForSelectorOptions().setTimeout(5000)); + log.info("成功点击页码: {},页面已切换", pageNumber); + } catch (Exception e) { + log.warn("等待页码 {} 激活状态超时,但点击操作已执行: {}", pageNumber, e.getMessage()); + } + + // 等待页面内容加载 + page.waitForLoadState(); + page.waitForTimeout(2000); + + return true; + + } catch (Exception e) { + log.error("点击页码 {} 时发生错误: {}", pageNumber, e.getMessage()); + return false; + } + } + + /** + * 获取当前激活的页码 + * 查找class="list-pagination-box"中带有"ant-pagination-item-active"类的li元素,获取其页码值 + * + * @param page Playwright页面对象 + * @return 当前激活的页码,如果未找到则返回-1 + */ + public static int getCurrentPageNumber(Page page) { + try { + // 查找分页容器 + Locator pagerBox = page.locator("div.list-pagination-box"); + + if (pagerBox.count() == 0) { + log.error("未找到分页元素 div.list-pagination-box"); + return -1; + } + + // 查找当前激活的页码元素 + Locator activePageElement = pagerBox.locator("li.ant-pagination-item-active"); + + if (activePageElement.count() > 0) { + String pageText = activePageElement.textContent().trim(); + try { + int currentPage = Integer.parseInt(pageText); + log.info("当前激活页码: {}", currentPage); + return currentPage; + } catch (NumberFormatException e) { + log.error("解析当前页码失败: {}", pageText); + return -1; + } + } + + log.error("未找到当前激活的页码元素"); + return -1; + + } catch (Exception e) { + log.error("获取当前页码时发生错误: {}", e.getMessage()); + return -1; + } + } + + /** + * 获取所有可见的页码列表 + * 从class="list-pagination-box"的div元素中获取所有li.ant-pagination-item元素的页码值 + * + * @param page Playwright页面对象 + * @return 所有可见页码的列表,如果出错则返回空列表 + */ + public static List getVisiblePageNumbers(Page page) { + List pageNumbers = new ArrayList<>(); + + try { + // 查找分页容器 + Locator pagerBox = page.locator("div.list-pagination-box"); + + if (pagerBox.count() == 0) { + log.error("未找到分页元素 div.list-pagination-box"); + return pageNumbers; + } + + // 获取所有页码元素 + Locator pageElements = pagerBox.locator("li.ant-pagination-item"); + int elementCount = pageElements.count(); + + for (int i = 0; i < elementCount; i++) { + Locator pageElement = pageElements.nth(i); + String pageText = pageElement.textContent().trim(); + + try { + int pageNumber = Integer.parseInt(pageText); + pageNumbers.add(pageNumber); + } catch (NumberFormatException e) { + log.warn("跳过非数字页码: {}", pageText); + } + } + + log.info("获取到可见页码列表: {}", pageNumbers); + + } catch (Exception e) { + log.error("获取可见页码列表时发生错误: {}", e.getMessage()); + } + + return pageNumbers; + } +} diff --git a/src/main/java/getjobs/modules/liepin/service/LiepinTaskService.java b/src/main/java/getjobs/modules/liepin/service/LiepinTaskService.java new file mode 100644 index 00000000..6f6953b6 --- /dev/null +++ b/src/main/java/getjobs/modules/liepin/service/LiepinTaskService.java @@ -0,0 +1,345 @@ +package getjobs.modules.liepin.service; + +import getjobs.common.dto.ConfigDTO; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.boss.dto.JobDTO; +import getjobs.common.enums.JobStatusEnum; +import getjobs.repository.entity.JobEntity; +import getjobs.repository.JobRepository; +import getjobs.service.JobService; +import getjobs.service.PlaywrightManager; +import getjobs.modules.task.dto.TaskUpdatePayload; +import getjobs.modules.task.enums.TaskStage; +import getjobs.modules.task.enums.TaskStatus; +import getjobs.modules.task.event.TaskUpdateEvent; +import getjobs.service.RecruitmentService; +import getjobs.service.RecruitmentServiceFactory; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * 猎聘任务服务 + * + * @author getjobs + */ +@Slf4j +@Service +public class LiepinTaskService { + + + private final RecruitmentServiceFactory serviceFactory; + + private final JobService jobService; + + private final JobRepository jobRepository; + private final ApplicationEventPublisher eventPublisher; + + private String dataPath; + + public LiepinTaskService(RecruitmentServiceFactory serviceFactory, + JobService jobService, JobRepository jobRepository, ApplicationEventPublisher eventPublisher) { + this.serviceFactory = serviceFactory; + this.jobService = jobService; + this.jobRepository = jobRepository; + this.eventPublisher = eventPublisher; + } + + @PostConstruct + public void init() { + try { + initializeDataFiles(); + } catch (IOException e) { + log.error("数据文件初始化失败", e); + } + } + + public LoginResult login(ConfigDTO config) { + publishTaskUpdate(TaskStage.LOGIN, TaskStatus.STARTED, 0, "开始登录"); + try { + log.info("开始执行猎聘登录操作"); + RecruitmentService liepinService = serviceFactory.getService(RecruitmentPlatformEnum.LIEPIN); + boolean success = liepinService.login(config); + + LoginResult result = new LoginResult(); + result.setSuccess(success); + result.setMessage(success ? "登录成功" : "登录失败"); + result.setTimestamp(new Date()); + + publishTaskUpdate(TaskStage.LOGIN, success ? TaskStatus.SUCCESS : TaskStatus.FAILURE, 0, result.getMessage()); + log.info("猎聘登录操作完成,结果: {}", success ? "成功" : "失败"); + return result; + + } catch (Exception e) { + log.error("猎聘登录操作执行失败", e); + publishTaskUpdate(TaskStage.LOGIN, TaskStatus.FAILURE, 0, "登录异常: " + e.getMessage()); + + LoginResult result = new LoginResult(); + result.setSuccess(false); + result.setMessage("登录异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + public CollectResult collectJobs(ConfigDTO config) { + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.STARTED, 0, "开始采集"); + try { + log.info("开始执行猎聘岗位采集操作"); + RecruitmentService liepinService = serviceFactory.getService(RecruitmentPlatformEnum.LIEPIN); + + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.IN_PROGRESS, 0, "正在采集岗位"); + List allJobDTOS = liepinService.collectJobs(config); + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.IN_PROGRESS, allJobDTOS.size(), "已采集 " + allJobDTOS.size() + " 个岗位"); + + + int savedCount = 0; + if (!allJobDTOS.isEmpty()) { + try { + savedCount = jobService.saveJobs(allJobDTOS, RecruitmentPlatformEnum.LIEPIN.name()); + log.info("成功保存 {} 个岗位到数据库", savedCount); + } catch (Exception e) { + log.error("保存岗位到数据库失败", e); + } + } + + CollectResult result = new CollectResult(); + result.setJobCount(allJobDTOS.size()); + result.setJobs(allJobDTOS); + String message = String.format("成功采集到 %d 个岗位,保存到数据库 %d 个", allJobDTOS.size(), savedCount); + result.setMessage(message); + result.setTimestamp(new Date()); + + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.SUCCESS, allJobDTOS.size(), message); + log.info("猎聘岗位采集操作完成,采集到 {} 个岗位,保存到数据库 {} 个", allJobDTOS.size(), savedCount); + return result; + + } catch (Exception e) { + log.error("猎聘岗位采集操作执行失败", e); + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.FAILURE, 0, "采集异常: " + e.getMessage()); + + CollectResult result = new CollectResult(); + result.setJobCount(0); + result.setJobs(new ArrayList<>()); + result.setMessage("采集异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + public FilterResult filterJobs(ConfigDTO config) { + publishTaskUpdate(TaskStage.FILTER, TaskStatus.STARTED, 0, "开始过滤"); + try { + log.info("开始执行猎聘岗位过滤操作"); + RecruitmentService liepinService = serviceFactory.getService(RecruitmentPlatformEnum.LIEPIN); + List allJobEntities = jobService.findAllJobEntitiesByPlatform(RecruitmentPlatformEnum.LIEPIN.getPlatformCode()); + if (allJobEntities == null || allJobEntities.isEmpty()) { + throw new IllegalArgumentException("数据库中未找到职位数据或职位数据为空"); + } + + List jobDTOS = allJobEntities.stream().map(jobService::convertToDTO).collect(Collectors.toList()); + List filteredJobDTOS = liepinService.filterJobs(jobDTOS, config); + + List filteredJobIds = jobDTOS.stream() + .filter(j -> !filteredJobDTOS.contains(j)) + .map(JobDTO::getEncryptJobId) + .collect(Collectors.toList()); + + if (!filteredJobIds.isEmpty()) { + jobService.updateJobStatus(filteredJobIds, JobStatusEnum.FILTERED.getCode(), "被过滤"); + } + + FilterResult result = new FilterResult(); + result.setOriginalCount(allJobEntities.size()); + result.setFilteredCount(filteredJobDTOS.size()); + result.setJobs(filteredJobDTOS); + String message = String.format("原始岗位 %d 个,过滤后剩余 %d 个,已过滤 %d 个", allJobEntities.size(), filteredJobDTOS.size(), filteredJobIds.size()); + result.setMessage(message); + result.setTimestamp(new Date()); + + publishTaskUpdate(TaskStage.FILTER, TaskStatus.SUCCESS, filteredJobDTOS.size(), message); + + log.info("猎聘岗位过滤操作完成,原始 {} 个,过滤后 {} 个,已过滤 {} 个", allJobEntities.size(), filteredJobDTOS.size(), filteredJobIds.size()); + return result; + + } catch (Exception e) { + log.error("猎聘岗位过滤操作执行失败", e); + publishTaskUpdate(TaskStage.FILTER, TaskStatus.FAILURE, 0, "过滤异常: " + e.getMessage()); + FilterResult result = new FilterResult(); + result.setOriginalCount(0); + result.setFilteredCount(0); + result.setJobs(new ArrayList<>()); + result.setMessage("过滤异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + public DeliveryResult deliverJobs(ConfigDTO config, boolean enableActualDelivery) { + publishTaskUpdate(TaskStage.DELIVER, TaskStatus.STARTED, 0, "开始投递"); + try { + log.info("开始执行猎聘岗位投递操作,实际投递: {}", enableActualDelivery); + List jobEntities = jobRepository.findByStatusAndPlatform(JobStatusEnum.PENDING.getCode(), RecruitmentPlatformEnum.LIEPIN.getPlatformCode()); + if (jobEntities == null || jobEntities.isEmpty()) { + throw new IllegalArgumentException("未找到可投递的猎聘岗位记录"); + } + + List filteredJobDTOS = jobEntities.stream().map(jobService::convertToDTO).collect(Collectors.toList()); + int deliveredCount = 0; + + if (enableActualDelivery) { + RecruitmentService liepinService = serviceFactory.getService(RecruitmentPlatformEnum.LIEPIN); + deliveredCount = liepinService.deliverJobs(filteredJobDTOS, config); + liepinService.saveData(dataPath); + log.info("实际投递完成,成功投递 {} 个岗位", deliveredCount); + } else { + deliveredCount = filteredJobDTOS.size(); + log.info("模拟投递完成,可投递岗位 {} 个", deliveredCount); + } + + DeliveryResult result = new DeliveryResult(); + result.setTotalCount(filteredJobDTOS.size()); + result.setDeliveredCount(deliveredCount); + result.setActualDelivery(enableActualDelivery); + String message = String.format("%s完成,处理 %d 个岗位", enableActualDelivery ? "实际投递" : "模拟投递", deliveredCount); + result.setMessage(message); + result.setTimestamp(new Date()); + + if (filteredJobDTOS.size() <= 10) { + result.setJobDetails(buildJobDetails(filteredJobDTOS)); + } + + publishTaskUpdate(TaskStage.DELIVER, TaskStatus.SUCCESS, deliveredCount, message); + log.info("猎聘岗位投递操作完成,处理 {} 个岗位", deliveredCount); + return result; + + } catch (Exception e) { + log.error("猎聘岗位投递操作执行失败", e); + publishTaskUpdate(TaskStage.DELIVER, TaskStatus.FAILURE, 0, "投递异常: " + e.getMessage()); + + DeliveryResult result = new DeliveryResult(); + result.setTotalCount(0); + result.setDeliveredCount(0); + result.setActualDelivery(enableActualDelivery); + result.setMessage("投递异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + private void publishTaskUpdate(TaskStage stage, TaskStatus status, Integer count, String message) { + TaskUpdatePayload payload = TaskUpdatePayload.builder() + .platform(RecruitmentPlatformEnum.LIEPIN) + .stage(stage) + .status(status) + .count(count) + .message(message) + .build(); + eventPublisher.publishEvent(new TaskUpdateEvent(this, payload)); + } + + private List buildJobDetails(List jobDTOS) { + return jobDTOS.stream() + .map(job -> String.format("%s - %s | %s | %s", job.getCompanyName(), job.getJobName(), job.getSalary(), job.getJobArea())) + .collect(Collectors.toList()); + } + + private void initializeDataFiles() throws IOException { + String userHome = System.getProperty("user.home"); + dataPath = userHome + File.separator + "getjobs"; + File getJobsDir = new File(dataPath); + if (!getJobsDir.exists()) { + getJobsDir.mkdirs(); + } + File dataDir = new File(dataPath, "data"); + if (!dataDir.exists()) { + dataDir.mkdirs(); + } + dataPath = dataDir.getAbsolutePath(); + } + + public static class LoginResult { + private String taskId; + private boolean success; + private String message; + private Date timestamp; + public String getTaskId() { return taskId; } + public void setTaskId(String taskId) { this.taskId = taskId; } + public boolean isSuccess() { return success; } + public void setSuccess(boolean success) { this.success = success; } + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + public Date getTimestamp() { return timestamp; } + public void setTimestamp(Date timestamp) { this.timestamp = timestamp; } + } + + public static class CollectResult { + private String taskId; + private int jobCount; + private List jobs; + private String message; + private Date timestamp; + public String getTaskId() { return taskId; } + public void setTaskId(String taskId) { this.taskId = taskId; } + public int getJobCount() { return jobCount; } + public void setJobCount(int jobCount) { this.jobCount = jobCount; } + public List getJobs() { return jobs; } + public void setJobs(List jobs) { this.jobs = jobs; } + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + public Date getTimestamp() { return timestamp; } + public void setTimestamp(Date timestamp) { this.timestamp = timestamp; } + } + + public static class FilterResult { + private String taskId; + private int originalCount; + private int filteredCount; + private List jobs; + private String message; + private Date timestamp; + public String getTaskId() { return taskId; } + public void setTaskId(String taskId) { this.taskId = taskId; } + public int getOriginalCount() { return originalCount; } + public void setOriginalCount(int originalCount) { this.originalCount = originalCount; } + public int getFilteredCount() { return filteredCount; } + public void setFilteredCount(int filteredCount) { this.filteredCount = filteredCount; } + public List getJobs() { return jobs; } + public void setJobs(List jobs) { this.jobs = jobs; } + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + public Date getTimestamp() { return timestamp; } + public void setTimestamp(Date timestamp) { this.timestamp = timestamp; } + } + + public static class DeliveryResult { + private String taskId; + private int totalCount; + private int deliveredCount; + private boolean actualDelivery; + private String message; + private Date timestamp; + private List jobDetails; + public String getTaskId() { return taskId; } + public void setTaskId(String taskId) { this.taskId = taskId; } + public int getTotalCount() { return totalCount; } + public void setTotalCount(int totalCount) { this.totalCount = totalCount; } + public int getDeliveredCount() { return deliveredCount; } + public void setDeliveredCount(int deliveredCount) { this.deliveredCount = deliveredCount; } + public boolean isActualDelivery() { return actualDelivery; } + public void setActualDelivery(boolean actualDelivery) { this.actualDelivery = actualDelivery; } + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + public Date getTimestamp() { return timestamp; } + public void setTimestamp(Date timestamp) { this.timestamp = timestamp; } + public List getJobDetails() { return jobDetails; } + public void setJobDetails(List jobDetails) { this.jobDetails = jobDetails; } + } +} diff --git a/src/main/java/getjobs/modules/liepin/service/impl/LiepinRecruitmentServiceImpl.java b/src/main/java/getjobs/modules/liepin/service/impl/LiepinRecruitmentServiceImpl.java new file mode 100644 index 00000000..9b8f5af1 --- /dev/null +++ b/src/main/java/getjobs/modules/liepin/service/impl/LiepinRecruitmentServiceImpl.java @@ -0,0 +1,356 @@ +package getjobs.modules.liepin.service.impl; + +import com.microsoft.playwright.Page; +import getjobs.common.dto.ConfigDTO; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.common.service.PlaywrightService; +import getjobs.modules.boss.dto.JobDTO; +import getjobs.service.JobFilterService; +import getjobs.modules.liepin.service.LiepinElementLocators; +import getjobs.modules.liepin.service.playwright.LiePinApiMonitorService; +import getjobs.repository.entity.ConfigEntity; +import getjobs.service.ConfigService; +import getjobs.service.RecruitmentService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import jakarta.annotation.PostConstruct; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; + +@Slf4j +@Service +@RequiredArgsConstructor +public class LiepinRecruitmentServiceImpl implements RecruitmentService { + + private final LiePinApiMonitorService liePinApiMonitorService; + private final PlaywrightService playwrightService; + private final ConfigService configService; + private final JobFilterService jobFilterService; + + private Page page; + + private static final String HOME_URL = RecruitmentPlatformEnum.LIEPIN.getHomeUrl(); + private static final String SEARCH_JOB_URL = "https://www.liepin.com/zhaopin/?"; + + @Override + public RecruitmentPlatformEnum getPlatform() { + return RecruitmentPlatformEnum.LIEPIN; + } + + @PostConstruct + public void init() { + this.page = playwrightService.getPage(RecruitmentPlatformEnum.LIEPIN); + liePinApiMonitorService.init(); + } + + @Override + public boolean login(ConfigDTO config) { + log.info("开始猎聘登录检查"); + try { + page.navigate(HOME_URL); + // 这里的登录检查逻辑需要根据猎聘的页面元素进行调整 + if (LiepinElementLocators.isLoginRequired(page)) { + log.info("需要登录,开始登录流程"); + return performLogin(); + } else { + log.info("猎聘已登录"); + return true; + } + } catch (Exception e) { + log.error("猎聘登录失败", e); + return false; + } + } + + @Override + public List collectJobs(ConfigDTO config) { + log.info("开始猎聘岗位采集"); + List allJobDTOS = new ArrayList<>(); + try { + for (String cityCode : config.getCityCodeCodes()) { + for (String keyword : config.getKeywordsList()) { + List jobDTOS = collectJobsByCity(cityCode, keyword, config); + allJobDTOS.addAll(jobDTOS); + } + } + log.info("猎聘岗位采集完成,共采集{}个岗位", allJobDTOS.size()); + return allJobDTOS; + } catch (Exception e) { + log.error("猎聘岗位采集失败", e); + return allJobDTOS; + } + } + + @Override + public List collectRecommendJobs(ConfigDTO config) { + return List.of(); + } + + @Override + public List filterJobs(List jobDTOS, ConfigDTO config) { + // 从数据库获取猎聘平台的配置,不使用前端传递的config + ConfigEntity configEntity = configService.loadByPlatformType(RecruitmentPlatformEnum.LIEPIN.getPlatformCode()); + if (configEntity == null) { + log.warn("数据库中未找到猎聘平台配置,跳过过滤"); + return jobDTOS; + } + + // 将ConfigEntity转换为ConfigDTO + ConfigDTO dbConfig = convertConfigEntityToDTO(configEntity); + + return jobFilterService.filterJobs(jobDTOS, dbConfig,false); + } + + @Override + public int deliverJobs(List jobDTOS, ConfigDTO config) { + log.info("开始执行猎聘岗位投递操作,待投递岗位数量: {}", jobDTOS.size()); + AtomicInteger successCount = new AtomicInteger(0); + try (Page jobPage = page.context().newPage()) { + for (JobDTO jobDTO : jobDTOS) { + try { + log.info("正在投递岗位: {}", jobDTO.getJobName()); + jobPage.navigate(jobDTO.getHref()); + jobPage.waitForLoadState(); + + // 等待页面加载完成 + jobPage.waitForTimeout(2000); + + // 1. 点击"聊一聊"按钮 + if (LiepinElementLocators.clickChatWithRecruiter(jobPage)) { + // 等待聊天窗口打开 + jobPage.waitForTimeout(2000); + + // 获取打招呼内容 + String greetingMessage = config.getSayHi(); + if (greetingMessage == null || greetingMessage.trim().isEmpty()) { + greetingMessage = "您好,我对这个职位很感兴趣,期待与您进一步沟通!"; + log.warn("未配置打招呼内容,使用默认消息"); + } + + // 2. 输入打招呼内容 + if (LiepinElementLocators.inputChatMessage(jobPage, greetingMessage)) { + // 等待输入完成 + jobPage.waitForTimeout(1000); + + // 3. 点击发送按钮 + if (LiepinElementLocators.clickSendButton(jobPage)) { + log.info("岗位投递成功: {}", jobDTO.getJobName()); + successCount.getAndIncrement(); + } else { + log.warn("发送消息失败: {}", jobDTO.getJobName()); + } + } else { + log.warn("输入打招呼内容失败: {}", jobDTO.getJobName()); + } + } else { + log.warn("点击聊一聊按钮失败或HR不可聊天: {}", jobDTO.getJobName()); + } + + // 随机等待 3-5 秒 + try { + Thread.sleep((3 + new Random().nextInt(3)) * 1000L); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } catch (Exception e) { + log.error("投递岗位 {} 时发生异常: {}", jobDTO.getJobName(), e.getMessage()); + } + } + } catch (Exception e) { + log.error("猎聘岗位投递过程中发生严重错误", e); + } + log.info("猎聘岗位投递完成,成功投递 {} 个岗位", successCount.get()); + return successCount.get(); + } + + @Override + public boolean isDeliveryLimitReached() { + return false; + } + + @Override + public void saveData(String dataPath) { + log.info("猎聘数据保存功能待实现"); + } + + /** + * 将ConfigEntity转换为ConfigDTO + * 使用反射创建ConfigDTO实例,因为构造函数是私有的 + */ + private ConfigDTO convertConfigEntityToDTO(ConfigEntity entity) { + try { + // 通过反射创建ConfigDTO实例 + java.lang.reflect.Constructor constructor = ConfigDTO.class.getDeclaredConstructor(); + constructor.setAccessible(true); + ConfigDTO dto = constructor.newInstance(); + + // 基础字段映射 + dto.setSayHi(entity.getSayHi()); + dto.setEnableAIJobMatchDetection(entity.getEnableAIJobMatchDetection()); + dto.setEnableAIGreeting(entity.getEnableAIGreeting()); + dto.setFilterDeadHR(entity.getFilterDeadHR()); + dto.setSendImgResume(entity.getSendImgResume()); + dto.setKeyFilter(entity.getKeyFilter()); + dto.setRecommendJobs(entity.getRecommendJobs()); + dto.setCheckStateOwned(entity.getCheckStateOwned()); + dto.setResumeImagePath(entity.getResumeImagePath()); + dto.setResumeContent(entity.getResumeContent()); + dto.setWaitTime(entity.getWaitTime()); + dto.setPlatformType(entity.getPlatformType()); + + // 列表字段转换为逗号分隔的字符串 + if (entity.getKeywords() != null) { + dto.setKeywords(String.join(",", entity.getKeywords())); + } + if (entity.getCityCode() != null) { + dto.setCityCode(String.join(",", entity.getCityCode())); + } + if (entity.getIndustry() != null) { + dto.setIndustry(String.join(",", entity.getIndustry())); + } + if (entity.getExperience() != null) { + dto.setExperience(String.join(",", entity.getExperience())); + } + if (entity.getDegree() != null) { + dto.setDegree(String.join(",", entity.getDegree())); + } + if (entity.getScale() != null) { + dto.setScale(String.join(",", entity.getScale())); + } + if (entity.getStage() != null) { + dto.setStage(String.join(",", entity.getStage())); + } + if (entity.getDeadStatus() != null) { + dto.setDeadStatus(entity.getDeadStatus()); + } + + // 期望薪资处理 + if (entity.getExpectedSalary() != null && entity.getExpectedSalary().size() >= 2) { + dto.setMinSalary(entity.getExpectedSalary().get(0)); + dto.setMaxSalary(entity.getExpectedSalary().get(1)); + } + + // 其他字段 + dto.setCustomCityCode(entity.getCustomCityCode()); + dto.setJobType(entity.getJobType()); + dto.setSalary(entity.getSalary()); + dto.setExpectedPosition(entity.getExpectedPosition()); + dto.setPublishTime(entity.getPublishTime()); + + return dto; + } catch (Exception e) { + log.error("ConfigEntity转换为ConfigDTO失败", e); + // 如果转换失败,返回ConfigDTO的单例实例作为备用 + return ConfigDTO.getInstance(); + } + } + + private List collectJobsByCity(String cityCode, String keyword, ConfigDTO config) { + String searchUrl = buildSearchUrl(cityCode, keyword, config); + log.info("开始采集,城市: {},关键词: {},URL: {}", cityCode, keyword, searchUrl); + + List jobDTOS = new ArrayList<>(); + + try { + page.navigate(searchUrl); + // 等待页面加载 + page.waitForLoadState(); + + // 从第1页开始循环点击分页,浏览所有岗位 + int pageNumber = 1; + while (LiepinElementLocators.clickPageNumber(page, pageNumber)) { + log.info("正在采集第 {} 页的职位", pageNumber); + + // 等待5-10秒,确保API响应被拦截并完成数据入库 + try { + int waitSeconds = 5 + new Random().nextInt(6); // 5-10秒 + log.info("等待 {} 秒以完成数据采集和入库", waitSeconds); + Thread.sleep(waitSeconds * 1000L); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("等待过程被中断: {}", e.getMessage()); + } + + pageNumber++; + } + + log.info("所有分页已遍历完成,共 {} 页", pageNumber - 1); + + } catch (Exception e) { + log.error("采集城市: {}, 关键词: {} 的职位失败", cityCode, keyword, e); + } + + log.info("城市: {}, 关键词: {} 的职位采集完成,共{}个职位", cityCode, keyword, jobDTOS.size()); + return jobDTOS; + } + + private String buildSearchUrl(String cityCode, String keyword, ConfigDTO config) { + StringBuilder url = new StringBuilder(SEARCH_JOB_URL); + try { + url.append("key=").append(URLEncoder.encode(keyword, StandardCharsets.UTF_8)); + if (cityCode != null && !cityCode.trim().isEmpty()) { + url.append("&city=").append(cityCode); + url.append("&dq=").append(cityCode); + } + if (config.getPublishTime() != null) { + url.append("&pubTime=").append(config.getPublishTime()); + } + url.append("¤tPage=").append(0); // 从第一页开始 + url.append("&pageSize=").append(40); + + if (config.getExperience() != null && !config.getExperience().isEmpty()) { + url.append("&workYearCode=").append(config.getExperience()); + } + if (config.getIndustry() != null && !config.getIndustry().isEmpty()) { + url.append("&industry=").append(URLEncoder.encode(config.getIndustry(), StandardCharsets.UTF_8)); + } + if (config.getSalary() != null && !config.getSalary().isEmpty()) { + url.append("&salaryCode=").append(config.getSalary()); + } + if (config.getJobType() != null && !config.getJobType().isEmpty()) { + url.append("&jobKind=").append(config.getJobType()); + } + if (config.getScale() != null && !config.getScale().isEmpty()) { + url.append("&compScale=").append(config.getScale()); + } + if (config.getCompanyType() != null && !config.getCompanyType().isEmpty()) { + url.append("&compKind=").append(config.getCompanyType()); + } + if (config.getStage() != null && !config.getStage().isEmpty()) { + url.append("&compStage=").append(config.getStage()); + } + if (config.getDegree() != null && !config.getDegree().isEmpty()) { + url.append("&eduLevel=").append(config.getDegree()); + } + url.append("&sfrom=search_job_pc"); + url.append("&scene=condition"); + + } catch (Exception e) { + log.error("构建搜索URL失败", e); + } + return url.toString(); + } + + private boolean performLogin() { + try { + page.navigate(HOME_URL); + + log.info("等待用户手动登录..."); +// 登录逻辑需要根据猎聘的页面元素进行调整 + while (!LiepinElementLocators.isUserLoggedIn(page)) { + Thread.sleep((3 + new Random().nextInt(3)) * 1000L); + } + log.info("登录成功"); + return true; + } catch (Exception e) { + log.error("登录过程中发生错误", e); + return false; + } + } +} diff --git a/src/main/java/getjobs/modules/liepin/service/playwright/LiePinApiMonitorService.java b/src/main/java/getjobs/modules/liepin/service/playwright/LiePinApiMonitorService.java new file mode 100644 index 00000000..3ccae6cb --- /dev/null +++ b/src/main/java/getjobs/modules/liepin/service/playwright/LiePinApiMonitorService.java @@ -0,0 +1,149 @@ +package getjobs.modules.liepin.service.playwright; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.playwright.Page; +import com.microsoft.playwright.PlaywrightException; +import com.microsoft.playwright.Response; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.common.service.PlaywrightService; +import getjobs.modules.liepin.dto.LiePinApiResponse; +import getjobs.repository.JobRepository; +import getjobs.repository.entity.JobEntity; +import getjobs.utils.LiePinDataConverter; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class LiePinApiMonitorService { + + private final JobRepository jobRepository; + private final LiePinDataConverter dataConverter; + private final PlaywrightService playwrightService; + private final ObjectMapper objectMapper = new ObjectMapper(); + + @PostConstruct + public void init() { + setupLiePinApiMonitor(); + } + + public void setupLiePinApiMonitor() { + try { + Page page = playwrightService.getPage(RecruitmentPlatformEnum.LIEPIN); + setupResponseMonitor(page); + log.info("猎聘API监控服务初始化完成"); + } catch (Exception e) { + log.error("猎聘API监控服务初始化失败: {}", e.getMessage(), e); + } + } + + private void setupResponseMonitor(Page page) { + page.onResponse(response -> { + try { + String url = response.url(); + if (url.contains("/api/com.liepin.searchfront4c.pc-search-job")) { + handleLiePinSearchResponse(response); + } + } catch (PlaywrightException e) { + // 忽略响应对象不存在的异常,避免影响主流程 + log.debug("响应监听器处理异常(可忽略): {}", e.getMessage()); + } catch (Exception e) { + log.warn("响应监听器处理发生意外异常: {}", e.getMessage()); + } + }); + } + + private void handleLiePinSearchResponse(Response response) { + try { + log.info("=== 猎聘职位搜索响应拦截 ==="); + log.info("响应状态: {}", response.status()); + log.info("响应URL: {}", response.url()); + + // 先检查响应是否有效 + if (response.ok()) { + String body = response.text(); + log.info("响应体长度: {} 字符", body.length()); + parseAndSaveLiePinData(body, "猎聘职位搜索"); + } else { + log.warn("响应状态不正常: {}", response.status()); + } + log.info("=========================="); + } catch (PlaywrightException e) { + // 响应对象已经不存在或无法访问 + log.debug("读取猎聘响应体失败(响应对象可能已清理): {}", e.getMessage()); + } catch (Exception e) { + log.error("处理猎聘响应时发生异常: {}", e.getMessage(), e); + } + } + + @Transactional + public void parseAndSaveLiePinData(String body, String source) { + try { + LiePinApiResponse response = objectMapper.readValue(body, LiePinApiResponse.class); + + if (response.getFlag() != 1 || response.getData() == null || response.getData().getData() == null || response.getData().getData().getJobCardList() == null) { + log.warn("猎聘API响应错误或没有职位数据"); + return; + } + + List jobCards = response.getData().getData().getJobCardList(); + log.info("从{}获取到 {} 个职位数据", source, jobCards.size()); + + List validJobCards = jobCards.stream() + .filter(dataConverter::isValidJobData) + .toList(); + + if (validJobCards.isEmpty()) { + log.warn("没有有效的职位数据,来源: {}", source); + return; + } + + log.info("过滤后有效职位数据: {} 个", validJobCards.size()); + + List jobEntities = validJobCards.stream() + .map(dataConverter::convertToJobEntity) + .filter(Objects::nonNull) + .toList(); + + if (!jobEntities.isEmpty()) { + List newJobs = jobEntities.stream() + .filter(entity -> !isJobExists(entity.getEncryptJobId())) + .collect(Collectors.toList()); + + if (!newJobs.isEmpty()) { + jobRepository.saveAll(newJobs); + log.info("成功保存 {} 个新职位到数据库,来源: {}", newJobs.size(), source); + newJobs.forEach(job -> log.info("保存职位: {} - {} - {}", + job.getJobTitle(), job.getCompanyName(), job.getSalaryDesc())); + } else { + log.info("所有职位都已存在,跳过保存,来源: {}", source); + } + } else { + log.warn("没有有效的职位数据可以保存,来源: {}", source); + } + + } catch (Exception e) { + log.error("解析并保存猎聘职位数据失败,来源: {}", source, e); + } + } + + private boolean isJobExists(String encryptJobId) { + if (encryptJobId == null || encryptJobId.trim().isEmpty()) { + return false; + } + try { + return jobRepository.existsByEncryptJobId(encryptJobId); + } catch (Exception e) { + log.warn("检查职位是否存在时发生错误: {}", e.getMessage()); + return false; + } + } +} diff --git a/src/main/java/getjobs/modules/liepin/web/LiepinTaskController.java b/src/main/java/getjobs/modules/liepin/web/LiepinTaskController.java new file mode 100644 index 00000000..f786d880 --- /dev/null +++ b/src/main/java/getjobs/modules/liepin/web/LiepinTaskController.java @@ -0,0 +1,212 @@ +package getjobs.modules.liepin.web; + +import getjobs.common.dto.ConfigDTO; +import getjobs.modules.liepin.service.LiepinTaskService; +import getjobs.modules.liepin.service.LiepinTaskService.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * 猎聘任务控制器 - 提供4个独立的API接口 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Slf4j +@RestController +@RequestMapping("/api/liepin/task") +public class LiepinTaskController { + + private final LiepinTaskService liepinTaskService; + + public LiepinTaskController(LiepinTaskService liepinTaskService) { + this.liepinTaskService = liepinTaskService; + } + + /** + * 1. 登录接口 + * POST /api/liepin/task/login + * + * @param config 猎聘配置信息 + * @return 登录结果 + */ + @PostMapping("/login") + public ResponseEntity> login(@RequestBody ConfigDTO config) { + log.info("接收到猎聘登录请求"); + + try { + LoginResult result = liepinTaskService.login(config); + + Map response = new HashMap<>(); + response.put("success", result.isSuccess()); + response.put("taskId", result.getTaskId()); + response.put("message", result.getMessage()); + response.put("timestamp", result.getTimestamp()); + + if (result.isSuccess()) { + log.info("猎聘登录成功,任务ID: {}", result.getTaskId()); + return ResponseEntity.ok(response); + } else { + log.warn("猎聘登录失败,任务ID: {}, 原因: {}", result.getTaskId(), result.getMessage()); + return ResponseEntity.badRequest().body(response); + } + + } catch (Exception e) { + log.error("猎聘登录接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "登录接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 2. 采集岗位接口 + * POST /api/liepin/task/collect + * + * @param config 猎聘配置信息 + * @return 采集结果 + */ + @PostMapping("/collect") + public ResponseEntity> collectJobs(@RequestBody ConfigDTO config) { + log.info("接收到猎聘岗位采集请求"); + + try { + CollectResult result = liepinTaskService.collectJobs(config); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("taskId", result.getTaskId()); + response.put("message", result.getMessage()); + response.put("jobCount", result.getJobCount()); + response.put("timestamp", result.getTimestamp()); + + // 如果需要返回详细岗位信息,可以添加以下行 + // response.put("jobs", result.getJobs()); + + log.info("猎聘岗位采集完成,任务ID: {}, 采集到 {} 个岗位", result.getTaskId(), result.getJobCount()); + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("猎聘岗位采集接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "采集接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 3. 过滤岗位接口 + * POST /api/liepin/task/filter + * + * @param request 过滤请求 + * @return 过滤结果 + */ + @PostMapping("/filter") + public ResponseEntity> filterJobs(@RequestBody FilterRequest request) { + log.info("接收到猎聘岗位过滤请求"); + + try { + FilterResult result = liepinTaskService.filterJobs(request.getConfig()); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", result.getMessage()); + response.put("originalCount", result.getOriginalCount()); + response.put("filteredCount", result.getFilteredCount()); + response.put("timestamp", result.getTimestamp()); + + // 如果需要返回详细岗位信息,可以添加以下行 + // response.put("jobs", result.getJobs()); + + log.info("猎聘岗位过滤完成,原始 {} 个,过滤后 {} 个", + result.getOriginalCount(), result.getFilteredCount()); + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("猎聘岗位过滤接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "过滤接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 4. 投递岗位接口 + * POST /api/liepin/task/deliver + * + * @param request 投递请求(包含配置和是否实际投递) + * @return 投递结果 + */ + @PostMapping("/deliver") + public ResponseEntity> deliverJobs(@RequestBody DeliveryRequest request) { + log.info("接收到猎聘岗位投递请求,实际投递: {}", + request.isEnableActualDelivery()); + + try { + DeliveryResult result = liepinTaskService.deliverJobs( + request.getConfig(), + request.isEnableActualDelivery()); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("taskId", result.getTaskId()); + response.put("message", result.getMessage()); + response.put("totalCount", result.getTotalCount()); + response.put("deliveredCount", result.getDeliveredCount()); + response.put("actualDelivery", result.isActualDelivery()); + response.put("timestamp", result.getTimestamp()); + + // 如果有岗位详情,添加到响应中 + if (result.getJobDetails() != null && !result.getJobDetails().isEmpty()) { + response.put("jobDetails", result.getJobDetails()); + } + + log.info("猎聘岗位投递完成,任务ID: {}, 处理 {} 个岗位", + result.getTaskId(), result.getDeliveredCount()); + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("猎聘岗位投递接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "投递接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + + // 请求DTO类 + public static class FilterRequest { + private ConfigDTO config; + public ConfigDTO getConfig() { return config; } + public void setConfig(ConfigDTO config) { this.config = config; } + } + + public static class DeliveryRequest { + private ConfigDTO config; + private boolean enableActualDelivery = false; // 默认为模拟投递 + + public ConfigDTO getConfig() { + return config; + } + + public void setConfig(ConfigDTO config) { + this.config = config; + } + + public boolean isEnableActualDelivery() { + return enableActualDelivery; + } + + public void setEnableActualDelivery(boolean enableActualDelivery) { + this.enableActualDelivery = enableActualDelivery; + } + } +} diff --git a/src/main/java/getjobs/modules/task/dto/LoginStatusDTO.java b/src/main/java/getjobs/modules/task/dto/LoginStatusDTO.java new file mode 100644 index 00000000..841331ca --- /dev/null +++ b/src/main/java/getjobs/modules/task/dto/LoginStatusDTO.java @@ -0,0 +1,48 @@ +package getjobs.modules.task.dto; + +import getjobs.common.enums.RecruitmentPlatformEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 登录状态DTO + * 用于存储各平台的登录状态检查结果 + * + * @author getjobs + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LoginStatusDTO { + + /** + * 平台枚举 + */ + private RecruitmentPlatformEnum platform; + + /** + * 是否已登录 + */ + private Boolean isLoggedIn; + + /** + * 检查时间 + */ + private LocalDateTime checkTime; + + /** + * 备注信息(如检查失败原因等) + */ + private String remark; + + /** + * 最后登录时间(如果有的话) + */ + private LocalDateTime lastLoginTime; +} + diff --git a/src/main/java/getjobs/modules/task/dto/TaskUpdatePayload.java b/src/main/java/getjobs/modules/task/dto/TaskUpdatePayload.java new file mode 100644 index 00000000..c2763548 --- /dev/null +++ b/src/main/java/getjobs/modules/task/dto/TaskUpdatePayload.java @@ -0,0 +1,17 @@ +package getjobs.modules.task.dto; + +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.task.enums.TaskStage; +import getjobs.modules.task.enums.TaskStatus; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class TaskUpdatePayload { + private RecruitmentPlatformEnum platform; + private TaskStage stage; + private TaskStatus status; + private Integer count; + private String message; +} diff --git a/src/main/java/getjobs/modules/task/enums/TaskStage.java b/src/main/java/getjobs/modules/task/enums/TaskStage.java new file mode 100644 index 00000000..4f067081 --- /dev/null +++ b/src/main/java/getjobs/modules/task/enums/TaskStage.java @@ -0,0 +1,17 @@ +package getjobs.modules.task.enums; + +import lombok.Getter; + +@Getter +public enum TaskStage { + LOGIN("LOGIN"), + COLLECT("COLLECT"), + FILTER("FILTER"), + DELIVER("DELIVER"); + + private final String name; + + TaskStage(String name) { + this.name = name; + } +} diff --git a/src/main/java/getjobs/modules/task/enums/TaskStatus.java b/src/main/java/getjobs/modules/task/enums/TaskStatus.java new file mode 100644 index 00000000..9e16524c --- /dev/null +++ b/src/main/java/getjobs/modules/task/enums/TaskStatus.java @@ -0,0 +1,8 @@ +package getjobs.modules.task.enums; + +public enum TaskStatus { + STARTED, + SUCCESS, + FAILURE, + IN_PROGRESS +} diff --git a/src/main/java/getjobs/modules/task/event/TaskUpdateEvent.java b/src/main/java/getjobs/modules/task/event/TaskUpdateEvent.java new file mode 100644 index 00000000..fce41cc0 --- /dev/null +++ b/src/main/java/getjobs/modules/task/event/TaskUpdateEvent.java @@ -0,0 +1,18 @@ +package getjobs.modules.task.event; + +import getjobs.modules.task.dto.TaskUpdatePayload; +import org.springframework.context.ApplicationEvent; + +public class TaskUpdateEvent extends ApplicationEvent { + + private final TaskUpdatePayload payload; + + public TaskUpdateEvent(Object source, TaskUpdatePayload payload) { + super(source); + this.payload = payload; + } + + public TaskUpdatePayload getPayload() { + return payload; + } +} diff --git a/src/main/java/getjobs/modules/task/listener/TaskStatusListener.java b/src/main/java/getjobs/modules/task/listener/TaskStatusListener.java new file mode 100644 index 00000000..441acb0f --- /dev/null +++ b/src/main/java/getjobs/modules/task/listener/TaskStatusListener.java @@ -0,0 +1,19 @@ +package getjobs.modules.task.listener; + +import getjobs.modules.task.event.TaskUpdateEvent; +import getjobs.modules.task.service.TaskStatusService; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class TaskStatusListener { + + private final TaskStatusService taskStatusService; + + @EventListener + public void handleTaskUpdateEvent(TaskUpdateEvent event) { + taskStatusService.updateTaskStatus(event.getPayload()); + } +} diff --git a/src/main/java/getjobs/modules/task/service/LoginStatusCheckScheduler.java b/src/main/java/getjobs/modules/task/service/LoginStatusCheckScheduler.java new file mode 100644 index 00000000..d06eef53 --- /dev/null +++ b/src/main/java/getjobs/modules/task/service/LoginStatusCheckScheduler.java @@ -0,0 +1,431 @@ +package getjobs.modules.task.service; + +import com.microsoft.playwright.Page; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.common.service.PlaywrightService; +import getjobs.modules.boss.BossElementLocators; +import getjobs.modules.job51.service.Job51ElementLocators; +import getjobs.modules.liepin.service.LiepinElementLocators; +import getjobs.modules.task.dto.LoginStatusDTO; +import getjobs.modules.task.dto.TaskUpdatePayload; +import getjobs.modules.task.enums.TaskStage; +import getjobs.modules.task.enums.TaskStatus; +import getjobs.modules.task.event.TaskUpdateEvent; +import getjobs.modules.zhilian.service.ZhiLianElementLocators; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 登录状态检查定时任务 + * 定期检查各个招聘平台的登录状态 + * + * @author getjobs + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class LoginStatusCheckScheduler { + + private final PlaywrightService playwrightService; + private final ApplicationEventPublisher eventPublisher; + + /** + * 存储各平台的登录状态 + */ + private final Map loginStatusMap = new ConcurrentHashMap<>(); + + /** + * 定时检查登录状态 + * 每5分钟执行一次 + */ + @Scheduled(fixedRate = 15000) // 15秒 + public void checkLoginStatus() { + log.info("========== 开始定时检查各平台登录状态 =========="); + + try { + // 预先检查所有平台的登录状态,确定哪些平台需要检查 + List platformsNeedCheck = preCheckLoginStatus(); + + // 只为需要检查的平台发布检查开始事件 + publishCheckStartEvent(platformsNeedCheck); + + // 只检查需要检查的平台 + if (platformsNeedCheck.contains(RecruitmentPlatformEnum.LIEPIN)) { + checkLiepinLoginStatus(); + } + + if (platformsNeedCheck.contains(RecruitmentPlatformEnum.JOB_51)) { + checkJob51LoginStatus(); + } + + if (platformsNeedCheck.contains(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN)) { + checkZhilianLoginStatus(); + } + + if (platformsNeedCheck.contains(RecruitmentPlatformEnum.BOSS_ZHIPIN)) { + checkBossLoginStatus(); + } + + // 打印登录状态汇总 + printLoginStatusSummary(); + + // 发布检查完成事件 + publishCheckCompleteEvent(); + + } catch (Exception e) { + log.error("定时检查登录状态时发生异常", e); + } + + log.info("========== 登录状态检查完成 =========="); + } + + /** + * 预先检查所有平台的登录状态 + * 返回需要进行检查的平台列表 + * + * @return 需要检查的平台列表 + */ + private List preCheckLoginStatus() { + List platformsNeedCheck = new ArrayList<>(); + + // 检查每个平台的当前登录状态 + for (RecruitmentPlatformEnum platform : RecruitmentPlatformEnum.values()) { + LoginStatusDTO currentStatus = loginStatusMap.get(platform); + + // 如果没有登录状态信息,或者当前状态为未登录,则需要检查 + if (currentStatus == null || !currentStatus.getIsLoggedIn()) { + platformsNeedCheck.add(platform); + log.debug("平台 {} 需要检查登录状态", platform.getPlatformName()); + } else { + log.debug("平台 {} 已登录,跳过检查", platform.getPlatformName()); + } + } + + return platformsNeedCheck; + } + + /** + * 发布检查开始事件 + * 只为需要检查的平台发布事件 + * + * @param platformsNeedCheck 需要检查的平台列表 + */ + private void publishCheckStartEvent(List platformsNeedCheck) { + try { + if (platformsNeedCheck.isEmpty()) { + log.debug("所有平台均已登录,无需发布检查开始事件"); + return; + } + + // 只为需要检查的平台发布检查开始事件 + for (RecruitmentPlatformEnum platform : platformsNeedCheck) { + TaskUpdatePayload payload = TaskUpdatePayload.builder() + .platform(platform) + .stage(TaskStage.LOGIN) + .status(TaskStatus.STARTED) + .count(0) + .message("开始检查登录状态") + .build(); + + eventPublisher.publishEvent(new TaskUpdateEvent(this, payload)); + } + log.debug("已为 {} 个平台发布登录状态检查开始事件", platformsNeedCheck.size()); + } catch (Exception e) { + log.error("发布检查开始事件失败", e); + } + } + + /** + * 发布检查完成事件 + */ + private void publishCheckCompleteEvent() { + try { + long loggedInCount = loginStatusMap.values().stream() + .filter(LoginStatusDTO::getIsLoggedIn) + .count(); + + String message = String.format("登录状态检查完成,%d/%d 个平台已登录", + loggedInCount, loginStatusMap.size()); + + log.info(message); + } catch (Exception e) { + log.error("发布检查完成事件失败", e); + } + } + + /** + * 检查猎聘登录状态 + */ + private void checkLiepinLoginStatus() { + try { + // 如果当前已经是登录状态,则跳过检查 + LoginStatusDTO currentStatus = loginStatusMap.get(RecruitmentPlatformEnum.LIEPIN); + if (currentStatus != null && currentStatus.getIsLoggedIn()) { + log.debug("猎聘当前已登录,跳过检查"); + return; + } + + Page page = playwrightService.getPage(RecruitmentPlatformEnum.LIEPIN); + if (page == null) { + log.warn("猎聘页面未初始化,无法检查登录状态"); + updateLoginStatus(RecruitmentPlatformEnum.LIEPIN, false, "页面未初始化"); + return; + } + + boolean isLoggedIn = LiepinElementLocators.isUserLoggedIn(page); + + // 如果登录成功,打印当前域的cookie + if (isLoggedIn) { + printPageCookies(page, "猎聘"); + } + + updateLoginStatus(RecruitmentPlatformEnum.LIEPIN, isLoggedIn, + isLoggedIn ? "已登录" : "未登录"); + + log.info("猎聘登录状态: {}", isLoggedIn ? "已登录" : "未登录"); + + } catch (Exception e) { + log.error("检查猎聘登录状态失败", e); + updateLoginStatus(RecruitmentPlatformEnum.LIEPIN, false, "检查失败: " + e.getMessage()); + } + } + + /** + * 检查51Job登录状态 + */ + private void checkJob51LoginStatus() { + try { + // 如果当前已经是登录状态,则跳过检查 + LoginStatusDTO currentStatus = loginStatusMap.get(RecruitmentPlatformEnum.JOB_51); + if (currentStatus != null && currentStatus.getIsLoggedIn()) { + log.debug("51Job当前已登录,跳过检查"); + return; + } + + Page page = playwrightService.getPage(RecruitmentPlatformEnum.JOB_51); + if (page == null) { + log.warn("51Job页面未初始化,无法检查登录状态"); + updateLoginStatus(RecruitmentPlatformEnum.JOB_51, false, "页面未初始化"); + return; + } + + boolean isLoggedIn = Job51ElementLocators.isUserLoggedIn(page); + + // 如果登录成功,打印当前域的cookie + if (isLoggedIn) { + printPageCookies(page, "51Job"); + } + + updateLoginStatus(RecruitmentPlatformEnum.JOB_51, isLoggedIn, + isLoggedIn ? "已登录" : "未登录"); + + log.info("51Job登录状态: {}", isLoggedIn ? "已登录" : "未登录"); + + } catch (Exception e) { + log.error("检查51Job登录状态失败", e); + updateLoginStatus(RecruitmentPlatformEnum.JOB_51, false, "检查失败: " + e.getMessage()); + } + } + + /** + * 检查智联招聘登录状态 + */ + private void checkZhilianLoginStatus() { + try { + // 如果当前已经是登录状态,则跳过检查 + LoginStatusDTO currentStatus = loginStatusMap.get(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN); + if (currentStatus != null && currentStatus.getIsLoggedIn()) { + log.debug("智联招聘当前已登录,跳过检查"); + return; + } + + Page page = playwrightService.getPage(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN); + if (page == null) { + log.warn("智联招聘页面未初始化,无法检查登录状态"); + updateLoginStatus(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN, false, "页面未初始化"); + return; + } + + boolean isLoggedIn = ZhiLianElementLocators.isUserLoggedIn(page); + + // 如果登录成功,打印当前域的cookie + if (isLoggedIn) { + printPageCookies(page, "智联招聘"); + } + + updateLoginStatus(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN, isLoggedIn, + isLoggedIn ? "已登录" : "未登录"); + + log.info("智联招聘登录状态: {}", isLoggedIn ? "已登录" : "未登录"); + + } catch (Exception e) { + log.error("检查智联招聘登录状态失败", e); + updateLoginStatus(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN, false, "检查失败: " + e.getMessage()); + } + } + + /** + * 检查Boss直聘登录状态 + */ + private void checkBossLoginStatus() { + try { + // 如果当前已经是登录状态,则跳过检查 + LoginStatusDTO currentStatus = loginStatusMap.get(RecruitmentPlatformEnum.BOSS_ZHIPIN); + if (currentStatus != null && currentStatus.getIsLoggedIn()) { + log.debug("Boss直聘当前已登录,跳过检查"); + return; + } + + Page page = playwrightService.getPage(RecruitmentPlatformEnum.BOSS_ZHIPIN); + if (page == null) { + log.warn("Boss直聘页面未初始化,无法检查登录状态"); + updateLoginStatus(RecruitmentPlatformEnum.BOSS_ZHIPIN, false, "页面未初始化"); + return; + } + + boolean isLoggedIn = BossElementLocators.isUserLoggedIn(page); + + // 如果登录成功,打印当前域的cookie + if (isLoggedIn) { + printPageCookies(page, "Boss直聘"); + } + + updateLoginStatus(RecruitmentPlatformEnum.BOSS_ZHIPIN, isLoggedIn, + isLoggedIn ? "已登录" : "未登录"); + + log.info("Boss直聘登录状态: {}", isLoggedIn ? "已登录" : "未登录"); + + } catch (Exception e) { + log.error("检查Boss直聘登录状态失败", e); + updateLoginStatus(RecruitmentPlatformEnum.BOSS_ZHIPIN, false, "检查失败: " + e.getMessage()); + } + } + + /** + * 更新登录状态 + */ + private void updateLoginStatus(RecruitmentPlatformEnum platform, boolean isLoggedIn, String remark) { + LoginStatusDTO statusDTO = LoginStatusDTO.builder() + .platform(platform) + .isLoggedIn(isLoggedIn) + .checkTime(LocalDateTime.now()) + .remark(remark) + .lastLoginTime(isLoggedIn ? LocalDateTime.now() : null) + .build(); + + loginStatusMap.put(platform, statusDTO); + + // 发布登录状态更新事件,推送到TaskStatusListener + publishLoginStatusUpdate(platform, isLoggedIn, remark); + } + + /** + * 发布登录状态更新事件 + * + * @param platform 平台枚举 + * @param isLoggedIn 是否已登录 + * @param remark 备注信息 + */ + private void publishLoginStatusUpdate(RecruitmentPlatformEnum platform, boolean isLoggedIn, String remark) { + try { + TaskUpdatePayload payload = TaskUpdatePayload.builder() + .platform(platform) + .stage(TaskStage.LOGIN) + .status(isLoggedIn ? TaskStatus.SUCCESS : TaskStatus.FAILURE) + .count(0) + .message(String.format("登录状态检查: %s", remark)) + .build(); + + eventPublisher.publishEvent(new TaskUpdateEvent(this, payload)); + log.debug("已发布平台 {} 的登录状态更新事件: {}", platform.getPlatformName(), remark); + } catch (Exception e) { + log.error("发布平台 {} 的登录状态更新事件失败", platform.getPlatformName(), e); + } + } + + /** + * 打印登录状态汇总 + */ + private void printLoginStatusSummary() { + log.info("---------- 登录状态汇总 ----------"); + loginStatusMap.forEach((platform, status) -> { + log.info("平台: {} | 状态: {} | 备注: {} | 检查时间: {}", + platform.getPlatformName(), + status.getIsLoggedIn() ? "✓ 已登录" : "✗ 未登录", + status.getRemark(), + status.getCheckTime()); + }); + log.info("----------------------------------"); + } + + /** + * 获取所有平台的登录状态 + * + * @return 登录状态列表 + */ + public List getAllLoginStatus() { + return new ArrayList<>(loginStatusMap.values()); + } + + /** + * 获取指定平台的登录状态 + * + * @param platform 平台枚举 + * @return 登录状态DTO + */ + public LoginStatusDTO getLoginStatus(RecruitmentPlatformEnum platform) { + return loginStatusMap.get(platform); + } + + /** + * 立即执行登录状态检查(手动触发) + */ + public void checkNow() { + log.info("手动触发登录状态检查"); + checkLoginStatus(); + } + + /** + * 打印页面的Cookie信息 + * + * @param page 页面对象 + * @param platformName 平台名称 + */ + private void printPageCookies(Page page, String platformName) { + try { + // 获取当前页面URL的所有cookie + var cookies = page.context().cookies(page.url()); + + log.info("========== {} 登录成功,当前域Cookie信息 ==========", platformName); + log.info("当前URL: {}", page.url()); + log.info("Cookie数量: {}", cookies.size()); + + for (int i = 0; i < cookies.size(); i++) { + var cookie = cookies.get(i); + log.info("Cookie[{}] - name: {}, value: {}, domain: {}, path: {}, expires: {}, httpOnly: {}, secure: {}", + i + 1, + cookie.name, + cookie.value, + cookie.domain, + cookie.path, + cookie.expires, + cookie.httpOnly, + cookie.secure); + } + + log.info("=".repeat(50 + platformName.length())); + } catch (Exception e) { + log.error("打印 {} 的Cookie信息失败", platformName, e); + } + } +} + diff --git a/src/main/java/getjobs/modules/task/service/TaskStatusService.java b/src/main/java/getjobs/modules/task/service/TaskStatusService.java new file mode 100644 index 00000000..dc67024b --- /dev/null +++ b/src/main/java/getjobs/modules/task/service/TaskStatusService.java @@ -0,0 +1,33 @@ +package getjobs.modules.task.service; + +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.task.dto.TaskUpdatePayload; +import getjobs.modules.task.enums.TaskStage; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class TaskStatusService { + + private final Map taskStatusMap = new ConcurrentHashMap<>(); + + public void updateTaskStatus(TaskUpdatePayload payload) { + String key = getKey(payload.getPlatform(), payload.getStage()); + taskStatusMap.put(key, payload); + } + + public TaskUpdatePayload getTaskStatus(RecruitmentPlatformEnum platform, TaskStage stage) { + String key = getKey(platform, stage); + return taskStatusMap.get(key); + } + + public Map getAllTaskStatuses() { + return taskStatusMap; + } + + private String getKey(RecruitmentPlatformEnum platform, TaskStage stage) { + return platform.name() + "_" + stage.getName(); + } +} diff --git a/src/main/java/getjobs/modules/task/web/LoginStatusController.java b/src/main/java/getjobs/modules/task/web/LoginStatusController.java new file mode 100644 index 00000000..d9799e01 --- /dev/null +++ b/src/main/java/getjobs/modules/task/web/LoginStatusController.java @@ -0,0 +1,166 @@ +package getjobs.modules.task.web; + +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.task.dto.LoginStatusDTO; +import getjobs.modules.task.service.LoginStatusCheckScheduler; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 登录状态查询控制器 + * 提供登录状态查询和手动触发检查的接口 + * + * @author getjobs + */ +@Slf4j +@RestController +@RequestMapping("/api/login-status") +@RequiredArgsConstructor +public class LoginStatusController { + + private final LoginStatusCheckScheduler loginStatusCheckScheduler; + + /** + * 获取所有平台的登录状态 + * + * @return 所有平台的登录状态列表 + */ + @GetMapping("/all") + public ResponseEntity> getAllLoginStatus() { + try { + List statusList = loginStatusCheckScheduler.getAllLoginStatus(); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "获取登录状态成功"); + response.put("data", statusList); + response.put("count", statusList.size()); + + return ResponseEntity.ok(response); + } catch (Exception e) { + log.error("获取所有平台登录状态失败", e); + + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "获取登录状态失败: " + e.getMessage()); + + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 获取指定平台的登录状态 + * + * @param platformName 平台名称(LIEPIN, JOB_51, ZHILIAN, BOSS_ZHIPIN) + * @return 指定平台的登录状态 + */ + @GetMapping("/{platformName}") + public ResponseEntity> getLoginStatus(@PathVariable String platformName) { + try { + RecruitmentPlatformEnum platform = RecruitmentPlatformEnum.valueOf(platformName.toUpperCase()); + LoginStatusDTO status = loginStatusCheckScheduler.getLoginStatus(platform); + + Map response = new HashMap<>(); + if (status != null) { + response.put("success", true); + response.put("message", "获取登录状态成功"); + response.put("data", status); + } else { + response.put("success", false); + response.put("message", "暂无该平台的登录状态信息"); + response.put("data", null); + } + + return ResponseEntity.ok(response); + } catch (IllegalArgumentException e) { + log.error("无效的平台名称: {}", platformName); + + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "无效的平台名称: " + platformName); + + return ResponseEntity.badRequest().body(response); + } catch (Exception e) { + log.error("获取平台登录状态失败", e); + + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "获取登录状态失败: " + e.getMessage()); + + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 手动触发登录状态检查 + * + * @return 触发结果 + */ + @PostMapping("/check") + public ResponseEntity> triggerLoginCheck() { + try { + log.info("收到手动触发登录状态检查请求"); + loginStatusCheckScheduler.checkNow(); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "登录状态检查已触发"); + + return ResponseEntity.ok(response); + } catch (Exception e) { + log.error("触发登录状态检查失败", e); + + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "触发检查失败: " + e.getMessage()); + + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 获取登录状态摘要 + * 返回各平台登录状态的统计信息 + * + * @return 登录状态摘要 + */ + @GetMapping("/summary") + public ResponseEntity> getLoginStatusSummary() { + try { + List statusList = loginStatusCheckScheduler.getAllLoginStatus(); + + long loggedInCount = statusList.stream() + .filter(LoginStatusDTO::getIsLoggedIn) + .count(); + long notLoggedInCount = statusList.size() - loggedInCount; + + Map summary = new HashMap<>(); + summary.put("totalPlatforms", statusList.size()); + summary.put("loggedIn", loggedInCount); + summary.put("notLoggedIn", notLoggedInCount); + summary.put("details", statusList); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "获取登录状态摘要成功"); + response.put("data", summary); + + return ResponseEntity.ok(response); + } catch (Exception e) { + log.error("获取登录状态摘要失败", e); + + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "获取摘要失败: " + e.getMessage()); + + return ResponseEntity.internalServerError().body(response); + } + } +} + diff --git a/src/main/java/getjobs/modules/task/web/TaskStatusController.java b/src/main/java/getjobs/modules/task/web/TaskStatusController.java new file mode 100644 index 00000000..d61fa880 --- /dev/null +++ b/src/main/java/getjobs/modules/task/web/TaskStatusController.java @@ -0,0 +1,23 @@ +package getjobs.modules.task.web; + +import getjobs.modules.task.dto.TaskUpdatePayload; +import getjobs.modules.task.service.TaskStatusService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@RequestMapping("/api/tasks") +@RequiredArgsConstructor +public class TaskStatusController { + + private final TaskStatusService taskStatusService; + + @GetMapping("/status") + public Map getAllTaskStatuses() { + return taskStatusService.getAllTaskStatuses(); + } +} diff --git a/src/main/java/getjobs/modules/zhilian/dto/ZhiLianApiResponse.java b/src/main/java/getjobs/modules/zhilian/dto/ZhiLianApiResponse.java new file mode 100644 index 00000000..700118f3 --- /dev/null +++ b/src/main/java/getjobs/modules/zhilian/dto/ZhiLianApiResponse.java @@ -0,0 +1,258 @@ +package getjobs.modules.zhilian.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 智联招聘API响应数据结构 + * 对应接口:https://fe-api.zhaopin.com/c/i/search/positions + * + * @author getjobs + * @since v2.1.1 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ZhiLianApiResponse { + + @JsonProperty("code") + private Integer code; + + @JsonProperty("data") + private ZhiLianData data; + + /** + * 主要数据包装类 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class ZhiLianData { + @JsonProperty("count") + private Integer count; + + @JsonProperty("list") + private List list; + + @JsonProperty("isEndPage") + private Integer isEndPage; + } + + /** + * 职位信息项 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class ZhiLianJobItem { + @JsonProperty("jobId") + private Long jobId; + + @JsonProperty("name") + private String name; + + @JsonProperty("salary60") + private String salary60; + + @JsonProperty("salaryReal") + private String salaryReal; + + @JsonProperty("workCity") + private String workCity; + + @JsonProperty("cityDistrict") + private String cityDistrict; + + @JsonProperty("streetName") + private String streetName; + + @JsonProperty("education") + private String education; + + @JsonProperty("workingExp") + private String workingExp; + + @JsonProperty("workType") + private String workType; + + @JsonProperty("jobSummary") + private String jobSummary; + + @JsonProperty("publishTime") + private String publishTime; + + @JsonProperty("firstPublishTime") + private String firstPublishTime; + + @JsonProperty("positionUrl") + private String positionUrl; + + @JsonProperty("number") + private String number; + + @JsonProperty("recruitNumber") + private Integer recruitNumber; + + // 公司信息 + @JsonProperty("companyId") + private Long companyId; + + @JsonProperty("companyName") + private String companyName; + + @JsonProperty("companyLogo") + private String companyLogo; + + @JsonProperty("companySize") + private String companySize; + + @JsonProperty("companyUrl") + private String companyUrl; + + @JsonProperty("industryName") + private String industryName; + + @JsonProperty("property") + private String property; + + @JsonProperty("propertyName") + private String propertyName; + + // 技能标签 + @JsonProperty("jobSkillTags") + private List jobSkillTags; + + @JsonProperty("skillLabel") + private List skillLabel; + + @JsonProperty("showSkillTags") + private List showSkillTags; + + // 福利标签 + @JsonProperty("welfareTagList") + private List welfareTagList; + + @JsonProperty("jobKnowledgeWelfareFeatures") + private List jobKnowledgeWelfareFeatures; + + // HR信息 + @JsonProperty("staffCard") + private StaffCard staffCard; + + // 匹配信息 + @JsonProperty("matchInfo") + private MatchInfo matchInfo; + + @JsonProperty("jobHitReason") + private String jobHitReason; + + // 其他信息 + @JsonProperty("subJobTypeLevelName") + private String subJobTypeLevelName; + + @JsonProperty("financingStage") + private FinancingStage financingStage; + } + + /** + * 技能标签 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class SkillTag { + @JsonProperty("id") + private Long id; + + @JsonProperty("name") + private String name; + + @JsonProperty("standard") + private Boolean standard; + } + + /** + * 技能标签(简化版) + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class SkillLabel { + @JsonProperty("state") + private Integer state; + + @JsonProperty("value") + private String value; + } + + /** + * 显示技能标签 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class ShowSkillTag { + @JsonProperty("tag") + private String tag; + + @JsonProperty("highlightBackGroundColor") + private String highlightBackGroundColor; + + @JsonProperty("highlightWordColor") + private String highlightWordColor; + } + + /** + * HR信息 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class StaffCard { + @JsonProperty("id") + private Long id; + + @JsonProperty("staffName") + private String staffName; + + @JsonProperty("hrJob") + private String hrJob; + + @JsonProperty("avatar") + private String avatar; + + @JsonProperty("hrOnlineState") + private String hrOnlineState; + + @JsonProperty("hrStateInfo") + private String hrStateInfo; + + @JsonProperty("lastOnlineTime") + private Long lastOnlineTime; + } + + /** + * 匹配信息 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class MatchInfo { + @JsonProperty("matched") + private Integer matched; + + @JsonProperty("icon") + private String icon; + + @JsonProperty("tagState") + private Integer tagState; + } + + /** + * 融资阶段 + */ + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class FinancingStage { + @JsonProperty("code") + private Integer code; + + @JsonProperty("name") + private String name; + } +} diff --git a/src/main/java/getjobs/modules/zhilian/service/ZhiLianElementLocators.java b/src/main/java/getjobs/modules/zhilian/service/ZhiLianElementLocators.java new file mode 100644 index 00000000..1697eb3a --- /dev/null +++ b/src/main/java/getjobs/modules/zhilian/service/ZhiLianElementLocators.java @@ -0,0 +1,408 @@ +package getjobs.modules.zhilian.service; + +import com.microsoft.playwright.Locator; +import com.microsoft.playwright.Page; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * 智联招聘网站元素定位器 + * 用于定位智联招聘网站上的各种元素和解析职位信息 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Slf4j +public class ZhiLianElementLocators { + + // ==================== 页面元素定位器 ==================== + + /** 登录按钮 */ + public static final String LOGIN_BUTTON = "a[href*='login']"; + + /** 登录/注册按钮 */ + public static final String LOGIN_REGISTER_BUTTON = "a.home-header__c-no-login"; + + /** 我要招人按钮(已登录状态) */ + public static final String RECRUITER_BUTTON = "a.home-header__b-login"; + + /** 用户信息区域 */ + public static final String USER_INFO_AREA = "div.user-info"; + + /** 页面头部右侧区域 */ + public static final String HEADER_RIGHT_AREA = "div.home-header__right"; + + /** 投递按钮 */ + public static final String APPLY_BUTTON = "button.apply-btn, a.apply-btn"; + + /** 已投递标记 */ + public static final String APPLIED_MARK = "span.applied-mark"; + + /** 用户登录后的用户名元素 */ + public static final String USER_WELCOME_USERNAME = "div.zp-welcome__username"; + + /** 退出按钮 */ + public static final String LOGOUT_BUTTON = "a#logout"; + + /** 详情页的立即投递按钮 */ + public static final String SUMMARY_APPLY_BUTTON = "div.summary-plane__action button.a-button"; + + // ==================== 职位采集方法 ==================== + + /** + * 根据页码点击分页元素 + * 在class="soupager"的div元素中查找指定页码的a元素并点击 + * + * @param page Playwright页面对象 + * @param pageNumber 要点击的页码 + * @return true表示点击成功,false表示未找到对应页码或点击失败 + */ + public static boolean clickPageNumber(Page page, int pageNumber) { + try { + // 首先等待分页元素出现 + try { + page.waitForSelector("div.soupager", new Page.WaitForSelectorOptions().setTimeout(8000)); + } catch (Exception e) { + log.error("等待分页元素出现超时: {}", e.getMessage()); + return false; + } + + // 查找class="soupager"的div元素 + Locator pagerElement = page.locator("div.soupager"); + + if (pagerElement.count() == 0) { + log.error("未找到分页元素 div.soupager"); + return false; + } + + // 查找包含指定页码文本的a元素,使用精确匹配避免误匹配 + Locator pageElement = pagerElement.locator("a.soupager__index"). + getByText(String.valueOf(pageNumber), new Locator.GetByTextOptions().setExact(true)); + + if (pageElement.count() == 0) { + log.info("未找到页码为 {} 的分页元素,可能已到达最后一页", pageNumber); + return false; + } + + // 检查元素是否已经是当前激活状态 + Locator activePageElement = pagerElement.locator("a.soupager__index--active"); + if (activePageElement.count() > 0) { + String activePageText = activePageElement.textContent().trim(); + if (String.valueOf(pageNumber).equals(activePageText)) { + log.info("页码 {} 已经是当前激活状态,无需点击", pageNumber); + return true; + } + } + + // 确保元素可见且可点击 + pageElement.scrollIntoViewIfNeeded(); + + // 在点击之前添加随机延迟3-5秒,模拟真实用户行为 + Random random = new Random(); + int delay = 3000 + random.nextInt(2001); // 3000-5000毫秒之间的随机延迟 + log.info("准备点击页码 {},随机延迟 {} 毫秒", pageNumber, delay); + page.waitForTimeout(delay); + + // 点击页码元素 + pageElement.click(); + + // 等待页面状态变化,确保点击生效 + try { + // 等待当前页码变为激活状态 + page.waitForSelector("div.soupager a.soupager__index--active:has-text('" + pageNumber + "')", + new Page.WaitForSelectorOptions().setTimeout(5000)); + log.info("成功点击页码: {},页面已切换", pageNumber); + } catch (Exception e) { + log.warn("等待页码 {} 激活状态超时,但点击操作已执行: {}", pageNumber, e.getMessage()); + } + + return true; + + } catch (Exception e) { + log.error("点击页码 {} 时发生错误: {}", pageNumber, e.getMessage()); + return false; + } + } + + /** + * 获取当前激活的页码 + * 查找class="soupager"中带有"soupager__index--active"类的a元素,获取其页码值 + * + * @param page Playwright页面对象 + * @return 当前激活的页码,如果未找到则返回-1 + */ + public static int getCurrentPageNumber(Page page) { + try { + // 查找class="soupager"的div元素 + Locator pagerElement = page.locator("div.soupager"); + + if (pagerElement.count() == 0) { + log.error("未找到分页元素 div.soupager"); + return -1; + } + + // 查找当前激活的页码元素 + Locator activePageElement = pagerElement.locator("a.soupager__index--active"); + + if (activePageElement.count() > 0) { + String pageText = activePageElement.textContent().trim(); + try { + int currentPage = Integer.parseInt(pageText); + log.info("当前激活页码: {}", currentPage); + return currentPage; + } catch (NumberFormatException e) { + log.error("解析当前页码失败: {}", pageText); + return -1; + } + } + + log.error("未找到当前激活的页码元素"); + return -1; + + } catch (Exception e) { + log.error("获取当前页码时发生错误: {}", e.getMessage()); + return -1; + } + } + + /** + * 获取所有可见的页码列表 + * 从class="soupager"的div元素中获取所有a.soupager__index元素的页码值 + * + * @param page Playwright页面对象 + * @return 所有可见页码的列表,如果出错则返回空列表 + */ + public static List getVisiblePageNumbers(Page page) { + List pageNumbers = new ArrayList<>(); + + try { + // 查找class="soupager"的div元素 + Locator pagerElement = page.locator("div.soupager"); + + if (pagerElement.count() == 0) { + log.error("未找到分页元素 div.soupager"); + return pageNumbers; + } + + // 获取所有页码元素 + Locator pageElements = pagerElement.locator("a.soupager__index"); + int elementCount = pageElements.count(); + + for (int i = 0; i < elementCount; i++) { + Locator pageElement = pageElements.nth(i); + String pageText = pageElement.textContent().trim(); + + try { + int pageNumber = Integer.parseInt(pageText); + pageNumbers.add(pageNumber); + } catch (NumberFormatException e) { + log.warn("跳过非数字页码: {}", pageText); + } + } + + log.info("获取到可见页码列表: {}", pageNumbers); + + } catch (Exception e) { + log.error("获取可见页码列表时发生错误: {}", e.getMessage()); + } + + return pageNumbers; + } + + /** + * 检查是否需要登录 + * + * @param page Playwright页面对象 + * @return true表示需要登录,false表示已登录 + */ + public static boolean isLoginRequired(Page page) { + try { + // 等待页面加载完成 + log.debug("等待页面加载完成..."); + page.waitForLoadState(); + + // 等待DOM稳定,确保所有元素都已加载 + page.waitForTimeout(2000); + + // 首先尝试等待页面头部右侧区域加载 + try { + page.waitForSelector(HEADER_RIGHT_AREA, new Page.WaitForSelectorOptions().setTimeout(5000)); + log.debug("页面头部右侧区域已加载"); + } catch (Exception e) { + log.debug("页面头部右侧区域未在5秒内加载完成,继续检查"); + } + + // 检查页面头部右侧区域是否存在 + Locator headerRightArea = page.locator(HEADER_RIGHT_AREA); + if (!headerRightArea.isVisible()) { + log.debug("未找到页面头部右侧区域,尝试其他检查方法"); + // 如果头部区域不存在,使用原有的检查方法 + Locator loginButton = page.locator(LOGIN_BUTTON); + if (loginButton.isVisible()) { + log.debug("找到传统登录按钮,需要登录"); + return true; + } + + Locator userInfo = page.locator(USER_INFO_AREA); + boolean isLoggedIn = userInfo.isVisible(); + log.debug("用户信息区域可见性: {}", isLoggedIn); + return !isLoggedIn; + } + + // 等待一下确保头部区域内的元素加载完成 + page.waitForTimeout(1000); + + // 检查是否存在"登录/注册"按钮 + Locator loginRegisterButton = page.locator(LOGIN_REGISTER_BUTTON); + if (loginRegisterButton.isVisible()) { + log.debug("找到'登录/注册'按钮,需要登录"); + return true; + } + + // 检查是否存在"我要招人"按钮(已登录状态的标识) + Locator recruiterButton = page.locator(RECRUITER_BUTTON); + if (recruiterButton.isVisible()) { + log.debug("找到'我要招人'按钮,已登录状态"); + return false; + } + + // 如果以上都没有找到,可能页面结构发生变化,使用备用检查方法 + log.debug("页面结构可能发生变化,使用备用检查方法"); + + // 等待一下再进行备用检查 + page.waitForTimeout(1000); + + // 检查是否存在传统的登录按钮 + Locator loginButton = page.locator(LOGIN_BUTTON); + if (loginButton.isVisible()) { + log.debug("找到传统登录按钮,需要登录"); + return true; + } + + // 检查是否存在用户信息区域 + Locator userInfo = page.locator(USER_INFO_AREA); + boolean isLoggedIn = userInfo.isVisible(); + log.debug("备用检查 - 用户信息区域可见性: {}", isLoggedIn); + return !isLoggedIn; + + } catch (Exception e) { + log.error("检查登录状态失败", e); + return true; // 出错时默认需要登录 + } + } + + /** + * 点击投递按钮 + * + * @param page Playwright页面对象 + * @return true表示投递成功,false表示投递失败 + */ + public static boolean clickApplyButton(Page page) { + try { + // 检查是否已经投递过 + Locator appliedMark = page.locator(APPLIED_MARK); + if (appliedMark.isVisible()) { + log.info("该职位已投递过"); + return false; + } + + // 点击投递按钮 + Locator applyButton = page.locator(APPLY_BUTTON); + if (applyButton.isVisible()) { + applyButton.click(); + page.waitForTimeout(1000); + log.info("投递成功"); + return true; + } else { + log.warn("未找到投递按钮"); + return false; + } + + } catch (Exception e) { + log.error("投递失败", e); + return false; + } + } + + /** + * 点击详情页的“立即投递”按钮 + * + * @param page Playwright页面对象 + * @return true表示点击成功,false表示失败 + */ + public static boolean clickSummaryApplyButton(Page page) { + try { + // 等待按钮出现 + try { + page.waitForSelector(SUMMARY_APPLY_BUTTON, new Page.WaitForSelectorOptions().setTimeout(5000)); + } catch (Exception e) { + log.warn("等待“立即投递”按钮超时(5秒),按钮可能不存在: {}", e.getMessage()); + return false; + } + + Locator applyButton = page.locator(SUMMARY_APPLY_BUTTON).filter(new Locator.FilterOptions().setHasText("立即投递")); + + if (applyButton.count() == 0) { + log.error("未找到“立即投递”按钮"); + return false; + } + + // 确保元素可见 + applyButton.scrollIntoViewIfNeeded(); + + // 点击按钮 + applyButton.click(); + + log.info("成功点击“立即投递”按钮"); + + // 等待一小段时间 + page.waitForTimeout(1000); + + return true; + + } catch (Exception e) { + log.error("点击“立即投递”按钮时发生错误: {}", e.getMessage()); + return false; + } + } + + + /** + * 判断用户是否已成功完成登录 + * 通过检查页面头部右侧区域是否存在"登录/注册"按钮来判断登录状态 + * 如果存在"登录/注册"按钮,说明未登录;不存在则说明已登录 + * + * @param page Playwright页面对象 + * @return true表示已登录,false表示未登录 + */ + public static boolean isUserLoggedIn(Page page) { + try { + // 等待页面加载完成 + page.waitForLoadState(); + + // 等待DOM稳定,确保所有元素都已加载 + page.waitForTimeout(2000); + + log.debug("检查用户登录状态..."); + + // 检查是否存在"登录/注册"按钮 + // 如果存在该按钮,说明用户未登录;不存在则说明用户已登录 + Locator loginRegisterButton = page.locator(LOGIN_REGISTER_BUTTON); + + if (loginRegisterButton.isVisible()) { + log.debug("找到'登录/注册'按钮,用户未登录"); + return false; + } + + log.debug("未找到'登录/注册'按钮,用户已登录"); + return true; + + } catch (Exception e) { + log.error("检查用户登录状态失败", e); + return false; // 出错时默认未登录 + } + } +} diff --git a/src/main/java/getjobs/modules/zhilian/service/ZhilianTaskService.java b/src/main/java/getjobs/modules/zhilian/service/ZhilianTaskService.java new file mode 100644 index 00000000..dfe337d0 --- /dev/null +++ b/src/main/java/getjobs/modules/zhilian/service/ZhilianTaskService.java @@ -0,0 +1,604 @@ +package getjobs.modules.zhilian.service; + +import getjobs.common.dto.ConfigDTO; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.boss.dto.JobDTO; +import getjobs.common.enums.JobStatusEnum; +import getjobs.repository.entity.JobEntity; +import getjobs.repository.JobRepository; +import getjobs.service.JobService; +import getjobs.service.PlaywrightManager; +import getjobs.modules.task.dto.TaskUpdatePayload; +import getjobs.modules.task.enums.TaskStage; +import getjobs.modules.task.enums.TaskStatus; +import getjobs.modules.task.event.TaskUpdateEvent; +import getjobs.service.RecruitmentService; +import getjobs.service.RecruitmentServiceFactory; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +import jakarta.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * 智联招聘任务服务 - 将4个核心操作分离为独立的服务方法 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Slf4j +@Service +public class ZhilianTaskService { + + + private final RecruitmentServiceFactory serviceFactory; + + private final JobService jobService; + + private final JobRepository jobRepository; + private final ApplicationEventPublisher eventPublisher; + + // 数据目录路径 + private String dataPath; + + public ZhilianTaskService(RecruitmentServiceFactory serviceFactory, + JobService jobService, JobRepository jobRepository, ApplicationEventPublisher eventPublisher) { + this.serviceFactory = serviceFactory; + this.jobService = jobService; + this.jobRepository = jobRepository; + this.eventPublisher = eventPublisher; + } + + @PostConstruct + public void init() { + try { + initializeDataFiles(); + } catch (IOException e) { + log.error("数据文件初始化失败", e); + } + } + + /** + * 1. 登录操作 + * + * @param config 配置信息 + * @return 登录结果 + */ + public LoginResult login(ConfigDTO config) { + publishTaskUpdate(TaskStage.LOGIN, TaskStatus.STARTED, 0, "开始登录"); + try { + log.info("开始执行智联招聘登录操作"); + + // 获取智联招聘服务 + RecruitmentService zhilianService = serviceFactory.getService(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN); + + // 执行登录 + boolean success = zhilianService.login(config); + + LoginResult result = new LoginResult(); + result.setSuccess(success); + result.setMessage(success ? "登录成功" : "登录失败"); + result.setTimestamp(new Date()); + + publishTaskUpdate(TaskStage.LOGIN, success ? TaskStatus.SUCCESS : TaskStatus.FAILURE, 0, result.getMessage()); + + log.info("智联招聘登录操作完成,结果: {}", success ? "成功" : "失败"); + return result; + + } catch (Exception e) { + log.error("智联招聘登录操作执行失败", e); + publishTaskUpdate(TaskStage.LOGIN, TaskStatus.FAILURE, 0, "登录异常: " + e.getMessage()); + + LoginResult result = new LoginResult(); + result.setSuccess(false); + result.setMessage("登录异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + /** + * 2. 采集操作 + * + * @param config 配置信息 + * @return 采集结果 + */ + public CollectResult collectJobs(ConfigDTO config) { + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.STARTED, 0, "开始采集"); + try { + log.info("开始执行智联招聘岗位采集操作"); + + // 获取智联招聘服务 + RecruitmentService zhilianService = serviceFactory.getService(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN); + + // 采集岗位 + List allJobDTOS = new ArrayList<>(); + + // 采集搜索岗位 + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.IN_PROGRESS, 0, "正在采集岗位"); + List searchJobDTOS = zhilianService.collectJobs(config); + allJobDTOS.addAll(searchJobDTOS); + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.IN_PROGRESS, allJobDTOS.size(), "已采集 " + allJobDTOS.size() + " 个岗位"); + + // 保存到数据库 + int savedCount = 0; + if (!allJobDTOS.isEmpty()) { + try { + savedCount = jobService.saveJobs(allJobDTOS, RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN.name()); + log.info("成功保存 {} 个岗位到数据库", savedCount); + } catch (Exception e) { + log.error("保存岗位到数据库失败", e); + // 即使数据库保存失败,也不影响采集结果的返回 + } + } + + CollectResult result = new CollectResult(); + result.setJobCount(allJobDTOS.size()); + result.setJobs(allJobDTOS); + String message = String.format("成功采集到 %d 个岗位,保存到数据库 %d 个", allJobDTOS.size(), savedCount); + result.setMessage(message); + result.setTimestamp(new Date()); + + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.SUCCESS, allJobDTOS.size(), message); + + log.info("智联招聘岗位采集操作完成,采集到 {} 个岗位,保存到数据库 {} 个", allJobDTOS.size(), savedCount); + return result; + + } catch (Exception e) { + log.error("智联招聘岗位采集操作执行失败", e); + publishTaskUpdate(TaskStage.COLLECT, TaskStatus.FAILURE, 0, "采集异常: " + e.getMessage()); + + CollectResult result = new CollectResult(); + result.setJobCount(0); + result.setJobs(new ArrayList<>()); + result.setMessage("采集异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + /** + * 3. 过滤操作 + * + * @param config 配置信息 + * @return 过滤结果 + */ + public FilterResult filterJobs(ConfigDTO config) { + publishTaskUpdate(TaskStage.FILTER, TaskStatus.STARTED, 0, "开始过滤"); + try { + log.info("开始执行智联招聘岗位过滤操作"); + + RecruitmentService zhilianService = serviceFactory.getService(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN); + + // 直接从数据库查询智联招聘平台的所有职位实体 + List allJobEntities = jobService.findAllJobEntitiesByPlatform( + RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN.getPlatformCode()); + if (allJobEntities == null || allJobEntities.isEmpty()) { + throw new IllegalArgumentException("数据库中未找到职位数据或职位数据为空"); + } + + // 执行过滤逻辑,获取过滤原因 + List filteredJobDTOS = new ArrayList<>(); + List filteredJobIds = new ArrayList<>(); + List filterReasons = new ArrayList<>(); + + List jobDTOS = new ArrayList<>(); + for (JobEntity entity : allJobEntities) { + JobDTO job = jobService.convertToDTO(entity); + jobDTOS.add(job); + } + List filterJobs = zhilianService.filterJobs(jobDTOS, config); + filterJobs.forEach(job -> { + String filterReason = job.getFilterReason(); + if (filterReason == null) { + // 通过过滤 + filteredJobDTOS.add(job); + } else { + // 被过滤,记录原因 + filteredJobIds.add(job.getEncryptJobId()); + filterReasons.add(filterReason); + } + }); + + // 批量更新被过滤的职位状态 + if (!filteredJobIds.isEmpty()) { + // 按过滤原因分组更新 + Map> reasonGroups = new HashMap<>(); + for (int i = 0; i < filteredJobIds.size(); i++) { + String reason = filterReasons.get(i); + String encryptJobId = filteredJobIds.get(i); + reasonGroups.computeIfAbsent(reason, k -> new ArrayList<>()).add(encryptJobId); + } + + for (Map.Entry> entry : reasonGroups.entrySet()) { + jobService.updateJobStatus(entry.getValue(), JobStatusEnum.FILTERED.getCode(), entry.getKey()); + } + } + + FilterResult result = new FilterResult(); + result.setOriginalCount(allJobEntities.size()); + result.setFilteredCount(filteredJobDTOS.size()); + result.setJobs(filteredJobDTOS); + String message = String.format("原始岗位 %d 个,过滤后剩余 %d 个,已过滤 %d 个", + allJobEntities.size(), filteredJobDTOS.size(), filteredJobIds.size()); + result.setMessage(message); + result.setTimestamp(new Date()); + + publishTaskUpdate(TaskStage.FILTER, TaskStatus.SUCCESS, filteredJobDTOS.size(), message); + + log.info("智联招聘岗位过滤操作完成,原始 {} 个,过滤后 {} 个,已过滤 {} 个", + allJobEntities.size(), filteredJobDTOS.size(), filteredJobIds.size()); + return result; + + } catch (Exception e) { + log.error("智联招聘岗位过滤操作执行失败", e); + publishTaskUpdate(TaskStage.FILTER, TaskStatus.FAILURE, 0, "过滤异常: " + e.getMessage()); + + FilterResult result = new FilterResult(); + result.setOriginalCount(0); + result.setFilteredCount(0); + result.setJobs(new ArrayList<>()); + result.setMessage("过滤异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + /** + * 4. 投递操作 + * + * @param config 配置信息 + * @param enableActualDelivery 是否启用实际投递 + * @return 投递结果 + */ + public DeliveryResult deliverJobs(ConfigDTO config, boolean enableActualDelivery) { + publishTaskUpdate(TaskStage.DELIVER, TaskStatus.STARTED, 0, "开始投递"); + try { + log.info("开始执行智联招聘岗位投递操作,实际投递: {}", enableActualDelivery); + + // 从数据库获取待处理状态的智联招聘平台岗位记录 + List jobEntities = jobRepository.findByStatusAndPlatform( + JobStatusEnum.PENDING.getCode(), + RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN.getPlatformCode()); + if (jobEntities == null || jobEntities.isEmpty()) { + throw new IllegalArgumentException("未找到可投递的智联招聘岗位记录,数据库中没有待处理状态的智联招聘岗位"); + } + + // 转换为JobDTO + List filteredJobDTOS = jobEntities.stream() + .map(jobService::convertToDTO) + .collect(Collectors.toList()); + + int deliveredCount = 0; + + if (enableActualDelivery) { + // 获取智联招聘服务 + RecruitmentService zhilianService = serviceFactory.getService(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN); + + // 执行实际投递 + deliveredCount = zhilianService.deliverJobs(filteredJobDTOS, config); + + // 保存数据 + zhilianService.saveData(dataPath); + + log.info("实际投递完成,成功投递 {} 个岗位", deliveredCount); + } else { + // 仅模拟投递 + deliveredCount = filteredJobDTOS.size(); + log.info("模拟投递完成,可投递岗位 {} 个", deliveredCount); + } + + DeliveryResult result = new DeliveryResult(); + result.setTotalCount(filteredJobDTOS.size()); + result.setDeliveredCount(deliveredCount); + result.setActualDelivery(enableActualDelivery); + String message = String.format("%s完成,处理 %d 个岗位", + enableActualDelivery ? "实际投递" : "模拟投递", deliveredCount); + result.setMessage(message); + result.setTimestamp(new Date()); + + // 显示岗位详情 + if (filteredJobDTOS.size() <= 10) { + result.setJobDetails(buildJobDetails(filteredJobDTOS)); + } + + publishTaskUpdate(TaskStage.DELIVER, TaskStatus.SUCCESS, deliveredCount, message); + + log.info("智联招聘岗位投递操作完成,处理 {} 个岗位", deliveredCount); + return result; + + } catch (Exception e) { + log.error("智联招聘岗位投递操作执行失败", e); + publishTaskUpdate(TaskStage.DELIVER, TaskStatus.FAILURE, 0, "投递异常: " + e.getMessage()); + + DeliveryResult result = new DeliveryResult(); + result.setTotalCount(0); + result.setDeliveredCount(0); + result.setActualDelivery(enableActualDelivery); + result.setMessage("投递异常: " + e.getMessage()); + result.setTimestamp(new Date()); + return result; + } + } + + private void publishTaskUpdate(TaskStage stage, TaskStatus status, Integer count, String message) { + TaskUpdatePayload payload = TaskUpdatePayload.builder() + .platform(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN) + .stage(stage) + .status(status) + .count(count) + .message(message) + .build(); + eventPublisher.publishEvent(new TaskUpdateEvent(this, payload)); + } + + private List buildJobDetails(List jobDTOS) { + return jobDTOS.stream() + .map(job -> String.format("%s - %s | %s | %s", + job.getCompanyName(), + job.getJobName(), + job.getSalary() != null ? job.getSalary() : "薪资未知", + job.getJobArea() != null ? job.getJobArea() : "地区未知")) + .collect(Collectors.toList()); + } + + private void initializeDataFiles() throws IOException { + // 初始化工作目录为用户home目录下的getjobs目录 + String userHome = System.getProperty("user.home"); + dataPath = userHome + File.separator + "getjobs"; + + log.info("初始化工作目录: {}", dataPath); + + // 检查getjobs目录是否存在,不存在则创建 + File getJobsDir = new File(dataPath); + if (!getJobsDir.exists()) { + boolean created = getJobsDir.mkdirs(); + if (created) { + log.info("成功创建getjobs目录: {}", dataPath); + } else { + log.error("创建getjobs目录失败: {}", dataPath); + throw new IOException("无法创建getjobs目录: " + dataPath); + } + } else { + log.info("getjobs目录已存在: {}", dataPath); + } + + // 检查并创建data子目录 + File dataDir = new File(dataPath, "data"); + if (!dataDir.exists()) { + boolean created = dataDir.mkdirs(); + if (created) { + log.info("成功创建data子目录: {}", dataDir.getAbsolutePath()); + } else { + log.error("创建data子目录失败: {}", dataDir.getAbsolutePath()); + throw new IOException("无法创建data子目录: " + dataDir.getAbsolutePath()); + } + } else { + log.info("data子目录已存在: {}", dataDir.getAbsolutePath()); + } + + // 更新dataPath为实际的data目录路径 + dataPath = dataDir.getAbsolutePath(); + log.info("数据文件目录设置为: {}", dataPath); + } + + public static class LoginResult { + private String taskId; + private boolean success; + private String message; + private Date timestamp; + + // getters and setters + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + } + + public static class CollectResult { + private String taskId; + private int jobCount; + private List jobDTOS; + private String message; + private Date timestamp; + + // getters and setters + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public int getJobCount() { + return jobCount; + } + + public void setJobCount(int jobCount) { + this.jobCount = jobCount; + } + + public List getJobs() { + return jobDTOS; + } + + public void setJobs(List jobDTOS) { + this.jobDTOS = jobDTOS; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + } + + public static class FilterResult { + private String taskId; + private int originalCount; + private int filteredCount; + private List jobDTOS; + private String message; + private Date timestamp; + + // getters and setters + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public int getOriginalCount() { + return originalCount; + } + + public void setOriginalCount(int originalCount) { + this.originalCount = originalCount; + } + + public int getFilteredCount() { + return filteredCount; + } + + public void setFilteredCount(int filteredCount) { + this.filteredCount = filteredCount; + } + + public List getJobs() { + return jobDTOS; + } + + public void setJobs(List jobDTOS) { + this.jobDTOS = jobDTOS; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + } + + public static class DeliveryResult { + private String taskId; + private int totalCount; + private int deliveredCount; + private boolean actualDelivery; + private String message; + private Date timestamp; + private List jobDetails; + + // getters and setters + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public int getTotalCount() { + return totalCount; + } + + public void setTotalCount(int totalCount) { + this.totalCount = totalCount; + } + + public int getDeliveredCount() { + return deliveredCount; + } + + public void setDeliveredCount(int deliveredCount) { + this.deliveredCount = deliveredCount; + } + + public boolean isActualDelivery() { + return actualDelivery; + } + + public void setActualDelivery(boolean actualDelivery) { + this.actualDelivery = actualDelivery; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public List getJobDetails() { + return jobDetails; + } + + public void setJobDetails(List jobDetails) { + this.jobDetails = jobDetails; + } + } +} diff --git a/src/main/java/getjobs/modules/zhilian/service/impl/ZhiLianRecruitmentServiceImpl.java b/src/main/java/getjobs/modules/zhilian/service/impl/ZhiLianRecruitmentServiceImpl.java new file mode 100644 index 00000000..3eeefedb --- /dev/null +++ b/src/main/java/getjobs/modules/zhilian/service/impl/ZhiLianRecruitmentServiceImpl.java @@ -0,0 +1,338 @@ +package getjobs.modules.zhilian.service.impl; + +import com.microsoft.playwright.Page; +import getjobs.common.dto.ConfigDTO; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.common.service.PlaywrightService; +import getjobs.modules.boss.dto.JobDTO; +import getjobs.modules.zhilian.service.ZhiLianElementLocators; +import getjobs.service.RecruitmentService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import jakarta.annotation.PostConstruct; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.Scanner; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 智联招聘服务实现类 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ZhiLianRecruitmentServiceImpl implements RecruitmentService { + + private static final String HOME_URL = RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN.getHomeUrl(); + private static final String SEARCH_JOB_URL = "https://www.zhaopin.com/sou?"; + // https://www.zhaopin.com/sou?el=4&we=0510&et=2&sl=15001,25000&jl=763&kw=java + + private final PlaywrightService playwrightService; + private Page page; + + @PostConstruct + public void init() { + this.page = playwrightService.getPage(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN); + } + + @Override + public RecruitmentPlatformEnum getPlatform() { + return RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN; + } + + @Override + public boolean login(ConfigDTO config) { + log.info("开始智联招聘登录检查"); + + try { + // 使用Playwright打开网站 + page.navigate(HOME_URL); + + // 检查是否需要登录 + if (ZhiLianElementLocators.isLoginRequired(page)) { + log.info("需要登录,开始登录流程"); + return performLogin(); + } else { + log.info("智联招聘已登录"); + return true; + } + } catch (Exception e) { + log.error("智联招聘登录失败", e); + return false; + } + } + + @Override + public List collectJobs(ConfigDTO config) { + log.info("开始智联招聘岗位采集"); + List allJobDTOS = new ArrayList<>(); + + try { + // 按城市和关键词搜索岗位 + for (String cityCode : config.getCityCodeCodes()) { + for (String keyword : config.getKeywordsList()) { + List jobDTOS = collectJobsByCity(cityCode, keyword, config); + allJobDTOS.addAll(jobDTOS); + } + } + + log.info("智联招聘岗位采集完成,共采集{}个岗位", allJobDTOS.size()); + return allJobDTOS; + } catch (Exception e) { + log.error("智联招聘岗位采集失败", e); + return allJobDTOS; + } + } + + @Override + public List collectRecommendJobs(ConfigDTO config) { + return List.of(); + } + + @Override + public List filterJobs(List jobDTOS, ConfigDTO config) { + return List.of(); + } + + @Override + public int deliverJobs(List jobDTOS, ConfigDTO config) { + log.info("开始执行智联招聘岗位投递操作,待投递岗位数量: {}", jobDTOS.size()); + AtomicInteger successCount = new AtomicInteger(0); + + // 在新标签页中打开岗位详情 + try (Page jobPage = page.context().newPage()) { + jobPage.setDefaultTimeout(30000); // 为新页面设置默认超时 + + for (JobDTO jobDTO : jobDTOS) { + try { + log.info("正在投递岗位: {}", jobDTO.getJobName()); + jobPage.navigate(jobDTO.getHref()); + jobPage.waitForLoadState(); // 等待页面加载 + + + // 投递完成会打开新页签,需要关闭新页签 + Page popup = jobPage.waitForPopup(() -> { + // 执行投递 + if (ZhiLianElementLocators.clickSummaryApplyButton(jobPage)) { + log.info("岗位投递成功: {}", jobDTO.getJobName()); + successCount.getAndIncrement(); + } else { + log.warn("岗位投递失败或已投递: {}", jobDTO.getJobName()); + } + }); + + if (popup != null) { + popup.waitForLoadState(); // 可选:等加载稳定 + // TODO: 可根据 URL/标题做一次校验,确认是"投递成功"页 + popup.close(); // 关闭新页签 + } + + + // 添加3-5秒随机延迟,避免投递过快 + try { + int randomSeconds = new Random().nextInt(3) + 3; // 3-5秒 + TimeUnit.SECONDS.sleep(randomSeconds); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + } catch (Exception e) { + log.error("投递岗位 {} 时发生异常: {}", jobDTO.getJobName(), e.getMessage()); + } + } + } catch (Exception e) { + log.error("智联招聘岗位投递过程中发生严重错误", e); + } + + log.info("智联招聘岗位投递完成,成功投递 {} 个岗位", successCount.get()); + return successCount.get(); + } + + @Override + public boolean isDeliveryLimitReached() { + return false; + } + + @Override + public void saveData(String dataPath) { + log.info("开始保存智联招聘数据到路径: {}", dataPath); + try { + // TODO: 实现智联招聘数据保存逻辑 + // 这里需要保存智联招聘相关的数据,如登录状态、采集的岗位信息等 + + log.info("智联招聘数据保存功能待实现"); + + } catch (Exception e) { + log.error("智联招聘数据保存失败", e); + } + } + + // ==================== 私有辅助方法 ==================== + + /** + * 按城市采集岗位 + */ + private List collectJobsByCity(String cityCode, String keyword, ConfigDTO config) { + String searchUrl = buildSearchUrl(cityCode, keyword, config); + log.info("开始采集,城市: {},关键词: {},URL: {}", cityCode, keyword, searchUrl); + + List jobDTOS = new ArrayList<>(); + + try { + page.navigate(searchUrl); + // 等待页面加载 + page.waitForLoadState(); + int pageNumber = 1; + while (ZhiLianElementLocators.clickPageNumber(page, pageNumber)){ + pageNumber++; + } + } catch (Exception e) { + log.error("采集城市: {}, 关键词: {} 的职位失败", cityCode, keyword, e); + } + + log.info("城市: {}, 关键词: {} 的职位采集完成,共{}个职位", cityCode, keyword, jobDTOS.size()); + return jobDTOS; + } + + /** + * 构建搜索URL + * 基于URL: https://www.zhaopin.com/sou?el=4&we=0510&et=2&sl=15001,25000&jl=763&kw=java + * + * @param cityCode 城市代码 + * @param keyword 关键词 + * @param config 配置信息 + * @return 完整的搜索URL + */ + private String buildSearchUrl(String cityCode, String keyword, ConfigDTO config) { + StringBuilder url = new StringBuilder(SEARCH_JOB_URL); + + try { + // 必需参数 + // 关键词需要URL编码 + String encodedKeyword = URLEncoder.encode(keyword, StandardCharsets.UTF_8); + url.append("kw=").append(encodedKeyword); + + // 城市参数 jl + if (cityCode != null && !cityCode.trim().isEmpty()) { + url.append("&jl=").append(cityCode); + } + + // 学历要求 el (Education Level) + // el=4 表示本科, 1=不限, 2=高中, 3=大专, 4=本科, 5=硕士, 6=博士 + if (config.getDegree() != null && !config.getDegree().trim().isEmpty()) { + url.append("&el=").append(config.getDegree()); + } + + // 职位类型 et (Employment Type) + // et=2 表示全职, 1=全职, 2=兼职, 3=实习 + if (config.getJobType() != null && !config.getJobType().trim().isEmpty()) { + url.append("&et=").append(config.getJobType()); + } + + // 公司性质 ct (Company Type) + if (config.getCompanyType() != null && !config.getCompanyType().trim().isEmpty()) { + url.append("&ct=").append(config.getCompanyType()); + } + + // 公司规模 cs (Company Size) + if (config.getScale() != null && !config.getScale().trim().isEmpty()) { + url.append("&cs=").append(config.getScale()); + } + + // 薪资范围 sl (Salary Level) + // 格式: sl=15001,25000 + if (config.getSalary() != null && !config.getSalary().trim().isEmpty()) { + url.append("&sl=").append(config.getSalary()); + } + + // 工作经验 we (Work Experience) + // we=0510 表示5-10年 + if (config.getExperience() != null && !config.getExperience().trim().isEmpty()) { + url.append("&we=").append(config.getExperience()); + } + + // 行业类型 in + if (config.getIndustry() != null && !config.getIndustry().trim().isEmpty()) { + url.append("&in=").append(URLEncoder.encode(config.getIndustry(), StandardCharsets.UTF_8)); + } + + } catch (Exception e) { + log.error("构建搜索URL失败", e); + // 返回基础URL + return SEARCH_JOB_URL + "kw=" + keyword; + } + + String finalUrl = url.toString(); + log.debug("构建的搜索URL: {}", finalUrl); + return finalUrl; + } + + /** + * 执行登录操作 + */ + private boolean performLogin() { + try { + // 直接首页登录即可,不需要单独使用登录页 + page.navigate(HOME_URL); + TimeUnit.SECONDS.sleep(3); + + log.info("等待用户手动登录..."); + log.info("请在浏览器中完成登录操作"); + + boolean loginSuccess = false; + + + while (!loginSuccess) { + try { + // 检查登录状态 + if (ZhiLianElementLocators.isUserLoggedIn(page)) { + loginSuccess = true; + log.info("登录成功"); + } + } catch (Exception e) { + log.debug("登录状态检查异常: {}", e.getMessage()); + } + + TimeUnit.SECONDS.sleep(2); + } + + return true; + + } catch (Exception e) { + log.error("登录过程中发生错误", e); + return false; + } + } + + /** + * 等待用户输入或超时 + */ + private boolean waitForUserInputOrTimeout(Scanner scanner) { + long end = System.currentTimeMillis() + 2000; + while (System.currentTimeMillis() < end) { + try { + if (System.in.available() > 0) { + scanner.nextLine(); + return true; + } + TimeUnit.SECONDS.sleep(1); + } catch (IOException e) { + // 忽略异常 + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + return false; + } +} diff --git a/src/main/java/getjobs/modules/zhilian/service/playwright/ZhiLianApiMonitorService.java b/src/main/java/getjobs/modules/zhilian/service/playwright/ZhiLianApiMonitorService.java new file mode 100644 index 00000000..c2a43981 --- /dev/null +++ b/src/main/java/getjobs/modules/zhilian/service/playwright/ZhiLianApiMonitorService.java @@ -0,0 +1,313 @@ +package getjobs.modules.zhilian.service.playwright; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.playwright.*; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.common.service.PlaywrightService; +import getjobs.modules.zhilian.dto.ZhiLianApiResponse; +import getjobs.repository.entity.JobEntity; +import getjobs.repository.JobRepository; +import getjobs.utils.ZhiLianDataConverter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.PostConstruct; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 智联招聘接口监控服务 + * 负责监听和记录智联招聘相关的API请求和响应 + * + * @author getjobs + * @since v2.1.1 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ZhiLianApiMonitorService { + + private final JobRepository jobRepository; + private final ZhiLianDataConverter dataConverter; + private final PlaywrightService playwrightService; + private final ObjectMapper objectMapper = new ObjectMapper(); + + // 全局接口调用频率限制:记录最后一次调用时间 + private static volatile long lastCallTime = 0L; + + /** + * 初始化监控服务 + * 设置智联招聘搜索接口监听器 + */ + @PostConstruct + public void init() { + setupZhiLianApiMonitor(); + } + + /** + * 设置智联招聘接口监听器 + * 监听所有相关的API响应并处理职位数据 + */ + public void setupZhiLianApiMonitor() { + try { + Page page = playwrightService.getPage(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN); + + // 监听智联招聘职位搜索接口的响应 + setupResponseMonitor(page); + + log.info("智联招聘API监控服务初始化完成"); + } catch (Exception e) { + log.error("智联招聘API监控服务初始化失败: {}", e.getMessage(), e); + } + } + + /** + * 设置响应监控 + */ + private void setupResponseMonitor(Page page) { + page.onResponse(response -> { + String url = response.url(); + + // 监听智联招聘职位搜索接口响应 + if (url.contains("/c/i/search/positions")) { + handleZhiLianSearchResponse(response); + } + // 可以在此添加其他智联招聘相关接口的监听 + }); + } + + /** + * 处理智联招聘职位搜索响应 + */ + private void handleZhiLianSearchResponse(Response response) { + log.info("=== 智联招聘职位搜索响应拦截 ==="); + log.info("响应状态: {}", response.status()); + log.info("响应URL: {}", response.url()); + log.info("响应头: {}", response.headers()); + + try { + String body = response.text(); + log.info("响应体长度: {} 字符", body.length()); + log.debug("响应体内容: {}", body); + + // 尝试解析JSON并美化输出 + formatJsonResponse(body); + + // 解析并保存职位数据 + parseAndSaveZhiLianData(body, "智联招聘职位搜索"); + + } catch (PlaywrightException e) { + log.error("读取智联招聘响应体失败: {}", e.getMessage()); + } + log.info("=========================="); + } + + /** + * 格式化JSON响应 + */ + private void formatJsonResponse(String body) { + try { + Object jsonObject = objectMapper.readValue(body, Object.class); + String formattedJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject); + log.debug("格式化JSON响应: {}", formattedJson); + } catch (Exception e) { + log.debug("响应体不是有效的JSON格式: {}", e.getMessage()); + } + } + + /** + * 解析并保存智联招聘职位数据 + * + * @param body 响应体JSON字符串 + * @param source 数据来源描述 + */ + @Transactional + public void parseAndSaveZhiLianData(String body, String source) { + try { + // 解析智联招聘API响应 + ZhiLianApiResponse response = objectMapper.readValue(body, ZhiLianApiResponse.class); + + if (!Integer.valueOf(200).equals(response.getCode())) { + log.warn("智联招聘API响应错误,code: {}", response.getCode()); + return; + } + + if (response.getData() == null || + response.getData().getList() == null) { + log.warn("智联招聘API响应中没有职位数据"); + return; + } + + List jobItems = response.getData().getList(); + log.info("从{}获取到 {} 个职位数据", source, jobItems.size()); + + // 过滤有效的职位数据 + List validJobItems = jobItems.stream() + .filter(dataConverter::isValidJobData) + .toList(); + + if (validJobItems.isEmpty()) { + log.warn("没有有效的职位数据,来源: {}", source); + return; + } + + log.info("过滤后有效职位数据: {} 个", validJobItems.size()); + + // 转换为JobEntity并保存 + List jobEntities = validJobItems.stream() + .map(dataConverter::convertToJobEntity) + .filter(Objects::nonNull) + .toList(); + + if (!jobEntities.isEmpty()) { + // 检查是否已存在相同的职位(基于encryptJobId) + List newJobs = jobEntities.stream() + .filter(entity -> !isJobExists(entity.getEncryptJobId())) + .collect(Collectors.toList()); + + if (!newJobs.isEmpty()) { + jobRepository.saveAll(newJobs); + log.info("成功保存 {} 个新职位到数据库,来源: {}", newJobs.size(), source); + + // 打印保存的职位信息 + newJobs.forEach(job -> log.info("保存职位: {} - {} - {}", + job.getJobTitle(), job.getCompanyName(), job.getSalaryDesc())); + } else { + log.info("所有职位都已存在,跳过保存,来源: {}", source); + } + } else { + log.warn("没有有效的职位数据可以保存,来源: {}", source); + } + + } catch (Exception e) { + log.error("解析并保存智联招聘职位数据失败,来源: {}", source, e); + } + } + + /** + * 检查职位是否已存在 + * + * @param encryptJobId 职位ID + * @return 是否存在 + */ + private boolean isJobExists(String encryptJobId) { + if (encryptJobId == null || encryptJobId.trim().isEmpty()) { + return false; + } + + try { + return jobRepository.existsByEncryptJobId(encryptJobId); + } catch (Exception e) { + log.warn("检查职位是否存在时发生错误: {}", e.getMessage()); + return false; + } + } + + /** + * 手动启动监控(如果需要重新启动) + */ + public void startMonitoring() { + log.info("手动启动智联招聘API监控服务"); + setupZhiLianApiMonitor(); + } + + /** + * 检查监控服务状态 + */ + public boolean isMonitoringActive() { + try { + BrowserContext ctx = playwrightService.getContext(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN); + return ctx != null; + } catch (Exception e) { + log.warn("检查智联招聘监控服务状态失败: {}", e.getMessage()); + return false; + } + } + + /** + * 刷新智联招聘页面来获取新的session + */ + public boolean refreshSession() { + try { + log.info("开始刷新智联招聘session"); + + // 获取Playwright上下文 + BrowserContext context = playwrightService.getContext(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN); + if (context == null) { + log.error("无法获取Playwright上下文,session刷新失败"); + return false; + } + + // 创建新页面访问zhaopin.com + Page refreshPage = context.newPage(); + + try { + // 设置用户代理,模拟真实浏览器访问 + refreshPage.setExtraHTTPHeaders(java.util.Map.of( + "User-Agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Accept", + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8", + "Accept-Encoding", "gzip, deflate, br", + "Cache-Control", "no-cache", + "Pragma", "no-cache")); + + // 访问智联招聘主页 + log.info("正在访问 https://www.zhaopin.com/"); + refreshPage.navigate("https://www.zhaopin.com/"); + + // 等待页面加载完成 + refreshPage.waitForLoadState(); + refreshPage.waitForTimeout(3000); // 等待3秒确保页面完全加载 + + // 模拟用户行为:滚动页面 + refreshPage.evaluate("window.scrollTo(0, document.body.scrollHeight / 2)"); + refreshPage.waitForTimeout(1000); + + // 模拟点击页面(不实际点击任何元素,只是模拟用户活动) + refreshPage.hover("body"); + refreshPage.waitForTimeout(1000); + + log.info("成功访问智联招聘,session刷新完成"); + return true; + + } finally { + // 关闭页面 + if (refreshPage != null) { + refreshPage.close(); + } + } + + } catch (Exception e) { + log.error("访问智联招聘刷新session时发生异常: {}", e.getMessage(), e); + return false; + } + } + + /** + * 获取监控统计信息 + */ + public java.util.Map getMonitoringStats() { + java.util.Map stats = new java.util.HashMap<>(); + + try { + // 统计数据库中智联招聘职位数量 + long totalJobs = jobRepository.countByPlatform("zhilian"); + stats.put("totalJobs", totalJobs); + stats.put("platform", "zhilian"); + stats.put("isActive", isMonitoringActive()); + stats.put("lastCallTime", lastCallTime); + + } catch (Exception e) { + log.error("获取监控统计信息失败: {}", e.getMessage(), e); + stats.put("error", e.getMessage()); + } + + return stats; + } +} diff --git a/src/main/java/getjobs/modules/zhilian/web/ZhilianTaskController.java b/src/main/java/getjobs/modules/zhilian/web/ZhilianTaskController.java new file mode 100644 index 00000000..4ace9207 --- /dev/null +++ b/src/main/java/getjobs/modules/zhilian/web/ZhilianTaskController.java @@ -0,0 +1,228 @@ +package getjobs.modules.zhilian.web; + +import getjobs.common.dto.ConfigDTO; +import getjobs.modules.zhilian.service.ZhilianTaskService; +import getjobs.modules.zhilian.service.ZhilianTaskService.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * 智联招聘任务控制器 - 提供4个独立的API接口 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Slf4j +@RestController +@RequestMapping("/api/zhilian/task") +public class ZhilianTaskController { + + private final ZhilianTaskService zhilianTaskService; + + public ZhilianTaskController(ZhilianTaskService zhilianTaskService) { + this.zhilianTaskService = zhilianTaskService; + } + + /** + * 1. 登录接口 + * POST /api/zhilian/task/login + * + * @param config 智联招聘配置信息 + * @return 登录结果 + */ + @PostMapping("/login") + public ResponseEntity> login(@RequestBody ConfigDTO config) { + log.info("接收到智联招聘登录请求"); + + try { + LoginResult result = zhilianTaskService.login(config); + + Map response = new HashMap<>(); + response.put("success", result.isSuccess()); + response.put("taskId", result.getTaskId()); + response.put("message", result.getMessage()); + response.put("timestamp", result.getTimestamp()); + + if (result.isSuccess()) { + log.info("智联招聘登录成功,任务ID: {}", result.getTaskId()); + return ResponseEntity.ok(response); + } else { + log.warn("智联招聘登录失败,任务ID: {}, 原因: {}", result.getTaskId(), result.getMessage()); + return ResponseEntity.badRequest().body(response); + } + + } catch (Exception e) { + log.error("智联招聘登录接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "登录接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 2. 采集岗位接口 + * POST /api/zhilian/task/collect + * + * @param config 智联招聘配置信息 + * @return 采集结果 + */ + @PostMapping("/collect") + public ResponseEntity> collectJobs(@RequestBody ConfigDTO config) { + log.info("接收到智联招聘岗位采集请求"); + + try { + CollectResult result = zhilianTaskService.collectJobs(config); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("taskId", result.getTaskId()); + response.put("message", result.getMessage()); + response.put("jobCount", result.getJobCount()); + response.put("timestamp", result.getTimestamp()); + + // 如果需要返回详细岗位信息,可以添加以下行 + // response.put("jobs", result.getJobs()); + + log.info("智联招聘岗位采集完成,任务ID: {}, 采集到 {} 个岗位", result.getTaskId(), result.getJobCount()); + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("智联招聘岗位采集接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "采集接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 3. 过滤岗位接口 + * POST /api/zhilian/task/filter + * + * @param request 过滤请求 + * @return 过滤结果 + */ + @PostMapping("/filter") + public ResponseEntity> filterJobs(@RequestBody FilterRequest request) { + log.info("接收到智联招聘岗位过滤请求"); + + try { + FilterResult result = zhilianTaskService.filterJobs(request.getConfig()); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", result.getMessage()); + response.put("originalCount", result.getOriginalCount()); + response.put("filteredCount", result.getFilteredCount()); + response.put("timestamp", result.getTimestamp()); + + // 如果需要返回详细岗位信息,可以添加以下行 + // response.put("jobs", result.getJobs()); + + log.info("智联招聘岗位过滤完成,原始 {} 个,过滤后 {} 个", + result.getOriginalCount(), result.getFilteredCount()); + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("智联招聘岗位过滤接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "过滤接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + /** + * 4. 投递岗位接口 + * POST /api/zhilian/task/deliver + * + * @param request 投递请求(包含配置和是否实际投递) + * @return 投递结果 + */ + @PostMapping("/deliver") + public ResponseEntity> deliverJobs(@RequestBody DeliveryRequest request) { + log.info("接收到智联招聘岗位投递请求,实际投递: {}", + request.isEnableActualDelivery()); + + try { + DeliveryResult result = zhilianTaskService.deliverJobs( + request.getConfig(), + request.isEnableActualDelivery()); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("taskId", result.getTaskId()); + response.put("message", result.getMessage()); + response.put("totalCount", result.getTotalCount()); + response.put("deliveredCount", result.getDeliveredCount()); + response.put("actualDelivery", result.isActualDelivery()); + response.put("timestamp", result.getTimestamp()); + + // 如果有岗位详情,添加到响应中 + if (result.getJobDetails() != null && !result.getJobDetails().isEmpty()) { + response.put("jobDetails", result.getJobDetails()); + } + + log.info("智联招聘岗位投递完成,任务ID: {}, 处理 {} 个岗位", + result.getTaskId(), result.getDeliveredCount()); + return ResponseEntity.ok(response); + + } catch (Exception e) { + log.error("智联招聘岗位投递接口执行异常", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "投递接口执行异常: " + e.getMessage()); + return ResponseEntity.internalServerError().body(response); + } + } + + + // 请求DTO类 + public static class FilterRequest { + private String collectTaskId; + private ConfigDTO config; + + public String getCollectTaskId() { + return collectTaskId; + } + + public void setCollectTaskId(String collectTaskId) { + this.collectTaskId = collectTaskId; + } + + public ConfigDTO getConfig() { + return config; + } + + public void setConfig(ConfigDTO config) { + this.config = config; + } + } + + public static class DeliveryRequest { + private ConfigDTO config; + private boolean enableActualDelivery = false; // 默认为模拟投递 + + public ConfigDTO getConfig() { + return config; + } + + public void setConfig(ConfigDTO config) { + this.config = config; + } + + public boolean isEnableActualDelivery() { + return enableActualDelivery; + } + + public void setEnableActualDelivery(boolean enableActualDelivery) { + this.enableActualDelivery = enableActualDelivery; + } + } +} diff --git a/src/main/java/getjobs/repository/AiPromptTemplateRepository.java b/src/main/java/getjobs/repository/AiPromptTemplateRepository.java new file mode 100644 index 00000000..b8696325 --- /dev/null +++ b/src/main/java/getjobs/repository/AiPromptTemplateRepository.java @@ -0,0 +1,10 @@ +package getjobs.repository; + +import getjobs.repository.entity.AiPromptTemplate; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface AiPromptTemplateRepository extends JpaRepository { + Optional findByName(String name); +} diff --git a/src/main/java/getjobs/repository/ConfigRepository.java b/src/main/java/getjobs/repository/ConfigRepository.java new file mode 100644 index 00000000..84b25461 --- /dev/null +++ b/src/main/java/getjobs/repository/ConfigRepository.java @@ -0,0 +1,30 @@ +package getjobs.repository; + +import getjobs.repository.entity.ConfigEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface ConfigRepository extends JpaRepository { + + /** + * 获取第一个配置记录(通常只有一个配置) + */ + @Query("SELECT c FROM ConfigEntity c ORDER BY c.id ASC LIMIT 1") + Optional findFirstConfig(); + + /** + * 获取默认配置(按创建时间排序的第一个) + */ + default Optional getDefaultConfig() { + return findFirstConfig(); + } + + /** + * 按平台类型获取配置 + */ + Optional findFirstByPlatformTypeOrderByIdAsc(String platformType); +} diff --git a/src/main/java/getjobs/repository/JobRepository.java b/src/main/java/getjobs/repository/JobRepository.java new file mode 100644 index 00000000..50a30704 --- /dev/null +++ b/src/main/java/getjobs/repository/JobRepository.java @@ -0,0 +1,117 @@ +package getjobs.repository; + +import getjobs.repository.entity.JobEntity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.List; + +public interface JobRepository extends JpaRepository { + + @Query("SELECT j FROM JobEntity j " + + "WHERE (:platform IS NULL OR LOWER(j.platform) = LOWER(:platform)) " + + "AND ( :keyword IS NULL " + + " OR LOWER(j.jobTitle) LIKE LOWER(CONCAT('%', :keyword, '%')) " + + " OR LOWER(j.companyName) LIKE LOWER(CONCAT('%', :keyword, '%')) " + + " OR LOWER(j.hrName) LIKE LOWER(CONCAT('%', :keyword, '%')) )") + Page search(@Param("platform") String platform, + @Param("keyword") String keyword, + Pageable pageable); + + /** + * 根据加密职位ID检查职位是否存在 + * + * @param encryptJobId 加密职位ID + * @return 是否存在 + */ + boolean existsByEncryptJobId(String encryptJobId); + + /** + * 根据平台统计职位数量 + * + * @param platform 平台名称 + * @return 职位数量 + */ + long countByPlatform(String platform); + + /** + * 根据安全ID查找职位 + * + * @param securityId 安全ID + * @return 职位实体 + */ + JobEntity findBySecurityId(String securityId); + + /** + * 根据加密职位ID查找职位 + * + * @param encryptJobId 加密职位ID + * @return 职位实体 + */ + JobEntity findByEncryptJobId(String encryptJobId); + + /** + * 查找状态不等于指定值的职位 + * + * @param status 状态值 + * @return 职位实体列表 + */ + List findByStatusNot(Integer status); + + /** + * 根据状态查找职位 + * + * @param status 状态值 + * @return 职位实体列表 + */ + List findByStatus(Integer status); + + List findAllByEncryptJobIdIn(List encryptJobIds); + + /** + * 统计指定时间范围内新增的岗位数量 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 岗位数量 + */ + long countByCreatedAtBetween(LocalDateTime startTime, LocalDateTime endTime); + + /** + * 统计指定时间范围内、指定平台的新增岗位数量 + * + * @param platform 平台名称 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 岗位数量 + */ + long countByPlatformAndCreatedAtBetween(String platform, LocalDateTime startTime, LocalDateTime endTime); + + /** + * 根据状态和平台查找职位 + * + * @param status 状态值 + * @param platform 平台名称 + * @return 职位实体列表 + */ + List findByStatusAndPlatform(Integer status, String platform); + + /** + * 根据平台查找职位 + * + * @param platform 平台名称 + * @return 职位实体列表 + */ + List findByPlatform(String platform); + + /** + * 根据平台删除职位 + * + * @param platform 平台名称 + */ + void deleteByPlatform(String platform); +} diff --git a/src/main/java/getjobs/repository/UserProfileRepository.java b/src/main/java/getjobs/repository/UserProfileRepository.java new file mode 100644 index 00000000..5b253f69 --- /dev/null +++ b/src/main/java/getjobs/repository/UserProfileRepository.java @@ -0,0 +1,13 @@ +package getjobs.repository; + +import getjobs.repository.entity.UserProfile; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * 用户求职信息Repository + * + * @author getjobs + */ +public interface UserProfileRepository extends JpaRepository { +} + diff --git a/src/main/java/getjobs/repository/entity/AiPromptTemplate.java b/src/main/java/getjobs/repository/entity/AiPromptTemplate.java new file mode 100644 index 00000000..fa8eb4c7 --- /dev/null +++ b/src/main/java/getjobs/repository/entity/AiPromptTemplate.java @@ -0,0 +1,29 @@ +package getjobs.repository.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; + +import java.util.Map; + +@Getter +@Setter +@Entity +@Table(name = "ai_prompt_template") +public class AiPromptTemplate extends BaseEntity { + + @Column(unique = true, nullable = false) + private String name; + + @Column(columnDefinition = "TEXT") + private String template; + + @Column(columnDefinition = "TEXT") + @Convert(converter = JsonMapStringConverter.class) + private Map placeholders; + + private String description; +} diff --git a/src/main/java/getjobs/repository/entity/BaseEntity.java b/src/main/java/getjobs/repository/entity/BaseEntity.java new file mode 100644 index 00000000..73a8fece --- /dev/null +++ b/src/main/java/getjobs/repository/entity/BaseEntity.java @@ -0,0 +1,56 @@ +package getjobs.repository.entity; + +import jakarta.persistence.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 基础实体类,包含公共字段 + * + * @author getjobs + * @since v2.0.1 + */ +@Data +@MappedSuperclass +public abstract class BaseEntity { + + /** + * 主键ID + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 创建时间 + */ + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt = LocalDateTime.now(); + + /** + * 更新时间 + */ + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + /** + * 是否删除 (逻辑删除) + */ + @Column(name = "is_deleted", nullable = false) + private Boolean isDeleted = false; + + /** + * 备注 + */ + @Column(name = "remark", length = 500) + private String remark; + + /** + * 更新时自动设置更新时间 + */ + @PreUpdate + public void preUpdate() { + this.updatedAt = LocalDateTime.now(); + } +} diff --git a/src/main/java/getjobs/repository/entity/ConfigEntity.java b/src/main/java/getjobs/repository/entity/ConfigEntity.java new file mode 100644 index 00000000..837cd53e --- /dev/null +++ b/src/main/java/getjobs/repository/entity/ConfigEntity.java @@ -0,0 +1,114 @@ +package getjobs.repository.entity; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; +import java.util.Map; + +/** + * 配置实体(支持多平台,平台类型由 platformType 字段指定) + */ +@Data +@Entity +@Table(name = "config") +@EqualsAndHashCode(callSuper = true) +public class ConfigEntity extends BaseEntity { + + @Column(name = "say_hi", columnDefinition = "TEXT") + private String sayHi; + + @Convert(converter = JsonListStringConverter.class) + @Column(name = "keywords", columnDefinition = "TEXT") + private List keywords; + + @Convert(converter = JsonListStringConverter.class) + @Column(name = "city_code", columnDefinition = "TEXT") + private List cityCode; + + @Convert(converter = JsonMapStringConverter.class) + @Column(name = "custom_city_code", columnDefinition = "TEXT") + private Map customCityCode; + + @Convert(converter = JsonListStringConverter.class) + @Column(name = "industry", columnDefinition = "TEXT") + private List industry; + + @Convert(converter = JsonListStringConverter.class) + @Column(name = "experience", columnDefinition = "TEXT") + private List experience; + + @Column(name = "job_type", length = 50) + private String jobType; + + @Column(name = "salary", length = 50) + private String salary; + + @Column(name = "publish_time", length = 50) + private String publishTime; + + @Column(name = "expected_position", length = 200) + private String expectedPosition; + + @Convert(converter = JsonListStringConverter.class) + @Column(name = "degree", columnDefinition = "TEXT") + private List degree; + + @Convert(converter = JsonListStringConverter.class) + @Column(name = "scale", columnDefinition = "TEXT") + private List scale; + + @Convert(converter = JsonListStringConverter.class) + @Column(name = "stage", columnDefinition = "TEXT") + private List stage; + + @Column(name = "enable_ai_job_match_detection") + private Boolean enableAIJobMatchDetection; + + @Column(name = "enable_ai_greeting") + private Boolean enableAIGreeting; + + @Column(name = "filter_dead_hr") + private Boolean filterDeadHR; + + @Column(name = "send_img_resume") + private Boolean sendImgResume; + + @Column(name = "resume_image_path") + private String resumeImagePath; + + @Column(name = "resume_content", columnDefinition = "TEXT") + private String resumeContent; + + @Convert(converter = JsonListIntegerConverter.class) + @Column(name = "expected_salary", columnDefinition = "TEXT") + private List expectedSalary; + + @Column(name = "wait_time", length = 50) + private String waitTime; + + @Column(name = "platform_type", length = 20) + private String platformType; + + @Convert(converter = JsonListStringConverter.class) + @Column(name = "dead_status", columnDefinition = "TEXT") + private List deadStatus; + + @Column(name = "key_filter") + private Boolean keyFilter; + + @Column(name = "recommend_jobs") + private Boolean recommendJobs; + + @Column(name = "check_state_owned") + private Boolean checkStateOwned; + + @Column(name = "cookie_data", columnDefinition = "TEXT") + private String cookieData; + + @Convert(converter = JsonListStringConverter.class) + @Column(name = "company_nature", columnDefinition = "TEXT") + private List companyNature; + +} diff --git a/src/main/java/getjobs/repository/entity/JobEntity.java b/src/main/java/getjobs/repository/entity/JobEntity.java new file mode 100644 index 00000000..0534f55c --- /dev/null +++ b/src/main/java/getjobs/repository/entity/JobEntity.java @@ -0,0 +1,702 @@ +package getjobs.repository.entity; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 职位信息实体类 + * + * @author getjobs + * @since v2.0.1 + */ +@Data +@Entity +@Table(name = "job_info") +@EqualsAndHashCode(callSuper = true) +public class JobEntity extends BaseEntity { + + // ==================== 基础职位信息 ==================== + + /** + * 职位标题 + */ + @Column(name = "job_title", nullable = false, length = 200) + private String jobTitle; + + /** + * 薪资描述 + */ + @Column(name = "salary_desc", length = 100) + private String salaryDesc; + + /** + * 工作经验要求 + */ + @Column(name = "job_experience", length = 50) + private String jobExperience; + + /** + * 学历要求 + */ + @Column(name = "job_degree", length = 50) + private String jobDegree; + + /** + * 职位标签(JSON数组格式) + */ + @Column(name = "job_labels", columnDefinition = "TEXT") + private String jobLabels; + + /** + * 技能要求(JSON数组格式) + */ + @Column(name = "skills", columnDefinition = "TEXT") + private String skills; + + /** + * 职位描述 + */ + @Column(name = "job_description", columnDefinition = "TEXT") + private String jobDescription; + + /** + * 职位要求 + */ + @Column(name = "job_requirements", columnDefinition = "TEXT") + private String jobRequirements; + + /** + * 职位链接 + */ + @Column(name = "job_url", length = 500) + private String jobUrl; + + // ==================== 公司信息 ==================== + + /** + * 公司名称 + */ + @Column(name = "company_name", nullable = false, length = 200) + private String companyName; + + /** + * 公司行业 + */ + @Column(name = "company_industry", length = 100) + private String companyIndustry; + + /** + * 公司融资阶段 + */ + @Column(name = "company_stage", length = 50) + private String companyStage; + + /** + * 公司规模 + */ + @Column(name = "company_scale", length = 50) + private String companyScale; + + /** + * 公司Logo + */ + @Column(name = "company_logo", length = 500) + private String companyLogo; + + /** + * 公司标签(列表以分隔符拼接) + */ + @Column(name = "company_tag", length = 200) + private String companyTag; + + // ==================== 工作地点信息 ==================== + + /** + * 工作城市 + */ + @Column(name = "work_city", length = 50) + private String workCity; + + /** + * 工作区域 + */ + @Column(name = "work_area", length = 100) + private String workArea; + + /** + * 商圈 + */ + @Column(name = "business_district", length = 100) + private String businessDistrict; + + /** + * 经度 + */ + @Column(name = "longitude", precision = 10, scale = 7) + private BigDecimal longitude; + + /** + * 纬度 + */ + @Column(name = "latitude", precision = 10, scale = 7) + private BigDecimal latitude; + + // ==================== HR信息 ==================== + + /** + * HR姓名 + */ + @Column(name = "hr_name", length = 100) + private String hrName; + + /** + * HR职位 + */ + @Column(name = "hr_title", length = 100) + private String hrTitle; + + /** + * HR头像 + */ + @Column(name = "hr_avatar", length = 500) + private String hrAvatar; + + /** + * HR是否在线 + */ + @Column(name = "hr_online") + private Boolean hrOnline; + + /** + * HR认证等级 + */ + @Column(name = "hr_cert_level") + private Integer hrCertLevel; + + /** + * HR 活跃时间/状态原文 + */ + @Column(name = "hr_active_time", length = 50) + private String hrActiveTime; + + // ==================== 系统信息 ==================== + + /** + * 数据来源平台 + */ + @Column(name = "platform", length = 50) + private String platform; + + /** + * 加密职位ID + */ + @Column(name = "encrypt_job_id", length = 100) + private String encryptJobId; + + /** + * 加密HR ID + */ + @Column(name = "encrypt_hr_id", length = 100) + private String encryptHrId; + + /** + * 加密公司ID + */ + @Column(name = "encrypt_company_id", length = 100) + private String encryptCompanyId; + + /** + * 安全ID + */ + @Column(name = "security_id", length = 200) + private String securityId; + + /** + * 职位状态 (0: 待处理, 1: 已处理, 2: 已忽略, 3: 已过滤) + */ + @Column(name = "status", nullable = false) + private Integer status = 0; + + /** + * 过滤原因说明 + */ + @Column(name = "filter_reason", length = 500) + private String filterReason; + + /** + * 是否收藏 + */ + @Column(name = "is_favorite", nullable = false) + private Boolean isFavorite = false; + + /** + * 是否最优职位 + */ + @Column(name = "is_optimal") + private Boolean isOptimal; + + /** + * 是否代理职位 + */ + @Column(name = "is_proxy_job") + private Boolean isProxyJob; + + /** + * 代理类型 + */ + @Column(name = "proxy_type") + private Integer proxyType; + + /** + * 是否金猎手 + */ + @Column(name = "is_gold_hunter") + private Boolean isGoldHunter; + + /** + * 是否联系过 + */ + @Column(name = "is_contacted") + private Boolean isContacted; + + /** + * 是否屏蔽 + */ + @Column(name = "is_shielded") + private Boolean isShielded; + + /** + * 职位有效性状态 + */ + @Column(name = "job_valid_status") + private Integer jobValidStatus; + + /** + * 福利列表(JSON数组格式) + */ + @Column(name = "welfare_list", columnDefinition = "TEXT") + private String welfareList; + + /** + * 图标标志列表(JSON数组格式) + */ + @Column(name = "icon_flag_list", columnDefinition = "TEXT") + private String iconFlagList; + + /** + * 名称前图标(JSON数组格式) + */ + @Column(name = "before_name_icons", columnDefinition = "TEXT") + private String beforeNameIcons; + + /** + * 名称后图标(JSON数组格式) + */ + @Column(name = "after_name_icons", columnDefinition = "TEXT") + private String afterNameIcons; + + /** + * 图标文字 + */ + @Column(name = "icon_word", length = 100) + private String iconWord; + + /** + * 最少工作月数描述 + */ + @Column(name = "least_month_desc", length = 50) + private String leastMonthDesc; + + /** + * 每周工作天数描述 + */ + @Column(name = "days_per_week_desc", length = 50) + private String daysPerWeekDesc; + + /** + * 是否显示顶部位置 + */ + @Column(name = "show_top_position") + private Boolean showTopPosition; + + /** + * 是否海外职位 + */ + @Column(name = "is_outland") + private Boolean isOutland; + + /** + * 匿名状态 + */ + @Column(name = "anonymous_status") + private Integer anonymousStatus; + + /** + * 项目ID + */ + @Column(name = "item_id") + private Integer itemId; + + /** + * 期望ID + */ + @Column(name = "expect_id") + private Long expectId; + + /** + * 城市编码 + */ + @Column(name = "city_code") + private Long cityCode; + + /** + * 行业编码 + */ + @Column(name = "industry_code") + private Long industryCode; + + /** + * 职位类型 + */ + @Column(name = "job_type") + private Integer jobType; + + /** + * 是否ATS直接投递 + */ + @Column(name = "ats_direct_post") + private Boolean atsDirectPost; + + /** + * 搜索ID + */ + @Column(name = "search_id", length = 100) + private String searchId; + + // ==================== 职位明细信息 (jobInfo) ==================== + + /** + * 加密职位ID (来自jobInfo.encryptId) + */ + @Column(name = "encrypt_job_detail_id", length = 100) + private String encryptJobDetailId; + + /** + * 加密用户ID (来自jobInfo.encryptUserId) + */ + @Column(name = "encrypt_job_user_id", length = 100) + private String encryptJobUserId; + + /** + * 职位有效性状态 (来自jobInfo.invalidStatus) + */ + @Column(name = "job_invalid_status") + private Boolean jobInvalidStatus; + + /** + * 职位类型编码 (来自jobInfo.position) + */ + @Column(name = "job_position_code") + private Long jobPositionCode; + + /** + * 职位类型名称 (来自jobInfo.positionName) + */ + @Column(name = "job_position_name", length = 100) + private String jobPositionName; + + /** + * 城市编码 (来自jobInfo.location) + */ + @Column(name = "job_location_code") + private Long jobLocationCode; + + /** + * 城市名称 (来自jobInfo.locationName) + */ + @Column(name = "job_location_name", length = 100) + private String jobLocationName; + + /** + * 城市URL (来自jobInfo.locationUrl) + */ + @Column(name = "job_location_url", length = 200) + private String jobLocationUrl; + + /** + * 工作经验描述 (来自jobInfo.experienceName) + */ + @Column(name = "job_experience_name", length = 100) + private String jobExperienceName; + + /** + * 学历描述 (来自jobInfo.degreeName) + */ + @Column(name = "job_degree_name", length = 100) + private String jobDegreeName; + + /** + * 职位类型 (来自jobInfo.jobType) + */ + @Column(name = "job_detail_type") + private Integer jobDetailType; + + /** + * 是否代理职位 (来自jobInfo.proxyJob) + */ + @Column(name = "job_proxy_job") + private Integer jobProxyJob; + + /** + * 代理类型 (来自jobInfo.proxyType) + */ + @Column(name = "job_proxy_type") + private Integer jobProxyType; + + /** + * 薪资类型描述 (来自jobInfo.payTypeDesc) + */ + @Column(name = "job_pay_type_desc", length = 100) + private String jobPayTypeDesc; + + /** + * 职位描述 (来自jobInfo.postDescription) + */ + @Column(name = "job_post_description", columnDefinition = "TEXT") + private String jobPostDescription; + + /** + * 加密地址ID (来自jobInfo.encryptAddressId) + */ + @Column(name = "encrypt_address_id", length = 100) + private String encryptAddressId; + + /** + * 详细地址 (来自jobInfo.address) + */ + @Column(name = "job_address", length = 500) + private String jobAddress; + + /** + * 经度 (来自jobInfo.longitude) + */ + @Column(name = "job_longitude", precision = 10, scale = 7) + private BigDecimal jobLongitude; + + /** + * 纬度 (来自jobInfo.latitude) + */ + @Column(name = "job_latitude", precision = 10, scale = 7) + private BigDecimal jobLatitude; + + /** + * 静态地图URL (来自jobInfo.staticMapUrl) + */ + @Column(name = "job_static_map_url", length = 500) + private String jobStaticMapUrl; + + /** + * PC静态地图URL (来自jobInfo.pcStaticMapUrl) + */ + @Column(name = "job_pc_static_map_url", length = 500) + private String jobPcStaticMapUrl; + + /** + * 百度静态地图URL (来自jobInfo.baiduStaticMapUrl) + */ + @Column(name = "job_baidu_static_map_url", length = 500) + private String jobBaiduStaticMapUrl; + + /** + * 百度PC静态地图URL (来自jobInfo.baiduPcStaticMapUrl) + */ + @Column(name = "job_baidu_pc_static_map_url", length = 500) + private String jobBaiduPcStaticMapUrl; + + /** + * 显示技能列表 (来自jobInfo.showSkills, JSON数组格式) + */ + @Column(name = "job_show_skills", columnDefinition = "TEXT") + private String jobShowSkills; + + /** + * 匿名状态 (来自jobInfo.anonymous) + */ + @Column(name = "job_anonymous") + private Integer jobAnonymous; + + /** + * 职位状态描述 (来自jobInfo.jobStatusDesc) + */ + @Column(name = "job_status_desc", length = 100) + private String jobStatusDesc; + + // ==================== Boss信息 (bossInfo) ==================== + + /** + * Boss姓名 (来自bossInfo.name) + */ + @Column(name = "boss_name", length = 100) + private String bossName; + + /** + * Boss职位 (来自bossInfo.title) + */ + @Column(name = "boss_title", length = 100) + private String bossTitle; + + /** + * Boss小头像 (来自bossInfo.tiny) + */ + @Column(name = "boss_tiny", length = 500) + private String bossTiny; + + /** + * Boss大头像 (来自bossInfo.large) + */ + @Column(name = "boss_large", length = 500) + private String bossLarge; + + /** + * Boss活跃时间描述 (来自bossInfo.activeTimeDesc) + */ + @Column(name = "boss_active_time_desc", length = 100) + private String bossActiveTimeDesc; + + /** + * Boss是否在线 (来自bossInfo.bossOnline) + */ + @Column(name = "boss_online") + private Boolean bossOnline; + + /** + * Boss品牌名称 (来自bossInfo.brandName) + */ + @Column(name = "boss_brand_name", length = 200) + private String bossBrandName; + + /** + * Boss来源 (来自bossInfo.bossSource) + */ + @Column(name = "boss_source") + private Integer bossSource; + + /** + * Boss是否认证 (来自bossInfo.certificated) + */ + @Column(name = "boss_certificated") + private Boolean bossCertificated; + + /** + * Boss标签图标URL (来自bossInfo.tagIconUrl) + */ + @Column(name = "boss_tag_icon_url", length = 500) + private String bossTagIconUrl; + + /** + * Boss头像贴纸URL (来自bossInfo.avatarStickerUrl) + */ + @Column(name = "boss_avatar_sticker_url", length = 500) + private String bossAvatarStickerUrl; + + // ==================== 品牌公司信息 (brandComInfo) ==================== + + /** + * 加密品牌ID (来自brandComInfo.encryptBrandId) + */ + @Column(name = "encrypt_brand_id", length = 100) + private String encryptBrandId; + + /** + * 品牌名称 (来自brandComInfo.brandName) + */ + @Column(name = "brand_name", length = 200) + private String brandName; + + /** + * 品牌Logo (来自brandComInfo.logo) + */ + @Column(name = "brand_logo", length = 500) + private String brandLogo; + + /** + * 品牌阶段 (来自brandComInfo.stage) + */ + @Column(name = "brand_stage") + private Long brandStage; + + /** + * 品牌阶段名称 (来自brandComInfo.stageName) + */ + @Column(name = "brand_stage_name", length = 100) + private String brandStageName; + + /** + * 品牌规模 (来自brandComInfo.scale) + */ + @Column(name = "brand_scale") + private Long brandScale; + + /** + * 品牌规模名称 (来自brandComInfo.scaleName) + */ + @Column(name = "brand_scale_name", length = 100) + private String brandScaleName; + + /** + * 品牌行业 (来自brandComInfo.industry) + */ + @Column(name = "brand_industry") + private Long brandIndustry; + + /** + * 品牌行业名称 (来自brandComInfo.industryName) + */ + @Column(name = "brand_industry_name", length = 100) + private String brandIndustryName; + + /** + * 品牌介绍 (来自brandComInfo.introduce) + */ + @Column(name = "brand_introduce", columnDefinition = "TEXT") + private String brandIntroduce; + + /** + * 品牌标签 (来自brandComInfo.labels, JSON数组格式) + */ + @Column(name = "brand_labels", columnDefinition = "TEXT") + private String brandLabels; + + /** + * 品牌活跃时间 (来自brandComInfo.activeTime) + */ + @Column(name = "brand_active_time") + private Long brandActiveTime; + + /** + * 是否显示品牌信息 (来自brandComInfo.visibleBrandInfo) + */ + @Column(name = "visible_brand_info") + private Boolean visibleBrandInfo; + + /** + * 是否关注品牌 (来自brandComInfo.focusBrand) + */ + @Column(name = "focus_brand") + private Boolean focusBrand; + + /** + * 客户品牌名称 (来自brandComInfo.customerBrandName) + */ + @Column(name = "customer_brand_name", length = 200) + private String customerBrandName; + + /** + * 客户品牌阶段名称 (来自brandComInfo.customerBrandStageName) + */ + @Column(name = "customer_brand_stage_name", length = 100) + private String customerBrandStageName; +} diff --git a/src/main/java/getjobs/repository/entity/JsonListIntegerConverter.java b/src/main/java/getjobs/repository/entity/JsonListIntegerConverter.java new file mode 100644 index 00000000..5aa98f32 --- /dev/null +++ b/src/main/java/getjobs/repository/entity/JsonListIntegerConverter.java @@ -0,0 +1,38 @@ +package getjobs.repository.entity; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +import java.util.Collections; +import java.util.List; + +@Converter +public class JsonListIntegerConverter implements AttributeConverter, String> { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Override + public String convertToDatabaseColumn(List attribute) { + try { + if (attribute == null) + return null; + return MAPPER.writeValueAsString(attribute); + } catch (Exception e) { + return null; + } + } + + @Override + public List convertToEntityAttribute(String dbData) { + try { + if (dbData == null || dbData.isEmpty()) + return Collections.emptyList(); + return MAPPER.readValue(dbData, new TypeReference>() { + }); + } catch (Exception e) { + return Collections.emptyList(); + } + } +} diff --git a/src/main/java/getjobs/repository/entity/JsonListStringConverter.java b/src/main/java/getjobs/repository/entity/JsonListStringConverter.java new file mode 100644 index 00000000..5c394cad --- /dev/null +++ b/src/main/java/getjobs/repository/entity/JsonListStringConverter.java @@ -0,0 +1,38 @@ +package getjobs.repository.entity; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +import java.util.Collections; +import java.util.List; + +@Converter +public class JsonListStringConverter implements AttributeConverter, String> { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Override + public String convertToDatabaseColumn(List attribute) { + try { + if (attribute == null) + return null; + return MAPPER.writeValueAsString(attribute); + } catch (Exception e) { + return null; + } + } + + @Override + public List convertToEntityAttribute(String dbData) { + try { + if (dbData == null || dbData.isEmpty()) + return Collections.emptyList(); + return MAPPER.readValue(dbData, new TypeReference>() { + }); + } catch (Exception e) { + return Collections.emptyList(); + } + } +} diff --git a/src/main/java/getjobs/repository/entity/JsonMapStringConverter.java b/src/main/java/getjobs/repository/entity/JsonMapStringConverter.java new file mode 100644 index 00000000..ff8e67ec --- /dev/null +++ b/src/main/java/getjobs/repository/entity/JsonMapStringConverter.java @@ -0,0 +1,38 @@ +package getjobs.repository.entity; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +import java.util.Collections; +import java.util.Map; + +@Converter +public class JsonMapStringConverter implements AttributeConverter, String> { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Override + public String convertToDatabaseColumn(Map attribute) { + try { + if (attribute == null) + return null; + return MAPPER.writeValueAsString(attribute); + } catch (Exception e) { + return null; + } + } + + @Override + public Map convertToEntityAttribute(String dbData) { + try { + if (dbData == null || dbData.isEmpty()) + return Collections.emptyMap(); + return MAPPER.readValue(dbData, new TypeReference>() { + }); + } catch (Exception e) { + return Collections.emptyMap(); + } + } +} diff --git a/src/main/java/getjobs/repository/entity/UserProfile.java b/src/main/java/getjobs/repository/entity/UserProfile.java new file mode 100644 index 00000000..ace2cd63 --- /dev/null +++ b/src/main/java/getjobs/repository/entity/UserProfile.java @@ -0,0 +1,102 @@ +package getjobs.repository.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; +import java.util.Map; + +/** + * 用户求职信息实体 + * + * @author getjobs + */ +@Getter +@Setter +@Entity +@Table(name = "user_profile") +public class UserProfile extends BaseEntity { + + /** + * 职位角色 + */ + @Column(nullable = false, length = 100) + private String role; + + /** + * 工作年限 + */ + @Column(nullable = false) + private Integer years; + + /** + * 领域列表(以JSON格式存储) + */ + @Column(columnDefinition = "TEXT") + @Convert(converter = JsonListStringConverter.class) + private List domains; + + /** + * 核心技术栈列表(以JSON格式存储) + */ + @Column(name = "core_stack", columnDefinition = "TEXT") + @Convert(converter = JsonListStringConverter.class) + private List coreStack; + + /** + * 规模指标(以JSON格式存储,包含qps_peak、sla等) + */ + @Column(columnDefinition = "TEXT") + @Convert(converter = JsonMapStringConverter.class) + private Map scale; + + /** + * 成就列表(以JSON格式存储) + */ + @Column(columnDefinition = "TEXT") + @Convert(converter = JsonListStringConverter.class) + private List achievements; + + /** + * 优势列表(以JSON格式存储) + */ + @Column(columnDefinition = "TEXT") + @Convert(converter = JsonListStringConverter.class) + private List strengths; + + /** + * 改进项列表(以JSON格式存储) + */ + @Column(columnDefinition = "TEXT") + @Convert(converter = JsonListStringConverter.class) + private List improvements; + + /** + * 到岗时间 + */ + @Column(length = 50) + private String availability; + + /** + * 链接信息(以JSON格式存储,包含github、portfolio等) + */ + @Column(columnDefinition = "TEXT") + @Convert(converter = JsonMapStringConverter.class) + private Map links; + + /** + * 岗位黑名单(以JSON格式存储) + */ + @Column(name = "position_blacklist", columnDefinition = "TEXT") + @Convert(converter = JsonListStringConverter.class) + private List positionBlacklist; + + /** + * 公司黑名单(以JSON格式存储) + */ + @Column(name = "company_blacklist", columnDefinition = "TEXT") + @Convert(converter = JsonListStringConverter.class) + private List companyBlacklist; +} + diff --git a/src/main/java/getjobs/service/ConfigService.java b/src/main/java/getjobs/service/ConfigService.java new file mode 100644 index 00000000..4e2afb8e --- /dev/null +++ b/src/main/java/getjobs/service/ConfigService.java @@ -0,0 +1,15 @@ +package getjobs.service; + +import getjobs.repository.entity.ConfigEntity; + +public interface ConfigService { + + ConfigEntity save(ConfigEntity entity); + + ConfigEntity load(); + + /** + * 按平台类型加载配置 + */ + ConfigEntity loadByPlatformType(String platformType); +} diff --git a/src/main/java/getjobs/service/DataBackupService.java b/src/main/java/getjobs/service/DataBackupService.java new file mode 100644 index 00000000..14a97406 --- /dev/null +++ b/src/main/java/getjobs/service/DataBackupService.java @@ -0,0 +1,45 @@ +package getjobs.service; + +import java.util.Map; + +/** + * 数据备份服务接口 + * 提供H2内存数据库的备份和恢复功能 + * + * @author getjobs + * @since v2.0.1 + */ +public interface DataBackupService { + + /** + * 导出当前H2内存数据库中的所有数据到用户目录 + * + * @return 备份文件路径 + * @throws Exception 导出过程中的异常 + */ + String exportData() throws Exception; + + /** + * 从用户目录导入数据到H2内存数据库 + * + * @return 是否成功恢复数据 + * @throws Exception 导入过程中的异常 + */ + boolean importData() throws Exception; + + /** + * 获取备份文件信息 + * + * @return 备份文件信息Map + * @throws Exception 获取信息过程中的异常 + */ + Map getBackupInfo() throws Exception; + + /** + * 清理备份文件 + * + * @return 是否成功删除备份文件 + * @throws Exception 清理过程中的异常 + */ + boolean cleanBackup() throws Exception; +} diff --git a/src/main/java/getjobs/service/JobFilterService.java b/src/main/java/getjobs/service/JobFilterService.java new file mode 100644 index 00000000..7c69561b --- /dev/null +++ b/src/main/java/getjobs/service/JobFilterService.java @@ -0,0 +1,315 @@ +package getjobs.service; + +import getjobs.common.dto.ConfigDTO; +import getjobs.modules.ai.common.enums.AiPlatform; +import getjobs.modules.ai.job.service.JobMatchAiService; +import getjobs.modules.boss.dto.JobDTO; +import getjobs.repository.UserProfileRepository; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class JobFilterService { + + private Set blackRecruiters = new HashSet<>(); + + private final JobMatchAiService jobMatchAiService; + + private final UserProfileRepository userProfileRepository; + + public JobFilterService(JobMatchAiService jobMatchAiService, UserProfileRepository userProfileRepository) { + this.jobMatchAiService = jobMatchAiService; + this.userProfileRepository = userProfileRepository; + } + + public List filterJobs(List jobDTOS, ConfigDTO config) { + return filterJobs(jobDTOS,config,true); + } + + public List filterJobs(List jobDTOS, ConfigDTO config, boolean salaryExpected) { + log.info("开始Boss直聘岗位过滤,原始岗位数量: {}", jobDTOS.size()); + List filteredJobDTOS = jobDTOS.stream() + .map(job -> { + String filterReason = getFilterReason(job, config, salaryExpected); + job.setFilterReason(filterReason); + return job; + }) + .collect(Collectors.toList()); + + log.info("Boss直聘岗位过滤完成,过滤后岗位数量: {}", filteredJobDTOS.size()); + return filteredJobDTOS; + } + + + /** + * 获取职位过滤原因 + * + * @param job 职位信息 + * @param config 配置信息 + * @return 过滤原因,null表示通过过滤 + */ + private String getFilterReason(JobDTO job, ConfigDTO config) { + return getFilterReason(job, config, true); + } + + + /** + * 获取职位过滤原因 + * + * @param job 职位信息 + * @param config 配置信息 + * @param salaryExpected 是否检查薪资 + * + * @return 过滤原因,null表示通过过滤 + */ + private String getFilterReason(JobDTO job, ConfigDTO config, boolean salaryExpected) { + // 检查岗位黑名单 + if (isJobInBlacklist(job)) { + return "岗位名称包含黑名单关键词"; + } + + // 检查公司黑名单 + if (isCompanyInBlacklist(job)) { + return "公司名称包含黑名单关键词"; + } + + // 检查招聘者黑名单 + if (isRecruiterInBlacklist(job)) { + return "招聘者包含黑名单关键词"; + } + + if(salaryExpected){ + // 检查薪资 + if (!isSalaryExpected(job, config)) { + return "薪资不符合预期范围"; + } + } + + // 检测HR活跃状态 + if (config.getDeadStatus() != null && !config.getDeadStatus().isEmpty()) { + if (config.getDeadStatus().contains(job.getHrActiveTime())) { + return "HR活跃状态已被过滤-" + job.getHrActiveTime(); + } + } + + // AI岗位匹配度过滤 + if (config.getEnableAIJobMatchDetection()) { + String myJd = userProfileRepository.findAll().stream() + .findFirst() + .map(profile -> profile.getRole()) + .orElse(null); + String jobDescription = job.getJobDescription(); + if(ObjectUtils.isEmpty(jobDescription)){ + return "AI岗位匹配失败,职位要求为空"; + } + if(ObjectUtils.isNotEmpty(myJd)){ + if (!jobMatchAiService.isMatch(myJd, job.getJobDescription(), AiPlatform.DEEPSEEK)) { + return "AI岗位匹配度低于阈值"; + } + }else { + return "AI岗位匹配失败,请补充用户职位角色信息"; + } + } + + return null; // 通过所有过滤条件 + } + + + /** + * 检查岗位是否在黑名单中 + */ + private boolean isJobInBlacklist(JobDTO jobDTO) { + List positionBlacklist = userProfileRepository.findAll().stream() + .findFirst() + .map(profile -> profile.getPositionBlacklist()) + .orElse(Collections.emptyList()); + + if (positionBlacklist == null || positionBlacklist.isEmpty()) { + return false; + } + + return positionBlacklist.stream() + .anyMatch(blackJob -> jobDTO.getJobName().contains(blackJob)); + } + + /** + * 检查公司是否在黑名单中 + */ + private boolean isCompanyInBlacklist(JobDTO jobDTO) { + List companyBlacklist = userProfileRepository.findAll().stream() + .findFirst() + .map(profile -> profile.getCompanyBlacklist()) + .orElse(Collections.emptyList()); + + if (companyBlacklist == null || companyBlacklist.isEmpty()) { + return false; + } + + return companyBlacklist.stream() + .anyMatch(blackCompany -> jobDTO.getCompanyName().contains(blackCompany)); + } + + /** + * 检查招聘者是否在黑名单中 + */ + private boolean isRecruiterInBlacklist(JobDTO jobDTO) { + return blackRecruiters.stream() + .anyMatch(blackRecruiter -> jobDTO.getRecruiter() != null + && jobDTO.getRecruiter().contains(blackRecruiter)); + } + + /** + * 检查薪资是否符合预期 + */ + private boolean isSalaryExpected(JobDTO jobDTO, ConfigDTO config) { + if (jobDTO.getSalary() == null || jobDTO.getSalary().isEmpty()) { + return true; // 没有薪资信息时默认通过 + } + + try { + List expectedSalary = config.getExpectedSalary(); + if (expectedSalary == null || expectedSalary.isEmpty()) { + return true; // 没有期望薪资时默认通过 + } + + return !isSalaryNotExpected(jobDTO.getSalary(), expectedSalary); + } catch (Exception e) { + log.debug("薪资验证失败: {}", e.getMessage()); + return true; // 验证失败时默认通过 + } + } + + /** + * 检查薪资是否不符合预期 + */ + private boolean isSalaryNotExpected(String salary, List expectedSalary) { + try { + // 清理薪资文本 + salary = removeYearBonusText(salary); + + if (!isSalaryInExpectedFormat(salary)) { + return true; + } + + salary = cleanSalaryText(salary); + String jobType = detectJobType(salary); + salary = removeDayUnitIfNeeded(salary); + + Integer[] jobSalaryRange = parseSalaryRange(salary); + return isSalaryOutOfRange(jobSalaryRange, + getMinimumSalary(expectedSalary), + getMaximumSalary(expectedSalary), + jobType); + } catch (Exception e) { + log.error("薪资解析出错", e); + return true; + } + } + + /** + * 去掉年终奖信息 + */ + private String removeYearBonusText(String salary) { + if (salary.contains("薪")) { + return salary.replaceAll("·\\d+薪", ""); + } + return salary; + } + + /** + * 判断薪资格式是否符合预期 + */ + private boolean isSalaryInExpectedFormat(String salaryText) { + return salaryText.contains("K") || salaryText.contains("k") || salaryText.contains("元/天"); + } + + /** + * 清理薪资文本 + */ + private String cleanSalaryText(String salaryText) { + salaryText = salaryText.replace("K", "").replace("k", ""); + int dotIndex = salaryText.indexOf('·'); + if (dotIndex != -1) { + salaryText = salaryText.substring(0, dotIndex); + } + return salaryText; + } + + /** + * 判断是否是按天计薪 + */ + private String detectJobType(String salary) { + if (salary.contains("元/天")) { + return "day"; + } + return "month"; + } + + /** + * 如果是日薪,则去除"元/天" + */ + private String removeDayUnitIfNeeded(String salary) { + if (salary.contains("元/天")) { + return salary.replaceAll("元/天", ""); + } + return salary; + } + + /** + * 解析薪资范围 + */ + private Integer[] parseSalaryRange(String salaryText) { + try { + return Arrays.stream(salaryText.split("-")) + .map(s -> s.replaceAll("[^0-9]", "")) + .map(Integer::parseInt) + .toArray(Integer[]::new); + } catch (Exception e) { + log.debug("薪资解析失败: {}", e.getMessage()); + return null; + } + } + + /** + * 检查薪资是否超出范围 + */ + private boolean isSalaryOutOfRange(Integer[] jobSalary, Integer miniSalary, Integer maxSalary, String jobType) { + if (jobSalary == null) { + return true; + } + if (miniSalary == null) { + return false; + } + + if (Objects.equals("day", jobType)) { + // 期望薪资转为平均每日的工资 + maxSalary = BigDecimal.valueOf(maxSalary).multiply(BigDecimal.valueOf(1000)) + .divide(BigDecimal.valueOf(21.75), 0, RoundingMode.HALF_UP).intValue(); + miniSalary = BigDecimal.valueOf(miniSalary).multiply(BigDecimal.valueOf(1000)) + .divide(BigDecimal.valueOf(21.75), 0, RoundingMode.HALF_UP).intValue(); + } + + // 如果职位薪资下限低于期望的最低薪资,返回不符合 + if (jobSalary[1] < miniSalary) { + return true; + } + // 如果职位薪资上限高于期望的最高薪资,返回不符合 + return maxSalary != null && jobSalary[0] > maxSalary; + } + + private Integer getMinimumSalary(List expectedSalary) { + return expectedSalary != null && !expectedSalary.isEmpty() ? expectedSalary.get(0) : null; + } + + private Integer getMaximumSalary(List expectedSalary) { + return expectedSalary != null && expectedSalary.size() > 1 ? expectedSalary.get(1) : null; + } + +} diff --git a/src/main/java/getjobs/service/JobService.java b/src/main/java/getjobs/service/JobService.java new file mode 100644 index 00000000..dab658ea --- /dev/null +++ b/src/main/java/getjobs/service/JobService.java @@ -0,0 +1,312 @@ +package getjobs.service; + +import getjobs.common.enums.JobStatusEnum; +import getjobs.modules.boss.dto.JobDTO; +import getjobs.repository.entity.JobEntity; +import getjobs.repository.JobRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 职位服务类 + * + * @author getjobs + * @since v2.0.1 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class JobService { + + private final JobRepository jobRepository; + + /** + * 批量保存职位信息到数据库 + * + * @param jobDTOs 职位DTO列表 + * @param platform 数据来源平台 + * @return 保存的职位数量 + */ + @Transactional + public int saveJobs(List jobDTOs, String platform) { + if (jobDTOs == null || jobDTOs.isEmpty()) { + log.warn("没有职位数据需要保存"); + return 0; + } + + try { + // 转换为实体对象 + List jobEntities = jobDTOs.stream() + .map(dto -> convertToEntity(dto, platform)) + .collect(Collectors.toList()); + + // 批量保存 + jobRepository.saveAll(jobEntities); + + log.info("成功保存 {} 个职位到数据库,平台: {}", jobEntities.size(), platform); + return jobEntities.size(); + + } catch (Exception e) { + log.error("保存职位数据到数据库失败", e); + throw new RuntimeException("保存职位数据失败: " + e.getMessage(), e); + } + } + + /** + * 分页搜索职位 + * + * @param platform 平台(可为空) + * @param keyword 关键字(可为空,匹配职位、公司或HR) + * @param page 页码(从0开始) + * @param size 每页大小 + * @return 分页结果 + */ + public Page search(String platform, String keyword, int page, int size) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "id")); + return jobRepository.search(platform, keyword, pageable); + } + + /** + * 查询所有职位实体并转换为JobDTO列表 + * + * @param platform 平台名称(可选,为null时查询所有平台) + * @return JobDTO列表 + */ + public List findAllJobsAsDTO(String platform) { + try { + List jobEntities; + if (platform != null && !platform.trim().isEmpty()) { + jobEntities = jobRepository.findAll().stream() + .filter(job -> platform.equalsIgnoreCase(job.getPlatform())) + .collect(Collectors.toList()); + } else { + jobEntities = jobRepository.findAll(); + } + + return jobEntities.stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + + } catch (Exception e) { + log.error("查询职位数据失败", e); + throw new RuntimeException("查询职位数据失败: " + e.getMessage(), e); + } + } + + /** + * 将JobEntity转换为JobDTO + * + * @param entity JobEntity对象 + * @return JobDTO对象 + */ + public JobDTO convertToDTO(JobEntity entity) { + JobDTO dto = new JobDTO(); + + // 基础职位信息映射 + dto.setHref(entity.getJobUrl()); + dto.setJobName(entity.getJobTitle()); + dto.setJobArea(entity.getWorkCity()); + dto.setJobInfo(entity.getJobDescription()); + dto.setSalary(entity.getSalaryDesc()); + dto.setSalaryDesc(entity.getSalaryDesc()); + dto.setJobExperience(entity.getJobExperience()); + dto.setJobDegree(entity.getJobDegree()); + + // 处理JSON格式的字段 + if (entity.getJobLabels() != null && !entity.getJobLabels().trim().isEmpty()) { + dto.setJobLabels(List.of(entity.getJobLabels().split(","))); + } + if (entity.getSkills() != null && !entity.getSkills().trim().isEmpty()) { + dto.setSkills(List.of(entity.getSkills().split(","))); + } + if (entity.getWelfareList() != null && !entity.getWelfareList().trim().isEmpty()) { + dto.setWelfareList(List.of(entity.getWelfareList().split(","))); + } + + dto.setJobDescription(entity.getJobPostDescription()); + dto.setJobRequirements(entity.getJobRequirements()); + + // 公司信息映射 + dto.setCompanyName(entity.getCompanyName()); + dto.setCompanyIndustry(entity.getCompanyIndustry()); + dto.setCompanyStage(entity.getCompanyStage()); + dto.setCompanyScale(entity.getCompanyScale()); + dto.setCompanyLogo(entity.getCompanyLogo()); + dto.setCompanyTag(entity.getCompanyTag()); + + // 工作地点信息映射 + dto.setWorkCity(entity.getWorkCity()); + dto.setWorkArea(entity.getWorkArea()); + dto.setBusinessDistrict(entity.getBusinessDistrict()); + dto.setLongitude(entity.getLongitude()); + dto.setLatitude(entity.getLatitude()); + + // HR信息映射 + dto.setRecruiter(entity.getHrName()); + dto.setHrName(entity.getHrName()); + dto.setHrTitle(entity.getHrTitle()); + dto.setHrAvatar(entity.getHrAvatar()); + dto.setHrOnline(entity.getHrOnline()); + dto.setHrCertLevel(entity.getHrCertLevel()); + dto.setHrActiveTime(entity.getBossActiveTimeDesc()); + + // 系统信息映射 + dto.setPlatform(entity.getPlatform()); + dto.setEncryptJobId(entity.getEncryptJobId()); + dto.setEncryptHrId(entity.getEncryptHrId()); + dto.setEncryptCompanyId(entity.getEncryptCompanyId()); + dto.setSecurityId(entity.getSecurityId()); + dto.setStatus(entity.getStatus()); + dto.setFilterReason(entity.getFilterReason()); + dto.setIsFavorite(entity.getIsFavorite()); + dto.setIsOptimal(entity.getIsOptimal()); + dto.setIsProxyJob(entity.getIsProxyJob()); + dto.setProxyType(entity.getProxyType()); + dto.setIsGoldHunter(entity.getIsGoldHunter()); + dto.setIsContacted(entity.getIsContacted()); + dto.setIsShielded(entity.getIsShielded()); + dto.setJobValidStatus(entity.getJobValidStatus()); + dto.setIconWord(entity.getIconWord()); + dto.setLeastMonthDesc(entity.getLeastMonthDesc()); + dto.setDaysPerWeekDesc(entity.getDaysPerWeekDesc()); + dto.setShowTopPosition(entity.getShowTopPosition()); + dto.setIsOutland(entity.getIsOutland()); + dto.setAnonymousStatus(entity.getAnonymousStatus()); + dto.setItemId(entity.getItemId()); + dto.setExpectId(entity.getExpectId()); + dto.setCityCode(entity.getCityCode()); + dto.setIndustryCode(entity.getIndustryCode()); + dto.setJobType(entity.getJobType()); + dto.setAtsDirectPost(entity.getAtsDirectPost()); + dto.setSearchId(entity.getSearchId()); + + return dto; + } + + /** + * 将JobDTO转换为JobEntity + * + * @param dto JobDTO对象 + * @param platform 数据来源平台 + * @return JobEntity对象 + */ + private JobEntity convertToEntity(JobDTO dto, String platform) { + JobEntity entity = new JobEntity(); + + // 基本信息映射 + entity.setJobTitle(dto.getJobName()); + entity.setCompanyName(dto.getCompanyName()); + entity.setSalaryDesc(dto.getSalary()); + entity.setWorkCity(dto.getJobArea()); + entity.setJobDescription(dto.getJobInfo()); + entity.setJobUrl(dto.getHref()); + entity.setPlatform(platform); + entity.setHrName(dto.getRecruiter()); + entity.setCompanyTag(dto.getCompanyTag()); + entity.setJobLabels(String.join(",", dto.getJobLabels())); + + // 设置默认值 + entity.setStatus(0); // 待处理 + entity.setIsFavorite(false); // 默认不收藏 + + return entity; + } + + /** + * 重置指定平台下的职位过滤状态:status=0, filterReason=null + * + * @param platform 平台名称 + * @return 重置的职位数量 + */ + @Transactional + public int resetFilterByPlatform(String platform) { + if (platform == null || platform.trim().isEmpty()) { + throw new IllegalArgumentException("platform不能为空"); + } + + List jobs = jobRepository.findByPlatform(platform); + if (jobs.isEmpty()) { + return 0; + } + for (JobEntity job : jobs) { + job.setStatus(JobStatusEnum.PENDING.getCode()); + job.setFilterReason(null); + } + jobRepository.saveAll(jobs); + return jobs.size(); + } + + /** + * 按平台删除所有职位 + * + * @param platform 平台名称 + */ + @Transactional + public void deleteAllByPlatform(String platform) { + if (platform == null || platform.trim().isEmpty()) { + throw new IllegalArgumentException("platform不能为空"); + } + jobRepository.deleteByPlatform(platform); + } + + /** + * 批量更新职位状态和过滤原因 + * + * @param encryptJobIds 加密职位ID列表 + * @param status 新状态 + * @param filterReason 过滤原因 + * @return 更新的职位数量 + */ + @Transactional + public int updateJobStatus(List encryptJobIds, Integer status, String filterReason) { + if (encryptJobIds == null || encryptJobIds.isEmpty()) { + log.warn("没有职位ID需要更新状态"); + return 0; + } + + try { + List jobEntities = jobRepository.findAllByEncryptJobIdIn(encryptJobIds); + for (JobEntity entity : jobEntities) { + entity.setStatus(status); + entity.setFilterReason(filterReason); + } + + jobRepository.saveAll(jobEntities); + log.info("成功更新 {} 个职位的状态为 {},过滤原因: {}", jobEntities.size(), status, filterReason); + return jobEntities.size(); + + } catch (Exception e) { + log.error("批量更新职位状态失败", e); + throw new RuntimeException("批量更新职位状态失败: " + e.getMessage(), e); + } + } + + /** + * 根据平台查询所有职位实体 + * + * @param platform 平台名称 + * @return 职位实体列表 + */ + public List findAllJobEntitiesByPlatform(String platform) { + try { + if (platform != null && !platform.trim().isEmpty()) { + // 使用数据库级别的查询,避免在内存中过滤大量数据 + return jobRepository.findByPlatform(platform); + } else { + return jobRepository.findAll(); + } + } catch (Exception e) { + log.error("查询职位实体失败", e); + throw new RuntimeException("查询职位实体失败: " + e.getMessage(), e); + } + } +} diff --git a/src/main/java/getjobs/service/PlaywrightManager.java b/src/main/java/getjobs/service/PlaywrightManager.java new file mode 100644 index 00000000..6beee420 --- /dev/null +++ b/src/main/java/getjobs/service/PlaywrightManager.java @@ -0,0 +1,81 @@ +package getjobs.service; + +import getjobs.utils.PlaywrightUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; + +/** + * Playwright管理器 - 单例管理浏览器实例 + * 在应用启动时初始化,应用关闭时清理 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Slf4j +//@Component +@Deprecated +public class PlaywrightManager { + + private volatile boolean initialized = false; + + /** + * Spring容器启动后初始化Playwright + */ + @PostConstruct + public void initializePlaywright() { + if (!initialized) { + synchronized (this) { + if (!initialized) { + log.info("正在初始化Playwright浏览器实例..."); + try { + PlaywrightUtil.init(); + initialized = true; + log.info("Playwright浏览器实例初始化成功"); + } catch (Exception e) { + log.error("Playwright浏览器初始化失败", e); + throw new RuntimeException("Playwright初始化失败", e); + } + } + } + } + } + + /** + * Spring容器关闭前清理Playwright资源 + */ + @PreDestroy + public void cleanup() { + if (initialized) { + log.info("正在清理Playwright资源..."); + try { + PlaywrightUtil.close(); + initialized = false; + log.info("Playwright资源清理完成"); + } catch (Exception e) { + log.error("Playwright资源清理失败", e); + } + } + } + + /** + * 检查Playwright是否已初始化 + * + * @return 是否已初始化 + */ + public boolean isInitialized() { + return initialized; + } + + /** + * 确保Playwright已初始化 + */ + public void ensureInitialized() { + if (!initialized) { + initializePlaywright(); + } + } +} diff --git a/src/main/java/getjobs/service/RecruitmentService.java b/src/main/java/getjobs/service/RecruitmentService.java new file mode 100644 index 00000000..880d920c --- /dev/null +++ b/src/main/java/getjobs/service/RecruitmentService.java @@ -0,0 +1,87 @@ +package getjobs.service; + +import getjobs.common.dto.ConfigDTO; +import getjobs.modules.boss.dto.JobDTO; +import getjobs.common.enums.RecruitmentPlatformEnum; + +import java.util.List; + +/** + * 招聘平台服务接口 + * 定义了招聘平台的四个核心功能:登录、采集岗位、过滤岗位、执行投递 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +public interface RecruitmentService { + + /** + * 获取当前服务支持的招聘平台 + * + * + * @return 招聘平台枚举 + */ + RecruitmentPlatformEnum getPlatform(); + + /** + * 1. 登录功能 + * 检查登录状态,如果未登录则进行登录操作 + * + * @param config 配置信息 + * @return 是否登录成功 + */ + boolean login(ConfigDTO config); + + /** + * 2. 采集岗位功能 + * 根据配置的城市代码和关键词搜索并采集岗位信息 + * + * @param config 配置信息,包含城市代码、关键词等搜索条件 + * @return 采集到的岗位列表 + */ + List collectJobs(ConfigDTO config); + + /** + * 采集推荐岗位 + * + * @param config 配置信息 + * @return 推荐岗位列表 + */ + List collectRecommendJobs(ConfigDTO config); + + /** + * 3. 过滤岗位功能 + * 根据配置的过滤条件对岗位进行筛选 + * + * @param jobDTOS 原始岗位列表 + * @param config 配置信息,包含薪资范围、黑名单等过滤条件 + * @return 过滤后的岗位列表 + */ + List filterJobs(List jobDTOS, ConfigDTO config); + + /** + * 4. 执行投递功能 + * 对过滤后的岗位执行投递操作 + * + * @param jobDTOS 过滤后的岗位列表 + * @param config 配置信息,包含打招呼内容、简历等 + * @return 投递成功的岗位数量 + */ + int deliverJobs(List jobDTOS, ConfigDTO config); + + /** + * 检查是否已达投递上限 + * + * @return 是否达到上限 + */ + boolean isDeliveryLimitReached(); + + /** + * 保存平台相关数据(如黑名单、Cookie等) + * + * @param dataPath 数据文件路径 + */ + void saveData(String dataPath); + +} diff --git a/src/main/java/getjobs/service/RecruitmentServiceFactory.java b/src/main/java/getjobs/service/RecruitmentServiceFactory.java new file mode 100644 index 00000000..1072ccd4 --- /dev/null +++ b/src/main/java/getjobs/service/RecruitmentServiceFactory.java @@ -0,0 +1,114 @@ +package getjobs.service; + +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.boss.service.impl.BossRecruitmentServiceImpl; +import getjobs.modules.job51.service.impl.Job51RecruitmentServiceImpl; +import getjobs.modules.liepin.service.impl.LiepinRecruitmentServiceImpl; +import getjobs.modules.zhilian.service.impl.ZhiLianRecruitmentServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import java.util.HashMap; +import java.util.Map; + +/** + * 招聘服务工厂类 + * 负责创建和管理不同招聘平台的服务实例 + * 支持Spring依赖注入 + * + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +@Slf4j +@Component +public class RecruitmentServiceFactory { + + private final Map serviceMap = new HashMap<>(); + + private final BossRecruitmentServiceImpl bossRecruitmentService; + private final Job51RecruitmentServiceImpl job51RecruitmentService; + private final ZhiLianRecruitmentServiceImpl zhiLianRecruitmentService; + private final LiepinRecruitmentServiceImpl liepinRecruitmentService; + + public RecruitmentServiceFactory(BossRecruitmentServiceImpl bossRecruitmentService, + Job51RecruitmentServiceImpl job51RecruitmentService, + ZhiLianRecruitmentServiceImpl zhiLianRecruitmentService, LiepinRecruitmentServiceImpl liepinRecruitmentService) { + this.bossRecruitmentService = bossRecruitmentService; + this.job51RecruitmentService = job51RecruitmentService; + this.zhiLianRecruitmentService = zhiLianRecruitmentService; + this.liepinRecruitmentService = liepinRecruitmentService; + } + + @PostConstruct + public void initServices() { + // 初始化各平台服务 + serviceMap.put(RecruitmentPlatformEnum.BOSS_ZHIPIN, bossRecruitmentService); + serviceMap.put(RecruitmentPlatformEnum.JOB_51, job51RecruitmentService); + serviceMap.put(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN, zhiLianRecruitmentService); + serviceMap.put(RecruitmentPlatformEnum.LIEPIN, liepinRecruitmentService); + + log.info("招聘服务工厂初始化完成,支持平台: {}", serviceMap.keySet()); + } + + /** + * 根据平台枚举获取对应的招聘服务 + * + * @param platform 招聘平台枚举 + * @return 招聘服务实例 + */ + public RecruitmentService getService(RecruitmentPlatformEnum platform) { + RecruitmentService service = serviceMap.get(platform); + if (service == null) { + log.error("暂不支持的招聘平台: {}", platform.getPlatformName()); + throw new UnsupportedOperationException("暂不支持的招聘平台: " + platform.getPlatformName()); + } + return service; + } + + /** + * 根据平台代码获取对应的招聘服务 + * + * @param platformCode 平台代码 + * @return 招聘服务实例 + */ + public RecruitmentService getService(String platformCode) { + RecruitmentPlatformEnum platform = RecruitmentPlatformEnum.getByCode(platformCode); + if (platform == null) { + log.error("未找到平台代码对应的招聘平台: {}", platformCode); + throw new IllegalArgumentException("未找到平台代码对应的招聘平台: " + platformCode); + } + return getService(platform); + } + + /** + * 获取所有支持的招聘平台 + * + * @return 支持的招聘平台数组 + */ + public RecruitmentPlatformEnum[] getSupportedPlatforms() { + return serviceMap.keySet().toArray(new RecruitmentPlatformEnum[0]); + } + + /** + * 检查是否支持指定平台 + * + * @param platform 招聘平台枚举 + * @return 是否支持 + */ + public boolean isSupported(RecruitmentPlatformEnum platform) { + return serviceMap.containsKey(platform); + } + + /** + * 检查是否支持指定平台代码 + * + * @param platformCode 平台代码 + * @return 是否支持 + */ + public boolean isSupported(String platformCode) { + RecruitmentPlatformEnum platform = RecruitmentPlatformEnum.getByCode(platformCode); + return platform != null && isSupported(platform); + } +} diff --git a/src/main/java/getjobs/service/impl/ConfigServiceImpl.java b/src/main/java/getjobs/service/impl/ConfigServiceImpl.java new file mode 100644 index 00000000..664923c6 --- /dev/null +++ b/src/main/java/getjobs/service/impl/ConfigServiceImpl.java @@ -0,0 +1,53 @@ +package getjobs.service.impl; + +import getjobs.repository.entity.ConfigEntity; +import getjobs.repository.ConfigRepository; +import getjobs.service.ConfigService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +public class ConfigServiceImpl implements ConfigService { + + private final ConfigRepository configRepository; + + public ConfigServiceImpl(ConfigRepository configRepository) { + this.configRepository = configRepository; + } + + @Override + @Transactional + public ConfigEntity save(ConfigEntity entity) { + // 若提供了平台类型,则按平台类型做 upsert;否则仍回退到单条记录策略 + if (entity.getPlatformType() != null && !entity.getPlatformType().trim().isEmpty()) { + return configRepository.findFirstByPlatformTypeOrderByIdAsc(entity.getPlatformType().trim()) + .map(existed -> { + entity.setId(existed.getId()); + return configRepository.save(entity); + }) + .orElseGet(() -> configRepository.save(entity)); + } else { + List all = configRepository.findAll(); + if (!all.isEmpty()) { + ConfigEntity existed = all.get(0); + entity.setId(existed.getId()); + } + return configRepository.save(entity); + } + } + + @Override + public ConfigEntity load() { + return configRepository.findAll().stream().findFirst().orElse(null); + } + + @Override + public ConfigEntity loadByPlatformType(String platformType) { + if (platformType == null || platformType.trim().isEmpty()) { + return load(); + } + return configRepository.findFirstByPlatformTypeOrderByIdAsc(platformType.trim()).orElse(null); + } +} diff --git a/src/main/java/getjobs/service/impl/DataBackupServiceImpl.java b/src/main/java/getjobs/service/impl/DataBackupServiceImpl.java new file mode 100644 index 00000000..238767a4 --- /dev/null +++ b/src/main/java/getjobs/service/impl/DataBackupServiceImpl.java @@ -0,0 +1,225 @@ +package getjobs.service.impl; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import getjobs.repository.entity.ConfigEntity; +import getjobs.repository.entity.JobEntity; +import getjobs.repository.entity.UserProfile; +import getjobs.repository.ConfigRepository; +import getjobs.repository.JobRepository; +import getjobs.repository.UserProfileRepository; +import getjobs.service.DataBackupService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 数据备份服务实现类 + * + * @author getjobs + * @since v2.0.1 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DataBackupServiceImpl implements DataBackupService { + + private final ConfigRepository configRepository; + private final JobRepository jobRepository; + private final UserProfileRepository userProfileRepository; + private final ObjectMapper objectMapper; + + private static final String BACKUP_DIR_NAME = "getjobs"; + private static final String BACKUP_FILE_NAME = "data_backup.json"; + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"); + + /** + * 获取用户目录下的备份目录路径 + */ + private Path getBackupDirPath() { + String userHome = System.getProperty("user.home"); + return Paths.get(userHome, BACKUP_DIR_NAME); + } + + /** + * 获取备份文件路径 + */ + private Path getBackupFilePath() { + return getBackupDirPath().resolve(BACKUP_FILE_NAME); + } + + @Override + public String exportData() throws Exception { + // 确保备份目录存在 + Path backupDir = getBackupDirPath(); + if (!Files.exists(backupDir)) { + Files.createDirectories(backupDir); + log.info("创建备份目录: {}", backupDir); + } + + // 收集所有数据 + Map backupData = new HashMap<>(); + + // 导出配置数据 + List configs = configRepository.findAll(); + backupData.put("configs", configs); + + // 导出职位数据 + List jobs = jobRepository.findAll(); + backupData.put("jobs", jobs); + + // 导出用户求职信息 + List userProfiles = userProfileRepository.findAll(); + backupData.put("userProfiles", userProfiles); + + // 添加元数据 + Map metadata = new HashMap<>(); + metadata.put("exportTime", LocalDateTime.now().format(DATE_FORMATTER)); + metadata.put("configCount", configs.size()); + metadata.put("jobCount", jobs.size()); + metadata.put("userProfileCount", userProfiles.size()); + metadata.put("version", "1.0"); + backupData.put("metadata", metadata); + + // 写入备份文件(覆盖写入) + Path backupFilePath = getBackupFilePath(); + objectMapper.writerWithDefaultPrettyPrinter().writeValue(backupFilePath.toFile(), backupData); + + log.info("数据备份完成 - 配置: {} 条, 职位: {} 条, 用户求职信息: {} 条, 备份文件: {}", + configs.size(), jobs.size(), userProfiles.size(), backupFilePath); + + return backupFilePath.toString(); + } + + @Override + @Transactional + public boolean importData() throws Exception { + Path backupFilePath = getBackupFilePath(); + + // 检查备份文件是否存在 + if (!Files.exists(backupFilePath)) { + log.warn("备份文件不存在: {}", backupFilePath); + return false; + } + + // 读取备份文件 + Map backupData = objectMapper.readValue(backupFilePath.toFile(), + new TypeReference>() { + }); + + // 检查备份文件是否为空 + if (backupData.isEmpty()) { + log.warn("备份文件为空: {}", backupFilePath); + return false; + } + + // 清空现有数据(可选,根据需求决定) + // configRepository.deleteAll(); + // jobRepository.deleteAll(); + + // 恢复配置数据 + if (backupData.containsKey("configs")) { + @SuppressWarnings("unchecked") + List> configMaps = (List>) backupData.get("configs"); + for (Map configMap : configMaps) { + ConfigEntity config = objectMapper.convertValue(configMap, ConfigEntity.class); + configRepository.save(config); + } + log.info("恢复配置数据: {} 条", configMaps.size()); + } + + // 恢复职位数据 + if (backupData.containsKey("jobs")) { + @SuppressWarnings("unchecked") + List> jobMaps = (List>) backupData.get("jobs"); + for (Map jobMap : jobMaps) { + JobEntity job = objectMapper.convertValue(jobMap, JobEntity.class); + jobRepository.save(job); + } + log.info("恢复职位数据: {} 条", jobMaps.size()); + } + + // 恢复用户求职信息 + if (backupData.containsKey("userProfiles")) { + @SuppressWarnings("unchecked") + List> userProfileMaps = (List>) backupData.get("userProfiles"); + for (Map userProfileMap : userProfileMaps) { + UserProfile userProfile = objectMapper.convertValue(userProfileMap, UserProfile.class); + userProfileRepository.save(userProfile); + } + log.info("恢复用户求职信息: {} 条", userProfileMaps.size()); + } + + // 输出备份元数据信息 + if (backupData.containsKey("metadata")) { + @SuppressWarnings("unchecked") + Map metadata = (Map) backupData.get("metadata"); + log.info("数据恢复完成 - 备份时间: {}, 版本: {}", + metadata.get("exportTime"), metadata.get("version")); + } + + return true; + } + + @Override + public Map getBackupInfo() throws Exception { + Map info = new HashMap<>(); + Path backupFilePath = getBackupFilePath(); + + if (Files.exists(backupFilePath)) { + File backupFile = backupFilePath.toFile(); + info.put("exists", true); + info.put("filePath", backupFilePath.toString()); + info.put("fileSize", backupFile.length()); + info.put("lastModified", backupFile.lastModified()); + + // 尝试读取备份文件中的元数据 + try { + Map backupData = objectMapper.readValue(backupFile, + new TypeReference>() { + }); + if (backupData.containsKey("metadata")) { + @SuppressWarnings("unchecked") + Map metadata = (Map) backupData.get("metadata"); + info.put("exportTime", metadata.get("exportTime")); + info.put("configCount", metadata.get("configCount")); + info.put("jobCount", metadata.get("jobCount")); + info.put("userProfileCount", metadata.get("userProfileCount")); + info.put("version", metadata.get("version")); + } + } catch (Exception e) { + log.warn("读取备份文件元数据失败", e); + } + } else { + info.put("exists", false); + info.put("filePath", backupFilePath.toString()); + } + + return info; + } + + @Override + public boolean cleanBackup() throws Exception { + Path backupFilePath = getBackupFilePath(); + + if (Files.exists(backupFilePath)) { + Files.delete(backupFilePath); + log.info("备份文件已删除: {}", backupFilePath); + return true; + } else { + log.info("备份文件不存在,无需删除: {}", backupFilePath); + return false; + } + } +} diff --git a/src/main/java/getjobs/utils/BossJobDataConverter.java b/src/main/java/getjobs/utils/BossJobDataConverter.java new file mode 100644 index 00000000..52ff6356 --- /dev/null +++ b/src/main/java/getjobs/utils/BossJobDataConverter.java @@ -0,0 +1,336 @@ +package getjobs.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import getjobs.modules.boss.dto.JobDTO; +import getjobs.repository.entity.JobEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +/** + * BOSS直聘职位数据转换工具类 + * + * @author getjobs + * @since v2.0.1 + */ +@Slf4j +@Component +public class BossJobDataConverter { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 将BOSS直聘API响应中的职位数据转换为JobEntity + * + * @param jobData BOSS直聘API响应中的单个职位数据 + * @return JobEntity 职位实体 + */ + public JobEntity convertToJobEntity(Map jobData) { + try { + JobEntity jobEntity = new JobEntity(); + + // 基础职位信息 + jobEntity.setJobTitle(getStringValue(jobData, "jobName")); + jobEntity.setSalaryDesc(getStringValue(jobData, "salaryDesc")); + jobEntity.setJobExperience(getStringValue(jobData, "jobExperience")); + jobEntity.setJobDegree(getStringValue(jobData, "jobDegree")); + jobEntity.setJobLabels(convertListToJson(getListValue(jobData, "jobLabels"))); + jobEntity.setSkills(convertListToJson(getListValue(jobData, "skills"))); + + // 公司信息 + jobEntity.setCompanyName(getStringValue(jobData, "brandName")); + jobEntity.setCompanyIndustry(getStringValue(jobData, "brandIndustry")); + jobEntity.setCompanyStage(getStringValue(jobData, "brandStageName")); + jobEntity.setCompanyScale(getStringValue(jobData, "brandScaleName")); + jobEntity.setCompanyLogo(getStringValue(jobData, "brandLogo")); + + // 工作地点信息 + jobEntity.setWorkCity(getStringValue(jobData, "cityName")); + jobEntity.setWorkArea(getStringValue(jobData, "areaDistrict")); + jobEntity.setBusinessDistrict(getStringValue(jobData, "businessDistrict")); + + // GPS坐标 + Map gps = getMapValue(jobData, "gps"); + if (gps != null) { + jobEntity.setLongitude( + getBigDecimal(gps, "longitude")); + jobEntity.setLatitude( + getBigDecimal(gps, "latitude")); + } + + // HR信息 + jobEntity.setHrName(getStringValue(jobData, "bossName")); + jobEntity.setHrTitle(getStringValue(jobData, "bossTitle")); + jobEntity.setHrAvatar(getStringValue(jobData, "bossAvatar")); + jobEntity.setHrOnline(getBooleanValue(jobData, "bossOnline")); + jobEntity.setHrCertLevel(getIntegerValue(jobData, "bossCert")); + + // 系统信息 + jobEntity.setPlatform("BOSS直聘"); + jobEntity.setEncryptJobId(getStringValue(jobData, "encryptJobId")); + jobEntity.setEncryptHrId(getStringValue(jobData, "encryptBossId")); + jobEntity.setEncryptCompanyId(getStringValue(jobData, "encryptBrandId")); + jobEntity.setSecurityId(getStringValue(jobData, "securityId")); + + // 状态信息 + jobEntity.setIsOptimal(getBooleanValue(jobData, "optimal")); + jobEntity.setIsProxyJob(getBooleanValue(jobData, "proxyJob")); + jobEntity.setProxyType(getIntegerValue(jobData, "proxyType")); + jobEntity.setIsGoldHunter(getBooleanValue(jobData, "goldHunter")); + jobEntity.setIsContacted(getBooleanValue(jobData, "contact")); + jobEntity.setIsShielded(getIntegerValue(jobData, "isShield") == 1); + jobEntity.setJobValidStatus(getIntegerValue(jobData, "jobValidStatus")); + + // 其他信息 + jobEntity.setWelfareList(convertListToJson(getListValue(jobData, "welfareList"))); + jobEntity.setIconFlagList(convertIntegerListToJson(getIntegerListValue(jobData, "iconFlagList"))); + jobEntity.setBeforeNameIcons(convertListToJson(getListValue(jobData, "beforeNameIcons"))); + jobEntity.setAfterNameIcons(convertListToJson(getListValue(jobData, "afterNameIcons"))); + jobEntity.setIconWord(getStringValue(jobData, "iconWord")); + jobEntity.setLeastMonthDesc(getStringValue(jobData, "leastMonthDesc")); + jobEntity.setDaysPerWeekDesc(getStringValue(jobData, "daysPerWeekDesc")); + jobEntity.setShowTopPosition(getBooleanValue(jobData, "showTopPosition")); + jobEntity.setIsOutland(getIntegerValue(jobData, "outland") == 1); + jobEntity.setAnonymousStatus(getIntegerValue(jobData, "anonymous")); + jobEntity.setItemId(getIntegerValue(jobData, "itemId")); + jobEntity.setExpectId(getLongValue(jobData, "expectId")); + jobEntity.setCityCode(getLongValue(jobData, "city")); + jobEntity.setIndustryCode(getLongValue(jobData, "industry")); + jobEntity.setJobType(getIntegerValue(jobData, "jobType")); + jobEntity.setAtsDirectPost(getBooleanValue(jobData, "atsDirectPost")); + jobEntity.setSearchId(getStringValue(jobData, "lid")); + + // 构造职位链接 + String encryptJobId = getStringValue(jobData, "encryptJobId"); + if (encryptJobId != null) { + jobEntity.setJobUrl("https://www.zhipin.com/job_detail/" + encryptJobId + ".html"); + } + + return jobEntity; + + } catch (Exception e) { + log.error("转换BOSS直聘职位数据失败: {}", e.getMessage(), e); + return null; + } + } + + /** + * 将JobEntity转换为JobDTO + * + * @param jobEntity 职位实体 + * @return JobDTO 职位数据传输对象 + */ + public JobDTO convertToJobDTO(JobEntity jobEntity) { + try { + JobDTO jobDTO = new JobDTO(); + + // 基础职位信息 + jobDTO.setJobName(jobEntity.getJobTitle()); + jobDTO.setSalary(jobEntity.getSalaryDesc()); + jobDTO.setSalaryDesc(jobEntity.getSalaryDesc()); + jobDTO.setJobExperience(jobEntity.getJobExperience()); + jobDTO.setJobDegree(jobEntity.getJobDegree()); + jobDTO.setJobLabels(convertJsonToList(jobEntity.getJobLabels())); + jobDTO.setSkills(convertJsonToList(jobEntity.getSkills())); + jobDTO.setJobDescription(jobEntity.getJobDescription()); + jobDTO.setJobRequirements(jobEntity.getJobRequirements()); + jobDTO.setHref(jobEntity.getJobUrl()); + + // 公司信息 + jobDTO.setCompanyName(jobEntity.getCompanyName()); + jobDTO.setCompanyIndustry(jobEntity.getCompanyIndustry()); + jobDTO.setCompanyStage(jobEntity.getCompanyStage()); + jobDTO.setCompanyScale(jobEntity.getCompanyScale()); + jobDTO.setCompanyLogo(jobEntity.getCompanyLogo()); + jobDTO.setCompanyTag(jobEntity.getCompanyTag()); + + // 工作地点信息 + jobDTO.setJobArea(jobEntity.getWorkCity()); + jobDTO.setWorkCity(jobEntity.getWorkCity()); + jobDTO.setWorkArea(jobEntity.getWorkArea()); + jobDTO.setBusinessDistrict(jobEntity.getBusinessDistrict()); + jobDTO.setLongitude(jobEntity.getLongitude()); + jobDTO.setLatitude(jobEntity.getLatitude()); + + // HR信息 + jobDTO.setRecruiter(jobEntity.getHrName()); + jobDTO.setHrName(jobEntity.getHrName()); + jobDTO.setHrTitle(jobEntity.getHrTitle()); + jobDTO.setHrAvatar(jobEntity.getHrAvatar()); + jobDTO.setHrOnline(jobEntity.getHrOnline()); + jobDTO.setHrCertLevel(jobEntity.getHrCertLevel()); + jobDTO.setHrActiveTime(jobEntity.getHrActiveTime()); + + // 系统信息 + jobDTO.setPlatform(jobEntity.getPlatform()); + jobDTO.setEncryptJobId(jobEntity.getEncryptJobId()); + jobDTO.setEncryptHrId(jobEntity.getEncryptHrId()); + jobDTO.setEncryptCompanyId(jobEntity.getEncryptCompanyId()); + jobDTO.setSecurityId(jobEntity.getSecurityId()); + jobDTO.setStatus(jobEntity.getStatus()); + jobDTO.setIsFavorite(jobEntity.getIsFavorite()); + jobDTO.setIsOptimal(jobEntity.getIsOptimal()); + jobDTO.setIsProxyJob(jobEntity.getIsProxyJob()); + jobDTO.setProxyType(jobEntity.getProxyType()); + jobDTO.setIsGoldHunter(jobEntity.getIsGoldHunter()); + jobDTO.setIsContacted(jobEntity.getIsContacted()); + jobDTO.setIsShielded(jobEntity.getIsShielded()); + jobDTO.setJobValidStatus(jobEntity.getJobValidStatus()); + + // 其他信息 + jobDTO.setWelfareList(convertJsonToList(jobEntity.getWelfareList())); + jobDTO.setIconFlagList(convertJsonToIntegerList(jobEntity.getIconFlagList())); + jobDTO.setBeforeNameIcons(convertJsonToList(jobEntity.getBeforeNameIcons())); + jobDTO.setAfterNameIcons(convertJsonToList(jobEntity.getAfterNameIcons())); + jobDTO.setIconWord(jobEntity.getIconWord()); + jobDTO.setLeastMonthDesc(jobEntity.getLeastMonthDesc()); + jobDTO.setDaysPerWeekDesc(jobEntity.getDaysPerWeekDesc()); + jobDTO.setShowTopPosition(jobEntity.getShowTopPosition()); + jobDTO.setIsOutland(jobEntity.getIsOutland()); + jobDTO.setAnonymousStatus(jobEntity.getAnonymousStatus()); + jobDTO.setItemId(jobEntity.getItemId()); + jobDTO.setExpectId(jobEntity.getExpectId()); + jobDTO.setCityCode(jobEntity.getCityCode()); + jobDTO.setIndustryCode(jobEntity.getIndustryCode()); + jobDTO.setJobType(jobEntity.getJobType()); + jobDTO.setAtsDirectPost(jobEntity.getAtsDirectPost()); + jobDTO.setSearchId(jobEntity.getSearchId()); + + return jobDTO; + + } catch (Exception e) { + log.error("转换JobEntity到JobDTO失败: {}", e.getMessage(), e); + return null; + } + } + + // ==================== 辅助方法 ==================== + + private String getStringValue(Map data, String key) { + Object value = data.get(key); + return value != null ? value.toString() : null; + } + + private Integer getIntegerValue(Map data, String key) { + Object value = data.get(key); + if (value instanceof Number) { + return ((Number) value).intValue(); + } + return null; + } + + private Long getLongValue(Map data, String key) { + Object value = data.get(key); + if (value instanceof Number) { + return ((Number) value).longValue(); + } + return null; + } + + private Double getDoubleValue(Map data, String key) { + Object value = data.get(key); + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + return null; + } + + private BigDecimal getBigDecimal(Map data, String key) { + Object value = data.get(key); + if (value instanceof Number) { + return BigDecimal.valueOf(((Number) value).doubleValue()); + } + return null; + } + + private Boolean getBooleanValue(Map data, String key) { + Object value = data.get(key); + if (value instanceof Boolean) { + return (Boolean) value; + } else if (value instanceof Number) { + return ((Number) value).intValue() == 1; + } + return null; + } + + @SuppressWarnings("unchecked") + private List getListValue(Map data, String key) { + Object value = data.get(key); + if (value instanceof List) { + return (List) value; + } + return null; + } + + @SuppressWarnings("unchecked") + private List getIntegerListValue(Map data, String key) { + Object value = data.get(key); + if (value instanceof List) { + return (List) value; + } + return null; + } + + @SuppressWarnings("unchecked") + private Map getMapValue(Map data, String key) { + Object value = data.get(key); + if (value instanceof Map) { + return (Map) value; + } + return null; + } + + private String convertListToJson(List list) { + if (list == null || list.isEmpty()) { + return null; + } + try { + return objectMapper.writeValueAsString(list); + } catch (JsonProcessingException e) { + log.error("转换List到JSON失败: {}", e.getMessage()); + return null; + } + } + + private String convertIntegerListToJson(List list) { + if (list == null || list.isEmpty()) { + return null; + } + try { + return objectMapper.writeValueAsString(list); + } catch (JsonProcessingException e) { + log.error("转换IntegerList到JSON失败: {}", e.getMessage()); + return null; + } + } + + @SuppressWarnings("unchecked") + private List convertJsonToList(String json) { + if (json == null || json.trim().isEmpty()) { + return null; + } + try { + return objectMapper.readValue(json, List.class); + } catch (JsonProcessingException e) { + log.error("转换JSON到List失败: {}", e.getMessage()); + return null; + } + } + + @SuppressWarnings("unchecked") + private List convertJsonToIntegerList(String json) { + if (json == null || json.trim().isEmpty()) { + return null; + } + try { + return objectMapper.readValue(json, List.class); + } catch (JsonProcessingException e) { + log.error("转换JSON到IntegerList失败: {}", e.getMessage()); + return null; + } + } +} diff --git a/src/main/java/getjobs/utils/Constant.java b/src/main/java/getjobs/utils/Constant.java new file mode 100644 index 00000000..00f03219 --- /dev/null +++ b/src/main/java/getjobs/utils/Constant.java @@ -0,0 +1,9 @@ +package getjobs.utils; + +/** + * @author loks666 + * 项目链接: https://github.com/loks666/get_jobs + */ +public class Constant { + public static String UNLIMITED_CODE = "0"; +} diff --git a/src/main/java/getjobs/utils/Job51DataConverter.java b/src/main/java/getjobs/utils/Job51DataConverter.java new file mode 100644 index 00000000..1b1322c1 --- /dev/null +++ b/src/main/java/getjobs/utils/Job51DataConverter.java @@ -0,0 +1,346 @@ +package getjobs.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import getjobs.modules.job51.dto.Job51ApiResponse; +import getjobs.repository.entity.JobEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 51Job数据转换器 + * 负责将51Job API响应数据转换为JobEntity + * + * @author getjobs + * @since v2.1.1 + */ +@Slf4j +@Component +public class Job51DataConverter { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 将51Job API响应中的职位数据转换为JobEntity + * + * @param jobItem 51Job API响应中的单个职位数据 + * @return JobEntity 职位实体 + */ + public JobEntity convertToJobEntity(Job51ApiResponse.Job51JobItem jobItem) { + try { + JobEntity jobEntity = new JobEntity(); + + // 基础职位信息 + jobEntity.setJobTitle(jobItem.getJobName()); + jobEntity.setSalaryDesc(jobItem.getProvideSalaryString()); + jobEntity.setJobExperience(jobItem.getWorkYearString()); + jobEntity.setJobDegree(jobItem.getDegreeString()); + jobEntity.setJobDescription(jobItem.getJobDescribe()); + jobEntity.setJobUrl(jobItem.getJobHref()); + + // 职位标签处理 - 优先使用jobTags,如果为空则使用jobTagsForOrder + List tags = jobItem.getJobTags(); + if (tags == null || tags.isEmpty()) { + tags = jobItem.getJobTagsForOrder(); + } + if (tags != null && !tags.isEmpty()) { + jobEntity.setJobLabels(convertListToJson(tags)); + jobEntity.setSkills(convertListToJson(tags)); + } + + // 公司信息 + jobEntity.setCompanyName(jobItem.getCompanyName()); + // 优先使用fullCompanyName,如果没有则使用companyName + if (jobItem.getFullCompanyName() != null && !jobItem.getFullCompanyName().isEmpty()) { + jobEntity.setCompanyName(jobItem.getFullCompanyName()); + } + + // 构建行业信息 + String industry = jobItem.getCompanyIndustryType1Str(); + if (jobItem.getIndustryType2Str() != null && !jobItem.getIndustryType2Str().isEmpty()) { + industry += "/" + jobItem.getIndustryType2Str(); + } + jobEntity.setCompanyIndustry(industry); + + jobEntity.setCompanyScale(jobItem.getCompanySizeString()); + jobEntity.setCompanyStage(jobItem.getCompanyTypeString()); + jobEntity.setCompanyLogo(jobItem.getCompanyLogo()); + + // 工作地点信息 + if (jobItem.getJobAreaLevelDetail() != null) { + Job51ApiResponse.Job51AreaDetail area = jobItem.getJobAreaLevelDetail(); + jobEntity.setWorkCity(area.getCityString()); + jobEntity.setWorkArea(area.getDistrictString()); + jobEntity.setBusinessDistrict(area.getLandMarkString()); + } else { + jobEntity.setWorkCity(jobItem.getJobAreaString()); + } + + // GPS坐标 + if (jobItem.getLon() != null && jobItem.getLat() != null) { + try { + jobEntity.setLongitude(new BigDecimal(jobItem.getLon())); + jobEntity.setLatitude(new BigDecimal(jobItem.getLat())); + } catch (NumberFormatException e) { + log.warn("GPS坐标格式错误: lon={}, lat={}", jobItem.getLon(), jobItem.getLat()); + } + } + + // HR信息 + jobEntity.setHrName(jobItem.getHrName()); + jobEntity.setHrTitle(jobItem.getHrPosition()); + jobEntity.setHrAvatar(jobItem.getSmallHrLogoUrl()); + jobEntity.setHrOnline(jobItem.getHrIsOnline()); + jobEntity.setHrActiveTime(jobItem.getHrActiveStatusGreen()); + + // 系统信息 + jobEntity.setPlatform("51job"); + jobEntity.setEncryptJobId(jobItem.getJobId()); + jobEntity.setEncryptHrId(jobItem.getHrUid()); + jobEntity.setEncryptCompanyId(jobItem.getEncCoId()); + + // 设置公司ID + if (jobItem.getCoId() != null) { + try { + jobEntity.setItemId(Integer.parseInt(jobItem.getCoId())); + } catch (NumberFormatException e) { + log.warn("公司ID格式错误: {}", jobItem.getCoId()); + } + } + + // 状态信息 + jobEntity.setStatus(0); // 待处理状态 + jobEntity.setIsFavorite(false); + jobEntity.setIsContacted(jobItem.getIsCommunicate()); + + // 福利信息 + if (jobItem.getJobWelfareCodeDataList() != null && !jobItem.getJobWelfareCodeDataList().isEmpty()) { + List welfareList = jobItem.getJobWelfareCodeDataList().stream() + .map(Job51ApiResponse.Job51WelfareData::getChineseTitle) + .collect(Collectors.toList()); + jobEntity.setWelfareList(convertListToJson(welfareList)); + } + + // 芝麻标签信息 + if (jobItem.getSesameLabelList() != null && !jobItem.getSesameLabelList().isEmpty()) { + List labelList = jobItem.getSesameLabelList().stream() + .map(Job51ApiResponse.Job51SesameLabel::getLabelName) + .collect(Collectors.toList()); + jobEntity.setCompanyTag(String.join(",", labelList)); + } + + // 51job特有字段映射 + jobEntity.setJobType(parseJobType(jobItem.getJobType())); + // 注:JobEntity中没有直接对应的字段,可以在jobLabels中记录这些信息 + if (Boolean.TRUE.equals(jobItem.getIsRemoteWork())) { + String currentLabels = jobEntity.getJobLabels(); + if (currentLabels == null) { + currentLabels = "[\"远程工作\"]"; + } else { + // 添加远程工作标签到现有标签中 + currentLabels = currentLabels.replace("]", ",\"远程工作\"]"); + } + jobEntity.setJobLabels(currentLabels); + } + if (Boolean.TRUE.equals(jobItem.getIsIntern())) { + String currentLabels = jobEntity.getJobLabels(); + if (currentLabels == null) { + currentLabels = "[\"实习\"]"; + } else { + // 添加实习标签到现有标签中 + currentLabels = currentLabels.replace("]", ",\"实习\"]"); + } + jobEntity.setJobLabels(currentLabels); + } + + // 薪资范围解析 - 使用精确的薪资范围数据 + parseSalaryRange(jobItem, jobEntity); + + // 更新时间信息 + if (jobItem.getUpdateDateTime() != null) { + jobEntity.setHrActiveTime(jobItem.getUpdateDateTime()); + } + + // 处理职位发布和确认时间 + if (jobItem.getIssueDateString() != null) { + // 可以在这里设置职位发布时间到相应字段,如果JobEntity有的话 + log.debug("职位发布时间: {}", jobItem.getIssueDateString()); + } + + // 行业和职能编码 + if (jobItem.getIndustryType1() != null) { + try { + jobEntity.setIndustryCode(Long.parseLong(jobItem.getIndustryType1())); + } catch (NumberFormatException e) { + log.warn("行业编码格式错误: {}", jobItem.getIndustryType1()); + } + } + + // 处理工作区域信息 + if (jobItem.getWorkAreaCode() != null) { + try { + jobEntity.setCityCode(Long.parseLong(jobItem.getWorkAreaCode())); + } catch (NumberFormatException e) { + log.warn("区域代码格式错误: {}", jobItem.getWorkAreaCode()); + } + } + + // 处理职位类型和任期信息 + if (jobItem.getTermStr() != null) { + // 可以将任期信息添加到职位标签中 + String currentLabels = jobEntity.getJobLabels(); + if (currentLabels == null) { + currentLabels = "[\"" + jobItem.getTermStr() + "\"]"; + } else { + currentLabels = currentLabels.replace("]", ",\"" + jobItem.getTermStr() + "\"]"); + } + jobEntity.setJobLabels(currentLabels); + } + + return jobEntity; + + } catch (Exception e) { + log.error("转换51Job职位数据失败: {}", e.getMessage(), e); + return null; + } + } + + /** + * 解析薪资范围 + */ + private void parseSalaryRange(Job51ApiResponse.Job51JobItem jobItem, JobEntity jobEntity) { + // 优先使用provideSalaryString,如果为空则使用jobSalaryMin和jobSalaryMax计算 + if (jobItem.getProvideSalaryString() != null && !jobItem.getProvideSalaryString().trim().isEmpty()) { + jobEntity.setSalaryDesc(jobItem.getProvideSalaryString()); + } else if (jobItem.getJobSalaryMin() != null && jobItem.getJobSalaryMax() != null) { + try { + // 51job的薪资单位通常是元/月 + int minMonthly = Integer.parseInt(jobItem.getJobSalaryMin()); + int maxMonthly = Integer.parseInt(jobItem.getJobSalaryMax()); + + // 构建薪资描述 + if (minMonthly >= 10000) { + double minWan = minMonthly / 10000.0; + double maxWan = maxMonthly / 10000.0; + if (minWan == (int) minWan && maxWan == (int) maxWan) { + jobEntity.setSalaryDesc(String.format("%.0f-%.0f万", minWan, maxWan)); + } else { + jobEntity.setSalaryDesc(String.format("%.1f-%.1f万", minWan, maxWan)); + } + } else { + jobEntity.setSalaryDesc(String.format("%d-%d元", minMonthly, maxMonthly)); + } + } catch (NumberFormatException e) { + log.warn("薪资范围解析失败: min={}, max={}", jobItem.getJobSalaryMin(), jobItem.getJobSalaryMax()); + } + } + } + + /** + * 解析职位类型 + */ + private Integer parseJobType(String jobTypeStr) { + if (jobTypeStr == null) { + return 0; // 默认全职 + } + + try { + return Integer.parseInt(jobTypeStr); + } catch (NumberFormatException e) { + log.warn("职位类型解析失败: {}", jobTypeStr); + return 0; + } + } + + /** + * 将字符串列表转换为JSON字符串 + */ + private String convertListToJson(List list) { + if (list == null || list.isEmpty()) { + return null; + } + try { + return objectMapper.writeValueAsString(list); + } catch (JsonProcessingException e) { + log.error("转换List到JSON失败: {}", e.getMessage()); + return null; + } + } + + /** + * 检查51Job职位数据是否有效 + * + * @param jobItem 职位数据 + * @return 是否有效 + */ + public boolean isValidJobData(Job51ApiResponse.Job51JobItem jobItem) { + if (jobItem == null) { + return false; + } + + // 基础必填字段检查 + if (jobItem.getJobId() == null || jobItem.getJobId().trim().isEmpty()) { + log.warn("职位ID为空,跳过该职位"); + return false; + } + + if (jobItem.getJobName() == null || jobItem.getJobName().trim().isEmpty()) { + log.warn("职位名称为空,跳过该职位: {}", jobItem.getJobId()); + return false; + } + + // 公司名称检查 - 优先检查fullCompanyName + String companyName = jobItem.getFullCompanyName(); + if (companyName == null || companyName.trim().isEmpty()) { + companyName = jobItem.getCompanyName(); + } + if (companyName == null || companyName.trim().isEmpty()) { + log.warn("公司名称为空,跳过该职位: {}", jobItem.getJobId()); + return false; + } + + // 检查是否过期 + if (Boolean.TRUE.equals(jobItem.getIsExpire())) { + log.debug("职位已过期,跳过该职位: {}", jobItem.getJobId()); + return false; + } + + // 检查薪资信息 - 至少要有一种薪资描述 + if ((jobItem.getProvideSalaryString() == null || jobItem.getProvideSalaryString().trim().isEmpty()) && + (jobItem.getJobSalaryMin() == null || jobItem.getJobSalaryMax() == null)) { + log.debug("薪资信息缺失,跳过该职位: {}", jobItem.getJobId()); + return false; + } + + // 检查工作地点信息 + if ((jobItem.getJobAreaString() == null || jobItem.getJobAreaString().trim().isEmpty()) && + (jobItem.getJobAreaLevelDetail() == null || + jobItem.getJobAreaLevelDetail().getCityString() == null || + jobItem.getJobAreaLevelDetail().getCityString().trim().isEmpty())) { + log.debug("工作地点信息缺失,跳过该职位: {}", jobItem.getJobId()); + return false; + } + + return true; + } + + /** + * 生成职位的唯一标识符 + * 用于去重判断 + * + * @param jobItem 职位数据 + * @return 唯一标识符 + */ + public String generateJobUniqueId(Job51ApiResponse.Job51JobItem jobItem) { + if (jobItem == null || jobItem.getJobId() == null) { + return null; + } + + // 使用职位ID作为唯一标识 + return "51job_" + jobItem.getJobId(); + } +} diff --git a/src/main/java/getjobs/utils/JobUtils.java b/src/main/java/getjobs/utils/JobUtils.java new file mode 100644 index 00000000..dec50099 --- /dev/null +++ b/src/main/java/getjobs/utils/JobUtils.java @@ -0,0 +1,31 @@ +package getjobs.utils; + +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static getjobs.utils.Constant.UNLIMITED_CODE; + +@Slf4j +public class JobUtils { + + public static String appendParam(String name, String value) { + return Optional.ofNullable(value) + .filter(v -> !Objects.equals(UNLIMITED_CODE, v)) + .map(v -> "&" + name + "=" + v) + .orElse(""); + } + + public static String appendListParam(String name, List values) { + return Optional.ofNullable(values) + .filter(list -> !list.isEmpty() && !Objects.equals(UNLIMITED_CODE, list.getFirst())) + .map(list -> "&" + name + "=" + String.join(",", list)) + .orElse(""); + } + + + + +} diff --git a/src/main/java/getjobs/utils/LiePinDataConverter.java b/src/main/java/getjobs/utils/LiePinDataConverter.java new file mode 100644 index 00000000..22f62b4f --- /dev/null +++ b/src/main/java/getjobs/utils/LiePinDataConverter.java @@ -0,0 +1,106 @@ +package getjobs.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.liepin.dto.LiePinApiResponse; +import getjobs.repository.entity.JobEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class LiePinDataConverter { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + public JobEntity convertToJobEntity(LiePinApiResponse.JobCard jobCard) { + try { + JobEntity jobEntity = new JobEntity(); + LiePinApiResponse.Job job = jobCard.getJob(); + LiePinApiResponse.Comp comp = jobCard.getComp(); + LiePinApiResponse.Recruiter recruiter = jobCard.getRecruiter(); + + jobEntity.setJobTitle(job.getTitle()); + jobEntity.setSalaryDesc(job.getSalary()); + jobEntity.setJobExperience(job.getRequireWorkYears()); + jobEntity.setJobDegree(job.getRequireEduLevel()); + jobEntity.setJobUrl(job.getLink()); + jobEntity.setJobType(Integer.parseInt(job.getJobKind())); + + if (job.getLabels() != null && !job.getLabels().isEmpty()) { + jobEntity.setJobLabels(convertListToJson(job.getLabels())); + jobEntity.setSkills(convertListToJson(job.getLabels())); + } + + jobEntity.setCompanyName(comp.getCompName()); + jobEntity.setCompanyIndustry(comp.getCompIndustry()); + jobEntity.setCompanyScale(comp.getCompScale()); + jobEntity.setCompanyStage(comp.getCompStage()); + jobEntity.setCompanyLogo(comp.getCompLogo()); + + jobEntity.setWorkCity(job.getDq()); + + jobEntity.setHrName(recruiter.getRecruiterName()); + jobEntity.setHrTitle(recruiter.getRecruiterTitle()); + jobEntity.setHrAvatar(recruiter.getRecruiterPhoto()); + jobEntity.setHrActiveTime(recruiter.getImShowText()); + + jobEntity.setPlatform(RecruitmentPlatformEnum.LIEPIN.getPlatformCode()); + jobEntity.setEncryptJobId(job.getJobId()); + jobEntity.setEncryptHrId(recruiter.getRecruiterId()); + if (comp.getCompId() != null) { + jobEntity.setEncryptCompanyId(String.valueOf(comp.getCompId())); + jobEntity.setItemId(comp.getCompId()); + } + + jobEntity.setStatus(0); + jobEntity.setIsFavorite(false); + jobEntity.setIsContacted(false); + + return jobEntity; + } catch (Exception e) { + log.error("转换猎聘职位数据失败: {}", e.getMessage(), e); + return null; + } + } + + private String convertListToJson(List list) { + if (list == null || list.isEmpty()) { + return null; + } + try { + return objectMapper.writeValueAsString(list); + } catch (JsonProcessingException e) { + log.error("转换List到JSON失败: {}", e.getMessage()); + return null; + } + } + + public boolean isValidJobData(LiePinApiResponse.JobCard jobCard) { + if (jobCard == null || jobCard.getJob() == null || jobCard.getComp() == null) { + return false; + } + + LiePinApiResponse.Job job = jobCard.getJob(); + if (job.getJobId() == null || job.getJobId().trim().isEmpty()) { + log.warn("职位ID为空,跳过该职位"); + return false; + } + + if (job.getTitle() == null || job.getTitle().trim().isEmpty()) { + log.warn("职位名称为空,跳过该职位: {}", job.getJobId()); + return false; + } + + if (jobCard.getComp().getCompName() == null || jobCard.getComp().getCompName().trim().isEmpty()) { + log.warn("公司名称为空,跳过该职位: {}", job.getJobId()); + return false; + } + + return true; + } +} diff --git a/src/main/java/getjobs/utils/PlaywrightUtil.java b/src/main/java/getjobs/utils/PlaywrightUtil.java new file mode 100644 index 00000000..d3cc2270 --- /dev/null +++ b/src/main/java/getjobs/utils/PlaywrightUtil.java @@ -0,0 +1,521 @@ +package getjobs.utils; + +import com.microsoft.playwright.*; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * Playwright工具类,提供浏览器自动化相关的功能 + */ +@Deprecated +@Slf4j +public class PlaywrightUtil { + + // Playwright实例 + private static Playwright PLAYWRIGHT; + + // 浏览器实例 + private static Browser BROWSER; + + // 浏览器上下文 + private static BrowserContext CONTEXT; + + // 浏览器页面 + private static Page PAGE; + + // 默认超时时间(毫秒) + private static final int DEFAULT_TIMEOUT = 30000; + + // 默认等待时间(毫秒) + private static final int DEFAULT_WAIT_TIME = 10000; + + // 随机User-Agent列表 + private static final String[] USER_AGENTS = { + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36" + }; + + /** + * 创建HTTP头部信息 + * + * @return HTTP头部Map + */ + private static Map createHeaders() { + Map headers = new java.util.HashMap<>(); + headers.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"); + headers.put("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"); + headers.put("Accept-Encoding", "gzip, deflate, br"); + headers.put("Cache-Control", "no-cache"); + headers.put("Pragma", "no-cache"); + headers.put("sec-ch-ua", "\"Chromium\";v=\"135\", \"Not A(Brand\";v=\"99\""); + headers.put("sec-ch-ua-mobile", "?0"); + headers.put("sec-ch-ua-platform", "\"Windows\""); + headers.put("sec-fetch-dest", "document"); + headers.put("sec-fetch-mode", "navigate"); + headers.put("sec-fetch-site", "none"); + headers.put("sec-fetch-user", "?1"); + headers.put("upgrade-insecure-requests", "1"); + return headers; + } + + /** + * 获取随机User-Agent + * @return 随机User-Agent字符串 + */ + public static String getRandomUserAgent() { + Random random = new Random(); + return USER_AGENTS[random.nextInt(USER_AGENTS.length)]; + } + + /** + * 初始化Playwright及浏览器实例 + */ + public static void init() { + // 启动Playwright + PLAYWRIGHT = Playwright.create(); + + List args = List.of( + "--disable-blink-features=AutomationControlled", // 禁用自动化控制特征 + "--disable-web-security", // 禁用web安全 + "--disable-features=VizDisplayCompositor", // 禁用一些特征 + "--disable-dev-shm-usage", // 禁用/dev/shm使用 + "--no-sandbox", // 禁用沙箱 + "--disable-extensions", // 禁用扩展 + "--disable-plugins", // 禁用插件 + "--disable-default-apps", // 禁用默认应用 + "--disable-background-timer-throttling", // 禁用后台定时器限制 + "--disable-renderer-backgrounding", // 禁用渲染器后台处理 + "--disable-backgrounding-occluded-windows", // 禁用隐藏窗口后台处理 + "--disable-ipc-flooding-protection" // 禁用IPC洪水保护 + ); + + // 创建浏览器实例 + BROWSER = PLAYWRIGHT.chromium().launch(new BrowserType.LaunchOptions() + .setHeadless(false) // 非无头模式,可视化调试 + .setSlowMo(50) // 放慢操作速度,便于调试 + .setArgs(List.of())); + + // 随机选择User-Agent + String randomUserAgent = getRandomUserAgent(); + + // 创建浏览器上下文,增强伪装设置 + CONTEXT = BROWSER.newContext(new Browser.NewContextOptions() + .setUserAgent(randomUserAgent) + .setJavaScriptEnabled(true) + .setBypassCSP(true) + .setPermissions(List.of("geolocation", "notifications")) // 添加一些常见权限 +// .setExtraHTTPHeaders(createHeaders()) + .setLocale("zh-CN") + .setTimezoneId("Asia/Shanghai")); + + // 创建页面 + PAGE = CONTEXT.newPage(); + PAGE.setDefaultTimeout(DEFAULT_TIMEOUT); + + // 注入反检测JavaScript代码 +// PAGE.addInitScript(""" +// // 移除webdriver属性 +// Object.defineProperty(navigator, 'webdriver', { +// get: () => undefined, +// }); +// +// // 重写plugins属性 +// Object.defineProperty(navigator, 'plugins', { +// get: () => [1, 2, 3, 4, 5], +// }); +// +// // 重写languages属性 +// Object.defineProperty(navigator, 'languages', { +// get: () => ['zh-CN', 'zh', 'en'], +// }); +// +// // 重写permissions查询 +// const originalQuery = window.navigator.permissions.query; +// window.navigator.permissions.query = (parameters) => ( +// parameters.name === 'notifications' ? +// Promise.resolve({ state: Notification.permission }) : +// originalQuery(parameters) +// ); +// +// // 隐藏Chrome automation扩展 +// window.chrome = { +// runtime: {}, +// }; +// +// // 重写Object.getOwnPropertyDescriptor +// const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +// Object.getOwnPropertyDescriptor = function(obj, prop) { +// if (prop === 'webdriver') { +// return undefined; +// } +// return getOwnPropertyDescriptor(obj, prop); +// }; +// """); + + // 启用JavaScript捕获控制台日志(用于调试) + PAGE.onConsoleMessage(message -> { + if (message.type().equals("error")) { + log.error("Browser console error: {}", message.text()); + } + }); + + log.info("Playwright和浏览器实例已初始化完成"); + } + + /** + * 关闭Playwright及浏览器实例 + */ + public static void close() { + if (PAGE != null) + PAGE.close(); + if (CONTEXT != null) + CONTEXT.close(); + if (BROWSER != null) + BROWSER.close(); + if (PLAYWRIGHT != null) + PLAYWRIGHT.close(); + + log.info("Playwright及浏览器实例已关闭"); + } + + + /** + * 等待指定时间(秒) + * + * @param seconds 等待的秒数 + */ + public static void sleep(int seconds) { + try { + TimeUnit.SECONDS.sleep(seconds); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Sleep被中断", e); + } + } + + /** + * 随机延迟等待(秒) + * + * @param minSeconds 最小等待秒数 + * @param maxSeconds 最大等待秒数 + */ + public static void randomSleep(int minSeconds, int maxSeconds) { + try { + Random random = new Random(); + int randomSeconds = random.nextInt(maxSeconds - minSeconds + 1) + minSeconds; + log.info("随机延迟{}秒", randomSeconds); + TimeUnit.SECONDS.sleep(randomSeconds); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("随机Sleep被中断", e); + } + } + + + + /** + * 查找元素并等待直到可见 + * + * @param selector 元素选择器 + * @param timeout 超时时间(毫秒) + * @return 元素对象,如果未找到则返回null + */ + public static Locator waitForElement(String selector, int timeout) { + Locator locator = PAGE.locator(selector); + locator.waitFor(new Locator.WaitForOptions().setTimeout(timeout)); + return locator; + } + + /** + * 使用默认超时时间等待元素 + * + * @param selector 元素选择器 + * @return 元素对象,如果未找到则返回null + */ + public static Locator waitForElement(String selector) { + return waitForElement(selector, DEFAULT_WAIT_TIME); + } + + /** + * 点击元素 + * + * @param selector 元素选择器 + */ + public static void click(String selector) { + try { + PAGE.locator(selector).click(); + log.info("已点击元素: {}", selector); + } catch (PlaywrightException e) { + log.error("点击元素失败: {}", selector, e); + } + } + + /** + * 填写表单字段 + * + * @param selector 元素选择器 + * @param text 要输入的文本 + */ + public static void fill(String selector, String text) { + try { + PAGE.locator(selector).fill(text); + log.info("已在元素{}中输入文本", selector); + } catch (PlaywrightException e) { + log.error("填写表单失败: {}", selector, e); + } + } + + /** + * 模拟人类输入文本(逐字输入) + * + * @param selector 元素选择器 + * @param text 要输入的文本 + * @param minDelay 字符间最小延迟(毫秒) + * @param maxDelay 字符间最大延迟(毫秒) + */ + public static void typeHumanLike(String selector, String text, int minDelay, int maxDelay) { + try { + Locator locator = PAGE.locator(selector); + + // 先模拟鼠标移动到元素 + simulateHumanMouseMove(selector); + + locator.click(); + + Random random = new Random(); + for (char c : text.toCharArray()) { + // 计算本次字符输入的延迟时间 + int delay = random.nextInt(maxDelay - minDelay + 1) + minDelay; + + // 输入单个字符 + locator.pressSequentially(String.valueOf(c), + new Locator.PressSequentiallyOptions().setDelay(delay)); + } + log.info("已模拟人类在元素{}中输入文本", selector); + } catch (PlaywrightException e) { + log.error("模拟人类输入失败: {}", selector, e); + } + } + + /** + * 模拟人类鼠标移动 + * + * @param selector 目标元素选择器 + */ + public static void simulateHumanMouseMove(String selector) { + try { + Locator locator = PAGE.locator(selector); + + // 获取元素边界框 + var boundingBox = locator.boundingBox(); + if (boundingBox != null) { + Random random = new Random(); + + // 在元素范围内随机选择一个点 + double targetX = boundingBox.x + random.nextDouble() * boundingBox.width; + double targetY = boundingBox.y + random.nextDouble() * boundingBox.height; + + // 模拟鼠标移动轨迹 + PAGE.mouse().move(targetX, targetY); + + // 添加随机短暂停留 + randomSleep(1, 2); + } + } catch (PlaywrightException e) { + log.error("模拟鼠标移动失败: {}", selector, e); + } + } + + /** + * 模拟人类点击行为(包含鼠标移动) + * + * @param selector 元素选择器 + */ + public static void clickHumanLike(String selector) { + try { + // 先模拟鼠标移动 + simulateHumanMouseMove(selector); + + // 随机延迟 + randomSleep(1, 3); + + // 点击元素 + PAGE.locator(selector).click(); + log.info("已模拟人类点击元素: {}", selector); + } catch (PlaywrightException e) { + log.error("模拟人类点击失败: {}", selector, e); + } + } + + /** + * 模拟人类滚动行为 + * + * @param scrollAmount 滚动量(像素) + * @param steps 滚动步数 + */ + public static void simulateHumanScroll(int scrollAmount, int steps) { + try { + Random random = new Random(); + int stepSize = scrollAmount / steps; + + for (int i = 0; i < steps; i++) { + // 随机化每步的滚动量 + int currentStep = stepSize + random.nextInt(20) - 10; + + PAGE.mouse().wheel(0, currentStep); + + // 随机延迟 + int delay = 100 + random.nextInt(200); + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + log.info("已模拟人类滚动行为"); + } catch (PlaywrightException e) { + log.error("模拟滚动失败", e); + } + } + + /** + * 随机模拟页面浏览行为 + */ + public static void simulateRandomBrowsing() { + try { + Random random = new Random(); + + // 随机滚动 + int scrollDirection = random.nextBoolean() ? 1 : -1; + int scrollAmount = 200 + random.nextInt(300); + simulateHumanScroll(scrollAmount * scrollDirection, 3 + random.nextInt(5)); + + // 随机暂停 + randomSleep(2, 5); + + // 随机移动鼠标到页面某处 + int pageWidth = 1920; // 假设页面宽度 + int pageHeight = 1080; // 假设页面高度 + double randomX = random.nextDouble() * pageWidth; + double randomY = random.nextDouble() * pageHeight; + PAGE.mouse().move(randomX, randomY); + + log.info("已模拟随机浏览行为"); + } catch (PlaywrightException e) { + log.error("模拟随机浏览失败", e); + } + } + + + /** + * 初始化Stealth模式(使浏览器更难被检测为自动化工具) + */ + @Deprecated + public static void initStealth() { + // 创建桌面设备上下文 + BrowserContext context = BROWSER.newContext(new Browser.NewContextOptions() + .setUserAgent( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36") + .setJavaScriptEnabled(true) + .setBypassCSP(true) + .setExtraHTTPHeaders(Map.of( + "sec-ch-ua", "\"Chromium\";v=\"135\", \"Not A(Brand\";v=\"99\"", + "sec-ch-ua-mobile", "?0", + "sec-ch-ua-platform", "\"Windows\"", + "sec-fetch-dest", "document", + "sec-fetch-mode", "navigate", + "sec-fetch-site", "same-origin", + "accept-language", "zh-CN,zh;q=0.9,en;q=0.8"))); + + // 更新全局上下文 + CONTEXT = context; + // 创建页面 + PAGE = CONTEXT.newPage(); + + // 执行stealth.min.js(需要事先准备此文件) + try { + String stealthJs = new String( + Files.readAllBytes(Paths.get( "/src/main/resources/stealth.min.js"))); + PAGE.addInitScript(stealthJs); + log.info("已启用Stealth模式"); + } catch (IOException e) { + log.error("启用Stealth模式失败,无法加载stealth.min.js", e); + } + } + + /** + * 获取Page对象 + * + * @return Page对象 + */ + public static Page getPageObject() { + return PAGE; + } + + /** + * 获取Browser对象 + * + * @return Browser对象 + */ + public static Browser getBrowser() { + return BROWSER; + } + + /** + * 获取BrowserContext对象 + * + * @return BrowserContext对象 + */ + public static BrowserContext getContext() { + return CONTEXT; + } + + /** + * 设置自定义Cookie + * + * @param name Cookie名称 + * @param value Cookie值 + * @param domain Cookie域 + * @param path Cookie路径 + * @param expires 过期时间(可选) + * @param secure 是否安全(可选) + * @param httpOnly 是否仅HTTP(可选) + */ + public static void setCookie(String name, String value, String domain, String path, + Double expires, Boolean secure, Boolean httpOnly) { + com.microsoft.playwright.options.Cookie cookie = new com.microsoft.playwright.options.Cookie(name, value); + cookie.domain = domain; + cookie.path = path; + + if (expires != null) { + cookie.expires = expires; + } + + if (secure != null) { + cookie.secure = secure; + } + + if (httpOnly != null) { + cookie.httpOnly = httpOnly; + } + + List cookies = new ArrayList<>(); + cookies.add(cookie); + + CONTEXT.addCookies(cookies); + log.info("已设置Cookie: {}", name); + } + +} diff --git a/src/main/java/getjobs/utils/SpringContextUtil.java b/src/main/java/getjobs/utils/SpringContextUtil.java new file mode 100644 index 00000000..b5ac6d51 --- /dev/null +++ b/src/main/java/getjobs/utils/SpringContextUtil.java @@ -0,0 +1,49 @@ +package getjobs.utils; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; + +/** + * Spring上下文工具类,用于在非Spring管理的类中获取Bean + */ +@Component +public class SpringContextUtil implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(@NonNull ApplicationContext context) throws BeansException { + applicationContext = context; + } + + /** + * 获取ApplicationContext + */ + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + /** + * 通过name获取Bean + */ + public static Object getBean(String name) { + return getApplicationContext().getBean(name); + } + + /** + * 通过class获取Bean + */ + public static T getBean(Class clazz) { + return getApplicationContext().getBean(clazz); + } + + /** + * 通过name和class获取Bean + */ + public static T getBean(String name, Class clazz) { + return getApplicationContext().getBean(name, clazz); + } +} diff --git a/src/main/java/getjobs/utils/ZhiLianDataConverter.java b/src/main/java/getjobs/utils/ZhiLianDataConverter.java new file mode 100644 index 00000000..901c566e --- /dev/null +++ b/src/main/java/getjobs/utils/ZhiLianDataConverter.java @@ -0,0 +1,319 @@ +package getjobs.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import getjobs.common.enums.RecruitmentPlatformEnum; +import getjobs.modules.zhilian.dto.ZhiLianApiResponse; +import getjobs.repository.entity.JobEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 智联招聘数据转换器 + * 负责将智联招聘API响应数据转换为JobEntity + * + * @author getjobs + * @since v2.1.1 + */ +@Slf4j +@Component +public class ZhiLianDataConverter { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 将智联招聘API响应中的职位数据转换为JobEntity + * + * @param jobItem 智联招聘API响应中的单个职位数据 + * @return JobEntity 职位实体 + */ + public JobEntity convertToJobEntity(ZhiLianApiResponse.ZhiLianJobItem jobItem) { + try { + JobEntity jobEntity = new JobEntity(); + + // 基础职位信息 + jobEntity.setJobTitle(jobItem.getName()); + jobEntity.setSalaryDesc(jobItem.getSalary60()); + jobEntity.setJobExperience(jobItem.getWorkingExp()); + jobEntity.setJobDegree(jobItem.getEducation()); + jobEntity.setJobDescription(jobItem.getJobSummary()); + jobEntity.setJobUrl(jobItem.getPositionUrl()); + jobEntity.setJobType(parseJobType(jobItem.getWorkType())); + + // 职位标签处理 - 使用技能标签 + List skillTags = null; + if (jobItem.getSkillLabel() != null && !jobItem.getSkillLabel().isEmpty()) { + skillTags = jobItem.getSkillLabel().stream() + .map(ZhiLianApiResponse.SkillLabel::getValue) + .filter(value -> value != null && !value.trim().isEmpty()) + .collect(Collectors.toList()); + } + + // 如果技能标签为空,尝试使用showSkillTags + if ((skillTags == null || skillTags.isEmpty()) && + jobItem.getShowSkillTags() != null && !jobItem.getShowSkillTags().isEmpty()) { + skillTags = jobItem.getShowSkillTags().stream() + .map(ZhiLianApiResponse.ShowSkillTag::getTag) + .filter(tag -> tag != null && !tag.trim().isEmpty()) + .collect(Collectors.toList()); + } + + if (skillTags != null && !skillTags.isEmpty()) { + jobEntity.setJobLabels(convertListToJson(skillTags)); + jobEntity.setSkills(convertListToJson(skillTags)); + } + + // 公司信息 + jobEntity.setCompanyName(jobItem.getCompanyName()); + jobEntity.setCompanyIndustry(jobItem.getIndustryName()); + jobEntity.setCompanyScale(jobItem.getCompanySize()); + jobEntity.setCompanyStage(jobItem.getPropertyName()); + jobEntity.setCompanyLogo(jobItem.getCompanyLogo()); + + // 工作地点信息 + jobEntity.setWorkCity(jobItem.getWorkCity()); + jobEntity.setWorkArea(jobItem.getCityDistrict()); + jobEntity.setBusinessDistrict(jobItem.getStreetName()); + + // HR信息 + if (jobItem.getStaffCard() != null) { + ZhiLianApiResponse.StaffCard staffCard = jobItem.getStaffCard(); + jobEntity.setHrName(staffCard.getStaffName()); + jobEntity.setHrTitle(staffCard.getHrJob()); + jobEntity.setHrAvatar(staffCard.getAvatar()); + if (staffCard.getHrOnlineState() != null && !staffCard.getHrOnlineState().isEmpty()) { + jobEntity.setHrOnline(true); + } + jobEntity.setHrActiveTime(staffCard.getHrStateInfo()); + } + + // 系统信息 + jobEntity.setPlatform(RecruitmentPlatformEnum.ZHILIAN_ZHAOPIN.getPlatformCode()); + jobEntity.setEncryptJobId(String.valueOf(jobItem.getJobId())); + jobEntity.setEncryptHrId(jobItem.getStaffCard() != null ? + String.valueOf(jobItem.getStaffCard().getId()) : null); + jobEntity.setEncryptCompanyId(String.valueOf(jobItem.getCompanyId())); + + // 设置公司ID + if (jobItem.getCompanyId() != null) { + jobEntity.setItemId(jobItem.getCompanyId().intValue()); + } + + // 状态信息 + jobEntity.setStatus(0); // 待处理状态 + jobEntity.setIsFavorite(false); + jobEntity.setIsContacted(false); + + // 福利信息 + List welfareList = jobItem.getWelfareTagList(); + if (welfareList == null || welfareList.isEmpty()) { + welfareList = jobItem.getJobKnowledgeWelfareFeatures(); + } + if (welfareList != null && !welfareList.isEmpty()) { + jobEntity.setWelfareList(convertListToJson(welfareList)); + } + + // 公司特性标签 + if (jobItem.getProperty() != null) { + jobEntity.setCompanyTag(jobItem.getProperty()); + } + + // 招聘人数 - 存储在备注字段或作为标签 + if (jobItem.getRecruitNumber() != null) { + String currentLabels = jobEntity.getJobLabels(); + String recruitLabel = "招聘" + jobItem.getRecruitNumber() + "人"; + if (currentLabels == null || currentLabels.isEmpty()) { + jobEntity.setJobLabels("[\"" + recruitLabel + "\"]"); + } else { + // 添加招聘人数标签到现有标签中 + currentLabels = currentLabels.replace("]", ",\"" + recruitLabel + "\"]"); + jobEntity.setJobLabels(currentLabels); + } + } + + // 职位分类 - 存储在jobPositionName字段 + if (jobItem.getSubJobTypeLevelName() != null) { + jobEntity.setJobPositionName(jobItem.getSubJobTypeLevelName()); + } + + // 薪资范围解析 + parseSalaryRange(jobItem, jobEntity); + + // 时间信息 - 存储在hrActiveTime字段 + if (jobItem.getPublishTime() != null) { + jobEntity.setHrActiveTime("发布时间: " + jobItem.getPublishTime()); + } + + // 融资阶段 + if (jobItem.getFinancingStage() != null && + jobItem.getFinancingStage().getName() != null) { + jobEntity.setCompanyStage(jobItem.getFinancingStage().getName()); + } + + // 匹配信息 - 存储在现有字段中 + if (jobItem.getMatchInfo() != null && jobItem.getMatchInfo().getMatched() != null) { + // 可以存储在某个整型字段或作为标签 + if (jobItem.getMatchInfo().getMatched() == 1) { + String currentLabels = jobEntity.getJobLabels(); + String matchLabel = "推荐匹配"; + if (currentLabels == null || currentLabels.isEmpty()) { + jobEntity.setJobLabels("[\"" + matchLabel + "\"]"); + } else { + currentLabels = currentLabels.replace("]", ",\"" + matchLabel + "\"]"); + jobEntity.setJobLabels(currentLabels); + } + } + } + + // 职位编号 - 存储在searchId字段 + if (jobItem.getNumber() != null) { + jobEntity.setSearchId(jobItem.getNumber()); + } + + return jobEntity; + + } catch (Exception e) { + log.error("转换智联招聘职位数据失败: {}", e.getMessage(), e); + return null; + } + } + + /** + * 解析薪资范围 + */ + private void parseSalaryRange(ZhiLianApiResponse.ZhiLianJobItem jobItem, JobEntity jobEntity) { + // 优先使用salary60 + if (jobItem.getSalary60() != null && !jobItem.getSalary60().trim().isEmpty()) { + jobEntity.setSalaryDesc(jobItem.getSalary60()); + } else if (jobItem.getSalaryReal() != null && !jobItem.getSalaryReal().trim().isEmpty()) { + // 解析salaryReal格式,例如 "18001-27000" + String salaryReal = jobItem.getSalaryReal(); + if (salaryReal.contains("-")) { + try { + String[] parts = salaryReal.split("-"); + if (parts.length == 2) { + int minSalary = Integer.parseInt(parts[0]); + int maxSalary = Integer.parseInt(parts[1]); + + // 转换为万元格式 + if (minSalary >= 10000) { + double minWan = minSalary / 10000.0; + double maxWan = maxSalary / 10000.0; + if (minWan == (int) minWan && maxWan == (int) maxWan) { + jobEntity.setSalaryDesc(String.format("%.0f-%.0f万", minWan, maxWan)); + } else { + jobEntity.setSalaryDesc(String.format("%.1f-%.1f万", minWan, maxWan)); + } + } else { + jobEntity.setSalaryDesc(String.format("%d-%d元", minSalary, maxSalary)); + } + } + } catch (NumberFormatException e) { + log.warn("薪资范围解析失败: {}", salaryReal); + jobEntity.setSalaryDesc(salaryReal); + } + } else { + jobEntity.setSalaryDesc(salaryReal); + } + } + } + + /** + * 解析职位类型 + */ + private Integer parseJobType(String workType) { + if (workType == null) { + return 0; // 默认全职 + } + + switch (workType) { + case "全职": + return 0; + case "兼职": + return 1; + case "实习": + return 2; + case "劳务": + return 3; + default: + return 0; + } + } + + /** + * 将字符串列表转换为JSON字符串 + */ + private String convertListToJson(List list) { + if (list == null || list.isEmpty()) { + return null; + } + try { + return objectMapper.writeValueAsString(list); + } catch (JsonProcessingException e) { + log.error("转换List到JSON失败: {}", e.getMessage()); + return null; + } + } + + /** + * 检查智联招聘职位数据是否有效 + * + * @param jobItem 职位数据 + * @return 是否有效 + */ + public boolean isValidJobData(ZhiLianApiResponse.ZhiLianJobItem jobItem) { + if (jobItem == null) { + return false; + } + + // 基础必填字段检查 + if (jobItem.getJobId() == null) { + log.warn("职位ID为空,跳过该职位"); + return false; + } + + if (jobItem.getName() == null || jobItem.getName().trim().isEmpty()) { + log.warn("职位名称为空,跳过该职位: {}", jobItem.getJobId()); + return false; + } + + if (jobItem.getCompanyName() == null || jobItem.getCompanyName().trim().isEmpty()) { + log.warn("公司名称为空,跳过该职位: {}", jobItem.getJobId()); + return false; + } + + // 检查薪资信息 + if ((jobItem.getSalary60() == null || jobItem.getSalary60().trim().isEmpty()) && + (jobItem.getSalaryReal() == null || jobItem.getSalaryReal().trim().isEmpty())) { + log.debug("薪资信息缺失,跳过该职位: {}", jobItem.getJobId()); + return false; + } + + // 检查工作地点信息 + if (jobItem.getWorkCity() == null || jobItem.getWorkCity().trim().isEmpty()) { + log.debug("工作地点信息缺失,跳过该职位: {}", jobItem.getJobId()); + return false; + } + + return true; + } + + /** + * 生成职位的唯一标识符 + * 用于去重判断 + * + * @param jobItem 职位数据 + * @return 唯一标识符 + */ + public String generateJobUniqueId(ZhiLianApiResponse.ZhiLianJobItem jobItem) { + if (jobItem == null || jobItem.getJobId() == null) { + return null; + } + + // 使用职位ID作为唯一标识 + return "zhilian_" + jobItem.getJobId(); + } +} diff --git a/src/main/java/job51/Job51.java b/src/main/java/job51/Job51.java deleted file mode 100644 index d69fff2c..00000000 --- a/src/main/java/job51/Job51.java +++ /dev/null @@ -1,238 +0,0 @@ -package job51; - -import lombok.SneakyThrows; -import org.openqa.selenium.*; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import utils.JobUtils; -import utils.SeleniumUtil; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import static utils.Bot.sendMessageByTime; -import static utils.Constant.*; -import static utils.JobUtils.formatDuration; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - * 前程无忧自动投递简历 - */ -public class Job51 { - static { - // 在类加载时就设置日志文件名,确保Logger初始化时能获取到正确的属性 - System.setProperty("log.name", "job51"); - } - - private static final Logger log = LoggerFactory.getLogger(Job51.class); - - static Integer page = 1; - static Integer maxPage = 50; - static String cookiePath = "./src/main/java/job51/cookie.json"; - static String homeUrl = "https://www.51job.com/"; - static String loginUrl = "https://login.51job.com/login.php?lang=c&url=https://www.51job.com/&qrlogin=2"; - static String baseUrl = "https://we.51job.com/pc/search?"; - static List resultList = new ArrayList<>(); - static Job51Config config = Job51Config.init(); - static Date startDate; - - public static void main(String[] args) { - String searchUrl = getSearchUrl(); - SeleniumUtil.initDriver(); - startDate = new Date(); - Login(); - config.getKeywords().forEach(keyword -> resume(searchUrl + "&keyword=" + keyword)); - printResult(); - } - - private static void printResult() { - String message = String.format("\n51job投递完成,共投递%d个简历,用时%s", resultList.size(), formatDuration(startDate, new Date())); - log.info(message); - sendMessageByTime(message); - resultList.clear(); - CHROME_DRIVER.close(); - CHROME_DRIVER.quit(); - - // 确保所有日志都被刷新到文件 - try { - Thread.sleep(1000); // 等待1秒确保日志写入完成 - // 强制刷新日志 - 使用正确的方法 - ch.qos.logback.classic.LoggerContext loggerContext = (ch.qos.logback.classic.LoggerContext) org.slf4j.LoggerFactory.getILoggerFactory(); - loggerContext.stop(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - private static String getSearchUrl() { - return baseUrl + - JobUtils.appendListParam("jobArea", config.getJobArea()) + - JobUtils.appendListParam("salary", config.getSalary()); - } - - private static void Login() { - CHROME_DRIVER.get(homeUrl); - if (SeleniumUtil.isCookieValid(cookiePath)) { - SeleniumUtil.loadCookie(cookiePath); - CHROME_DRIVER.navigate().refresh(); - SeleniumUtil.sleep(1); - } - if (isLoginRequired()) { - log.error("cookie失效,尝试扫码登录..."); - scanLogin(); - } - } - - private static boolean isLoginRequired() { - try { - String text = CHROME_DRIVER.findElement(By.cssSelector("span.login")).getText(); - return text != null && text.contains("登录"); - } catch (Exception e) { - log.info("cookie有效,已登录..."); - return false; - } - } - - @SneakyThrows - private static void resume(String url) { - CHROME_DRIVER.get(url); - SeleniumUtil.sleep(1); - - // 再次判断是否登录 - WebElement login = WAIT.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//a[contains(@class, 'uname')]"))); - if (login != null && isNotNullOrEmpty(login.getText()) && login.getText().contains("登录")) { - login.click(); - WAIT.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//i[contains(@class, 'passIcon')]"))).click(); - log.info("请扫码登录..."); - WAIT.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//div[contains(@class, 'joblist')]"))); - SeleniumUtil.saveCookie(cookiePath); - } - - //由于51更新,每投递一页之前,停止10秒 - SeleniumUtil.sleep(10); - - int i = 0; - try { - CHROME_DRIVER.findElements(By.className("ss")).get(i).click(); - } catch (Exception e) { - findAnomaly(); - } - for (int j = page; j <= maxPage; j++) { - while (true) { - try { - WebElement mytxt = WAIT.until(ExpectedConditions.visibilityOfElementLocated(By.id("jump_page"))); - SeleniumUtil.sleep(5); - mytxt.click(); - mytxt.clear(); - mytxt.sendKeys(String.valueOf(j)); - WAIT.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("#app > div > div.post > div > div > div.j_result > div > div:nth-child(2) > div > div.bottom-page > div > div > span.jumpPage"))).click(); - ACTIONS.keyDown(Keys.CONTROL).sendKeys(Keys.HOME).keyUp(Keys.CONTROL).perform(); - log.info("第 {} 页", j); - break; - } catch (Exception e) { - log.error("mytxt.clear()可能异常..."); - SeleniumUtil.sleep(1); - findAnomaly(); - CHROME_DRIVER.navigate().refresh(); - } - } - postCurrentJob(); - } - } - - public static boolean isNullOrEmpty(String str) { - return str == null || str.isBlank(); - } - - public static boolean isNotNullOrEmpty(String str) { - return !isNullOrEmpty(str); - } - - - @SneakyThrows - private static void postCurrentJob() { - SeleniumUtil.sleep(1); - // 选择所有岗位,批量投递 - List checkboxes = CHROME_DRIVER.findElements(By.cssSelector("div.ick")); - if (checkboxes.isEmpty()) { - return; - } - List titles = CHROME_DRIVER.findElements(By.cssSelector("[class*='jname text-cut']")); - List companies = CHROME_DRIVER.findElements(By.cssSelector("[class*='cname text-cut']")); - JavascriptExecutor executor = CHROME_DRIVER; - for (int i = 0; i < checkboxes.size(); i++) { - WebElement checkbox = checkboxes.get(i); - executor.executeScript("arguments[0].click();", checkbox); - String title = titles.get(i).getText(); - String company = companies.get(i).getText(); - resultList.add(company + " | " + title); - log.info("选中:{} | {} 职位", company, title); - } - SeleniumUtil.sleep(1); - ACTIONS.keyDown(Keys.CONTROL).sendKeys(Keys.HOME).keyUp(Keys.CONTROL).perform(); - boolean success = false; - while (!success) { - try { - // 查询按钮是否存在 - WebElement parent = CHROME_DRIVER.findElement(By.cssSelector("div.tabs_in")); - List button = parent.findElements(By.cssSelector("button.p_but")); - // 如果按钮存在,则点击 - if (button != null && !button.isEmpty()) { - SeleniumUtil.sleep(1); - button.get(1).click(); - success = true; - } - } catch (ElementClickInterceptedException e) { - log.error("失败,1s后重试.."); - SeleniumUtil.sleep(1); - } - } - - try { - SeleniumUtil.sleep(3); - String text = CHROME_DRIVER.findElement(By.xpath("//div[@class='successContent']")).getText(); - if (text.contains("快来扫码下载~")) { - //关闭弹窗 - CHROME_DRIVER.findElement(By.cssSelector("[class*='van-icon van-icon-cross van-popup__close-icon van-popup__close-icon--top-right']")).click(); - } - } catch (Exception ignored) { - log.info("未找到投递成功弹窗!可能为单独投递申请弹窗!"); - } - String particularly = null; - try { - particularly = CHROME_DRIVER.findElement(By.xpath("//div[@class='el-dialog__body']/span")).getText(); - } catch (Exception ignored) { - } - if (particularly != null && particularly.contains("需要到企业招聘平台单独申请")) { - //关闭弹窗 - CHROME_DRIVER.findElement(By.cssSelector("#app > div > div.post > div > div > div.j_result > div > div:nth-child(2) > div > div:nth-child(2) > div:nth-child(2) > div > div.el-dialog__header > button > i")).click(); - log.info("关闭单独投递申请弹窗成功!"); - } - } - - private static void findAnomaly() { - try { - String verify = CHROME_DRIVER.findElement(By.xpath("//p[@class='waf-nc-title']")).getText(); - if (verify.contains("验证")) { - //关闭弹窗 - log.error("出现访问验证了!程序退出..."); - printResult(); - CHROME_DRIVER.close(); - CHROME_DRIVER.quit(); - } - } catch (Exception ignored) { - log.info("未出现访问验证,继续运行..."); - } - } - - private static void scanLogin() { - log.info("等待扫码登陆.."); - CHROME_DRIVER.get(loginUrl); - WAIT.until(ExpectedConditions.presenceOfElementLocated(By.xpath("//a[contains(text(), '在线简历')]"))); - SeleniumUtil.saveCookie(cookiePath); - } - -} diff --git a/src/main/java/job51/Job51Config.java b/src/main/java/job51/Job51Config.java deleted file mode 100644 index 83389a56..00000000 --- a/src/main/java/job51/Job51Config.java +++ /dev/null @@ -1,45 +0,0 @@ -package job51; - -import lombok.Data; -import lombok.SneakyThrows; -import utils.JobUtils; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - * 前程无忧自动投递简历 - */ -@Data -public class Job51Config { - - - /** - * 搜索关键词列表 - */ - private List keywords; - - /** - * 城市编码 - */ - private List jobArea; - - /** - * 薪资范围 - */ - private List salary; - - - @SneakyThrows - public static Job51Config init() { - Job51Config config = JobUtils.getConfig(Job51Config.class); - // 转换城市编码 - config.setJobArea(config.getJobArea().stream().map(value -> Job51Enum.jobArea.forValue(value).getCode()).collect(Collectors.toList())); - // 转换薪资范围 - config.setSalary(config.getSalary().stream().map(value -> Job51Enum.Salary.forValue(value).getCode()).collect(Collectors.toList())); - return config; - } - -} diff --git a/src/main/java/job51/Job51Enum.java b/src/main/java/job51/Job51Enum.java deleted file mode 100644 index 97a28ebc..00000000 --- a/src/main/java/job51/Job51Enum.java +++ /dev/null @@ -1,77 +0,0 @@ -package job51; - -import com.fasterxml.jackson.annotation.JsonCreator; -import lombok.Getter; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - * 前程无忧自动投递简历 - */ -public class Job51Enum { - - @Getter - public enum jobArea { - NULL("不限", "0"), - BEIJING("北京", "010000"), - SHANGHAI("上海", "020000"), - GUANGZHOU("广州", "030200"), - SHENZHEN("深圳", "040000"), - WUHAN("武汉", "180200"), - CHENGDU("成都", "090200"); - private final String name; - private final String code; - - jobArea(String name, String code) { - this.name = name; - this.code = code; - } - - @JsonCreator - public static jobArea forValue(String value) { - for (jobArea cityCode : jobArea.values()) { - if (cityCode.name.equals(value)) { - return cityCode; - } - } - return NULL; - } - - } - - @Getter - public enum Salary { - NULL("不限", "0"), - BELOW_2K("2千以下", "01"), - FROM_2K_TO_3K("2-3千", "02"), - FROM_3K_TO_4_5K("3-4.5千", "03"), - FROM_4_5K_TO_6K("4.5-6千", "04"), - FROM_6K_TO_8K("6-8千", "05"), - FROM_8K_TO_10K("0.8-1万", "06"), - FROM_10K_TO_15K("1-1.5万", "07"), - FROM_15K_TO_20K("1.5-2万", "08"), - FROM_20K_TO_30K("2-3万", "09"), - FROM_30K_TO_40K("3-4万", "10"), - FROM_40K_TO_50K("4-5万", "11"), - ABOVE_50K("5万以上", "12"); - - private final String name; - private final String code; - - Salary(String name, String code) { - this.name = name; - this.code = code; - } - - @JsonCreator - public static Salary forValue(String value) { - for (Salary salary : Salary.values()) { - if (salary.name.equals(value)) { - return salary; - } - } - return NULL; - } - } - -} diff --git a/src/main/java/job51/Job51Scheduled.java b/src/main/java/job51/Job51Scheduled.java deleted file mode 100644 index 394fc9df..00000000 --- a/src/main/java/job51/Job51Scheduled.java +++ /dev/null @@ -1,31 +0,0 @@ -package job51; - -import lombok.extern.slf4j.Slf4j; -import utils.JobUtils; -import utils.Platform; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - * 前程无忧自动投递简历 - */ -@Slf4j -public class Job51Scheduled { - - public static void main(String[] args) { - JobUtils.runScheduled(Platform.JOB51); - } - - public static void postJobs() { - safeRun(() -> Job51.main(null)); - } - - // 任务执行的安全包装,防止异常 - private static void safeRun(Runnable task) { - try { - task.run(); - } catch (Exception e) { - log.error("safeRun异常:{}", e.getMessage(), e); - } - } -} diff --git a/src/main/java/lagou/Lagou.java b/src/main/java/lagou/Lagou.java deleted file mode 100644 index b46dc5d1..00000000 --- a/src/main/java/lagou/Lagou.java +++ /dev/null @@ -1,387 +0,0 @@ -package lagou; - -import lombok.SneakyThrows; -import org.openqa.selenium.By; -import org.openqa.selenium.Keys; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import utils.JobUtils; -import utils.SeleniumUtil; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import static utils.Bot.sendMessageByTime; -import static utils.Constant.*; -import static utils.JobUtils.formatDuration; -import static utils.SeleniumUtil.isCookieValid; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -public class Lagou { - static { - // 在类加载时就设置日志文件名,确保Logger初始化时能获取到正确的属性 - System.setProperty("log.name", "lagou"); - } - - private static final Logger log = LoggerFactory.getLogger(Lagou.class); - - static Integer page = 1; - static Integer maxPage = 4; - static String homeUrl = "https://www.lagou.com/?"; - static String wechatUrl = "https://open.weixin.qq.com/connect/qrconnect?appid=wx9d8d3686b76baff8&redirect_uri=https%3A%2F%2Fpassport.lagou.com%2Foauth20%2Fcallback_weixinProvider.html&response_type=code&scope=snsapi_login#wechat_redirect"; - static int oneKeyMaxJob = 20; - static int currentKeyJobNum = 0; - static int jobCount = 0; - static String cookiePath = "./src/main/java/lagou/cookie.json"; - static LagouConfig config = LagouConfig.init(); - static Date startDate; - - - public static void main(String[] args) { - SeleniumUtil.initDriver(); - startDate = new Date(); - login(); - CHROME_DRIVER.get(homeUrl); - homeUrl = "https://www.lagou.com/wn/zhaopin?fromSearch=true"; - config.getKeywords().forEach(keyword -> { - String searchUrl = getSearchUrl(keyword); - CHROME_DRIVER.get(searchUrl); - setMaxPage(); - for (int i = page; i <= maxPage || currentKeyJobNum > oneKeyMaxJob; i++) { - submit(); - try { - getWindow(); - CHROME_DRIVER.findElements(By.className("lg-pagination-item-link")).get(1).click(); - } catch (Exception e) { - break; - } - } - currentKeyJobNum = 0; - }); - printResult(); - } - - private static void printResult() { - String message = String.format("\n拉勾投递完成,共投递%d个岗位,用时%s", jobCount, formatDuration(startDate, new Date())); - log.info(message); - sendMessageByTime(message); - jobCount = 0; - CHROME_DRIVER.close(); - CHROME_DRIVER.quit(); - - // 确保所有日志都被刷新到文件 - try { - Thread.sleep(1000); // 等待1秒确保日志写入完成 - // 强制刷新日志 - 使用正确的方法 - ch.qos.logback.classic.LoggerContext loggerContext = (ch.qos.logback.classic.LoggerContext) org.slf4j.LoggerFactory.getILoggerFactory(); - loggerContext.stop(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - private static String getSearchUrl(String keyword) { - return homeUrl + - JobUtils.appendParam("city", config.getCityCode()) + - JobUtils.appendParam("kd", keyword) + - JobUtils.appendParam("yx", config.getSalary()) + - JobUtils.appendParam("gj", config.getGj()) + - JobUtils.appendListParam("gm", config.getScale()); - } - - /** - * 设置选项 - */ - private static void setMaxPage() { - // 模拟 Ctrl + End - ACTIONS.keyDown(Keys.CONTROL).sendKeys(Keys.END).keyUp(Keys.CONTROL).perform(); - WebElement secondLastLi = CHROME_DRIVER.findElement(By.xpath("(//ul[@class='lg-pagination']/li)[last()-1]")); - if (secondLastLi != null && secondLastLi.getText().matches("\\d+")) { - maxPage = Integer.parseInt(secondLastLi.getText()); - } - // 模拟 Ctrl + Home - ACTIONS.keyDown(Keys.CONTROL).sendKeys(Keys.HOME).keyUp(Keys.CONTROL).perform(); - } - - @SneakyThrows - private static void submit() { - // 获取所有的元素 - List elements = null; - try { - ACTIONS.sendKeys(Keys.HOME).perform(); - SeleniumUtil.sleep(1); - WAIT.until(ExpectedConditions.presenceOfElementLocated(By.id("openWinPostion"))); - elements = CHROME_DRIVER.findElements(By.id("openWinPostion")); - - } catch (Exception ignore) { - } - if (elements != null) { - for (int i = 0; i < elements.size() || currentKeyJobNum > oneKeyMaxJob; i++) { - WebElement element = null; - try { - element = elements.get(i); - } catch (Exception e) { - log.error("获取岗位列表中某个岗位失败,岗位列表数量:{},获取第【{}】个元素失败", i + 1, elements.size()); - } - try { - ACTIONS.moveToElement(element).perform(); - } catch (Exception e) { - getWindow(); - } - if (-1 == tryClick(element, i)) { - continue; - } - TimeUnit.SECONDS.sleep(1); - getWindow(); - String jobName; - WebElement submit; - try { - jobName = CHROME_DRIVER.findElement(By.className("header__HY1Cm")).getText(); - } catch (Exception e) { - try { - jobName = CHROME_DRIVER.findElement(By.className("position-head-wrap-position-name")).getText(); - } catch (Exception ex) { - SeleniumUtil.sleep(10); - continue; - } - - } - if (!(jobName != null && !jobName.isEmpty() && !jobName.contains("销"))) { - CHROME_DRIVER.close(); - getWindow(); - continue; - } - submit = CHROME_DRIVER.findElement(By.className("resume-deliver")); - if ("投简历".equals(submit.getText())) { - String jobTitle = null; - String companyName = null; - String jobInfo = null; - String companyInfo = null; - String salary = null; - String weal = null; - try { - jobTitle = CHROME_DRIVER.findElement(By.cssSelector("span.name__36WTQ")).getText(); - companyName = CHROME_DRIVER.findElement(By.cssSelector("span.company")).getText(); - jobInfo = CHROME_DRIVER.findElements(By.cssSelector("h3.position-tags span")) - .stream() - .map(WebElement::getText) - .collect(Collectors.joining("/")); - companyInfo = CHROME_DRIVER.findElement(By.cssSelector("div.header__HY1Cm")).getText(); - salary = CHROME_DRIVER.findElement(By.cssSelector("span.salary__22Kt_")).getText(); - weal = CHROME_DRIVER.findElement(By.cssSelector("li.labels")).getText(); - } catch (Exception e) { - log.error("获取职位信息失败", e); - try { - jobTitle = CHROME_DRIVER.findElement(By.cssSelector("span.position-head-wrap-position-name")).getText(); - companyName = CHROME_DRIVER.findElement(By.cssSelector("span.company")).getText(); - List jobInfoElements = CHROME_DRIVER.findElements(By.cssSelector("h3.position-tags span:not(.tag-point)")); - jobInfo = jobInfoElements.stream() - .map(WebElement::getText) - .collect(Collectors.joining("/")); - companyInfo = CHROME_DRIVER.findElement(By.cssSelector("span.company")).getText(); - salary = CHROME_DRIVER.findElement(By.cssSelector("span.salary")).getText(); - weal = CHROME_DRIVER.findElement(By.cssSelector("dd.job-advantage p")).getText(); - } catch (Exception ex) { - log.error("第二次获取职位信息失败,放弃了!", ex); - } - } - log.info("投递: {},职位: {},公司: {},职位信息: {},公司信息: {},薪资: {},福利: {}", jobTitle, jobTitle, companyName, jobInfo, companyInfo, salary, weal); - jobCount++; - currentKeyJobNum++; - TimeUnit.SECONDS.sleep(2); - submit.click(); - TimeUnit.SECONDS.sleep(2); - try { - WebElement send = CHROME_DRIVER.findElement(By.cssSelector("body > div:nth-child(45) > div > div.lg-design-modal-wrap.position-modal > div > div.lg-design-modal-content > div.lg-design-modal-footer > button.lg-design-btn.lg-design-btn-default")); - if ("确认投递".equals(send.getText())) { - send.click(); - } - } catch (Exception e) { - log.error("没有【确认投递】的弹窗,继续!"); - } - try { - WebElement confirm = CHROME_DRIVER.findElement(By.cssSelector("button.lg-design-btn.lg-design-btn-primary span")); - String buttonText = confirm.getText(); - if ("我知道了".equals(buttonText)) { - confirm.click(); - } else { - TimeUnit.SECONDS.sleep(1); - } - } catch (Exception e) { - log.error("第一次点击【我知道了】按钮失败...重试xpath点击..."); - TimeUnit.SECONDS.sleep(1); - try { - CHROME_DRIVER.findElement(By.xpath("/html/body/div[7]/div/div[2]/div/div[2]/div[2]/button[2]")).click(); - } catch (Exception ex) { - log.error("第二次点击【我知道了】按钮失败...放弃了!", ex); - TimeUnit.SECONDS.sleep(10); - CHROME_DRIVER.navigate().refresh(); - } - } - try { - TimeUnit.SECONDS.sleep(2); - CHROME_DRIVER.findElement(By.cssSelector("#__next > div:nth-child(3) > div > div > div.feedback_job__3EnWp > div.feedback_job_title__2y8Bj > div.feedback_job_deliver__3UIB5.feedback_job_active__3bbLa")).click(); - } catch (Exception e) { - log.error("这个岗位没有推荐职位..."); - TimeUnit.SECONDS.sleep(1); - } - } else if ("立即沟通".equals(submit.getText())) { - submit.click(); - try { - WAIT.until(ExpectedConditions.presenceOfElementLocated(By.xpath("//*[@id=\"modalConIm\"]"))).click(); - } catch (Exception e) { - submit.click(); - WAIT.until(ExpectedConditions.presenceOfElementLocated(By.xpath("//*[@id=\"modalConIm\"]"))).click(); - } - } else { - log.info("这个岗位没有投简历按钮...一秒后关闭标签页面!"); - TimeUnit.SECONDS.sleep(1); - } - CHROME_DRIVER.close(); - getWindow(); - } - } - } - - private static void getWindow() { - try { - ArrayList tabs = new ArrayList<>(CHROME_DRIVER.getWindowHandles()); - if (tabs.size() > 1) { - CHROME_DRIVER.switchTo().window(tabs.get(1)); - } else { - CHROME_DRIVER.switchTo().window(tabs.get(0)); - } - } catch (Exception ignore) { - } - } - - private static int tryClick(WebElement element, int i) { - boolean isClicked = false; - int maxRetryCount = 5; - int retryCount = 0; - - try { - element.click(); - isClicked = true; - } catch (Exception e) { - try { - CHROME_DRIVER.findElements(By.id("openWinPostion")).get(i).click(); - isClicked = true; - } catch (Exception ex) { - log.info(ex.getMessage()); - } - } - return 0; - - /* - while (!isClicked && retryCount < maxRetryCount) { - try { - element.click(); - isClicked = true; - } catch (Exception e) { - retryCount++; - log.error("element.click() 点击失败,正在尝试重新点击...(正在尝试:第 {} 次)", retryCount); - TimeUnit.SECONDS.sleep(5); - - try { - CHROME_DRIVER.findElements(By.id("openWinPostion")).get(i).click(); - isClicked = true; - } catch (Exception ex) { - log.error(" get(i).click() 重试失败,尝试使用Actions点击...(正在尝试:第 {} 次)", retryCount); - TimeUnit.SECONDS.sleep(5); - try { - ACTIONS.keyDown(Keys.CONTROL).click(element).keyUp(Keys.CONTROL).build().perform(); - isClicked = true; - } catch (Exception exc) { - log.error("使用Actions点击也失败,等待10秒后再次尝试...(正在尝试:第 {} 次)", retryCount); - TimeUnit.SECONDS.sleep(10); - } - } - } - } - if (!isClicked) { - log.error("已尝试 {} 次,已达最大重试次数,少侠请重新来过!", maxRetryCount); - log.info("已投递 {} 次,正在退出...", jobCount); - CHROME_DRIVER.quit(); - return -1; - } else { - return 0; - } - */ - } - - @SneakyThrows - private static void newTab(int index) { - String windowHandle = CHROME_DRIVER.getWindowHandle(); - String company = CHROME_DRIVER.findElement(By.cssSelector(".company-name__2-SjF a")).getText(); - - String jobTitle = CHROME_DRIVER.findElement(By.cssSelector(".p-top__1F7CL a")).getText(); - CHROME_DRIVER.findElements(By.id("openWinPostion")).get(index).click(); - WAIT.until(ExpectedConditions.presenceOfElementLocated(By.className("resume-deliver"))); - - Set windowHandles = CHROME_DRIVER.getWindowHandles(); - windowHandles.remove(windowHandle); - String newWindowHandle = windowHandles.iterator().next(); - CHROME_DRIVER.switchTo().window(newWindowHandle); - WAIT.until(ExpectedConditions.presenceOfElementLocated(By.className("resume-deliver"))); - - if (!"已投递".equals(CHROME_DRIVER.findElements(By.className("resume-deliver")).get(0).getText())) { - CHROME_DRIVER.findElements(By.className("resume-deliver")).get(0).click(); - TimeUnit.SECONDS.sleep(1); - WAIT.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("button.lg-design-btn.lg-design-btn-primary"))).click(); - log.info("投递【{}】公司: 【{}】岗位", company, jobTitle); - } - CHROME_DRIVER.close(); - CHROME_DRIVER.switchTo().window(windowHandle); - } - - @SneakyThrows - private static void login() { - log.info("正在打开拉勾..."); - CHROME_DRIVER.get("https://www.lagou.com/"); - log.info("拉勾正在登录..."); - if (isCookieValid(cookiePath)) { - SeleniumUtil.loadCookie(cookiePath); - CHROME_DRIVER.navigate().refresh(); - } - WAIT.until(ExpectedConditions.presenceOfElementLocated(By.id("search_button"))); - if (isLoginRequired()) { - log.info("cookie失效,尝试扫码登录..."); - scanLogin(); - SeleniumUtil.saveCookie(cookiePath); - } else { - log.info("cookie有效,准备投递..."); - } - } - - private static boolean isLoginRequired() { - try { - WebElement header = CHROME_DRIVER.findElement(By.id("lg_tbar")); - return header.getText().contains("登录"); - } catch (Exception e) { - return true; - } - } - - private static void scanLogin() { - try { - CHROME_DRIVER.get(wechatUrl); - log.info("等待扫码.."); - WAIT.until(ExpectedConditions.elementToBeClickable(By.id("search_button"))); - } catch (Exception e) { - CHROME_DRIVER.navigate().refresh(); - } - - } - - -} diff --git a/src/main/java/lagou/LagouConfig.java b/src/main/java/lagou/LagouConfig.java deleted file mode 100644 index a7c5ecb4..00000000 --- a/src/main/java/lagou/LagouConfig.java +++ /dev/null @@ -1,52 +0,0 @@ -package lagou; - -import lombok.Data; -import lombok.SneakyThrows; -import utils.JobUtils; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -@Data -public class LagouConfig { - /** - * 搜索关键词列表 - */ - private List keywords; - - /** - * 城市编码 - */ - private String cityCode; - - /** - * 薪资范围 - */ - private String salary; - - /** - * 公司规模 - */ - private List scale; - - /** - * 工作年限 - */ - private String gj; - - @SneakyThrows - public static LagouConfig init() { - LagouConfig config = JobUtils.getConfig(LagouConfig.class); - // 转换城市编码 - config.setSalary(Objects.equals("不限", config.getSalary()) ? "0" : config.getSalary()); - List scales = config.getScale(); - config.setScale(scales.stream().map(scale -> "不限".equals(scale) ? "0" : scale).collect(Collectors.toList())); - return config; - } - -} diff --git a/src/main/java/lagou/LagouEnum.java b/src/main/java/lagou/LagouEnum.java deleted file mode 100644 index 03ee6565..00000000 --- a/src/main/java/lagou/LagouEnum.java +++ /dev/null @@ -1,36 +0,0 @@ -package lagou; - -import com.fasterxml.jackson.annotation.JsonCreator; -import lombok.Getter; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -public class LagouEnum { - - @Getter - public enum CityCode { - NULL("不限", "0"), - ALL("全国", "0"); - - private final String name; - private final String code; - - CityCode(String name, String code) { - this.name = name; - this.code = code; - } - - @JsonCreator - public static CityCode forValue(String value) { - for (CityCode cityCode : CityCode.values()) { - if (cityCode.name.equals(value)) { - return cityCode; - } - } - return CityCode.valueOf(value); - } - } - -} diff --git a/src/main/java/lagou/LagouScheduled.java b/src/main/java/lagou/LagouScheduled.java deleted file mode 100644 index f90a8856..00000000 --- a/src/main/java/lagou/LagouScheduled.java +++ /dev/null @@ -1,30 +0,0 @@ -package lagou; - -import lombok.extern.slf4j.Slf4j; -import utils.JobUtils; -import utils.Platform; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -@Slf4j -public class LagouScheduled { - - public static void main(String[] args) { - JobUtils.runScheduled(Platform.LAGOU); - } - - public static void postJobs() { - safeRun(() -> Lagou.main(null)); - } - - // 任务执行的安全包装,防止异常 - private static void safeRun(Runnable task) { - try { - task.run(); - } catch (Exception e) { - log.error("safeRun异常:{}", e.getMessage(), e); - } - } -} diff --git a/src/main/java/liepin/Liepin.java b/src/main/java/liepin/Liepin.java deleted file mode 100644 index abe29ebe..00000000 --- a/src/main/java/liepin/Liepin.java +++ /dev/null @@ -1,531 +0,0 @@ -package liepin; - -import com.microsoft.playwright.Locator; -import com.microsoft.playwright.Page; -import com.microsoft.playwright.options.LoadState; -import lombok.SneakyThrows; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.util.StringUtils; -import utils.JobUtils; -import utils.PlaywrightUtil; -import utils.SeleniumUtil; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.*; - -import static liepin.Locators.*; -import static utils.Bot.sendMessageByTime; -import static utils.JobUtils.formatDuration; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -public class Liepin { - static { - // 在类加载时就设置日志文件名,确保Logger初始化时能获取到正确的属性 - System.setProperty("log.name", "liepin"); - } - - private static final Logger log = LoggerFactory.getLogger(Liepin.class); - static String homeUrl = "https://www.liepin.com/"; - static String cookiePath = "./src/main/java/liepin/cookie.json"; - static int maxPage = 50; - static List resultList = new ArrayList<>(); - static String baseUrl = "https://www.liepin.com/zhaopin/?"; - static LiepinConfig config = LiepinConfig.init(); - static Date startDate; - - /** - * 保存页面源码到日志和文件,用于调试 - */ - private static void savePageSource(Page page, String context) { - try { - String pageSource = page.content(); - String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")); - - // 保存完整源码到文件 - Path sourceDir = Paths.get("./target/logs/page_sources"); - Files.createDirectories(sourceDir); - - String fileName = String.format("liepin_page_%s_%s.html", context.replaceAll("[^a-zA-Z0-9]", "_"), timestamp); - Path sourceFile = sourceDir.resolve(fileName); - Files.write(sourceFile, pageSource.getBytes("UTF-8")); - - log.info("完整页面源码已保存到文件: {}", sourceFile.toAbsolutePath()); - - } catch (IOException e) { - log.error("保存页面源码失败: {}", e.getMessage()); - } - } - - - - public static void main(String[] args) { - PlaywrightUtil.init(); - startDate = new Date(); - login(); - for (String keyword : config.getKeywords()) { - submit(keyword); - } - printResult(); - } - - private static void printResult() { - String message = String.format("\n猎聘投递完成,共投递%d个岗位,用时%s", resultList.size(), formatDuration(startDate, new Date())); - log.info(message); - sendMessageByTime(message); - resultList.clear(); - PlaywrightUtil.close(); - - // 确保所有日志都被刷新到文件 - try { - Thread.sleep(1000); // 等待1秒确保日志写入完成 - // 强制刷新日志 - 使用正确的方法 - ch.qos.logback.classic.LoggerContext loggerContext = (ch.qos.logback.classic.LoggerContext) org.slf4j.LoggerFactory.getILoggerFactory(); - loggerContext.stop(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - - @SneakyThrows - private static void submit(String keyword) { - Page page = PlaywrightUtil.getPageObject(); - page.navigate(getSearchUrl() + "&key=" + keyword); - - // 等待分页元素加载 - page.waitForSelector(PAGINATION_BOX, new Page.WaitForSelectorOptions().setTimeout(10000)); - Locator paginationBox = page.locator(PAGINATION_BOX); - Locator lis = paginationBox.locator("li"); - setMaxPage(lis); - - for (int i = 0; i < maxPage; i++) { - try { - // 尝试关闭订阅弹窗 - Locator closeBtn = page.locator(SUBSCRIBE_CLOSE_BTN); - if (closeBtn.count() > 0) { - closeBtn.click(); - } - } catch (Exception ignored) { - } - - // 等待岗位卡片加载 - page.waitForSelector(JOB_CARDS, new Page.WaitForSelectorOptions().setTimeout(10000)); - log.info("正在投递【{}】第【{}】页...", keyword, i + 1); - submitJob(); - log.info("已投递第【{}】页所有的岗位...\n", i + 1); - - // 查找下一页按钮 - paginationBox = page.locator(PAGINATION_BOX); - Locator nextPage = paginationBox.locator(NEXT_PAGE); - if (nextPage.count() > 0 && nextPage.getAttribute("disabled") == null) { - nextPage.click(); - // PlaywrightUtil.sleep(1); // 休息一秒 - } else { - break; - } - } - log.info("【{}】关键词投递完成!", keyword); - } - - private static String getSearchUrl() { - return baseUrl + - JobUtils.appendParam("city", config.getCityCode()) + - JobUtils.appendParam("salary", config.getSalary()) + - JobUtils.appendParam("pubTime", config.getPubTime()) + - "¤tPage=" + 0 + "&dq=" + config.getCityCode(); - } - - - private static void setMaxPage(Locator lis) { - try { - int count = lis.count(); - if (count >= 2) { - String pageText = lis.nth(count - 2).textContent(); - int page = Integer.parseInt(pageText); - if (page > 1) { - maxPage = page; - } - } - } catch (Exception ignored) { - } - } - - private static void submitJob() { - Page page = PlaywrightUtil.getPageObject(); - - // 等待页面完全加载 - // try { - // page.waitForLoadState(LoadState.NETWORKIDLE, new Page.WaitForLoadStateOptions().setTimeout(10000)); - // } catch (Exception e) { - // log.warn("等待页面网络空闲超时,继续执行: {}", e.getMessage()); - // } - - // 获取hr数量 - Locator jobCards = page.locator(JOB_CARDS); - - // 等待岗位卡片加载完成 - // try { - // jobCards.first().waitFor(new Locator.WaitForOptions().setTimeout(10000)); - // } catch (Exception e) { - // log.warn("等待岗位卡片加载超时: {}", e.getMessage()); - // } - - int count = jobCards.count(); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < count; i++) { - - Locator jobTitleElements = page.locator(JOB_TITLE); - Locator companyNameElements = page.locator(COMPANY_NAME); - Locator salaryElements = page.locator(JOB_SALARY); - - if (i >= jobTitleElements.count() || i >= companyNameElements.count() || i >= salaryElements.count()) { - continue; - } - - String jobName = jobTitleElements.nth(i).textContent().replaceAll("\n", " ").replaceAll("【 ", "[").replaceAll(" 】", "]"); - String companyName = companyNameElements.nth(i).textContent().replaceAll("\n", " "); - String salary = salaryElements.nth(i).textContent().replaceAll("\n", " "); - String recruiterName = null; - - try { - // 获取当前岗位卡片 - Locator currentJobCard = page.locator(JOB_CARDS).nth(i); - - // 使用JavaScript滚动到卡片位置,更稳定 - try { - // 先滚动到卡片位置 - page.evaluate("(element) => element.scrollIntoView({behavior: 'instant', block: 'center'})", currentJobCard.elementHandle()); - // PlaywrightUtil.sleep(1); // 等待滚动完成 - - // 再次确保元素在视窗中 - page.evaluate("(element) => { const rect = element.getBoundingClientRect(); if (rect.top < 0 || rect.bottom > window.innerHeight) { element.scrollIntoView({behavior: 'instant', block: 'center'}); } }", currentJobCard.elementHandle()); - // PlaywrightUtil.sleep(1); - } catch (Exception scrollError) { - log.warn("JavaScript滚动失败,尝试页面滚动: {}", scrollError.getMessage()); - // 备用方案:滚动页面到大概位置 - page.evaluate("window.scrollBy(0, " + (i * 200) + ")"); - // PlaywrightUtil.sleep(1); - } - - // 查找HR区域 - 尝试多种可能的HR标签选择器 - Locator hrArea = null; - String[] hrSelectors = { - ".recruiter-info-box", // 根据页面源码,这是主要的HR区域类名 - ".recruiter-info, .hr-info, .contact-info", - "[class*='recruiter'], [class*='hr-'], [class*='contact']", - ".job-card-footer, .card-footer", - ".job-bottom, .bottom-info" - }; - - for (String selector : hrSelectors) { - Locator tempHrArea = currentJobCard.locator(selector); - if (tempHrArea.count() > 0) { - hrArea = tempHrArea.first(); - log.debug("找到HR区域,使用选择器: {}", selector); - break; - } - } - - // 如果找不到特定的HR区域,使用整个卡片 - if (hrArea == null) { - log.debug("未找到特定HR区域,使用整个岗位卡片"); - hrArea = currentJobCard; - } - - // 鼠标悬停到HR区域,触发按钮显示 - 简化悬停逻辑 - boolean hoverSuccess = false; - int hoverRetries = 3; - for (int retry = 0; retry < hoverRetries; retry++) { - try { - // 检查HR区域是否可见,如果不可见则跳过悬停 - if (!hrArea.isVisible()) { - log.debug("HR区域不可见,跳过悬停操作"); - hoverSuccess = true; // 设为成功,继续后续流程 - break; - } - - // 直接悬停,不再进行复杂的微调 - hrArea.hover(new Locator.HoverOptions().setTimeout(5000)); - hoverSuccess = true; - break; - } catch (Exception hoverError) { - log.warn("第{}次悬停失败: {}", retry + 1, hoverError.getMessage()); - if (retry < hoverRetries - 1) { - // 重试前重新滚动确保元素可见 - try { - page.evaluate("(element) => element.scrollIntoView({behavior: 'instant', block: 'center'})", currentJobCard.elementHandle()); - Thread.sleep(500); // 等待滚动完成 - } catch (Exception e) { - log.warn("重试前滚动失败: {}", e.getMessage()); - } - } - } - } - - if (!hoverSuccess) { - log.warn("悬停操作失败,但继续查找按钮"); - // 不再跳过,而是继续查找按钮,因为有些按钮可能不需要悬停就能显示 - } - - // PlaywrightUtil.sleep(1); // 等待按钮显示 - - // 获取hr名字 - try { - Locator hrNameElement = currentJobCard.locator(".recruiter-name, .hr-name, .contact-name, [class*='recruiter-name'], [class*='hr-name']"); - if (hrNameElement.count() > 0) { - recruiterName = hrNameElement.first().textContent(); - } else { - recruiterName = "HR"; - } - } catch (Exception e) { - log.error("获取HR名字失败: {}", e.getMessage()); - recruiterName = "HR"; - } - - } catch (Exception e) { - log.error("处理岗位卡片失败: {}", e.getMessage()); - continue; - } - - // 查找聊一聊按钮 - Locator button = null; - String buttonText = ""; - try { - // 在当前岗位卡片中查找按钮,尝试多种选择器 - Locator currentJobCard = page.locator(JOB_CARDS).nth(i); - - String[] buttonSelectors = { - "button.ant-btn.ant-btn-primary.ant-btn-round", - "button.ant-btn.ant-btn-round.ant-btn-primary", - "button[class*='ant-btn'][class*='primary']", - "button[class*='ant-btn'][class*='round']", - "button[class*='chat'], button[class*='talk']", - ".chat-btn, .talk-btn, .contact-btn", - "button:has-text('聊一聊')", - "button" // 最后尝试所有按钮 - }; - - for (String selector : buttonSelectors) { - try { - Locator tempButtons = currentJobCard.locator(selector); - int buttonCount = tempButtons.count(); - log.debug("选择器 '{}' 找到 {} 个按钮", selector, buttonCount); - - for (int j = 0; j < buttonCount; j++) { - Locator tempButton = tempButtons.nth(j); - try { - if (tempButton.isVisible()) { - String text = tempButton.textContent(); - log.debug("按钮文本: '{}'", text); - if (text != null && !text.trim().isEmpty()) { - button = tempButton; - buttonText = text.trim(); - // 只关注"聊一聊"按钮 - if (text.contains("聊一聊")) { - log.debug("找到目标按钮: '{}'", text); - break; - } - } - } - } catch (Exception ignore) { - log.debug("获取按钮文本失败: {}", ignore.getMessage()); - } - } - - if (button != null && buttonText.contains("聊一聊")) { - break; - } - } catch (Exception e) { - log.debug("选择器 '{}' 查找失败: {}", selector, e.getMessage()); - } - } - - } catch (Exception e) { - log.error("查找按钮失败: {}", e.getMessage()); - // 保存页面源码用于调试 - savePageSource(page, "button_search_failed"); - continue; - } - - // 检查按钮文本并点击 - if (button != null && buttonText.contains("聊一聊")) { - try { - // 在点击按钮前进行鼠标微调,先向右移动2像素,再向左移动2像素 - try { - var boundingBox = button.boundingBox(); - if (boundingBox != null) { - double centerX = boundingBox.x + boundingBox.width / 2; - double centerY = boundingBox.y + boundingBox.height / 2; - - // 先移动到按钮中心 - page.mouse().move(centerX, centerY); - Thread.sleep(50); - - // 向右移动2像素 - page.mouse().move(centerX + 2, centerY); - Thread.sleep(50); - - // 向左移动2像素(回到中心再向左2像素) - page.mouse().move(centerX - 2, centerY); - Thread.sleep(50); - - // 回到中心位置 - page.mouse().move(centerX, centerY); - Thread.sleep(50); - - log.debug("完成鼠标微调,准备点击按钮"); - } - } catch (Exception moveError) { - log.warn("鼠标微调失败,直接点击按钮: {}", moveError.getMessage()); - } - - button.click(); - // PlaywrightUtil.sleep(1); // 等待点击响应 - - // 猎聘会自动发送打招呼语,所以我们只需要关闭聊天窗口 - try { - // 等待聊天界面加载 - page.waitForSelector(CHAT_HEADER, new Page.WaitForSelectorOptions().setTimeout(3000)); - - // 直接关闭聊天窗口 - Locator close = page.locator(CHAT_CLOSE); - if (close.count() > 0) { - PlaywrightUtil.sleep(1); - close.click(); - } - - resultList.add(sb.append("【").append(companyName).append(" ").append(jobName).append(" ").append(salary).append(" ").append(recruiterName).append(" ").append("】").toString()); - sb.setLength(0); - log.info("成功发起聊天:【{}】的【{}·{}】岗位", companyName, jobName, salary); - - } catch (Exception e) { - log.warn("关闭聊天窗口失败,但投递可能已成功: {}", e.getMessage()); - // 即使关闭失败,也认为投递成功 - resultList.add(sb.append("【").append(companyName).append(" ").append(jobName).append(" ").append(salary).append(" ").append(recruiterName).append(" ").append("】").toString()); - sb.setLength(0); - } - - } catch (Exception e) { - log.error("点击按钮失败: {}", e.getMessage()); - // 保存页面源码用于调试 - savePageSource(page, "button_click_failed"); - } - } else { - if (button != null) { - log.debug("跳过岗位(按钮文本不匹配): 【{}】的【{}·{}】岗位,按钮文本: '{}'", companyName, jobName, salary, buttonText); - } else { -// log.warn("未找到可点击的按钮: 【{}】的【{}·{}】岗位", companyName, jobName, salary); - // 保存页面源码用于调试 - savePageSource(page, "no_button_found"); - } - } - - // 等待一下,避免操作过快 - // PlaywrightUtil.sleep(1); - } - } - - @SneakyThrows - private static void login() { - log.info("正在打开猎聘网站..."); - Page page = PlaywrightUtil.getPageObject(); - page.navigate(homeUrl); - log.info("猎聘正在登录..."); - - if (PlaywrightUtil.isCookieValid(cookiePath)) { - PlaywrightUtil.loadCookies(cookiePath); - page.reload(); - } - - page.waitForSelector(HEADER_LOGO, new Page.WaitForSelectorOptions().setTimeout(10000)); - - if (isLoginRequired()) { - log.info("cookie失效,尝试扫码登录..."); - scanLogin(); - PlaywrightUtil.saveCookies(cookiePath); - } else { - log.info("cookie有效,准备投递..."); - } - } - - private static boolean isLoginRequired() { - Page page = PlaywrightUtil.getPageObject(); - String currentUrl = page.url(); - return !currentUrl.contains("c.liepin.com"); - } - - private static void scanLogin() { - try { - Page page = PlaywrightUtil.getPageObject(); - - // 点击切换登录类型按钮 - Locator switchBtn = page.locator(LOGIN_SWITCH_BTN); - if (switchBtn.count() > 0) { - switchBtn.click(); - } - - log.info("等待扫码.."); - - // 记录开始时间 - long startTime = System.currentTimeMillis(); - long maxWaitTime = 10 * 60 * 1000; // 10分钟,单位毫秒 - - // 主循环,直到登录成功或超时 - while (true) { - try { - // 检查是否已登录 - Locator loginButtons = page.locator(LOGIN_BUTTONS); - if (loginButtons.count() > 0) { - String login = loginButtons.first().textContent(); - if (!login.contains("登录")) { - log.info("用户扫码成功,继续执行..."); - break; - } - } - } catch (Exception ignored) { - try { - Locator userInfo = page.locator(USER_INFO); - if (userInfo.count() > 0) { - String login = userInfo.first().textContent(); - if (login.contains("你好")){ - break; - } - } - } catch (Exception e) { - log.error("获取登录状态失败!"); - } - } - - // 检查是否超过最大等待时间 - long elapsedTime = System.currentTimeMillis() - startTime; - if (elapsedTime > maxWaitTime) { - log.error("登录超时,10分钟内未完成扫码登录,程序将退出。"); - PlaywrightUtil.close(); // 关闭浏览器 - return; // 返回而不是退出整个程序 - } - PlaywrightUtil.sleep(1); - } - - // 登录成功后,保存Cookie - PlaywrightUtil.saveCookies(cookiePath); - log.info("登录成功,Cookie已保存。"); - - } catch (Exception e) { - log.error("scanLogin() 失败: {}", e.getMessage()); - PlaywrightUtil.close(); // 关闭浏览器 - return; // 返回而不是退出整个程序 - } - } - - - -} diff --git a/src/main/java/liepin/LiepinConfig.java b/src/main/java/liepin/LiepinConfig.java deleted file mode 100644 index 0f4e30b6..00000000 --- a/src/main/java/liepin/LiepinConfig.java +++ /dev/null @@ -1,46 +0,0 @@ -package liepin; - -import lombok.Data; -import lombok.SneakyThrows; -import utils.JobUtils; - -import java.util.List; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -@Data -public class LiepinConfig { - /** - * 搜索关键词列表 - */ - private List keywords; - - /** - * 城市编码 - */ - private String cityCode; - - /** - * 薪资范围 - */ - private String salary; - - - - /** - * 发布时间 - */ - private String pubTime; - - - @SneakyThrows - public static LiepinConfig init() { - LiepinConfig config = JobUtils.getConfig(LiepinConfig.class); - // 转换城市编码 - config.setCityCode(LiepinEnum.CityCode.forValue(config.getCityCode()).getCode()); - return config; - } - -} diff --git a/src/main/java/liepin/LiepinEnum.java b/src/main/java/liepin/LiepinEnum.java deleted file mode 100644 index f95c9dbc..00000000 --- a/src/main/java/liepin/LiepinEnum.java +++ /dev/null @@ -1,43 +0,0 @@ -package liepin; - -import com.fasterxml.jackson.annotation.JsonCreator; -import lombok.Getter; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -public class LiepinEnum { - - @Getter - public enum CityCode { - NULL("不限", "0"), - ALL("全国", "410"), - BEIJING("北京", "010"), - SHANGHAI("上海", "020"), - GUANGZHOU("广州", "050020"), - SHENZHEN("深圳", "050090"), - WUHAN("武汉", "170020"), - CHENGDU("成都", "280020"); - - private final String name; - private final String code; - - CityCode(String name, String code) { - this.name = name; - this.code = code; - } - - @JsonCreator - public static CityCode forValue(String value) { - for (CityCode cityCode : CityCode.values()) { - if (cityCode.name.equals(value)) { - return cityCode; - } - } - return NULL; - } - - } - -} diff --git a/src/main/java/liepin/LiepinScheduled.java b/src/main/java/liepin/LiepinScheduled.java deleted file mode 100644 index 7e4fc72e..00000000 --- a/src/main/java/liepin/LiepinScheduled.java +++ /dev/null @@ -1,30 +0,0 @@ -package liepin; - -import lombok.extern.slf4j.Slf4j; -import utils.JobUtils; -import utils.Platform; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -@Slf4j -public class LiepinScheduled { - - public static void main(String[] args) { - JobUtils.runScheduled(Platform.LIEPIN); - } - - public static void postJobs() { - safeRun(() -> Liepin.main(null)); - } - - // 任务执行的安全包装,防止异常 - private static void safeRun(Runnable task) { - try { - task.run(); - } catch (Exception e) { - log.error("safeRun异常:{}", e.getMessage(), e); - } - } -} \ No newline at end of file diff --git a/src/main/java/liepin/Locators.java b/src/main/java/liepin/Locators.java deleted file mode 100644 index a1f9eb3b..00000000 --- a/src/main/java/liepin/Locators.java +++ /dev/null @@ -1,33 +0,0 @@ -package liepin; - -/** - * 猎聘网页元素定位器 - * 集中管理所有页面元素的定位表达式 - */ -public class Locators { - // 主页相关元素 - public static final String HEADER_LOGO = "#header-logo-box"; - public static final String LOGIN_SWITCH_BTN = "//div[@class='jsx-263198893 btn-sign-switch']"; - public static final String LOGIN_BUTTONS = "//button[@type='button']"; - public static final String USER_INFO = "//div[@id='header-quick-menu-user-info']"; - - // 搜索结果页相关元素 - public static final String PAGINATION_BOX = ".list-pagination-box"; - public static final String PAGINATION_ITEMS = ".list-pagination-box li"; - public static final String NEXT_PAGE = "li[title='Next Page']"; - public static final String SUBSCRIBE_CLOSE_BTN = "//div[contains(@class, 'subscribe-close-btn')]"; - - // 岗位列表相关元素 - public static final String JOB_CARDS = "//div[contains(@class, 'job-card-pc-container')]"; - public static final String JOB_TITLE = "//div[contains(@class, 'job-title-box')]"; - public static final String COMPANY_NAME = "//span[contains(@class, 'company-name')]"; - public static final String JOB_SALARY = "//span[contains(@class, 'job-salary')]"; - - // 聊天相关元素 - public static final String CHAT_BUTTON_PRIMARY = "//button[@class='ant-btn ant-btn-primary ant-btn-round']"; - public static final String CHAT_BUTTON_ALTERNATIVE = "//button[@class='ant-btn ant-btn-round ant-btn-primary']"; - public static final String CHAT_HEADER = ".__im_basic__header-wrap"; - public static final String CHAT_TEXTAREA = "//textarea[contains(@class, '__im_basic__textarea')]"; - public static final String CHAT_CLOSE = "div.__im_basic__contacts-title svg"; - public static final String RECRUITER_INFO = "//div[contains(@class, 'recruiter-info-box')]"; -} \ No newline at end of file diff --git a/src/main/java/utils/Bot.java b/src/main/java/utils/Bot.java deleted file mode 100644 index 742a830e..00000000 --- a/src/main/java/utils/Bot.java +++ /dev/null @@ -1,106 +0,0 @@ -package utils; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import io.github.cdimascio.dotenv.Dotenv; -import lombok.extern.slf4j.Slf4j; -import org.apache.hc.client5.http.fluent.Request; - -import java.io.File; -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -@Slf4j -public class Bot { - - private static final String HOOK_URL; - private static boolean isSend; - - static { - // 加载环境变量 - Dotenv dotenv = Dotenv.configure() - .directory("src/main/resources") - .filename(".env") - .load(); - - HOOK_URL = dotenv.get("HOOK_URL"); - - // 使用 Jackson 加载 config.yaml 配置 - try { - ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); - HashMap config = mapper.readValue(new File("src/main/resources/config.yaml"), new TypeReference<>() { - }); - log.info("YAML 配置内容: {}", config); - - // 获取 bot 配置 - HashMap botConfig = safeCast(config.get("bot"), HashMap.class); - if (botConfig != null && botConfig.get("is_send") != null) { - isSend = Boolean.TRUE.equals(safeCast(botConfig.get("is_send"), Boolean.class)); - } else { - log.warn("配置文件中缺少 'bot.is_send' 键或值为空,不发送消息。"); - isSend = false; - } - } catch (IOException e) { - log.error("读取 config.yaml 异常:{}", e.getMessage()); - isSend = false; // 如果读取配置文件失败,默认不发送消息 - } - } - - public static void sendMessageByTime(String message) { - if (!isSend) { - return; - } - // 格式化当前时间 - String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); - String formattedMessage = String.format("%s %s", currentTime, message); - sendMessage(formattedMessage); - } - - public static void sendMessage(String message) { - if (!isSend) { - return; - } - // 发送HTTP请求 - try { - String response = Request.post(HOOK_URL) - .bodyString("{\"msgtype\": \"text\", \"text\": {\"content\": \"" + message + "\"}}", - org.apache.hc.core5.http.ContentType.APPLICATION_JSON) - .execute() - .returnContent() - .asString(); - log.info("消息推送成功: {}", response); - } catch (Exception e) { - log.error("消息推送失败: {}", e.getMessage()); - } - } - - public static void main(String[] args) { - sendMessageByTime("企业微信推送测试消息..."); - } - - /** - * 通用的安全类型转换方法,避免未检查的类型转换警告 - * - * @param obj 要转换的对象 - * @param clazz 目标类型的 Class 对象 - * @param 目标类型 - * @return 如果对象类型匹配,则返回转换后的对象,否则返回 null - */ - @SuppressWarnings("unchecked") - public static T safeCast(Object obj, Class clazz) { - if (clazz.isInstance(obj)) { - return (T) obj; - } else { - return null; - } - } -} diff --git a/src/main/java/utils/Constant.java b/src/main/java/utils/Constant.java deleted file mode 100644 index a53c668e..00000000 --- a/src/main/java/utils/Constant.java +++ /dev/null @@ -1,21 +0,0 @@ -package utils; - -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.chrome.ChromeDriver; -import org.openqa.selenium.interactions.Actions; -import org.openqa.selenium.support.ui.WebDriverWait; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -public class Constant { - public static ChromeDriver CHROME_DRIVER; - public static ChromeDriver MOBILE_CHROME_DRIVER; - public static Actions ACTIONS; - public static Actions MOBILE_ACTIONS; - public static WebDriverWait WAIT; - public static WebDriverWait MOBILE_WAIT; - public static int WAIT_TIME = 30; - public static String UNLIMITED_CODE = "0"; -} diff --git a/src/main/java/utils/EncryptDecryptUtil.java b/src/main/java/utils/EncryptDecryptUtil.java deleted file mode 100644 index d25cbf17..00000000 --- a/src/main/java/utils/EncryptDecryptUtil.java +++ /dev/null @@ -1,58 +0,0 @@ -package utils; - -import lombok.extern.slf4j.Slf4j; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.util.Base64; - -@Slf4j -public class EncryptDecryptUtil { - private static final String AES_KEY = "sB1+lkOiIqzMKO2yR/B91A=="; - - private static final String PLAIN_TEXT = ""; - - public static void main(String[] args) { - try { - String encryptedText = encrypt(PLAIN_TEXT, AES_KEY); - System.out.println(encryptedText); - String decryptedText = decrypt(encryptedText, AES_KEY); - System.out.printf(decryptedText); - } catch (Exception e) { - log.error(e.getMessage()); - } - } - - public static String encrypt(String plainText, String base64Key) throws Exception { - byte[] decodedKey = Base64.getDecoder().decode(base64Key); - SecretKey secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES"); - - Cipher cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); - byte[] encryptedBytes = cipher.doFinal(plainText.getBytes()); - - return Base64.getEncoder().encodeToString(encryptedBytes); - } - - public static String decrypt(String encryptedText, String base64Key) throws Exception { - byte[] decodedKey = Base64.getDecoder().decode(base64Key); - SecretKey secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES"); - - Cipher cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.DECRYPT_MODE, secretKey); - byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText)); - - return new String(decryptedBytes); - } - - public static String decrypt(String encryptedText) { - try { - return decrypt(encryptedText, AES_KEY); - } catch (Exception e) { - log.error("decrypt异常!"); - return PLAIN_TEXT; - } - } - -} diff --git a/src/main/java/utils/Finder.java b/src/main/java/utils/Finder.java deleted file mode 100644 index 7a256233..00000000 --- a/src/main/java/utils/Finder.java +++ /dev/null @@ -1,242 +0,0 @@ -package utils; - -import com.microsoft.playwright.ElementHandle; -import com.microsoft.playwright.Locator; -import com.microsoft.playwright.Page; -import org.openqa.selenium.By; -import org.openqa.selenium.NoSuchElementException; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.openqa.selenium.support.ui.WebDriverWait; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.Duration; -import java.util.List; -import java.util.Optional; - -/** - * Boss直聘元素查找工具类 - * 封装Selenium和Playwright元素查找逻辑 - * - * 替代原来代码中直接使用的: - * - CHROME_DRIVER.findElement(By.xpath("...")) - * - CHROME_DRIVER.findElement(By.cssSelector("...")) - * - WAIT.until(ExpectedConditions.presenceOfElementLocated(By.xpath("..."))) - * - SeleniumUtil.findElement("...", "") - */ -public class Finder { - private static final Logger log = LoggerFactory.getLogger(Finder.class); - private static final WebDriver driver = Constant.CHROME_DRIVER; - private static final int DEFAULT_TIMEOUT_SECONDS = 10; - - /** - * 基于Selenium WebDriver查找单个元素 - * - * 替代原来的代码模式: - * WAIT.until(ExpectedConditions.presenceOfElementLocated(By.xpath("..."))) - * - * @param selector 选择器表达式 - * @param timeoutSeconds 超时时间(秒) - * @return 找到的元素,如果没找到返回Optional.empty() - */ - public static Optional findElement(String selector, int timeoutSeconds) { - try { - By by = parseSelector(selector); - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(timeoutSeconds)); - return Optional.of(wait.until(ExpectedConditions.presenceOfElementLocated(by))); - } catch (Exception e) { - log.debug("未找到元素: {}, 原因: {}", selector, e.getMessage()); - return Optional.empty(); - } - } - - /** - * 基于Selenium WebDriver查找单个元素,使用默认超时时间 - * - * 替代原来的代码模式: - * CHROME_DRIVER.findElement(By.xpath("...")) - * - * @param selector 选择器表达式 - * @return 找到的元素,如果没找到返回Optional.empty() - */ - public static Optional findElement(String selector) { - return findElement(selector, DEFAULT_TIMEOUT_SECONDS); - } - - /** - * 基于Selenium WebDriver查找多个元素 - * - * 替代原来的代码模式: - * CHROME_DRIVER.findElements(By.xpath("...")) - * - * @param selector 选择器表达式 - * @return 找到的元素列表,如果没找到返回空列表 - */ - public static List findElements(String selector) { - try { - By by = parseSelector(selector); - return driver.findElements(by); - } catch (Exception e) { - log.debug("查找元素列表失败: {}, 原因: {}", selector, e.getMessage()); - return List.of(); - } - } - - /** - * 等待元素可见 - * - * 替代原来的代码模式: - * WAIT.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("..."))) - * - * @param selector 选择器表达式 - * @param timeoutSeconds 超时时间(秒) - * @return 找到的元素,如果没找到返回Optional.empty() - */ - public static Optional waitForElementVisible(String selector, int timeoutSeconds) { - try { - By by = parseSelector(selector); - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(timeoutSeconds)); - return Optional.of(wait.until(ExpectedConditions.visibilityOfElementLocated(by))); - } catch (Exception e) { - log.debug("等待元素可见超时: {}, 原因: {}", selector, e.getMessage()); - return Optional.empty(); - } - } - - /** - * 等待元素可见,使用默认超时时间 - * - * @param selector 选择器表达式 - * @return 找到的元素,如果没找到返回Optional.empty() - */ - public static Optional waitForElementVisible(String selector) { - return waitForElementVisible(selector, DEFAULT_TIMEOUT_SECONDS); - } - - /** - * 等待元素可点击 - * - * 替代原来的代码模式: - * WAIT.until(ExpectedConditions.elementToBeClickable(By.xpath("..."))) - * - * @param selector 选择器表达式 - * @param timeoutSeconds 超时时间(秒) - * @return 找到的元素,如果没找到返回Optional.empty() - */ - public static Optional waitForElementClickable(String selector, int timeoutSeconds) { - try { - By by = parseSelector(selector); - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(timeoutSeconds)); - return Optional.of(wait.until(ExpectedConditions.elementToBeClickable(by))); - } catch (Exception e) { - log.debug("等待元素可点击超时: {}, 原因: {}", selector, e.getMessage()); - return Optional.empty(); - } - } - - /** - * 等待元素可点击,使用默认超时时间 - * - * @param selector 选择器表达式 - * @return 找到的元素,如果没找到返回Optional.empty() - */ - public static Optional waitForElementClickable(String selector) { - return waitForElementClickable(selector, DEFAULT_TIMEOUT_SECONDS); - } - - /** - * 判断元素是否存在 - * - * 替代原来的try-catch模式: - * try { - * CHROME_DRIVER.findElement(By.xpath("...")); - * return true; - * } catch (Exception e) { - * return false; - * } - * - * @param selector 选择器表达式 - * @return true如果元素存在,否则false - */ - public static boolean isElementPresent(String selector) { - try { - By by = parseSelector(selector); - driver.findElement(by); - return true; - } catch (NoSuchElementException e) { - return false; - } - } - - /** - * 基于Playwright查找单个元素 - * - * 替代原来的代码模式: - * page.querySelector("...") - * - * @param page Playwright页面对象 - * @param selector 选择器表达式 - * @return 找到的元素,如果没找到返回null - */ - public static ElementHandle findPlaywrightElement(Page page, String selector) { - try { - return page.querySelector(selector); - } catch (Exception e) { - log.debug("Playwright未找到元素: {}, 原因: {}", selector, e.getMessage()); - return null; - } - } - - /** - * 基于Playwright查找多个元素 - * - * 替代原来的代码模式: - * page.querySelectorAll("...") - * - * @param page Playwright页面对象 - * @param selector 选择器表达式 - * @return 找到的元素列表,如果没找到返回空列表 - */ - public static List findPlaywrightElements(Page page, String selector) { - try { - return page.querySelectorAll(selector); - } catch (Exception e) { - log.debug("Playwright查找元素列表失败: {}, 原因: {}", selector, e.getMessage()); - return List.of(); - } - } - - /** - * 获取Playwright的Locator对象 - * - * 替代原来的代码模式: - * page.locator("...") - * - * @param page Playwright页面对象 - * @param selector 选择器表达式 - * @return Locator对象 - */ - public static Locator getPlaywrightLocator(Page page, String selector) { - return page.locator(selector); - } - - /** - * 解析选择器表达式,判断是XPath还是CSS选择器 - * - * 自动判断选择器类型,避免手动区分By.xpath和By.cssSelector - * - * @param selector 选择器表达式 - * @return 解析后的By对象 - */ - private static By parseSelector(String selector) { - if (selector.startsWith("//") || selector.startsWith("(//") || selector.startsWith("/")) { - return By.xpath(selector); - } else if (selector.startsWith("[")) { - return By.cssSelector(selector); - } else { - return By.cssSelector(selector); - } - } -} \ No newline at end of file diff --git a/src/main/java/utils/Job.java b/src/main/java/utils/Job.java deleted file mode 100644 index 1ba031f4..00000000 --- a/src/main/java/utils/Job.java +++ /dev/null @@ -1,74 +0,0 @@ -package utils; - -import lombok.Data; - -import java.io.Serializable; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -@Data -public class Job implements Serializable { - /** - * 岗位链接 - */ - private String href; - - /** - * 岗位名称 - */ - private String jobName; - - /** - * 岗位地区 - */ - private String jobArea; - - /** - * 岗位信息 - */ - private String jobInfo; - - /** - * 岗位薪水 - */ - private String salary; - - /** - * 公司标签 - */ - private String companyTag; - - /** - * HR名称 - */ - private String recruiter; - - /** - * 公司名字 - */ - private String companyName; - - /** - * 公司信息 - */ - private String companyInfo; - - @Override - public String toString() { - return String.format("【%s, %s, %s, %s, %s, %s】", companyName, jobName, jobArea, salary, companyTag, recruiter); - } - - public String toString(Platform platform) { - if (platform == Platform.ZHILIAN) { - return String.format("【%s, %s, %s, %s, %s, %s, %s】", companyName, jobName, jobArea, companyTag, salary, recruiter, href); - } - if (platform == Platform.BOSS) { - return String.format("【%s, %s, %s, %s, %s, %s】", companyName, jobName, jobArea, salary, companyTag, recruiter); - } - return String.format("【%s, %s, %s, %s, %s, %s, %s】", companyName, jobName, jobArea, salary, companyTag, recruiter, href); - } -} - - diff --git a/src/main/java/utils/JobUtils.java b/src/main/java/utils/JobUtils.java deleted file mode 100644 index ee7891fd..00000000 --- a/src/main/java/utils/JobUtils.java +++ /dev/null @@ -1,171 +0,0 @@ -package utils; - -import static utils.Constant.UNLIMITED_CODE; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Random; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import boss.BossScheduled; -import job51.Job51Scheduled; -import lagou.LagouScheduled; -import liepin.LiepinScheduled; -import zhilian.ZhilianScheduled; - -@Slf4j -public class JobUtils { - - public static String appendParam(String name, String value) { - return Optional.ofNullable(value) - .filter(v -> !Objects.equals(UNLIMITED_CODE, v)) - .map(v -> "&" + name + "=" + v) - .orElse(""); - } - - public static String appendListParam(String name, List values) { - return Optional.ofNullable(values) - .filter(list -> !list.isEmpty() && !Objects.equals(UNLIMITED_CODE, list.getFirst())) - .map(list -> "&" + name + "=" + String.join(",", list)) - .orElse(""); - } - - @SneakyThrows - public static T getConfig(Class clazz) { - ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); - InputStream is = clazz.getClassLoader().getResourceAsStream("config.yaml"); - if (is == null) { - throw new FileNotFoundException("无法找到 config.yaml 文件"); - } - JsonNode rootNode = mapper.readTree(is); - String key = clazz.getSimpleName().toLowerCase().replaceAll("config", ""); - JsonNode configNode = rootNode.path(key); - return mapper.treeToValue(configNode, clazz); - } - - public static void runScheduled(Platform platform) { - String platformName = platform.getPlatformName(); - switch (platform) { - case BOSS -> { - BossScheduled.postJobs(); - scheduleTaskAtTime(platformName, 10, 0, BossScheduled::postJobs); - scheduleTaskAtTime(platformName, 15, 0, BossScheduled::postJobs); - } - case JOB51 -> { - Job51Scheduled.postJobs(); - scheduleTaskAtTime(platformName, 10, 0, Job51Scheduled::postJobs); - } - case LIEPIN -> { - LiepinScheduled.postJobs(); - scheduleTaskAtTime(platformName, 10, 0, LiepinScheduled::postJobs); - } - case ZHILIAN -> { - ZhilianScheduled.postJobs(); - scheduleTaskAtTime(platformName, 10, 0, ZhilianScheduled::postJobs); - } - case LAGOU -> { - LagouScheduled.postJobs(); - scheduleTaskAtTime(platformName, 10, 0, LagouScheduled::postJobs); - } - default -> log.warn("未定义的平台任务:{}", platformName); - } - } - - - /** - * 计算并格式化时间(毫秒) - * - * @param startDate 开始时间 - * @param endDate 结束时间 - * @return 格式化后的时间字符串,格式为 "HH:mm:ss" - */ - public static String formatDuration(Date startDate, Date endDate) { - long durationMillis = endDate.getTime() - startDate.getTime(); - long seconds = (durationMillis / 1000) % 60; - long minutes = (durationMillis / (1000 * 60)) % 60; - long hours = durationMillis / (1000 * 60 * 60); - return String.format("%d时%d分%d秒", hours, minutes, seconds); - } - - /** - * 将给定的毫秒时间戳转换为格式化的时间字符串 - * - * @param durationSeconds 持续时间的时间戳(秒) - * @return 格式化后的时间字符串,格式为 "HH:mm:ss" - */ - public static String formatDuration(long durationSeconds) { - long seconds = durationSeconds % 60; - long minutes = (durationSeconds / 60) % 60; - long hours = durationSeconds / 3600; // 直接计算总小时数 - - return String.format("%d时%d分%d秒", hours, minutes, seconds); - } - - - /** - * 通用的任务调度方法 - * - * @param hour 要设置的小时,0-23之间的整数 - * @param minute 要设置的分钟,0-59之间的整数 - */ - public static void scheduleTaskAtTime(String platform, int hour, int minute, Runnable task) { - long delay = getInitialDelay(hour, minute); // 计算初始延迟 - String msg = String.format("【%s】距离下次任务投递还有:%s,执行时间:%02d:%02d", platform, formatDuration(delay), hour, minute); - log.info(msg); - Bot.sendMessage(msg); - - // 安排定时任务,每24小时执行一次 - Executors.newScheduledThreadPool(4).scheduleAtFixedRate(task, delay, TimeUnit.DAYS.toSeconds(1), TimeUnit.SECONDS); - } - - /** - * 计算从当前时间到指定时间(小时:分钟)的延迟 - * - * @param targetHour 目标执行的小时 - * @param targetMinute 目标执行的分钟 - * @return 延迟的秒数 - */ - public static long getInitialDelay(int targetHour, int targetMinute) { - Calendar now = Calendar.getInstance(); - Calendar nextRun = Calendar.getInstance(); - - // 设置目标时间 - nextRun.set(Calendar.HOUR_OF_DAY, targetHour); - nextRun.set(Calendar.MINUTE, targetMinute); - nextRun.set(Calendar.SECOND, 0); - nextRun.set(Calendar.MILLISECOND, 0); - - // 如果当前时间已经过了今天的目标时间,则将任务安排在明天 - if (now.after(nextRun)) { - nextRun.add(Calendar.DAY_OF_YEAR, 1); // 调整为明天 - } - - long currentTime = System.currentTimeMillis(); - return (nextRun.getTimeInMillis() - currentTime) / 1000; // 返回秒数 - } - - public static int getRandomNumberInRange(int min, int max) { - if (min > max) { - throw new IllegalArgumentException("max must be greater than or equal to min"); - } - Random random = new Random(); - return random.nextInt((max - min) + 1) + min; - } - - public static void main(String[] args) { - Date star = new Date(); - SeleniumUtil.sleep(3); - String a = formatDuration(star, new Date()); - System.out.println(a); - } -} diff --git a/src/main/java/utils/KeyUtil.java b/src/main/java/utils/KeyUtil.java deleted file mode 100644 index 83d6ffa6..00000000 --- a/src/main/java/utils/KeyUtil.java +++ /dev/null @@ -1,22 +0,0 @@ -package utils; - -import lombok.extern.slf4j.Slf4j; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import java.util.Base64; - -@Slf4j -public class KeyUtil { - public static void main(String[] args) throws Exception { - KeyGenerator keyGen = KeyGenerator.getInstance("AES"); - keyGen.init(128); - SecretKey secretKey = keyGen.generateKey(); - String encodedKey = Base64.getEncoder().encodeToString(secretKey.getEncoded()); - } - - public static void printLog() { - System.out.println(EncryptDecryptUtil.decrypt("zGxvj++nOpkryewylR0gxuCA8Bbaj9msK9+4LCSTVlJWvNH2wVccnebDaMwDfipobmugpJ/T5KGYikBPMIiNjg==")); - } -} - diff --git a/src/main/java/utils/Operate.java b/src/main/java/utils/Operate.java deleted file mode 100644 index 2b3dadf9..00000000 --- a/src/main/java/utils/Operate.java +++ /dev/null @@ -1,325 +0,0 @@ -package utils; - -import boss.Locators; -import com.microsoft.playwright.ElementHandle; -import com.microsoft.playwright.Page; -import org.openqa.selenium.JavascriptExecutor; -import org.openqa.selenium.Keys; -import org.openqa.selenium.WebElement; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.Random; - -import static utils.Constant.ACTIONS; -import static utils.Constant.CHROME_DRIVER; - -/** - * Boss直聘页面操作工具类 - * 封装常见页面操作逻辑 - * - * 替代原始代码中重复的页面操作逻辑: - * - 下拉加载更多岗位 - * - 随机等待和模拟用户行为 - * - 标签页管理 - * - 滚动页面 - * - 发送简历 - */ -public class Operate { - private static final Logger log = LoggerFactory.getLogger(Operate.class); - private static final Random random = new Random(); - - /** - * 页面下拉加载更多岗位 - * - * 替代原始代码: - * ```java - * // 记录下拉前后的岗位数量 - * int previousJobCount = 0; - * int currentJobCount = 0; - * int unchangedCount = 0; - * - * while (unchangedCount < 2) { - * // 获取所有岗位卡片 - * List jobCards = page.querySelectorAll("ul.rec-job-list - * li.job-card-box"); - * currentJobCount = jobCards.size(); - * - * if (currentJobCount > previousJobCount) { - * previousJobCount = currentJobCount; - * unchangedCount = 0; - * PlaywrightUtil.evaluate("window.scrollTo(0, document.body.scrollHeight)"); - * page.waitForTimeout(2000); - * } else { - * unchangedCount++; - * } - * } - * ``` - * - * @param page Playwright页面对象 - * @param maxLoadAttempts 最大尝试加载次数 - * @return 最终加载的岗位数量 - */ - public static int scrollToLoadMoreJobs(Page page, int maxLoadAttempts) { - int previousJobCount = 0; - int currentJobCount = 0; - int unchangedCount = 0; - int loadAttempts = 0; - - while (unchangedCount < 2 && loadAttempts < maxLoadAttempts) { - // 获取所有岗位卡片 - List jobCards = page.querySelectorAll(Locators.JOB_CARD_BOX); - currentJobCount = jobCards.size(); - - log.info("当前已加载岗位数量: " + currentJobCount); - - // 判断是否有新增岗位 - if (currentJobCount > previousJobCount) { - previousJobCount = currentJobCount; - unchangedCount = 0; - - // 滚动到页面底部加载更多 - PlaywrightUtil.evaluate("window.scrollTo(0, document.body.scrollHeight)"); - log.info("下拉页面加载更多..."); - - // 等待新内容加载 - page.waitForTimeout(2000); - } else { - unchangedCount++; - if (unchangedCount < 2) { - log.info("下拉后岗位数量未增加,再次尝试..."); - // 再次尝试滚动 - page.evaluate("window.scrollTo(0, document.body.scrollHeight)"); - page.waitForTimeout(2000); - } else { - break; - } - } - - loadAttempts++; - } - - log.info("已获取所有可加载岗位,共计: " + currentJobCount + " 个"); - return currentJobCount; - } - - /** - * 模拟随机等待时间 - * - * 替代原始代码: - * ```java - * private static void RandomWait() { - * SeleniumUtil.sleep(JobUtils.getRandomNumberInRange(3, 20)); - * } - * ``` - */ - public static void randomWait() { - int seconds = JobUtils.getRandomNumberInRange(3, 20); - SeleniumUtil.sleep(seconds); - } - - /** - * 模拟用户浏览行为 - * - * 替代原始代码: - * ```java - * private static void simulateWait() { - * for (int i = 0; i < 3; i++) { - * ACTIONS.sendKeys(" ").perform(); - * SeleniumUtil.sleep(1); - * } - * ACTIONS.keyDown(Keys.CONTROL) - * .sendKeys(Keys.HOME) - * .keyUp(Keys.CONTROL) - * .perform(); - * SeleniumUtil.sleep(1); - * } - * ``` - */ - public static void simulateUserBrowsing() { - for (int i = 0; i < 3; i++) { - ACTIONS.sendKeys(" ").perform(); - SeleniumUtil.sleep(1); - } - ACTIONS.keyDown(Keys.CONTROL) - .sendKeys(Keys.HOME) - .keyUp(Keys.CONTROL) - .perform(); - SeleniumUtil.sleep(1); - } - - /** - * 关闭当前标签页并返回到指定的标签页 - * - * 替代原始代码: - * ```java - * private static void closeWindow(ArrayList tabs) { - * SeleniumUtil.sleep(1); - * CHROME_DRIVER.close(); - * CHROME_DRIVER.switchTo().window(tabs.get(0)); - * } - * ``` - * - * @param tabs 标签页列表 - * @param tabIndex 要切换到的标签页索引 - */ - public static void closeCurrentTabAndSwitchTo(ArrayList tabs, int tabIndex) { - SeleniumUtil.sleep(1); - CHROME_DRIVER.close(); - CHROME_DRIVER.switchTo().window(tabs.get(tabIndex)); - } - - /** - * 在新标签页中打开链接 - * - * 替代原始代码: - * ```java - * JavascriptExecutor jse = CHROME_DRIVER; - * jse.executeScript("var newTab = window.open(arguments[0], '_blank'); - * newTab.blur(); window.focus();", job.getHref()); - * ArrayList tabs = new ArrayList<>(CHROME_DRIVER.getWindowHandles()); - * CHROME_DRIVER.switchTo().window(tabs.getLast()); - * ``` - * - * @param url 要打开的链接 - * @return 所有标签页的句柄列表 - */ - public static ArrayList openLinkInNewTab(String url) { - JavascriptExecutor jse = CHROME_DRIVER; - // 使用JavaScript控制焦点,避免了每次打开新页签时浏览器窗口自动切换到前台的问题 - jse.executeScript("var newTab = window.open(arguments[0], '_blank'); newTab.blur(); window.focus();", url); - // 获取所有标签页句柄 - ArrayList tabs = new ArrayList<>(CHROME_DRIVER.getWindowHandles()); - // 切换到新标签页 - CHROME_DRIVER.switchTo().window(tabs.getLast()); - return tabs; - } - - /** - * 向下滚动聊天记录页面,直到加载全部内容 - * - * 替代原始代码: - * ```java - * JavascriptExecutor js = CHROME_DRIVER; - * boolean shouldBreak = false; - * while (!shouldBreak) { - * try { - * WebElement bottom = - * CHROME_DRIVER.findElement(By.xpath("//div[@class='finished']")); - * if ("没有更多了".equals(bottom.getText())) { - * shouldBreak = true; - * } - * } catch (Exception ignore) {} - * - * WebElement element; - * try { - * WAIT.until(ExpectedConditions.presenceOfElementLocated(By.xpath("//div[contains(text(), - * '滚动加载更多')]"))); - * element = CHROME_DRIVER.findElement(By.xpath("//div[contains(text(), - * '滚动加载更多')]")); - * } catch (Exception e) { - * break; - * } - * - * if (element != null) { - * js.executeScript("arguments[0].scrollIntoView();", element); - * } else { - * js.executeScript("window.scrollTo(0, document.body.scrollHeight);"); - * } - * } - * ``` - */ - public static void scrollChatListUntilFinished() { - JavascriptExecutor js = CHROME_DRIVER; - boolean shouldBreak = false; - - while (!shouldBreak) { - try { - Optional finishedElement = Finder.findElement(Locators.FINISHED_TEXT); - if (finishedElement.isPresent() && "没有更多了".equals(finishedElement.get().getText())) { - shouldBreak = true; - } - } catch (Exception ignore) { - // 未找到底部标识,继续滚动 - } - - // 尝试查找"滚动加载更多"元素 - Optional loadMoreElement = Finder.findElement(Locators.SCROLL_LOAD_MORE); - - if (loadMoreElement.isPresent()) { - try { - js.executeScript("arguments[0].scrollIntoView();", loadMoreElement.get()); - SeleniumUtil.sleep(1); - } catch (Exception e) { - log.error("滚动到元素出错", e); - // 尝试滚动到页面底部 - js.executeScript("window.scrollTo(0, document.body.scrollHeight);"); - } - } else { - try { - js.executeScript("window.scrollTo(0, document.body.scrollHeight);"); - SeleniumUtil.sleep(1); - } catch (Exception e) { - log.error("滚动到页面底部出错", e); - break; - } - } - - // 防止无限循环,给一个额外的检查 - List items = Finder.findElements(Locators.CHAT_LIST_ITEM); - if (items.isEmpty()) { - log.info("没有找到聊天记录项,停止滚动"); - break; - } - } - } - - /** - * 发送简历图片 - * - * 替代原始代码: - * ```java - * public static Boolean sendResume(String company) { - * if (!config.getSendImgResume()) { - * return false; - * } - * try { - * URL resourceUrl = Boss.class.getResource("/resume.jpg"); - * if (resourceUrl == null) { - * return false; - * } - * File imageFile = new File(resourceUrl.toURI()); - * if (!imageFile.exists()) { - * return false; - * } - * WebElement fileInput = - * CHROME_DRIVER.findElement(By.xpath("//div[@aria-label='发送图片']//input[@type='file']")); - * fileInput.sendKeys(imageFile.getAbsolutePath()); - * return true; - * } catch (Exception e) { - * log.error("发送简历图片时出错:{}", e.getMessage()); - * return false; - * } - * } - * ``` - * - * @param imagePath 简历图片路径 - * @return 是否发送成功 - */ - public static boolean sendResumeImage(String imagePath) { - try { - Optional fileInput = Finder.findElement(Locators.IMAGE_UPLOAD); - if (fileInput.isPresent()) { - fileInput.get().sendKeys(imagePath); - return true; - } - return false; - } catch (Exception e) { - log.error("发送简历图片时出错:{}", e.getMessage()); - return false; - } - } -} \ No newline at end of file diff --git a/src/main/java/utils/Platform.java b/src/main/java/utils/Platform.java deleted file mode 100644 index c546662b..00000000 --- a/src/main/java/utils/Platform.java +++ /dev/null @@ -1,27 +0,0 @@ -package utils; - -import lombok.Getter; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -@Getter -public enum Platform { - ZHILIAN("智联招聘"), - BOSS("Boss直聘"), - LIEPIN("猎聘"), - JOB51("前程无忧"), - LAGOU("拉勾网"), - UNKNOWN("未知平台"); - - // 获取枚举值的描述 - private final String platformName; - - // 构造函数 - Platform(String platformName) { - this.platformName = platformName; - } - -} - diff --git a/src/main/java/utils/PlaywrightUtil.java b/src/main/java/utils/PlaywrightUtil.java deleted file mode 100644 index 93b7fa9c..00000000 --- a/src/main/java/utils/PlaywrightUtil.java +++ /dev/null @@ -1,950 +0,0 @@ -package utils; - -import com.microsoft.playwright.*; -import com.microsoft.playwright.options.Cookie; -import com.microsoft.playwright.options.LoadState; -import com.microsoft.playwright.options.SelectOption; -import org.json.JSONArray; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.FileWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.TimeUnit; -import java.util.Optional; - -/** - * Playwright工具类,提供浏览器自动化相关的功能 - */ -public class PlaywrightUtil { - private static final Logger log = LoggerFactory.getLogger(PlaywrightUtil.class); - - /** - * 设备类型枚举 - */ - public enum DeviceType { - DESKTOP, // 桌面设备 - MOBILE // 移动设备 - } - - // 默认设备类型 - private static DeviceType defaultDeviceType = DeviceType.DESKTOP; - - // Playwright实例 - private static Playwright PLAYWRIGHT; - - // 浏览器实例 - private static Browser BROWSER; - - // 桌面浏览器上下文 - private static BrowserContext DESKTOP_CONTEXT; - - // 移动设备浏览器上下文 - private static BrowserContext MOBILE_CONTEXT; - - // 桌面浏览器页面 - private static Page DESKTOP_PAGE; - - // 移动设备浏览器页面 - private static Page MOBILE_PAGE; - - // 默认超时时间(毫秒) - private static final int DEFAULT_TIMEOUT = 30000; - - // 默认等待时间(毫秒) - private static final int DEFAULT_WAIT_TIME = 10000; - - /** - * 初始化Playwright及浏览器实例 - */ - public static void init() { - // 启动Playwright - PLAYWRIGHT = Playwright.create(); - - // 创建浏览器实例 - BROWSER = PLAYWRIGHT.chromium().launch(new BrowserType.LaunchOptions() - .setHeadless(false) // 非无头模式,可视化调试 - .setSlowMo(50)); // 放慢操作速度,便于调试 - - // 创建桌面浏览器上下文 - DESKTOP_CONTEXT = BROWSER.newContext(new Browser.NewContextOptions() - .setViewportSize(1920, 1080) - .setUserAgent( - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36")); - - // 创建移动设备浏览器上下文 - MOBILE_CONTEXT = BROWSER.newContext(new Browser.NewContextOptions() - .setViewportSize(375, 812) - .setDeviceScaleFactor(3.0) - .setIsMobile(true) - .setHasTouch(true) - .setUserAgent( - "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1")); - - // 创建桌面页面 - DESKTOP_PAGE = DESKTOP_CONTEXT.newPage(); - DESKTOP_PAGE.setDefaultTimeout(DEFAULT_TIMEOUT); - -// // 启用JavaScript捕获控制台日志(用于调试) -// DESKTOP_PAGE.onConsoleMessage(message -> { -// if (message.type().equals("error")) { -// log.error("Browser console error: {}", message.text()); -// } -// }); - } - - /** - * 设置默认设备类型 - * - * @param deviceType 设备类型 - */ - public static void setDefaultDeviceType(DeviceType deviceType) { - defaultDeviceType = deviceType; - log.info("已设置默认设备类型为: {}", deviceType); - } - - /** - * 获取当前页面(基于当前设备类型) - * - * @param deviceType 设备类型 - * @return 对应的Page对象 - */ - private static Page getPage(DeviceType deviceType) { - return deviceType == DeviceType.DESKTOP ? DESKTOP_PAGE : MOBILE_PAGE; - } - - /** - * 获取当前上下文(基于当前设备类型) - * - * @param deviceType 设备类型 - * @return 对应的BrowserContext对象 - */ - private static BrowserContext getContext(DeviceType deviceType) { - return deviceType == DeviceType.DESKTOP ? DESKTOP_CONTEXT : MOBILE_CONTEXT; - } - - /** - * 关闭Playwright及浏览器实例 - */ - public static void close() { - if (DESKTOP_PAGE != null) - DESKTOP_PAGE.close(); - if (MOBILE_PAGE != null) - MOBILE_PAGE.close(); - if (DESKTOP_CONTEXT != null) - DESKTOP_CONTEXT.close(); - if (MOBILE_CONTEXT != null) - MOBILE_CONTEXT.close(); - if (BROWSER != null) - BROWSER.close(); - if (PLAYWRIGHT != null) - PLAYWRIGHT.close(); - - log.info("Playwright及浏览器实例已关闭"); - } - - /** - * 导航到指定URL - * - * @param url 目标URL - * @param deviceType 设备类型 - */ - public static void navigate(String url, DeviceType deviceType) { - getPage(deviceType).navigate(url); - log.info("已导航到URL: {} (设备类型: {})", url, deviceType); - } - - /** - * 使用默认设备类型导航到指定URL - * - * @param url 目标URL - */ - public static void navigate(String url) { - navigate(url, defaultDeviceType); - } - - /** - * 移动设备导航到指定URL (兼容旧代码) - * - * @param url 目标URL - */ - public static void mobileNavigate(String url) { - navigate(url, DeviceType.MOBILE); - } - - /** - * 等待指定时间(秒) - * - * @param seconds 等待的秒数 - */ - public static void sleep(int seconds) { - try { - TimeUnit.SECONDS.sleep(seconds); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - log.error("Sleep被中断", e); - } - } - - /** - * 等待指定时间(毫秒) - * - * @param millis 等待的毫秒数 - */ - public static void sleepMillis(int millis) { - try { - TimeUnit.MILLISECONDS.sleep(millis); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - log.error("Sleep被中断", e); - } - } - - /** - * 兼容SeleniumUtil的sleepByMilliSeconds方法 - * - * @param milliSeconds 等待的毫秒数 - */ - public static void sleepByMilliSeconds(int milliSeconds) { - sleepMillis(milliSeconds); - } - - /** - * 查找元素 - * - * @param selector 元素选择器 - * @param deviceType 设备类型 - * @return 元素对象,如果未找到则返回null - */ - public static Locator findElement(String selector, DeviceType deviceType) { - return getPage(deviceType).locator(selector); - } - - /** - * 使用默认设备类型查找元素 - * - * @param selector 元素选择器 - * @return 元素对象,如果未找到则返回null - */ - public static Locator findElement(String selector) { - return findElement(selector, defaultDeviceType); - } - - /** - * 查找元素并等待直到可见 - * - * @param selector 元素选择器 - * @param timeout 超时时间(毫秒) - * @param deviceType 设备类型 - * @return 元素对象,如果未找到则返回null - */ - public static Locator waitForElement(String selector, int timeout, DeviceType deviceType) { - Locator locator = getPage(deviceType).locator(selector); - locator.waitFor(new Locator.WaitForOptions().setTimeout(timeout)); - return locator; - } - - /** - * 使用默认设备类型查找元素并等待直到可见 - * - * @param selector 元素选择器 - * @param timeout 超时时间(毫秒) - * @return 元素对象,如果未找到则返回null - */ - public static Locator waitForElement(String selector, int timeout) { - return waitForElement(selector, timeout, defaultDeviceType); - } - - /** - * 使用默认超时时间和默认设备类型等待元素 - * - * @param selector 元素选择器 - * @return 元素对象,如果未找到则返回null - */ - public static Locator waitForElement(String selector) { - return waitForElement(selector, DEFAULT_WAIT_TIME, defaultDeviceType); - } - - /** - * 点击元素 - * - * @param selector 元素选择器 - * @param deviceType 设备类型 - */ - public static void click(String selector, DeviceType deviceType) { - try { - getPage(deviceType).locator(selector).click(); - log.info("已点击元素: {} (设备类型: {})", selector, deviceType); - } catch (PlaywrightException e) { - log.error("点击元素失败: {} (设备类型: {})", selector, deviceType, e); - } - } - - /** - * 使用默认设备类型点击元素 - * - * @param selector 元素选择器 - */ - public static void click(String selector) { - click(selector, defaultDeviceType); - } - - /** - * 填写表单字段 - * - * @param selector 元素选择器 - * @param text 要输入的文本 - * @param deviceType 设备类型 - */ - public static void fill(String selector, String text, DeviceType deviceType) { - try { - getPage(deviceType).locator(selector).fill(text); - log.info("已在元素{}中输入文本 (设备类型: {})", selector, deviceType); - } catch (PlaywrightException e) { - log.error("填写表单失败: {} (设备类型: {})", selector, deviceType, e); - } - } - - /** - * 使用默认设备类型填写表单字段 - * - * @param selector 元素选择器 - * @param text 要输入的文本 - */ - public static void fill(String selector, String text) { - fill(selector, text, defaultDeviceType); - } - - /** - * 模拟人类输入文本(逐字输入) - * - * @param selector 元素选择器 - * @param text 要输入的文本 - * @param minDelay 字符间最小延迟(毫秒) - * @param maxDelay 字符间最大延迟(毫秒) - * @param deviceType 设备类型 - */ - public static void typeHumanLike(String selector, String text, int minDelay, int maxDelay, DeviceType deviceType) { - try { - Locator locator = getPage(deviceType).locator(selector); - locator.click(); - - Random random = new Random(); - for (char c : text.toCharArray()) { - // 计算本次字符输入的延迟时间 - int delay = random.nextInt(maxDelay - minDelay + 1) + minDelay; - - // 输入单个字符 - locator.pressSequentially(String.valueOf(c), - new Locator.PressSequentiallyOptions().setDelay(delay)); - } - log.info("已模拟人类在元素{}中输入文本 (设备类型: {})", selector, deviceType); - } catch (PlaywrightException e) { - log.error("模拟人类输入失败: {} (设备类型: {})", selector, deviceType, e); - } - } - - /** - * 使用默认设备类型模拟人类输入文本 - * - * @param selector 元素选择器 - * @param text 要输入的文本 - * @param minDelay 字符间最小延迟(毫秒) - * @param maxDelay 字符间最大延迟(毫秒) - */ - public static void typeHumanLike(String selector, String text, int minDelay, int maxDelay) { - typeHumanLike(selector, text, minDelay, maxDelay, defaultDeviceType); - } - - /** - * 获取元素文本 - * - * @param selector 元素选择器 - * @param deviceType 设备类型 - * @return 元素文本内容 - */ - public static String getText(String selector, DeviceType deviceType) { - try { - return getPage(deviceType).locator(selector).textContent(); - } catch (PlaywrightException e) { - log.error("获取元素文本失败: {} (设备类型: {})", selector, deviceType, e); - return ""; - } - } - - /** - * 使用默认设备类型获取元素文本 - * - * @param selector 元素选择器 - * @return 元素文本内容 - */ - public static String getText(String selector) { - return getText(selector, defaultDeviceType); - } - - /** - * 获取元素属性值 - * - * @param selector 元素选择器 - * @param attributeName 属性名 - * @param deviceType 设备类型 - * @return 属性值 - */ - public static String getAttribute(String selector, String attributeName, DeviceType deviceType) { - try { - return getPage(deviceType).locator(selector).getAttribute(attributeName); - } catch (PlaywrightException e) { - log.error("获取元素属性失败: {}[{}] (设备类型: {})", selector, attributeName, deviceType, e); - return ""; - } - } - - /** - * 使用默认设备类型获取元素属性值 - * - * @param selector 元素选择器 - * @param attributeName 属性名 - * @return 属性值 - */ - public static String getAttribute(String selector, String attributeName) { - return getAttribute(selector, attributeName, defaultDeviceType); - } - - /** - * 截取页面截图并保存 - * - * @param path 保存路径 - * @param deviceType 设备类型 - */ - public static void screenshot(String path, DeviceType deviceType) { - try { - getPage(deviceType).screenshot(new Page.ScreenshotOptions().setPath(Paths.get(path))); - log.info("已保存截图到: {} (设备类型: {})", path, deviceType); - } catch (PlaywrightException e) { - log.error("截图失败 (设备类型: {})", deviceType, e); - } - } - - /** - * 使用默认设备类型截取页面截图并保存 - * - * @param path 保存路径 - */ - public static void screenshot(String path) { - screenshot(path, defaultDeviceType); - } - - /** - * 截取特定元素的截图 - * - * @param selector 元素选择器 - * @param path 保存路径 - * @param deviceType 设备类型 - */ - public static void screenshotElement(String selector, String path, DeviceType deviceType) { - try { - getPage(deviceType).locator(selector).screenshot(new Locator.ScreenshotOptions().setPath(Paths.get(path))); - log.info("已保存元素截图到: {} (设备类型: {})", path, deviceType); - } catch (PlaywrightException e) { - log.error("元素截图失败: {} (设备类型: {})", selector, deviceType, e); - } - } - - /** - * 使用默认设备类型截取特定元素的截图 - * - * @param selector 元素选择器 - * @param path 保存路径 - */ - public static void screenshotElement(String selector, String path) { - screenshotElement(selector, path, defaultDeviceType); - } - - /** - * 保存Cookie到文件 - * - * @param path 保存路径 - * @param deviceType 设备类型 - */ - public static void saveCookies(String path, DeviceType deviceType) { - try { - List cookies = getContext(deviceType).cookies(); - JSONArray jsonArray = new JSONArray(); - - for (Cookie cookie : cookies) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("name", cookie.name); - jsonObject.put("value", cookie.value); - jsonObject.put("domain", cookie.domain); - jsonObject.put("path", cookie.path); - if (cookie.expires != null) { - jsonObject.put("expires", cookie.expires); - } - jsonObject.put("secure", cookie.secure); - jsonObject.put("httpOnly", cookie.httpOnly); - jsonArray.put(jsonObject); - } - - try (FileWriter file = new FileWriter(path)) { - file.write(jsonArray.toString(4)); - log.info("Cookie已保存到文件: {} (设备类型: {})", path, deviceType); - } - } catch (IOException e) { - log.error("保存Cookie失败 (设备类型: {})", deviceType, e); - } - } - - /** - * 使用默认设备类型保存Cookie到文件 - * - * @param path 保存路径 - */ - public static void saveCookies(String path) { - saveCookies(path, defaultDeviceType); - } - - /** - * 从文件加载Cookie - * - * @param path Cookie文件路径 - * @param deviceType 设备类型 - */ - public static void loadCookies(String path, DeviceType deviceType) { - try { - String jsonText = new String(Files.readAllBytes(Paths.get(path))); - JSONArray jsonArray = new JSONArray(jsonText); - - List cookies = new ArrayList<>(); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject jsonObject = jsonArray.getJSONObject(i); - - com.microsoft.playwright.options.Cookie cookie = new com.microsoft.playwright.options.Cookie( - jsonObject.getString("name"), - jsonObject.getString("value")); - - if (!jsonObject.isNull("domain")) { - cookie.domain = jsonObject.getString("domain"); - } - - if (!jsonObject.isNull("path")) { - cookie.path = jsonObject.getString("path"); - } - - if (!jsonObject.isNull("expires")) { - cookie.expires = jsonObject.getDouble("expires"); - } - - if (!jsonObject.isNull("secure")) { - cookie.secure = jsonObject.getBoolean("secure"); - } - - if (!jsonObject.isNull("httpOnly")) { - cookie.httpOnly = jsonObject.getBoolean("httpOnly"); - } - - cookies.add(cookie); - } - - getContext(deviceType).addCookies(cookies); - log.info("已从文件加载Cookie: {} (设备类型: {})", path, deviceType); - } catch (IOException e) { - log.error("加载Cookie失败 (设备类型: {})", deviceType, e); - } - } - - /** - * 使用默认设备类型从文件加载Cookie - * - * @param path Cookie文件路径 - */ - public static void loadCookies(String path) { - loadCookies(path, defaultDeviceType); - } - - /** - * 执行JavaScript代码 - * - * @param script JavaScript代码 - * @param deviceType 设备类型 - */ - public static void evaluate(String script, DeviceType deviceType) { - try { - getPage(deviceType).evaluate(script); - } catch (PlaywrightException e) { - log.error("执行JavaScript失败 (设备类型: {})", deviceType, e); - } - } - - /** - * 使用默认设备类型执行JavaScript代码 - * - * @param script JavaScript代码 - */ - public static void evaluate(String script) { - evaluate(script, defaultDeviceType); - } - - /** - * 等待页面加载完成 - * - * @param deviceType 设备类型 - */ - public static void waitForPageLoad(DeviceType deviceType) { - getPage(deviceType).waitForLoadState(LoadState.DOMCONTENTLOADED); - getPage(deviceType).waitForLoadState(LoadState.NETWORKIDLE); - } - - /** - * 检查元素是否可见 - * - * @param selector 元素选择器 - * @param deviceType 设备类型 - * @return 是否可见 - */ - public static boolean elementIsVisible(String selector, DeviceType deviceType) { - try { - return getPage(deviceType).locator(selector).isVisible(); - } catch (PlaywrightException e) { - return false; - } - } - - /** - * 使用默认设备类型检查元素是否可见 - * - * @param selector 元素选择器 - * @return 是否可见 - */ - public static boolean elementIsVisible(String selector) { - return elementIsVisible(selector, defaultDeviceType); - } - - /** - * 选择下拉列表选项(通过文本) - * - * @param selector 选择器 - * @param optionText 选项文本 - * @param deviceType 设备类型 - */ - public static void selectByText(String selector, String optionText, DeviceType deviceType) { - getPage(deviceType).locator(selector).selectOption(new SelectOption().setLabel(optionText)); - } - - /** - * 使用默认设备类型选择下拉列表选项(通过文本) - * - * @param selector 选择器 - * @param optionText 选项文本 - */ - public static void selectByText(String selector, String optionText) { - selectByText(selector, optionText, defaultDeviceType); - } - - /** - * 选择下拉列表选项(通过值) - * - * @param selector 选择器 - * @param value 选项值 - * @param deviceType 设备类型 - */ - public static void selectByValue(String selector, String value, DeviceType deviceType) { - getPage(deviceType).locator(selector).selectOption(new SelectOption().setValue(value)); - } - - /** - * 使用默认设备类型选择下拉列表选项(通过值) - * - * @param selector 选择器 - * @param value 选项值 - */ - public static void selectByValue(String selector, String value) { - selectByValue(selector, value, defaultDeviceType); - } - - /** - * 获取当前页面标题 - * - * @param deviceType 设备类型 - * @return 页面标题 - */ - public static String getTitle(DeviceType deviceType) { - return getPage(deviceType).title(); - } - - /** - * 使用默认设备类型获取当前页面标题 - * - * @return 页面标题 - */ - public static String getTitle() { - return getTitle(defaultDeviceType); - } - - /** - * 获取当前页面URL - * - * @param deviceType 设备类型 - * @return 页面URL - */ - public static String getUrl(DeviceType deviceType) { - return getPage(deviceType).url(); - } - - /** - * 使用默认设备类型获取当前页面URL - * - * @return 页面URL - */ - public static String getUrl() { - return getUrl(defaultDeviceType); - } - - /** - * 初始化Stealth模式(使浏览器更难被检测为自动化工具) - * 增强版本,集成SeleniumUtil的反检测功能 - * - * @param deviceType 设备类型 - */ - public static void initStealth(DeviceType deviceType) { - // 获取当前页面,不重新创建上下文和页面 - Page page = getPage(deviceType); - - // 为现有上下文设置额外的HTTP头 - BrowserContext context = getContext(deviceType); - if (deviceType == DeviceType.DESKTOP) { - context.setExtraHTTPHeaders(Map.of( - "sec-ch-ua", "\"Google Chrome\";v=\"135\", \"Not-A.Brand\";v=\"8\", \"Chromium\";v=\"135\"", - "sec-ch-ua-mobile", "?0", - "sec-ch-ua-platform", "\"macOS\"", - "accept-language", "zh-CN,zh;q=0.9", - "referer", "https://www.zhipin.com/", - "sec-fetch-dest", "document", - "sec-fetch-mode", "navigate", - "sec-fetch-site", "same-origin")); - } else { - context.setExtraHTTPHeaders(Map.of( - "sec-ch-ua", "\"Chromium\";v=\"135\", \"Not A(Brand\";v=\"99\"", - "sec-ch-ua-mobile", "?1", - "sec-ch-ua-platform", "\"iOS\"", - "accept-language", "zh-CN,zh;q=0.9", - "sec-fetch-dest", "document", - "sec-fetch-mode", "navigate", - "sec-fetch-site", "same-origin")); - } - - // 注入反检测脚本(从SeleniumUtil移植) - String stealthScript = """ - Object.defineProperty(navigator, 'webdriver', {get: () => undefined}); - delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array; - delete window.cdc_adoQpoasnfa76pfcZLmcfl_JSON; - delete window.cdc_adoQpoasnfa76pfcZLmcfl_Object; - delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise; - delete window.cdc_adoQpoasnfa76pfcZLmcfl_Proxy; - delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol; - delete window.cdc_adoQpoasnfa76pfcZLmcfl_Window; - window.navigator.chrome = { runtime: {} }; - Object.defineProperty(navigator, 'languages', {get: () => ['zh-CN', 'zh']}); - Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3]}); - Object.defineProperty(navigator, 'injected', {get: () => 123}); - """; - - page.addInitScript(stealthScript); - - // 如果有stealth.min.js文件,也尝试加载 - try { - String stealthJs = new String( - Files.readAllBytes(Paths.get("src/main/resources/stealth.min.js"))); - page.addInitScript(stealthJs); - log.info("已加载stealth.min.js文件"); - } catch (IOException e) { - log.info("未找到stealth.min.js文件,使用内置反检测脚本"); - } - log.info("已启用增强Stealth模式 (设备类型: {})", deviceType); - } - - /** - * 使用默认设备类型初始化Stealth模式 - */ - public static void initStealth() { - initStealth(defaultDeviceType); - } - - /** - * 设置默认请求头(从SeleniumUtil移植) - * - * @param deviceType 设备类型 - */ - public static void setDefaultHeaders(DeviceType deviceType) { - BrowserContext context = getContext(deviceType); - - Map headers = Map.of( - "sec-ch-ua", "\"Google Chrome\";v=\"135\", \"Not-A.Brand\";v=\"8\", \"Chromium\";v=\"135\"", - "sec-ch-ua-mobile", deviceType == DeviceType.MOBILE ? "?1" : "?0", - "sec-ch-ua-platform", deviceType == DeviceType.MOBILE ? "\"iOS\"" : "\"macOS\"", - "user-agent", deviceType == DeviceType.MOBILE ? - "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" : - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36", - "accept-language", "zh-CN,zh;q=0.9", - "referer", "https://www.zhipin.com/" - ); - - context.setExtraHTTPHeaders(headers); - log.info("已设置默认请求头 (设备类型: {})", deviceType); - } - - /** - * 使用默认设备类型设置默认请求头 - */ - public static void setDefaultHeaders() { - setDefaultHeaders(defaultDeviceType); - } - - /** - * 获取当前设备类型的Page对象 - * - * @param deviceType 设备类型 - * @return 对应的Page对象 - */ - public static Page getPageObject(DeviceType deviceType) { - return deviceType == DeviceType.DESKTOP ? DESKTOP_PAGE : MOBILE_PAGE; - } - - /** - * 使用默认设备类型获取Page对象 - * - * @return 对应的Page对象 - */ - public static Page getPageObject() { - return getPageObject(defaultDeviceType); - } - - /** - * 设置自定义Cookie - * - * @param name Cookie名称 - * @param value Cookie值 - * @param domain Cookie域 - * @param path Cookie路径 - * @param expires 过期时间(可选) - * @param secure 是否安全(可选) - * @param httpOnly 是否仅HTTP(可选) - * @param deviceType 设备类型 - */ - public static void setCookie(String name, String value, String domain, String path, - Double expires, Boolean secure, Boolean httpOnly, DeviceType deviceType) { - com.microsoft.playwright.options.Cookie cookie = new com.microsoft.playwright.options.Cookie(name, value); - cookie.domain = domain; - cookie.path = path; - - if (expires != null) { - cookie.expires = expires; - } - - if (secure != null) { - cookie.secure = secure; - } - - if (httpOnly != null) { - cookie.httpOnly = httpOnly; - } - - List cookies = new ArrayList<>(); - cookies.add(cookie); - - getContext(deviceType).addCookies(cookies); - log.info("已设置Cookie: {} (设备类型: {})", name, deviceType); - } - - /** - * 使用默认设备类型设置自定义Cookie - * - * @param name Cookie名称 - * @param value Cookie值 - * @param domain Cookie域 - * @param path Cookie路径 - * @param expires 过期时间(可选) - * @param secure 是否安全(可选) - * @param httpOnly 是否仅HTTP(可选) - */ - public static void setCookie(String name, String value, String domain, String path, - Double expires, Boolean secure, Boolean httpOnly) { - setCookie(name, value, domain, path, expires, secure, httpOnly, defaultDeviceType); - } - - /** - * 简化的设置Cookie方法 - * - * @param name Cookie名称 - * @param value Cookie值 - * @param domain Cookie域 - * @param path Cookie路径 - * @param deviceType 设备类型 - */ - public static void setCookie(String name, String value, String domain, String path, DeviceType deviceType) { - setCookie(name, value, domain, path, null, null, null, deviceType); - } - - /** - * 使用默认设备类型的简化设置Cookie方法 - * - * @param name Cookie名称 - * @param value Cookie值 - * @param domain Cookie域 - * @param path Cookie路径 - */ - public static void setCookie(String name, String value, String domain, String path) { - setCookie(name, value, domain, path, null, null, null, defaultDeviceType); - } - - /** - * 检查Cookie文件是否有效(从SeleniumUtil移植) - * - * @param cookiePath Cookie文件路径 - * @return 文件是否存在 - */ - public static boolean isCookieValid(String cookiePath) { - return Files.exists(Paths.get(cookiePath)); - } - - /** - * 带错误消息的元素查找(从SeleniumUtil移植) - * - * @param selector 元素选择器 - * @param message 错误消息 - * @param deviceType 设备类型 - * @return 元素对象的Optional包装 - */ - public static Optional findElementWithMessage(String selector, String message, DeviceType deviceType) { - try { - Locator locator = getPage(deviceType).locator(selector); - // 检查元素是否存在 - if (locator.count() > 0) { - return Optional.of(locator); - } else { - log.error(message); - return Optional.empty(); - } - } catch (Exception e) { - log.error(message + ": " + e.getMessage()); - return Optional.empty(); - } - } - - /** - * 使用默认设备类型的带错误消息的元素查找 - * - * @param selector 元素选择器 - * @param message 错误消息 - * @return 元素对象的Optional包装 - */ - public static Optional findElementWithMessage(String selector, String message) { - return findElementWithMessage(selector, message, defaultDeviceType); - } -} \ No newline at end of file diff --git a/src/main/java/utils/SeleniumUtil.java b/src/main/java/utils/SeleniumUtil.java deleted file mode 100644 index 43c779f2..00000000 --- a/src/main/java/utils/SeleniumUtil.java +++ /dev/null @@ -1,341 +0,0 @@ -package utils; - -import boss.BossConfig; -import org.json.JSONArray; -import org.json.JSONObject; -import org.openqa.selenium.By; -import org.openqa.selenium.Cookie; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.chrome.ChromeDriver; -import org.openqa.selenium.chrome.ChromeOptions; -import org.openqa.selenium.devtools.DevTools; -import org.openqa.selenium.devtools.v135.network.Network; -import org.openqa.selenium.devtools.v135.network.model.Headers; -import org.openqa.selenium.devtools.v135.page.Page; -import org.openqa.selenium.interactions.Actions; -import org.openqa.selenium.support.ui.WebDriverWait; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.awt.*; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.time.Duration; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.*; -import java.util.concurrent.TimeUnit; - -import static utils.Constant.*; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -public class SeleniumUtil { - private static final Logger log = LoggerFactory.getLogger(SeleniumUtil.class); - - public static void initDriver(boolean mobile) { - SeleniumUtil.getChromeDriver(mobile); - SeleniumUtil.getActions(); - SeleniumUtil.getWait(WAIT_TIME); - } - - public static void initDriver() { - SeleniumUtil.getChromeDriver(); - SeleniumUtil.getActions(); - SeleniumUtil.getWait(WAIT_TIME); - } - - public static void getChromeDriver() { - getChromeDriver(false); - } - - public static void getChromeDriver(Boolean mobile) { - ChromeOptions options = new ChromeOptions(); - // 添加扩展插件 - String osName = System.getProperty("os.name").toLowerCase(); - KeyUtil.printLog(); - log.info("当前操作系统为【{}】", osName); - String osType = getOSType(osName); - switch (osType) { - case "windows": - options.setBinary("C:/Program Files/Google/Chrome/Application/chrome.exe"); - System.setProperty("webdriver.chrome.driver", "src/main/resources/chromedriver.exe"); - break; - case "mac": - options.setBinary("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"); - System.setProperty("webdriver.chrome.driver", "src/main/resources/chromedriver"); - break; - case "linux": - options.setBinary("/usr/bin/google-chrome-stable"); - System.setProperty("webdriver.chrome.driver", "src/main/resources/chromedriver-linux64/chromedriver"); - break; - default: - log.info("你这什么破系统,没见过,别跑了!"); - break; - } - BossConfig config = BossConfig.init(); - if (config.getDebugger()) { - options.addExtensions(new File("src/main/resources/xpathHelper.crx")); - } else { - options.addArguments("--disable-extensions"); - } - GraphicsDevice[] screens = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices(); - if (screens.length > 1) { - options.addArguments("--window-position=2800,1000"); //将窗口移动到副屏的起始位置 - } -// options.addArguments("--headless"); //使用无头模式 - - options.setExperimentalOption("excludeSwitches", new String[]{"enable-automation"}); - options.setExperimentalOption("useAutomationExtension", false); // 禁用默认扩展 - options.addArguments("user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"); - - - CHROME_DRIVER = new ChromeDriver(options); - CHROME_DRIVER.manage().window().maximize(); - - - // 创建移动设备Chrome驱动 - ChromeOptions mobileOptions = new ChromeOptions(); - addMobileEmulationOptions(mobileOptions); - - if (mobile) { - MOBILE_CHROME_DRIVER = new ChromeDriver(mobileOptions); - MOBILE_CHROME_DRIVER.manage().window().maximize(); - } - - } - - /** - * 添加移动设备模拟配置到ChromeOptions - * - * @param options ChromeOptions对象 - */ - private static void addMobileEmulationOptions(ChromeOptions options) { - // 添加移动设备模拟配置 - Map mobileEmulation = new HashMap<>(); - mobileEmulation.put("deviceName", "iPhone X"); - // 如果需要自定义设备参数,可以使用下面的配置替代deviceName - // Map deviceMetrics = new HashMap<>(); - // deviceMetrics.put("width", 375); - // deviceMetrics.put("height", 812); - // deviceMetrics.put("pixelRatio", 3.0); - // mobileEmulation.put("deviceMetrics", deviceMetrics); - // mobileEmulation.put("userAgent", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1"); - - options.setExperimentalOption("mobileEmulation", mobileEmulation); - - options.addArguments("--disable-features=ExternalProtocolDialog"); // 禁用弹窗(部分版本有效) - } - - private static String getOSType(String osName) { - if (osName.contains("win")) { - return "windows"; - } - if (osName.contains("linux")) { - return "linux"; - } - if (osName.contains("mac") || osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) { - return "mac"; - } - return "unknown"; - } - - public static void saveCookie(String path) { - // 获取所有的cookies - Set cookies = CHROME_DRIVER.manage().getCookies(); - // 创建一个JSONArray来保存所有的cookie信息 - JSONArray jsonArray = new JSONArray(); - // 将每个cookie转换为一个JSONObject,并添加到JSONArray中 - for (Cookie cookie : cookies) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("name", cookie.getName()); - jsonObject.put("value", cookie.getValue()); - jsonObject.put("domain", cookie.getDomain()); - jsonObject.put("path", cookie.getPath()); - if (cookie.getExpiry() != null) { - jsonObject.put("expiry", cookie.getExpiry().getTime()); - } - jsonObject.put("isSecure", cookie.isSecure()); - jsonObject.put("isHttpOnly", cookie.isHttpOnly()); - jsonArray.put(jsonObject); - } - // 将JSONArray写入到一个文件中 - saveCookieToFile(jsonArray, path); - } - - private static void saveCookieToFile(JSONArray jsonArray, String path) { - // 将JSONArray写入到一个文件中 - try (FileWriter file = new FileWriter(path)) { - file.write(jsonArray.toString(4)); // 使用4个空格的缩进 - log.info("Cookie已保存到文件:{}", path); - } catch (IOException e) { - log.error("保存cookie异常!保存路径:{}", path); - } - } - - private static void updateCookieFile(JSONArray jsonArray, String path) { - // 将JSONArray写入到一个文件中 - try (FileWriter file = new FileWriter(path)) { - file.write(jsonArray.toString(4)); // 使用4个空格的缩进 - log.info("cookie文件更新:{}", path); - } catch (IOException e) { - log.error("更新cookie异常!保存路径:{}", path); - } - } - - public static void loadCookie(String cookiePath) { - // 首先清除由于浏览器打开已有的cookies - CHROME_DRIVER.manage().deleteAllCookies(); - if (Objects.nonNull(MOBILE_CHROME_DRIVER)) { - MOBILE_CHROME_DRIVER.manage().deleteAllCookies(); - } - // 从文件中读取JSONArray - JSONArray jsonArray = null; - try { - String jsonText = new String(Files.readAllBytes(Paths.get(cookiePath))); - if (!jsonText.isEmpty()) { - jsonArray = new JSONArray(jsonText); - } - } catch (IOException e) { - log.error("读取cookie异常!"); - } - // 遍历JSONArray中的每个JSONObject,并从中获取cookie的信息 - if (jsonArray != null) { - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject jsonObject = jsonArray.getJSONObject(i); - String name = jsonObject.getString("name"); - String value = jsonObject.getString("value"); - String domain = jsonObject.getString("domain"); - String path = jsonObject.getString("path"); - Date expiry = null; - if (!jsonObject.isNull("expiry")) { - expiry = new Date(Instant.now().plus(7, ChronoUnit.DAYS).toEpochMilli()); - jsonObject.put("expiry", Instant.now().plus(7, ChronoUnit.DAYS).toEpochMilli()); // 更新expiry - } - boolean isSecure = jsonObject.getBoolean("isSecure"); - boolean isHttpOnly = jsonObject.getBoolean("isHttpOnly"); - // 使用这些信息来创建新的Cookie对象,并将它们添加到WebDriver中 - Cookie cookie = new Cookie.Builder(name, value) - .domain(domain) - .path(path) - .expiresOn(expiry) - .isSecure(isSecure) - .isHttpOnly(isHttpOnly) - .build(); - try { - CHROME_DRIVER.manage().addCookie(cookie); - if (Objects.nonNull(MOBILE_CHROME_DRIVER)) { - MOBILE_CHROME_DRIVER.manage().addCookie(cookie); - } - } catch (Exception ignore) { - } - } - // 更新cookie文件 - updateCookieFile(jsonArray, cookiePath); - } - } - - public static void getActions() { - ACTIONS = new Actions(Constant.CHROME_DRIVER); - if (Objects.nonNull(MOBILE_CHROME_DRIVER)) { - MOBILE_ACTIONS = new Actions(MOBILE_CHROME_DRIVER); - } - } - - public static void getWait(long time) { - WAIT = new WebDriverWait(Constant.CHROME_DRIVER, Duration.ofSeconds(time)); - if (Objects.nonNull(MOBILE_CHROME_DRIVER)) { - MOBILE_WAIT = new WebDriverWait(MOBILE_CHROME_DRIVER, Duration.ofSeconds(time)); - } - } - - public static void sleep(int seconds) { - try { - TimeUnit.SECONDS.sleep(seconds); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - log.error("Sleep was interrupted", e); - } - } - - public static void sleepByMilliSeconds(int milliSeconds) { - try { - TimeUnit.MILLISECONDS.sleep(milliSeconds); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - log.error("Sleep was interrupted", e); - } - } - - public static Optional findElement(String xpath, String message) { - try { - return Optional.of(CHROME_DRIVER.findElement(By.xpath(xpath))); - } catch (Exception e) { - log.error(message); - return Optional.empty(); - } - } - - public static void click(By by) { - try { - CHROME_DRIVER.findElement(by).click(); - } catch (Exception e) { - log.error("click element:{}", by, e); - } - } - - public static boolean isCookieValid(String cookiePath) { - return Files.exists(Paths.get(cookiePath)); - } - - /** - * 注入反自动化检测的脚本,隐藏 webdriver、语言、插件等特征字段。 - * 必须在 driver.get(url) 之前调用。 - */ - public static void injectStealthJs(DevTools devTools) { - String script = """ - Object.defineProperty(navigator, 'webdriver', {get: () => undefined}); - delete cdc_adoQpoasnfa76pfcZLmcfl_Array; - delete cdc_adoQpoasnfa76pfcZLmcfl_JSON; - delete cdc_adoQpoasnfa76pfcZLmcfl_Object; - delete cdc_adoQpoasnfa76pfcZLmcfl_Promise; - delete cdc_adoQpoasnfa76pfcZLmcfl_Proxy; - delete cdc_adoQpoasnfa76pfcZLmcfl_Symbol; - delete cdc_adoQpoasnfa76pfcZLmcfl_Window; - window.navigator.chrome = { runtime: {} }; - Object.defineProperty(navigator, 'languages', {get: () => ['zh-CN', 'zh']}); - Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3]}); - """; - - devTools.send(Page.addScriptToEvaluateOnNewDocument( - script, - Optional.empty(), - Optional.of(false), - Optional.of(true) - )); - } - - /** - * 设置标准防指纹伪装请求头(适配 Chrome 135,macOS 平台) - */ - public static void setDefaultHeaders(DevTools devTools) { - Map headersMap = new HashMap<>(); -// headersMap.put("sec-ch-ua", "\"Google Chrome\";v=\"135\", \"Chromium\";v=\"135\", \"Not;A=Brand\";v=\"99\""); - headersMap.put("sec-ch-ua", "\"Google Chrome\";v=\"135\", \"Not-A.Brand\";v=\"8\", \"Chromium\";v=\"135\""); - headersMap.put("sec-ch-ua-mobile", "?0"); - headersMap.put("sec-ch-ua-platform", "\"macOS\""); - headersMap.put("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"); - headersMap.put("accept-language", "zh-CN,zh;q=0.9"); - headersMap.put("referer", "https://www.zhipin.com/"); - - devTools.send(Network.enable(Optional.empty(), Optional.empty(), Optional.empty())); - devTools.send(Network.setExtraHTTPHeaders(new Headers(headersMap))); - } - - -} diff --git a/src/main/java/zhilian/ZhiLian.java b/src/main/java/zhilian/ZhiLian.java deleted file mode 100644 index 558f4a4b..00000000 --- a/src/main/java/zhilian/ZhiLian.java +++ /dev/null @@ -1,242 +0,0 @@ -package zhilian; - -import org.openqa.selenium.*; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import utils.Job; -import utils.JobUtils; -import utils.SeleniumUtil; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.stream.Collectors; - -import static utils.Bot.sendMessageByTime; -import static utils.Constant.*; -import static utils.JobUtils.formatDuration; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -public class ZhiLian { - static { - // 在类加载时就设置日志文件名,确保Logger初始化时能获取到正确的属性 - System.setProperty("log.name", "zhilian"); - } - - private static final Logger log = LoggerFactory.getLogger(ZhiLian.class); - static String loginUrl = "https://passport.zhaopin.com/login"; - static String homeUrl = "https://sou.zhaopin.com/?"; - static boolean isLimit = false; - static int maxPage = 500; - static ZhilianConfig config = ZhilianConfig.init(); - static List resultList = new ArrayList<>(); - static Date startDate; - - public static void main(String[] args) { - SeleniumUtil.initDriver(); - startDate = new Date(); - login(); - config.getKeywords().forEach(keyword -> { - if (isLimit) { - return; - } - CHROME_DRIVER.get(getSearchUrl(keyword, 1)); - submitJobs(keyword); - - }); - log.info(resultList.isEmpty() ? "未投递新的岗位..." : "新投递公司如下:\n{}", resultList.stream().map(Object::toString).collect(Collectors.joining("\n"))); - printResult(); - } - - private static void printResult() { - String message = String.format("\n智联招聘投递完成,共投递%d个岗位,用时%s", resultList.size(), formatDuration(startDate, new Date())); - log.info(message); - sendMessageByTime(message); - resultList.clear(); - CHROME_DRIVER.close(); - CHROME_DRIVER.quit(); - - // 确保所有日志都被刷新到文件 - try { - Thread.sleep(1000); // 等待1秒确保日志写入完成 - // 强制刷新日志 - 使用正确的方法 - ch.qos.logback.classic.LoggerContext loggerContext = (ch.qos.logback.classic.LoggerContext) org.slf4j.LoggerFactory.getILoggerFactory(); - loggerContext.stop(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - private static String getSearchUrl(String keyword, int page) { - return homeUrl + - JobUtils.appendParam("jl", config.getCityCode()) + - JobUtils.appendParam("kw", keyword) + - JobUtils.appendParam("sl", config.getSalary()) + - "&p=" + page; - } - - private static void submitJobs(String keyword) { - if (isLimit) { - return; - } - WAIT.until(ExpectedConditions.presenceOfElementLocated(By.xpath("//div[contains(@class, 'joblist-box__item')]"))); - setMaxPages(); - for (int i = 1; i <= maxPage; i++) { - if (i != 1) { - CHROME_DRIVER.get(getSearchUrl(keyword, i)); - } - log.info("开始投递【{}】关键词,第【{}】页...", keyword, i); - // 等待岗位出现 - try { - WAIT.until(ExpectedConditions.presenceOfElementLocated(By.xpath("//div[@class='positionlist']"))); - } catch (Exception ignore) { - CHROME_DRIVER.navigate().refresh(); - SeleniumUtil.sleep(1); - } - // 全选 - try { - WebElement allSelect = WAIT.until(ExpectedConditions.presenceOfElementLocated(By.xpath("//i[@class='betch__checkall__checkbox']"))); - allSelect.click(); - } catch (Exception e) { - log.info("没有全选按钮,程序退出..."); - continue; - } - // 投递 - WebElement submit = WAIT.until(ExpectedConditions.presenceOfElementLocated(By.xpath("//button[@class='betch__button']"))); - submit.click(); - if (checkIsLimit()) { - break; - } - SeleniumUtil.sleep(1); - // 切换到新的标签页 - ArrayList tabs = new ArrayList<>(CHROME_DRIVER.getWindowHandles()); - CHROME_DRIVER.switchTo().window(tabs.get(tabs.size() - 1)); - //关闭弹框 - try { - WebElement result = CHROME_DRIVER.findElement(By.xpath("//div[@class='deliver-dialog']")); - if (result.getText().contains("申请成功")) { - log.info("岗位申请成功!"); - } - } catch (Exception e) { - log.error("关闭投递弹框失败..."); - } - try { - WebElement close = CHROME_DRIVER.findElement(By.xpath("//img[@title='close-icon']")); - close.click(); - } catch (Exception e) { - if (checkIsLimit()) { - break; - } - } - try { - // 投递相似职位 - WebElement checkButton = CHROME_DRIVER.findElement(By.xpath("//div[contains(@class, 'applied-select-all')]//input")); - if (!checkButton.isSelected()) { - checkButton.click(); - } - List jobs = CHROME_DRIVER.findElements(By.xpath("//div[@class='recommend-job']")); - WebElement post = CHROME_DRIVER.findElement(By.xpath("//div[contains(@class, 'applied-select-all')]//button")); - post.click(); - printRecommendJobs(jobs); - log.info("相似职位投递成功!"); - } catch (NoSuchElementException e) { - log.error("没有匹配到相似职位..."); - } catch (Exception e) { - log.error("相似职位投递异常!!!"); - } - // 投完了关闭当前窗口并切换至第一个窗口 - CHROME_DRIVER.close(); - CHROME_DRIVER.switchTo().window(tabs.get(0)); - } - } - - private static boolean checkIsLimit() { - try { - SeleniumUtil.sleepByMilliSeconds(500); - WebElement result = CHROME_DRIVER.findElement(By.xpath("//div[@class='a-job-apply-workflow']")); - if (result.getText().contains("达到上限")) { - log.info("今日投递已达上限!"); - isLimit = true; - return true; - } - return false; - } catch (Exception e) { - return false; - } - } - - private static void setMaxPages() { - try { - // 到底部 - ACTIONS.keyDown(Keys.CONTROL).sendKeys(Keys.END).keyUp(Keys.CONTROL).perform(); - WebElement inputElement = CHROME_DRIVER.findElement(By.className("soupager__pagebox__goinp")); - inputElement.clear(); - inputElement.sendKeys("99999"); - //使用 JavaScript 获取输入元素的当前值 - JavascriptExecutor js = CHROME_DRIVER; - String modifiedValue = (String) js.executeScript("return arguments[0].value;", inputElement); - maxPage = Integer.parseInt(modifiedValue); - log.info("设置最大页数:{}", maxPage); - WebElement home = CHROME_DRIVER.findElement(By.xpath("//li[@class='listsort__item']")); - ACTIONS.moveToElement(home).perform(); - } catch (Exception ignore) { - StackTraceElement element = Thread.currentThread().getStackTrace()[1]; - log.info("setMaxPages@设置最大页数异常!({}:{})", element.getFileName(), element.getLineNumber()); - log.info("设置默认最大页数50,如有需要请自行调整..."); - maxPage = 50; - } - } - - private static void printRecommendJobs(List jobs) { - jobs.forEach(j -> { - String jobName = j.findElement(By.xpath(".//*[contains(@class, 'recommend-job__position')]")).getText(); - String salary = j.findElement(By.xpath(".//span[@class='recommend-job__demand__salary']")).getText(); - String years = j.findElement(By.xpath(".//span[@class='recommend-job__demand__experience']")).getText().replaceAll("\n", " "); - String education = j.findElement(By.xpath(".//span[@class='recommend-job__demand__educational']")).getText().replaceAll("\n", " "); - String companyName = j.findElement(By.xpath(".//*[contains(@class, 'recommend-job__cname')]")).getText(); - String companyTag = j.findElement(By.xpath(".//*[contains(@class, 'recommend-job__demand__cinfo')]")).getText().replaceAll("\n", " "); - Job job = new Job(); - job.setJobName(jobName); - job.setSalary(salary); - job.setCompanyTag(companyTag); - job.setCompanyName(companyName); - job.setJobInfo(years + "·" + education); - log.info("投递【{}】公司【{}】岗位,薪资【{}】,要求【{}·{}】,规模【{}】", companyName, jobName, salary, years, education, companyTag); - resultList.add(job); - }); - } - - private static void login() { - CHROME_DRIVER.get(loginUrl); - if (SeleniumUtil.isCookieValid("./src/main/java/zhilian/cookie.json")) { - SeleniumUtil.loadCookie("./src/main/java/zhilian/cookie.json"); - CHROME_DRIVER.navigate().refresh(); - SeleniumUtil.sleep(1); - } - if (isLoginRequired()) { - scanLogin(); - } - } - - private static void scanLogin() { - try { - WebElement button = CHROME_DRIVER.findElement(By.xpath("//div[@class='zppp-panel-normal-bar__img']")); - button.click(); - log.info("等待扫码登录中..."); - WAIT.until(ExpectedConditions.presenceOfElementLocated(By.xpath("//div[@class='zp-main__personal']"))); - log.info("扫码登录成功!"); - SeleniumUtil.saveCookie("./src/main/java/zhilian/cookie.json"); - } catch (Exception e) { - log.error("扫码登录异常!"); - System.exit(-1); - } - } - - private static boolean isLoginRequired() { - return !CHROME_DRIVER.getCurrentUrl().contains("i.zhaopin.com"); - } -} diff --git a/src/main/java/zhilian/ZhilianConfig.java b/src/main/java/zhilian/ZhilianConfig.java deleted file mode 100644 index 95594c06..00000000 --- a/src/main/java/zhilian/ZhilianConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package zhilian; - -import lombok.Data; -import lombok.SneakyThrows; -import utils.JobUtils; - -import java.util.List; -import java.util.Objects; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -@Data -public class ZhilianConfig { - /** - * 搜索关键词列表 - */ - private List keywords; - - /** - * 城市编码 - */ - private String cityCode; - - /** - * 薪资范围 - */ - private String salary; - - - @SneakyThrows - public static ZhilianConfig init() { - ZhilianConfig config = JobUtils.getConfig(ZhilianConfig.class); - // 转换城市编码 - config.setCityCode(ZhilianEnum.CityCode.forValue(config.getCityCode()).getCode()); - String salary = config.getSalary(); - config.setSalary(Objects.equals("不限", salary) ? "0" : salary); - return config; - } - -} diff --git a/src/main/java/zhilian/ZhilianEnum.java b/src/main/java/zhilian/ZhilianEnum.java deleted file mode 100644 index 2d939587..00000000 --- a/src/main/java/zhilian/ZhilianEnum.java +++ /dev/null @@ -1,41 +0,0 @@ -package zhilian; - -import com.fasterxml.jackson.annotation.JsonCreator; -import lombok.Getter; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -public class ZhilianEnum { - - @Getter - public enum CityCode { - NULL("不限", "0"), - BEIJING("北京", "530"), - SHANGHAI("上海", "538"), - GUANGZHOU("广州", "763"), - SHENZHEN("深圳", "765"), - WUHAN("武汉", "736"), - CHENGDU("成都", "801"); - - private final String name; - private final String code; - - CityCode(String name, String code) { - this.name = name; - this.code = code; - } - - @JsonCreator - public static CityCode forValue(String value) { - for (CityCode cityCode : CityCode.values()) { - if (cityCode.name.equals(value)) { - return cityCode; - } - } - return NULL; - } - } - -} diff --git a/src/main/java/zhilian/ZhilianScheduled.java b/src/main/java/zhilian/ZhilianScheduled.java deleted file mode 100644 index ca3e1782..00000000 --- a/src/main/java/zhilian/ZhilianScheduled.java +++ /dev/null @@ -1,29 +0,0 @@ -package zhilian; - -import lombok.extern.slf4j.Slf4j; -import utils.JobUtils; -import utils.Platform; - -/** - * @author loks666 - * 项目链接: https://github.com/loks666/get_jobs - */ -@Slf4j -public class ZhilianScheduled { - - public static void main(String[] args) { - JobUtils.runScheduled(Platform.ZHILIAN); - } - - public static void postJobs() { - safeRun(() -> ZhiLian.main(null)); - } - - private static void safeRun(Runnable task) { - try { - task.run(); - } catch (Exception e) { - log.error("safeRun异常:{}", e.getMessage(), e); - } - } -} diff --git a/src/main/resources/.env_template b/src/main/resources/.env_template deleted file mode 100644 index a42e3a55..00000000 --- a/src/main/resources/.env_template +++ /dev/null @@ -1,4 +0,0 @@ -HOOK_URL=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your_key_here -BASE_URL=https://api.ruyun.fun -API_KEY=sk-xxxxxxxxxxxxxxxxx -MODEL=gpt-5-nano-2025-08-07 \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 00000000..e69de29b diff --git a/src/main/resources/application-gpt.yml b/src/main/resources/application-gpt.yml new file mode 100644 index 00000000..d05a14c4 --- /dev/null +++ b/src/main/resources/application-gpt.yml @@ -0,0 +1,74 @@ +spring: + ai: + openai: + api-key: ${OPENAI_API_KEY:xxx} + base-url: ${OPENAI_BASE_URL:https://api.openai.com} + chat: + # 启用OpenAI聊天模型 + enabled: true + options: + # 默认使用的模型 + model: ${OPENAI_MODEL:gpt-4o} + # 默认温度参数 (1.0.0-M6版本使用Double类型) + temperature: 0.7 + # 默认最大token数 + max-tokens: 2000 + deepseek: + api-key: ${DEEPSEEK_API_KEY:sk-0720b1480e3247e8bfcab361a679e7cb} + base-url: ${DEEPSEEK_BASE_URL:https://api.deepseek.com} + chat: + # 启用OpenAI聊天模型 + enabled: true + options: + # 默认使用的模型 + model: ${DEEPSEEK_MODEL:deepseek-chat} + # 默认温度参数 (1.0.0-M6版本使用Double类型) + temperature: 0.7 + # 默认最大token数 + max-tokens: 5000 + +getjobs: + ai: + prompts: + job: + match-position: + description: 基于候选人目标职责与职位描述判断是否匹配 + placeholders: + my_jd: + required: true + description: "我的岗位职责" + jd: + required: false + description: "职位描述" + template: |- + 帮我根据职位描述判断对应职位是否匹配我的岗位, + 忽略并且不要考虑:学历、年限、管理经验、行业领域、具体技术深度或软技能等要求;不要向我提问、不要解释。 + 判定标准(仅比较“工作内容/岗位职责”是否对口): + - 若 JD 的核心职责与我期望从事的工作内容一致或高度相关,则返回 true。 + - 若核心职责不一致、方向不同,或仅偶尔提及而非常规主责,则返回 false。 + - 无法判断时返回 false。 + + 我需要从事的工作简要说明如下: + {my_jd} + + 只需返回是否匹配:是返回 true,否返回 false(小写、仅布尔值)。 + + 职位描述如下: + {jd} + +id: "greeting-v1" +description: "JD→一句打招呼" +segments: + - type: SYSTEM + content: | + 你是资深招聘文案生成器。目标:基于候选人资料与JD,生成一条中文打招呼句,≤{{max_chars}}字,语气{{tone}};覆盖“核心经验/量化成果/与JD贴合点{{#show_weakness}}/1个可控改进点及改进行动{{/show_weakness}}”。禁止虚构;技术名词≤3个。 + - type: GUIDELINES + content: | + 规则:1) 融入≥2个JD关键词;2) 避免空话与堆栈;3) 无数据用“动作+场景”。输出仅一句中文。 + - type: USER + content: | + 【候选人资料】{{{profile_json}}} + 【JD原文】{{{jd_text}}} + 【JD关键词(可能为空)】{{{jd_keywords}}} + 请生成一句中文(≤{{max_chars}}字)。 + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 00000000..125d70be --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,29794 @@ +server: + port: 8080 + +spring: + profiles: + active: dev,gpt + application: + name: npe-get-jobs + datasource: + url: 'jdbc:sqlite:file:memdb1?mode=memory&cache=shared' + driver-class-name: org.sqlite.JDBC + hikari: + maximum-pool-size: 10 + minimum-idle: 5 + idle-timeout: 0 # 不回收 + max-lifetime: 0 # 不重建 + username: + password: + sql: + init: + mode: always + jpa: + hibernate: + ddl-auto: create-drop + show-sql: false + properties: + hibernate: + format_sql: true + dialect: + hbm2ddl: + auto: create-drop + database-platform: + +logging: + level: + root: INFO + ai: DEBUG + pattern: + console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" +boss: + dict-industry-json: | + { + "code": 0, + "message": "Success", + "zpData": [ + { + "code": 100000, + "name": "互联网/AI", + "tip": null, + "subLevelModelList": [ + { + "code": 100020, + "name": "互联网", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100001, + "name": "电子商务", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100021, + "name": "计算机软件", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100007, + "name": "生活服务(O2O)", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100015, + "name": "企业服务", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100006, + "name": "医疗健康", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100002, + "name": "游戏", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100003, + "name": "社交网络与媒体", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100028, + "name": "人工智能", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100029, + "name": "云计算", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100012, + "name": "在线教育", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100023, + "name": "计算机服务", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100005, + "name": "大数据", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100004, + "name": "广告营销", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100030, + "name": "物联网", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100017, + "name": "新零售", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100016, + "name": "信息安全", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ], + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101400, + "name": "电子/通信/半导体", + "tip": null, + "subLevelModelList": [ + { + "code": 101405, + "name": "半导体/芯片", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101406, + "name": "电子/硬件开发", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101402, + "name": "通信/网络设备", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101401, + "name": "智能硬件/消费电子", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101403, + "name": "运营商/增值服务", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101404, + "name": "计算机硬件", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101407, + "name": "电子/半导体/集成电路", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ], + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101100, + "name": "服务业", + "tip": null, + "subLevelModelList": [ + { + "code": 101101, + "name": "餐饮", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101111, + "name": "美容", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101112, + "name": "美发", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101102, + "name": "酒店/民宿", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101107, + "name": "休闲/娱乐", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101113, + "name": "运动/健身", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101114, + "name": "保健/养生", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101109, + "name": "家政服务", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101103, + "name": "旅游/景区", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101105, + "name": "婚庆/摄影", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101110, + "name": "宠物服务", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101108, + "name": "回收/维修", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101104, + "name": "美容/美发", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101106, + "name": "其他生活服务", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ], + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101000, + "name": "消费品/批发/零售", + "tip": null, + "subLevelModelList": [ + { + "code": 101011, + "name": "批发/零售", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101012, + "name": "进出口贸易", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101001, + "name": "食品/饮料/烟酒", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101003, + "name": "服装/纺织", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101009, + "name": "家具/家居", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101010, + "name": "家用电器", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101002, + "name": "日化", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101006, + "name": "珠宝/首饰", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101004, + "name": "家具/家电/家居", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101013, + "name": "其他消费品", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ], + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100700, + "name": "房地产/建筑", + "tip": null, + "subLevelModelList": [ + { + "code": 100704, + "name": "装修装饰", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100708, + "name": "房屋建筑工程", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100709, + "name": "土木工程", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100710, + "name": "机电工程", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100707, + "name": "物业管理", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100706, + "name": "房地产中介/租赁", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100705, + "name": "建筑材料", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100701, + "name": "房地产开发经营", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100703, + "name": "建筑设计", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100711, + "name": "建筑工程咨询服务", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100712, + "name": "土地与公共设施管理", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100702, + "name": "工程施工", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ], + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100300, + "name": "教育培训", + "tip": null, + "subLevelModelList": [ + { + "code": 100303, + "name": "培训/辅导机构", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100305, + "name": "职业培训", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100301, + "name": "学前教育", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100302, + "name": "学校/学历教育", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100304, + "name": "学术/科研", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ], + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100100, + "name": "广告/传媒/文化/体育", + "tip": null, + "subLevelModelList": [ + { + "code": 100104, + "name": "文化艺术/娱乐", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100105, + "name": "体育", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100101, + "name": "广告/公关/会展", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100103, + "name": "广播/影视", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100102, + "name": "新闻/出版", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ], + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100900, + "name": "制造业", + "tip": null, + "subLevelModelList": [ + { + "code": 100906, + "name": "通用设备", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100907, + "name": "专用设备", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100908, + "name": "电气机械/器材", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100909, + "name": "金属制品", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100910, + "name": "非金属矿物制品", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100911, + "name": "橡胶/塑料制品", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100912, + "name": "化学原料/化学制品", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100913, + "name": "仪器仪表", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100914, + "name": "自动化设备", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100904, + "name": "印刷/包装/造纸", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100905, + "name": "铁路/船舶/航空/航天制造", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100915, + "name": "计算机/通信/其他电子设备", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100916, + "name": "新材料", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100901, + "name": "机械设备/机电/重工", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100902, + "name": "仪器仪表/工业自动化", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100903, + "name": "原材料及加工/模具", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100917, + "name": "其他制造业", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ], + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100600, + "name": "专业服务", + "tip": null, + "subLevelModelList": [ + { + "code": 100601, + "name": "咨询", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100605, + "name": "财务/审计/税务", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100604, + "name": "人力资源服务", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100602, + "name": "法律", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100609, + "name": "检测/认证/知识产权", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100603, + "name": "翻译", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100608, + "name": "其他专业服务", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ], + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100400, + "name": "制药/医疗", + "tip": null, + "subLevelModelList": [ + { + "code": 100402, + "name": "医疗服务", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100404, + "name": "医美服务", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100403, + "name": "医疗器械", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100405, + "name": "IVD", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100401, + "name": "生物/制药", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100406, + "name": "医药批发零售", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100407, + "name": "医疗研发外包", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ], + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100800, + "name": "汽车", + "tip": null, + "subLevelModelList": [ + { + "code": 100804, + "name": "新能源汽车", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100805, + "name": "汽车智能网联", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100806, + "name": "汽车经销商", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100807, + "name": "汽车后市场", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100801, + "name": "汽车研发/制造", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100802, + "name": "汽车零部件", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100808, + "name": "摩托车/自行车制造", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100803, + "name": "4S店/后市场", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ], + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100500, + "name": "交通运输/物流", + "tip": null, + "subLevelModelList": [ + { + "code": 100505, + "name": "即时配送", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100506, + "name": "快递", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100507, + "name": "公路物流", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100508, + "name": "同城货运", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100509, + "name": "跨境物流", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100510, + "name": "装卸搬运和仓储业", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100511, + "name": "客运服务", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100512, + "name": "港口/铁路/公路/机场", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100501, + "name": "交通/运输", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100502, + "name": "物流/仓储", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ], + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101200, + "name": "能源/化工/环保", + "tip": null, + "subLevelModelList": [ + { + "code": 101208, + "name": "光伏", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101209, + "name": "储能", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101210, + "name": "动力电池", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101211, + "name": "风电", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101212, + "name": "其他新能源", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101207, + "name": "环保", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101202, + "name": "化工", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101205, + "name": "电力/热力/燃气/水利", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101201, + "name": "石油/石化", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101203, + "name": "矿产/地质", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101204, + "name": "采掘/冶炼", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101206, + "name": "新能源", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ], + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100200, + "name": "金融", + "tip": null, + "subLevelModelList": [ + { + "code": 100206, + "name": "互联网金融", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100201, + "name": "银行", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100207, + "name": "投资/融资", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100203, + "name": "证券/期货", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100204, + "name": "基金", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100202, + "name": "保险", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100208, + "name": "租赁/拍卖/典当/担保", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100205, + "name": "信托", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100209, + "name": "财富管理", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 100210, + "name": "其他金融业", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ], + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101300, + "name": "政府/非盈利机构/其他", + "tip": null, + "subLevelModelList": [ + { + "code": 101303, + "name": "农/林/牧/渔", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101302, + "name": "非盈利机构", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101301, + "name": "政府/公共事业", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + }, + { + "code": 101304, + "name": "其他行业", + "tip": null, + "subLevelModelList": null, + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ], + "firstChar": null, + "pinyin": null, + "rank": 0, + "mark": 0, + "positionType": 0, + "cityType": 0, + "capital": 0, + "color": null, + "recruitmentType": null, + "cityCode": null, + "regionCode": 0, + "centerGeo": null, + "value": null + } + ] + } +zhilian: + dict-industry-json: | + [ + { + "code": "-1", + "parentCode": null, + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "100000000", + "parentCode": null, + "name": "互联网/IT/电子/通信", + "en_name": "Information", + "deleted": false, + "sublist": [ + { + "code": "100000000", + "parentCode": "100000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "100020000", + "parentCode": "100000000", + "name": "电子商务", + "en_name": "E-commerce", + "deleted": false, + "sublist": [] + }, + { + "code": "100070000", + "parentCode": "100000000", + "name": "企业服务", + "en_name": "Corporate Services", + "deleted": false, + "sublist": [] + }, + { + "code": "100080000", + "parentCode": "100000000", + "name": "人工智能", + "en_name": "AI/Artificial Intelligence", + "deleted": false, + "sublist": [] + }, + { + "code": "100000220", + "parentCode": "100000000", + "name": "智能硬件", + "en_name": "Intelligent Hardware", + "deleted": false, + "sublist": [] + }, + { + "code": "100160000", + "parentCode": "100000000", + "name": "在线教育", + "en_name": "Online education", + "deleted": false, + "sublist": [] + }, + { + "code": "100150000", + "parentCode": "100000000", + "name": "在线医疗", + "en_name": "Online healthcare", + "deleted": false, + "sublist": [] + }, + { + "code": "100110000", + "parentCode": "100000000", + "name": "新媒体", + "en_name": "New Media", + "deleted": false, + "sublist": [] + }, + { + "code": "100180000", + "parentCode": "100000000", + "name": "物联网", + "en_name": "Internet of things", + "deleted": false, + "sublist": [] + }, + { + "code": "100190000", + "parentCode": "100000000", + "name": "新零售", + "en_name": "New retail", + "deleted": false, + "sublist": [] + }, + { + "code": "100170000", + "parentCode": "100000000", + "name": "区块链", + "en_name": "Blockchain", + "deleted": false, + "sublist": [] + }, + { + "code": "100120000", + "parentCode": "100000000", + "name": "游戏", + "en_name": "Game", + "deleted": false, + "sublist": [] + }, + { + "code": "100000250", + "parentCode": "100000000", + "name": "社交网络", + "en_name": "Social Networks", + "deleted": false, + "sublist": [] + }, + { + "code": "100000260", + "parentCode": "100000000", + "name": "在线招聘/求职", + "en_name": "Online Recruitment or Job Search", + "deleted": false, + "sublist": [] + }, + { + "code": "100130000", + "parentCode": "100000000", + "name": "云计算/大数据", + "en_name": "Cloud Computing/Big Data", + "deleted": false, + "sublist": [] + }, + { + "code": "100100000", + "parentCode": "100000000", + "name": "网络/信息安全", + "en_name": "Network/Information Security", + "deleted": false, + "sublist": [] + }, + { + "code": "100210000", + "parentCode": "100000000", + "name": "在线生活服务(O2O)", + "en_name": "Online life service (o2o)", + "deleted": false, + "sublist": [] + }, + { + "code": "100200000", + "parentCode": "100000000", + "name": "在线音乐/视频/阅读", + "en_name": "Online music or video or reading", + "deleted": false, + "sublist": [] + }, + { + "code": "100030000", + "parentCode": "100000000", + "name": "互联网", + "en_name": "Internet", + "deleted": false, + "sublist": [] + }, + { + "code": "100040000", + "parentCode": "100000000", + "name": "IT服务", + "en_name": "IT Services", + "deleted": false, + "sublist": [] + }, + { + "code": "100050000", + "parentCode": "100000000", + "name": "计算机软件", + "en_name": "Computer Software", + "deleted": false, + "sublist": [] + }, + { + "code": "100060000", + "parentCode": "100000000", + "name": "计算机硬件", + "en_name": "Computer Hardware", + "deleted": false, + "sublist": [] + }, + { + "code": "100090000", + "parentCode": "100000000", + "name": "通信/网络设备", + "en_name": "Telecom & Network Equipment", + "deleted": false, + "sublist": [] + }, + { + "code": "100140000", + "parentCode": "100000000", + "name": "运营商/增值服务", + "en_name": "Telecom Operators/Service Providers", + "deleted": false, + "sublist": [] + }, + { + "code": "100010000", + "parentCode": "100000000", + "name": "电子/半导体/集成电路", + "en_name": "Electronics/Semiconductor/IC", + "deleted": false, + "sublist": [] + }, + { + "code": "100000230", + "parentCode": "100000000", + "name": "消费电子产品", + "en_name": "Consumer Electronics", + "deleted": false, + "sublist": [] + }, + { + "code": "100000240", + "parentCode": "100000000", + "name": "光电子行业", + "en_name": "Optoelectronics Industry", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "400000000", + "parentCode": null, + "name": "房地产/建筑", + "en_name": "Real estate or construction", + "deleted": false, + "sublist": [ + { + "code": "400000000", + "parentCode": "400000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "200010000", + "parentCode": "400000000", + "name": "房地产开发", + "en_name": "Real Estate Development", + "deleted": false, + "sublist": [] + }, + { + "code": "200040000", + "parentCode": "400000000", + "name": "土地与公共设施管理", + "en_name": "Management of L& & Public Properties", + "deleted": false, + "sublist": [] + }, + { + "code": "200020000", + "parentCode": "400000000", + "name": "房地产中介", + "en_name": "Real Estate Agency & Leasing", + "deleted": false, + "sublist": [] + }, + { + "code": "200050000", + "parentCode": "400000000", + "name": "物业管理", + "en_name": "Property Management", + "deleted": false, + "sublist": [] + }, + { + "code": "400050000", + "parentCode": "400000000", + "name": "建筑设计", + "en_name": "Architectural design", + "deleted": false, + "sublist": [] + }, + { + "code": "400030000", + "parentCode": "400000000", + "name": "工程施工", + "en_name": "Engineering construction", + "deleted": false, + "sublist": [] + }, + { + "code": "400010000", + "parentCode": "400000000", + "name": "建筑设备安装", + "en_name": "Construction equipment installation", + "deleted": false, + "sublist": [] + }, + { + "code": "400040000", + "parentCode": "400000000", + "name": "装饰装修", + "en_name": "Decoration", + "deleted": false, + "sublist": [] + }, + { + "code": "400060000", + "parentCode": "400000000", + "name": "建材", + "en_name": "Building material", + "deleted": false, + "sublist": [] + }, + { + "code": "400070000", + "parentCode": "400000000", + "name": "建筑工程检测", + "en_name": "Construction engineering inspection", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "300000000", + "parentCode": null, + "name": "金融业", + "en_name": "Finance", + "deleted": false, + "sublist": [ + { + "code": "300000000", + "parentCode": "300000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "300070000", + "parentCode": "300000000", + "name": "银行", + "en_name": "Bank", + "deleted": false, + "sublist": [] + }, + { + "code": "300010000", + "parentCode": "300000000", + "name": "保险", + "en_name": "Insurance", + "deleted": false, + "sublist": [] + }, + { + "code": "300040000", + "parentCode": "300000000", + "name": "基金", + "en_name": "Fund", + "deleted": false, + "sublist": [] + }, + { + "code": "300060000", + "parentCode": "300000000", + "name": "信托", + "en_name": "Trust", + "deleted": false, + "sublist": [] + }, + { + "code": "300080000", + "parentCode": "300000000", + "name": "证券/期货", + "en_name": "Securities & Futures", + "deleted": false, + "sublist": [] + }, + { + "code": "300090000", + "parentCode": "300000000", + "name": "投资/融资", + "en_name": "Investment or financing", + "deleted": false, + "sublist": [] + }, + { + "code": "300050000", + "parentCode": "300000000", + "name": "汽车金融", + "en_name": "Auto Finance", + "deleted": false, + "sublist": [] + }, + { + "code": "300030000", + "parentCode": "300000000", + "name": "互联网金融/小额贷款", + "en_name": "Internet Finance", + "deleted": false, + "sublist": [] + }, + { + "code": "300020000", + "parentCode": "300000000", + "name": "租赁/拍卖/典当/担保", + "en_name": "Lease or auction or pawn or guarantee", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1200000000", + "parentCode": null, + "name": "教育培训/科研", + "en_name": "Educational & Scientific Research", + "deleted": false, + "sublist": [ + { + "code": "1200000000", + "parentCode": "1200000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "1200020000", + "parentCode": "1200000000", + "name": "培训/辅导服务", + "en_name": "Training or Counselling", + "deleted": false, + "sublist": [] + }, + { + "code": "1200040000", + "parentCode": "1200000000", + "name": "学校/学历教育", + "en_name": "School or Academic Education", + "deleted": false, + "sublist": [] + }, + { + "code": "1200030000", + "parentCode": "1200000000", + "name": "学术/科研", + "en_name": "Research & experimental development", + "deleted": false, + "sublist": [] + }, + { + "code": "1200010000", + "parentCode": "1200000000", + "name": "科学技术推广", + "en_name": "Science and Technology Promotion", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "900000000", + "parentCode": null, + "name": "广告/传媒/文化/体育", + "en_name": "Advertising or media or culture or sports", + "deleted": false, + "sublist": [ + { + "code": "900000000", + "parentCode": "900000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "800040000", + "parentCode": "900000000", + "name": "广告/营销", + "en_name": "Advertising or Marketing", + "deleted": false, + "sublist": [] + }, + { + "code": "900010000", + "parentCode": "900000000", + "name": "广播/影视", + "en_name": "Broadcast or Movies", + "deleted": false, + "sublist": [] + }, + { + "code": "800050000", + "parentCode": "900000000", + "name": "会议/展览", + "en_name": "Exhibition Services", + "deleted": false, + "sublist": [] + }, + { + "code": "900030000", + "parentCode": "900000000", + "name": "文化艺术/娱乐", + "en_name": "Arts/Entertainment", + "deleted": false, + "sublist": [] + }, + { + "code": "900020000", + "parentCode": "900000000", + "name": "体育", + "en_name": "Sports", + "deleted": false, + "sublist": [] + }, + { + "code": "900040000", + "parentCode": "900000000", + "name": "新闻/出版", + "en_name": "News/Publishing", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1300000000", + "parentCode": null, + "name": "生物医药/医疗", + "en_name": "Biomedical or medical", + "deleted": false, + "sublist": [ + { + "code": "1300000000", + "parentCode": "1300000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "1300030000", + "parentCode": "1300000000", + "name": "医院", + "en_name": "Hospitals", + "deleted": false, + "sublist": [] + }, + { + "code": "1300010000", + "parentCode": "1300000000", + "name": "卫生服务", + "en_name": "Health care", + "deleted": false, + "sublist": [] + }, + { + "code": "1300040000", + "parentCode": "1300000000", + "name": "生物工程", + "en_name": "bioengineering", + "deleted": false, + "sublist": [] + }, + { + "code": "500170000", + "parentCode": "1300000000", + "name": "医药制造", + "en_name": "Manufacture of Pharmaceutical Products", + "deleted": false, + "sublist": [] + }, + { + "code": "1300050000", + "parentCode": "1300000000", + "name": "医疗检测", + "en_name": "Medical tests", + "deleted": false, + "sublist": [] + }, + { + "code": "1300060000", + "parentCode": "1300000000", + "name": "医药批发/零售", + "en_name": "Pharmaceutical wholesale or retail", + "deleted": false, + "sublist": [] + }, + { + "code": "500210000", + "parentCode": "1300000000", + "name": "医疗设备/器械", + "en_name": "Manufacture of Medical Equipment & Facilities", + "deleted": false, + "sublist": [] + }, + { + "code": "1300000800", + "parentCode": "1300000000", + "name": "IVD", + "en_name": "IVD", + "deleted": false, + "sublist": [] + }, + { + "code": "1300000700", + "parentCode": "1300000000", + "name": "医美/健康服务", + "en_name": "Medical Beauty or Health Services", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "700000000", + "parentCode": null, + "name": "批发/零售/贸易", + "en_name": "Wholesale & Retail Trade", + "deleted": false, + "sublist": [ + { + "code": "700000000", + "parentCode": "700000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "700010000", + "parentCode": "700000000", + "name": "快速消费品", + "en_name": "Fast Moving Consumer Goods", + "deleted": false, + "sublist": [] + }, + { + "code": "700030000", + "parentCode": "700000000", + "name": "耐用消费品", + "en_name": "Durable Goods", + "deleted": false, + "sublist": [] + }, + { + "code": "700040000", + "parentCode": "700000000", + "name": "零售/批发", + "en_name": "Retail/Wholesale", + "deleted": false, + "sublist": [] + }, + { + "code": "700050000", + "parentCode": "700000000", + "name": "食品/饮料", + "en_name": "Food or beverage", + "deleted": false, + "sublist": [] + }, + { + "code": "700060000", + "parentCode": "700000000", + "name": "烟草/酒业", + "en_name": "Tobacco and Wine", + "deleted": false, + "sublist": [] + }, + { + "code": "700070000", + "parentCode": "700000000", + "name": "日化", + "en_name": "Chemicals for daily use", + "deleted": false, + "sublist": [] + }, + { + "code": "700110000", + "parentCode": "700000000", + "name": "服装/纺织/皮革", + "en_name": "Clothing or textile or leather", + "deleted": false, + "sublist": [] + }, + { + "code": "700000140", + "parentCode": "700000000", + "name": "奢侈品", + "en_name": "Luxury", + "deleted": false, + "sublist": [] + }, + { + "code": "700080000", + "parentCode": "700000000", + "name": "玩具/礼品", + "en_name": "Toys or gifts", + "deleted": false, + "sublist": [] + }, + { + "code": "700090000", + "parentCode": "700000000", + "name": "珠宝/首饰", + "en_name": "Jewelry or jewelry", + "deleted": false, + "sublist": [] + }, + { + "code": "700130000", + "parentCode": "700000000", + "name": "办公用品/设备", + "en_name": "Office Supplies or Equipment", + "deleted": false, + "sublist": [] + }, + { + "code": "700100000", + "parentCode": "700000000", + "name": "工艺品/收藏品/艺术品", + "en_name": "Crafts or Collectibles or Artworks", + "deleted": false, + "sublist": [] + }, + { + "code": "700120000", + "parentCode": "700000000", + "name": "家具/家居/家电", + "en_name": "Furniture or home or home appliances", + "deleted": false, + "sublist": [] + }, + { + "code": "700020000", + "parentCode": "700000000", + "name": "贸易/进出口", + "en_name": "Trading or Import or Export", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "500000000", + "parentCode": null, + "name": "制造业", + "en_name": "Manufacturing", + "deleted": false, + "sublist": [ + { + "code": "500000000", + "parentCode": "500000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "500010000", + "parentCode": "500000000", + "name": "船舶/航空/航天/火车制造", + "en_name": "Manufacture of Other Transport Equipment", + "deleted": false, + "sublist": [] + }, + { + "code": "500020000", + "parentCode": "500000000", + "name": "电气机械/电力设备", + "en_name": "Electrical Machinery or Electrical Equipment", + "deleted": false, + "sublist": [] + }, + { + "code": "500030000", + "parentCode": "500000000", + "name": "电子设备制造", + "en_name": "Manufacture of Electronic Products", + "deleted": false, + "sublist": [] + }, + { + "code": "500000280", + "parentCode": "500000000", + "name": "机器人", + "en_name": "Robot", + "deleted": false, + "sublist": [] + }, + { + "code": "500060000", + "parentCode": "500000000", + "name": "钢铁/有色金属冶炼及加工", + "en_name": "Metallurgy", + "deleted": false, + "sublist": [] + }, + { + "code": "500220000", + "parentCode": "500000000", + "name": "专用设备制造", + "en_name": "Manufacture of Specific-purpose Machinery", + "deleted": false, + "sublist": [] + }, + { + "code": "500000270", + "parentCode": "500000000", + "name": "军工制造", + "en_name": "Military Manufacturing", + "deleted": false, + "sublist": [] + }, + { + "code": "500090000", + "parentCode": "500000000", + "name": "金属制品业", + "en_name": "Manufacture of Metallic Mineral Products", + "deleted": false, + "sublist": [] + }, + { + "code": "500140000", + "parentCode": "500000000", + "name": "通用设备制造", + "en_name": "Manufacture of General-purpose Machinery", + "deleted": false, + "sublist": [] + }, + { + "code": "500180000", + "parentCode": "500000000", + "name": "仪器仪表制造", + "en_name": "Manufacture of Instruments & Appliances", + "deleted": false, + "sublist": [] + }, + { + "code": "500000260", + "parentCode": "500000000", + "name": "摩托车/自行车制造", + "en_name": "Motorcycle or Bicycle Manufacturing", + "deleted": false, + "sublist": [] + }, + { + "code": "500050000", + "parentCode": "500000000", + "name": "非金属矿物制品业", + "en_name": "Manufacture of Non-metallic Mineral Products", + "deleted": false, + "sublist": [] + }, + { + "code": "500000250", + "parentCode": "500000000", + "name": "新材料", + "en_name": "New Materials", + "deleted": false, + "sublist": [] + }, + { + "code": "500070000", + "parentCode": "500000000", + "name": "化学纤维制造业", + "en_name": "Manufacture of Chemical Fiber", + "deleted": false, + "sublist": [] + }, + { + "code": "500080000", + "parentCode": "500000000", + "name": "化学原料/化学制品", + "en_name": "Manufacture of Chemicals & Chemical Products ", + "deleted": false, + "sublist": [] + }, + { + "code": "500130000", + "parentCode": "500000000", + "name": "日化产品制造", + "en_name": "Manufacture of Household Chemical Products", + "deleted": false, + "sublist": [] + }, + { + "code": "500040000", + "parentCode": "500000000", + "name": "纺织业/服饰产品加工制造", + "en_name": "Manufacture of Textiles & Wearing Apparel", + "deleted": false, + "sublist": [] + }, + { + "code": "500100000", + "parentCode": "500000000", + "name": "农副产品加工制造", + "en_name": "Agricultural Processing", + "deleted": false, + "sublist": [] + }, + { + "code": "500120000", + "parentCode": "500000000", + "name": "燃料资源加工制造", + "en_name": "Fuel Manufacturing & Processing", + "deleted": false, + "sublist": [] + }, + { + "code": "500150000", + "parentCode": "500000000", + "name": "橡胶和塑料制品", + "en_name": "Manufacture of Rubber & Plastic Products", + "deleted": false, + "sublist": [] + }, + { + "code": "500240000", + "parentCode": "500000000", + "name": "文体/办公设备制造", + "en_name": "Sports or office equipment manufacturing", + "deleted": false, + "sublist": [] + }, + { + "code": "500200000", + "parentCode": "500000000", + "name": "家具制造", + "en_name": "Furniture manufacturing", + "deleted": false, + "sublist": [] + }, + { + "code": "500190000", + "parentCode": "500000000", + "name": "印刷/包装/造纸", + "en_name": "Printing or packaging or paper making", + "deleted": false, + "sublist": [] + }, + { + "code": "500230000", + "parentCode": "500000000", + "name": "工业自动化", + "en_name": "Tndustrial automation", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1600000000", + "parentCode": null, + "name": "汽车", + "en_name": "Automobile", + "deleted": false, + "sublist": [ + { + "code": "1600000000", + "parentCode": "1600000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "500110000", + "parentCode": "1600000000", + "name": "汽车研发/制造", + "en_name": "Automotive Research or Manufacturing", + "deleted": false, + "sublist": [] + }, + { + "code": "1600030000", + "parentCode": "1600000000", + "name": "新能源汽车", + "en_name": "New energy vehicle", + "deleted": false, + "sublist": [] + }, + { + "code": "1600000400", + "parentCode": "1600000000", + "name": "汽车智能互联", + "en_name": "Automotive Intelligent Iinterconnection", + "deleted": false, + "sublist": [] + }, + { + "code": "1600020000", + "parentCode": "1600000000", + "name": "汽车零部件", + "en_name": "Auto parts", + "deleted": false, + "sublist": [] + }, + { + "code": "1600010000", + "parentCode": "1600000000", + "name": "汽车4S店/经销商", + "en_name": "Automotive 4S Stores or Dealers", + "deleted": false, + "sublist": [] + }, + { + "code": "1600000500", + "parentCode": "1600000000", + "name": "汽车后市场", + "en_name": "Automotive Aftermarket", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1000000000", + "parentCode": null, + "name": "交通运输/仓储/物流", + "en_name": "Transportation & Warehousing", + "deleted": false, + "sublist": [ + { + "code": "1000000000", + "parentCode": "1000000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "1000010000", + "parentCode": "1000000000", + "name": "火车站/港口/汽车站/路政", + "en_name": "L& Transportation for Passengers", + "deleted": false, + "sublist": [] + }, + { + "code": "1000030000", + "parentCode": "1000000000", + "name": "客运服务", + "en_name": "Passenger Services", + "deleted": false, + "sublist": [] + }, + { + "code": "1000020000", + "parentCode": "1000000000", + "name": "货运/物流/仓储", + "en_name": "Freight or Logistics or Warehousing", + "deleted": false, + "sublist": [] + }, + { + "code": "1000040000", + "parentCode": "1000000000", + "name": "邮政/快递", + "en_name": "Postal & Express Services", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "800000000", + "parentCode": null, + "name": "专业服务", + "en_name": "Professional Services", + "deleted": false, + "sublist": [ + { + "code": "800000000", + "parentCode": "800000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "800020000", + "parentCode": "800000000", + "name": "法律服务", + "en_name": "Legal Services", + "deleted": false, + "sublist": [] + }, + { + "code": "800120000", + "parentCode": "800000000", + "name": "咨询服务", + "en_name": "Consultancy", + "deleted": false, + "sublist": [] + }, + { + "code": "800140000", + "parentCode": "800000000", + "name": "翻译服务", + "en_name": "Translation services", + "deleted": false, + "sublist": [] + }, + { + "code": "800080000", + "parentCode": "800000000", + "name": "人力资源服务", + "en_name": "Human Resources", + "deleted": false, + "sublist": [] + }, + { + "code": "800010000", + "parentCode": "800000000", + "name": "财务/审计/税务", + "en_name": "Accounting/Auditing/Tax", + "deleted": false, + "sublist": [] + }, + { + "code": "800030000", + "parentCode": "800000000", + "name": "工程技术与设计服务", + "en_name": "Engineering & Related Technical Services", + "deleted": false, + "sublist": [] + }, + { + "code": "800060000", + "parentCode": "800000000", + "name": "检测/认证", + "en_name": "Testing/Certification", + "deleted": false, + "sublist": [] + }, + { + "code": "800070000", + "parentCode": "800000000", + "name": "景区/商业/市场等综合管理", + "en_name": "Public Property Management", + "deleted": false, + "sublist": [] + }, + { + "code": "800090000", + "parentCode": "800000000", + "name": "商业代理服务", + "en_name": "Agency Services", + "deleted": false, + "sublist": [] + }, + { + "code": "800100000", + "parentCode": "800000000", + "name": "专利/商标/知识产权", + "en_name": "Patents/Trademarks/Copyrights", + "deleted": false, + "sublist": [] + }, + { + "code": "800130000", + "parentCode": "800000000", + "name": "租赁服务", + "en_name": "Leasing Services", + "deleted": false, + "sublist": [] + }, + { + "code": "800110000", + "parentCode": "800000000", + "name": "专业技术服务", + "en_name": "Technique Services", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1500000000", + "parentCode": null, + "name": "生活服务", + "en_name": "Residential Services", + "deleted": false, + "sublist": [ + { + "code": "1500000000", + "parentCode": "1500000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "1500010000", + "parentCode": "1500000000", + "name": "餐饮服务", + "en_name": "Catering Services", + "deleted": false, + "sublist": [] + }, + { + "code": "1500020000", + "parentCode": "1500000000", + "name": "酒店/民宿", + "en_name": "Accommodation", + "deleted": false, + "sublist": [] + }, + { + "code": "1500040000", + "parentCode": "1500000000", + "name": "旅游服务", + "en_name": "Tourism Services", + "deleted": false, + "sublist": [] + }, + { + "code": "1500060000", + "parentCode": "1500000000", + "name": "婚庆/摄影", + "en_name": "Wedding or photography", + "deleted": false, + "sublist": [] + }, + { + "code": "1500050000", + "parentCode": "1500000000", + "name": "美发/美容/保健", + "en_name": "Hairdressing or Beauty or Healthcare", + "deleted": false, + "sublist": [] + }, + { + "code": "1500000900", + "parentCode": "1500000000", + "name": "宠物服务", + "en_name": "Pet Services", + "deleted": false, + "sublist": [] + }, + { + "code": "1500001000", + "parentCode": "1500000000", + "name": "家政服务", + "en_name": "Housekeeping Services", + "deleted": false, + "sublist": [] + }, + { + "code": "1500001100", + "parentCode": "1500000000", + "name": "回收/维修", + "en_name": "Recycling or Maintenance", + "deleted": false, + "sublist": [] + }, + { + "code": "1500000800", + "parentCode": "1500000000", + "name": "休闲/娱乐", + "en_name": "Leisure or Amusement", + "deleted": false, + "sublist": [] + }, + { + "code": "1500000700", + "parentCode": "1500000000", + "name": "搬家/生活配送", + "en_name": "Moving or Living Delivery", + "deleted": false, + "sublist": [] + }, + { + "code": "1500030000", + "parentCode": "1500000000", + "name": "居民服务", + "en_name": "Personal Care & Services", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1100000000", + "parentCode": null, + "name": "能源/环保/矿产", + "en_name": "Mining, Quarrying, & Environmental Protection", + "deleted": false, + "sublist": [ + { + "code": "1100000000", + "parentCode": "1100000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "1100040000", + "parentCode": "1100000000", + "name": "石油/石化", + "en_name": "Petroleum & Petrochemical", + "deleted": false, + "sublist": [] + }, + { + "code": "1100060000", + "parentCode": "1100000000", + "name": "化工", + "en_name": "Chemical industry", + "deleted": false, + "sublist": [] + }, + { + "code": "1100010000", + "parentCode": "1100000000", + "name": "电力/水利/热力/燃气", + "en_name": "Electricity/Water Supply/Gas/Heating", + "deleted": false, + "sublist": [] + }, + { + "code": "1100050000", + "parentCode": "1100000000", + "name": "新能源", + "en_name": "Clean Energy", + "deleted": false, + "sublist": [] + }, + { + "code": "1100020000", + "parentCode": "1100000000", + "name": "环保", + "en_name": "Environmental Protection", + "deleted": false, + "sublist": [] + }, + { + "code": "1100030000", + "parentCode": "1100000000", + "name": "矿产/采掘", + "en_name": "Mining", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1400000000", + "parentCode": null, + "name": "政府/非盈利机构", + "en_name": "Government or nonprofit", + "deleted": false, + "sublist": [ + { + "code": "1400000000", + "parentCode": "1400000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "1400010000", + "parentCode": "1400000000", + "name": "政府/公共事业", + "en_name": "Government or Public Services", + "deleted": false, + "sublist": [] + }, + { + "code": "1400020000", + "parentCode": "1400000000", + "name": "社团/组织/社会保障", + "en_name": "Non-profit Organizations", + "deleted": false, + "sublist": [] + }, + { + "code": "1300020000", + "parentCode": "1400000000", + "name": "养老/孤儿/看护", + "en_name": "Social Assistance", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "600000000", + "parentCode": null, + "name": "农/林/牧/渔", + "en_name": "Agriculture or forestry or animal husbandry or fishing", + "deleted": false, + "sublist": [ + { + "code": "600000000", + "parentCode": "600000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "600010000", + "parentCode": "600000000", + "name": "农/林/牧/渔", + "en_name": "Agriculture or forestry or animal husbandry or fishing", + "deleted": false, + "sublist": [] + } + ] + } + ] +json51: + dict-json: | + { + "status": "1", + "message": "成功", + "resultbody": { + "d_industry": [ + { + "id": "01", + "value": "计算机\/互联网\/通信\/电子", + "sub": [ + { + "id": "01", + "value": "计算机软件", + "sub": [], + "trace": [ + "01", + "01" + ] + }, + { + "id": "37", + "value": "计算机硬件", + "sub": [], + "trace": [ + "01", + "37" + ] + }, + { + "id": "38", + "value": "计算机服务(系统、数据服务、维修)", + "sub": [], + "trace": [ + "01", + "38" + ] + }, + { + "id": "31", + "value": "通信\/电信\/网络设备", + "sub": [], + "trace": [ + "01", + "31" + ] + }, + { + "id": "39", + "value": "通信\/电信运营、增值服务", + "sub": [], + "trace": [ + "01", + "39" + ] + }, + { + "id": "32", + "value": "互联网\/电子商务", + "sub": [], + "trace": [ + "01", + "32" + ] + }, + { + "id": "40", + "value": "网络游戏", + "sub": [], + "trace": [ + "01", + "40" + ] + }, + { + "id": "02", + "value": "电子技术\/半导体\/集成电路", + "sub": [], + "trace": [ + "01", + "02" + ] + }, + { + "id": "35", + "value": "仪器仪表\/工业自动化", + "sub": [], + "trace": [ + "01", + "35" + ] + } + ], + "trace": [ + "01" + ] + }, + { + "id": "41", + "value": "会计\/金融\/银行\/保险", + "sub": [ + { + "id": "41", + "value": "会计\/审计", + "sub": [], + "trace": [ + "41", + "41" + ] + }, + { + "id": "03", + "value": "金融\/投资\/证券", + "sub": [], + "trace": [ + "41", + "03" + ] + }, + { + "id": "42", + "value": "银行", + "sub": [], + "trace": [ + "41", + "42" + ] + }, + { + "id": "43", + "value": "保险", + "sub": [], + "trace": [ + "41", + "43" + ] + }, + { + "id": "62", + "value": "信托\/担保\/拍卖\/典当", + "sub": [], + "trace": [ + "41", + "62" + ] + } + ], + "trace": [ + "41" + ] + }, + { + "id": "04", + "value": "贸易\/消费\/制造\/营运", + "sub": [ + { + "id": "04", + "value": "贸易\/进出口", + "sub": [], + "trace": [ + "04", + "04" + ] + }, + { + "id": "22", + "value": "批发\/零售", + "sub": [], + "trace": [ + "04", + "22" + ] + }, + { + "id": "05", + "value": "快速消费品(食品、饮料、化妆品)", + "sub": [], + "trace": [ + "04", + "05" + ] + }, + { + "id": "06", + "value": "服装\/纺织\/皮革", + "sub": [], + "trace": [ + "04", + "06" + ] + }, + { + "id": "44", + "value": "家具\/家电\/玩具\/礼品", + "sub": [], + "trace": [ + "04", + "44" + ] + }, + { + "id": "60", + "value": "奢侈品\/收藏品\/工艺品\/珠宝", + "sub": [], + "trace": [ + "04", + "60" + ] + }, + { + "id": "45", + "value": "办公用品及设备", + "sub": [], + "trace": [ + "04", + "45" + ] + }, + { + "id": "14", + "value": "机械\/设备\/重工", + "sub": [], + "trace": [ + "04", + "14" + ] + }, + { + "id": "33", + "value": "汽车", + "sub": [], + "trace": [ + "04", + "33" + ] + }, + { + "id": "65", + "value": "汽车零配件", + "sub": [], + "trace": [ + "04", + "65" + ] + } + ], + "trace": [ + "04" + ] + }, + { + "id": "08", + "value": "制药\/医疗", + "sub": [ + { + "id": "08", + "value": "制药\/生物工程", + "sub": [], + "trace": [ + "08", + "08" + ] + }, + { + "id": "46", + "value": "医疗\/护理\/卫生", + "sub": [], + "trace": [ + "08", + "46" + ] + }, + { + "id": "47", + "value": "医疗设备\/器械", + "sub": [], + "trace": [ + "08", + "47" + ] + } + ], + "trace": [ + "08" + ] + }, + { + "id": "12", + "value": "广告\/媒体", + "sub": [ + { + "id": "12", + "value": "广告", + "sub": [], + "trace": [ + "12", + "12" + ] + }, + { + "id": "48", + "value": "公关\/市场推广\/会展", + "sub": [], + "trace": [ + "12", + "48" + ] + }, + { + "id": "49", + "value": "影视\/媒体\/艺术\/文化传播", + "sub": [], + "trace": [ + "12", + "49" + ] + }, + { + "id": "13", + "value": "文字媒体\/出版", + "sub": [], + "trace": [ + "12", + "13" + ] + }, + { + "id": "15", + "value": "印刷\/包装\/造纸", + "sub": [], + "trace": [ + "12", + "15" + ] + } + ], + "trace": [ + "12" + ] + }, + { + "id": "26", + "value": "房地产\/建筑", + "sub": [ + { + "id": "26", + "value": "房地产", + "sub": [], + "trace": [ + "26", + "26" + ] + }, + { + "id": "09", + "value": "建筑\/建材\/工程", + "sub": [], + "trace": [ + "26", + "09" + ] + }, + { + "id": "50", + "value": "家居\/室内设计\/装潢", + "sub": [], + "trace": [ + "26", + "50" + ] + }, + { + "id": "51", + "value": "物业管理\/商业中心", + "sub": [], + "trace": [ + "26", + "51" + ] + }, + { + "id": "34", + "value": "中介服务", + "sub": [], + "trace": [ + "26", + "34" + ] + }, + { + "id": "63", + "value": "租赁服务", + "sub": [], + "trace": [ + "26", + "63" + ] + } + ], + "trace": [ + "26" + ] + }, + { + "id": "07", + "value": "专业服务\/教育\/培训", + "sub": [ + { + "id": "07", + "value": "专业服务(咨询、人力资源、财会)", + "sub": [], + "trace": [ + "07", + "07" + ] + }, + { + "id": "59", + "value": "外包服务", + "sub": [], + "trace": [ + "07", + "59" + ] + }, + { + "id": "52", + "value": "检测,认证", + "sub": [], + "trace": [ + "07", + "52" + ] + }, + { + "id": "18", + "value": "法律", + "sub": [], + "trace": [ + "07", + "18" + ] + }, + { + "id": "23", + "value": "教育\/培训\/院校", + "sub": [], + "trace": [ + "07", + "23" + ] + }, + { + "id": "24", + "value": "学术\/科研", + "sub": [], + "trace": [ + "07", + "24" + ] + } + ], + "trace": [ + "07" + ] + }, + { + "id": "11", + "value": "服务业", + "sub": [ + { + "id": "11", + "value": "餐饮业", + "sub": [], + "trace": [ + "11", + "11" + ] + }, + { + "id": "53", + "value": "酒店\/旅游", + "sub": [], + "trace": [ + "11", + "53" + ] + }, + { + "id": "17", + "value": "娱乐\/休闲\/体育", + "sub": [], + "trace": [ + "11", + "17" + ] + }, + { + "id": "54", + "value": "美容\/保健", + "sub": [], + "trace": [ + "11", + "54" + ] + }, + { + "id": "27", + "value": "生活服务", + "sub": [], + "trace": [ + "11", + "27" + ] + } + ], + "trace": [ + "11" + ] + }, + { + "id": "21", + "value": "物流\/运输", + "sub": [ + { + "id": "21", + "value": "交通\/运输\/物流", + "sub": [], + "trace": [ + "21", + "21" + ] + }, + { + "id": "55", + "value": "航天\/航空", + "sub": [], + "trace": [ + "21", + "55" + ] + } + ], + "trace": [ + "21" + ] + }, + { + "id": "19", + "value": "能源\/环保\/化工", + "sub": [ + { + "id": "19", + "value": "石油\/化工\/矿产\/地质", + "sub": [], + "trace": [ + "19", + "19" + ] + }, + { + "id": "16", + "value": "采掘业\/冶炼", + "sub": [], + "trace": [ + "19", + "16" + ] + }, + { + "id": "36", + "value": "电气\/电力\/水利", + "sub": [], + "trace": [ + "19", + "36" + ] + }, + { + "id": "61", + "value": "新能源", + "sub": [], + "trace": [ + "19", + "61" + ] + }, + { + "id": "56", + "value": "原材料和加工", + "sub": [], + "trace": [ + "19", + "56" + ] + }, + { + "id": "20", + "value": "环保", + "sub": [], + "trace": [ + "19", + "20" + ] + } + ], + "trace": [ + "19" + ] + }, + { + "id": "28", + "value": "政府\/非营利组织\/其他", + "sub": [ + { + "id": "28", + "value": "政府\/公共事业", + "sub": [], + "trace": [ + "28", + "28" + ] + }, + { + "id": "57", + "value": "非营利组织", + "sub": [], + "trace": [ + "28", + "57" + ] + }, + { + "id": "29", + "value": "农\/林\/牧\/渔", + "sub": [], + "trace": [ + "28", + "29" + ] + }, + { + "id": "58", + "value": "多元化业务集团公司", + "sub": [], + "trace": [ + "28", + "58" + ] + } + ], + "trace": [ + "28" + ] + } + ], + "d_area": [ + { + "id": "000000", + "value": "热门地区", + "sub": [ + { + "id": "010000", + "value": "北京", + "sub": [ + { + "id": "010000", + "value": "北京", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "010100", + "value": "东城区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "010200", + "value": "西城区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "010500", + "value": "朝阳区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "010600", + "value": "丰台区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "010700", + "value": "石景山区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "010800", + "value": "海淀区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "010900", + "value": "门头沟区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011000", + "value": "房山区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011100", + "value": "通州区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011200", + "value": "顺义区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011300", + "value": "昌平区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011400", + "value": "大兴区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011500", + "value": "怀柔区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011600", + "value": "平谷区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011700", + "value": "密云区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011800", + "value": "延庆区", + "sub": [], + "trace": [ + "010000" + ] + } + ] + }, + { + "id": "020000", + "value": "上海", + "sub": [ + { + "id": "020000", + "value": "上海", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "020100", + "value": "黄浦区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "020300", + "value": "徐汇区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "020400", + "value": "长宁区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "020500", + "value": "静安区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "020600", + "value": "普陀区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "020800", + "value": "虹口区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "020900", + "value": "杨浦区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021000", + "value": "浦东新区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021100", + "value": "闵行区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021200", + "value": "宝山区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021300", + "value": "嘉定区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021400", + "value": "金山区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021500", + "value": "松江区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021600", + "value": "青浦区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021800", + "value": "奉贤区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021900", + "value": "崇明区", + "sub": [], + "trace": [ + "020000" + ] + } + ] + }, + { + "id": "030200", + "value": "广州", + "sub": [ + { + "id": "030200", + "value": "广州", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030201", + "value": "越秀区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030202", + "value": "荔湾区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030203", + "value": "海珠区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030204", + "value": "天河区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030205", + "value": "白云区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030206", + "value": "黄埔区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030207", + "value": "番禺区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030208", + "value": "花都区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030209", + "value": "南沙区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030211", + "value": "增城区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030212", + "value": "从化区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + } + ] + }, + { + "id": "040000", + "value": "深圳", + "sub": [ + { + "id": "040000", + "value": "深圳", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040100", + "value": "福田区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040200", + "value": "罗湖区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040300", + "value": "南山区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040400", + "value": "盐田区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040500", + "value": "宝安区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040600", + "value": "龙岗区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040700", + "value": "光明区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040800", + "value": "坪山区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040900", + "value": "大鹏新区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "041000", + "value": "龙华区", + "sub": [], + "trace": [ + "040000" + ] + } + ] + }, + { + "id": "180200", + "value": "武汉", + "sub": [ + { + "id": "180200", + "value": "武汉", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180201", + "value": "江岸区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180202", + "value": "江汉区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180203", + "value": "硚口区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180204", + "value": "汉阳区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180205", + "value": "武昌区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180206", + "value": "青山区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180207", + "value": "洪山区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180208", + "value": "东西湖区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180209", + "value": "汉南区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180210", + "value": "蔡甸区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180211", + "value": "江夏区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180212", + "value": "黄陂区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180213", + "value": "新洲区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180214", + "value": "武汉经济开发区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180215", + "value": "东湖新技术产业开发区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + } + ] + }, + { + "id": "200200", + "value": "西安", + "sub": [ + { + "id": "200200", + "value": "西安", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200201", + "value": "莲湖区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200202", + "value": "新城区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200203", + "value": "碑林区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200204", + "value": "灞桥区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200205", + "value": "未央区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200206", + "value": "雁塔区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200207", + "value": "阎良区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200208", + "value": "临潼区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200209", + "value": "长安区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200210", + "value": "蓝田县", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200211", + "value": "周至县", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200212", + "value": "鄠邑区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200213", + "value": "高陵区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200214", + "value": "高新技术产业开发区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200215", + "value": "经济技术开发区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200216", + "value": "曲江新区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200217", + "value": "浐灞生态区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200218", + "value": "国家民用航天产业基地", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200219", + "value": "西咸新区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200220", + "value": "西安阎良航空基地", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200221", + "value": "西安国际港务区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + } + ] + }, + { + "id": "080200", + "value": "杭州", + "sub": [ + { + "id": "080200", + "value": "杭州", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080201", + "value": "拱墅区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080202", + "value": "上城区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080205", + "value": "西湖区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080206", + "value": "滨江区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080207", + "value": "余杭区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080208", + "value": "萧山区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080209", + "value": "临安区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080210", + "value": "富阳区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080211", + "value": "建德市", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080212", + "value": "桐庐县", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080213", + "value": "淳安县", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080214", + "value": "临平区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080215", + "value": "钱塘区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + } + ] + }, + { + "id": "070200", + "value": "南京", + "sub": [ + { + "id": "070200", + "value": "南京", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070201", + "value": "玄武区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070203", + "value": "秦淮区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070204", + "value": "建邺区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070205", + "value": "鼓楼区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070207", + "value": "浦口区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070208", + "value": "六合区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070209", + "value": "栖霞区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070210", + "value": "雨花台区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070211", + "value": "江宁区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070212", + "value": "溧水区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070213", + "value": "高淳区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070214", + "value": "江北新区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070215", + "value": "经济技术开发区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + } + ] + }, + { + "id": "090200", + "value": "成都", + "sub": [ + { + "id": "090200", + "value": "成都", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090201", + "value": "青羊区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090202", + "value": "锦江区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090203", + "value": "金牛区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090204", + "value": "武侯区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090205", + "value": "成华区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090206", + "value": "龙泉驿区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090207", + "value": "青白江区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090208", + "value": "新都区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090209", + "value": "温江区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090210", + "value": "都江堰市", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090211", + "value": "彭州市", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090212", + "value": "邛崃市", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090213", + "value": "崇州市", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090214", + "value": "金堂县", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090215", + "value": "双流区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090216", + "value": "郫都区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090217", + "value": "大邑县", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090218", + "value": "蒲江县", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090219", + "value": "新津区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090220", + "value": "高新区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090221", + "value": "简阳市", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090222", + "value": "天府新区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090223", + "value": "经济技术开发区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + } + ] + }, + { + "id": "060000", + "value": "重庆", + "sub": [ + { + "id": "060000", + "value": "重庆", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "060100", + "value": "渝中区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "060200", + "value": "大渡口区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "060300", + "value": "江北区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "060400", + "value": "沙坪坝区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "060600", + "value": "合川区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "060700", + "value": "渝北区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "060800", + "value": "永川区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "060900", + "value": "巴南区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061000", + "value": "南川区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061100", + "value": "九龙坡区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061200", + "value": "万州区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061300", + "value": "涪陵区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061400", + "value": "黔江区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061500", + "value": "南岸区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061600", + "value": "北碚区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061700", + "value": "长寿区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061900", + "value": "江津区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062000", + "value": "綦江区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062100", + "value": "潼南区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062200", + "value": "铜梁区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062300", + "value": "大足区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062400", + "value": "荣昌区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062500", + "value": "璧山区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062600", + "value": "垫江县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062700", + "value": "丰都县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062800", + "value": "忠县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062900", + "value": "石柱县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063000", + "value": "城口县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063100", + "value": "彭水县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063200", + "value": "梁平区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063300", + "value": "酉阳县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063400", + "value": "开州区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063500", + "value": "秀山县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063600", + "value": "巫溪县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063700", + "value": "巫山县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063800", + "value": "奉节县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063900", + "value": "武隆区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "064000", + "value": "云阳县", + "sub": [], + "trace": [ + "060000" + ] + } + ] + }, + { + "id": "030800", + "value": "东莞", + "sub": [ + { + "id": "030800", + "value": "东莞", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030801", + "value": "莞城区", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030802", + "value": "南城区", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030803", + "value": "东城区", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030804", + "value": "万江区", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030805", + "value": "石碣镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030806", + "value": "石龙镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030807", + "value": "茶山镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030808", + "value": "石排镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030809", + "value": "企石镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030810", + "value": "横沥镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030811", + "value": "桥头镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030812", + "value": "谢岗镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030813", + "value": "东坑镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030814", + "value": "常平镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030815", + "value": "寮步镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030816", + "value": "大朗镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030817", + "value": "麻涌镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030818", + "value": "中堂镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030819", + "value": "高埗镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030820", + "value": "樟木头镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030821", + "value": "大岭山镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030822", + "value": "望牛墩镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030823", + "value": "黄江镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030824", + "value": "洪梅镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030825", + "value": "清溪镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030826", + "value": "沙田镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030827", + "value": "道滘镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030828", + "value": "塘厦镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030829", + "value": "虎门镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030830", + "value": "厚街镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030831", + "value": "凤岗镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030832", + "value": "长安镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030833", + "value": "松山湖区", + "sub": [], + "trace": [ + "030000", + "030800" + ] + } + ] + }, + { + "id": "230300", + "value": "大连", + "sub": [ + { + "id": "230300", + "value": "大连", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230301", + "value": "西岗区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230302", + "value": "中山区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230303", + "value": "沙河口区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230304", + "value": "甘井子区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230305", + "value": "旅顺口区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230306", + "value": "金州区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230307", + "value": "瓦房店市", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230308", + "value": "普兰店区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230309", + "value": "庄河市", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230310", + "value": "长海县", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230312", + "value": "高新园区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230313", + "value": "长兴岛", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230314", + "value": "大连保税区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + } + ] + }, + { + "id": "230200", + "value": "沈阳", + "sub": [ + { + "id": "230200", + "value": "沈阳", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230201", + "value": "大东区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230202", + "value": "浑南区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230203", + "value": "康平县", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230204", + "value": "和平区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230205", + "value": "皇姑区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230206", + "value": "沈北新区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230207", + "value": "沈河区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230208", + "value": "苏家屯区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230209", + "value": "铁西区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230210", + "value": "于洪区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230211", + "value": "法库县", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230212", + "value": "辽中区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230213", + "value": "新民市", + "sub": [], + "trace": [ + "230000", + "230200" + ] + } + ] + }, + { + "id": "070300", + "value": "苏州", + "sub": [ + { + "id": "070300", + "value": "苏州", + "sub": [], + "trace": [ + "070000", + "070300" + ] + }, + { + "id": "070301", + "value": "姑苏区", + "sub": [], + "trace": [ + "070000", + "070300" + ] + }, + { + "id": "070303", + "value": "吴中区", + "sub": [], + "trace": [ + "070000", + "070300" + ] + }, + { + "id": "070304", + "value": "相城区", + "sub": [], + "trace": [ + "070000", + "070300" + ] + }, + { + "id": "070305", + "value": "吴江区", + "sub": [], + "trace": [ + "070000", + "070300" + ] + }, + { + "id": "070306", + "value": "苏州工业园区", + "sub": [], + "trace": [ + "070000", + "070300" + ] + }, + { + "id": "070307", + "value": "高新区", + "sub": [], + "trace": [ + "070000", + "070300" + ] + }, + { + "id": "070308", + "value": "虎丘区", + "sub": [], + "trace": [ + "070000", + "070300" + ] + } + ] + }, + { + "id": "250200", + "value": "昆明", + "sub": [ + { + "id": "250200", + "value": "昆明", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250201", + "value": "五华区", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250202", + "value": "盘龙区", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250203", + "value": "官渡区", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250204", + "value": "西山区", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250205", + "value": "东川区", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250206", + "value": "呈贡区", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250207", + "value": "晋宁区", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250208", + "value": "富民县", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250209", + "value": "宜良县", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250210", + "value": "石林彝族自治县", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250211", + "value": "嵩明县", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250212", + "value": "禄劝彝族苗族自治县", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250213", + "value": "寻甸县", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250214", + "value": "安宁市", + "sub": [], + "trace": [ + "250000", + "250200" + ] + } + ] + }, + { + "id": "190200", + "value": "长沙", + "sub": [ + { + "id": "190200", + "value": "长沙", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190201", + "value": "芙蓉区", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190202", + "value": "天心区", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190203", + "value": "岳麓区", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190204", + "value": "开福区", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190205", + "value": "雨花区", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190206", + "value": "望城区", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190207", + "value": "长沙县", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190208", + "value": "宁乡市", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190209", + "value": "浏阳市", + "sub": [], + "trace": [ + "190000", + "190200" + ] + } + ] + }, + { + "id": "150200", + "value": "合肥", + "sub": [ + { + "id": "150200", + "value": "合肥", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150201", + "value": "瑶海区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150202", + "value": "庐阳区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150203", + "value": "蜀山区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150204", + "value": "包河区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150205", + "value": "经开区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150206", + "value": "滨湖新区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150207", + "value": "新站区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150208", + "value": "高新区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150209", + "value": "政务区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150210", + "value": "北城新区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150211", + "value": "巢湖市", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150212", + "value": "长丰县", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150213", + "value": "肥东县", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150214", + "value": "肥西县", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150215", + "value": "庐江县", + "sub": [], + "trace": [ + "150000", + "150200" + ] + } + ] + }, + { + "id": "080300", + "value": "宁波", + "sub": [ + { + "id": "080300", + "value": "宁波", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080301", + "value": "海曙区", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080303", + "value": "江北区", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080304", + "value": "北仑区", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080305", + "value": "镇海区", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080306", + "value": "鄞州区", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080307", + "value": "慈溪市", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080308", + "value": "余姚市", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080309", + "value": "奉化区", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080310", + "value": "宁海县", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080311", + "value": "象山县", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080312", + "value": "高新区", + "sub": [], + "trace": [ + "080000", + "080300" + ] + } + ] + }, + { + "id": "170200", + "value": "郑州", + "sub": [ + { + "id": "170200", + "value": "郑州", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170201", + "value": "中原区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170202", + "value": "二七区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170203", + "value": "管城回族区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170204", + "value": "金水区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170205", + "value": "上街区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170206", + "value": "惠济区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170207", + "value": "中牟县", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170208", + "value": "巩义市", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170209", + "value": "荥阳市", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170210", + "value": "新密市", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170211", + "value": "新郑市", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170212", + "value": "登封市", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170213", + "value": "郑东新区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170214", + "value": "高新区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170215", + "value": "经开区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170216", + "value": "郑州航空港区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + } + ] + }, + { + "id": "050000", + "value": "天津", + "sub": [ + { + "id": "050000", + "value": "天津", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050100", + "value": "和平区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050200", + "value": "河东区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050300", + "value": "河西区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050400", + "value": "南开区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050500", + "value": "河北区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050600", + "value": "红桥区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050700", + "value": "东丽区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050800", + "value": "西青区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050900", + "value": "津南区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "051000", + "value": "北辰区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "051100", + "value": "武清区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "051200", + "value": "宝坻区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "051300", + "value": "滨海新区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "051400", + "value": "宁河区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "051500", + "value": "静海区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "051600", + "value": "蓟州区", + "sub": [], + "trace": [ + "050000" + ] + } + ] + }, + { + "id": "120300", + "value": "青岛", + "sub": [ + { + "id": "120300", + "value": "青岛", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120301", + "value": "市南区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120302", + "value": "市北区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120303", + "value": "黄岛区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120304", + "value": "崂山区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120305", + "value": "城阳区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120306", + "value": "李沧区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120307", + "value": "胶州市", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120308", + "value": "即墨区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120309", + "value": "平度市", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120310", + "value": "莱西市", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120311", + "value": "高新区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + } + ] + }, + { + "id": "120200", + "value": "济南", + "sub": [ + { + "id": "120200", + "value": "济南", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120201", + "value": "历下区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120202", + "value": "市中区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120203", + "value": "槐荫区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120204", + "value": "天桥区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120205", + "value": "历城区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120206", + "value": "长清区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120207", + "value": "平阴县", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120208", + "value": "济阳区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120209", + "value": "商河县", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120210", + "value": "章丘区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120211", + "value": "高新区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120212", + "value": "莱芜区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120213", + "value": "钢城区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + } + ] + }, + { + "id": "220200", + "value": "哈尔滨", + "sub": [ + { + "id": "220200", + "value": "哈尔滨", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220201", + "value": "道里区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220202", + "value": "南岗区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220203", + "value": "道外区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220204", + "value": "平房区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220205", + "value": "松北区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220206", + "value": "香坊区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220207", + "value": "呼兰区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220208", + "value": "阿城区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220209", + "value": "依兰县", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220210", + "value": "方正县", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220211", + "value": "宾县", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220212", + "value": "巴彦县", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220213", + "value": "木兰县", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220214", + "value": "通河县", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220215", + "value": "延寿县", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220216", + "value": "双城区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220217", + "value": "尚志市", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220218", + "value": "五常市", + "sub": [], + "trace": [ + "220000", + "220200" + ] + } + ] + }, + { + "id": "240200", + "value": "长春", + "sub": [ + { + "id": "240200", + "value": "长春", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240201", + "value": "朝阳区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240202", + "value": "南关区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240203", + "value": "宽城区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240204", + "value": "二道区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240205", + "value": "绿园区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240206", + "value": "双阳区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240207", + "value": "经济技术开发区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240208", + "value": "高新技术产业开发区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240209", + "value": "净月高新技术产业开发区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240210", + "value": "汽车经济技术开发区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240211", + "value": "榆树市", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240212", + "value": "九台区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240213", + "value": "德惠市", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240214", + "value": "农安县", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240215", + "value": "公主岭市", + "sub": [], + "trace": [ + "240000", + "240200" + ] + } + ] + }, + { + "id": "110200", + "value": "福州", + "sub": [ + { + "id": "110200", + "value": "福州", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110201", + "value": "鼓楼区", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110202", + "value": "台江区", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110203", + "value": "仓山区", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110204", + "value": "马尾区", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110205", + "value": "晋安区", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110206", + "value": "闽侯县", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110207", + "value": "连江县", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110208", + "value": "罗源县", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110209", + "value": "闽清县", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110210", + "value": "永泰县", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110211", + "value": "平潭县", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110212", + "value": "福清市", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110213", + "value": "长乐区", + "sub": [], + "trace": [ + "110000", + "110200" + ] + } + ] + } + ] + }, + { + "id": "010000", + "value": "北京", + "sub": [ + { + "id": "010000", + "value": "北京", + "sub": [ + { + "id": "010100", + "value": "东城区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "010200", + "value": "西城区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "010500", + "value": "朝阳区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "010600", + "value": "丰台区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "010700", + "value": "石景山区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "010800", + "value": "海淀区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "010900", + "value": "门头沟区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011000", + "value": "房山区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011100", + "value": "通州区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011200", + "value": "顺义区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011300", + "value": "昌平区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011400", + "value": "大兴区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011500", + "value": "怀柔区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011600", + "value": "平谷区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011700", + "value": "密云区", + "sub": [], + "trace": [ + "010000" + ] + }, + { + "id": "011800", + "value": "延庆区", + "sub": [], + "trace": [ + "010000" + ] + } + ] + } + ] + }, + { + "id": "020000", + "value": "上海", + "sub": [ + { + "id": "020000", + "value": "上海", + "sub": [ + { + "id": "020100", + "value": "黄浦区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "020300", + "value": "徐汇区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "020400", + "value": "长宁区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "020500", + "value": "静安区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "020600", + "value": "普陀区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "020800", + "value": "虹口区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "020900", + "value": "杨浦区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021000", + "value": "浦东新区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021100", + "value": "闵行区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021200", + "value": "宝山区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021300", + "value": "嘉定区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021400", + "value": "金山区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021500", + "value": "松江区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021600", + "value": "青浦区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021800", + "value": "奉贤区", + "sub": [], + "trace": [ + "020000" + ] + }, + { + "id": "021900", + "value": "崇明区", + "sub": [], + "trace": [ + "020000" + ] + } + ] + } + ] + }, + { + "id": "030000", + "value": "广东省", + "sub": [ + { + "id": "030200", + "value": "广州", + "sub": [ + { + "id": "030200", + "value": "广州", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030201", + "value": "越秀区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030202", + "value": "荔湾区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030203", + "value": "海珠区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030204", + "value": "天河区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030205", + "value": "白云区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030206", + "value": "黄埔区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030207", + "value": "番禺区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030208", + "value": "花都区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030209", + "value": "南沙区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030211", + "value": "增城区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + }, + { + "id": "030212", + "value": "从化区", + "sub": [], + "trace": [ + "030000", + "030200" + ] + } + ], + "trace": [ + "030000" + ] + }, + { + "id": "040000", + "value": "深圳", + "sub": [ + { + "id": "040000", + "value": "深圳", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040100", + "value": "福田区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040200", + "value": "罗湖区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040300", + "value": "南山区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040400", + "value": "盐田区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040500", + "value": "宝安区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040600", + "value": "龙岗区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040700", + "value": "光明区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040800", + "value": "坪山区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040900", + "value": "大鹏新区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "041000", + "value": "龙华区", + "sub": [], + "trace": [ + "040000" + ] + } + ] + }, + { + "id": "030300", + "value": "惠州", + "sub": [ + { + "id": "030300", + "value": "惠州", + "sub": [], + "trace": [ + "030000", + "030300" + ] + }, + { + "id": "030301", + "value": "惠城区", + "sub": [], + "trace": [ + "030000", + "030300" + ] + }, + { + "id": "030302", + "value": "惠阳区", + "sub": [], + "trace": [ + "030000", + "030300" + ] + }, + { + "id": "030303", + "value": "大亚湾区", + "sub": [], + "trace": [ + "030000", + "030300" + ] + }, + { + "id": "030304", + "value": "仲恺区", + "sub": [], + "trace": [ + "030000", + "030300" + ] + }, + { + "id": "030305", + "value": "惠东县", + "sub": [], + "trace": [ + "030000", + "030300" + ] + }, + { + "id": "030306", + "value": "博罗县", + "sub": [], + "trace": [ + "030000", + "030300" + ] + }, + { + "id": "030307", + "value": "龙门县", + "sub": [], + "trace": [ + "030000", + "030300" + ] + } + ], + "trace": [ + "030000" + ] + }, + { + "id": "030400", + "value": "汕头", + "sub": [ + { + "id": "030400", + "value": "汕头", + "sub": [], + "trace": [ + "030000", + "030400" + ] + }, + { + "id": "030401", + "value": "金平区", + "sub": [], + "trace": [ + "030000", + "030400" + ] + }, + { + "id": "030402", + "value": "龙湖区", + "sub": [], + "trace": [ + "030000", + "030400" + ] + }, + { + "id": "030403", + "value": "澄海区", + "sub": [], + "trace": [ + "030000", + "030400" + ] + }, + { + "id": "030404", + "value": "濠江区", + "sub": [], + "trace": [ + "030000", + "030400" + ] + }, + { + "id": "030405", + "value": "潮阳区", + "sub": [], + "trace": [ + "030000", + "030400" + ] + }, + { + "id": "030406", + "value": "潮南区", + "sub": [], + "trace": [ + "030000", + "030400" + ] + }, + { + "id": "030407", + "value": "南澳县", + "sub": [], + "trace": [ + "030000", + "030400" + ] + } + ], + "trace": [ + "030000" + ] + }, + { + "id": "030500", + "value": "珠海", + "sub": [ + { + "id": "030500", + "value": "珠海", + "sub": [], + "trace": [ + "030000", + "030500" + ] + }, + { + "id": "030501", + "value": "香洲区", + "sub": [], + "trace": [ + "030000", + "030500" + ] + }, + { + "id": "030502", + "value": "斗门区", + "sub": [], + "trace": [ + "030000", + "030500" + ] + }, + { + "id": "030503", + "value": "金湾区", + "sub": [], + "trace": [ + "030000", + "030500" + ] + }, + { + "id": "030504", + "value": "横琴新区", + "sub": [], + "trace": [ + "030000", + "030500" + ] + }, + { + "id": "030505", + "value": "高栏港经济区", + "sub": [], + "trace": [ + "030000", + "030500" + ] + }, + { + "id": "030506", + "value": "珠海高新区", + "sub": [], + "trace": [ + "030000", + "030500" + ] + }, + { + "id": "030507", + "value": "珠海保税区", + "sub": [], + "trace": [ + "030000", + "030500" + ] + }, + { + "id": "030508", + "value": "万山海洋开发试验区", + "sub": [], + "trace": [ + "030000", + "030500" + ] + } + ], + "trace": [ + "030000" + ] + }, + { + "id": "030600", + "value": "佛山", + "sub": [ + { + "id": "030600", + "value": "佛山", + "sub": [], + "trace": [ + "030000", + "030600" + ] + }, + { + "id": "030601", + "value": "禅城区", + "sub": [], + "trace": [ + "030000", + "030600" + ] + }, + { + "id": "030602", + "value": "顺德区", + "sub": [], + "trace": [ + "030000", + "030600" + ] + }, + { + "id": "030603", + "value": "南海区", + "sub": [], + "trace": [ + "030000", + "030600" + ] + }, + { + "id": "030604", + "value": "三水区", + "sub": [], + "trace": [ + "030000", + "030600" + ] + }, + { + "id": "030605", + "value": "高明区", + "sub": [], + "trace": [ + "030000", + "030600" + ] + } + ], + "trace": [ + "030000" + ] + }, + { + "id": "030700", + "value": "中山", + "sub": [ + { + "id": "030700", + "value": "中山", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030701", + "value": "石岐区", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030702", + "value": "东区", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030703", + "value": "西区", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030704", + "value": "南区", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030705", + "value": "五桂山区", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030706", + "value": "火炬开发区", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030707", + "value": "黄圃镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030708", + "value": "南头镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030709", + "value": "东凤镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030710", + "value": "阜沙镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030711", + "value": "小榄镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030712", + "value": "东升镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030713", + "value": "古镇镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030714", + "value": "横栏镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030715", + "value": "三角镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030716", + "value": "民众镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030717", + "value": "南朗镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030718", + "value": "港口镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030719", + "value": "大涌镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030720", + "value": "沙溪镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030721", + "value": "三乡镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030722", + "value": "板芙镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030723", + "value": "神湾镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + }, + { + "id": "030724", + "value": "坦洲镇", + "sub": [], + "trace": [ + "030000", + "030700" + ] + } + ], + "trace": [ + "030000" + ] + }, + { + "id": "030800", + "value": "东莞", + "sub": [ + { + "id": "030800", + "value": "东莞", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030801", + "value": "莞城区", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030802", + "value": "南城区", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030803", + "value": "东城区", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030804", + "value": "万江区", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030805", + "value": "石碣镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030806", + "value": "石龙镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030807", + "value": "茶山镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030808", + "value": "石排镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030809", + "value": "企石镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030810", + "value": "横沥镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030811", + "value": "桥头镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030812", + "value": "谢岗镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030813", + "value": "东坑镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030814", + "value": "常平镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030815", + "value": "寮步镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030816", + "value": "大朗镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030817", + "value": "麻涌镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030818", + "value": "中堂镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030819", + "value": "高埗镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030820", + "value": "樟木头镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030821", + "value": "大岭山镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030822", + "value": "望牛墩镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030823", + "value": "黄江镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030824", + "value": "洪梅镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030825", + "value": "清溪镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030826", + "value": "沙田镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030827", + "value": "道滘镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030828", + "value": "塘厦镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030829", + "value": "虎门镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030830", + "value": "厚街镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030831", + "value": "凤岗镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030832", + "value": "长安镇", + "sub": [], + "trace": [ + "030000", + "030800" + ] + }, + { + "id": "030833", + "value": "松山湖区", + "sub": [], + "trace": [ + "030000", + "030800" + ] + } + ], + "trace": [ + "030000" + ] + }, + { + "id": "031400", + "value": "韶关", + "sub": [], + "trace": [ + "030000" + ] + }, + { + "id": "031500", + "value": "江门", + "sub": [ + { + "id": "031500", + "value": "江门", + "sub": [], + "trace": [ + "030000", + "031500" + ] + }, + { + "id": "031501", + "value": "蓬江区", + "sub": [], + "trace": [ + "030000", + "031500" + ] + }, + { + "id": "031502", + "value": "江海区", + "sub": [], + "trace": [ + "030000", + "031500" + ] + }, + { + "id": "031503", + "value": "新会区", + "sub": [], + "trace": [ + "030000", + "031500" + ] + }, + { + "id": "031504", + "value": "台山市", + "sub": [], + "trace": [ + "030000", + "031500" + ] + }, + { + "id": "031505", + "value": "开平市", + "sub": [], + "trace": [ + "030000", + "031500" + ] + }, + { + "id": "031506", + "value": "鹤山市", + "sub": [], + "trace": [ + "030000", + "031500" + ] + }, + { + "id": "031507", + "value": "恩平市", + "sub": [], + "trace": [ + "030000", + "031500" + ] + } + ], + "trace": [ + "030000" + ] + }, + { + "id": "031700", + "value": "湛江", + "sub": [ + { + "id": "031700", + "value": "湛江", + "sub": [], + "trace": [ + "030000", + "031700" + ] + }, + { + "id": "031701", + "value": "赤坎区", + "sub": [], + "trace": [ + "030000", + "031700" + ] + }, + { + "id": "031702", + "value": "霞山区", + "sub": [], + "trace": [ + "030000", + "031700" + ] + }, + { + "id": "031703", + "value": "坡头区", + "sub": [], + "trace": [ + "030000", + "031700" + ] + }, + { + "id": "031704", + "value": "麻章区", + "sub": [], + "trace": [ + "030000", + "031700" + ] + }, + { + "id": "031705", + "value": "廉江市", + "sub": [], + "trace": [ + "030000", + "031700" + ] + }, + { + "id": "031706", + "value": "雷州市", + "sub": [], + "trace": [ + "030000", + "031700" + ] + }, + { + "id": "031707", + "value": "吴川市", + "sub": [], + "trace": [ + "030000", + "031700" + ] + }, + { + "id": "031708", + "value": "遂溪县", + "sub": [], + "trace": [ + "030000", + "031700" + ] + }, + { + "id": "031709", + "value": "徐闻县", + "sub": [], + "trace": [ + "030000", + "031700" + ] + } + ], + "trace": [ + "030000" + ] + }, + { + "id": "031800", + "value": "肇庆", + "sub": [ + { + "id": "031800", + "value": "肇庆", + "sub": [], + "trace": [ + "030000", + "031800" + ] + }, + { + "id": "031801", + "value": "端州区", + "sub": [], + "trace": [ + "030000", + "031800" + ] + }, + { + "id": "031802", + "value": "鼎湖区", + "sub": [], + "trace": [ + "030000", + "031800" + ] + }, + { + "id": "031803", + "value": "高要区", + "sub": [], + "trace": [ + "030000", + "031800" + ] + }, + { + "id": "031804", + "value": "广宁县", + "sub": [], + "trace": [ + "030000", + "031800" + ] + }, + { + "id": "031805", + "value": "德庆县", + "sub": [], + "trace": [ + "030000", + "031800" + ] + }, + { + "id": "031806", + "value": "封开县", + "sub": [], + "trace": [ + "030000", + "031800" + ] + }, + { + "id": "031807", + "value": "怀集县", + "sub": [], + "trace": [ + "030000", + "031800" + ] + }, + { + "id": "031808", + "value": "四会市", + "sub": [], + "trace": [ + "030000", + "031800" + ] + } + ], + "trace": [ + "030000" + ] + }, + { + "id": "031900", + "value": "清远", + "sub": [ + { + "id": "031900", + "value": "清远", + "sub": [], + "trace": [ + "030000", + "031900" + ] + }, + { + "id": "031901", + "value": "清城区", + "sub": [], + "trace": [ + "030000", + "031900" + ] + }, + { + "id": "031902", + "value": "清新区", + "sub": [], + "trace": [ + "030000", + "031900" + ] + }, + { + "id": "031903", + "value": "英德市", + "sub": [], + "trace": [ + "030000", + "031900" + ] + }, + { + "id": "031904", + "value": "连州市", + "sub": [], + "trace": [ + "030000", + "031900" + ] + }, + { + "id": "031905", + "value": "佛冈县", + "sub": [], + "trace": [ + "030000", + "031900" + ] + }, + { + "id": "031906", + "value": "阳山县", + "sub": [], + "trace": [ + "030000", + "031900" + ] + }, + { + "id": "031907", + "value": "连南瑶族自治县", + "sub": [], + "trace": [ + "030000", + "031900" + ] + }, + { + "id": "031908", + "value": "连山壮族瑶族自治县", + "sub": [], + "trace": [ + "030000", + "031900" + ] + } + ], + "trace": [ + "030000" + ] + }, + { + "id": "032000", + "value": "潮州", + "sub": [], + "trace": [ + "030000" + ] + }, + { + "id": "032100", + "value": "河源", + "sub": [], + "trace": [ + "030000" + ] + }, + { + "id": "032200", + "value": "揭阳", + "sub": [], + "trace": [ + "030000" + ] + }, + { + "id": "032300", + "value": "茂名", + "sub": [], + "trace": [ + "030000" + ] + }, + { + "id": "032400", + "value": "汕尾", + "sub": [], + "trace": [ + "030000" + ] + }, + { + "id": "032600", + "value": "梅州", + "sub": [], + "trace": [ + "030000" + ] + }, + { + "id": "032700", + "value": "开平", + "sub": [], + "trace": [ + "030000" + ] + }, + { + "id": "032800", + "value": "阳江", + "sub": [], + "trace": [ + "030000" + ] + }, + { + "id": "032900", + "value": "云浮", + "sub": [], + "trace": [ + "030000" + ] + } + ], + "trace": [ + "070000" + ] + }, + { + "id": "040000", + "value": "深圳", + "sub": [ + { + "id": "040000", + "value": "深圳", + "sub": [ + { + "id": "040100", + "value": "福田区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040200", + "value": "罗湖区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040300", + "value": "南山区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040400", + "value": "盐田区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040500", + "value": "宝安区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040600", + "value": "龙岗区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040700", + "value": "光明区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040800", + "value": "坪山区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "040900", + "value": "大鹏新区", + "sub": [], + "trace": [ + "040000" + ] + }, + { + "id": "041000", + "value": "龙华区", + "sub": [], + "trace": [ + "040000" + ] + } + ], + "trace": [ + "040000" + ] + } + ] + }, + { + "id": "050000", + "value": "天津", + "sub": [ + { + "id": "050000", + "value": "天津", + "sub": [ + { + "id": "050100", + "value": "和平区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050200", + "value": "河东区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050300", + "value": "河西区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050400", + "value": "南开区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050500", + "value": "河北区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050600", + "value": "红桥区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050700", + "value": "东丽区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050800", + "value": "西青区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "050900", + "value": "津南区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "051000", + "value": "北辰区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "051100", + "value": "武清区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "051200", + "value": "宝坻区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "051300", + "value": "滨海新区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "051400", + "value": "宁河区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "051500", + "value": "静海区", + "sub": [], + "trace": [ + "050000" + ] + }, + { + "id": "051600", + "value": "蓟州区", + "sub": [], + "trace": [ + "050000" + ] + } + ], + "trace": [ + "050000" + ] + } + ] + }, + { + "id": "060000", + "value": "重庆", + "sub": [ + { + "id": "060000", + "value": "重庆", + "sub": [ + { + "id": "060100", + "value": "渝中区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "060200", + "value": "大渡口区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "060300", + "value": "江北区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "060400", + "value": "沙坪坝区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "060600", + "value": "合川区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "060700", + "value": "渝北区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "060800", + "value": "永川区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "060900", + "value": "巴南区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061000", + "value": "南川区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061100", + "value": "九龙坡区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061200", + "value": "万州区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061300", + "value": "涪陵区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061400", + "value": "黔江区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061500", + "value": "南岸区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061600", + "value": "北碚区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061700", + "value": "长寿区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "061900", + "value": "江津区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062000", + "value": "綦江区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062100", + "value": "潼南区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062200", + "value": "铜梁区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062300", + "value": "大足区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062400", + "value": "荣昌区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062500", + "value": "璧山区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062600", + "value": "垫江县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062700", + "value": "丰都县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062800", + "value": "忠县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "062900", + "value": "石柱县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063000", + "value": "城口县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063100", + "value": "彭水县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063200", + "value": "梁平区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063300", + "value": "酉阳县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063400", + "value": "开州区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063500", + "value": "秀山县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063600", + "value": "巫溪县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063700", + "value": "巫山县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063800", + "value": "奉节县", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "063900", + "value": "武隆区", + "sub": [], + "trace": [ + "060000" + ] + }, + { + "id": "064000", + "value": "云阳县", + "sub": [], + "trace": [ + "060000" + ] + } + ], + "trace": [ + "060000" + ] + } + ] + }, + { + "id": "070000", + "value": "江苏省", + "sub": [ + { + "id": "070200", + "value": "南京", + "sub": [ + { + "id": "070200", + "value": "南京", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070201", + "value": "玄武区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070203", + "value": "秦淮区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070204", + "value": "建邺区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070205", + "value": "鼓楼区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070207", + "value": "浦口区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070208", + "value": "六合区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070209", + "value": "栖霞区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070210", + "value": "雨花台区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070211", + "value": "江宁区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070212", + "value": "溧水区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070213", + "value": "高淳区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070214", + "value": "江北新区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + }, + { + "id": "070215", + "value": "经济技术开发区", + "sub": [], + "trace": [ + "070000", + "070200" + ] + } + ], + "trace": [ + "070000" + ] + }, + { + "id": "070300", + "value": "苏州", + "sub": [ + { + "id": "070300", + "value": "苏州", + "sub": [], + "trace": [ + "070000", + "070300" + ] + }, + { + "id": "070301", + "value": "姑苏区", + "sub": [], + "trace": [ + "070000", + "070300" + ] + }, + { + "id": "070303", + "value": "吴中区", + "sub": [], + "trace": [ + "070000", + "070300" + ] + }, + { + "id": "070304", + "value": "相城区", + "sub": [], + "trace": [ + "070000", + "070300" + ] + }, + { + "id": "070305", + "value": "吴江区", + "sub": [], + "trace": [ + "070000", + "070300" + ] + }, + { + "id": "070306", + "value": "苏州工业园区", + "sub": [], + "trace": [ + "070000", + "070300" + ] + }, + { + "id": "070307", + "value": "高新区", + "sub": [], + "trace": [ + "070000", + "070300" + ] + }, + { + "id": "070308", + "value": "虎丘区", + "sub": [], + "trace": [ + "070000", + "070300" + ] + } + ], + "trace": [ + "070000" + ] + }, + { + "id": "070400", + "value": "无锡", + "sub": [ + { + "id": "070400", + "value": "无锡", + "sub": [], + "trace": [ + "070000", + "070400" + ] + }, + { + "id": "070401", + "value": "梁溪区", + "sub": [], + "trace": [ + "070000", + "070400" + ] + }, + { + "id": "070404", + "value": "滨湖区", + "sub": [], + "trace": [ + "070000", + "070400" + ] + }, + { + "id": "070405", + "value": "新吴区", + "sub": [], + "trace": [ + "070000", + "070400" + ] + }, + { + "id": "070406", + "value": "惠山区", + "sub": [], + "trace": [ + "070000", + "070400" + ] + }, + { + "id": "070407", + "value": "锡山区", + "sub": [], + "trace": [ + "070000", + "070400" + ] + }, + { + "id": "070408", + "value": "宜兴市", + "sub": [], + "trace": [ + "070000", + "070400" + ] + }, + { + "id": "070409", + "value": "江阴市", + "sub": [], + "trace": [ + "070000", + "070400" + ] + } + ], + "trace": [ + "070000" + ] + }, + { + "id": "070500", + "value": "常州", + "sub": [ + { + "id": "070500", + "value": "常州", + "sub": [], + "trace": [ + "070000", + "070500" + ] + }, + { + "id": "070501", + "value": "天宁区", + "sub": [], + "trace": [ + "070000", + "070500" + ] + }, + { + "id": "070502", + "value": "钟楼区", + "sub": [], + "trace": [ + "070000", + "070500" + ] + }, + { + "id": "070504", + "value": "新北区", + "sub": [], + "trace": [ + "070000", + "070500" + ] + }, + { + "id": "070505", + "value": "武进区", + "sub": [], + "trace": [ + "070000", + "070500" + ] + }, + { + "id": "070506", + "value": "金坛区", + "sub": [], + "trace": [ + "070000", + "070500" + ] + }, + { + "id": "070507", + "value": "溧阳市", + "sub": [], + "trace": [ + "070000", + "070500" + ] + } + ], + "trace": [ + "070000" + ] + }, + { + "id": "070600", + "value": "昆山", + "sub": [], + "trace": [ + "070000" + ] + }, + { + "id": "070700", + "value": "常熟", + "sub": [], + "trace": [ + "070000" + ] + }, + { + "id": "070800", + "value": "扬州", + "sub": [ + { + "id": "070800", + "value": "扬州", + "sub": [], + "trace": [ + "070000", + "070800" + ] + }, + { + "id": "070801", + "value": "邗江区", + "sub": [], + "trace": [ + "070000", + "070800" + ] + }, + { + "id": "070802", + "value": "广陵区", + "sub": [], + "trace": [ + "070000", + "070800" + ] + }, + { + "id": "070803", + "value": "江都区", + "sub": [], + "trace": [ + "070000", + "070800" + ] + }, + { + "id": "070804", + "value": "仪征市", + "sub": [], + "trace": [ + "070000", + "070800" + ] + }, + { + "id": "070805", + "value": "高邮市", + "sub": [], + "trace": [ + "070000", + "070800" + ] + }, + { + "id": "070806", + "value": "宝应县", + "sub": [], + "trace": [ + "070000", + "070800" + ] + }, + { + "id": "070807", + "value": "开发区", + "sub": [], + "trace": [ + "070000", + "070800" + ] + } + ], + "trace": [ + "070000" + ] + }, + { + "id": "070900", + "value": "南通", + "sub": [ + { + "id": "070900", + "value": "南通", + "sub": [], + "trace": [ + "070000", + "070900" + ] + }, + { + "id": "070901", + "value": "崇川区", + "sub": [], + "trace": [ + "070000", + "070900" + ] + }, + { + "id": "070903", + "value": "通州区", + "sub": [], + "trace": [ + "070000", + "070900" + ] + }, + { + "id": "070904", + "value": "如东县", + "sub": [], + "trace": [ + "070000", + "070900" + ] + }, + { + "id": "070905", + "value": "如皋市", + "sub": [], + "trace": [ + "070000", + "070900" + ] + }, + { + "id": "070906", + "value": "海门区", + "sub": [], + "trace": [ + "070000", + "070900" + ] + }, + { + "id": "070907", + "value": "启东市", + "sub": [], + "trace": [ + "070000", + "070900" + ] + }, + { + "id": "070908", + "value": "海安市", + "sub": [], + "trace": [ + "070000", + "070900" + ] + } + ], + "trace": [ + "070000" + ] + }, + { + "id": "071000", + "value": "镇江", + "sub": [ + { + "id": "071000", + "value": "镇江", + "sub": [], + "trace": [ + "070000", + "071000" + ] + }, + { + "id": "071001", + "value": "京口区", + "sub": [], + "trace": [ + "070000", + "071000" + ] + }, + { + "id": "071002", + "value": "润州区", + "sub": [], + "trace": [ + "070000", + "071000" + ] + }, + { + "id": "071003", + "value": "丹徒区", + "sub": [], + "trace": [ + "070000", + "071000" + ] + }, + { + "id": "071005", + "value": "扬中市", + "sub": [], + "trace": [ + "070000", + "071000" + ] + }, + { + "id": "071006", + "value": "句容市", + "sub": [], + "trace": [ + "070000", + "071000" + ] + }, + { + "id": "071007", + "value": "丹阳市", + "sub": [], + "trace": [ + "070000", + "071000" + ] + }, + { + "id": "071008", + "value": "镇江新区", + "sub": [], + "trace": [ + "070000", + "071000" + ] + }, + { + "id": "071009", + "value": "镇江高新区", + "sub": [], + "trace": [ + "070000", + "071000" + ] + } + ], + "trace": [ + "070000" + ] + }, + { + "id": "071100", + "value": "徐州", + "sub": [ + { + "id": "071100", + "value": "徐州", + "sub": [], + "trace": [ + "070000", + "071100" + ] + }, + { + "id": "071101", + "value": "泉山区", + "sub": [], + "trace": [ + "070000", + "071100" + ] + }, + { + "id": "071102", + "value": "鼓楼区", + "sub": [], + "trace": [ + "070000", + "071100" + ] + }, + { + "id": "071103", + "value": "云龙区", + "sub": [], + "trace": [ + "070000", + "071100" + ] + }, + { + "id": "071104", + "value": "贾汪区", + "sub": [], + "trace": [ + "070000", + "071100" + ] + }, + { + "id": "071105", + "value": "铜山区", + "sub": [], + "trace": [ + "070000", + "071100" + ] + }, + { + "id": "071106", + "value": "睢宁县", + "sub": [], + "trace": [ + "070000", + "071100" + ] + }, + { + "id": "071107", + "value": "沛县", + "sub": [], + "trace": [ + "070000", + "071100" + ] + }, + { + "id": "071108", + "value": "丰县", + "sub": [], + "trace": [ + "070000", + "071100" + ] + }, + { + "id": "071109", + "value": "邳州市", + "sub": [], + "trace": [ + "070000", + "071100" + ] + }, + { + "id": "071110", + "value": "新沂市", + "sub": [], + "trace": [ + "070000", + "071100" + ] + } + ], + "trace": [ + "070000" + ] + }, + { + "id": "071200", + "value": "连云港", + "sub": [ + { + "id": "071200", + "value": "连云港", + "sub": [], + "trace": [ + "070000", + "071200" + ] + }, + { + "id": "071201", + "value": "海州区", + "sub": [], + "trace": [ + "070000", + "071200" + ] + }, + { + "id": "071202", + "value": "连云区", + "sub": [], + "trace": [ + "070000", + "071200" + ] + }, + { + "id": "071203", + "value": "赣榆区", + "sub": [], + "trace": [ + "070000", + "071200" + ] + }, + { + "id": "071204", + "value": "灌云县", + "sub": [], + "trace": [ + "070000", + "071200" + ] + }, + { + "id": "071205", + "value": "东海县", + "sub": [], + "trace": [ + "070000", + "071200" + ] + }, + { + "id": "071206", + "value": "灌南县", + "sub": [], + "trace": [ + "070000", + "071200" + ] + } + ], + "trace": [ + "070000" + ] + }, + { + "id": "071300", + "value": "盐城", + "sub": [ + { + "id": "071300", + "value": "盐城", + "sub": [], + "trace": [ + "070000", + "071300" + ] + }, + { + "id": "071301", + "value": "亭湖区", + "sub": [], + "trace": [ + "070000", + "071300" + ] + }, + { + "id": "071302", + "value": "盐都区", + "sub": [], + "trace": [ + "070000", + "071300" + ] + }, + { + "id": "071303", + "value": "大丰区", + "sub": [], + "trace": [ + "070000", + "071300" + ] + }, + { + "id": "071304", + "value": "建湖县", + "sub": [], + "trace": [ + "070000", + "071300" + ] + }, + { + "id": "071305", + "value": "射阳县", + "sub": [], + "trace": [ + "070000", + "071300" + ] + }, + { + "id": "071306", + "value": "阜宁县", + "sub": [], + "trace": [ + "070000", + "071300" + ] + }, + { + "id": "071307", + "value": "滨海县", + "sub": [], + "trace": [ + "070000", + "071300" + ] + }, + { + "id": "071308", + "value": "响水县", + "sub": [], + "trace": [ + "070000", + "071300" + ] + }, + { + "id": "071309", + "value": "东台市", + "sub": [], + "trace": [ + "070000", + "071300" + ] + } + ], + "trace": [ + "070000" + ] + }, + { + "id": "071400", + "value": "张家港", + "sub": [], + "trace": [ + "070000" + ] + }, + { + "id": "071600", + "value": "太仓", + "sub": [], + "trace": [ + "070000" + ] + }, + { + "id": "071800", + "value": "泰州", + "sub": [ + { + "id": "071800", + "value": "泰州", + "sub": [], + "trace": [ + "070000", + "071800" + ] + }, + { + "id": "071801", + "value": "海陵区", + "sub": [], + "trace": [ + "070000", + "071800" + ] + }, + { + "id": "071802", + "value": "高港区", + "sub": [], + "trace": [ + "070000", + "071800" + ] + }, + { + "id": "071803", + "value": "姜堰区", + "sub": [], + "trace": [ + "070000", + "071800" + ] + }, + { + "id": "071804", + "value": "兴化市", + "sub": [], + "trace": [ + "070000", + "071800" + ] + }, + { + "id": "071805", + "value": "泰兴市", + "sub": [], + "trace": [ + "070000", + "071800" + ] + }, + { + "id": "071806", + "value": "靖江市", + "sub": [], + "trace": [ + "070000", + "071800" + ] + } + ], + "trace": [ + "070000" + ] + }, + { + "id": "071900", + "value": "淮安", + "sub": [ + { + "id": "071900", + "value": "淮安", + "sub": [], + "trace": [ + "070000", + "071900" + ] + }, + { + "id": "071901", + "value": "清江浦区", + "sub": [], + "trace": [ + "070000", + "071900" + ] + }, + { + "id": "071902", + "value": "淮阴区", + "sub": [], + "trace": [ + "070000", + "071900" + ] + }, + { + "id": "071903", + "value": "淮安区", + "sub": [], + "trace": [ + "070000", + "071900" + ] + }, + { + "id": "071904", + "value": "洪泽区", + "sub": [], + "trace": [ + "070000", + "071900" + ] + }, + { + "id": "071905", + "value": "涟水县", + "sub": [], + "trace": [ + "070000", + "071900" + ] + }, + { + "id": "071906", + "value": "盱眙县", + "sub": [], + "trace": [ + "070000", + "071900" + ] + }, + { + "id": "071907", + "value": "金湖县", + "sub": [], + "trace": [ + "070000", + "071900" + ] + } + ], + "trace": [ + "070000" + ] + }, + { + "id": "072000", + "value": "宿迁", + "sub": [], + "trace": [ + "070000" + ] + } + ], + "trace": [ + "070000" + ] + }, + { + "id": "080000", + "value": "浙江省", + "sub": [ + { + "id": "080200", + "value": "杭州", + "sub": [ + { + "id": "080200", + "value": "杭州", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080201", + "value": "拱墅区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080202", + "value": "上城区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080205", + "value": "西湖区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080206", + "value": "滨江区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080207", + "value": "余杭区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080208", + "value": "萧山区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080209", + "value": "临安区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080210", + "value": "富阳区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080211", + "value": "建德市", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080212", + "value": "桐庐县", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080213", + "value": "淳安县", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080214", + "value": "临平区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + }, + { + "id": "080215", + "value": "钱塘区", + "sub": [], + "trace": [ + "080000", + "080200" + ] + } + ], + "trace": [ + "080000" + ] + }, + { + "id": "080300", + "value": "宁波", + "sub": [ + { + "id": "080300", + "value": "宁波", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080301", + "value": "海曙区", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080303", + "value": "江北区", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080304", + "value": "北仑区", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080305", + "value": "镇海区", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080306", + "value": "鄞州区", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080307", + "value": "慈溪市", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080308", + "value": "余姚市", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080309", + "value": "奉化区", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080310", + "value": "宁海县", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080311", + "value": "象山县", + "sub": [], + "trace": [ + "080000", + "080300" + ] + }, + { + "id": "080312", + "value": "高新区", + "sub": [], + "trace": [ + "080000", + "080300" + ] + } + ], + "trace": [ + "080000" + ] + }, + { + "id": "080400", + "value": "温州", + "sub": [ + { + "id": "080400", + "value": "温州", + "sub": [], + "trace": [ + "080000", + "080400" + ] + }, + { + "id": "080401", + "value": "鹿城区", + "sub": [], + "trace": [ + "080000", + "080400" + ] + }, + { + "id": "080402", + "value": "龙湾区", + "sub": [], + "trace": [ + "080000", + "080400" + ] + }, + { + "id": "080403", + "value": "瓯海区", + "sub": [], + "trace": [ + "080000", + "080400" + ] + }, + { + "id": "080404", + "value": "洞头区", + "sub": [], + "trace": [ + "080000", + "080400" + ] + }, + { + "id": "080405", + "value": "瑞安市", + "sub": [], + "trace": [ + "080000", + "080400" + ] + }, + { + "id": "080406", + "value": "乐清市", + "sub": [], + "trace": [ + "080000", + "080400" + ] + }, + { + "id": "080407", + "value": "龙港市", + "sub": [], + "trace": [ + "080000", + "080400" + ] + }, + { + "id": "080408", + "value": "永嘉县", + "sub": [], + "trace": [ + "080000", + "080400" + ] + }, + { + "id": "080409", + "value": "平阳县", + "sub": [], + "trace": [ + "080000", + "080400" + ] + }, + { + "id": "080410", + "value": "苍南县", + "sub": [], + "trace": [ + "080000", + "080400" + ] + }, + { + "id": "080411", + "value": "文成县", + "sub": [], + "trace": [ + "080000", + "080400" + ] + }, + { + "id": "080412", + "value": "泰顺县", + "sub": [], + "trace": [ + "080000", + "080400" + ] + } + ], + "trace": [ + "080000" + ] + }, + { + "id": "080500", + "value": "绍兴", + "sub": [ + { + "id": "080500", + "value": "绍兴", + "sub": [], + "trace": [ + "080000", + "080500" + ] + }, + { + "id": "080501", + "value": "越城区", + "sub": [], + "trace": [ + "080000", + "080500" + ] + }, + { + "id": "080502", + "value": "柯桥区", + "sub": [], + "trace": [ + "080000", + "080500" + ] + }, + { + "id": "080503", + "value": "上虞区", + "sub": [], + "trace": [ + "080000", + "080500" + ] + }, + { + "id": "080504", + "value": "新昌县", + "sub": [], + "trace": [ + "080000", + "080500" + ] + }, + { + "id": "080505", + "value": "嵊州市", + "sub": [], + "trace": [ + "080000", + "080500" + ] + }, + { + "id": "080506", + "value": "诸暨市", + "sub": [], + "trace": [ + "080000", + "080500" + ] + } + ], + "trace": [ + "080000" + ] + }, + { + "id": "080600", + "value": "金华", + "sub": [ + { + "id": "080600", + "value": "金华", + "sub": [], + "trace": [ + "080000", + "080600" + ] + }, + { + "id": "080601", + "value": "婺城区", + "sub": [], + "trace": [ + "080000", + "080600" + ] + }, + { + "id": "080602", + "value": "金东区", + "sub": [], + "trace": [ + "080000", + "080600" + ] + }, + { + "id": "080603", + "value": "兰溪市", + "sub": [], + "trace": [ + "080000", + "080600" + ] + }, + { + "id": "080605", + "value": "东阳市", + "sub": [], + "trace": [ + "080000", + "080600" + ] + }, + { + "id": "080606", + "value": "永康市", + "sub": [], + "trace": [ + "080000", + "080600" + ] + }, + { + "id": "080607", + "value": "浦江县", + "sub": [], + "trace": [ + "080000", + "080600" + ] + }, + { + "id": "080608", + "value": "武义县", + "sub": [], + "trace": [ + "080000", + "080600" + ] + }, + { + "id": "080609", + "value": "磐安县", + "sub": [], + "trace": [ + "080000", + "080600" + ] + } + ], + "trace": [ + "080000" + ] + }, + { + "id": "080700", + "value": "嘉兴", + "sub": [ + { + "id": "080700", + "value": "嘉兴", + "sub": [], + "trace": [ + "080000", + "080700" + ] + }, + { + "id": "080701", + "value": "南湖区", + "sub": [], + "trace": [ + "080000", + "080700" + ] + }, + { + "id": "080702", + "value": "秀洲区", + "sub": [], + "trace": [ + "080000", + "080700" + ] + }, + { + "id": "080703", + "value": "嘉善县", + "sub": [], + "trace": [ + "080000", + "080700" + ] + }, + { + "id": "080704", + "value": "海盐县", + "sub": [], + "trace": [ + "080000", + "080700" + ] + }, + { + "id": "080706", + "value": "平湖市", + "sub": [], + "trace": [ + "080000", + "080700" + ] + }, + { + "id": "080707", + "value": "桐乡市", + "sub": [], + "trace": [ + "080000", + "080700" + ] + } + ], + "trace": [ + "080000" + ] + }, + { + "id": "080800", + "value": "台州", + "sub": [ + { + "id": "080800", + "value": "台州", + "sub": [], + "trace": [ + "080000", + "080800" + ] + }, + { + "id": "080801", + "value": "椒江区", + "sub": [], + "trace": [ + "080000", + "080800" + ] + }, + { + "id": "080802", + "value": "黄岩区", + "sub": [], + "trace": [ + "080000", + "080800" + ] + }, + { + "id": "080803", + "value": "路桥区", + "sub": [], + "trace": [ + "080000", + "080800" + ] + }, + { + "id": "080804", + "value": "三门县", + "sub": [], + "trace": [ + "080000", + "080800" + ] + }, + { + "id": "080805", + "value": "天台县", + "sub": [], + "trace": [ + "080000", + "080800" + ] + }, + { + "id": "080806", + "value": "仙居县", + "sub": [], + "trace": [ + "080000", + "080800" + ] + }, + { + "id": "080807", + "value": "温岭市", + "sub": [], + "trace": [ + "080000", + "080800" + ] + }, + { + "id": "080808", + "value": "临海市", + "sub": [], + "trace": [ + "080000", + "080800" + ] + }, + { + "id": "080809", + "value": "玉环市", + "sub": [], + "trace": [ + "080000", + "080800" + ] + } + ], + "trace": [ + "080000" + ] + }, + { + "id": "080900", + "value": "湖州", + "sub": [ + { + "id": "080900", + "value": "湖州", + "sub": [], + "trace": [ + "080000", + "080900" + ] + }, + { + "id": "080901", + "value": "吴兴区", + "sub": [], + "trace": [ + "080000", + "080900" + ] + }, + { + "id": "080902", + "value": "南浔区", + "sub": [], + "trace": [ + "080000", + "080900" + ] + }, + { + "id": "080903", + "value": "德清县", + "sub": [], + "trace": [ + "080000", + "080900" + ] + }, + { + "id": "080904", + "value": "长兴县", + "sub": [], + "trace": [ + "080000", + "080900" + ] + }, + { + "id": "080905", + "value": "安吉县", + "sub": [], + "trace": [ + "080000", + "080900" + ] + } + ], + "trace": [ + "080000" + ] + }, + { + "id": "081000", + "value": "丽水", + "sub": [], + "trace": [ + "080000" + ] + }, + { + "id": "081100", + "value": "舟山", + "sub": [], + "trace": [ + "080000" + ] + }, + { + "id": "081200", + "value": "衢州", + "sub": [], + "trace": [ + "080000" + ] + }, + { + "id": "081400", + "value": "义乌", + "sub": [], + "trace": [ + "080000" + ] + }, + { + "id": "081600", + "value": "海宁", + "sub": [], + "trace": [ + "080000" + ] + } + ], + "trace": [ + "080000" + ] + }, + { + "id": "090000", + "value": "四川省", + "sub": [ + { + "id": "090200", + "value": "成都", + "sub": [ + { + "id": "090200", + "value": "成都", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090201", + "value": "青羊区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090202", + "value": "锦江区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090203", + "value": "金牛区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090204", + "value": "武侯区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090205", + "value": "成华区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090206", + "value": "龙泉驿区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090207", + "value": "青白江区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090208", + "value": "新都区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090209", + "value": "温江区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090210", + "value": "都江堰市", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090211", + "value": "彭州市", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090212", + "value": "邛崃市", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090213", + "value": "崇州市", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090214", + "value": "金堂县", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090215", + "value": "双流区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090216", + "value": "郫都区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090217", + "value": "大邑县", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090218", + "value": "蒲江县", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090219", + "value": "新津县", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090220", + "value": "高新区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090221", + "value": "简阳市", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090222", + "value": "天府新区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + }, + { + "id": "090223", + "value": "经济技术开发区", + "sub": [], + "trace": [ + "090000", + "090200" + ] + } + ], + "trace": [ + "090000" + ] + }, + { + "id": "090300", + "value": "绵阳", + "sub": [ + { + "id": "090300", + "value": "绵阳", + "sub": [], + "trace": [ + "090000", + "090300" + ] + }, + { + "id": "090301", + "value": "涪城区", + "sub": [], + "trace": [ + "090000", + "090300" + ] + }, + { + "id": "090302", + "value": "游仙区", + "sub": [], + "trace": [ + "090000", + "090300" + ] + }, + { + "id": "090303", + "value": "安州区", + "sub": [], + "trace": [ + "090000", + "090300" + ] + }, + { + "id": "090304", + "value": "江油市", + "sub": [], + "trace": [ + "090000", + "090300" + ] + }, + { + "id": "090305", + "value": "三台县", + "sub": [], + "trace": [ + "090000", + "090300" + ] + }, + { + "id": "090306", + "value": "梓潼县", + "sub": [], + "trace": [ + "090000", + "090300" + ] + }, + { + "id": "090307", + "value": "盐亭县", + "sub": [], + "trace": [ + "090000", + "090300" + ] + }, + { + "id": "090308", + "value": "平武县", + "sub": [], + "trace": [ + "090000", + "090300" + ] + }, + { + "id": "090309", + "value": "北川羌族自治县", + "sub": [], + "trace": [ + "090000", + "090300" + ] + } + ], + "trace": [ + "090000" + ] + }, + { + "id": "090400", + "value": "乐山", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "090500", + "value": "泸州", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "090600", + "value": "德阳", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "090700", + "value": "宜宾", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "090800", + "value": "自贡", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "090900", + "value": "内江", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "091000", + "value": "攀枝花", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "091100", + "value": "南充", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "091200", + "value": "眉山", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "091300", + "value": "广安", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "091400", + "value": "资阳", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "091500", + "value": "遂宁", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "091600", + "value": "广元", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "091700", + "value": "达州", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "091800", + "value": "雅安", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "091900", + "value": "西昌", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "092000", + "value": "巴中", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "092100", + "value": "甘孜", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "092200", + "value": "阿坝", + "sub": [], + "trace": [ + "090000" + ] + }, + { + "id": "092300", + "value": "凉山", + "sub": [], + "trace": [ + "090000" + ] + } + ], + "trace": [ + "090000" + ] + }, + { + "id": "100000", + "value": "海南省", + "sub": [ + { + "id": "100200", + "value": "海口", + "sub": [ + { + "id": "100200", + "value": "海口", + "sub": [], + "trace": [ + "100000", + "100200" + ] + }, + { + "id": "100201", + "value": "龙华区", + "sub": [], + "trace": [ + "100000", + "100200" + ] + }, + { + "id": "100202", + "value": "秀英区", + "sub": [], + "trace": [ + "100000", + "100200" + ] + }, + { + "id": "100203", + "value": "琼山区", + "sub": [], + "trace": [ + "100000", + "100200" + ] + }, + { + "id": "100204", + "value": "美兰区", + "sub": [], + "trace": [ + "100000", + "100200" + ] + } + ], + "trace": [ + "100000" + ] + }, + { + "id": "100300", + "value": "三亚", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "100400", + "value": "洋浦经济开发区", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "100500", + "value": "文昌", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "100600", + "value": "琼海", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "100700", + "value": "万宁", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "100800", + "value": "儋州", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "100900", + "value": "东方", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "101000", + "value": "五指山", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "101100", + "value": "定安", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "101200", + "value": "屯昌", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "101300", + "value": "澄迈", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "101400", + "value": "临高", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "101500", + "value": "三沙", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "101600", + "value": "琼中", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "101700", + "value": "保亭", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "101800", + "value": "白沙", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "101900", + "value": "昌江", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "102000", + "value": "乐东", + "sub": [], + "trace": [ + "100000" + ] + }, + { + "id": "102100", + "value": "陵水", + "sub": [], + "trace": [ + "100000" + ] + } + ], + "trace": [ + "100000" + ] + }, + { + "id": "110000", + "value": "福建省", + "sub": [ + { + "id": "110200", + "value": "福州", + "sub": [ + { + "id": "110200", + "value": "福州", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110201", + "value": "鼓楼区", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110202", + "value": "台江区", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110203", + "value": "仓山区", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110204", + "value": "马尾区", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110205", + "value": "晋安区", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110206", + "value": "闽侯县", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110207", + "value": "连江县", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110208", + "value": "罗源县", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110209", + "value": "闽清县", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110210", + "value": "永泰县", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110211", + "value": "平潭县", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110212", + "value": "福清市", + "sub": [], + "trace": [ + "110000", + "110200" + ] + }, + { + "id": "110213", + "value": "长乐区", + "sub": [], + "trace": [ + "110000", + "110200" + ] + } + ], + "trace": [ + "110000" + ] + }, + { + "id": "110300", + "value": "厦门", + "sub": [ + { + "id": "110300", + "value": "厦门", + "sub": [], + "trace": [ + "110000", + "110300" + ] + }, + { + "id": "110301", + "value": "思明区", + "sub": [], + "trace": [ + "110000", + "110300" + ] + }, + { + "id": "110302", + "value": "海沧区", + "sub": [], + "trace": [ + "110000", + "110300" + ] + }, + { + "id": "110303", + "value": "湖里区", + "sub": [], + "trace": [ + "110000", + "110300" + ] + }, + { + "id": "110304", + "value": "集美区", + "sub": [], + "trace": [ + "110000", + "110300" + ] + }, + { + "id": "110305", + "value": "同安区", + "sub": [], + "trace": [ + "110000", + "110300" + ] + }, + { + "id": "110306", + "value": "翔安区", + "sub": [], + "trace": [ + "110000", + "110300" + ] + } + ], + "trace": [ + "110000" + ] + }, + { + "id": "110400", + "value": "泉州", + "sub": [ + { + "id": "110400", + "value": "泉州", + "sub": [], + "trace": [ + "110000", + "110400" + ] + }, + { + "id": "110401", + "value": "鲤城区", + "sub": [], + "trace": [ + "110000", + "110400" + ] + }, + { + "id": "110402", + "value": "丰泽区", + "sub": [], + "trace": [ + "110000", + "110400" + ] + }, + { + "id": "110403", + "value": "洛江区", + "sub": [], + "trace": [ + "110000", + "110400" + ] + }, + { + "id": "110404", + "value": "泉港区", + "sub": [], + "trace": [ + "110000", + "110400" + ] + }, + { + "id": "110405", + "value": "惠安县", + "sub": [], + "trace": [ + "110000", + "110400" + ] + }, + { + "id": "110406", + "value": "安溪县", + "sub": [], + "trace": [ + "110000", + "110400" + ] + }, + { + "id": "110407", + "value": "永春县", + "sub": [], + "trace": [ + "110000", + "110400" + ] + }, + { + "id": "110408", + "value": "德化县", + "sub": [], + "trace": [ + "110000", + "110400" + ] + }, + { + "id": "110409", + "value": "金门县", + "sub": [], + "trace": [ + "110000", + "110400" + ] + }, + { + "id": "110410", + "value": "石狮市", + "sub": [], + "trace": [ + "110000", + "110400" + ] + }, + { + "id": "110411", + "value": "晋江市", + "sub": [], + "trace": [ + "110000", + "110400" + ] + }, + { + "id": "110412", + "value": "南安市", + "sub": [], + "trace": [ + "110000", + "110400" + ] + } + ], + "trace": [ + "110000" + ] + }, + { + "id": "110500", + "value": "漳州", + "sub": [], + "trace": [ + "110000" + ] + }, + { + "id": "110600", + "value": "莆田", + "sub": [], + "trace": [ + "110000" + ] + }, + { + "id": "110700", + "value": "三明", + "sub": [], + "trace": [ + "110000" + ] + }, + { + "id": "110800", + "value": "南平", + "sub": [], + "trace": [ + "110000" + ] + }, + { + "id": "110900", + "value": "宁德", + "sub": [], + "trace": [ + "110000" + ] + }, + { + "id": "111000", + "value": "龙岩", + "sub": [], + "trace": [ + "110000" + ] + } + ], + "trace": [ + "110000" + ] + }, + { + "id": "120000", + "value": "山东省", + "sub": [ + { + "id": "120200", + "value": "济南", + "sub": [ + { + "id": "120200", + "value": "济南", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120201", + "value": "历下区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120202", + "value": "市中区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120203", + "value": "槐荫区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120204", + "value": "天桥区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120205", + "value": "历城区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120206", + "value": "长清区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120207", + "value": "平阴县", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120208", + "value": "济阳区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120209", + "value": "商河县", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120210", + "value": "章丘区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120211", + "value": "高新区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120212", + "value": "莱芜区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + }, + { + "id": "120213", + "value": "钢城区", + "sub": [], + "trace": [ + "120000", + "120200" + ] + } + ], + "trace": [ + "120000" + ] + }, + { + "id": "120300", + "value": "青岛", + "sub": [ + { + "id": "120300", + "value": "青岛", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120301", + "value": "市南区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120302", + "value": "市北区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120303", + "value": "黄岛区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120304", + "value": "崂山区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120305", + "value": "城阳区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120306", + "value": "李沧区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120307", + "value": "胶州市", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120308", + "value": "即墨区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120309", + "value": "平度市", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120310", + "value": "莱西市", + "sub": [], + "trace": [ + "120000", + "120300" + ] + }, + { + "id": "120311", + "value": "高新区", + "sub": [], + "trace": [ + "120000", + "120300" + ] + } + ], + "trace": [ + "120000" + ] + }, + { + "id": "120400", + "value": "烟台", + "sub": [ + { + "id": "120400", + "value": "烟台", + "sub": [], + "trace": [ + "120000", + "120400" + ] + }, + { + "id": "120401", + "value": "芝罘区", + "sub": [], + "trace": [ + "120000", + "120400" + ] + }, + { + "id": "120402", + "value": "福山区", + "sub": [], + "trace": [ + "120000", + "120400" + ] + }, + { + "id": "120403", + "value": "牟平区", + "sub": [], + "trace": [ + "120000", + "120400" + ] + }, + { + "id": "120404", + "value": "莱山区", + "sub": [], + "trace": [ + "120000", + "120400" + ] + }, + { + "id": "120405", + "value": "长岛县", + "sub": [], + "trace": [ + "120000", + "120400" + ] + }, + { + "id": "120406", + "value": "龙口市", + "sub": [], + "trace": [ + "120000", + "120400" + ] + }, + { + "id": "120407", + "value": "莱阳市", + "sub": [], + "trace": [ + "120000", + "120400" + ] + }, + { + "id": "120408", + "value": "莱州市", + "sub": [], + "trace": [ + "120000", + "120400" + ] + }, + { + "id": "120409", + "value": "蓬莱区", + "sub": [], + "trace": [ + "120000", + "120400" + ] + }, + { + "id": "120410", + "value": "招远市", + "sub": [], + "trace": [ + "120000", + "120400" + ] + }, + { + "id": "120411", + "value": "栖霞市", + "sub": [], + "trace": [ + "120000", + "120400" + ] + }, + { + "id": "120412", + "value": "海阳市", + "sub": [], + "trace": [ + "120000", + "120400" + ] + }, + { + "id": "120413", + "value": "高新区", + "sub": [], + "trace": [ + "120000", + "120400" + ] + }, + { + "id": "120414", + "value": "开发区", + "sub": [], + "trace": [ + "120000", + "120400" + ] + } + ], + "trace": [ + "120000" + ] + }, + { + "id": "120500", + "value": "潍坊", + "sub": [ + { + "id": "120500", + "value": "潍坊", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120501", + "value": "潍城区", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120502", + "value": "寒亭区", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120503", + "value": "坊子区", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120504", + "value": "奎文区", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120505", + "value": "临朐县", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120506", + "value": "昌乐县", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120507", + "value": "青州市", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120508", + "value": "诸城市", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120509", + "value": "寿光市", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120510", + "value": "安丘市", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120511", + "value": "高密市", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120512", + "value": "昌邑市", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120513", + "value": "高新技术开发区", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120514", + "value": "滨海经济开发区", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120515", + "value": "峡山生态经济发展区", + "sub": [], + "trace": [ + "120000", + "120500" + ] + }, + { + "id": "120516", + "value": "潍坊综合保税区", + "sub": [], + "trace": [ + "120000", + "120500" + ] + } + ], + "trace": [ + "120000" + ] + }, + { + "id": "120600", + "value": "威海", + "sub": [], + "trace": [ + "120000" + ] + }, + { + "id": "120700", + "value": "淄博", + "sub": [], + "trace": [ + "120000" + ] + }, + { + "id": "120800", + "value": "临沂", + "sub": [ + { + "id": "120800", + "value": "临沂", + "sub": [], + "trace": [ + "120000", + "120800" + ] + }, + { + "id": "120801", + "value": "兰山区", + "sub": [], + "trace": [ + "120000", + "120800" + ] + }, + { + "id": "120802", + "value": "罗庄区", + "sub": [], + "trace": [ + "120000", + "120800" + ] + }, + { + "id": "120803", + "value": "河东区", + "sub": [], + "trace": [ + "120000", + "120800" + ] + }, + { + "id": "120804", + "value": "沂南县", + "sub": [], + "trace": [ + "120000", + "120800" + ] + }, + { + "id": "120805", + "value": "郯城县", + "sub": [], + "trace": [ + "120000", + "120800" + ] + }, + { + "id": "120806", + "value": "沂水县", + "sub": [], + "trace": [ + "120000", + "120800" + ] + }, + { + "id": "120807", + "value": "兰陵县", + "sub": [], + "trace": [ + "120000", + "120800" + ] + }, + { + "id": "120808", + "value": "费县", + "sub": [], + "trace": [ + "120000", + "120800" + ] + }, + { + "id": "120809", + "value": "平邑县", + "sub": [], + "trace": [ + "120000", + "120800" + ] + }, + { + "id": "120810", + "value": "莒南县", + "sub": [], + "trace": [ + "120000", + "120800" + ] + }, + { + "id": "120811", + "value": "蒙阴县", + "sub": [], + "trace": [ + "120000", + "120800" + ] + }, + { + "id": "120812", + "value": "临沭县", + "sub": [], + "trace": [ + "120000", + "120800" + ] + } + ], + "trace": [ + "120000" + ] + }, + { + "id": "120900", + "value": "济宁", + "sub": [], + "trace": [ + "120000" + ] + }, + { + "id": "121000", + "value": "东营", + "sub": [], + "trace": [ + "120000" + ] + }, + { + "id": "121100", + "value": "泰安", + "sub": [], + "trace": [ + "120000" + ] + }, + { + "id": "121200", + "value": "日照", + "sub": [], + "trace": [ + "120000" + ] + }, + { + "id": "121300", + "value": "德州", + "sub": [], + "trace": [ + "120000" + ] + }, + { + "id": "121400", + "value": "菏泽", + "sub": [], + "trace": [ + "120000" + ] + }, + { + "id": "121500", + "value": "滨州", + "sub": [], + "trace": [ + "120000" + ] + }, + { + "id": "121600", + "value": "枣庄", + "sub": [], + "trace": [ + "120000" + ] + }, + { + "id": "121700", + "value": "聊城", + "sub": [], + "trace": [ + "120000" + ] + } + ], + "trace": [ + "120000" + ] + }, + { + "id": "130000", + "value": "江西省", + "sub": [ + { + "id": "130200", + "value": "南昌", + "sub": [ + { + "id": "130200", + "value": "南昌", + "sub": [], + "trace": [ + "130000", + "130200" + ] + }, + { + "id": "130201", + "value": "东湖区", + "sub": [], + "trace": [ + "130000", + "130200" + ] + }, + { + "id": "130202", + "value": "西湖区", + "sub": [], + "trace": [ + "130000", + "130200" + ] + }, + { + "id": "130203", + "value": "青云谱区", + "sub": [], + "trace": [ + "130000", + "130200" + ] + }, + { + "id": "130205", + "value": "青山湖区", + "sub": [], + "trace": [ + "130000", + "130200" + ] + }, + { + "id": "130206", + "value": "南昌县", + "sub": [], + "trace": [ + "130000", + "130200" + ] + }, + { + "id": "130207", + "value": "新建区", + "sub": [], + "trace": [ + "130000", + "130200" + ] + }, + { + "id": "130208", + "value": "安义县", + "sub": [], + "trace": [ + "130000", + "130200" + ] + }, + { + "id": "130209", + "value": "进贤县", + "sub": [], + "trace": [ + "130000", + "130200" + ] + }, + { + "id": "130210", + "value": "红谷滩区", + "sub": [], + "trace": [ + "130000", + "130200" + ] + } + ], + "trace": [ + "130000" + ] + }, + { + "id": "130300", + "value": "九江", + "sub": [], + "trace": [ + "130000" + ] + }, + { + "id": "130400", + "value": "景德镇", + "sub": [], + "trace": [ + "130000" + ] + }, + { + "id": "130500", + "value": "萍乡", + "sub": [], + "trace": [ + "130000" + ] + }, + { + "id": "130600", + "value": "新余", + "sub": [], + "trace": [ + "130000" + ] + }, + { + "id": "130700", + "value": "鹰潭", + "sub": [], + "trace": [ + "130000" + ] + }, + { + "id": "130800", + "value": "赣州", + "sub": [ + { + "id": "130800", + "value": "赣州", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130801", + "value": "章贡区", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130802", + "value": "赣县区", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130803", + "value": "南康区", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130804", + "value": "信丰县", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130805", + "value": "大余县", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130806", + "value": "上犹县", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130807", + "value": "崇义县", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130808", + "value": "安远县", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130809", + "value": "龙南市", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130810", + "value": "定南县", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130811", + "value": "全南县", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130812", + "value": "宁都县", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130813", + "value": "于都县", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130814", + "value": "兴国县", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130815", + "value": "会昌县", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130816", + "value": "寻乌县", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130817", + "value": "石城县", + "sub": [], + "trace": [ + "130000", + "130800" + ] + }, + { + "id": "130818", + "value": "瑞金市", + "sub": [], + "trace": [ + "130000", + "130800" + ] + } + ], + "trace": [ + "130000" + ] + }, + { + "id": "130900", + "value": "吉安", + "sub": [], + "trace": [ + "130000" + ] + }, + { + "id": "131000", + "value": "宜春", + "sub": [], + "trace": [ + "130000" + ] + }, + { + "id": "131100", + "value": "抚州", + "sub": [], + "trace": [ + "130000" + ] + }, + { + "id": "131200", + "value": "上饶", + "sub": [], + "trace": [ + "130000" + ] + } + ], + "trace": [ + "130000" + ] + }, + { + "id": "140000", + "value": "广西", + "sub": [ + { + "id": "140200", + "value": "南宁", + "sub": [ + { + "id": "140200", + "value": "南宁", + "sub": [], + "trace": [ + "140000", + "140200" + ] + }, + { + "id": "140201", + "value": "兴宁区", + "sub": [], + "trace": [ + "140000", + "140200" + ] + }, + { + "id": "140202", + "value": "青秀区", + "sub": [], + "trace": [ + "140000", + "140200" + ] + }, + { + "id": "140203", + "value": "西乡塘区", + "sub": [], + "trace": [ + "140000", + "140200" + ] + }, + { + "id": "140204", + "value": "江南区", + "sub": [], + "trace": [ + "140000", + "140200" + ] + }, + { + "id": "140205", + "value": "良庆区", + "sub": [], + "trace": [ + "140000", + "140200" + ] + }, + { + "id": "140206", + "value": "邕宁区", + "sub": [], + "trace": [ + "140000", + "140200" + ] + }, + { + "id": "140207", + "value": "武鸣区", + "sub": [], + "trace": [ + "140000", + "140200" + ] + }, + { + "id": "140208", + "value": "隆安县", + "sub": [], + "trace": [ + "140000", + "140200" + ] + }, + { + "id": "140209", + "value": "马山县", + "sub": [], + "trace": [ + "140000", + "140200" + ] + }, + { + "id": "140210", + "value": "上林县", + "sub": [], + "trace": [ + "140000", + "140200" + ] + }, + { + "id": "140211", + "value": "宾阳县", + "sub": [], + "trace": [ + "140000", + "140200" + ] + }, + { + "id": "140212", + "value": "横州市", + "sub": [], + "trace": [ + "140000", + "140200" + ] + } + ], + "trace": [ + "140000" + ] + }, + { + "id": "140300", + "value": "桂林", + "sub": [], + "trace": [ + "140000" + ] + }, + { + "id": "140400", + "value": "柳州", + "sub": [ + { + "id": "140400", + "value": "柳州", + "sub": [], + "trace": [ + "140000", + "140400" + ] + }, + { + "id": "140401", + "value": "城中区", + "sub": [], + "trace": [ + "140000", + "140400" + ] + }, + { + "id": "140402", + "value": "鱼峰区", + "sub": [], + "trace": [ + "140000", + "140400" + ] + }, + { + "id": "140403", + "value": "柳南区", + "sub": [], + "trace": [ + "140000", + "140400" + ] + }, + { + "id": "140404", + "value": "柳北区", + "sub": [], + "trace": [ + "140000", + "140400" + ] + }, + { + "id": "140405", + "value": "柳江区", + "sub": [], + "trace": [ + "140000", + "140400" + ] + }, + { + "id": "140406", + "value": "柳城县", + "sub": [], + "trace": [ + "140000", + "140400" + ] + }, + { + "id": "140407", + "value": "鹿寨县", + "sub": [], + "trace": [ + "140000", + "140400" + ] + }, + { + "id": "140408", + "value": "融安县", + "sub": [], + "trace": [ + "140000", + "140400" + ] + }, + { + "id": "140409", + "value": "融水苗族自治县", + "sub": [], + "trace": [ + "140000", + "140400" + ] + }, + { + "id": "140410", + "value": "三江侗族自治县", + "sub": [], + "trace": [ + "140000", + "140400" + ] + } + ], + "trace": [ + "140000" + ] + }, + { + "id": "140500", + "value": "北海", + "sub": [], + "trace": [ + "140000" + ] + }, + { + "id": "140600", + "value": "玉林", + "sub": [], + "trace": [ + "140000" + ] + }, + { + "id": "140700", + "value": "梧州", + "sub": [], + "trace": [ + "140000" + ] + }, + { + "id": "140800", + "value": "防城港", + "sub": [], + "trace": [ + "140000" + ] + }, + { + "id": "140900", + "value": "钦州", + "sub": [], + "trace": [ + "140000" + ] + }, + { + "id": "141000", + "value": "贵港", + "sub": [], + "trace": [ + "140000" + ] + }, + { + "id": "141100", + "value": "百色", + "sub": [], + "trace": [ + "140000" + ] + }, + { + "id": "141200", + "value": "河池", + "sub": [], + "trace": [ + "140000" + ] + }, + { + "id": "141300", + "value": "来宾", + "sub": [], + "trace": [ + "140000" + ] + }, + { + "id": "141400", + "value": "崇左", + "sub": [], + "trace": [ + "140000" + ] + }, + { + "id": "141500", + "value": "贺州", + "sub": [], + "trace": [ + "140000" + ] + } + ], + "trace": [ + "140000" + ] + }, + { + "id": "150000", + "value": "安徽省", + "sub": [ + { + "id": "150200", + "value": "合肥", + "sub": [ + { + "id": "150200", + "value": "合肥", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150201", + "value": "瑶海区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150202", + "value": "庐阳区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150203", + "value": "蜀山区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150204", + "value": "包河区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150205", + "value": "经开区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150206", + "value": "滨湖新区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150207", + "value": "新站区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150208", + "value": "高新区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150209", + "value": "政务区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150210", + "value": "北城新区", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150211", + "value": "巢湖市", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150212", + "value": "长丰县", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150213", + "value": "肥东县", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150214", + "value": "肥西县", + "sub": [], + "trace": [ + "150000", + "150200" + ] + }, + { + "id": "150215", + "value": "庐江县", + "sub": [], + "trace": [ + "150000", + "150200" + ] + } + ], + "trace": [ + "150000" + ] + }, + { + "id": "150300", + "value": "芜湖", + "sub": [ + { + "id": "150300", + "value": "芜湖", + "sub": [], + "trace": [ + "150000", + "150300" + ] + }, + { + "id": "150301", + "value": "镜湖区", + "sub": [], + "trace": [ + "150000", + "150300" + ] + }, + { + "id": "150302", + "value": "弋江区", + "sub": [], + "trace": [ + "150000", + "150300" + ] + }, + { + "id": "150303", + "value": "三山区", + "sub": [], + "trace": [ + "150000", + "150300" + ] + }, + { + "id": "150304", + "value": "鸠江区", + "sub": [], + "trace": [ + "150000", + "150300" + ] + }, + { + "id": "150305", + "value": "湾沚区", + "sub": [], + "trace": [ + "150000", + "150300" + ] + }, + { + "id": "150306", + "value": "繁昌区", + "sub": [], + "trace": [ + "150000", + "150300" + ] + }, + { + "id": "150307", + "value": "南陵县", + "sub": [], + "trace": [ + "150000", + "150300" + ] + }, + { + "id": "150308", + "value": "无为市", + "sub": [], + "trace": [ + "150000", + "150300" + ] + } + ], + "trace": [ + "150000" + ] + }, + { + "id": "150400", + "value": "安庆", + "sub": [], + "trace": [ + "150000" + ] + }, + { + "id": "150500", + "value": "马鞍山", + "sub": [], + "trace": [ + "150000" + ] + }, + { + "id": "150600", + "value": "蚌埠", + "sub": [], + "trace": [ + "150000" + ] + }, + { + "id": "150700", + "value": "阜阳", + "sub": [], + "trace": [ + "150000" + ] + }, + { + "id": "150800", + "value": "铜陵", + "sub": [], + "trace": [ + "150000" + ] + }, + { + "id": "150900", + "value": "滁州", + "sub": [], + "trace": [ + "150000" + ] + }, + { + "id": "151000", + "value": "黄山", + "sub": [], + "trace": [ + "150000" + ] + }, + { + "id": "151100", + "value": "淮南", + "sub": [], + "trace": [ + "150000" + ] + }, + { + "id": "151200", + "value": "六安", + "sub": [], + "trace": [ + "150000" + ] + }, + { + "id": "151400", + "value": "宣城", + "sub": [], + "trace": [ + "150000" + ] + }, + { + "id": "151500", + "value": "池州", + "sub": [], + "trace": [ + "150000" + ] + }, + { + "id": "151600", + "value": "宿州", + "sub": [], + "trace": [ + "150000" + ] + }, + { + "id": "151700", + "value": "淮北", + "sub": [], + "trace": [ + "150000" + ] + }, + { + "id": "151800", + "value": "亳州", + "sub": [], + "trace": [ + "150000" + ] + } + ], + "trace": [ + "150000" + ] + }, + { + "id": "160000", + "value": "河北省", + "sub": [ + { + "id": "160100", + "value": "雄安新区", + "sub": [], + "trace": [ + "160000" + ] + }, + { + "id": "160200", + "value": "石家庄", + "sub": [ + { + "id": "160200", + "value": "石家庄", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160201", + "value": "长安区", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160202", + "value": "裕华区", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160203", + "value": "桥西区", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160204", + "value": "新华区", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160205", + "value": "藁城区", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160206", + "value": "鹿泉区", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160207", + "value": "栾城区", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160208", + "value": "东开发区", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160209", + "value": "井陉矿区", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160210", + "value": "井陉县", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160211", + "value": "正定县", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160212", + "value": "行唐县", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160213", + "value": "灵寿县", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160214", + "value": "高邑县", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160215", + "value": "深泽县", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160216", + "value": "赞皇县", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160217", + "value": "无极县", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160218", + "value": "平山县", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160219", + "value": "元氏县", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160220", + "value": "赵县", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160221", + "value": "辛集市", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160222", + "value": "晋州市", + "sub": [], + "trace": [ + "160000", + "160200" + ] + }, + { + "id": "160223", + "value": "新乐市", + "sub": [], + "trace": [ + "160000", + "160200" + ] + } + ], + "trace": [ + "160000" + ] + }, + { + "id": "160300", + "value": "廊坊", + "sub": [], + "trace": [ + "160000" + ] + }, + { + "id": "160400", + "value": "保定", + "sub": [], + "trace": [ + "160000" + ] + }, + { + "id": "160500", + "value": "唐山", + "sub": [], + "trace": [ + "160000" + ] + }, + { + "id": "160600", + "value": "秦皇岛", + "sub": [], + "trace": [ + "160000" + ] + }, + { + "id": "160700", + "value": "邯郸", + "sub": [], + "trace": [ + "160000" + ] + }, + { + "id": "160800", + "value": "沧州", + "sub": [], + "trace": [ + "160000" + ] + }, + { + "id": "160900", + "value": "张家口", + "sub": [], + "trace": [ + "160000" + ] + }, + { + "id": "161000", + "value": "承德", + "sub": [], + "trace": [ + "160000" + ] + }, + { + "id": "161100", + "value": "邢台", + "sub": [], + "trace": [ + "160000" + ] + }, + { + "id": "161200", + "value": "衡水", + "sub": [], + "trace": [ + "160000" + ] + }, + { + "id": "161300", + "value": "燕郊开发区", + "sub": [], + "trace": [ + "160000" + ] + } + ], + "trace": [ + "160000" + ] + }, + { + "id": "170000", + "value": "河南省", + "sub": [ + { + "id": "170200", + "value": "郑州", + "sub": [ + { + "id": "170200", + "value": "郑州", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170201", + "value": "中原区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170202", + "value": "二七区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170203", + "value": "管城回族区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170204", + "value": "金水区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170205", + "value": "上街区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170206", + "value": "惠济区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170207", + "value": "中牟县", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170208", + "value": "巩义市", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170209", + "value": "荥阳市", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170210", + "value": "新密市", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170211", + "value": "新郑市", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170212", + "value": "登封市", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170213", + "value": "郑东新区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170214", + "value": "高新区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170215", + "value": "经开区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + }, + { + "id": "170216", + "value": "郑州航空港区", + "sub": [], + "trace": [ + "170000", + "170200" + ] + } + ], + "trace": [ + "170000" + ] + }, + { + "id": "170300", + "value": "洛阳", + "sub": [ + { + "id": "170300", + "value": "洛阳", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170301", + "value": "老城区", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170302", + "value": "西工区", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170303", + "value": "瀍河回族区", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170304", + "value": "涧西区", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170305", + "value": "吉利区", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170306", + "value": "洛龙区", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170307", + "value": "伊滨区", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170308", + "value": "高新区", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170309", + "value": "孟津区", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170310", + "value": "新安县", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170311", + "value": "栾川县", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170312", + "value": "嵩县", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170313", + "value": "汝阳县", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170314", + "value": "宜阳县", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170315", + "value": "洛宁县", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170316", + "value": "伊川县", + "sub": [], + "trace": [ + "170000", + "170300" + ] + }, + { + "id": "170317", + "value": "偃师区", + "sub": [], + "trace": [ + "170000", + "170300" + ] + } + ], + "trace": [ + "170000" + ] + }, + { + "id": "170400", + "value": "开封", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "170500", + "value": "焦作", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "170600", + "value": "南阳", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "170700", + "value": "新乡", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "170800", + "value": "周口", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "170900", + "value": "安阳", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "171000", + "value": "平顶山", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "171100", + "value": "许昌", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "171200", + "value": "信阳", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "171300", + "value": "商丘", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "171400", + "value": "驻马店", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "171500", + "value": "漯河", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "171600", + "value": "濮阳", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "171700", + "value": "鹤壁", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "171800", + "value": "三门峡", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "171900", + "value": "济源", + "sub": [], + "trace": [ + "170000" + ] + }, + { + "id": "172000", + "value": "邓州", + "sub": [], + "trace": [ + "170000" + ] + } + ], + "trace": [ + "170000" + ] + }, + { + "id": "180000", + "value": "湖北省", + "sub": [ + { + "id": "180200", + "value": "武汉", + "sub": [ + { + "id": "180200", + "value": "武汉", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180201", + "value": "江岸区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180202", + "value": "江汉区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180203", + "value": "硚口区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180204", + "value": "汉阳区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180205", + "value": "武昌区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180206", + "value": "青山区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180207", + "value": "洪山区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180208", + "value": "东西湖区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180209", + "value": "汉南区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180210", + "value": "蔡甸区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180211", + "value": "江夏区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180212", + "value": "黄陂区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180213", + "value": "新洲区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180214", + "value": "武汉经济开发区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + }, + { + "id": "180215", + "value": "东湖新技术产业开发区", + "sub": [], + "trace": [ + "180000", + "180200" + ] + } + ], + "trace": [ + "180000" + ] + }, + { + "id": "180300", + "value": "宜昌", + "sub": [ + { + "id": "180300", + "value": "宜昌", + "sub": [], + "trace": [ + "180000", + "180300" + ] + }, + { + "id": "180301", + "value": "西陵区", + "sub": [], + "trace": [ + "180000", + "180300" + ] + }, + { + "id": "180302", + "value": "伍家岗区", + "sub": [], + "trace": [ + "180000", + "180300" + ] + }, + { + "id": "180303", + "value": "点军区", + "sub": [], + "trace": [ + "180000", + "180300" + ] + }, + { + "id": "180304", + "value": "猇亭区", + "sub": [], + "trace": [ + "180000", + "180300" + ] + }, + { + "id": "180305", + "value": "夷陵区", + "sub": [], + "trace": [ + "180000", + "180300" + ] + }, + { + "id": "180306", + "value": "远安县", + "sub": [], + "trace": [ + "180000", + "180300" + ] + }, + { + "id": "180307", + "value": "兴山县", + "sub": [], + "trace": [ + "180000", + "180300" + ] + }, + { + "id": "180308", + "value": "秭归县", + "sub": [], + "trace": [ + "180000", + "180300" + ] + }, + { + "id": "180309", + "value": "长阳土家族自治县", + "sub": [], + "trace": [ + "180000", + "180300" + ] + }, + { + "id": "180310", + "value": "五峰土家族自治县", + "sub": [], + "trace": [ + "180000", + "180300" + ] + }, + { + "id": "180311", + "value": "宜都市", + "sub": [], + "trace": [ + "180000", + "180300" + ] + }, + { + "id": "180312", + "value": "当阳市", + "sub": [], + "trace": [ + "180000", + "180300" + ] + }, + { + "id": "180313", + "value": "枝江市", + "sub": [], + "trace": [ + "180000", + "180300" + ] + } + ], + "trace": [ + "180000" + ] + }, + { + "id": "180400", + "value": "黄石", + "sub": [], + "trace": [ + "180000" + ] + }, + { + "id": "180500", + "value": "襄阳", + "sub": [ + { + "id": "180500", + "value": "襄阳", + "sub": [], + "trace": [ + "180000", + "180500" + ] + }, + { + "id": "180501", + "value": "襄城区", + "sub": [], + "trace": [ + "180000", + "180500" + ] + }, + { + "id": "180502", + "value": "樊城区", + "sub": [], + "trace": [ + "180000", + "180500" + ] + }, + { + "id": "180503", + "value": "襄州区", + "sub": [], + "trace": [ + "180000", + "180500" + ] + }, + { + "id": "180504", + "value": "南漳县", + "sub": [], + "trace": [ + "180000", + "180500" + ] + }, + { + "id": "180505", + "value": "谷城县", + "sub": [], + "trace": [ + "180000", + "180500" + ] + }, + { + "id": "180506", + "value": "保康县", + "sub": [], + "trace": [ + "180000", + "180500" + ] + }, + { + "id": "180507", + "value": "老河口市", + "sub": [], + "trace": [ + "180000", + "180500" + ] + }, + { + "id": "180508", + "value": "枣阳市", + "sub": [], + "trace": [ + "180000", + "180500" + ] + }, + { + "id": "180509", + "value": "宜城市", + "sub": [], + "trace": [ + "180000", + "180500" + ] + } + ], + "trace": [ + "180000" + ] + }, + { + "id": "180600", + "value": "十堰", + "sub": [], + "trace": [ + "180000" + ] + }, + { + "id": "180700", + "value": "荆州", + "sub": [ + { + "id": "180700", + "value": "荆州", + "sub": [], + "trace": [ + "180000", + "180700" + ] + }, + { + "id": "180701", + "value": "沙市区", + "sub": [], + "trace": [ + "180000", + "180700" + ] + }, + { + "id": "180702", + "value": "荆州区", + "sub": [], + "trace": [ + "180000", + "180700" + ] + }, + { + "id": "180703", + "value": "公安县", + "sub": [], + "trace": [ + "180000", + "180700" + ] + }, + { + "id": "180704", + "value": "监利市", + "sub": [], + "trace": [ + "180000", + "180700" + ] + }, + { + "id": "180705", + "value": "江陵县", + "sub": [], + "trace": [ + "180000", + "180700" + ] + }, + { + "id": "180706", + "value": "石首市", + "sub": [], + "trace": [ + "180000", + "180700" + ] + }, + { + "id": "180707", + "value": "洪湖市", + "sub": [], + "trace": [ + "180000", + "180700" + ] + }, + { + "id": "180708", + "value": "松滋市", + "sub": [], + "trace": [ + "180000", + "180700" + ] + } + ], + "trace": [ + "180000" + ] + }, + { + "id": "180800", + "value": "荆门", + "sub": [], + "trace": [ + "180000" + ] + }, + { + "id": "180900", + "value": "孝感", + "sub": [], + "trace": [ + "180000" + ] + }, + { + "id": "181000", + "value": "鄂州", + "sub": [], + "trace": [ + "180000" + ] + }, + { + "id": "181100", + "value": "黄冈", + "sub": [], + "trace": [ + "180000" + ] + }, + { + "id": "181200", + "value": "随州", + "sub": [], + "trace": [ + "180000" + ] + }, + { + "id": "181300", + "value": "咸宁", + "sub": [], + "trace": [ + "180000" + ] + }, + { + "id": "181400", + "value": "仙桃", + "sub": [], + "trace": [ + "180000" + ] + }, + { + "id": "181500", + "value": "潜江", + "sub": [], + "trace": [ + "180000" + ] + }, + { + "id": "181600", + "value": "天门", + "sub": [], + "trace": [ + "180000" + ] + }, + { + "id": "181700", + "value": "神农架", + "sub": [], + "trace": [ + "180000" + ] + }, + { + "id": "181800", + "value": "恩施", + "sub": [], + "trace": [ + "180000" + ] + } + ], + "trace": [ + "180000" + ] + }, + { + "id": "190000", + "value": "湖南省", + "sub": [ + { + "id": "190200", + "value": "长沙", + "sub": [ + { + "id": "190200", + "value": "长沙", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190201", + "value": "芙蓉区", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190202", + "value": "天心区", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190203", + "value": "岳麓区", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190204", + "value": "开福区", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190205", + "value": "雨花区", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190206", + "value": "望城区", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190207", + "value": "长沙县", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190208", + "value": "宁乡市", + "sub": [], + "trace": [ + "190000", + "190200" + ] + }, + { + "id": "190209", + "value": "浏阳市", + "sub": [], + "trace": [ + "190000", + "190200" + ] + } + ], + "trace": [ + "190000" + ] + }, + { + "id": "190300", + "value": "株洲", + "sub": [ + { + "id": "190300", + "value": "株洲", + "sub": [], + "trace": [ + "190000", + "190300" + ] + }, + { + "id": "190301", + "value": "荷塘区", + "sub": [], + "trace": [ + "190000", + "190300" + ] + }, + { + "id": "190302", + "value": "芦淞区", + "sub": [], + "trace": [ + "190000", + "190300" + ] + }, + { + "id": "190303", + "value": "石峰区", + "sub": [], + "trace": [ + "190000", + "190300" + ] + }, + { + "id": "190304", + "value": "天元区", + "sub": [], + "trace": [ + "190000", + "190300" + ] + }, + { + "id": "190305", + "value": "渌口区", + "sub": [], + "trace": [ + "190000", + "190300" + ] + }, + { + "id": "190306", + "value": "攸县", + "sub": [], + "trace": [ + "190000", + "190300" + ] + }, + { + "id": "190307", + "value": "茶陵县", + "sub": [], + "trace": [ + "190000", + "190300" + ] + }, + { + "id": "190308", + "value": "炎陵县", + "sub": [], + "trace": [ + "190000", + "190300" + ] + }, + { + "id": "190309", + "value": "醴陵市", + "sub": [], + "trace": [ + "190000", + "190300" + ] + }, + { + "id": "190310", + "value": "云龙示范区", + "sub": [], + "trace": [ + "190000", + "190300" + ] + } + ], + "trace": [ + "190000" + ] + }, + { + "id": "190400", + "value": "湘潭", + "sub": [], + "trace": [ + "190000" + ] + }, + { + "id": "190500", + "value": "衡阳", + "sub": [], + "trace": [ + "190000" + ] + }, + { + "id": "190600", + "value": "岳阳", + "sub": [], + "trace": [ + "190000" + ] + }, + { + "id": "190700", + "value": "常德", + "sub": [], + "trace": [ + "190000" + ] + }, + { + "id": "190800", + "value": "益阳", + "sub": [], + "trace": [ + "190000" + ] + }, + { + "id": "190900", + "value": "郴州", + "sub": [], + "trace": [ + "190000" + ] + }, + { + "id": "191000", + "value": "邵阳", + "sub": [], + "trace": [ + "190000" + ] + }, + { + "id": "191100", + "value": "怀化", + "sub": [], + "trace": [ + "190000" + ] + }, + { + "id": "191200", + "value": "娄底", + "sub": [], + "trace": [ + "190000" + ] + }, + { + "id": "191300", + "value": "永州", + "sub": [], + "trace": [ + "190000" + ] + }, + { + "id": "191400", + "value": "张家界", + "sub": [], + "trace": [ + "190000" + ] + }, + { + "id": "191500", + "value": "湘西", + "sub": [], + "trace": [ + "190000" + ] + } + ], + "trace": [ + "190000" + ] + }, + { + "id": "200000", + "value": "陕西省", + "sub": [ + { + "id": "200200", + "value": "西安", + "sub": [ + { + "id": "200200", + "value": "西安", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200201", + "value": "莲湖区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200202", + "value": "新城区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200203", + "value": "碑林区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200204", + "value": "灞桥区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200205", + "value": "未央区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200206", + "value": "雁塔区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200207", + "value": "阎良区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200208", + "value": "临潼区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200209", + "value": "长安区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200210", + "value": "蓝田县", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200211", + "value": "周至县", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200212", + "value": "鄠邑区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200213", + "value": "高陵区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200214", + "value": "高新技术产业开发区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200215", + "value": "经济技术开发区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200216", + "value": "曲江新区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200217", + "value": "浐灞生态区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200218", + "value": "国家民用航天产业基地", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200219", + "value": "西咸新区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200220", + "value": "西安阎良航空基地", + "sub": [], + "trace": [ + "200000", + "200200" + ] + }, + { + "id": "200221", + "value": "西安国际港务区", + "sub": [], + "trace": [ + "200000", + "200200" + ] + } + ], + "trace": [ + "200000" + ] + }, + { + "id": "200300", + "value": "咸阳", + "sub": [ + { + "id": "200300", + "value": "咸阳", + "sub": [], + "trace": [ + "200000", + "200300" + ] + }, + { + "id": "200301", + "value": "秦都区", + "sub": [], + "trace": [ + "200000", + "200300" + ] + }, + { + "id": "200302", + "value": "杨陵区", + "sub": [], + "trace": [ + "200000", + "200300" + ] + }, + { + "id": "200303", + "value": "渭城区", + "sub": [], + "trace": [ + "200000", + "200300" + ] + }, + { + "id": "200304", + "value": "三原县", + "sub": [], + "trace": [ + "200000", + "200300" + ] + }, + { + "id": "200305", + "value": "泾阳县", + "sub": [], + "trace": [ + "200000", + "200300" + ] + }, + { + "id": "200306", + "value": "乾县", + "sub": [], + "trace": [ + "200000", + "200300" + ] + }, + { + "id": "200307", + "value": "礼泉县", + "sub": [], + "trace": [ + "200000", + "200300" + ] + }, + { + "id": "200308", + "value": "永寿县", + "sub": [], + "trace": [ + "200000", + "200300" + ] + }, + { + "id": "200309", + "value": "长武县", + "sub": [], + "trace": [ + "200000", + "200300" + ] + }, + { + "id": "200310", + "value": "旬邑县", + "sub": [], + "trace": [ + "200000", + "200300" + ] + }, + { + "id": "200311", + "value": "淳化县", + "sub": [], + "trace": [ + "200000", + "200300" + ] + }, + { + "id": "200312", + "value": "武功县", + "sub": [], + "trace": [ + "200000", + "200300" + ] + }, + { + "id": "200313", + "value": "彬州市", + "sub": [], + "trace": [ + "200000", + "200300" + ] + }, + { + "id": "200314", + "value": "兴平市", + "sub": [], + "trace": [ + "200000", + "200300" + ] + } + ], + "trace": [ + "200000" + ] + }, + { + "id": "200400", + "value": "宝鸡", + "sub": [], + "trace": [ + "200000" + ] + }, + { + "id": "200500", + "value": "铜川", + "sub": [], + "trace": [ + "200000" + ] + }, + { + "id": "200600", + "value": "延安", + "sub": [], + "trace": [ + "200000" + ] + }, + { + "id": "200700", + "value": "渭南", + "sub": [], + "trace": [ + "200000" + ] + }, + { + "id": "200800", + "value": "榆林", + "sub": [], + "trace": [ + "200000" + ] + }, + { + "id": "200900", + "value": "汉中", + "sub": [], + "trace": [ + "200000" + ] + }, + { + "id": "201000", + "value": "安康", + "sub": [], + "trace": [ + "200000" + ] + }, + { + "id": "201100", + "value": "商洛", + "sub": [], + "trace": [ + "200000" + ] + }, + { + "id": "201200", + "value": "杨凌", + "sub": [], + "trace": [ + "200000" + ] + } + ], + "trace": [ + "200000" + ] + }, + { + "id": "210000", + "value": "山西省", + "sub": [ + { + "id": "210200", + "value": "太原", + "sub": [ + { + "id": "210200", + "value": "太原", + "sub": [], + "trace": [ + "210000", + "210200" + ] + }, + { + "id": "210201", + "value": "小店区", + "sub": [], + "trace": [ + "210000", + "210200" + ] + }, + { + "id": "210202", + "value": "迎泽区", + "sub": [], + "trace": [ + "210000", + "210200" + ] + }, + { + "id": "210203", + "value": "杏花岭区", + "sub": [], + "trace": [ + "210000", + "210200" + ] + }, + { + "id": "210204", + "value": "尖草坪区", + "sub": [], + "trace": [ + "210000", + "210200" + ] + }, + { + "id": "210205", + "value": "万柏林区", + "sub": [], + "trace": [ + "210000", + "210200" + ] + }, + { + "id": "210206", + "value": "晋源区", + "sub": [], + "trace": [ + "210000", + "210200" + ] + }, + { + "id": "210207", + "value": "清徐县", + "sub": [], + "trace": [ + "210000", + "210200" + ] + }, + { + "id": "210208", + "value": "阳曲县", + "sub": [], + "trace": [ + "210000", + "210200" + ] + }, + { + "id": "210209", + "value": "娄烦县", + "sub": [], + "trace": [ + "210000", + "210200" + ] + }, + { + "id": "210210", + "value": "古交市", + "sub": [], + "trace": [ + "210000", + "210200" + ] + } + ], + "trace": [ + "210000" + ] + }, + { + "id": "210300", + "value": "运城", + "sub": [], + "trace": [ + "210000" + ] + }, + { + "id": "210400", + "value": "大同", + "sub": [], + "trace": [ + "210000" + ] + }, + { + "id": "210500", + "value": "临汾", + "sub": [], + "trace": [ + "210000" + ] + }, + { + "id": "210600", + "value": "长治", + "sub": [], + "trace": [ + "210000" + ] + }, + { + "id": "210700", + "value": "晋城", + "sub": [], + "trace": [ + "210000" + ] + }, + { + "id": "210800", + "value": "阳泉", + "sub": [], + "trace": [ + "210000" + ] + }, + { + "id": "210900", + "value": "朔州", + "sub": [], + "trace": [ + "210000" + ] + }, + { + "id": "211000", + "value": "晋中", + "sub": [], + "trace": [ + "210000" + ] + }, + { + "id": "211100", + "value": "忻州", + "sub": [], + "trace": [ + "210000" + ] + }, + { + "id": "211200", + "value": "吕梁", + "sub": [], + "trace": [ + "210000" + ] + } + ], + "trace": [ + "210000" + ] + }, + { + "id": "220000", + "value": "黑龙江省", + "sub": [ + { + "id": "220200", + "value": "哈尔滨", + "sub": [ + { + "id": "220200", + "value": "哈尔滨", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220201", + "value": "道里区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220202", + "value": "南岗区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220203", + "value": "道外区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220204", + "value": "平房区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220205", + "value": "松北区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220206", + "value": "香坊区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220207", + "value": "呼兰区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220208", + "value": "阿城区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220209", + "value": "依兰县", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220210", + "value": "方正县", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220211", + "value": "宾县", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220212", + "value": "巴彦县", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220213", + "value": "木兰县", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220214", + "value": "通河县", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220215", + "value": "延寿县", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220216", + "value": "双城区", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220217", + "value": "尚志市", + "sub": [], + "trace": [ + "220000", + "220200" + ] + }, + { + "id": "220218", + "value": "五常市", + "sub": [], + "trace": [ + "220000", + "220200" + ] + } + ], + "trace": [ + "220000" + ] + }, + { + "id": "220300", + "value": "伊春", + "sub": [], + "trace": [ + "220000" + ] + }, + { + "id": "220400", + "value": "绥化", + "sub": [], + "trace": [ + "220000" + ] + }, + { + "id": "220500", + "value": "大庆", + "sub": [], + "trace": [ + "220000" + ] + }, + { + "id": "220600", + "value": "齐齐哈尔", + "sub": [], + "trace": [ + "220000" + ] + }, + { + "id": "220700", + "value": "牡丹江", + "sub": [], + "trace": [ + "220000" + ] + }, + { + "id": "220800", + "value": "佳木斯", + "sub": [], + "trace": [ + "220000" + ] + }, + { + "id": "220900", + "value": "鸡西", + "sub": [], + "trace": [ + "220000" + ] + }, + { + "id": "221000", + "value": "鹤岗", + "sub": [], + "trace": [ + "220000" + ] + }, + { + "id": "221100", + "value": "双鸭山", + "sub": [], + "trace": [ + "220000" + ] + }, + { + "id": "221200", + "value": "黑河", + "sub": [], + "trace": [ + "220000" + ] + }, + { + "id": "221300", + "value": "七台河", + "sub": [], + "trace": [ + "220000" + ] + }, + { + "id": "221400", + "value": "大兴安岭", + "sub": [], + "trace": [ + "220000" + ] + } + ], + "trace": [ + "220000" + ] + }, + { + "id": "230000", + "value": "辽宁省", + "sub": [ + { + "id": "230200", + "value": "沈阳", + "sub": [ + { + "id": "230200", + "value": "沈阳", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230201", + "value": "大东区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230202", + "value": "浑南区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230203", + "value": "康平县", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230204", + "value": "和平区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230205", + "value": "皇姑区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230206", + "value": "沈北新区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230207", + "value": "沈河区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230208", + "value": "苏家屯区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230209", + "value": "铁西区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230210", + "value": "于洪区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230211", + "value": "法库县", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230212", + "value": "辽中区", + "sub": [], + "trace": [ + "230000", + "230200" + ] + }, + { + "id": "230213", + "value": "新民市", + "sub": [], + "trace": [ + "230000", + "230200" + ] + } + ], + "trace": [ + "230000" + ] + }, + { + "id": "230300", + "value": "大连", + "sub": [ + { + "id": "230300", + "value": "大连", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230301", + "value": "西岗区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230302", + "value": "中山区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230303", + "value": "沙河口区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230304", + "value": "甘井子区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230305", + "value": "旅顺口区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230306", + "value": "金州区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230307", + "value": "瓦房店市", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230308", + "value": "普兰店区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230309", + "value": "庄河市", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230310", + "value": "长海县", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230312", + "value": "高新园区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230313", + "value": "长兴岛", + "sub": [], + "trace": [ + "230000", + "230300" + ] + }, + { + "id": "230314", + "value": "大连保税区", + "sub": [], + "trace": [ + "230000", + "230300" + ] + } + ], + "trace": [ + "230000" + ] + }, + { + "id": "230400", + "value": "鞍山", + "sub": [], + "trace": [ + "230000" + ] + }, + { + "id": "230500", + "value": "营口", + "sub": [], + "trace": [ + "230000" + ] + }, + { + "id": "230600", + "value": "抚顺", + "sub": [], + "trace": [ + "230000" + ] + }, + { + "id": "230700", + "value": "锦州", + "sub": [], + "trace": [ + "230000" + ] + }, + { + "id": "230800", + "value": "丹东", + "sub": [], + "trace": [ + "230000" + ] + }, + { + "id": "230900", + "value": "葫芦岛", + "sub": [], + "trace": [ + "230000" + ] + }, + { + "id": "231000", + "value": "本溪", + "sub": [], + "trace": [ + "230000" + ] + }, + { + "id": "231100", + "value": "辽阳", + "sub": [], + "trace": [ + "230000" + ] + }, + { + "id": "231200", + "value": "铁岭", + "sub": [], + "trace": [ + "230000" + ] + }, + { + "id": "231300", + "value": "盘锦", + "sub": [], + "trace": [ + "230000" + ] + }, + { + "id": "231400", + "value": "朝阳", + "sub": [], + "trace": [ + "230000" + ] + }, + { + "id": "231500", + "value": "阜新", + "sub": [], + "trace": [ + "230000" + ] + } + ], + "trace": [ + "230000" + ] + }, + { + "id": "240000", + "value": "吉林省", + "sub": [ + { + "id": "240200", + "value": "长春", + "sub": [ + { + "id": "240200", + "value": "长春", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240201", + "value": "朝阳区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240202", + "value": "南关区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240203", + "value": "宽城区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240204", + "value": "二道区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240205", + "value": "绿园区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240206", + "value": "双阳区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240207", + "value": "经济技术开发区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240208", + "value": "高新技术产业开发区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240209", + "value": "净月高新技术产业开发区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240210", + "value": "汽车经济技术开发区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240211", + "value": "榆树市", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240212", + "value": "九台区", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240213", + "value": "德惠市", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240214", + "value": "农安县", + "sub": [], + "trace": [ + "240000", + "240200" + ] + }, + { + "id": "240215", + "value": "公主岭市", + "sub": [], + "trace": [ + "240000", + "240200" + ] + } + ], + "trace": [ + "240000" + ] + }, + { + "id": "240300", + "value": "吉林", + "sub": [], + "trace": [ + "240000" + ] + }, + { + "id": "240400", + "value": "辽源", + "sub": [], + "trace": [ + "240000" + ] + }, + { + "id": "240500", + "value": "通化", + "sub": [], + "trace": [ + "240000" + ] + }, + { + "id": "240600", + "value": "四平", + "sub": [], + "trace": [ + "240000" + ] + }, + { + "id": "240700", + "value": "松原", + "sub": [], + "trace": [ + "240000" + ] + }, + { + "id": "240800", + "value": "延吉", + "sub": [], + "trace": [ + "240000" + ] + }, + { + "id": "240900", + "value": "白山", + "sub": [], + "trace": [ + "240000" + ] + }, + { + "id": "241000", + "value": "白城", + "sub": [], + "trace": [ + "240000" + ] + }, + { + "id": "241100", + "value": "延边", + "sub": [], + "trace": [ + "240000" + ] + } + ], + "trace": [ + "240000" + ] + }, + { + "id": "250000", + "value": "云南省", + "sub": [ + { + "id": "250200", + "value": "昆明", + "sub": [ + { + "id": "250200", + "value": "昆明", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250201", + "value": "五华区", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250202", + "value": "盘龙区", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250203", + "value": "官渡区", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250204", + "value": "西山区", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250205", + "value": "东川区", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250206", + "value": "呈贡区", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250207", + "value": "晋宁区", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250208", + "value": "富民县", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250209", + "value": "宜良县", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250210", + "value": "石林彝族自治县", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250211", + "value": "嵩明县", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250212", + "value": "禄劝县", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250213", + "value": "寻甸回族彝族自治县", + "sub": [], + "trace": [ + "250000", + "250200" + ] + }, + { + "id": "250214", + "value": "安宁市", + "sub": [], + "trace": [ + "250000", + "250200" + ] + } + ], + "trace": [ + "250000" + ] + }, + { + "id": "250300", + "value": "曲靖", + "sub": [], + "trace": [ + "250000" + ] + }, + { + "id": "250400", + "value": "玉溪", + "sub": [], + "trace": [ + "250000" + ] + }, + { + "id": "250500", + "value": "大理", + "sub": [], + "trace": [ + "250000" + ] + }, + { + "id": "250600", + "value": "丽江", + "sub": [], + "trace": [ + "250000" + ] + }, + { + "id": "251000", + "value": "红河州", + "sub": [], + "trace": [ + "250000" + ] + }, + { + "id": "251100", + "value": "普洱", + "sub": [], + "trace": [ + "250000" + ] + }, + { + "id": "251200", + "value": "保山", + "sub": [], + "trace": [ + "250000" + ] + }, + { + "id": "251300", + "value": "昭通", + "sub": [], + "trace": [ + "250000" + ] + }, + { + "id": "251400", + "value": "文山", + "sub": [], + "trace": [ + "250000" + ] + }, + { + "id": "251500", + "value": "西双版纳", + "sub": [], + "trace": [ + "250000" + ] + }, + { + "id": "251600", + "value": "德宏", + "sub": [], + "trace": [ + "250000" + ] + }, + { + "id": "251700", + "value": "楚雄", + "sub": [], + "trace": [ + "250000" + ] + }, + { + "id": "251800", + "value": "临沧", + "sub": [], + "trace": [ + "250000" + ] + }, + { + "id": "251900", + "value": "怒江", + "sub": [], + "trace": [ + "250000" + ] + }, + { + "id": "252000", + "value": "迪庆", + "sub": [], + "trace": [ + "250000" + ] + } + ], + "trace": [ + "250000" + ] + }, + { + "id": "260000", + "value": "贵州省", + "sub": [ + { + "id": "260200", + "value": "贵阳", + "sub": [ + { + "id": "260200", + "value": "贵阳", + "sub": [], + "trace": [ + "260000", + "260200" + ] + }, + { + "id": "260201", + "value": "南明区", + "sub": [], + "trace": [ + "260000", + "260200" + ] + }, + { + "id": "260202", + "value": "云岩区", + "sub": [], + "trace": [ + "260000", + "260200" + ] + }, + { + "id": "260203", + "value": "花溪区", + "sub": [], + "trace": [ + "260000", + "260200" + ] + }, + { + "id": "260204", + "value": "观山湖区", + "sub": [], + "trace": [ + "260000", + "260200" + ] + }, + { + "id": "260205", + "value": "乌当区", + "sub": [], + "trace": [ + "260000", + "260200" + ] + }, + { + "id": "260206", + "value": "白云区", + "sub": [], + "trace": [ + "260000", + "260200" + ] + }, + { + "id": "260207", + "value": "开阳县", + "sub": [], + "trace": [ + "260000", + "260200" + ] + }, + { + "id": "260208", + "value": "息烽县", + "sub": [], + "trace": [ + "260000", + "260200" + ] + }, + { + "id": "260209", + "value": "修文县", + "sub": [], + "trace": [ + "260000", + "260200" + ] + }, + { + "id": "260210", + "value": "清镇市", + "sub": [], + "trace": [ + "260000", + "260200" + ] + } + ], + "trace": [ + "260000" + ] + }, + { + "id": "260300", + "value": "遵义", + "sub": [], + "trace": [ + "260000" + ] + }, + { + "id": "260400", + "value": "六盘水", + "sub": [], + "trace": [ + "260000" + ] + }, + { + "id": "260500", + "value": "安顺", + "sub": [], + "trace": [ + "260000" + ] + }, + { + "id": "260600", + "value": "铜仁", + "sub": [], + "trace": [ + "260000" + ] + }, + { + "id": "260700", + "value": "毕节", + "sub": [], + "trace": [ + "260000" + ] + }, + { + "id": "260800", + "value": "黔西南", + "sub": [], + "trace": [ + "260000" + ] + }, + { + "id": "260900", + "value": "黔东南", + "sub": [], + "trace": [ + "260000" + ] + }, + { + "id": "261000", + "value": "黔南", + "sub": [], + "trace": [ + "260000" + ] + } + ], + "trace": [ + "260000" + ] + }, + { + "id": "270000", + "value": "甘肃省", + "sub": [ + { + "id": "270200", + "value": "兰州", + "sub": [ + { + "id": "270200", + "value": "兰州", + "sub": [], + "trace": [ + "270000", + "270200" + ] + }, + { + "id": "270201", + "value": "城关区", + "sub": [], + "trace": [ + "270000", + "270200" + ] + }, + { + "id": "270202", + "value": "七里河区", + "sub": [], + "trace": [ + "270000", + "270200" + ] + }, + { + "id": "270203", + "value": "西固区", + "sub": [], + "trace": [ + "270000", + "270200" + ] + }, + { + "id": "270204", + "value": "安宁区", + "sub": [], + "trace": [ + "270000", + "270200" + ] + }, + { + "id": "270205", + "value": "红古区", + "sub": [], + "trace": [ + "270000", + "270200" + ] + }, + { + "id": "270206", + "value": "兰州新区", + "sub": [], + "trace": [ + "270000", + "270200" + ] + }, + { + "id": "270207", + "value": "永登县", + "sub": [], + "trace": [ + "270000", + "270200" + ] + }, + { + "id": "270208", + "value": "皋兰县", + "sub": [], + "trace": [ + "270000", + "270200" + ] + }, + { + "id": "270209", + "value": "榆中县", + "sub": [], + "trace": [ + "270000", + "270200" + ] + } + ], + "trace": [ + "270000" + ] + }, + { + "id": "270300", + "value": "金昌", + "sub": [], + "trace": [ + "270000" + ] + }, + { + "id": "270400", + "value": "嘉峪关", + "sub": [], + "trace": [ + "270000" + ] + }, + { + "id": "270500", + "value": "酒泉", + "sub": [], + "trace": [ + "270000" + ] + }, + { + "id": "270600", + "value": "天水", + "sub": [], + "trace": [ + "270000" + ] + }, + { + "id": "270700", + "value": "武威", + "sub": [], + "trace": [ + "270000" + ] + }, + { + "id": "270800", + "value": "白银", + "sub": [], + "trace": [ + "270000" + ] + }, + { + "id": "270900", + "value": "张掖", + "sub": [], + "trace": [ + "270000" + ] + }, + { + "id": "271000", + "value": "平凉", + "sub": [], + "trace": [ + "270000" + ] + }, + { + "id": "271100", + "value": "定西", + "sub": [], + "trace": [ + "270000" + ] + }, + { + "id": "271200", + "value": "陇南", + "sub": [], + "trace": [ + "270000" + ] + }, + { + "id": "271300", + "value": "庆阳", + "sub": [], + "trace": [ + "270000" + ] + }, + { + "id": "271400", + "value": "临夏", + "sub": [], + "trace": [ + "270000" + ] + }, + { + "id": "271500", + "value": "甘南", + "sub": [], + "trace": [ + "270000" + ] + } + ], + "trace": [ + "270000" + ] + }, + { + "id": "280000", + "value": "内蒙古", + "sub": [ + { + "id": "280200", + "value": "呼和浩特", + "sub": [ + { + "id": "280200", + "value": "呼和浩特", + "sub": [], + "trace": [ + "280000", + "280200" + ] + }, + { + "id": "280201", + "value": "新城区", + "sub": [], + "trace": [ + "280000", + "280200" + ] + }, + { + "id": "280202", + "value": "回民区", + "sub": [], + "trace": [ + "280000", + "280200" + ] + }, + { + "id": "280203", + "value": "玉泉区", + "sub": [], + "trace": [ + "280000", + "280200" + ] + }, + { + "id": "280204", + "value": "赛罕区", + "sub": [], + "trace": [ + "280000", + "280200" + ] + }, + { + "id": "280205", + "value": "土默特左旗", + "sub": [], + "trace": [ + "280000", + "280200" + ] + }, + { + "id": "280206", + "value": "托克托县", + "sub": [], + "trace": [ + "280000", + "280200" + ] + }, + { + "id": "280207", + "value": "和林格尔县", + "sub": [], + "trace": [ + "280000", + "280200" + ] + }, + { + "id": "280208", + "value": "清水河县", + "sub": [], + "trace": [ + "280000", + "280200" + ] + }, + { + "id": "280209", + "value": "武川县", + "sub": [], + "trace": [ + "280000", + "280200" + ] + } + ], + "trace": [ + "280000" + ] + }, + { + "id": "280300", + "value": "赤峰", + "sub": [], + "trace": [ + "280000" + ] + }, + { + "id": "280400", + "value": "包头", + "sub": [], + "trace": [ + "280000" + ] + }, + { + "id": "280700", + "value": "通辽", + "sub": [], + "trace": [ + "280000" + ] + }, + { + "id": "280800", + "value": "鄂尔多斯", + "sub": [], + "trace": [ + "280000" + ] + }, + { + "id": "280900", + "value": "巴彦淖尔", + "sub": [], + "trace": [ + "280000" + ] + }, + { + "id": "281000", + "value": "乌海", + "sub": [], + "trace": [ + "280000" + ] + }, + { + "id": "281100", + "value": "呼伦贝尔", + "sub": [], + "trace": [ + "280000" + ] + }, + { + "id": "281200", + "value": "乌兰察布", + "sub": [], + "trace": [ + "280000" + ] + }, + { + "id": "281300", + "value": "兴安盟", + "sub": [], + "trace": [ + "280000" + ] + }, + { + "id": "281400", + "value": "锡林郭勒盟", + "sub": [], + "trace": [ + "280000" + ] + }, + { + "id": "281500", + "value": "阿拉善盟", + "sub": [], + "trace": [ + "280000" + ] + } + ], + "trace": [ + "280000" + ] + }, + { + "id": "290000", + "value": "宁夏", + "sub": [ + { + "id": "290200", + "value": "银川", + "sub": [], + "trace": [ + "290000" + ] + }, + { + "id": "290300", + "value": "吴忠", + "sub": [], + "trace": [ + "290000" + ] + }, + { + "id": "290400", + "value": "中卫", + "sub": [], + "trace": [ + "290000" + ] + }, + { + "id": "290500", + "value": "石嘴山", + "sub": [], + "trace": [ + "290000" + ] + }, + { + "id": "290600", + "value": "固原", + "sub": [], + "trace": [ + "290000" + ] + } + ], + "trace": [ + "290000" + ] + }, + { + "id": "300000", + "value": "西藏", + "sub": [ + { + "id": "300200", + "value": "拉萨", + "sub": [], + "trace": [ + "300000" + ] + }, + { + "id": "300300", + "value": "日喀则", + "sub": [], + "trace": [ + "300000" + ] + }, + { + "id": "300400", + "value": "林芝", + "sub": [], + "trace": [ + "300000" + ] + }, + { + "id": "300500", + "value": "山南", + "sub": [], + "trace": [ + "300000" + ] + }, + { + "id": "300600", + "value": "昌都", + "sub": [], + "trace": [ + "300000" + ] + }, + { + "id": "300700", + "value": "那曲", + "sub": [], + "trace": [ + "300000" + ] + }, + { + "id": "300800", + "value": "阿里", + "sub": [], + "trace": [ + "300000" + ] + } + ], + "trace": [ + "300000" + ] + }, + { + "id": "310000", + "value": "新疆", + "sub": [ + { + "id": "310200", + "value": "乌鲁木齐", + "sub": [ + { + "id": "310200", + "value": "乌鲁木齐", + "sub": [], + "trace": [ + "310000", + "310200" + ] + }, + { + "id": "310201", + "value": "天山区", + "sub": [], + "trace": [ + "310000", + "310200" + ] + }, + { + "id": "310202", + "value": "沙依巴克区", + "sub": [], + "trace": [ + "310000", + "310200" + ] + }, + { + "id": "310203", + "value": "新市区", + "sub": [], + "trace": [ + "310000", + "310200" + ] + }, + { + "id": "310204", + "value": "水磨沟区", + "sub": [], + "trace": [ + "310000", + "310200" + ] + }, + { + "id": "310205", + "value": "头屯河区", + "sub": [], + "trace": [ + "310000", + "310200" + ] + }, + { + "id": "310206", + "value": "达坂城区", + "sub": [], + "trace": [ + "310000", + "310200" + ] + }, + { + "id": "310207", + "value": "米东区", + "sub": [], + "trace": [ + "310000", + "310200" + ] + }, + { + "id": "310208", + "value": "乌鲁木齐县", + "sub": [], + "trace": [ + "310000", + "310200" + ] + } + ], + "trace": [ + "310000" + ] + }, + { + "id": "310300", + "value": "克拉玛依", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "310400", + "value": "喀什地区", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "310500", + "value": "伊犁", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "310600", + "value": "阿克苏", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "310700", + "value": "哈密", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "310800", + "value": "石河子", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "310900", + "value": "阿拉尔", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "311000", + "value": "五家渠", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "311100", + "value": "图木舒克", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "311200", + "value": "昌吉", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "311300", + "value": "阿勒泰", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "311400", + "value": "吐鲁番", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "311500", + "value": "塔城", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "311600", + "value": "和田", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "311700", + "value": "克孜勒苏柯尔克孜", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "311800", + "value": "巴音郭楞", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "311900", + "value": "博尔塔拉", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "312000", + "value": "昆玉", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "312100", + "value": "北屯", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "312200", + "value": "铁门关", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "312300", + "value": "可克达拉", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "312400", + "value": "胡杨河", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "312500", + "value": "双河", + "sub": [], + "trace": [ + "310000" + ] + }, + { + "id": "312600", + "value": "新星", + "sub": [], + "trace": [ + "310000" + ] + } + ], + "trace": [ + "310000" + ] + }, + { + "id": "320000", + "value": "青海省", + "sub": [ + { + "id": "320200", + "value": "西宁", + "sub": [], + "trace": [ + "320000" + ] + }, + { + "id": "320300", + "value": "海东", + "sub": [], + "trace": [ + "320000" + ] + }, + { + "id": "320400", + "value": "海西", + "sub": [], + "trace": [ + "320000" + ] + }, + { + "id": "320500", + "value": "海北", + "sub": [], + "trace": [ + "320000" + ] + }, + { + "id": "320600", + "value": "黄南", + "sub": [], + "trace": [ + "320000" + ] + }, + { + "id": "320700", + "value": "海南州", + "sub": [], + "trace": [ + "320000" + ] + }, + { + "id": "320800", + "value": "果洛", + "sub": [], + "trace": [ + "320000" + ] + }, + { + "id": "320900", + "value": "玉树", + "sub": [], + "trace": [ + "320000" + ] + } + ], + "trace": [ + "320000" + ] + }, + { + "id": "330000", + "value": "香港", + "sub": [ + { + "id": "330000", + "value": "香港", + "sub": [ + { + "id": "330000", + "value": "香港", + "sub": [] + } + ] + } + ] + }, + { + "id": "340000", + "value": "澳门", + "sub": [ + { + "id": "340000", + "value": "澳门", + "sub": [ + { + "id": "340000", + "value": "澳门", + "sub": [] + } + ] + } + ] + }, + { + "id": "350000", + "value": "台湾", + "sub": [ + { + "id": "350000", + "value": "台湾", + "sub": [ + { + "id": "350000", + "value": "台湾", + "sub": [] + } + ] + } + ] + } + ], + "d_search_cottype": [ + { + "id": "04", + "value": "国企", + "sub": [] + }, + { + "id": "01", + "value": "外资(欧美)", + "sub": [] + }, + { + "id": "02", + "value": "外资(非欧美)", + "sub": [] + }, + { + "id": "10", + "value": "已上市", + "sub": [] + }, + { + "id": "03", + "value": "合资", + "sub": [] + }, + { + "id": "05", + "value": "民营", + "sub": [] + }, + { + "id": "06", + "value": "外企代表处", + "sub": [] + }, + { + "id": "07", + "value": "政府机关", + "sub": [] + }, + { + "id": "08", + "value": "事业单位", + "sub": [] + }, + { + "id": "09", + "value": "非营利组织", + "sub": [] + }, + { + "id": "11", + "value": "创业公司", + "sub": [] + } + ], + "d_search_workyear": [ + { + "id": "01", + "value": "在校生\/应届生", + "sub": [] + }, + { + "id": "02", + "value": "1-3年", + "sub": [] + }, + { + "id": "03", + "value": "3-5年", + "sub": [] + }, + { + "id": "04", + "value": "5-10年", + "sub": [] + }, + { + "id": "05", + "value": "10年以上", + "sub": [] + }, + { + "id": "06", + "value": "无需经验", + "sub": [] + } + ], + "d_search_providesalary": [ + { + "id": "01", + "value": "2千以下", + "sub": [] + }, + { + "id": "02", + "value": "2-3千", + "sub": [] + }, + { + "id": "03", + "value": "3-4.5千", + "sub": [] + }, + { + "id": "04", + "value": "4.5-6千", + "sub": [] + }, + { + "id": "05", + "value": "6-8千", + "sub": [] + }, + { + "id": "06", + "value": "0.8-1万", + "sub": [] + }, + { + "id": "07", + "value": "1-1.5万", + "sub": [] + }, + { + "id": "08", + "value": "1.5-2万", + "sub": [] + }, + { + "id": "09", + "value": "2-3万", + "sub": [] + }, + { + "id": "10", + "value": "3-4万", + "sub": [] + }, + { + "id": "11", + "value": "4-5万", + "sub": [] + }, + { + "id": "12", + "value": "5万以上", + "sub": [] + } + ], + "d_search_companysize": [ + { + "id": "01", + "value": "少于50人", + "sub": [] + }, + { + "id": "02", + "value": "50-150人", + "sub": [] + }, + { + "id": "03", + "value": "150-500人", + "sub": [] + }, + { + "id": "04", + "value": "500-1000人", + "sub": [] + }, + { + "id": "05", + "value": "1000-5000人", + "sub": [] + }, + { + "id": "06", + "value": "5000-10000人", + "sub": [] + }, + { + "id": "07", + "value": "10000人以上", + "sub": [] + } + ], + "d_search_degreefrom": [ + { + "id": "01", + "value": "初中及以下", + "sub": [] + }, + { + "id": "02", + "value": "高中\/中技\/中专", + "sub": [] + }, + { + "id": "03", + "value": "大专", + "sub": [] + }, + { + "id": "04", + "value": "本科", + "sub": [] + }, + { + "id": "05", + "value": "硕士", + "sub": [] + }, + { + "id": "06", + "value": "博士", + "sub": [] + }, + { + "id": "07", + "value": "无学历要求", + "sub": [] + } + ], + "d_search_jobterm": [ + { + "id": "01", + "value": "全职", + "sub": [] + }, + { + "id": "02", + "value": "兼职", + "sub": [] + }, + { + "id": "03", + "value": "实习全职", + "sub": [] + }, + { + "id": "04", + "value": "实习兼职", + "sub": [] + } + ], + "d_search_issuedate": [ + { + "id": "0", + "value": "24小时内", + "sub": [] + }, + { + "id": "1", + "value": "近三天", + "sub": [] + }, + { + "id": "2", + "value": "近一周", + "sub": [] + }, + { + "id": "3", + "value": "近一月", + "sub": [] + }, + { + "id": "4", + "value": "其他", + "sub": [] + } + ], + "d_search_postchannel": [ + { + "id": "000100", + "value": "校园", + "sub": [] + }, + { + "id": "100000", + "value": "实习", + "sub": [] + } + ] + } + } + +liepin: + dict-json: | + { + "flag": 1, + "data": { + "workExperiences": [ + { + "code": "1", + "name": "应届生" + }, + { + "code": "2", + "name": "实习生" + }, + { + "code": "0$1", + "name": "1年以内" + }, + { + "code": "1$3", + "name": "1-3年" + }, + { + "code": "3$5", + "name": "3-5年" + }, + { + "code": "5$10", + "name": "5-10年" + }, + { + "code": "10$999", + "name": "10年以上" + } + ], + "yearSalaries": [ + { + "code": "1", + "name": "10万以下" + }, + { + "code": "2", + "name": "10-15万" + }, + { + "code": "3", + "name": "16-20万" + }, + { + "code": "4", + "name": "21-30万" + }, + { + "code": "5", + "name": "31-50万" + }, + { + "code": "6", + "name": "51-100万" + }, + { + "code": "7", + "name": "100万以上" + } + ], + "jobKinds": [ + { + "code": "1", + "name": "猎头职位" + }, + { + "code": "2", + "name": "企业职位" + } + ], + "salaries": [ + { + "code": "0$3", + "name": "3K以下" + }, + { + "code": "3$5", + "name": "3K-5k" + }, + { + "code": "5$10", + "name": "5K-10k" + }, + { + "code": "10$20", + "name": "10K-20k" + }, + { + "code": "20$40", + "name": "20K-40k" + }, + { + "code": "40$60", + "name": "40K-60k" + }, + { + "code": "60$999", + "name": "60K以上" + } + ], + "dqs": [], + "compScales": [ + { + "code": "010", + "name": "1-49人" + }, + { + "code": "020", + "name": "50-99人" + }, + { + "code": "030", + "name": "100-499人" + }, + { + "code": "040", + "name": "500-999人" + }, + { + "code": "050", + "name": "1000-2000人" + }, + { + "code": "060", + "name": "2000-5000人" + }, + { + "code": "070", + "name": "5000-10000人" + }, + { + "code": "080", + "name": "10000人以上" + } + ], + "industries": [ + { + "children": [ + { + "code": "H01", + "name": "不限" + }, + { + "code": "H0001", + "name": "游戏" + }, + { + "code": "H0002", + "name": "电子商务" + }, + { + "code": "H0003", + "name": "新零售" + }, + { + "code": "H0004", + "name": "在线社交/媒体" + }, + { + "code": "H0005", + "name": "生活服务O2O" + }, + { + "code": "H0006", + "name": "在线教育" + }, + { + "code": "H0007", + "name": "互联网医疗" + }, + { + "code": "H0008", + "name": "云计算/大数据" + }, + { + "code": "H0009", + "name": "人工智能" + }, + { + "code": "H0010", + "name": "物联网" + }, + { + "code": "H0011", + "name": "区块链" + }, + { + "code": "H0012", + "name": "网络/信息安全" + }, + { + "code": "H0013", + "name": "计算机软件" + }, + { + "code": "H0014", + "name": "计算机硬件" + }, + { + "code": "H0015", + "name": "智能硬件" + }, + { + "code": "H0016", + "name": "IT服务" + }, + { + "code": "H0017", + "name": "互联网" + } + ], + "code": "H01", + "name": "IT/互联网/游戏" + }, + { + "children": [ + { + "code": "H02", + "name": "不限" + }, + { + "code": "H0018", + "name": "电子/半导体/集成电路" + }, + { + "code": "H0019", + "name": "智能硬件" + }, + { + "code": "H0020", + "name": "运营商/增值服务" + }, + { + "code": "H0021", + "name": "通信设备" + } + ], + "code": "H02", + "name": "电子/通信/半导体" + }, + { + "children": [ + { + "code": "H03", + "name": "不限" + }, + { + "code": "H0022", + "name": "房地产开发经营" + }, + { + "code": "H0023", + "name": "房地产租赁/中介" + }, + { + "code": "H0024", + "name": "物业/商业管理" + }, + { + "code": "H0025", + "name": "建材" + }, + { + "code": "H0026", + "name": "工程管理/勘察/监理" + }, + { + "code": "H0027", + "name": "建筑/工程设计" + }, + { + "code": "H0028", + "name": "工程施工" + }, + { + "code": "H0029", + "name": "装饰装修" + } + ], + "code": "H03", + "name": "房地产/建筑" + }, + { + "children": [ + { + "code": "H04", + "name": "不限" + }, + { + "code": "H0030", + "name": "银行" + }, + { + "code": "H0031", + "name": "保险" + }, + { + "code": "H0032", + "name": "基金/证券/期货" + }, + { + "code": "H0033", + "name": "资产管理" + }, + { + "code": "H0034", + "name": "担保/拍卖/典当" + }, + { + "code": "H0035", + "name": "信托" + }, + { + "code": "H0036", + "name": "科技金融" + }, + { + "code": "H0037", + "name": "融资租赁/保理" + }, + { + "code": "H0038", + "name": "其他金融" + } + ], + "code": "H04", + "name": "金融" + }, + { + "children": [ + { + "code": "H05", + "name": "不限" + }, + { + "code": "H0039", + "name": "食品/饮料/酒水" + }, + { + "code": "H0040", + "name": "日化" + }, + { + "code": "H0041", + "name": "烟草" + }, + { + "code": "H0042", + "name": "服装/纺织/皮革" + }, + { + "code": "H0043", + "name": "家具/家居" + }, + { + "code": "H0044", + "name": "家电" + }, + { + "code": "H0045", + "name": "办公用品/设备" + }, + { + "code": "H0046", + "name": "工艺品" + }, + { + "code": "H0047", + "name": "珠宝/首饰" + }, + { + "code": "H0048", + "name": "文娱用品/器材" + }, + { + "code": "H0049", + "name": "日用杂品" + } + ], + "code": "H05", + "name": "消费品" + }, + { + "children": [ + { + "code": "H06", + "name": "不限" + }, + { + "code": "H0050", + "name": "制药" + }, + { + "code": "H0051", + "name": "生物技术" + }, + { + "code": "H0052", + "name": "医疗器械" + }, + { + "code": "H0053", + "name": "医疗机构" + }, + { + "code": "H0054", + "name": "医药流通" + }, + { + "code": "H0055", + "name": "医药外包" + } + ], + "code": "H06", + "name": "医疗健康" + }, + { + "children": [ + { + "code": "H07", + "name": "不限" + }, + { + "code": "H0056", + "name": "汽车零部件及配件" + }, + { + "code": "H0057", + "name": "整车制造" + }, + { + "code": "H0058", + "name": "新能源汽车" + }, + { + "code": "H0059", + "name": "汽车交易/后市场" + } + ], + "code": "H07", + "name": "汽车" + }, + { + "children": [ + { + "code": "H08", + "name": "不限" + }, + { + "code": "H0060", + "name": "机械/设备" + }, + { + "code": "H0061", + "name": "电气机械/器材" + }, + { + "code": "H0062", + "name": "仪器仪表" + }, + { + "code": "H0063", + "name": "轨道交通/船舶设备" + }, + { + "code": "H0064", + "name": "航空/航天设备" + }, + { + "code": "H0065", + "name": "新材料" + }, + { + "code": "H0066", + "name": "金属制品" + }, + { + "code": "H0067", + "name": "非金属矿物制品" + }, + { + "code": "H0068", + "name": "橡胶/塑料制品" + }, + { + "code": "H0069", + "name": "印刷/包装/造纸" + }, + { + "code": "H0070", + "name": "工业自动化" + }, + { + "code": "H0071", + "name": "家电" + }, + { + "code": "H0072", + "name": "家具/家居" + }, + { + "code": "H0073", + "name": "其他制造业" + } + ], + "code": "H08", + "name": "机械/制造" + }, + { + "children": [ + { + "code": "H09", + "name": "不限" + }, + { + "code": "H0074", + "name": "学前教育" + }, + { + "code": "H0075", + "name": "学校教育" + }, + { + "code": "H0076", + "name": "培训服务" + }, + { + "code": "H0077", + "name": "其他教育培训" + }, + { + "code": "H0086", + "name": "学术/科研" + } + ], + "code": "H09", + "name": "教育培训/科研" + }, + { + "children": [ + { + "code": "H10", + "name": "不限" + }, + { + "code": "H0078", + "name": "法律服务" + }, + { + "code": "H0079", + "name": "人力资源服务" + }, + { + "code": "H0080", + "name": "财务/审计/税务" + }, + { + "code": "H0081", + "name": "知识产权服务" + }, + { + "code": "H0082", + "name": "翻译服务" + }, + { + "code": "H0083", + "name": "咨询服务" + }, + { + "code": "H0084", + "name": "租赁业" + }, + { + "code": "H0085", + "name": "检测/认证" + }, + { + "code": "H0087", + "name": "专业技术服务" + }, + { + "code": "H0088", + "name": "科技推广服务" + }, + { + "code": "H0089", + "name": "其他商务服务业" + } + ], + "code": "H10", + "name": "专业服务" + }, + { + "children": [ + { + "code": "H11", + "name": "不限" + }, + { + "code": "H0090", + "name": "广告/公关/会展" + }, + { + "code": "H0091", + "name": "广播/影视/录音" + }, + { + "code": "H0092", + "name": "新闻和出版业" + }, + { + "code": "H0093", + "name": "文化艺术业" + }, + { + "code": "H0094", + "name": "体育" + } + ], + "code": "H11", + "name": "广告/传媒/文化/体育" + }, + { + "children": [ + { + "code": "H12", + "name": "不限" + }, + { + "code": "H0095", + "name": "餐饮业" + }, + { + "code": "H0096", + "name": "酒店/民宿" + }, + { + "code": "H0097", + "name": "旅游" + }, + { + "code": "H0098", + "name": "室内娱乐" + }, + { + "code": "H0099", + "name": "家政服务" + }, + { + "code": "H0100", + "name": "养老服务" + }, + { + "code": "H0101", + "name": "美容/美发/保健" + }, + { + "code": "H0102", + "name": "婚嫁/摄影" + }, + { + "code": "H0103", + "name": "宠物服务" + }, + { + "code": "H0104", + "name": "其他生活服务" + } + ], + "code": "H12", + "name": "生活服务" + }, + { + "children": [ + { + "code": "H13", + "name": "不限" + }, + { + "code": "H0105", + "name": "民航/铁路/公路/水路客运" + }, + { + "code": "H0106", + "name": "货运/物流/仓储" + }, + { + "code": "H0107", + "name": "邮政/快递" + }, + { + "code": "H0108", + "name": "贸易/进出口" + }, + { + "code": "H0109", + "name": "批发/零售" + } + ], + "code": "H13", + "name": "交通/物流/贸易/零售" + }, + { + "children": [ + { + "code": "H14", + "name": "不限" + }, + { + "code": "H0110", + "name": "矿产开采" + }, + { + "code": "H0111", + "name": "金属冶炼" + }, + { + "code": "H0112", + "name": "煤炭/燃料加工" + }, + { + "code": "H0113", + "name": "电力/热力/燃气/水务" + }, + { + "code": "H0114", + "name": "新能源" + }, + { + "code": "H0115", + "name": "石化" + }, + { + "code": "H0116", + "name": "化工" + }, + { + "code": "H0117", + "name": "环保" + } + ], + "code": "H14", + "name": "能源/化工/环保" + }, + { + "children": [ + { + "code": "H15", + "name": "不限" + }, + { + "code": "H0118", + "name": "政府/公共事业" + }, + { + "code": "H0119", + "name": "非营利组织" + }, + { + "code": "H0120", + "name": "农/林/牧/渔" + }, + { + "code": "H0121", + "name": "其他行业" + } + ], + "code": "H15", + "name": "政府/非营利组织/其他" + } + ], + "famousComps": [ + { + "code": "qua_0004", + "name": "财富中国500强" + }, + { + "code": "qua_0009", + "name": "创新企业100强" + }, + { + "code": "qua_0005", + "name": "制造业500强" + }, + { + "code": "qua_0003", + "name": "专精特新企业" + }, + { + "code": "qua_0001", + "name": "高新技术企业" + }, + { + "code": "qua_0008", + "name": "独角兽" + } + ], + "hotCities": [ + { + "code": "410", + "name": "全国" + }, + { + "code": "010", + "name": "北京" + }, + { + "code": "020", + "name": "上海" + }, + { + "code": "030", + "name": "天津" + }, + { + "code": "040", + "name": "重庆" + }, + { + "code": "050020", + "name": "广州" + }, + { + "code": "050090", + "name": "深圳" + }, + { + "code": "060080", + "name": "苏州" + }, + { + "code": "060020", + "name": "南京" + }, + { + "code": "070020", + "name": "杭州" + }, + { + "code": "210040", + "name": "大连" + }, + { + "code": "280020", + "name": "成都" + }, + { + "code": "170020", + "name": "武汉" + }, + { + "code": "270020", + "name": "西安" + } + ], + "pubTimes": [ + { + "code": "", + "name": "不限" + }, + { + "code": "1", + "name": "一天以内" + }, + { + "code": "3", + "name": "三天以内" + }, + { + "code": "7", + "name": "一周以内" + }, + { + "code": "30", + "name": "一个月以内" + } + ], + "educations": [ + { + "code": "010", + "name": "博士" + }, + { + "code": "030", + "name": "硕士" + }, + { + "code": "040", + "name": "本科" + }, + { + "code": "050", + "name": "大专" + }, + { + "code": "060", + "name": "中专/中技" + }, + { + "code": "080", + "name": "高中" + }, + { + "code": "090", + "name": "初中及以下" + } + ], + "financeStages": [ + { + "code": "01", + "name": "天使轮" + }, + { + "code": "02", + "name": "A轮" + }, + { + "code": "03", + "name": "B轮" + }, + { + "code": "04", + "name": "C轮" + }, + { + "code": "05", + "name": "D轮及以上" + }, + { + "code": "06", + "name": "已上市" + }, + { + "code": "07", + "name": "战略融资" + }, + { + "code": "08", + "name": "融资未公开" + }, + { + "code": "99", + "name": "其他" + } + ], + "compNatures": [ + { + "code": "010", + "name": "外商独资·外企办事处" + }, + { + "code": "020", + "name": "中外合营(合资·合作)" + }, + { + "code": "030", + "name": "私营·民营企业" + }, + { + "code": "040", + "name": "国有企业" + }, + { + "code": "050", + "name": "国内上市公司" + }, + { + "code": "060", + "name": "政府机关/非盈利机构" + }, + { + "code": "070", + "name": "事业单位" + }, + { + "code": "999", + "name": "其他" + } + ] + } + } + dict-city-json: | + { + "430070050": { + "p": "430070", + "a": "2", + "c": "430070050", + "en": "Hamilton", + "l": "19", + "ca": 3, + "n": "汉密尔顿" + }, + "420250": { + "p": "360030", + "a": "2", + "c": "420250", + "en": "Missouri", + "l": "17", + "ca": 2, + "n": "密苏里州" + }, + "420490": { + "p": "360030", + "a": "2", + "c": "420490", + "en": "Wisconsin", + "l": "17", + "ca": 2, + "n": "威斯康星州" + }, + "420010": { + "p": "360030", + "a": "2", + "c": "420010", + "en": "Alabama", + "l": "17", + "ca": 2, + "n": "亚拉巴马州" + }, + "270050": { + "p": "270", + "a": "0", + "c": "270050", + "en": "Tongchuan", + "l": "7", + "ca": 3, + "n": "铜川" + }, + "270060": { + "p": "270", + "a": "0", + "c": "270060", + "en": "Weinan", + "l": "7", + "ca": 3, + "n": "渭南" + }, + "420360030": { + "p": "420360", + "a": "2", + "c": "420360030", + "en": "Lawton", + "l": "19", + "ca": 3, + "n": "劳顿" + }, + "350060": { + "p": "350", + "a": "2", + "c": "350060", + "en": "Philippines", + "l": "2", + "ca": 1, + "n": "菲律宾" + }, + "190050": { + "p": "190", + "a": "0", + "c": "190050", + "en": "Liaoyuan", + "l": "7", + "ca": 3, + "n": "辽源" + }, + "430060": { + "p": "360020", + "a": "2", + "c": "430060", + "en": "Nova Scotia", + "l": "4", + "ca": 2, + "n": "新斯科舍省" + }, + "110150": { + "p": "110", + "a": "0", + "c": "110150", + "en": "Guigang", + "l": "7", + "ca": 3, + "n": "贵港" + }, + "420280030": { + "p": "420280", + "a": "2", + "c": "420280030", + "en": "Reno", + "l": "19", + "ca": 3, + "n": "里诺" + }, + "280090": { + "p": "280", + "a": "0", + "c": "280090", + "en": "Panzhihua", + "l": "7", + "ca": 3, + "n": "攀枝花" + }, + "430070040": { + "p": "430070", + "a": "2", + "c": "430070040", + "en": "Brampton", + "l": "19", + "ca": 3, + "n": "宾顿" + }, + "100110": { + "p": "100", + "a": "0", + "c": "100110", + "en": "Longnan", + "l": "7", + "ca": 3, + "n": "陇南" + }, + "420260": { + "p": "360030", + "a": "2", + "c": "420260", + "en": "Montana", + "l": "17", + "ca": 2, + "n": "蒙大拿州" + }, + "420440010": { + "p": "420440", + "a": "2", + "c": "420440010", + "en": "Rutland", + "l": "19", + "ca": 3, + "n": "拉特兰" + }, + "180020": { + "p": "180", + "a": "0", + "c": "180020", + "en": "Changsha", + "l": "7", + "ca": 3, + "n": "长沙" + }, + "270040": { + "p": "270", + "a": "0", + "c": "270040", + "en": "Xianyang", + "l": "7", + "ca": 3, + "n": "咸阳" + }, + "420020": { + "p": "360030", + "a": "2", + "c": "420020", + "en": "Alaska", + "l": "17", + "ca": 2, + "n": "阿拉斯加州" + }, + "420360020": { + "p": "420360", + "a": "2", + "c": "420360020", + "en": "Tulsa", + "l": "19", + "ca": 3, + "n": "塔尔萨" + }, + "1": { + "en": "Hot country", + "n": "热门国家" + }, + "2": { + "en": "Hot city", + "n": "热门城市" + }, + "350070": { + "p": "350", + "a": "2", + "c": "350070", + "en": "Vietnam", + "l": "2", + "ca": 1, + "n": "越南" + }, + "190060": { + "p": "190", + "a": "0", + "c": "190060", + "en": "Tonghua", + "l": "7", + "ca": 3, + "n": "通化" + }, + "100100": { + "p": "100", + "a": "0", + "c": "100100", + "en": "Dingxi", + "l": "7", + "ca": 3, + "n": "定西" + }, + "430050": { + "p": "360020", + "a": "2", + "c": "430050", + "en": "New Brunswick", + "l": "4", + "ca": 2, + "n": "新不伦瑞克省" + }, + "110140": { + "p": "110", + "a": "0", + "c": "110140", + "en": "Hechi", + "l": "7", + "ca": 3, + "n": "河池" + }, + "280080": { + "p": "280", + "a": "0", + "c": "280080", + "en": "Zigong", + "l": "7", + "ca": 3, + "n": "自贡" + }, + "430070030": { + "p": "430070", + "a": "2", + "c": "430070030", + "en": "Mississauga", + "l": "19", + "ca": 3, + "n": "密西沙加" + }, + "420030": { + "p": "360030", + "a": "2", + "c": "420030", + "en": "Arizona", + "l": "17", + "ca": 2, + "n": "亚利桑那州" + }, + "420270": { + "p": "360030", + "a": "2", + "c": "420270", + "en": "Nebraska", + "l": "17", + "ca": 2, + "n": "内布拉斯加州" + }, + "270030": { + "p": "270", + "a": "0", + "c": "270030", + "en": "Baoji", + "l": "7", + "ca": 3, + "n": "宝鸡" + }, + "350080": { + "p": "350", + "a": "2", + "c": "350080", + "en": "Laos", + "l": "2", + "ca": 1, + "n": "老挝" + }, + "190030": { + "p": "190", + "a": "0", + "c": "190030", + "en": "Jilin", + "l": "7", + "ca": 3, + "n": "吉林市" + }, + "400610": { + "p": "400", + "a": "2", + "c": "400610", + "en": "The Democratic Republic of the Congo", + "l": "2", + "ca": 1, + "n": "刚果(金)" + }, + "430080": { + "p": "360020", + "a": "2", + "c": "430080", + "en": "Prince Edward Island", + "l": "4", + "ca": 2, + "n": "爱德华王子岛省" + }, + "110130": { + "p": "110", + "a": "0", + "c": "110130", + "en": "Hezhou", + "l": "7", + "ca": 3, + "n": "贺州" + }, + "280070": { + "p": "280", + "a": "0", + "c": "280070", + "en": "Yibin", + "l": "7", + "ca": 3, + "n": "宜宾" + }, + "430070020": { + "p": "430070", + "a": "2", + "c": "430070020", + "en": "Ottawa", + "l": "19", + "ca": 3, + "n": "渥太华" + }, + "110110": { + "p": "110", + "a": "0", + "c": "110110", + "en": "Baise", + "l": "7", + "ca": 3, + "n": "百色" + }, + "420350010": { + "p": "420350", + "a": "2", + "c": "420350010", + "en": "Columbus", + "l": "19", + "ca": 3, + "n": "哥伦布" + }, + "420040": { + "p": "360030", + "a": "2", + "c": "420040", + "en": "Arkansas", + "l": "17", + "ca": 2, + "n": "阿肯色州" + }, + "420280": { + "p": "360030", + "a": "2", + "c": "420280", + "en": "Nevada", + "l": "17", + "ca": 2, + "n": "内华达州" + }, + "270020": { + "p": "270", + "a": "0", + "c": "270020", + "en": "Xi'an", + "l": "7", + "ca": 3, + "n": "西安" + }, + "gangaotai": { + "en": "HK&TW&Macao", + "n": "港澳台" + }, + "350090": { + "p": "350", + "a": "2", + "c": "350090", + "en": "Cambodia", + "l": "2", + "ca": 1, + "n": "柬埔寨" + }, + "420360040": { + "p": "420360", + "a": "2", + "c": "420360040", + "en": "Norman", + "l": "19", + "ca": 3, + "n": "诺曼城" + }, + "190040": { + "p": "190", + "a": "0", + "c": "190040", + "en": "Siping", + "l": "7", + "ca": 3, + "n": "四平" + }, + "400600": { + "p": "400", + "a": "2", + "c": "400600", + "en": "The Republic of South Sudan", + "l": "2", + "ca": 1, + "n": "南苏丹" + }, + "430070": { + "p": "360020", + "a": "2", + "c": "430070", + "en": "Ontario", + "l": "4", + "ca": 2, + "n": "安大略省" + }, + "110120": { + "p": "110", + "a": "0", + "c": "110120", + "en": "Qinzhou", + "l": "7", + "ca": 3, + "n": "钦州" + }, + "280060": { + "p": "280", + "a": "0", + "c": "280060", + "en": "Neijiang", + "l": "7", + "ca": 3, + "n": "内江" + }, + "100140": { + "p": "100", + "a": "0", + "c": "100140", + "en": "Linxia Prefecture", + "l": "8", + "ca": 3, + "n": "临夏" + }, + "430070010": { + "p": "430070", + "a": "2", + "c": "430070010", + "en": "Toronto", + "l": "19", + "ca": 3, + "n": "多伦多" + }, + "420350020": { + "p": "420350", + "a": "2", + "c": "420350020", + "en": "Cleveland", + "l": "19", + "ca": 3, + "n": "克利夫兰" + }, + "390310": { + "p": "390", + "a": "2", + "c": "390310", + "en": "Serbia", + "l": "2", + "ca": 1, + "n": "塞尔维亚" + }, + "400200": { + "p": "400", + "a": "2", + "c": "400200", + "en": "Chad", + "l": "2", + "ca": 1, + "n": "乍得" + }, + "400440": { + "p": "400", + "a": "2", + "c": "400440", + "en": "Zambia", + "l": "2", + "ca": 1, + "n": "赞比亚" + }, + "420210": { + "p": "360030", + "a": "2", + "c": "420210", + "en": "Massachusetts", + "l": "17", + "ca": 2, + "n": "麻省" + }, + "420450": { + "p": "360030", + "a": "2", + "c": "420450", + "en": "Virginia", + "l": "17", + "ca": 2, + "n": "弗吉尼亚州" + }, + "420290030": { + "p": "390220", + "a": "2", + "c": "420290030", + "en": "Portsmouth", + "l": "19", + "ca": 3, + "n": "朴次茅斯" + }, + "420200": { + "p": "360030", + "a": "2", + "c": "420200", + "en": "Maryland", + "l": "17", + "ca": 2, + "n": "马里兰州" + }, + "420450030": { + "p": "420450", + "a": "2", + "c": "420450030", + "en": "Virginia Beach", + "l": "19", + "ca": 3, + "n": "弗吉尼亚滩" + }, + "430020": { + "p": "360020", + "a": "2", + "c": "430020", + "en": "British Columbia", + "l": "4", + "ca": 2, + "n": "不列颠哥伦比亚省" + }, + "260060": { + "p": "260", + "a": "0", + "c": "260060", + "en": "Changzhi", + "l": "7", + "ca": 3, + "n": "长治" + }, + "260040": { + "p": "260", + "a": "0", + "c": "260040", + "en": "Linfen", + "l": "7", + "ca": 3, + "n": "临汾" + }, + "100150": { + "p": "100", + "a": "0", + "c": "100150", + "en": "Gannan", + "l": "8", + "ca": 3, + "n": "甘南" + }, + "420350030": { + "p": "420350", + "a": "2", + "c": "420350030", + "en": "Cincinnati", + "l": "19", + "ca": 3, + "n": "辛辛那提" + }, + "390320": { + "p": "390", + "a": "2", + "c": "390320", + "en": "North Macedonia", + "l": "2", + "ca": 1, + "n": "北马其顿" + }, + "420270010": { + "p": "420270", + "a": "2", + "c": "420270010", + "en": "Lincoln", + "l": "19", + "ca": 3, + "n": "林肯" + }, + "420220": { + "p": "360030", + "a": "2", + "c": "420220", + "en": "Michigan", + "l": "17", + "ca": 2, + "n": "密歇根州" + }, + "420460": { + "p": "360030", + "a": "2", + "c": "420460", + "en": "Washington", + "l": "17", + "ca": 2, + "n": "华盛顿州" + }, + "190020": { + "p": "190", + "a": "0", + "c": "190020", + "en": "Changchun", + "l": "7", + "ca": 3, + "n": "长春" + }, + "270090": { + "p": "270", + "a": "0", + "c": "270090", + "en": "Shangluo", + "l": "7", + "ca": 3, + "n": "商洛" + }, + "050190": { + "p": "050", + "a": "0", + "c": "050190", + "en": "Meizhou", + "l": "7", + "ca": 3, + "n": "梅州" + }, + "260050": { + "p": "260", + "a": "0", + "c": "260050", + "en": "Yuncheng", + "l": "7", + "ca": 3, + "n": "运城" + }, + "430010": { + "p": "360020", + "a": "2", + "c": "430010", + "en": "Alberta", + "l": "4", + "ca": 2, + "n": "阿尔伯塔省" + }, + "260030": { + "p": "260", + "a": "0", + "c": "260030", + "en": "Datong", + "l": "7", + "ca": 3, + "n": "大同" + }, + "090030": { + "p": "090", + "a": "0", + "c": "090030", + "en": "Quanzhou", + "l": "7", + "ca": 3, + "n": "泉州" + }, + "100120": { + "p": "100", + "a": "0", + "c": "100120", + "en": "Pingliang", + "l": "7", + "ca": 3, + "n": "平凉" + }, + "420470": { + "p": "360030", + "a": "2", + "c": "420470", + "en": "Rhode Island", + "l": "17", + "ca": 2, + "n": "罗得岛州" + }, + "400420": { + "p": "400", + "a": "2", + "c": "400420", + "en": "Niger", + "l": "2", + "ca": 1, + "n": "尼日尔" + }, + "420230": { + "p": "360030", + "a": "2", + "c": "420230", + "en": "Minnesota", + "l": "17", + "ca": 2, + "n": "明尼苏达州" + }, + "270080": { + "p": "270", + "a": "0", + "c": "270080", + "en": "Ankang", + "l": "7", + "ca": 3, + "n": "安康" + }, + "050180": { + "p": "050", + "a": "0", + "c": "050180", + "en": "Maoming", + "l": "7", + "ca": 3, + "n": "茂名" + }, + "430040": { + "p": "360020", + "a": "2", + "c": "430040", + "en": "Newfoundland and Labrador", + "l": "4", + "ca": 2, + "n": "纽芬兰省" + }, + "420450010": { + "p": "380020", + "a": "2", + "c": "420450010", + "en": "Richmond", + "l": "19", + "ca": 3, + "n": "里齐蒙得" + }, + "420280010": { + "p": "420280", + "a": "2", + "c": "420280010", + "en": "Carson City", + "l": "19", + "ca": 3, + "n": "卡森城" + }, + "260020": { + "p": "260", + "a": "0", + "c": "260020", + "en": "Taiyuan", + "l": "7", + "ca": 3, + "n": "太原" + }, + "090020": { + "p": "090", + "a": "0", + "c": "090020", + "en": "Fuzhou", + "l": "7", + "ca": 3, + "n": "福州" + }, + "100130": { + "p": "100", + "a": "0", + "c": "100130", + "en": "Qingyang", + "l": "7", + "ca": 3, + "n": "庆阳" + }, + "400410": { + "p": "400", + "a": "2", + "c": "400410", + "en": "Benin", + "l": "2", + "ca": 1, + "n": "贝宁" + }, + "390300": { + "p": "390", + "a": "2", + "c": "390300", + "en": "Bulgaria", + "l": "2", + "ca": 1, + "n": "保加利亚" + }, + "420480": { + "p": "360030", + "a": "2", + "c": "420480", + "en": "West Virginia", + "l": "17", + "ca": 2, + "n": "西佛吉尼亚州" + }, + "050160": { + "p": "050", + "a": "0", + "c": "050160", + "en": "Yangjiang", + "l": "7", + "ca": 3, + "n": "阳江" + }, + "420240": { + "p": "360030", + "a": "2", + "c": "420240", + "en": "Mississippi", + "l": "17", + "ca": 2, + "n": "密西西比州" + }, + "270070": { + "p": "270", + "a": "0", + "c": "270070", + "en": "Hanzhoung", + "l": "7", + "ca": 3, + "n": "汉中" + }, + "420340010": { + "p": "420340", + "a": "2", + "c": "420340010", + "en": "Bismark", + "l": "19", + "ca": 3, + "n": "俾斯麦" + }, + "050170": { + "p": "050", + "a": "0", + "c": "050170", + "en": "Shaoguan", + "l": "7", + "ca": 3, + "n": "韶关" + }, + "400400": { + "p": "400", + "a": "2", + "c": "400400", + "en": "Togo", + "l": "2", + "ca": 1, + "n": "多哥" + }, + "420450020": { + "p": "420450", + "a": "2", + "c": "420450020", + "en": "Norfolk", + "l": "19", + "ca": 3, + "n": "诺福克" + }, + "420280020": { + "p": "420280", + "a": "2", + "c": "420280020", + "en": "Las Vegas", + "l": "19", + "ca": 3, + "n": "拉斯维加斯" + }, + "430030": { + "p": "360020", + "a": "2", + "c": "430030", + "en": "Manitoba", + "l": "4", + "ca": 2, + "n": "曼尼托巴省" + }, + "390350": { + "p": "390", + "a": "2", + "c": "390350", + "en": "Slovenia", + "l": "2", + "ca": 1, + "n": "斯洛文尼亚" + }, + "390110": { + "p": "390", + "a": "2", + "c": "390110", + "en": "Belarus", + "l": "2", + "ca": 1, + "n": "白俄罗斯" + }, + "250050": { + "p": "250", + "a": "0", + "c": "250050", + "en": "Jining", + "l": "7", + "ca": 3, + "n": "济宁" + }, + "420420010": { + "p": "420420", + "a": "2", + "c": "420420010", + "en": "Austin", + "l": "19", + "ca": 3, + "n": "奧斯汀" + }, + "420400": { + "p": "360030", + "a": "2", + "c": "420400", + "en": "South Dakota", + "l": "17", + "ca": 2, + "n": "南达科他州" + }, + "420500010": { + "p": "420500", + "a": "2", + "c": "420500010", + "en": "Cheyenne", + "l": "19", + "ca": 3, + "n": "夏延" + }, + "170040": { + "p": "170", + "a": "0", + "c": "170040", + "en": "Xiangyang", + "l": "7", + "ca": 3, + "n": "襄阳" + }, + "390360": { + "p": "390", + "a": "2", + "c": "390360", + "en": "Croatia", + "l": "2", + "ca": 1, + "n": "克罗地亚" + }, + "390120": { + "p": "390", + "a": "2", + "c": "390120", + "en": "Russia", + "l": "2", + "ca": 1, + "n": "俄罗斯" + }, + "250060": { + "p": "250", + "a": "0", + "c": "250060", + "en": "Linyi", + "l": "7", + "ca": 3, + "n": "临沂" + }, + "420420020": { + "p": "420420", + "a": "2", + "c": "420420020", + "en": "Houston", + "l": "19", + "ca": 3, + "n": "休斯顿" + }, + "420410": { + "p": "360030", + "a": "2", + "c": "420410", + "en": "Tennessee", + "l": "17", + "ca": 2, + "n": "田纳西州" + }, + "400": { + "p": "", + "a": "2", + "c": "400", + "en": "Africa", + "l": "1", + "ca": 0, + "n": "非洲" + }, + "170030": { + "p": "170", + "a": "0", + "c": "170030", + "en": "Shiyan", + "l": "7", + "ca": 3, + "n": "十堰" + }, + "260090": { + "p": "260", + "a": "0", + "c": "260090", + "en": "Shuozhou", + "l": "7", + "ca": 3, + "n": "朔州" + }, + "420290010": { + "p": "390220", + "a": "2", + "c": "420290010", + "en": "Manchester", + "l": "19", + "ca": 3, + "n": "曼彻斯特" + }, + "420370010": { + "p": "420370", + "a": "2", + "c": "420370010", + "en": "Salem", + "l": "19", + "ca": 3, + "n": "塞伦" + }, + "170070": { + "p": "170", + "a": "0", + "c": "170070", + "en": "Jingmen", + "l": "7", + "ca": 3, + "n": "荆门" + }, + "390330": { + "p": "390", + "a": "2", + "c": "390330", + "en": "Albania", + "l": "2", + "ca": 1, + "n": "阿尔巴尼亚" + }, + "180070": { + "p": "180", + "a": "0", + "c": "180070", + "en": "Yiyang", + "l": "7", + "ca": 3, + "n": "益阳" + }, + "420430": { + "p": "360030", + "a": "2", + "c": "420430", + "en": "Utah", + "l": "17", + "ca": 2, + "n": "犹他州" + }, + "160020": { + "p": "160", + "a": "0", + "c": "160020", + "en": "Harbin", + "l": "7", + "ca": 3, + "n": "哈尔滨" + }, + "250020": { + "p": "250", + "a": "0", + "c": "250020", + "en": "Jinan", + "l": "7", + "ca": 3, + "n": "济南" + }, + "420420030": { + "p": "420420", + "a": "2", + "c": "420420030", + "en": "Dallas", + "l": "19", + "ca": 3, + "n": "达拉斯" + }, + "420500030": { + "p": "420090", + "a": "2", + "c": "420500030", + "en": "Laramie", + "l": "19", + "ca": 3, + "n": "拉阿密" + }, + "410": { + "p": "", + "a": "0", + "c": "410", + "en": "All Country", + "l": "", + "ca": -1, + "n": "全国" + }, + "420420": { + "p": "360030", + "a": "2", + "c": "420420", + "en": "Texas", + "l": "17", + "ca": 2, + "n": "德克萨斯州" + }, + "300180": { + "p": "300", + "a": "0", + "c": "300180", + "en": "Bayingolin", + "l": "8", + "ca": 3, + "n": "巴音郭楞" + }, + "180080": { + "p": "180", + "a": "0", + "c": "180080", + "en": "Chenzhou", + "l": "7", + "ca": 3, + "n": "郴州" + }, + "170060": { + "p": "170", + "a": "0", + "c": "170060", + "en": "Qianjiang", + "l": "7", + "ca": 3, + "n": "潜江" + }, + "260080": { + "p": "260", + "a": "0", + "c": "260080", + "en": "Jincheng", + "l": "7", + "ca": 3, + "n": "晋城" + }, + "420390060": { + "p": "420390", + "a": "2", + "c": "420390060", + "en": "Clemson", + "l": "19", + "ca": 3, + "n": "克伦孙" + }, + "390340": { + "p": "390", + "a": "2", + "c": "390340", + "en": "Greece", + "l": "2", + "ca": 1, + "n": "希腊" + }, + "390100": { + "p": "390", + "a": "2", + "c": "390100", + "en": "Lithuania", + "l": "2", + "ca": 1, + "n": "立陶宛" + }, + "250040": { + "p": "250", + "a": "0", + "c": "250040", + "en": "Dongying", + "l": "7", + "ca": 3, + "n": "东营" + }, + "420440": { + "p": "360030", + "a": "2", + "c": "420440", + "en": "Vermont", + "l": "17", + "ca": 2, + "n": "佛蒙特州" + }, + "420290020": { + "p": "420290", + "a": "2", + "c": "420290020", + "en": "Nashua", + "l": "19", + "ca": 3, + "n": "南雪" + }, + "420": { + "p": "", + "a": "2", + "c": "420", + "en": "All Overseas", + "l": "", + "ca": -1, + "n": "全海外" + }, + "420500020": { + "p": "420500", + "a": "2", + "c": "420500020", + "en": "Casper", + "l": "19", + "ca": 3, + "n": "卡斯柏" + }, + "250030": { + "p": "250", + "a": "0", + "c": "250030", + "en": "Dezhou", + "l": "7", + "ca": 3, + "n": "德州" + }, + "300190": { + "p": "300", + "a": "0", + "c": "300190", + "en": "Bortala", + "l": "8", + "ca": 3, + "n": "博尔塔拉" + }, + "180090": { + "p": "180", + "a": "0", + "c": "180090", + "en": "Yueyang", + "l": "7", + "ca": 3, + "n": "岳阳" + }, + "170050": { + "p": "170", + "a": "0", + "c": "170050", + "en": "Yichang", + "l": "7", + "ca": 3, + "n": "宜昌" + }, + "260070": { + "p": "260", + "a": "0", + "c": "260070", + "en": "Yangquan", + "l": "7", + "ca": 3, + "n": "阳泉" + }, + "390390": { + "p": "390", + "a": "2", + "c": "390390", + "en": "Vatican", + "l": "2", + "ca": 1, + "n": "梵蒂冈" + }, + "390150": { + "p": "390", + "a": "2", + "c": "390150", + "en": "Poland", + "l": "2", + "ca": 1, + "n": "波兰" + }, + "420370030": { + "p": "420370", + "a": "2", + "c": "420370030", + "en": "Eugene", + "l": "19", + "ca": 3, + "n": "尤金" + }, + "420430020": { + "p": "420430", + "a": "2", + "c": "420430020", + "en": "Ogden", + "l": "19", + "ca": 3, + "n": "奥格登" + }, + "municipality": { + "en": "MUNICIPALITY", + "n": "直辖市" + }, + "250090": { + "p": "250", + "a": "0", + "c": "250090", + "en": "Tai'an", + "l": "7", + "ca": 3, + "n": "泰安" + }, + "180050": { + "p": "180", + "a": "0", + "c": "180050", + "en": "Changde", + "l": "7", + "ca": 3, + "n": "常德" + }, + "380100": { + "p": "380", + "a": "2", + "c": "380100", + "en": "Nauru", + "l": "2", + "ca": 1, + "n": "瑙鲁" + }, + "420090020": { + "p": "420090", + "a": "2", + "c": "420090020", + "en": "Tampa", + "l": "19", + "ca": 3, + "n": "坦帕" + }, + "240050": { + "p": "240", + "a": "0", + "c": "240050", + "en": "Haibei", + "l": "8", + "ca": 3, + "n": "海北" + }, + "300160": { + "p": "300", + "a": "0", + "c": "300160", + "en": "Hotan Prefecture", + "l": "10", + "ca": 3, + "n": "和田" + }, + "390160": { + "p": "390", + "a": "2", + "c": "390160", + "en": "Czechoslovakia", + "l": "2", + "ca": 1, + "n": "捷克" + }, + "420370020": { + "p": "420370", + "a": "2", + "c": "420370020", + "en": "Portland", + "l": "19", + "ca": 3, + "n": "波特兰" + }, + "180060": { + "p": "180", + "a": "0", + "c": "180060", + "en": "Hengyang", + "l": "7", + "ca": 3, + "n": "衡阳" + }, + "420010050": { + "p": "420010", + "a": "2", + "c": "420010050", + "en": "Mobile", + "l": "19", + "ca": 3, + "n": "莫比尔港" + }, + "200": { + "p": "350490", + "a": "0", + "c": "200", + "en": "Jiangxi", + "l": "4", + "ca": 2, + "n": "江西" + }, + "420090010": { + "p": "420090", + "a": "2", + "c": "420090010", + "en": "Tallahassee", + "l": "19", + "ca": 3, + "n": "塔拉赫西" + }, + "300170": { + "p": "300", + "a": "0", + "c": "300170", + "en": "Kizilsu", + "l": "8", + "ca": 3, + "n": "克州" + }, + "240060": { + "p": "240", + "a": "0", + "c": "240060", + "en": "Huangnan", + "l": "8", + "ca": 3, + "n": "黄南" + }, + "420430010": { + "p": "420430", + "a": "2", + "c": "420430010", + "en": "Salt Lake City", + "l": "19", + "ca": 3, + "n": "盐湖城" + }, + "390370": { + "p": "390", + "a": "2", + "c": "390370", + "en": "Bosnia and Herzegovina", + "l": "2", + "ca": 1, + "n": "波黑" + }, + "390130": { + "p": "390", + "a": "2", + "c": "390130", + "en": "Ukraine", + "l": "2", + "ca": 1, + "n": "乌克兰" + }, + "240020": { + "p": "240", + "a": "0", + "c": "240020", + "en": "Xining", + "l": "7", + "ca": 3, + "n": "西宁" + }, + "230020": { + "p": "230", + "a": "0", + "c": "230020", + "en": "Yinchuan", + "l": "7", + "ca": 3, + "n": "银川" + }, + "180030": { + "p": "180", + "a": "0", + "c": "180030", + "en": "Xiangtan", + "l": "7", + "ca": 3, + "n": "湘潭" + }, + "250070": { + "p": "250", + "a": "0", + "c": "250070", + "en": "Qingdao", + "l": "7", + "ca": 3, + "n": "青岛" + }, + "210": { + "p": "350490", + "a": "0", + "c": "210", + "en": "Liaoning", + "l": "4", + "ca": 2, + "n": "辽宁" + }, + "380120": { + "p": "380", + "a": "2", + "c": "380120", + "en": "Tuvalu", + "l": "2", + "ca": 1, + "n": "图瓦卢" + }, + "190070": { + "p": "190", + "a": "0", + "c": "190070", + "en": "Baishan", + "l": "7", + "ca": 3, + "n": "白山" + }, + "420360010": { + "p": "420360", + "a": "2", + "c": "420360010", + "en": "Oklahoma City", + "l": "19", + "ca": 3, + "n": "俄克拉何马城" + }, + "300140": { + "p": "300", + "a": "0", + "c": "300140", + "en": "Turpan", + "l": "7", + "ca": 3, + "n": "吐鲁番" + }, + "420090040": { + "p": "420090", + "a": "2", + "c": "420090040", + "en": "Miami", + "l": "19", + "ca": 3, + "n": "迈阿密" + }, + "240030": { + "p": "240", + "a": "0", + "c": "240030", + "en": "Haidong", + "l": "7", + "ca": 3, + "n": "海东" + }, + "170020": { + "p": "170", + "a": "0", + "c": "170020", + "en": "Wuhan", + "l": "7", + "ca": 3, + "n": "武汉" + }, + "390380": { + "p": "390", + "a": "2", + "c": "390380", + "en": "Italy", + "l": "2", + "ca": 1, + "n": "意大利" + }, + "390140": { + "p": "390", + "a": "2", + "c": "390140", + "en": "Moldova", + "l": "2", + "ca": 1, + "n": "摩尔多瓦" + }, + "420370040": { + "p": "420370", + "a": "2", + "c": "420370040", + "en": "Corvallis", + "l": "19", + "ca": 3, + "n": "科瓦利" + }, + "420430030": { + "p": "420430", + "a": "2", + "c": "420430030", + "en": "Provo", + "l": "19", + "ca": 3, + "n": "普罗沃" + }, + "190090": { + "p": "190", + "a": "0", + "c": "190090", + "en": "Baicheng", + "l": "7", + "ca": 3, + "n": "白城" + }, + "250080": { + "p": "250", + "a": "0", + "c": "250080", + "en": "Rizhao", + "l": "7", + "ca": 3, + "n": "日照" + }, + "180040": { + "p": "180", + "a": "0", + "c": "180040", + "en": "Zhuzhou", + "l": "7", + "ca": 3, + "n": "株洲" + }, + "220": { + "p": "350490", + "a": "0", + "c": "220", + "en": "Inner Mongolia", + "l": "5", + "ca": 2, + "n": "内蒙古" + }, + "420090030": { + "p": "420090", + "a": "2", + "c": "420090030", + "en": "Jacksonville", + "l": "19", + "ca": 3, + "n": "杰克逊维尔" + }, + "380110": { + "p": "380", + "a": "2", + "c": "380110", + "en": "Kiribati", + "l": "2", + "ca": 1, + "n": "基里巴斯" + }, + "190080": { + "p": "190", + "a": "0", + "c": "190080", + "en": "Songyuan", + "l": "7", + "ca": 3, + "n": "松原" + }, + "240040": { + "p": "240", + "a": "0", + "c": "240040", + "en": "Haixi", + "l": "8", + "ca": 3, + "n": "海西" + }, + "300150": { + "p": "300", + "a": "0", + "c": "300150", + "en": "Tacheng Prefecture", + "l": "10", + "ca": 3, + "n": "塔城" + }, + "150160": { + "p": "150", + "a": "0", + "c": "150160", + "en": "Zhumadian", + "l": "7", + "ca": 3, + "n": "驻马店" + }, + "070040": { + "p": "070", + "a": "0", + "c": "070040", + "en": "Wenzhou", + "l": "7", + "ca": 3, + "n": "温州" + }, + "310040": { + "p": "310", + "a": "0", + "c": "310040", + "en": "Lijiang", + "l": "7", + "ca": 3, + "n": "丽江" + }, + "230040": { + "p": "230", + "a": "0", + "c": "230040", + "en": "Wuzhong", + "l": "7", + "ca": 3, + "n": "吴忠" + }, + "080090": { + "p": "080", + "a": "0", + "c": "080090", + "en": "Tongling", + "l": "7", + "ca": 3, + "n": "铜陵" + }, + "230": { + "p": "350490", + "a": "0", + "c": "230", + "en": "Ningxia", + "l": "5", + "ca": 2, + "n": "宁夏" + }, + "420160010": { + "p": "420160", + "a": "2", + "c": "420160010", + "en": "Topeka", + "l": "19", + "ca": 3, + "n": "托皮卡" + }, + "420010020": { + "p": "420010", + "a": "2", + "c": "420010020", + "en": "Montgomery", + "l": "19", + "ca": 3, + "n": "蒙哥马利" + }, + "380020": { + "p": "380", + "a": "2", + "c": "380020", + "en": "Australia", + "l": "2", + "ca": 1, + "n": "澳大利亚" + }, + "140110": { + "p": "140", + "a": "0", + "c": "140110", + "en": "Cangzhou", + "l": "7", + "ca": 3, + "n": "沧州" + }, + "080080": { + "p": "080", + "a": "0", + "c": "080080", + "en": "Huaibei", + "l": "7", + "ca": 3, + "n": "淮北" + }, + "420330050": { + "p": "420330", + "a": "2", + "c": "420330050", + "en": "Asheville", + "l": "19", + "ca": 3, + "n": "阿什维尔" + }, + "420080020": { + "p": "420080", + "a": "2", + "c": "420080020", + "en": "Wilmington", + "l": "19", + "ca": 3, + "n": "维明顿" + }, + "300240": { + "p": "300", + "a": "0", + "c": "300240", + "en": "Kunyu", + "l": "7", + "ca": 3, + "n": "昆玉" + }, + "420320010": { + "p": "420320", + "a": "2", + "c": "420320010", + "en": "Albany", + "l": "19", + "ca": 3, + "n": "奥尔巴尼" + }, + "240090": { + "p": "240", + "a": "0", + "c": "240090", + "en": "Yushu Prefecture", + "l": "8", + "ca": 3, + "n": "玉树" + }, + "390080": { + "p": "390", + "a": "2", + "c": "390080", + "en": "Estonia", + "l": "2", + "ca": 1, + "n": "爱沙尼亚" + }, + "150150": { + "p": "150", + "a": "0", + "c": "150150", + "en": "Zhoukou", + "l": "7", + "ca": 3, + "n": "周口" + }, + "150170": { + "p": "150", + "a": "0", + "c": "150170", + "en": "Nanyang", + "l": "7", + "ca": 3, + "n": "南阳" + }, + "400190": { + "p": "400", + "a": "2", + "c": "400190", + "en": "Seychelles", + "l": "2", + "ca": 1, + "n": "塞舌尔" + }, + "070050": { + "p": "070", + "a": "0", + "c": "070050", + "en": "Shaoxing", + "l": "7", + "ca": 3, + "n": "绍兴" + }, + "420480030": { + "p": "420480", + "a": "2", + "c": "420480030", + "en": "Morgantown", + "l": "19", + "ca": 3, + "n": "摩根敦" + }, + "230030": { + "p": "230", + "a": "0", + "c": "230030", + "en": "Shizuishan", + "l": "7", + "ca": 3, + "n": "石嘴山" + }, + "310050": { + "p": "310", + "a": "0", + "c": "310050", + "en": "Yuxi", + "l": "7", + "ca": 3, + "n": "玉溪" + }, + "420010010": { + "p": "420010", + "a": "2", + "c": "420010010", + "en": "Birmingham", + "l": "19", + "ca": 3, + "n": "伯明翰" + }, + "240": { + "p": "350490", + "a": "0", + "c": "240", + "en": "Qinghai", + "l": "4", + "ca": 2, + "n": "青海" + }, + "140120": { + "p": "140", + "a": "0", + "c": "140120", + "en": "Hengshui", + "l": "7", + "ca": 3, + "n": "衡水" + }, + "420330040": { + "p": "420330", + "a": "2", + "c": "420330040", + "en": "Chapel Hill", + "l": "19", + "ca": 3, + "n": "查伯山" + }, + "420090050": { + "p": "420090", + "a": "2", + "c": "420090050", + "en": "Gainesville", + "l": "19", + "ca": 3, + "n": "盖恩斯维尔" + }, + "300250": { + "p": "300", + "a": "0", + "c": "300250", + "en": "Beitun", + "l": "7", + "ca": 3, + "n": "北屯" + }, + "420080010": { + "p": "420080", + "a": "2", + "c": "420080010", + "en": "Dover", + "l": "19", + "ca": 3, + "n": "多佛" + }, + "390090": { + "p": "390", + "a": "2", + "c": "390090", + "en": "Latvia", + "l": "2", + "ca": 1, + "n": "拉脱维亚" + }, + "420480020": { + "p": "420480", + "a": "2", + "c": "420480020", + "en": "Huntington", + "l": "19", + "ca": 3, + "n": "亨丁顿" + }, + "390050": { + "p": "390", + "a": "2", + "c": "390050", + "en": "Iceland", + "l": "2", + "ca": 1, + "n": "冰岛" + }, + "400180": { + "p": "400", + "a": "2", + "c": "400180", + "en": "Burundi", + "l": "2", + "ca": 1, + "n": "布隆迪" + }, + "150180": { + "p": "150", + "a": "0", + "c": "150180", + "en": "Xinyang", + "l": "7", + "ca": 3, + "n": "信阳" + }, + "070060": { + "p": "070", + "a": "0", + "c": "070060", + "en": "Jinhua", + "l": "7", + "ca": 3, + "n": "金华" + }, + "310020": { + "p": "310", + "a": "0", + "c": "310020", + "en": "Kunming", + "l": "7", + "ca": 3, + "n": "昆明" + }, + "230060": { + "p": "230", + "a": "0", + "c": "230060", + "en": "Zhongwei", + "l": "7", + "ca": 3, + "n": "中卫" + }, + "250": { + "p": "350490", + "a": "0", + "c": "250", + "en": "Shandong", + "l": "4", + "ca": 2, + "n": "山东" + }, + "420160030": { + "p": "420160", + "a": "2", + "c": "420160030", + "en": "Kansas City", + "l": "19", + "ca": 3, + "n": "堪萨斯城" + }, + "010": { + "p": "350490", + "a": "0", + "c": "010", + "en": "Beijing", + "l": "3", + "ca": 2, + "n": "北京" + }, + "420010040": { + "p": "420010", + "a": "2", + "c": "420010040", + "en": "Tuscaloosa", + "l": "19", + "ca": 3, + "n": "塔斯卡卢萨" + }, + "380040": { + "p": "380", + "a": "2", + "c": "380040", + "en": "The Independent State of Papua New Guinea", + "l": "2", + "ca": 1, + "n": "巴布亚新几内亚" + }, + "220020": { + "p": "220", + "a": "0", + "c": "220020", + "en": "Hohhot", + "l": "7", + "ca": 3, + "n": "呼和浩特" + }, + "420330030": { + "p": "420330", + "a": "2", + "c": "420330030", + "en": "Greensboro", + "l": "19", + "ca": 3, + "n": "格林斯堡" + }, + "240070": { + "p": "240", + "a": "0", + "c": "240070", + "en": "Hainan", + "l": "8", + "ca": 3, + "n": "海南州" + }, + "300220": { + "p": "300", + "a": "0", + "c": "300220", + "en": "Shuanghe", + "l": "7", + "ca": 3, + "n": "双河" + }, + "390060": { + "p": "390", + "a": "2", + "c": "390060", + "en": "Denmark", + "l": "2", + "ca": 1, + "n": "丹麦" + }, + "400170": { + "p": "400", + "a": "2", + "c": "400170", + "en": "Rwanda", + "l": "2", + "ca": 1, + "n": "卢旺达" + }, + "420480010": { + "p": "420480", + "a": "2", + "c": "420480010", + "en": "Charleston", + "l": "19", + "ca": 3, + "n": "查理斯敦" + }, + "130100": { + "p": "130", + "a": "0", + "c": "130100", + "en": "Dongfang", + "l": "7", + "ca": 3, + "n": "东方" + }, + "150190": { + "p": "150", + "a": "0", + "c": "150190", + "en": "Jiyuan", + "l": "7", + "ca": 3, + "n": "济源" + }, + "070070": { + "p": "070", + "a": "0", + "c": "070070", + "en": "Taizhou", + "l": "7", + "ca": 3, + "n": "台州" + }, + "420330020": { + "p": "420330", + "a": "2", + "c": "420330020", + "en": "Charlotte", + "l": "19", + "ca": 3, + "n": "夏洛特" + }, + "310030": { + "p": "310", + "a": "0", + "c": "310030", + "en": "Dali Prefecture", + "l": "8", + "ca": 3, + "n": "大理州" + }, + "230050": { + "p": "230", + "a": "0", + "c": "230050", + "en": "Guyuan", + "l": "7", + "ca": 3, + "n": "固原" + }, + "260": { + "p": "350490", + "a": "0", + "c": "260", + "en": "Shanxi", + "l": "4", + "ca": 2, + "n": "山西" + }, + "420160020": { + "p": "420160", + "a": "2", + "c": "420160020", + "en": "Wichita", + "l": "19", + "ca": 3, + "n": "威奇托" + }, + "350400": { + "p": "350", + "a": "2", + "c": "350400", + "en": "Kuwait", + "l": "2", + "ca": 1, + "n": "科威特" + }, + "020": { + "p": "350490", + "a": "0", + "c": "020", + "en": "Shanghai", + "l": "3", + "ca": 2, + "n": "上海" + }, + "420010030": { + "p": "420010", + "a": "2", + "c": "420010030", + "en": "Huntsville", + "l": "19", + "ca": 3, + "n": "亨次维尔" + }, + "380030": { + "p": "380", + "a": "2", + "c": "380030", + "en": "New Zealand", + "l": "2", + "ca": 1, + "n": "新西兰" + }, + "360200": { + "p": "360", + "a": "2", + "c": "360200", + "en": "Dominica", + "l": "2", + "ca": 1, + "n": "多米尼克" + }, + "420080030": { + "p": "420080", + "a": "2", + "c": "420080030", + "en": "Newark", + "l": "19", + "ca": 3, + "n": "纽华克" + }, + "070080": { + "p": "070", + "a": "0", + "c": "070080", + "en": "Huzhou", + "l": "7", + "ca": 3, + "n": "湖州" + }, + "300230": { + "p": "300", + "a": "0", + "c": "300230", + "en": "Kokdala", + "l": "7", + "ca": 3, + "n": "可克达拉" + }, + "240080": { + "p": "240", + "a": "0", + "c": "240080", + "en": "Guoluo", + "l": "8", + "ca": 3, + "n": "果洛" + }, + "370020": { + "p": "370", + "a": "2", + "c": "370020", + "en": "Columbia", + "l": "2", + "ca": 1, + "n": "哥伦比亚" + }, + "420330010": { + "p": "420330", + "a": "2", + "c": "420330010", + "en": "Raleigh", + "l": "19", + "ca": 3, + "n": "洛利" + }, + "420070010": { + "p": "420070", + "a": "2", + "c": "420070010", + "en": "Hartford", + "l": "19", + "ca": 3, + "n": "哈特福" + }, + "270": { + "p": "350490", + "a": "0", + "c": "270", + "en": "Shaanxi", + "l": "4", + "ca": 2, + "n": "陕西" + }, + "380070": { + "p": "380", + "a": "2", + "c": "380070", + "en": "Micronesia", + "l": "2", + "ca": 1, + "n": "密克罗尼西亚" + }, + "350410": { + "p": "350", + "a": "2", + "c": "350410", + "en": "United Arab Emirates", + "l": "2", + "ca": 1, + "n": "阿联酋" + }, + "030": { + "p": "350490", + "a": "0", + "c": "030", + "en": "Tianjin", + "l": "3", + "ca": 2, + "n": "天津" + }, + "420170020": { + "p": "420170", + "a": "2", + "c": "420170020", + "en": "Lexington", + "l": "19", + "ca": 3, + "n": "列克星敦" + }, + "360210": { + "p": "360", + "a": "2", + "c": "360210", + "en": "Saint Lucia", + "l": "2", + "ca": 1, + "n": "圣卢西亚" + }, + "220040": { + "p": "220", + "a": "0", + "c": "220040", + "en": "Chifeng", + "l": "7", + "ca": 3, + "n": "赤峰" + }, + "070090": { + "p": "070", + "a": "0", + "c": "070090", + "en": "Jiaxing", + "l": "7", + "ca": 3, + "n": "嘉兴" + }, + "150110": { + "p": "150", + "a": "0", + "c": "150110", + "en": "Xuchang", + "l": "7", + "ca": 3, + "n": "许昌" + }, + "420020030": { + "p": "420020", + "a": "2", + "c": "420020030", + "en": "Fairbanks", + "l": "19", + "ca": 3, + "n": "费尔班克斯" + }, + "280": { + "p": "350490", + "a": "0", + "c": "280", + "en": "Sichuan", + "l": "4", + "ca": 2, + "n": "四川" + }, + "350420": { + "p": "350", + "a": "2", + "c": "350420", + "en": "Oman", + "l": "2", + "ca": 1, + "n": "阿曼" + }, + "040": { + "p": "350490", + "a": "0", + "c": "040", + "en": "Chongqing", + "l": "3", + "ca": 2, + "n": "重庆" + }, + "380060": { + "p": "380", + "a": "2", + "c": "380060", + "en": "Vanuatu", + "l": "2", + "ca": 1, + "n": "瓦努阿图" + }, + "380050": { + "p": "380", + "a": "2", + "c": "380050", + "en": "Solomon Islands", + "l": "2", + "ca": 1, + "n": "所罗门群岛" + }, + "220030": { + "p": "220", + "a": "0", + "c": "220030", + "en": "Baotou", + "l": "7", + "ca": 3, + "n": "包头" + }, + "360220": { + "p": "360", + "a": "2", + "c": "360220", + "en": "Saint Vincent and the Grenadines", + "l": "2", + "ca": 1, + "n": "圣文森特和格林纳丁斯" + }, + "district": { + "en": "", + "n": "有下属区县的城市" + }, + "150120": { + "p": "150", + "a": "0", + "c": "150120", + "en": "Luohe", + "l": "7", + "ca": 3, + "n": "漯河" + }, + "370040": { + "p": "370", + "a": "2", + "c": "370040", + "en": "Guyana", + "l": "2", + "ca": 1, + "n": "圭亚那" + }, + "290": { + "p": "350490", + "a": "0", + "c": "290", + "en": "Tibet", + "l": "5", + "ca": 2, + "n": "西藏" + }, + "430100010": { + "p": "430100", + "a": "2", + "c": "430100010", + "en": "Saskatoon", + "l": "19", + "ca": 3, + "n": "萨斯卡通" + }, + "350430": { + "p": "350", + "a": "2", + "c": "350430", + "en": "Yemen", + "l": "2", + "ca": 1, + "n": "也门" + }, + "050": { + "p": "350490", + "a": "0", + "c": "050", + "en": "Guangdong", + "l": "4", + "ca": 2, + "n": "广东" + }, + "380090": { + "p": "380", + "a": "2", + "c": "380090", + "en": "The Republic of Palau", + "l": "2", + "ca": 1, + "n": "帕劳" + }, + "220060": { + "p": "220", + "a": "0", + "c": "220060", + "en": "Wuhai", + "l": "7", + "ca": 3, + "n": "乌海" + }, + "360230": { + "p": "360", + "a": "2", + "c": "360230", + "en": "Grenada", + "l": "2", + "ca": 1, + "n": "格林纳达" + }, + "150130": { + "p": "150", + "a": "0", + "c": "150130", + "en": "Sanmenxia", + "l": "7", + "ca": 3, + "n": "三门峡" + }, + "420020010": { + "p": "420020", + "a": "2", + "c": "420020010", + "en": "Juneau", + "l": "19", + "ca": 3, + "n": "朱诺" + }, + "370030": { + "p": "370", + "a": "2", + "c": "370030", + "en": "Venezuela", + "l": "2", + "ca": 1, + "n": "委内瑞拉" + }, + "210020": { + "p": "210", + "a": "0", + "c": "210020", + "en": "Shenyang", + "l": "7", + "ca": 3, + "n": "沈阳" + }, + "430100020": { + "p": "430100", + "a": "2", + "c": "430100020", + "en": "Regina", + "l": "19", + "ca": 3, + "n": "里贾纳" + }, + "350440": { + "p": "350", + "a": "2", + "c": "350440", + "en": "Georgia", + "l": "2", + "ca": 1, + "n": "格鲁吉亚" + }, + "060": { + "p": "350490", + "a": "0", + "c": "060", + "en": "Jiangsu", + "l": "4", + "ca": 2, + "n": "江苏" + }, + "350200": { + "p": "350", + "a": "2", + "c": "350200", + "en": "India", + "l": "2", + "ca": 1, + "n": "印度" + }, + "380080": { + "p": "380", + "a": "2", + "c": "380080", + "en": "Marshall Islands", + "l": "2", + "ca": 1, + "n": "马绍尔群岛" + }, + "420170010": { + "p": "420170", + "a": "2", + "c": "420170010", + "en": "Louisville", + "l": "19", + "ca": 3, + "n": "路易斯维尔" + }, + "220050": { + "p": "220", + "a": "0", + "c": "220050", + "en": "Ordos", + "l": "7", + "ca": 3, + "n": "鄂尔多斯" + }, + "140100": { + "p": "140", + "a": "0", + "c": "140100", + "en": "Xingtai", + "l": "7", + "ca": 3, + "n": "邢台" + }, + "360240": { + "p": "360", + "a": "2", + "c": "360240", + "en": "Barbados", + "l": "2", + "ca": 1, + "n": "巴巴多斯" + }, + "420020020": { + "p": "420020", + "a": "2", + "c": "420020020", + "en": "Anchorage", + "l": "19", + "ca": 3, + "n": "安克拉奇" + }, + "150140": { + "p": "150", + "a": "0", + "c": "150140", + "en": "Hebi", + "l": "7", + "ca": 3, + "n": "鹤壁" + }, + "060080": { + "p": "060", + "a": "0", + "c": "060080", + "en": "Suzhou", + "l": "7", + "ca": 3, + "n": "苏州" + }, + "090050": { + "p": "090", + "a": "0", + "c": "090050", + "en": "Zhangzhou", + "l": "7", + "ca": 3, + "n": "漳州" + }, + "210030": { + "p": "210", + "a": "0", + "c": "210030", + "en": "Anshan", + "l": "7", + "ca": 3, + "n": "鞍山" + }, + "050030": { + "p": "050", + "a": "0", + "c": "050030", + "en": "Chaozhou", + "l": "7", + "ca": 3, + "n": "潮州" + }, + "420030030": { + "p": "420030", + "a": "2", + "c": "420030030", + "en": "Mesa", + "l": "19", + "ca": 3, + "n": "梅萨" + }, + "350450": { + "p": "350", + "a": "2", + "c": "350450", + "en": "Armenia", + "l": "2", + "ca": 1, + "n": "亚美尼亚" + }, + "070": { + "p": "350490", + "a": "0", + "c": "070", + "en": "Zhejiang", + "l": "4", + "ca": 2, + "n": "浙江" + }, + "350210": { + "p": "350", + "a": "2", + "c": "350210", + "en": "Pakistan", + "l": "2", + "ca": 1, + "n": "巴基斯坦" + }, + "400120": { + "p": "400", + "a": "2", + "c": "400120", + "en": "Somalia", + "l": "2", + "ca": 1, + "n": "索马里" + }, + "400360": { + "p": "400", + "a": "2", + "c": "400360", + "en": "Sierra Leone", + "l": "2", + "ca": 1, + "n": "塞拉利昂" + }, + "220080": { + "p": "220", + "a": "0", + "c": "220080", + "en": "Hulunbuir", + "l": "7", + "ca": 3, + "n": "呼伦贝尔" + }, + "420060010": { + "p": "420060", + "a": "2", + "c": "420060010", + "en": "Denver", + "l": "19", + "ca": 3, + "n": "丹佛" + }, + "420490010": { + "p": "420490", + "a": "2", + "c": "420490010", + "en": "Madison", + "l": "19", + "ca": 3, + "n": "麦迪逊" + }, + "360250": { + "p": "360", + "a": "2", + "c": "360250", + "en": "Trinidad and Tobago", + "l": "2", + "ca": 1, + "n": "特多" + }, + "120100": { + "p": "120", + "a": "0", + "c": "120100", + "en": "Qiannan", + "l": "8", + "ca": 3, + "n": "黔南" + }, + "420340020": { + "p": "420340", + "a": "2", + "c": "420340020", + "en": "Fargo", + "l": "19", + "ca": 3, + "n": "法戈" + }, + "370070": { + "p": "370", + "a": "2", + "c": "370070", + "en": "Ecuador", + "l": "2", + "ca": 1, + "n": "厄瓜多尔" + }, + "280130": { + "p": "280", + "a": "0", + "c": "280130", + "en": "Nanchong", + "l": "7", + "ca": 3, + "n": "南充" + }, + "130160": { + "p": "130", + "a": "0", + "c": "130160", + "en": "Qiongzhong Co.", + "l": "7", + "ca": 3, + "n": "琼中县" + }, + "210040": { + "p": "210", + "a": "0", + "c": "210040", + "en": "Dalian", + "l": "7", + "ca": 3, + "n": "大连" + }, + "420300010": { + "p": "420300", + "a": "2", + "c": "420300010", + "en": "Newark", + "l": "19", + "ca": 3, + "n": "纽瓦克" + }, + "090040": { + "p": "090", + "a": "0", + "c": "090040", + "en": "Xiamen", + "l": "7", + "ca": 3, + "n": "厦门" + }, + "060070": { + "p": "060", + "a": "0", + "c": "060070", + "en": "Nantong", + "l": "7", + "ca": 3, + "n": "南通" + }, + "350460": { + "p": "350", + "a": "2", + "c": "350460", + "en": "Azerbaijan", + "l": "2", + "ca": 1, + "n": "阿塞拜疆" + }, + "080": { + "p": "350490", + "a": "0", + "c": "080", + "en": "Anhui", + "l": "4", + "ca": 2, + "n": "安徽" + }, + "050020": { + "p": "050", + "a": "0", + "c": "050020", + "en": "Guangzhou", + "l": "7", + "ca": 3, + "n": "广州" + }, + "350220": { + "p": "350", + "a": "2", + "c": "350220", + "en": "Sri Lanka", + "l": "2", + "ca": 1, + "n": "斯里兰卡" + }, + "400110": { + "p": "400", + "a": "2", + "c": "400110", + "en": "Eritrea", + "l": "2", + "ca": 1, + "n": "厄立特里亚" + }, + "400350": { + "p": "400", + "a": "2", + "c": "400350", + "en": "Cape Verde", + "l": "2", + "ca": 1, + "n": "佛得角" + }, + "400590": { + "p": "400", + "a": "2", + "c": "400590", + "en": "Nigeria", + "l": "2", + "ca": 1, + "n": "尼日利亚" + }, + "220070": { + "p": "220", + "a": "0", + "c": "220070", + "en": "Tongliao", + "l": "7", + "ca": 3, + "n": "通辽" + }, + "420060020": { + "p": "420060", + "a": "2", + "c": "420060020", + "en": "Boulder", + "l": "19", + "ca": 3, + "n": "波尔德" + }, + "360020": { + "p": "360", + "a": "2", + "c": "360020", + "en": "Canada", + "l": "2", + "ca": 1, + "n": "加拿大" + }, + "350470": { + "p": "350", + "a": "2", + "c": "350470", + "en": "Turkey", + "l": "2", + "ca": 1, + "n": "土耳其" + }, + "420490020": { + "p": "420490", + "a": "2", + "c": "420490020", + "en": "Milwaukee", + "l": "19", + "ca": 3, + "n": "密尔沃基" + }, + "060060": { + "p": "060", + "a": "0", + "c": "060060", + "en": "Lianyungang", + "l": "7", + "ca": 3, + "n": "连云港" + }, + "370060": { + "p": "370", + "a": "2", + "c": "370060", + "en": "Surinam", + "l": "2", + "ca": 1, + "n": "苏里南" + }, + "280120": { + "p": "280", + "a": "0", + "c": "280120", + "en": "Suining", + "l": "7", + "ca": 3, + "n": "遂宁" + }, + "130170": { + "p": "130", + "a": "0", + "c": "130170", + "en": "Baoting Co.", + "l": "7", + "ca": 3, + "n": "保亭县" + }, + "090070": { + "p": "090", + "a": "0", + "c": "090070", + "en": "Sanming", + "l": "7", + "ca": 3, + "n": "三明" + }, + "210050": { + "p": "210", + "a": "0", + "c": "210050", + "en": "Huludao", + "l": "7", + "ca": 3, + "n": "葫芦岛" + }, + "420460010": { + "p": "420460", + "a": "2", + "c": "420460010", + "en": "Olympia", + "l": "19", + "ca": 3, + "n": "奥林匹亚" + }, + "090": { + "p": "350490", + "a": "0", + "c": "090", + "en": "Fujian", + "l": "4", + "ca": 2, + "n": "福建" + }, + "350230": { + "p": "350", + "a": "2", + "c": "350230", + "en": "Maldives", + "l": "2", + "ca": 1, + "n": "马尔代夫" + }, + "360040": { + "p": "360", + "a": "2", + "c": "360040", + "en": "Mexico", + "l": "2", + "ca": 1, + "n": "墨西哥" + }, + "400100": { + "p": "400", + "a": "2", + "c": "400100", + "en": "Ethiopia", + "l": "2", + "ca": 1, + "n": "埃塞俄比亚" + }, + "400340": { + "p": "400", + "a": "2", + "c": "400340", + "en": "Guinea-Bissau", + "l": "2", + "ca": 1, + "n": "几内亚比绍" + }, + "420030010": { + "p": "420030", + "a": "2", + "c": "420030010", + "en": "Phoenix", + "l": "19", + "ca": 3, + "n": "菲尼克斯" + }, + "420060030": { + "p": "420060", + "a": "2", + "c": "420060030", + "en": "Colorado Springs", + "l": "19", + "ca": 3, + "n": "科罗拉多斯普林斯" + }, + "080020": { + "p": "080", + "a": "0", + "c": "080020", + "en": "Hefei", + "l": "7", + "ca": 3, + "n": "合肥" + }, + "420310020": { + "p": "420310", + "a": "2", + "c": "420310020", + "en": "Albuquerque", + "l": "19", + "ca": 3, + "n": "阿尔布开克" + }, + "360030": { + "p": "360", + "a": "2", + "c": "360030", + "en": "America", + "l": "2", + "ca": 1, + "n": "美国" + }, + "420490030": { + "p": "420490", + "a": "2", + "c": "420490030", + "en": "Racine", + "l": "19", + "ca": 3, + "n": "拉辛" + }, + "280110": { + "p": "280", + "a": "0", + "c": "280110", + "en": "Guangyuan", + "l": "7", + "ca": 3, + "n": "广元" + }, + "130180": { + "p": "130", + "a": "0", + "c": "130180", + "en": "Baisha Co.", + "l": "7", + "ca": 3, + "n": "白沙县" + }, + "370090": { + "p": "370", + "a": "2", + "c": "370090", + "en": "Bolivia", + "l": "2", + "ca": 1, + "n": "玻利维亚" + }, + "090060": { + "p": "090", + "a": "0", + "c": "090060", + "en": "Putian", + "l": "7", + "ca": 3, + "n": "莆田" + }, + "420320060": { + "p": "420320", + "a": "2", + "c": "420320060", + "en": "Ithaca", + "l": "19", + "ca": 3, + "n": "绮色佳" + }, + "210060": { + "p": "210", + "a": "0", + "c": "210060", + "en": "Fushun", + "l": "7", + "ca": 3, + "n": "抚顺" + }, + "420460020": { + "p": "420460", + "a": "2", + "c": "420460020", + "en": "Seattle", + "l": "19", + "ca": 3, + "n": "西雅图" + }, + "350240": { + "p": "350", + "a": "2", + "c": "350240", + "en": "Kazakhstan", + "l": "2", + "ca": 1, + "n": "哈萨克斯坦" + }, + "420030020": { + "p": "420030", + "a": "2", + "c": "420030020", + "en": "Tucson", + "l": "19", + "ca": 3, + "n": "图森" + }, + "400330": { + "p": "400", + "a": "2", + "c": "400330", + "en": "Guinea", + "l": "2", + "ca": 1, + "n": "几内亚" + }, + "420180010": { + "p": "420180", + "a": "2", + "c": "420180010", + "en": "New Orleans", + "l": "19", + "ca": 3, + "n": "新奥尔良" + }, + "200020": { + "p": "200", + "a": "0", + "c": "200020", + "en": "Nanchang", + "l": "7", + "ca": 3, + "n": "南昌" + }, + "080030": { + "p": "080", + "a": "0", + "c": "080030", + "en": "Anqing", + "l": "7", + "ca": 3, + "n": "安庆" + }, + "350490": { + "p": "350", + "a": "0", + "c": "350490", + "en": "China", + "l": "2", + "ca": 1, + "n": "中国" + }, + "350250": { + "p": "350", + "a": "2", + "c": "350250", + "en": "Kyrghyzstan", + "l": "2", + "ca": 1, + "n": "吉尔吉斯斯坦" + }, + "370080": { + "p": "370", + "a": "2", + "c": "370080", + "en": "Peru", + "l": "2", + "ca": 1, + "n": "秘鲁" + }, + "130190": { + "p": "130", + "a": "0", + "c": "130190", + "en": "Changjiang Co.", + "l": "7", + "ca": 3, + "n": "昌江县" + }, + "280100": { + "p": "280", + "a": "0", + "c": "280100", + "en": "Deyang", + "l": "7", + "ca": 3, + "n": "德阳" + }, + "060040": { + "p": "060", + "a": "0", + "c": "060040", + "en": "Changzhou", + "l": "7", + "ca": 3, + "n": "常州" + }, + "220090": { + "p": "220", + "a": "0", + "c": "220090", + "en": "Bayannur", + "l": "7", + "ca": 3, + "n": "巴彦淖尔" + }, + "130110": { + "p": "130", + "a": "0", + "c": "130110", + "en": "Wuzhishan", + "l": "7", + "ca": 3, + "n": "五指山" + }, + "400160": { + "p": "400", + "a": "2", + "c": "400160", + "en": "Uganda", + "l": "2", + "ca": 1, + "n": "乌干达" + }, + "280180": { + "p": "280", + "a": "0", + "c": "280180", + "en": "Bazhong", + "l": "7", + "ca": 3, + "n": "巴中" + }, + "090090": { + "p": "090", + "a": "0", + "c": "090090", + "en": "Longyan", + "l": "7", + "ca": 3, + "n": "龙岩" + }, + "210070": { + "p": "210", + "a": "0", + "c": "210070", + "en": "Benxi", + "l": "7", + "ca": 3, + "n": "本溪" + }, + "420320050": { + "p": "420320", + "a": "2", + "c": "420320050", + "en": "Rochester", + "l": "19", + "ca": 3, + "n": "罗彻斯特市" + }, + "110100": { + "p": "110", + "a": "0", + "c": "110100", + "en": "Fangchenggang", + "l": "7", + "ca": 3, + "n": "防城港" + }, + "360060": { + "p": "360", + "a": "2", + "c": "360060", + "en": "Guatemala", + "l": "2", + "ca": 1, + "n": "危地马拉" + }, + "420170": { + "p": "360030", + "a": "2", + "c": "420170", + "en": "Kentucky", + "l": "17", + "ca": 2, + "n": "肯塔基州" + }, + "050230": { + "p": "050", + "a": "0", + "c": "050230", + "en": "Yunfu", + "l": "7", + "ca": 3, + "n": "云浮" + }, + "080040": { + "p": "080", + "a": "0", + "c": "080040", + "en": "Bengbu", + "l": "7", + "ca": 3, + "n": "蚌埠" + }, + "350260": { + "p": "350", + "a": "2", + "c": "350260", + "en": "Tajikistan", + "l": "2", + "ca": 1, + "n": "塔吉克斯坦" + }, + "200030": { + "p": "200", + "a": "0", + "c": "200030", + "en": "Jiujiang", + "l": "7", + "ca": 3, + "n": "九江" + }, + "280170": { + "p": "280", + "a": "0", + "c": "280170", + "en": "Ya'an", + "l": "7", + "ca": 3, + "n": "雅安" + }, + "400390": { + "p": "400", + "a": "2", + "c": "400390", + "en": "Ghana", + "l": "2", + "ca": 1, + "n": "加纳" + }, + "090080": { + "p": "090", + "a": "0", + "c": "090080", + "en": "Nanping", + "l": "7", + "ca": 3, + "n": "南平" + }, + "130120": { + "p": "130", + "a": "0", + "c": "130120", + "en": "Ding'an Co.", + "l": "7", + "ca": 3, + "n": "定安县" + }, + "420180": { + "p": "360030", + "a": "2", + "c": "420180", + "en": "Louisiana", + "l": "17", + "ca": 2, + "n": "路易斯安那州" + }, + "420320040": { + "p": "420320", + "a": "2", + "c": "420320040", + "en": "New York", + "l": "19", + "ca": 3, + "n": "纽约" + }, + "350020": { + "p": "350", + "a": "2", + "c": "350020", + "en": "Mongolia", + "l": "2", + "ca": 1, + "n": "蒙古" + }, + "360070": { + "p": "360", + "a": "2", + "c": "360070", + "en": "Belize", + "l": "2", + "ca": 1, + "n": "伯利兹" + }, + "270120": { + "p": "270", + "a": "0", + "c": "270120", + "en": "Yangling", + "l": "7", + "ca": 3, + "n": "杨凌" + }, + "210080": { + "p": "210", + "a": "0", + "c": "210080", + "en": "Dandong", + "l": "7", + "ca": 3, + "n": "丹东" + }, + "400150": { + "p": "400", + "a": "2", + "c": "400150", + "en": "Tanzania", + "l": "2", + "ca": 1, + "n": "坦桑尼亚" + }, + "050220": { + "p": "050", + "a": "0", + "c": "050220", + "en": "Jieyang", + "l": "7", + "ca": 3, + "n": "揭阳" + }, + "420310010": { + "p": "420310", + "a": "2", + "c": "420310010", + "en": "Santa Fe", + "l": "19", + "ca": 3, + "n": "圣达菲" + }, + "350270": { + "p": "350", + "a": "2", + "c": "350270", + "en": "Uzbekistan", + "l": "2", + "ca": 1, + "n": "乌兹别克斯坦" + }, + "350030": { + "p": "350", + "a": "2", + "c": "350030", + "en": "North Korea", + "l": "2", + "ca": 1, + "n": "朝鲜" + }, + "200040": { + "p": "200", + "a": "0", + "c": "200040", + "en": "Ganzhou", + "l": "7", + "ca": 3, + "n": "赣州" + }, + "080050": { + "p": "080", + "a": "0", + "c": "080050", + "en": "Wuhu", + "l": "7", + "ca": 3, + "n": "芜湖" + }, + "420190020": { + "p": "420190", + "a": "2", + "c": "420190020", + "en": "Portland", + "l": "19", + "ca": 3, + "n": "波特兰" + }, + "060020": { + "p": "060", + "a": "0", + "c": "060020", + "en": "Nanjing", + "l": "7", + "ca": 3, + "n": "南京" + }, + "280160": { + "p": "280", + "a": "0", + "c": "280160", + "en": "Dazhou", + "l": "7", + "ca": 3, + "n": "达州" + }, + "400380": { + "p": "400", + "a": "2", + "c": "400380", + "en": "Cote Divoire", + "l": "2", + "ca": 1, + "n": "科特迪瓦" + }, + "130130": { + "p": "130", + "a": "0", + "c": "130130", + "en": "Tuenchang Co.", + "l": "7", + "ca": 3, + "n": "屯昌县" + }, + "070020": { + "p": "070", + "a": "0", + "c": "070020", + "en": "Hangzhou", + "l": "7", + "ca": 3, + "n": "杭州" + }, + "420320030": { + "p": "420320", + "a": "2", + "c": "420320030", + "en": "Long Island", + "l": "19", + "ca": 3, + "n": "长岛" + }, + "420190": { + "p": "360030", + "a": "2", + "c": "420190", + "en": "Maine", + "l": "17", + "ca": 2, + "n": "缅因州" + }, + "420050060": { + "p": "420050", + "a": "2", + "c": "420050060", + "en": "San Francisco", + "l": "19", + "ca": 3, + "n": "旧金山" + }, + "270110": { + "p": "270", + "a": "0", + "c": "270110", + "en": "Yulin", + "l": "7", + "ca": 3, + "n": "榆林" + }, + "050210": { + "p": "050", + "a": "0", + "c": "050210", + "en": "Heyuan", + "l": "7", + "ca": 3, + "n": "河源" + }, + "140180": { + "p": "140", + "a": "0", + "c": "140180", + "en": "Xiong'an", + "l": "7", + "ca": 3, + "n": "雄安新区" + }, + "360080": { + "p": "370100", + "a": "2", + "c": "360080", + "en": "Salvador", + "l": "2", + "ca": 1, + "n": "萨尔瓦多" + }, + "210090": { + "p": "210", + "a": "0", + "c": "210090", + "en": "Jinzhou", + "l": "7", + "ca": 3, + "n": "锦州" + }, + "400140": { + "p": "400", + "a": "2", + "c": "400140", + "en": "Kenya", + "l": "2", + "ca": 1, + "n": "肯尼亚" + }, + "350280": { + "p": "350", + "a": "2", + "c": "350280", + "en": "Turkmenistan", + "l": "2", + "ca": 1, + "n": "土库曼斯坦" + }, + "200050": { + "p": "200", + "a": "0", + "c": "200050", + "en": "Yichun", + "l": "7", + "ca": 3, + "n": "宜春" + }, + "420470020": { + "p": "420470", + "a": "2", + "c": "420470020", + "en": "Newport", + "l": "19", + "ca": 3, + "n": "纽波特" + }, + "350040": { + "p": "350", + "a": "2", + "c": "350040", + "en": "Korea", + "l": "2", + "ca": 1, + "n": "韩国" + }, + "080060": { + "p": "080", + "a": "0", + "c": "080060", + "en": "Huainan", + "l": "7", + "ca": 3, + "n": "淮南" + }, + "420190010": { + "p": "420190", + "a": "2", + "c": "420190010", + "en": "Augusta", + "l": "19", + "ca": 3, + "n": "奥古斯塔" + }, + "280150": { + "p": "280", + "a": "0", + "c": "280150", + "en": "Guang'an", + "l": "7", + "ca": 3, + "n": "广安" + }, + "420040020": { + "p": "420040", + "a": "2", + "c": "420040020", + "en": "Fayetteville", + "l": "19", + "ca": 3, + "n": "费耶特维尔" + }, + "130140": { + "p": "130", + "a": "0", + "c": "130140", + "en": "Chengmai Co.", + "l": "7", + "ca": 3, + "n": "澄迈县" + }, + "070030": { + "p": "070", + "a": "0", + "c": "070030", + "en": "Ningbo", + "l": "7", + "ca": 3, + "n": "宁波" + }, + "420050050": { + "p": "370110", + "a": "2", + "c": "420050050", + "en": "San Diego", + "l": "19", + "ca": 3, + "n": "圣地亚哥" + }, + "420320020": { + "p": "420320", + "a": "2", + "c": "420320020", + "en": "Buffalo", + "l": "19", + "ca": 3, + "n": "布法罗" + }, + "270100": { + "p": "270", + "a": "0", + "c": "270100", + "en": "Yanan", + "l": "7", + "ca": 3, + "n": "延安" + }, + "400130": { + "p": "400", + "a": "2", + "c": "400130", + "en": "Djibouti", + "l": "2", + "ca": 1, + "n": "吉布提" + }, + "360090": { + "p": "360", + "a": "2", + "c": "360090", + "en": "Honduras", + "l": "2", + "ca": 1, + "n": "洪都拉斯" + }, + "050200": { + "p": "050", + "a": "0", + "c": "050200", + "en": "Shanwei", + "l": "7", + "ca": 3, + "n": "汕尾" + }, + "400370": { + "p": "400", + "a": "2", + "c": "400370", + "en": "Liberia", + "l": "2", + "ca": 1, + "n": "利比里亚" + }, + "420470010": { + "p": "420470", + "a": "2", + "c": "420470010", + "en": "Providence", + "l": "19", + "ca": 3, + "n": "普洛威顿斯" + }, + "350290": { + "p": "350", + "a": "2", + "c": "350290", + "en": "Afghanistan", + "l": "2", + "ca": 1, + "n": "阿富汗" + }, + "350050": { + "p": "350", + "a": "2", + "c": "350050", + "en": "Japan", + "l": "2", + "ca": 1, + "n": "日本" + }, + "200060": { + "p": "200", + "a": "0", + "c": "200060", + "en": "Ji'an", + "l": "7", + "ca": 3, + "n": "吉安" + }, + "080070": { + "p": "080", + "a": "0", + "c": "080070", + "en": "Ma'anshan", + "l": "7", + "ca": 3, + "n": "马鞍山" + }, + "130150": { + "p": "130", + "a": "0", + "c": "130150", + "en": "Linggao Co.", + "l": "7", + "ca": 3, + "n": "临高县" + }, + "280140": { + "p": "280", + "a": "0", + "c": "280140", + "en": "Meishan", + "l": "7", + "ca": 3, + "n": "眉山" + }, + "420040010": { + "p": "420040", + "a": "2", + "c": "420040010", + "en": "Little Rock", + "l": "19", + "ca": 3, + "n": "小石城" + }, + "420050040": { + "p": "420050", + "a": "2", + "c": "420050040", + "en": "Los Angeles", + "l": "19", + "ca": 3, + "n": "洛杉矶" + }, + "400520": { + "p": "400", + "a": "2", + "c": "400520", + "en": "Swaziland", + "l": "2", + "ca": 1, + "n": "斯威士兰" + }, + "420370": { + "p": "360030", + "a": "2", + "c": "420370", + "en": "Oregon", + "l": "17", + "ca": 2, + "n": "俄勒冈州" + }, + "180130": { + "p": "180", + "a": "0", + "c": "180130", + "en": "Yongzhou", + "l": "7", + "ca": 3, + "n": "永州" + }, + "120070": { + "p": "120", + "a": "0", + "c": "120070", + "en": "Tongren", + "l": "7", + "ca": 3, + "n": "铜仁" + }, + "420130": { + "p": "360030", + "a": "2", + "c": "420130", + "en": "Illinois", + "l": "17", + "ca": 2, + "n": "伊利诺伊州" + }, + "350180": { + "p": "350", + "a": "2", + "c": "350180", + "en": "Bhutan", + "l": "2", + "ca": 1, + "n": "不丹" + }, + "400510": { + "p": "400", + "a": "2", + "c": "400510", + "en": "South Africa", + "l": "2", + "ca": 1, + "n": "南非" + }, + "200070": { + "p": "200", + "a": "0", + "c": "200070", + "en": "Shangrao", + "l": "7", + "ca": 3, + "n": "上饶" + }, + "110030": { + "p": "110", + "a": "0", + "c": "110030", + "en": "Beihai", + "l": "7", + "ca": 3, + "n": "北海" + }, + "provinces": { + "en": "ALL PROVINCE", + "n": "全部省份" + }, + "420050030": { + "p": "420050", + "a": "2", + "c": "420050030", + "en": "San Jose", + "l": "19", + "ca": 3, + "n": "圣荷西" + }, + "260120": { + "p": "260", + "a": "0", + "c": "260120", + "en": "Lyuliang", + "l": "7", + "ca": 3, + "n": "吕梁" + }, + "420140": { + "p": "360030", + "a": "2", + "c": "420140", + "en": "Indiana", + "l": "17", + "ca": 2, + "n": "印第安那州" + }, + "390400": { + "p": "390", + "a": "2", + "c": "390400", + "en": "San Marino", + "l": "2", + "ca": 1, + "n": "圣马力诺" + }, + "420380": { + "p": "360030", + "a": "2", + "c": "420380", + "en": "Pennsylvania", + "l": "17", + "ca": 2, + "n": "宾州" + }, + "180140": { + "p": "180", + "a": "0", + "c": "180140", + "en": "Huaihua", + "l": "7", + "ca": 3, + "n": "怀化" + }, + "120060": { + "p": "120", + "a": "0", + "c": "120060", + "en": "Bijie", + "l": "7", + "ca": 3, + "n": "毕节" + }, + "350190": { + "p": "350", + "a": "2", + "c": "350190", + "en": "Bangladesh", + "l": "2", + "ca": 1, + "n": "孟加拉国" + }, + "420120010": { + "p": "420120", + "a": "2", + "c": "420120010", + "en": "Boise", + "l": "19", + "ca": 3, + "n": "波夕" + }, + "400500": { + "p": "400", + "a": "2", + "c": "400500", + "en": "Namibia", + "l": "2", + "ca": 1, + "n": "纳米比亚" + }, + "110020": { + "p": "110", + "a": "0", + "c": "110020", + "en": "Nanning", + "l": "7", + "ca": 3, + "n": "南宁" + }, + "200080": { + "p": "200", + "a": "0", + "c": "200080", + "en": "Fuzhou", + "l": "7", + "ca": 3, + "n": "抚州" + }, + "420050020": { + "p": "420050", + "a": "2", + "c": "420050020", + "en": "Sonoma", + "l": "19", + "ca": 3, + "n": "索诺马" + }, + "260110": { + "p": "260", + "a": "0", + "c": "260110", + "en": "Xinzhou", + "l": "7", + "ca": 3, + "n": "忻州" + }, + "120050": { + "p": "120", + "a": "0", + "c": "120050", + "en": "Anshun", + "l": "7", + "ca": 3, + "n": "安顺" + }, + "420150": { + "p": "360030", + "a": "2", + "c": "420150", + "en": "Iowa", + "l": "17", + "ca": 2, + "n": "爱荷华州" + }, + "420390": { + "p": "360030", + "a": "2", + "c": "420390", + "en": "South Carolina", + "l": "17", + "ca": 2, + "n": "南卡省" + }, + "180110": { + "p": "180", + "a": "0", + "c": "180110", + "en": "Zhangjiajie", + "l": "7", + "ca": 3, + "n": "张家界" + }, + "420120020": { + "p": "420120", + "a": "2", + "c": "420120020", + "en": "Pocatello", + "l": "19", + "ca": 3, + "n": "波卡特洛" + }, + "420200010": { + "p": "420200", + "a": "2", + "c": "420200010", + "en": "Annapolis", + "l": "19", + "ca": 3, + "n": "安纳波利斯" + }, + "200090": { + "p": "200", + "a": "0", + "c": "200090", + "en": "Jingdezhen", + "l": "7", + "ca": 3, + "n": "景德镇" + }, + "170100": { + "p": "170", + "a": "0", + "c": "170100", + "en": "Ezhou", + "l": "7", + "ca": 3, + "n": "鄂州" + }, + "280190": { + "p": "280", + "a": "0", + "c": "280190", + "en": "Ziyang", + "l": "7", + "ca": 3, + "n": "资阳" + }, + "420050010": { + "p": "420050", + "a": "2", + "c": "420050010", + "en": "Sacramento", + "l": "19", + "ca": 3, + "n": "萨克拉门托" + }, + "260100": { + "p": "260", + "a": "0", + "c": "260100", + "en": "Jinzhong", + "l": "7", + "ca": 3, + "n": "晋中" + }, + "430070140": { + "p": "430070", + "a": "2", + "c": "430070140", + "en": "Greater Sudbury", + "l": "19", + "ca": 3, + "n": "萨德伯里" + }, + "090100": { + "p": "090", + "a": "0", + "c": "090100", + "en": "Ningde", + "l": "7", + "ca": 3, + "n": "宁德" + }, + "120040": { + "p": "120", + "a": "0", + "c": "120040", + "en": "Liupanshui", + "l": "7", + "ca": 3, + "n": "六盘水" + }, + "420160": { + "p": "360030", + "a": "2", + "c": "420160", + "en": "Kansas", + "l": "17", + "ca": 2, + "n": "堪萨斯州" + }, + "180120": { + "p": "180", + "a": "0", + "c": "180120", + "en": "Loudi", + "l": "7", + "ca": 3, + "n": "娄底" + }, + "420120030": { + "p": "420120", + "a": "2", + "c": "420120030", + "en": "Idaho Falls", + "l": "19", + "ca": 3, + "n": "爱达荷福尔斯" + }, + "050090": { + "p": "050", + "a": "0", + "c": "050090", + "en": "Shenzhen", + "l": "7", + "ca": 3, + "n": "深圳" + }, + "420200020": { + "p": "420200", + "a": "2", + "c": "420200020", + "en": "Baltimore", + "l": "19", + "ca": 3, + "n": "巴尔的摩" + }, + "100020": { + "p": "100", + "a": "0", + "c": "100020", + "en": "Lanzhou", + "l": "7", + "ca": 3, + "n": "兰州" + }, + "430070130": { + "p": "430070", + "a": "2", + "c": "430070130", + "en": "Burlington", + "l": "19", + "ca": 3, + "n": "伯灵顿" + }, + "420300040": { + "p": "420300", + "a": "2", + "c": "420300040", + "en": "Elizabeth", + "l": "19", + "ca": 3, + "n": "依丽沙白" + }, + "390430": { + "p": "390", + "a": "2", + "c": "390430", + "en": "Portugal", + "l": "2", + "ca": 1, + "n": "葡萄牙" + }, + "400320": { + "p": "400", + "a": "2", + "c": "400320", + "en": "Burkina Faso", + "l": "2", + "ca": 1, + "n": "布基纳法索" + }, + "400560": { + "p": "400", + "a": "2", + "c": "400560", + "en": "Mauritius", + "l": "2", + "ca": 1, + "n": "毛里求斯" + }, + "420140040": { + "p": "420140", + "a": "2", + "c": "420140040", + "en": "Lafayette", + "l": "19", + "ca": 3, + "n": "拉法叶" + }, + "420330": { + "p": "360030", + "a": "2", + "c": "420330", + "en": "North Carolina", + "l": "17", + "ca": 2, + "n": "北卡罗莱纳州" + }, + "250120": { + "p": "250", + "a": "0", + "c": "250120", + "en": "Yantai", + "l": "7", + "ca": 3, + "n": "烟台" + }, + "420200030": { + "p": "420200", + "a": "2", + "c": "420200030", + "en": "Rockville", + "l": "19", + "ca": 3, + "n": "洛克威尔" + }, + "050080": { + "p": "050", + "a": "0", + "c": "050080", + "en": "Shantou", + "l": "7", + "ca": 3, + "n": "汕头" + }, + "130080": { + "p": "130", + "a": "0", + "c": "130080", + "en": "Wanning", + "l": "7", + "ca": 3, + "n": "万宁" + }, + "110070": { + "p": "110", + "a": "0", + "c": "110070", + "en": "Wuzhou", + "l": "7", + "ca": 3, + "n": "梧州" + }, + "180100": { + "p": "180", + "a": "0", + "c": "180100", + "en": "Shaoyang", + "l": "7", + "ca": 3, + "n": "邵阳" + }, + "100030": { + "p": "100", + "a": "0", + "c": "100030", + "en": "Jiayuguan", + "l": "7", + "ca": 3, + "n": "嘉峪关" + }, + "430070120": { + "p": "430070", + "a": "2", + "c": "430070120", + "en": "Okville", + "l": "19", + "ca": 3, + "n": "奥克维尔" + }, + "390440": { + "p": "390", + "a": "2", + "c": "390440", + "en": "Andorra", + "l": "2", + "ca": 1, + "n": "安道尔" + }, + "390200": { + "p": "390", + "a": "2", + "c": "390200", + "en": "Switzerland", + "l": "2", + "ca": 1, + "n": "瑞士" + }, + "400310": { + "p": "400", + "a": "2", + "c": "400310", + "en": "Mali", + "l": "2", + "ca": 1, + "n": "马里" + }, + "400550": { + "p": "400", + "a": "2", + "c": "400550", + "en": "Comorin", + "l": "2", + "ca": 1, + "n": "科摩罗" + }, + "420100": { + "p": "360030", + "a": "2", + "c": "420100", + "en": "Georgia", + "l": "17", + "ca": 2, + "n": "乔治亚州" + }, + "420340": { + "p": "360030", + "a": "2", + "c": "420340", + "en": "North Dakota", + "l": "17", + "ca": 2, + "n": "北达科他州" + }, + "250130": { + "p": "250", + "a": "0", + "c": "250130", + "en": "Zibo", + "l": "7", + "ca": 3, + "n": "淄博" + }, + "050070": { + "p": "050", + "a": "0", + "c": "050070", + "en": "Qingyuan", + "l": "7", + "ca": 3, + "n": "清远" + }, + "130090": { + "p": "130", + "a": "0", + "c": "130090", + "en": "Danzhou", + "l": "7", + "ca": 3, + "n": "儋州" + }, + "430130": { + "p": "360020", + "a": "2", + "c": "430130", + "en": "Yukon", + "l": "10", + "ca": 2, + "n": "育空地区" + }, + "420130010": { + "p": "420130", + "a": "2", + "c": "420130010", + "en": "Springfield", + "l": "19", + "ca": 3, + "n": "春田" + }, + "110060": { + "p": "110", + "a": "0", + "c": "110060", + "en": "Yulin", + "l": "7", + "ca": 3, + "n": "玉林" + }, + "420300020": { + "p": "420300", + "a": "2", + "c": "420300020", + "en": "Jersey City", + "l": "19", + "ca": 3, + "n": "泽西市" + }, + "390410": { + "p": "390", + "a": "2", + "c": "390410", + "en": "Malta", + "l": "2", + "ca": 1, + "n": "马耳他" + }, + "400300": { + "p": "400", + "a": "2", + "c": "400300", + "en": "Gambia", + "l": "2", + "ca": 1, + "n": "冈比亚" + }, + "400540": { + "p": "400", + "a": "2", + "c": "400540", + "en": "Madagascar", + "l": "2", + "ca": 1, + "n": "马达加斯加" + }, + "050050": { + "p": "050", + "a": "0", + "c": "050050", + "en": "Foshan", + "l": "7", + "ca": 3, + "n": "佛山" + }, + "430070110": { + "p": "430070", + "a": "2", + "c": "430070110", + "en": "Richmond Hill", + "l": "19", + "ca": 3, + "n": "列治文山" + }, + "120090": { + "p": "120", + "a": "0", + "c": "120090", + "en": "Qiandongnan", + "l": "8", + "ca": 3, + "n": "黔东南" + }, + "420110": { + "p": "360030", + "a": "2", + "c": "420110", + "en": "Hawaii", + "l": "17", + "ca": 2, + "n": "夏威夷州" + }, + "420350": { + "p": "360030", + "a": "2", + "c": "420350", + "en": "Ohio", + "l": "17", + "ca": 2, + "n": "俄亥俄州" + }, + "050060": { + "p": "050", + "a": "0", + "c": "050060", + "en": "Huizhou", + "l": "7", + "ca": 3, + "n": "惠州" + }, + "250100": { + "p": "250", + "a": "0", + "c": "250100", + "en": "Weihai", + "l": "7", + "ca": 3, + "n": "威海" + }, + "420130020": { + "p": "420130", + "a": "2", + "c": "420130020", + "en": "Chicago", + "l": "19", + "ca": 3, + "n": "芝加哥" + }, + "080100": { + "p": "080", + "a": "0", + "c": "080100", + "en": "Huangshan", + "l": "7", + "ca": 3, + "n": "黄山" + }, + "110050": { + "p": "110", + "a": "0", + "c": "110050", + "en": "Liuzhou", + "l": "7", + "ca": 3, + "n": "柳州" + }, + "190110": { + "p": "190", + "a": "0", + "c": "190110", + "en": "Yanbian", + "l": "8", + "ca": 3, + "n": "延边" + }, + "420300030": { + "p": "420300", + "a": "2", + "c": "420300030", + "en": "Atlantic City", + "l": "19", + "ca": 3, + "n": "大西洋城" + }, + "390420": { + "p": "390", + "a": "2", + "c": "390420", + "en": "Spain", + "l": "2", + "ca": 1, + "n": "西班牙" + }, + "420360": { + "p": "360030", + "a": "2", + "c": "420360", + "en": "Oklahoma", + "l": "17", + "ca": 2, + "n": "俄克拉何马州" + }, + "400530": { + "p": "400", + "a": "2", + "c": "400530", + "en": "Lesotho", + "l": "2", + "ca": 1, + "n": "莱索托" + }, + "430070100": { + "p": "430070", + "a": "2", + "c": "430070100", + "en": "Windsor", + "l": "19", + "ca": 3, + "n": "温莎" + }, + "050040": { + "p": "050", + "a": "0", + "c": "050040", + "en": "Dongguan", + "l": "7", + "ca": 3, + "n": "东莞" + }, + "120080": { + "p": "120", + "a": "0", + "c": "120080", + "en": "Qianxinan", + "l": "8", + "ca": 3, + "n": "黔西南" + }, + "420120": { + "p": "360030", + "a": "2", + "c": "420120", + "en": "Idaho", + "l": "17", + "ca": 2, + "n": "爱达荷州" + }, + "250110": { + "p": "250", + "a": "0", + "c": "250110", + "en": "Weifang", + "l": "7", + "ca": 3, + "n": "潍坊" + }, + "110040": { + "p": "110", + "a": "0", + "c": "110040", + "en": "Guilin", + "l": "7", + "ca": 3, + "n": "桂林" + }, + "080110": { + "p": "080", + "a": "0", + "c": "080110", + "en": "Chuzhou", + "l": "7", + "ca": 3, + "n": "滁州" + }, + "420130030": { + "p": "420130", + "a": "2", + "c": "420130030", + "en": "Rockford", + "l": "19", + "ca": 3, + "n": "洛克福特" + }, + "100060": { + "p": "100", + "a": "0", + "c": "100060", + "en": "Baiyin", + "l": "7", + "ca": 3, + "n": "白银" + }, + "390470": { + "p": "390", + "a": "2", + "c": "390470", + "en": "Cyprus", + "l": "2", + "ca": 1, + "n": "塞浦路斯" + }, + "390230": { + "p": "390", + "a": "2", + "c": "390230", + "en": "Ireland", + "l": "2", + "ca": 1, + "n": "爱尔兰" + }, + "160120": { + "p": "160", + "a": "0", + "c": "160120", + "en": "Heihe", + "l": "7", + "ca": 3, + "n": "黑河" + }, + "250170": { + "p": "250", + "a": "0", + "c": "250170", + "en": "Heze", + "l": "7", + "ca": 3, + "n": "菏泽" + }, + "430010020": { + "p": "430010", + "a": "2", + "c": "430010020", + "en": "Edmonton", + "l": "19", + "ca": 3, + "n": "埃德蒙顿" + }, + "300080": { + "p": "300", + "a": "0", + "c": "300080", + "en": "Shihezi", + "l": "7", + "ca": 3, + "n": "石河子" + }, + "170160": { + "p": "170", + "a": "0", + "c": "170160", + "en": "Tianmen", + "l": "7", + "ca": 3, + "n": "天门" + }, + "100070": { + "p": "100", + "a": "0", + "c": "100070", + "en": "Tianshui", + "l": "7", + "ca": 3, + "n": "天水" + }, + "390240": { + "p": "390", + "a": "2", + "c": "390240", + "en": "Netherlands", + "l": "2", + "ca": 1, + "n": "荷兰" + }, + "160110": { + "p": "160", + "a": "0", + "c": "160110", + "en": "Qitaihe", + "l": "7", + "ca": 3, + "n": "七台河" + }, + "420140010": { + "p": "420140", + "a": "2", + "c": "420140010", + "en": "Indianapolis", + "l": "19", + "ca": 3, + "n": "印第安纳波利斯" + }, + "300090": { + "p": "300", + "a": "0", + "c": "300090", + "en": "Alar", + "l": "7", + "ca": 3, + "n": "阿拉尔" + }, + "170150": { + "p": "170", + "a": "0", + "c": "170150", + "en": "Xiantao", + "l": "7", + "ca": 3, + "n": "仙桃" + }, + "420150040": { + "p": "420150", + "a": "2", + "c": "420150040", + "en": "Iowa City", + "l": "19", + "ca": 3, + "n": "衣阿华城" + }, + "100040": { + "p": "100", + "a": "0", + "c": "100040", + "en": "Jiuquan", + "l": "7", + "ca": 3, + "n": "酒泉" + }, + "390450": { + "p": "390", + "a": "2", + "c": "390450", + "en": "Slovakia", + "l": "2", + "ca": 1, + "n": "斯洛伐克" + }, + "420220020": { + "p": "420220", + "a": "2", + "c": "420220020", + "en": "Detroit", + "l": "19", + "ca": 3, + "n": "底特律" + }, + "390210": { + "p": "390", + "a": "2", + "c": "390210", + "en": "Liechtenstein", + "l": "2", + "ca": 1, + "n": "列支敦士登" + }, + "250150": { + "p": "250", + "a": "0", + "c": "250150", + "en": "Binzhou", + "l": "7", + "ca": 3, + "n": "滨州" + }, + "420140020": { + "p": "420140", + "a": "2", + "c": "420140020", + "en": "Fort Wayne", + "l": "19", + "ca": 3, + "n": "韦恩堡" + }, + "160140": { + "p": "160", + "a": "0", + "c": "160140", + "en": "Daxing'anling", + "l": "10", + "ca": 3, + "n": "大兴安岭" + }, + "110090": { + "p": "110", + "a": "0", + "c": "110090", + "en": "Laibin", + "l": "7", + "ca": 3, + "n": "来宾" + }, + "420300": { + "p": "360030", + "a": "2", + "c": "420300", + "en": "New Jersey", + "l": "17", + "ca": 2, + "n": "新泽西州" + }, + "250140": { + "p": "250", + "a": "0", + "c": "250140", + "en": "Zaozhuang", + "l": "7", + "ca": 3, + "n": "枣庄" + }, + "420230030": { + "p": "420230", + "a": "2", + "c": "420230030", + "en": "Duluth", + "l": "19", + "ca": 3, + "n": "杜鲁司" + }, + "300060": { + "p": "300", + "a": "0", + "c": "300060", + "en": "Aksu Prefecture", + "l": "10", + "ca": 3, + "n": "阿克苏" + }, + "430120": { + "p": "360020", + "a": "2", + "c": "430120", + "en": "Northwest Territories", + "l": "10", + "ca": 2, + "n": "西北地区" + }, + "430100": { + "p": "360020", + "a": "2", + "c": "430100", + "en": "Saskatchewan", + "l": "4", + "ca": 2, + "n": "萨省" + }, + "100050": { + "p": "100", + "a": "0", + "c": "100050", + "en": "Jinchang", + "l": "7", + "ca": 3, + "n": "金昌" + }, + "390460": { + "p": "390", + "a": "2", + "c": "390460", + "en": "Montenegro", + "l": "2", + "ca": 1, + "n": "黑山" + }, + "170180": { + "p": "170", + "a": "0", + "c": "170180", + "en": "Enshi Prefecture", + "l": "8", + "ca": 3, + "n": "恩施州" + }, + "420220010": { + "p": "420220", + "a": "2", + "c": "420220010", + "en": "Lansing", + "l": "19", + "ca": 3, + "n": "兰辛" + }, + "390220": { + "p": "390", + "a": "2", + "c": "390220", + "en": "United Kingdom", + "l": "2", + "ca": 1, + "n": "英国" + }, + "420320": { + "p": "360030", + "a": "2", + "c": "420320", + "en": "New York", + "l": "17", + "ca": 2, + "n": "纽约州" + }, + "250160": { + "p": "250", + "a": "0", + "c": "250160", + "en": "Liaocheng", + "l": "7", + "ca": 3, + "n": "聊城" + }, + "160130": { + "p": "160", + "a": "0", + "c": "160130", + "en": "Suihua", + "l": "7", + "ca": 3, + "n": "绥化" + }, + "420140030": { + "p": "420140", + "a": "2", + "c": "420140030", + "en": "Bloomington", + "l": "19", + "ca": 3, + "n": "伯明顿" + }, + "300": { + "p": "350490", + "a": "0", + "c": "300", + "en": "Xinjiang", + "l": "5", + "ca": 2, + "n": "新疆" + }, + "420310": { + "p": "360030", + "a": "2", + "c": "420310", + "en": "New Mexico", + "l": "17", + "ca": 2, + "n": "新墨西哥州" + }, + "300070": { + "p": "300", + "a": "0", + "c": "300070", + "en": "Hami", + "l": "7", + "ca": 3, + "n": "哈密" + }, + "170170": { + "p": "170", + "a": "0", + "c": "170170", + "en": "Shennongjia", + "l": "11", + "ca": 3, + "n": "神农架" + }, + "150100": { + "p": "150", + "a": "0", + "c": "150100", + "en": "Puyang", + "l": "7", + "ca": 3, + "n": "濮阳" + }, + "430110": { + "p": "360020", + "a": "2", + "c": "430110", + "en": "Nunavut", + "l": "10", + "ca": 2, + "n": "努纳武特地区" + }, + "110080": { + "p": "110", + "a": "0", + "c": "110080", + "en": "Chongzuo", + "l": "7", + "ca": 3, + "n": "崇左" + }, + "390270": { + "p": "390", + "a": "2", + "c": "390270", + "en": "France", + "l": "2", + "ca": 1, + "n": "法国" + }, + "390030": { + "p": "390", + "a": "2", + "c": "390030", + "en": "Sweden", + "l": "2", + "ca": 1, + "n": "瑞典" + }, + "310080": { + "p": "310", + "a": "0", + "c": "310080", + "en": "Zhaotong", + "l": "7", + "ca": 3, + "n": "昭通" + }, + "310": { + "p": "350490", + "a": "0", + "c": "310", + "en": "Yunnan", + "l": "4", + "ca": 2, + "n": "云南" + }, + "300280": { + "p": "300", + "a": "0", + "c": "300280", + "en": "Xinxing", + "l": "7", + "ca": 3, + "n": "新星" + }, + "380220": { + "p": "380", + "a": "2", + "c": "380220", + "en": "Niue", + "l": "2", + "ca": 1, + "n": "纽埃" + }, + "420220040": { + "p": "420220", + "a": "2", + "c": "420220040", + "en": "Flint", + "l": "19", + "ca": 3, + "n": "弗林特" + }, + "300040": { + "p": "300", + "a": "0", + "c": "300040", + "en": "Karamay", + "l": "7", + "ca": 3, + "n": "克拉玛依" + }, + "430020030": { + "p": "430020", + "a": "2", + "c": "430020030", + "en": "Burnaby", + "l": "19", + "ca": 3, + "n": "本拿比" + }, + "420150010": { + "p": "420150", + "a": "2", + "c": "420150010", + "en": "Des Moines", + "l": "19", + "ca": 3, + "n": "得梅因" + }, + "170120": { + "p": "170", + "a": "0", + "c": "170120", + "en": "Xiaogan", + "l": "7", + "ca": 3, + "n": "孝感" + }, + "390280": { + "p": "390", + "a": "2", + "c": "390280", + "en": "Monaco", + "l": "2", + "ca": 1, + "n": "摩纳哥" + }, + "390040": { + "p": "390", + "a": "2", + "c": "390040", + "en": "Norway", + "l": "2", + "ca": 1, + "n": "挪威" + }, + "310090": { + "p": "310", + "a": "0", + "c": "310090", + "en": "Pu'er", + "l": "7", + "ca": 3, + "n": "普洱" + }, + "420160040": { + "p": "420160", + "a": "2", + "c": "420160040", + "en": "Lawrence", + "l": "19", + "ca": 3, + "n": "罗伦斯" + }, + "320": { + "p": "350490", + "a": "1", + "c": "320", + "en": "Hongkong", + "l": "6", + "ca": 2, + "n": "香港" + }, + "300050": { + "p": "300", + "a": "0", + "c": "300050", + "en": "Ili", + "l": "8", + "ca": 3, + "n": "伊犁" + }, + "420220030": { + "p": "420220", + "a": "2", + "c": "420220030", + "en": "Grand Rapids", + "l": "19", + "ca": 3, + "n": "大溪城" + }, + "430020020": { + "p": "430020", + "a": "2", + "c": "430020020", + "en": "Surrey", + "l": "19", + "ca": 3, + "n": "素里" + }, + "170110": { + "p": "170", + "a": "0", + "c": "170110", + "en": "Huanggang", + "l": "7", + "ca": 3, + "n": "黄冈" + }, + "390290": { + "p": "390", + "a": "2", + "c": "390290", + "en": "Roumania", + "l": "2", + "ca": 1, + "n": "罗马尼亚" + }, + "390250": { + "p": "390", + "a": "2", + "c": "390250", + "en": "Belgium", + "l": "2", + "ca": 1, + "n": "比利时" + }, + "420210020": { + "p": "420210", + "a": "2", + "c": "420210020", + "en": "Worcester", + "l": "19", + "ca": 3, + "n": "伍斯特" + }, + "310060": { + "p": "310", + "a": "0", + "c": "310060", + "en": "Qujing", + "l": "7", + "ca": 3, + "n": "曲靖" + }, + "180150": { + "p": "180", + "a": "0", + "c": "180150", + "en": "Xiangxi", + "l": "8", + "ca": 3, + "n": "湘西" + }, + "160100": { + "p": "160", + "a": "0", + "c": "160100", + "en": "Yichun", + "l": "7", + "ca": 3, + "n": "伊春" + }, + "100080": { + "p": "100", + "a": "0", + "c": "100080", + "en": "Zhangye", + "l": "7", + "ca": 3, + "n": "张掖" + }, + "330": { + "p": "350490", + "a": "1", + "c": "330", + "en": "Macao", + "l": "6", + "ca": 2, + "n": "澳门" + }, + "420500": { + "p": "360030", + "a": "2", + "c": "420500", + "en": "Wyoming", + "l": "17", + "ca": 2, + "n": "怀俄明州" + }, + "170140": { + "p": "170", + "a": "0", + "c": "170140", + "en": "Suizhou", + "l": "7", + "ca": 3, + "n": "随州" + }, + "420150030": { + "p": "420150", + "a": "2", + "c": "420150030", + "en": "Daven Port", + "l": "19", + "ca": 3, + "n": "丹芬堡特" + }, + "300020": { + "p": "300", + "a": "0", + "c": "300020", + "en": "Urumqi", + "l": "7", + "ca": 3, + "n": "乌鲁木齐" + }, + "300260": { + "p": "300", + "a": "0", + "c": "300260", + "en": "Tiemenguan", + "l": "7", + "ca": 3, + "n": "铁门关" + }, + "390260": { + "p": "390", + "a": "2", + "c": "390260", + "en": "Luxembourg", + "l": "2", + "ca": 1, + "n": "卢森堡" + }, + "430020040": { + "p": "430020", + "a": "2", + "c": "430020040", + "en": "Richmond", + "l": "19", + "ca": 3, + "n": "列治文" + }, + "390020": { + "p": "390", + "a": "2", + "c": "390020", + "en": "Finland", + "l": "2", + "ca": 1, + "n": "芬兰" + }, + "310070": { + "p": "310", + "a": "0", + "c": "310070", + "en": "Baoshan", + "l": "7", + "ca": 3, + "n": "保山" + }, + "430010010": { + "p": "430010", + "a": "2", + "c": "430010010", + "en": "Calgary", + "l": "19", + "ca": 3, + "n": "卡尔加里" + }, + "100090": { + "p": "100", + "a": "0", + "c": "100090", + "en": "Wuwei", + "l": "7", + "ca": 3, + "n": "武威" + }, + "340": { + "p": "350490", + "a": "1", + "c": "340", + "en": "Taiwan", + "l": "4", + "ca": 2, + "n": "台湾" + }, + "100": { + "p": "350490", + "a": "0", + "c": "100", + "en": "Gansu", + "l": "4", + "ca": 2, + "n": "甘肃" + }, + "420510": { + "p": "360030", + "a": "2", + "c": "420510", + "en": "Washington, DC", + "l": "18", + "ca": 2, + "n": "华盛顿哥伦比亚特区" + }, + "420210010": { + "p": "420210", + "a": "2", + "c": "420210010", + "en": "Boston", + "l": "19", + "ca": 3, + "n": "波士顿" + }, + "300030": { + "p": "300", + "a": "0", + "c": "300030", + "en": "Kashi Prefecture", + "l": "10", + "ca": 3, + "n": "喀什" + }, + "300270": { + "p": "300", + "a": "0", + "c": "300270", + "en": "Huyanghe", + "l": "7", + "ca": 3, + "n": "胡杨河" + }, + "170130": { + "p": "170", + "a": "0", + "c": "170130", + "en": "Xianning", + "l": "7", + "ca": 3, + "n": "咸宁" + }, + "420150020": { + "p": "420150", + "a": "2", + "c": "420150020", + "en": "Cedar Rapids", + "l": "19", + "ca": 3, + "n": "锡达拉皮兹" + }, + "370100": { + "p": "370", + "a": "2", + "c": "370100", + "en": "Brazil", + "l": "2", + "ca": 1, + "n": "巴西" + }, + "420240010": { + "p": "420240", + "a": "2", + "c": "420240010", + "en": "Jackson", + "l": "19", + "ca": 3, + "n": "杰克逊" + }, + "220110": { + "p": "220", + "a": "0", + "c": "220110", + "en": "Hinggan", + "l": "9", + "ca": 3, + "n": "兴安盟" + }, + "310160": { + "p": "310", + "a": "0", + "c": "310160", + "en": "Nujiang", + "l": "8", + "ca": 3, + "n": "怒江" + }, + "350": { + "p": "", + "a": "2", + "c": "350", + "en": "Asia", + "l": "1", + "ca": 0, + "n": "亚洲" + }, + "420400010": { + "p": "420400", + "a": "2", + "c": "420400010", + "en": "Sioux Falls", + "l": "19", + "ca": 3, + "n": "苏瀑市" + }, + "380140": { + "p": "380", + "a": "2", + "c": "380140", + "en": "The Republic of Fiji", + "l": "2", + "ca": 1, + "n": "斐济" + }, + "110": { + "p": "350490", + "a": "0", + "c": "110", + "en": "Guangxi", + "l": "5", + "ca": 2, + "n": "广西" + }, + "160090": { + "p": "160", + "a": "0", + "c": "160090", + "en": "Shuangyashan", + "l": "7", + "ca": 3, + "n": "双鸭山" + }, + "220120": { + "p": "220", + "a": "0", + "c": "220120", + "en": "Xilingol", + "l": "9", + "ca": 3, + "n": "锡盟" + }, + "060110": { + "p": "060", + "a": "0", + "c": "060110", + "en": "Xuzhou", + "l": "7", + "ca": 3, + "n": "徐州" + }, + "300120": { + "p": "300", + "a": "0", + "c": "300120", + "en": "Changji Prefecture", + "l": "8", + "ca": 3, + "n": "昌吉" + }, + "150030": { + "p": "150", + "a": "0", + "c": "150030", + "en": "Kaifeng", + "l": "7", + "ca": 3, + "n": "开封" + }, + "420240020": { + "p": "420220", + "a": "2", + "c": "420240020", + "en": "Meridian", + "l": "19", + "ca": 3, + "n": "密烈地安" + }, + "150050": { + "p": "150", + "a": "0", + "c": "150050", + "en": "Shangqiu", + "l": "7", + "ca": 3, + "n": "商丘" + }, + "400070": { + "p": "400", + "a": "2", + "c": "400070", + "en": "Morocco", + "l": "2", + "ca": 1, + "n": "摩洛哥" + }, + "130200": { + "p": "130", + "a": "0", + "c": "130200", + "en": "Ledong Co.", + "l": "7", + "ca": 3, + "n": "乐东县" + }, + "220100": { + "p": "220", + "a": "0", + "c": "220100", + "en": "Ulanqab", + "l": "7", + "ca": 3, + "n": "乌兰察布" + }, + "310170": { + "p": "310", + "a": "0", + "c": "310170", + "en": "Diqing", + "l": "8", + "ca": 3, + "n": "迪庆" + }, + "360": { + "p": "", + "a": "2", + "c": "360", + "en": "NorthAmerica", + "l": "1", + "ca": 0, + "n": "北美洲" + }, + "120": { + "p": "350490", + "a": "0", + "c": "120", + "en": "Guizhou", + "l": "4", + "ca": 2, + "n": "贵州" + }, + "420400020": { + "p": "420400", + "a": "2", + "c": "420400020", + "en": "Rapid City", + "l": "19", + "ca": 3, + "n": "拉皮特城" + }, + "380130": { + "p": "380", + "a": "2", + "c": "380130", + "en": "Samoa", + "l": "2", + "ca": 1, + "n": "萨摩亚" + }, + "160080": { + "p": "160", + "a": "0", + "c": "160080", + "en": "Hegang", + "l": "7", + "ca": 3, + "n": "鹤岗" + }, + "300130": { + "p": "300", + "a": "0", + "c": "300130", + "en": "Altay Prefecture", + "l": "10", + "ca": 3, + "n": "阿勒泰" + }, + "420390010": { + "p": "420390", + "a": "2", + "c": "420390010", + "en": "Columbia", + "l": "19", + "ca": 3, + "n": "哥伦比亚" + }, + "150040": { + "p": "150", + "a": "0", + "c": "150040", + "en": "Luoyang", + "l": "7", + "ca": 3, + "n": "洛阳" + }, + "060100": { + "p": "060", + "a": "0", + "c": "060100", + "en": "Wuxi", + "l": "7", + "ca": 3, + "n": "无锡" + }, + "390170": { + "p": "390", + "a": "2", + "c": "390170", + "en": "Hungary", + "l": "2", + "ca": 1, + "n": "匈牙利" + }, + "400060": { + "p": "400", + "a": "2", + "c": "400060", + "en": "Algeria", + "l": "2", + "ca": 1, + "n": "阿尔及利亚" + }, + "150060": { + "p": "150", + "a": "0", + "c": "150060", + "en": "Anyang", + "l": "7", + "ca": 3, + "n": "安阳" + }, + "130210": { + "p": "130", + "a": "0", + "c": "130210", + "en": "Lingshui Co.", + "l": "7", + "ca": 3, + "n": "陵水县" + }, + "370120": { + "p": "370", + "a": "2", + "c": "370120", + "en": "Argentina", + "l": "2", + "ca": 1, + "n": "阿根廷" + }, + "310140": { + "p": "310", + "a": "0", + "c": "310140", + "en": "Dehong", + "l": "8", + "ca": 3, + "n": "德宏" + }, + "370": { + "p": "", + "a": "2", + "c": "370", + "en": "SouthAmerica", + "l": "1", + "ca": 0, + "n": "南美洲" + }, + "130": { + "p": "350490", + "a": "0", + "c": "130", + "en": "Hainan", + "l": "4", + "ca": 2, + "n": "海南" + }, + "420250040": { + "p": "400330", + "a": "2", + "c": "420250040", + "en": "Rolla", + "l": "19", + "ca": 3, + "n": "洛拉" + }, + "380160": { + "p": "380", + "a": "2", + "c": "380160", + "en": "Cook Islands", + "l": "2", + "ca": 1, + "n": "库克群岛" + }, + "300100": { + "p": "300", + "a": "0", + "c": "300100", + "en": "Wujiaqu", + "l": "7", + "ca": 3, + "n": "五家渠" + }, + "390180": { + "p": "390", + "a": "2", + "c": "390180", + "en": "Germany", + "l": "2", + "ca": 1, + "n": "德国" + }, + "430020010": { + "p": "430020", + "a": "2", + "c": "430020010", + "en": "Vancouver", + "l": "19", + "ca": 3, + "n": "温哥华" + }, + "370110": { + "p": "370", + "a": "2", + "c": "370110", + "en": "Chile", + "l": "2", + "ca": 1, + "n": "智利" + }, + "400050": { + "p": "400", + "a": "2", + "c": "400050", + "en": "Tunisia", + "l": "2", + "ca": 1, + "n": "突尼斯" + }, + "400290": { + "p": "400", + "a": "2", + "c": "400290", + "en": "Senegal", + "l": "2", + "ca": 1, + "n": "塞内加尔" + }, + "150070": { + "p": "150", + "a": "0", + "c": "150070", + "en": "Pingdingshan", + "l": "7", + "ca": 3, + "n": "平顶山" + }, + "310150": { + "p": "310", + "a": "0", + "c": "310150", + "en": "Chuxiong Prefecture", + "l": "8", + "ca": 3, + "n": "楚雄州" + }, + "380": { + "p": "", + "a": "2", + "c": "380", + "en": "Oceania", + "l": "1", + "ca": 0, + "n": "大洋洲" + }, + "140": { + "p": "350490", + "a": "0", + "c": "140", + "en": "Hebei", + "l": "4", + "ca": 2, + "n": "河北" + }, + "140020": { + "p": "140", + "a": "0", + "c": "140020", + "en": "Shijiazhuang", + "l": "7", + "ca": 3, + "n": "石家庄" + }, + "420250030": { + "p": "420250", + "a": "2", + "c": "420250030", + "en": "Kansas City", + "l": "19", + "ca": 3, + "n": "堪萨斯城" + }, + "380150": { + "p": "380", + "a": "2", + "c": "380150", + "en": "Tonga", + "l": "2", + "ca": 1, + "n": "汤加" + }, + "430090010": { + "p": "430090", + "a": "2", + "c": "430090010", + "en": "Montreal", + "l": "19", + "ca": 3, + "n": "蒙特利尔" + }, + "220130": { + "p": "220", + "a": "0", + "c": "220130", + "en": "Alxa", + "l": "9", + "ca": 3, + "n": "阿拉善盟" + }, + "390190": { + "p": "390", + "a": "2", + "c": "390190", + "en": "Austria", + "l": "2", + "ca": 1, + "n": "奥地利" + }, + "300110": { + "p": "300", + "a": "0", + "c": "300110", + "en": "Tumxuk", + "l": "7", + "ca": 3, + "n": "图木舒克" + }, + "210110": { + "p": "210", + "a": "0", + "c": "210110", + "en": "Fuxin", + "l": "7", + "ca": 3, + "n": "阜新" + }, + "370140": { + "p": "370", + "a": "2", + "c": "370140", + "en": "Paraguay", + "l": "2", + "ca": 1, + "n": "巴拉圭" + }, + "170090": { + "p": "170", + "a": "0", + "c": "170090", + "en": "Huangshi", + "l": "7", + "ca": 3, + "n": "黄石" + }, + "390": { + "p": "", + "a": "2", + "c": "390", + "en": "Europe", + "l": "1", + "ca": 0, + "n": "欧洲" + }, + "150": { + "p": "350490", + "a": "0", + "c": "150", + "en": "Henan", + "l": "4", + "ca": 2, + "n": "河南" + }, + "160040": { + "p": "160", + "a": "0", + "c": "160040", + "en": "Jiamusi", + "l": "7", + "ca": 3, + "n": "佳木斯" + }, + "310120": { + "p": "310", + "a": "0", + "c": "310120", + "en": "Wenshan Prefecture", + "l": "8", + "ca": 3, + "n": "文山州" + }, + "420230010": { + "p": "370100", + "a": "2", + "c": "420230010", + "en": "Saint Paul", + "l": "19", + "ca": 3, + "n": "圣保罗" + }, + "430090020": { + "p": "430090", + "a": "2", + "c": "430090020", + "en": "Quebec City", + "l": "19", + "ca": 3, + "n": "魁北克城" + }, + "420390040": { + "p": "420390", + "a": "2", + "c": "420390040", + "en": "Aiken", + "l": "19", + "ca": 3, + "n": "阿干" + }, + "280210": { + "p": "280", + "a": "0", + "c": "280210", + "en": "Ganzi", + "l": "8", + "ca": 3, + "n": "甘孜" + }, + "210100": { + "p": "210", + "a": "0", + "c": "210100", + "en": "Yingkou", + "l": "7", + "ca": 3, + "n": "营口" + }, + "420410010": { + "p": "420410", + "a": "2", + "c": "420410010", + "en": "Nemphis", + "l": "19", + "ca": 3, + "n": "那什维尔" + }, + "210120": { + "p": "210", + "a": "0", + "c": "210120", + "en": "Liaoyang", + "l": "7", + "ca": 3, + "n": "辽阳" + }, + "170080": { + "p": "170", + "a": "0", + "c": "170080", + "en": "Jingzhou", + "l": "7", + "ca": 3, + "n": "荆州" + }, + "370130": { + "p": "370", + "a": "2", + "c": "370130", + "en": "Uruguay", + "l": "2", + "ca": 1, + "n": "乌拉圭" + }, + "160": { + "p": "350490", + "a": "0", + "c": "160", + "en": "Heilongjiang", + "l": "4", + "ca": 2, + "n": "黑龙江" + }, + "310130": { + "p": "310", + "a": "0", + "c": "310130", + "en": "Xishuangbanna", + "l": "8", + "ca": 3, + "n": "西双版纳" + }, + "160030": { + "p": "160", + "a": "0", + "c": "160030", + "en": "Daqing", + "l": "7", + "ca": 3, + "n": "大庆" + }, + "360100": { + "p": "360", + "a": "2", + "c": "360100", + "en": "Nicaragua", + "l": "2", + "ca": 1, + "n": "尼加拉瓜" + }, + "420230020": { + "p": "420230", + "a": "2", + "c": "420230020", + "en": "Minneapolis", + "l": "19", + "ca": 3, + "n": "明尼阿波利斯" + }, + "430090030": { + "p": "430090", + "a": "2", + "c": "430090030", + "en": "Laval", + "l": "19", + "ca": 3, + "n": "拉瓦尔" + }, + "420410020": { + "p": "420410", + "a": "2", + "c": "420410020", + "en": "Memphis", + "l": "19", + "ca": 3, + "n": "孟斐斯" + }, + "420390050": { + "p": "420390", + "a": "2", + "c": "420390050", + "en": "Myrtle Beach", + "l": "19", + "ca": 3, + "n": "美特尔沙滨" + }, + "210130": { + "p": "210", + "a": "0", + "c": "210130", + "en": "Panjin", + "l": "7", + "ca": 3, + "n": "盘锦" + }, + "430030010": { + "p": "430030", + "a": "2", + "c": "430030010", + "en": "Winnipeg", + "l": "19", + "ca": 3, + "n": "温尼伯" + }, + "420380010": { + "p": "420380", + "a": "2", + "c": "420380010", + "en": "Harrisburg", + "l": "19", + "ca": 3, + "n": "哈里斯堡" + }, + "170": { + "p": "350490", + "a": "0", + "c": "170", + "en": "Hubei", + "l": "4", + "ca": 2, + "n": "湖北" + }, + "310100": { + "p": "310", + "a": "0", + "c": "310100", + "en": "Lincang", + "l": "7", + "ca": 3, + "n": "临沧" + }, + "160070": { + "p": "160", + "a": "0", + "c": "160070", + "en": "Jixi", + "l": "7", + "ca": 3, + "n": "鸡西" + }, + "360110": { + "p": "360", + "a": "2", + "c": "360110", + "en": "Costa Rica", + "l": "2", + "ca": 1, + "n": "哥斯达黎加" + }, + "430090040": { + "p": "430090", + "a": "2", + "c": "430090040", + "en": "Gatineau", + "l": "19", + "ca": 3, + "n": "加蒂诺" + }, + "420390020": { + "p": "420390", + "a": "2", + "c": "420390020", + "en": "North Charleston", + "l": "19", + "ca": 3, + "n": "查理斯敦" + }, + "420410030": { + "p": "420410", + "a": "2", + "c": "420410030", + "en": "Knoxville", + "l": "19", + "ca": 3, + "n": "诺克斯维尔" + }, + "210140": { + "p": "210", + "a": "0", + "c": "210140", + "en": "Tieling", + "l": "7", + "ca": 3, + "n": "铁岭" + }, + "180": { + "p": "350490", + "a": "0", + "c": "180", + "en": "Hunan", + "l": "4", + "ca": 2, + "n": "湖南" + }, + "160050": { + "p": "160", + "a": "0", + "c": "160050", + "en": "Mudanjiang", + "l": "7", + "ca": 3, + "n": "牡丹江" + }, + "310110": { + "p": "310", + "a": "0", + "c": "310110", + "en": "Honghe", + "l": "8", + "ca": 3, + "n": "红河" + }, + "200100": { + "p": "200", + "a": "0", + "c": "200100", + "en": "Pingxiang", + "l": "7", + "ca": 3, + "n": "萍乡" + }, + "160060": { + "p": "160", + "a": "0", + "c": "160060", + "en": "Qiqihar", + "l": "7", + "ca": 3, + "n": "齐齐哈尔" + }, + "360120": { + "p": "360", + "a": "2", + "c": "360120", + "en": "Panama", + "l": "2", + "ca": 1, + "n": "巴拿马" + }, + "420390030": { + "p": "420390", + "a": "2", + "c": "420390030", + "en": "Greenville", + "l": "19", + "ca": 3, + "n": "格林威尔" + }, + "420410040": { + "p": "420410", + "a": "2", + "c": "420410040", + "en": "Oak Ridge", + "l": "19", + "ca": 3, + "n": "橡树岭" + }, + "430090050": { + "p": "430090", + "a": "2", + "c": "430090050", + "en": "Longueuil", + "l": "19", + "ca": 3, + "n": "朗基尔" + }, + "150020": { + "p": "150", + "a": "0", + "c": "150020", + "en": "Zhengzhou", + "l": "7", + "ca": 3, + "n": "郑州" + }, + "210150": { + "p": "210", + "a": "0", + "c": "210150", + "en": "Chaoyang", + "l": "7", + "ca": 3, + "n": "朝阳" + }, + "420090": { + "p": "360030", + "a": "2", + "c": "420090", + "en": "Florida", + "l": "17", + "ca": 2, + "n": "佛罗里达州" + }, + "190": { + "p": "350490", + "a": "0", + "c": "190", + "en": "Jilin", + "l": "4", + "ca": 2, + "n": "吉林" + }, + "350330": { + "p": "350", + "a": "2", + "c": "350330", + "en": "Jordan", + "l": "2", + "ca": 1, + "n": "约旦" + }, + "140080": { + "p": "140", + "a": "0", + "c": "140080", + "en": "Tangshan", + "l": "7", + "ca": 3, + "n": "唐山" + }, + "050150": { + "p": "050", + "a": "0", + "c": "050150", + "en": "Jiangmen", + "l": "7", + "ca": 3, + "n": "江门" + }, + "400240": { + "p": "400", + "a": "2", + "c": "400240", + "en": "Gabon", + "l": "2", + "ca": 1, + "n": "加蓬" + }, + "290060": { + "p": "290", + "a": "0", + "c": "290060", + "en": "Qamdo", + "l": "7", + "ca": 3, + "n": "昌都" + }, + "400480": { + "p": "400", + "a": "2", + "c": "400480", + "en": "Mozambique", + "l": "2", + "ca": 1, + "n": "莫桑比克" + }, + "200110": { + "p": "200", + "a": "0", + "c": "200110", + "en": "Xinyu", + "l": "7", + "ca": 3, + "n": "新余" + }, + "080120": { + "p": "080", + "a": "0", + "c": "080120", + "en": "Fuyang", + "l": "7", + "ca": 3, + "n": "阜阳" + }, + "360130": { + "p": "360", + "a": "2", + "c": "360130", + "en": "Bahamas", + "l": "2", + "ca": 1, + "n": "巴哈马" + }, + "420260020": { + "p": "420260", + "a": "2", + "c": "420260020", + "en": "Billings", + "l": "19", + "ca": 3, + "n": "比林斯" + }, + "430090060": { + "p": "430090", + "a": "2", + "c": "430090060", + "en": "Sherbrooke", + "l": "19", + "ca": 3, + "n": "舍布鲁克" + }, + "130040": { + "p": "130", + "a": "0", + "c": "130040", + "en": "Sansha", + "l": "7", + "ca": 3, + "n": "三沙" + }, + "050140": { + "p": "050", + "a": "0", + "c": "050140", + "en": "Zhuhai", + "l": "7", + "ca": 3, + "n": "珠海" + }, + "350340": { + "p": "350", + "a": "2", + "c": "350340", + "en": "Lebanon", + "l": "2", + "ca": 1, + "n": "黎巴嫩" + }, + "360150": { + "p": "360", + "a": "2", + "c": "360150", + "en": "Jamaica", + "l": "2", + "ca": 1, + "n": "牙买加" + }, + "140090": { + "p": "140", + "a": "0", + "c": "140090", + "en": "Zhangjiakou", + "l": "7", + "ca": 3, + "n": "张家口" + }, + "350100": { + "p": "350", + "a": "2", + "c": "350100", + "en": "Burma", + "l": "2", + "ca": 1, + "n": "缅甸" + }, + "290070": { + "p": "290", + "a": "0", + "c": "290070", + "en": "Nagqu", + "l": "7", + "ca": 3, + "n": "那曲" + }, + "400230": { + "p": "400", + "a": "2", + "c": "400230", + "en": "Equatorial Guinea", + "l": "2", + "ca": 1, + "n": "赤道几内亚" + }, + "400470": { + "p": "400", + "a": "2", + "c": "400470", + "en": "Malawi", + "l": "2", + "ca": 1, + "n": "马拉维" + }, + "200120": { + "p": "200", + "a": "0", + "c": "200120", + "en": "Yingtan", + "l": "7", + "ca": 3, + "n": "鹰潭" + }, + "080130": { + "p": "080", + "a": "0", + "c": "080130", + "en": "Suzhou", + "l": "7", + "ca": 3, + "n": "宿州" + }, + "290080": { + "p": "290", + "a": "0", + "c": "290080", + "en": "Ngari", + "l": "10", + "ca": 3, + "n": "阿里" + }, + "360140": { + "p": "360", + "a": "2", + "c": "360140", + "en": "Cuba", + "l": "2", + "ca": 1, + "n": "古巴" + }, + "420260010": { + "p": "420260", + "a": "2", + "c": "420260010", + "en": "Heldna", + "l": "19", + "ca": 3, + "n": "海伦娜" + }, + "420110010": { + "p": "420110", + "a": "2", + "c": "420110010", + "en": "Honolulu", + "l": "19", + "ca": 3, + "n": "檀香山" + }, + "350350": { + "p": "350", + "a": "2", + "c": "350350", + "en": "Israel", + "l": "2", + "ca": 1, + "n": "以色列" + }, + "360160": { + "p": "360", + "a": "2", + "c": "360160", + "en": "Haiti", + "l": "2", + "ca": 1, + "n": "海地" + }, + "050130": { + "p": "050", + "a": "0", + "c": "050130", + "en": "Zhongshan", + "l": "7", + "ca": 3, + "n": "中山" + }, + "350110": { + "p": "350", + "a": "2", + "c": "350110", + "en": "Thailand", + "l": "2", + "ca": 1, + "n": "泰国" + }, + "400220": { + "p": "400", + "a": "2", + "c": "400220", + "en": "Cameroon", + "l": "2", + "ca": 1, + "n": "喀麦隆" + }, + "400460": { + "p": "400", + "a": "2", + "c": "400460", + "en": "Zimbabwe", + "l": "2", + "ca": 1, + "n": "津巴布韦" + }, + "070100": { + "p": "070", + "a": "0", + "c": "070100", + "en": "Quzhou", + "l": "7", + "ca": 3, + "n": "衢州" + }, + "080140": { + "p": "080", + "a": "0", + "c": "080140", + "en": "Lu'an", + "l": "7", + "ca": 3, + "n": "六安" + }, + "350360": { + "p": "350", + "a": "2", + "c": "350360", + "en": "Palestine", + "l": "2", + "ca": 1, + "n": "巴勒斯坦" + }, + "060170": { + "p": "060", + "a": "0", + "c": "060170", + "en": "Suqian", + "l": "7", + "ca": 3, + "n": "宿迁" + }, + "280230": { + "p": "280", + "a": "0", + "c": "280230", + "en": "Liangshan", + "l": "8", + "ca": 3, + "n": "凉山" + }, + "130060": { + "p": "130", + "a": "0", + "c": "130060", + "en": "Wenchang", + "l": "7", + "ca": 3, + "n": "文昌" + }, + "070110": { + "p": "070", + "a": "0", + "c": "070110", + "en": "Lishui", + "l": "7", + "ca": 3, + "n": "丽水" + }, + "350120": { + "p": "350", + "a": "2", + "c": "350120", + "en": "Malaysia", + "l": "2", + "ca": 1, + "n": "马来西亚" + }, + "400210": { + "p": "400", + "a": "2", + "c": "400210", + "en": "Central Africa", + "l": "2", + "ca": 1, + "n": "中非" + }, + "360170": { + "p": "360", + "a": "2", + "c": "360170", + "en": "Dominican Republic", + "l": "2", + "ca": 1, + "n": "多米尼加" + }, + "400450": { + "p": "400", + "a": "2", + "c": "400450", + "en": "Angola", + "l": "2", + "ca": 1, + "n": "安哥拉" + }, + "050120": { + "p": "050", + "a": "0", + "c": "050120", + "en": "Zhaoqing", + "l": "7", + "ca": 3, + "n": "肇庆" + }, + "080150": { + "p": "080", + "a": "0", + "c": "080150", + "en": "Bozhou", + "l": "7", + "ca": 3, + "n": "亳州" + }, + "350370": { + "p": "350", + "a": "2", + "c": "350370", + "en": "Saudi Arabia", + "l": "2", + "ca": 1, + "n": "沙特阿拉伯" + }, + "280220": { + "p": "280", + "a": "0", + "c": "280220", + "en": "Aba", + "l": "8", + "ca": 3, + "n": "阿坝" + }, + "060160": { + "p": "060", + "a": "0", + "c": "060160", + "en": "Taizhou", + "l": "7", + "ca": 3, + "n": "泰州" + }, + "130070": { + "p": "130", + "a": "0", + "c": "130070", + "en": "Qionghai", + "l": "7", + "ca": 3, + "n": "琼海" + }, + "070120": { + "p": "070", + "a": "0", + "c": "070120", + "en": "Zhoushan", + "l": "7", + "ca": 3, + "n": "舟山" + }, + "150080": { + "p": "150", + "a": "0", + "c": "150080", + "en": "Xinxiang", + "l": "7", + "ca": 3, + "n": "新乡" + }, + "420290": { + "p": "360030", + "a": "2", + "c": "420290", + "en": "New Hampshire", + "l": "17", + "ca": 2, + "n": "新罕布什尔州" + }, + "350130": { + "p": "350", + "a": "2", + "c": "350130", + "en": "Brunei", + "l": "2", + "ca": 1, + "n": "文莱" + }, + "290020": { + "p": "290", + "a": "0", + "c": "290020", + "en": "Lhasa", + "l": "7", + "ca": 3, + "n": "拉萨" + }, + "360180": { + "p": "360", + "a": "2", + "c": "360180", + "en": "Antigua and Barbuda", + "l": "2", + "ca": 1, + "n": "安提瓜和巴布达" + }, + "420050": { + "p": "360030", + "a": "2", + "c": "420050", + "en": "California", + "l": "17", + "ca": 2, + "n": "加州" + }, + "120030": { + "p": "120", + "a": "0", + "c": "120030", + "en": "Zunyi", + "l": "7", + "ca": 3, + "n": "遵义" + }, + "420100030": { + "p": "420100", + "a": "2", + "c": "420100030", + "en": "Macon", + "l": "19", + "ca": 3, + "n": "梅肯" + }, + "400040": { + "p": "400", + "a": "2", + "c": "400040", + "en": "Sultan", + "l": "2", + "ca": 1, + "n": "苏丹" + }, + "140040": { + "p": "140", + "a": "0", + "c": "140040", + "en": "Chengde", + "l": "7", + "ca": 3, + "n": "承德" + }, + "050110": { + "p": "050", + "a": "0", + "c": "050110", + "en": "Zhanjiang", + "l": "7", + "ca": 3, + "n": "湛江" + }, + "140030": { + "p": "140", + "a": "0", + "c": "140030", + "en": "Baoding", + "l": "7", + "ca": 3, + "n": "保定" + }, + "420250020": { + "p": "420250", + "a": "2", + "c": "420250020", + "en": "Saint Louis", + "l": "19", + "ca": 3, + "n": "圣路易斯" + }, + "350380": { + "p": "350", + "a": "2", + "c": "350380", + "en": "Bahrain", + "l": "2", + "ca": 1, + "n": "巴林" + }, + "350140": { + "p": "350", + "a": "2", + "c": "350140", + "en": "Singapore", + "l": "2", + "ca": 1, + "n": "新加坡" + }, + "080160": { + "p": "080", + "a": "0", + "c": "080160", + "en": "Chizhou", + "l": "7", + "ca": 3, + "n": "池州" + }, + "060150": { + "p": "060", + "a": "0", + "c": "060150", + "en": "Yancheng", + "l": "7", + "ca": 3, + "n": "盐城" + }, + "430070090": { + "p": "430070", + "a": "2", + "c": "430070090", + "en": "Kitchener", + "l": "19", + "ca": 3, + "n": "基奇纳" + }, + "280050": { + "p": "280", + "a": "0", + "c": "280050", + "en": "Mianyang", + "l": "7", + "ca": 3, + "n": "绵阳" + }, + "400270": { + "p": "400", + "a": "2", + "c": "400270", + "en": "Mauritania", + "l": "2", + "ca": 1, + "n": "毛里塔尼亚" + }, + "430070080": { + "p": "430070", + "a": "2", + "c": "430070080", + "en": "Vaughan", + "l": "19", + "ca": 3, + "n": "旺市" + }, + "150090": { + "p": "150", + "a": "0", + "c": "150090", + "en": "Jiaozuo", + "l": "7", + "ca": 3, + "n": "焦作" + }, + "290030": { + "p": "290", + "a": "0", + "c": "290030", + "en": "Xigaze", + "l": "7", + "ca": 3, + "n": "日喀则" + }, + "420060": { + "p": "360030", + "a": "2", + "c": "420060", + "en": "Colorado", + "l": "17", + "ca": 2, + "n": "科罗拉多州" + }, + "120020": { + "p": "120", + "a": "0", + "c": "120020", + "en": "Guiyang", + "l": "7", + "ca": 3, + "n": "贵阳" + }, + "420100020": { + "p": "420100", + "a": "2", + "c": "420100020", + "en": "Columbus", + "l": "19", + "ca": 3, + "n": "哥伦布" + }, + "360190": { + "p": "360", + "a": "2", + "c": "360190", + "en": "Saint Kitts and Nevis", + "l": "2", + "ca": 1, + "n": "圣基茨和尼维斯" + }, + "continents": { + "en": "ALL CONTINENTS", + "n": "全部大洲" + }, + "140050": { + "p": "140", + "a": "0", + "c": "140050", + "en": "Handan", + "l": "7", + "ca": 3, + "n": "邯郸" + }, + "400030": { + "p": "400", + "a": "2", + "c": "400030", + "en": "Libya", + "l": "2", + "ca": 1, + "n": "利比亚" + }, + "350390": { + "p": "350", + "a": "2", + "c": "350390", + "en": "Qatar", + "l": "2", + "ca": 1, + "n": "卡塔尔" + }, + "420250010": { + "p": "420250", + "a": "2", + "c": "420250010", + "en": "Jefferson City", + "l": "19", + "ca": 3, + "n": "杰佛逊城" + }, + "350150": { + "p": "350", + "a": "2", + "c": "350150", + "en": "Indonesia", + "l": "2", + "ca": 1, + "n": "印度尼西亚" + }, + "080170": { + "p": "080", + "a": "0", + "c": "080170", + "en": "Xuancheng", + "l": "7", + "ca": 3, + "n": "宣城" + }, + "430090": { + "p": "360020", + "a": "2", + "c": "430090", + "en": "Québec", + "l": "4", + "ca": 2, + "n": "魁北克省" + }, + "060140": { + "p": "060", + "a": "0", + "c": "060140", + "en": "Huai'an", + "l": "7", + "ca": 3, + "n": "淮安" + }, + "280040": { + "p": "280", + "a": "0", + "c": "280040", + "en": "Luzhou", + "l": "7", + "ca": 3, + "n": "泸州" + }, + "430070070": { + "p": "430070", + "a": "2", + "c": "430070070", + "en": "Markham", + "l": "19", + "ca": 3, + "n": "万锦" + }, + "420070": { + "p": "360030", + "a": "2", + "c": "420070", + "en": "Connecticut", + "l": "17", + "ca": 2, + "n": "康涅狄格州" + }, + "400020": { + "p": "400", + "a": "2", + "c": "400020", + "en": "Egypt", + "l": "2", + "ca": 1, + "n": "埃及" + }, + "140060": { + "p": "140", + "a": "0", + "c": "140060", + "en": "Langfang", + "l": "7", + "ca": 3, + "n": "廊坊" + }, + "400260": { + "p": "400", + "a": "2", + "c": "400260", + "en": "Sao Tome and Principe", + "l": "2", + "ca": 1, + "n": "圣普" + }, + "290040": { + "p": "290", + "a": "0", + "c": "290040", + "en": "Nyingchi", + "l": "7", + "ca": 3, + "n": "林芝" + }, + "430060030": { + "p": "430060", + "a": "2", + "c": "430060030", + "en": "Halifax", + "l": "19", + "ca": 3, + "n": "哈利法克斯" + }, + "420100010": { + "p": "420100", + "a": "2", + "c": "420100010", + "en": "Atlanta", + "l": "19", + "ca": 3, + "n": "亚特兰大" + }, + "350160": { + "p": "350", + "a": "2", + "c": "350160", + "en": "East Timor", + "l": "2", + "ca": 1, + "n": "东帝汶" + }, + "060130": { + "p": "060", + "a": "0", + "c": "060130", + "en": "Zhenjiang", + "l": "7", + "ca": 3, + "n": "镇江" + }, + "280030": { + "p": "280", + "a": "0", + "c": "280030", + "en": "Leshan", + "l": "7", + "ca": 3, + "n": "乐山" + }, + "400490": { + "p": "400", + "a": "2", + "c": "400490", + "en": "Botswana", + "l": "2", + "ca": 1, + "n": "博茨瓦纳" + }, + "430070060": { + "p": "390220", + "a": "2", + "c": "430070060", + "en": "London", + "l": "19", + "ca": 3, + "n": "伦敦" + }, + "130020": { + "p": "130", + "a": "0", + "c": "130020", + "en": "Haikou", + "l": "7", + "ca": 3, + "n": "海口" + }, + "420080": { + "p": "360030", + "a": "2", + "c": "420080", + "en": "Delaware", + "l": "17", + "ca": 2, + "n": "特拉华州" + }, + "140070": { + "p": "140", + "a": "0", + "c": "140070", + "en": "Qinhuangdao", + "l": "7", + "ca": 3, + "n": "秦皇岛" + }, + "290050": { + "p": "290", + "a": "0", + "c": "290050", + "en": "Shannan", + "l": "7", + "ca": 3, + "n": "山南" + }, + "400250": { + "p": "400", + "a": "2", + "c": "400250", + "en": "The Republic of the Congo", + "l": "2", + "ca": 1, + "n": "刚果(布)" + }, + "350170": { + "p": "350", + "a": "2", + "c": "350170", + "en": "Nepal", + "l": "2", + "ca": 1, + "n": "尼泊尔" + }, + "280020": { + "p": "280", + "a": "0", + "c": "280020", + "en": "Chengdu", + "l": "7", + "ca": 3, + "n": "成都" + }, + "060120": { + "p": "060", + "a": "0", + "c": "060120", + "en": "Yangzhou", + "l": "7", + "ca": 3, + "n": "扬州" + }, + "420260030": { + "p": "420260", + "a": "2", + "c": "420260030", + "en": "Missoula", + "l": "19", + "ca": 3, + "n": "密苏拉" + }, + "130030": { + "p": "130", + "a": "0", + "c": "130030", + "en": "Sanya", + "l": "7", + "ca": 3, + "n": "三亚" + } + } \ No newline at end of file diff --git a/src/main/resources/config.yaml b/src/main/resources/config.yaml index f21739a1..cee924d3 100644 --- a/src/main/resources/config.yaml +++ b/src/main/resources/config.yaml @@ -1,49 +1,47 @@ -# 带[ ]括号的,就是多选,不带的就是单选,所有选项的 开对应true,关对应false,自行根据需要调整 boss: - debugger: false # 开发者模式,默认为false即可 - sayHi: "您好,我有8年工作经验,还有AIGC大模型、Java,Python,Golang和运维的相关经验,希望应聘这个岗位,期待可以与您进一步沟通,谢谢!" #必须要关闭boss的自动打招呼 - keywords: [ "大模型","Python","Golang","Java" ] # 需要搜索的职位,会依次投递 - industry: [ "不限" ] # 公司行业,只能选三个,相关代码枚举的部分,如果需要其他的需要自己找 - cityCode: [ "上海" ] # 只列举了部分,如果没有的需要自己找:目前支持的:全国 北京 上海 杭州 广州 深圳 成都 天津 - experience: [ "5-10年" ] # 工作经验:"应届毕业生", "1年以下", "1-3年", "3-5年", "5-10年", "10年以上" - jobType: "不限" #求职类型:"全职", "兼职" - salary: "20-50K" # 薪资(单选):"3K以下", "3-5K", "5-10K", "10-20K", "20-50K", "50K以上" - degree: [ "不限" ] # 学历: "初中及以下", "中专/中技", "高中", "大专", "本科", "硕士", "博士" - scale: [ "不限" ] # 公司规模:"0-20人", "20-99人", "100-499人", "500-999人", "1000-9999人", "10000人以上" - stage: [ "不限" ] # "未融资", "天使轮", "A轮", "B轮", "C轮", "D轮及以上", "已上市", "不需要融资" - expectedSalary: [ 15,25 ] #期望薪资,单位为K,第一个数字为最低薪资,第二个数字为最高薪资,只填一个数字默认为最低薪水 - waitTime: 10 #每投递一个岗位,等待几秒 - filterDeadHR: true # 是否过滤不活跃HR,该选项会过滤半年前活跃的HR - enableAI: true # 开启AI检测与自动生成打招呼语 - sendImgResume: false # 是否发送图片简历 - deadStatus: [ "2周内活跃","本月活跃","2月内活跃","半年前活跃" ] # 过滤掉HR状态 - -job51: - jobArea: [ "上海" ] #工作地区:目前只有【北京 成都 上海 广州 深圳】 - keywords: [ "java", "python", "go", "golang", "大模型", "软件工程师" ] #关键词:依次投递 - salary: [ "不限" ] #薪资范围:只能选5个【"2千以下", "2-3千", "3-4.5千", "4.5-6千", "6-8千", "0.8-1万", "1-1.5万", "1.5-2万", "2-3万", "3-4万", "4-5万", "5万以上"】 - -lagou: - keywords: [ "AI工程师","Java","Golang","Python" ] #搜索关键词 - cityCode: "上海" #拉勾城市名没有限制,直接填写即可 - salary: "不限" #薪资【"不限","2k以下", "2k-5k", "5k-10k", "10k-15k", "15k-25k", "25k-50k", "50k以上"】 - scale: [ "不限" ] #公司规模【"不限","少于15人", "15-50人", "50-150人", "150-500人", "500-2000人", "2000人以上"】 - gj: "在校/应届,3年及以下" - -liepin: - cityCode: "上海" # 目前支持的:全国 北京 上海 广州 深圳 成都 - keywords: [ "Java", "Python", "Golang", "大模型" ] - salary: "15$30" # 填 15$30 代表 15k-30k - pubTime: "30" # 发布时间,单位天 - -zhilian: - cityCode: "上海" - salary: "25001,35000" #薪资区间 - keywords: [ "AI", "Java", "Python", "Golang" ] - -ai: - introduce: "我熟练使用Java Python Golang语言进行开发,目前主要方向为AI开发,Java熟悉Spring Boot、Cloud生态体系,擅长MySQL、Oracle、PostgreSQL等关系型数据库以及MongoDB、Redis等非关系型数据库。熟悉Docker、Kubernetes等容器化技术,掌握WebSocket、Netty等通信协议,拥有即时通讯系统的开发经验。熟练使用MyBatis-Plus、Spring Data、Django ORM等ORM框架,熟练使用Python、Golang开发,具备机器学习、深度学习及大语言模型的开发与部署经验。此外,我熟悉前端开发,涉及Vue、React、Nginx配置及PHP框架应用" #这是喂给AI的提示词,主要介绍自己的优势 - prompt: "我目前在找工作,%s,我期望的的岗位方向是【%s】,目前我需要投递的岗位名称是【%s】,这个岗位的要求是【%s】,如果这个岗位和我的期望与经历基本符合,注意是基本符合,那么请帮我写一个给HR打招呼的文本发给我,如果这个岗位和我的期望经历完全不相干,直接返回false给我,注意只要返回我需要的内容即可,不要有其他的语气助词,重点要突出我和岗位的匹配度以及我的优势,我自己写的招呼语是:【%s】,你可以参照我自己写的根据岗位情况进行适当调整" #这是AI的提示词,可以自行修改 - -bot: - is_send: true #开启企业微信消息推送 + customCityCode: {珠海: '101280700', 佛山: '101280800'} + keywords: [Java] + cityCode: [广州] + apiDomain: https://9d09-120-85-107-188.ngrok-free.app + debugger: false + keyFilter: false + degree: [本科] + scale: [不限] + industry: [不限] + enableAIJobMatchDetection: true + experience: [5-10年] + salary: 10-20K + checkStateOwned: false + sayHi: | + 您好,我具备多年 Java Web 开发经验,完整经历从 JSP/Servlet 到 SSM(Spring + Spring MVC + MyBatis),再到 Spring Boot / Spring Cloud 微服务架构的演进过程,熟悉系统拆分、服务治理、配置中心、链路追踪等核心组件;掌握 Redis、Kafka、Nacos、Docker、Jenkins 等中间件与持续集成运维工具,具备微服务系统的全流程开发与交付能力。 + 前端方面,经历了从传统 HTML/CSS/JavaScript 开发,到 jQuery、Bootstrap 样式框架、ExtJS 富客户端框架,再到现代 Vue + Element-UI 的组件化开发模式,熟悉前后端分离架构与 RESTful API 调用,能够独立完成页面交互、组件封装及联调工作。 + 参与过支付、商城、物流仓储、订单中心等系统,具备持续集成及运维能力。独立完成需求、完成系统设计及开发实现,也具备任务拆解与团队协作推进能力。 + filterDeadHR: true + stage: [不限] + vipKey: zhangk + deadStatus: [2周内活跃, 本月活跃, 3月内活跃, 2月内活跃, 半年前活跃] + sendImgResume: true + resumeImagePath: D:\Users\zhang\IdeaProjects\npe_get_jobs\src\main\resources\resume.jpg + resumeContent: | + 个人简历内容模板: + + 姓名:[您的姓名] + 联系方式:[您的电话/邮箱] + 工作经验:[您的工作年限] + + 主要技能: + - Java开发经验丰富,熟练使用Spring Boot、Spring Cloud等框架 + - 具备微服务架构设计与开发能力 + - 熟悉MySQL、Redis等数据库技术 + - 掌握前端技术Vue、React等 + + 工作经历: + [详细工作经历描述] + + 项目经验: + [主要项目经验说明] + recommendJobs: false + expectedSalary: [15, 25] + jobType: 全职 + waitTime: 10 +bot: {is_send: true} diff --git a/src/main/resources/data/zhilian.json b/src/main/resources/data/zhilian.json new file mode 100644 index 00000000..3fbe5ca0 --- /dev/null +++ b/src/main/resources/data/zhilian.json @@ -0,0 +1,41552 @@ +{ + "code": 200, + "message": "", + "data": { + "companyType": [ + { + "parentCode": null, + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "1", + "parentCode": null, + "name": "国企", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "2;3", + "parentCode": null, + "name": "外企", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "4", + "parentCode": null, + "name": "合资", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "5", + "parentCode": null, + "name": "民营", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "9", + "parentCode": null, + "name": "上市公司", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "8", + "parentCode": null, + "name": "股份制企业", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "6;10", + "parentCode": null, + "name": "事业单位", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "11;12;13;14;15;16;7", + "parentCode": null, + "name": "其他", + "en_name": "", + "deleted": false, + "sublist": [] + } + ], + "salaryType": [ + { + "code": "0000,9999999", + "parentCode": null, + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "0000,4000", + "parentCode": null, + "name": "4K以下", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "4001,6000", + "parentCode": null, + "name": "4K-6K", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "6001,8000", + "parentCode": null, + "name": "6K-8K", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "8001,10000", + "parentCode": null, + "name": "8K-10K", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "10001,15000", + "parentCode": null, + "name": "10K-15K", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "15001,25000", + "parentCode": null, + "name": "15K-25K", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "25001,35000", + "parentCode": null, + "name": "25K-35K", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "35001,50000", + "parentCode": null, + "name": "35K-50K", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "50001,9999999", + "parentCode": null, + "name": "50K以上", + "en_name": "", + "deleted": false, + "sublist": [] + } + ], + "jobType": [ + { + "code": "-1", + "parentCode": null, + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "19000000000000", + "parentCode": null, + "name": "销售/商务拓展", + "en_name": "Sales/BD", + "deleted": false, + "sublist": [ + { + "code": "19000200000000", + "parentCode": "19000000000000", + "name": "销售顾问", + "en_name": "Salesperson", + "deleted": false, + "sublist": [ + { + "code": "19000200100000", + "parentCode": "19000200000000", + "name": "销售顾问", + "en_name": "Sales Consultant", + "deleted": false, + "sublist": [] + }, + { + "code": "12000300060000", + "parentCode": "19000200000000", + "name": "客户经理", + "en_name": "Account Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "19000200020000", + "parentCode": "19000200000000", + "name": "大客户代表", + "en_name": "Key Account Representative", + "deleted": false, + "sublist": [] + }, + { + "code": "19000200030000", + "parentCode": "19000200000000", + "name": "电话销售", + "en_name": "Tele-sales", + "deleted": false, + "sublist": [] + }, + { + "code": "19000200060000", + "parentCode": "19000200000000", + "name": "商品销售", + "en_name": "Product Sales", + "deleted": false, + "sublist": [] + }, + { + "code": "19000200080000", + "parentCode": "19000200000000", + "name": "网络销售", + "en_name": "Internet Sales", + "deleted": false, + "sublist": [] + }, + { + "code": "19000200070000", + "parentCode": "19000200000000", + "name": "外贸业务员", + "en_name": "Foreign Trade Salesman", + "deleted": false, + "sublist": [] + }, + { + "code": "19000200180000", + "parentCode": "19000200000000", + "name": "外贸经理", + "en_name": "Foreign Trade Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "19000200090000", + "parentCode": "19000200000000", + "name": "销售工程师", + "en_name": "Sales Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "19000200190000", + "parentCode": "19000200000000", + "name": "代理商销售", + "en_name": "Agent Sales", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "19000100000000", + "parentCode": "19000000000000", + "name": "商务拓展", + "en_name": "BD", + "deleted": false, + "sublist": [ + { + "code": "19000100010000", + "parentCode": "19000100000000", + "name": "BD经理", + "en_name": "BD Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "19000100040000", + "parentCode": "19000100000000", + "name": "渠道销售", + "en_name": "Channel Sales", + "deleted": false, + "sublist": [] + }, + { + "code": "19000100070000", + "parentCode": "19000100000000", + "name": "渠道经理", + "en_name": "Channel manager", + "deleted": false, + "sublist": [] + }, + { + "code": "19000100080000", + "parentCode": "19000100000000", + "name": "商务专员", + "en_name": "Business Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "19000100090000", + "parentCode": "19000100000000", + "name": "商务经理", + "en_name": "Business Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "19000100100000", + "parentCode": "19000100000000", + "name": "商务总监", + "en_name": "Commercial Director", + "deleted": false, + "sublist": [] + }, + { + "code": "19000100110000", + "parentCode": "19000100000000", + "name": "商务渠道", + "en_name": "Business Channel", + "deleted": false, + "sublist": [] + }, + { + "code": "19000100060000", + "parentCode": "19000100000000", + "name": "招商", + "en_name": "Property Lease/Rent", + "deleted": false, + "sublist": [] + }, + { + "code": "19000100020000", + "parentCode": "19000100000000", + "name": "选址开发", + "en_name": "Site Selection And Development", + "deleted": false, + "sublist": [] + }, + { + "code": "19000100030000", + "parentCode": "19000100000000", + "name": "广告销售", + "en_name": "Advertising Sales", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "19000300000000", + "parentCode": "19000000000000", + "name": "销售管理", + "en_name": "Sales Management", + "deleted": false, + "sublist": [ + { + "code": "19000300030000", + "parentCode": "19000300000000", + "name": "销售团队经理", + "en_name": "sales team manager", + "deleted": false, + "sublist": [] + }, + { + "code": "19000300010000", + "parentCode": "19000300000000", + "name": "城市经理", + "en_name": "City Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "19000300040000", + "parentCode": "19000300000000", + "name": "销售总监", + "en_name": "Sales Director", + "deleted": false, + "sublist": [] + }, + { + "code": "19000300020000", + "parentCode": "19000300000000", + "name": "销售督导", + "en_name": "Sales Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "19000300050000", + "parentCode": "19000300000000", + "name": "区域总监", + "en_name": "Regional Director", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "14000000000000", + "parentCode": null, + "name": "人事/行政/财务/法务", + "en_name": "HR/Administration/Accounting/Legal", + "deleted": false, + "sublist": [ + { + "code": "14000600000000", + "parentCode": "14000000000000", + "name": "人事", + "en_name": "HR", + "deleted": false, + "sublist": [ + { + "code": "14000600100000", + "parentCode": "14000600000000", + "name": "人力资源专员/助理", + "en_name": "Hr Specialist / Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600090000", + "parentCode": "14000600000000", + "name": "人力资源经理/主管", + "en_name": "HR Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600110000", + "parentCode": "14000600000000", + "name": "人力资源总监", + "en_name": "HR Director", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600200000", + "parentCode": "14000600000000", + "name": "人力资源VP/CHO", + "en_name": "Hr Vp / Cho", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600010000", + "parentCode": "14000600000000", + "name": "HRBP", + "en_name": "HRBP ", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600160000", + "parentCode": "14000600000000", + "name": "招聘专员/助理", + "en_name": "Recruitment specialist or assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600190000", + "parentCode": "14000600000000", + "name": "招聘经理/主管", + "en_name": "Recruitment manager or supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600040000", + "parentCode": "14000600000000", + "name": "绩效考核专员", + "en_name": "Performance Assessment Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600030000", + "parentCode": "14000600000000", + "name": "绩效考核经理", + "en_name": "Performance Assessment Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600140000", + "parentCode": "14000600000000", + "name": "薪酬福利", + "en_name": "Compensation", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600050000", + "parentCode": "14000600000000", + "name": "培训经理/主管", + "en_name": "Training Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600060000", + "parentCode": "14000600000000", + "name": "培训专员/助理", + "en_name": "Training Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600150000", + "parentCode": "14000600000000", + "name": "员工关系", + "en_name": "Employee Relations", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600070000", + "parentCode": "14000600000000", + "name": "企业文化", + "en_name": "Corporate Culture", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600130000", + "parentCode": "14000600000000", + "name": "社保专员", + "en_name": "Social Security Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600170000", + "parentCode": "14000600000000", + "name": "组织发展(OD)", + "en_name": "organization development(OD)", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600120000", + "parentCode": "14000600000000", + "name": "人事信息系统(HRIS)管理", + "en_name": "HRIS Management", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600180000", + "parentCode": "14000600000000", + "name": "人事测评", + "en_name": "HR Assessment", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600020000", + "parentCode": "14000600000000", + "name": "工会干事", + "en_name": "Trade Union Officer", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "14000100000000", + "parentCode": "14000000000000", + "name": "财务", + "en_name": "Accounting", + "deleted": false, + "sublist": [ + { + "code": "14000100100000", + "parentCode": "14000100000000", + "name": "出纳", + "en_name": "General Cashier", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100050000", + "parentCode": "14000100000000", + "name": "财务助理", + "en_name": "Financial Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100220000", + "parentCode": "14000100000000", + "name": "财务专员/助理", + "en_name": "Financial officer", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100040000", + "parentCode": "14000100000000", + "name": "财务主管", + "en_name": "Financial Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100030000", + "parentCode": "14000100000000", + "name": "财务经理/主管", + "en_name": "Finance Manager/Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100060000", + "parentCode": "14000100000000", + "name": "财务总监", + "en_name": "Financial Director", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100230000", + "parentCode": "14000100000000", + "name": "会计", + "en_name": "Accounting", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100150000", + "parentCode": "14000100000000", + "name": "会计助理", + "en_name": "Accounting Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100140000", + "parentCode": "14000100000000", + "name": "会计经理", + "en_name": "Accounting Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100210000", + "parentCode": "14000100000000", + "name": "总账会计", + "en_name": "GL Accountant", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100080000", + "parentCode": "14000100000000", + "name": "成本会计", + "en_name": "Cost Accounting", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100090000", + "parentCode": "14000100000000", + "name": "成本经理/主管", + "en_name": "Cost Manager ", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100070000", + "parentCode": "14000100000000", + "name": "成本管理", + "en_name": "Cost Management", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100180000", + "parentCode": "14000100000000", + "name": "注册会计师", + "en_name": "Registered Accountant", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100120000", + "parentCode": "14000100000000", + "name": "固定资产会计", + "en_name": "Fixed Assets Accounting", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100160000", + "parentCode": "14000100000000", + "name": "审计", + "en_name": "Auditing", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100240000", + "parentCode": "14000100000000", + "name": "审计经理", + "en_name": "Audit Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100170000", + "parentCode": "14000100000000", + "name": "税务", + "en_name": "Tax", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100190000", + "parentCode": "14000100000000", + "name": "资产管理", + "en_name": "Asset management", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100200000", + "parentCode": "14000100000000", + "name": "资金管理", + "en_name": "Fund management", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100010000", + "parentCode": "14000100000000", + "name": "财务分析", + "en_name": "Financial Analysis ", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100020000", + "parentCode": "14000100000000", + "name": "财务顾问", + "en_name": "Financial Advisor", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100130000", + "parentCode": "14000100000000", + "name": "核销员", + "en_name": "Write-off", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "14000400000000", + "parentCode": "14000000000000", + "name": "行政", + "en_name": "Administration", + "deleted": false, + "sublist": [ + { + "code": "14000400030000", + "parentCode": "14000400000000", + "name": "行政专员/助理", + "en_name": "Administrative Specialist / Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "14000400080000", + "parentCode": "14000400000000", + "name": "行政经理/主管", + "en_name": "Administrative Manager or Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "14000400040000", + "parentCode": "14000400000000", + "name": "行政总监", + "en_name": "Administration Director", + "deleted": false, + "sublist": [] + }, + { + "code": "14000400070000", + "parentCode": "14000400000000", + "name": "销售助理", + "en_name": "Sales Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "14000400060000", + "parentCode": "14000400000000", + "name": "销售行政主管", + "en_name": "Sales Administration Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "14000400050000", + "parentCode": "14000400000000", + "name": "后勤", + "en_name": "Logistics", + "deleted": false, + "sublist": [] + }, + { + "code": "14000400010000", + "parentCode": "14000400000000", + "name": "党务/党群", + "en_name": "Party Affairs OR Party Masses", + "deleted": false, + "sublist": [] + }, + { + "code": "14000400090000", + "parentCode": "14000400000000", + "name": "纪检监察", + "en_name": "Discipline Inspection And Supervision", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "14000500000000", + "parentCode": "14000000000000", + "name": "合规风控/法务/律师", + "en_name": "Compliance/Risk Management/Legal", + "deleted": false, + "sublist": [ + { + "code": "14000500030000", + "parentCode": "14000500000000", + "name": "法务专员/助理", + "en_name": "Legal officer or assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "14000500120000", + "parentCode": "14000500000000", + "name": "法务经理/主管", + "en_name": "Legal manager or supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "14000500150000", + "parentCode": "14000500000000", + "name": "法务总监", + "en_name": "Legal Director", + "deleted": false, + "sublist": [] + }, + { + "code": "14000500010000", + "parentCode": "14000500000000", + "name": "法律顾问", + "en_name": "Legal Advisor", + "deleted": false, + "sublist": [] + }, + { + "code": "14000500020000", + "parentCode": "14000500000000", + "name": "风控", + "en_name": "Risk Control", + "deleted": false, + "sublist": [] + }, + { + "code": "14000500040000", + "parentCode": "14000500000000", + "name": "合规稽查", + "en_name": "Compliance", + "deleted": false, + "sublist": [] + }, + { + "code": "14000500060000", + "parentCode": "14000500000000", + "name": "民事律师", + "en_name": "Civilian Lawyer", + "deleted": false, + "sublist": [] + }, + { + "code": "14000500050000", + "parentCode": "14000500000000", + "name": "律师助理", + "en_name": "Lawyer Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "14000500070000", + "parentCode": "14000500000000", + "name": "企业律师/合规顾问", + "en_name": "Corporate Lawyer/Compliance Counsel", + "deleted": false, + "sublist": [] + }, + { + "code": "14000500080000", + "parentCode": "14000500000000", + "name": "律师", + "en_name": "Firm Lawyer", + "deleted": false, + "sublist": [] + }, + { + "code": "14000500090000", + "parentCode": "14000500000000", + "name": "诉讼律师", + "en_name": "Litigation Lawyer", + "deleted": false, + "sublist": [] + }, + { + "code": "14000500100000", + "parentCode": "14000500000000", + "name": "知识产权/专利律师", + "en_name": "IP Lawyer", + "deleted": false, + "sublist": [] + }, + { + "code": "14000500110000", + "parentCode": "14000500000000", + "name": "专利律师", + "en_name": "Patent Lawyer", + "deleted": false, + "sublist": [] + }, + { + "code": "14000500130000", + "parentCode": "14000500000000", + "name": "司法鉴定", + "en_name": "Judicial expertise", + "deleted": false, + "sublist": [] + }, + { + "code": "14000500140000", + "parentCode": "14000500000000", + "name": "法医", + "en_name": "Forensic medicine", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "14000200000000", + "parentCode": "14000000000000", + "name": "档案管理", + "en_name": "Document management", + "deleted": false, + "sublist": [ + { + "code": "14000200010000", + "parentCode": "14000200000000", + "name": "档案管理", + "en_name": "Documentary Management", + "deleted": false, + "sublist": [] + }, + { + "code": "14000200020000", + "parentCode": "14000200000000", + "name": "合同管理", + "en_name": "Contract Management", + "deleted": false, + "sublist": [] + }, + { + "code": "14000200030000", + "parentCode": "14000200000000", + "name": "图书管理", + "en_name": "Library Management", + "deleted": false, + "sublist": [] + }, + { + "code": "14000200040000", + "parentCode": "14000200000000", + "name": "资料管理", + "en_name": "File & Document Management", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "14000700000000", + "parentCode": "14000000000000", + "name": "文员/助理", + "en_name": "Assistant/Secretary", + "deleted": false, + "sublist": [ + { + "code": "14000700060000", + "parentCode": "14000700000000", + "name": "秘书/文员", + "en_name": "Secretary", + "deleted": false, + "sublist": [] + }, + { + "code": "14000700080000", + "parentCode": "14000700000000", + "name": "前台", + "en_name": "Reception", + "deleted": false, + "sublist": [] + }, + { + "code": "14000700040000", + "parentCode": "14000700000000", + "name": "录入员", + "en_name": "Entry Clerk", + "deleted": false, + "sublist": [] + }, + { + "code": "14000700050000", + "parentCode": "14000700000000", + "name": "订单处理员", + "en_name": "Order Handler", + "deleted": false, + "sublist": [] + }, + { + "code": "14000700110000", + "parentCode": "14000700000000", + "name": "统计员", + "en_name": "Statistician", + "deleted": false, + "sublist": [] + }, + { + "code": "14000700130000", + "parentCode": "14000700000000", + "name": "合约专员", + "en_name": "Contract officer", + "deleted": false, + "sublist": [] + }, + { + "code": "14000700010000", + "parentCode": "14000700000000", + "name": "IT技术文员/助理", + "en_name": "IT Secretary", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "9000000000000", + "parentCode": null, + "name": "互联网/通信及硬件", + "en_name": "IT/Internet", + "deleted": false, + "sublist": [ + { + "code": "9000300000000", + "parentCode": "9000000000000", + "name": "软件研发", + "en_name": "Software Developer", + "deleted": false, + "sublist": [ + { + "code": "9000300110000", + "parentCode": "9000300000000", + "name": "Java", + "en_name": "Java", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300150000", + "parentCode": "9000300000000", + "name": "PHP", + "en_name": "PHP", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300160000", + "parentCode": "9000300000000", + "name": "Python", + "en_name": "Python", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300010000", + "parentCode": "9000300000000", + "name": ".NET", + "en_name": ".NET", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300020000", + "parentCode": "9000300000000", + "name": "C", + "en_name": "C", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300030000", + "parentCode": "9000300000000", + "name": "C#", + "en_name": "C#", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300040000", + "parentCode": "9000300000000", + "name": "C/C++", + "en_name": "C/C++", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300050000", + "parentCode": "9000300000000", + "name": "Delphi", + "en_name": "Delphi", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300060000", + "parentCode": "9000300000000", + "name": "Erlang", + "en_name": "Erlang", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300080000", + "parentCode": "9000300000000", + "name": "GIS", + "en_name": "GIS", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300090000", + "parentCode": "9000300000000", + "name": "Golang", + "en_name": "Golang", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300120000", + "parentCode": "9000300000000", + "name": "mano", + "en_name": "Mano", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300130000", + "parentCode": "9000300000000", + "name": "Node.js", + "en_name": "Node.js", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300140000", + "parentCode": "9000300000000", + "name": "Perl", + "en_name": "Perl", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300170000", + "parentCode": "9000300000000", + "name": "Ruby", + "en_name": "Ruby", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300180000", + "parentCode": "9000300000000", + "name": "VB", + "en_name": "VB", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300190000", + "parentCode": "9000300000000", + "name": "架构师", + "en_name": "Architect", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300290000", + "parentCode": "9000300000000", + "name": "全栈工程师", + "en_name": "Full stack developer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300220000", + "parentCode": "9000300000000", + "name": "嵌入式软件开发", + "en_name": "Embedded Software Development", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300200000", + "parentCode": "9000300000000", + "name": "脚本开发", + "en_name": "Script Development", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300250000", + "parentCode": "9000300000000", + "name": "需求分析工程师", + "en_name": "Demand Analysis Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300210000", + "parentCode": "9000300000000", + "name": "配置管理", + "en_name": "Configuration Management", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300240000", + "parentCode": "9000300000000", + "name": "系统集成", + "en_name": "System Integration", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300280000", + "parentCode": "9000300000000", + "name": "云计算", + "en_name": "Cloud Computing", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300270000", + "parentCode": "9000300000000", + "name": "语音/视频/图形开发", + "en_name": "Audio/Video/Image Development", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300070000", + "parentCode": "9000300000000", + "name": "ERP技术/应用", + "en_name": "ERP application", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300260000", + "parentCode": "9000300000000", + "name": "研发经理", + "en_name": "R&D Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "9000300100000", + "parentCode": "9000300000000", + "name": "IT技术/研发总监", + "en_name": "IT R&D Director", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "9000100000000", + "parentCode": "9000000000000", + "name": "前端开发", + "en_name": "Front-End Developer", + "deleted": false, + "sublist": [ + { + "code": "9000100030000", + "parentCode": "9000100000000", + "name": "web前端", + "en_name": "Web Front-end", + "deleted": false, + "sublist": [] + }, + { + "code": "9000100040000", + "parentCode": "9000100000000", + "name": "前端开发", + "en_name": "Front-end Development", + "deleted": false, + "sublist": [] + }, + { + "code": "9000100010000", + "parentCode": "9000100000000", + "name": "HTML5", + "en_name": "HTML5", + "deleted": false, + "sublist": [] + }, + { + "code": "9000100020000", + "parentCode": "9000100000000", + "name": "JavaScript", + "en_name": "JavaScript", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "9000600000000", + "parentCode": "9000000000000", + "name": "移动研发", + "en_name": "Mobile Developer", + "deleted": false, + "sublist": [ + { + "code": "9000600010000", + "parentCode": "9000600000000", + "name": "Android", + "en_name": "Android", + "deleted": false, + "sublist": [] + }, + { + "code": "9000600040000", + "parentCode": "9000600000000", + "name": "iOS", + "en_name": "iOS", + "deleted": false, + "sublist": [] + }, + { + "code": "9000600030000", + "parentCode": "9000600000000", + "name": "COCOS2DX", + "en_name": "COCOS2DX", + "deleted": false, + "sublist": [] + }, + { + "code": "9000600050000", + "parentCode": "9000600000000", + "name": "U3D", + "en_name": "U3D", + "deleted": false, + "sublist": [] + }, + { + "code": "9000600120000", + "parentCode": "9000600000000", + "name": "UE4", + "en_name": "UE4", + "deleted": false, + "sublist": [] + }, + { + "code": "9000600070000", + "parentCode": "9000600000000", + "name": "微信开发", + "en_name": "Wechat development", + "deleted": false, + "sublist": [] + }, + { + "code": "9000600080000", + "parentCode": "9000600000000", + "name": "小程序开发", + "en_name": "Small program development", + "deleted": false, + "sublist": [] + }, + { + "code": "9000600090000", + "parentCode": "9000600000000", + "name": "小游戏开发", + "en_name": "Small game development", + "deleted": false, + "sublist": [] + }, + { + "code": "9000600110000", + "parentCode": "9000600000000", + "name": "移动开发", + "en_name": "Mobile Development", + "deleted": false, + "sublist": [] + }, + { + "code": "9000600130000", + "parentCode": "9000600000000", + "name": "鸿蒙开发工程师", + "en_name": "HarmonyOS development engineer", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "9000500000000", + "parentCode": "9000000000000", + "name": "通信及硬件研发", + "en_name": "Telecom & Hardware Developer", + "deleted": false, + "sublist": [ + { + "code": "9000500460000", + "parentCode": "9000500000000", + "name": "硬件工程师", + "en_name": "Hardware Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500470000", + "parentCode": "9000500000000", + "name": "硬件交互设计师", + "en_name": "Hardware Interaction Design Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500350000", + "parentCode": "9000500000000", + "name": "嵌入式硬件", + "en_name": "Embedded Hardware", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500540000", + "parentCode": "9000500000000", + "name": "驱动开发", + "en_name": "Drive development", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500520000", + "parentCode": "9000500000000", + "name": "无人机工程师", + "en_name": "UAV Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500120000", + "parentCode": "9000500000000", + "name": "单片机", + "en_name": "SCM", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500020000", + "parentCode": "9000500000000", + "name": "ARM开发", + "en_name": "ARM Development", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500040000", + "parentCode": "9000500000000", + "name": "DSP开发", + "en_name": "DSP Development", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500050000", + "parentCode": "9000500000000", + "name": "FPGA开发", + "en_name": "FPGA Development", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500070000", + "parentCode": "9000500000000", + "name": "PCB工程师", + "en_name": "PCB Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500080000", + "parentCode": "9000500000000", + "name": "PLC工程师", + "en_name": "PLC Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500410000", + "parentCode": "9000500000000", + "name": "通信技术工程师", + "en_name": "Communication Technology Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500440000", + "parentCode": "9000500000000", + "name": "移动通信工程师", + "en_name": "Mobile Communication Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500510000", + "parentCode": "9000500000000", + "name": "数据通信工程师", + "en_name": "data communication engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500390000", + "parentCode": "9000500000000", + "name": "通信标准化工程师", + "en_name": "Communication Standardization Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500400000", + "parentCode": "9000500000000", + "name": "通信电源工程师", + "en_name": "Communication Power Supply Engineer ", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500550000", + "parentCode": "9000500000000", + "name": "通信设备工程师", + "en_name": "Communication Equipment Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500560000", + "parentCode": "9000500000000", + "name": "通信研发工程师", + "en_name": "Communication R &Amp; D Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500530000", + "parentCode": "9000500000000", + "name": "基站工程师", + "en_name": "Base station Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500360000", + "parentCode": "9000500000000", + "name": "射频工程师", + "en_name": "RF Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500380000", + "parentCode": "9000500000000", + "name": "室分设计工程师", + "en_name": "Compartment Design Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500420000", + "parentCode": "9000500000000", + "name": "无线电工程师", + "en_name": "Radio Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500500000", + "parentCode": "9000500000000", + "name": "无线通信工程师", + "en_name": "Wireless communication engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500480000", + "parentCode": "9000500000000", + "name": "有线传输工程师", + "en_name": "Wire Transmission Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500010000", + "parentCode": "9000500000000", + "name": "5G", + "en_name": "5G", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500290000", + "parentCode": "9000500000000", + "name": "核心网工程师", + "en_name": "Core Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500090000", + "parentCode": "9000500000000", + "name": "安防系统工程师", + "en_name": "Security System Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500170000", + "parentCode": "9000500000000", + "name": "电信交换工程师", + "en_name": "Telecommunication Exchange Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500180000", + "parentCode": "9000500000000", + "name": "电信网络工程师", + "en_name": "Telecommunication Network Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500230000", + "parentCode": "9000500000000", + "name": "仿真应用工程师", + "en_name": "Simulation Application Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500270000", + "parentCode": "9000500000000", + "name": "光通信工程师", + "en_name": "Optical Communication Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500250000", + "parentCode": "9000500000000", + "name": "光传输工程师", + "en_name": "Optical Transmission Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500260000", + "parentCode": "9000500000000", + "name": "光伏系统工程师", + "en_name": "PV System Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500280000", + "parentCode": "9000500000000", + "name": "光网络工程师", + "en_name": "Optical Network Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500310000", + "parentCode": "9000500000000", + "name": "激光/光电子技术", + "en_name": "Laser/Optoelectroni", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500450000", + "parentCode": "9000500000000", + "name": "音频/视频工程师", + "en_name": "Audio/Video Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300570000", + "parentCode": "9000500000000", + "name": "转播工程师", + "en_name": "Broadcasting Engineer", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "9000200000000", + "parentCode": "9000000000000", + "name": "人工智能", + "en_name": "AI", + "deleted": false, + "sublist": [ + { + "code": "9000200010000", + "parentCode": "9000200000000", + "name": "机器视觉", + "en_name": "Machine vision", + "deleted": false, + "sublist": [] + }, + { + "code": "9000200020000", + "parentCode": "9000200000000", + "name": "机器学习", + "en_name": "Machine learning", + "deleted": false, + "sublist": [] + }, + { + "code": "9000200050000", + "parentCode": "9000200000000", + "name": "深度学习", + "en_name": "Deep Learning", + "deleted": false, + "sublist": [] + }, + { + "code": "9000200100000", + "parentCode": "9000200000000", + "name": "自然语言处理", + "en_name": "Natural Language Processing", + "deleted": false, + "sublist": [] + }, + { + "code": "9000200070000", + "parentCode": "9000200000000", + "name": "算法工程师", + "en_name": "Algorithm Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000200110000", + "parentCode": "9000200000000", + "name": "搜索算法", + "en_name": "Search algorithm", + "deleted": false, + "sublist": [] + }, + { + "code": "9000200120000", + "parentCode": "9000200000000", + "name": "推荐算法", + "en_name": "Recommendation algorithm", + "deleted": false, + "sublist": [] + }, + { + "code": "9000200080000", + "parentCode": "9000200000000", + "name": "图像识别", + "en_name": "Image Recognition", + "deleted": false, + "sublist": [] + }, + { + "code": "9000200170000", + "parentCode": "9000200000000", + "name": "音视频算法", + "en_name": "Audio and video algorithm", + "deleted": false, + "sublist": [] + }, + { + "code": "9000200130000", + "parentCode": "9000200000000", + "name": "语音识别", + "en_name": "Speech Recognition", + "deleted": false, + "sublist": [] + }, + { + "code": "9000200160000", + "parentCode": "9000200000000", + "name": "导航算法", + "en_name": "Navigation algorithm", + "deleted": false, + "sublist": [] + }, + { + "code": "9000200150000", + "parentCode": "9000200000000", + "name": "反欺诈/风控算法", + "en_name": "Anti Fraud / Risk Control Algorithm", + "deleted": false, + "sublist": [] + }, + { + "code": "9000200140000", + "parentCode": "9000200000000", + "name": "机器人算法", + "en_name": "Robot algorithm", + "deleted": false, + "sublist": [] + }, + { + "code": "9000200180000", + "parentCode": "9000200000000", + "name": "智能驾驶系统工程师", + "en_name": "Intelligent Driving System Engineer", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "9000400000000", + "parentCode": "9000000000000", + "name": "数据工程师", + "en_name": "Data Engineer", + "deleted": false, + "sublist": [ + { + "code": "9000400010000", + "parentCode": "9000400000000", + "name": "BI", + "en_name": "BI", + "deleted": false, + "sublist": [] + }, + { + "code": "9000400020000", + "parentCode": "9000400000000", + "name": "ETL工程师", + "en_name": "ETL Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000400030000", + "parentCode": "9000400000000", + "name": "Hadoop", + "en_name": "Hadoop", + "deleted": false, + "sublist": [] + }, + { + "code": "9000400040000", + "parentCode": "9000400000000", + "name": "爬虫", + "en_name": "Crawler", + "deleted": false, + "sublist": [] + }, + { + "code": "9000400050000", + "parentCode": "9000400000000", + "name": "数据采集", + "en_name": "Data collection", + "deleted": false, + "sublist": [] + }, + { + "code": "9000400060000", + "parentCode": "9000400000000", + "name": "数据仓库", + "en_name": "Data Warehouse", + "deleted": false, + "sublist": [] + }, + { + "code": "9000400110000", + "parentCode": "9000400000000", + "name": "数据库开发", + "en_name": "Database Development", + "deleted": false, + "sublist": [] + }, + { + "code": "9000400070000", + "parentCode": "9000400000000", + "name": "数据分析师", + "en_name": "Data Analyst", + "deleted": false, + "sublist": [] + }, + { + "code": "9000400080000", + "parentCode": "9000400000000", + "name": "数据架构师", + "en_name": "Data Architect", + "deleted": false, + "sublist": [] + }, + { + "code": "9000400090000", + "parentCode": "9000400000000", + "name": "数据开发", + "en_name": "Data Development", + "deleted": false, + "sublist": [] + }, + { + "code": "9000400100000", + "parentCode": "9000400000000", + "name": "数据挖掘", + "en_name": "Data Mining", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "9000700000000", + "parentCode": "9000000000000", + "name": "电子/电器/自动化", + "en_name": "Electronics or Electrical or Automation", + "deleted": false, + "sublist": [ + { + "code": "9000500150000", + "parentCode": "9000700000000", + "name": "电气工程师", + "en_name": "Electrical Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500160000", + "parentCode": "9000700000000", + "name": "电气线路设计", + "en_name": "Design of Electrical Line", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300590000", + "parentCode": "9000700000000", + "name": "自动化工程师", + "en_name": "Automation Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "10000200160000", + "parentCode": "9000700000000", + "name": "自动化设计工程师", + "en_name": "Automation Design Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500200000", + "parentCode": "9000700000000", + "name": "电子工程设计", + "en_name": "Electronic Engineering Design", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500210000", + "parentCode": "9000700000000", + "name": "电子技术研发工程师", + "en_name": "Electronic Engineering R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500220000", + "parentCode": "9000700000000", + "name": "电子元器件工程师", + "en_name": "Component Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "10000200030000", + "parentCode": "9000700000000", + "name": "电池工程师", + "en_name": "Battery Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500140000", + "parentCode": "9000700000000", + "name": "电路工程师/技术员", + "en_name": "Circuit Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500430000", + "parentCode": "9000700000000", + "name": "线路结构设计", + "en_name": "Circuit Design Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000700010000", + "parentCode": "9000700000000", + "name": "电磁兼容工程师", + "en_name": "EMC Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000700020000", + "parentCode": "9000700000000", + "name": "SMT工程师", + "en_name": "SMT Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000700080000", + "parentCode": "9000700000000", + "name": "失效分析工程师(FA)", + "en_name": "Failure Analysis Engineer (FA)", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300060000", + "parentCode": "9000700000000", + "name": "变压器与磁电工程师", + "en_name": "Transformer & Magnetoelectricity Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500130000", + "parentCode": "9000700000000", + "name": "电控工程师", + "en_name": "Electronic Control Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "10000200040000", + "parentCode": "9000700000000", + "name": "电器研发", + "en_name": "Electric Appliance R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300110000", + "parentCode": "9000700000000", + "name": "电子工程师", + "en_name": "Electronic Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500190000", + "parentCode": "9000700000000", + "name": "电子/电器工艺/制程工程师", + "en_name": "Electronic/Electrical Process Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200120000", + "parentCode": "9000700000000", + "name": "电子/电器设备工程师", + "en_name": "Electronic/Electrical Equipment Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "10000200100000", + "parentCode": "9000700000000", + "name": "家电/数码产品研发", + "en_name": "Home Appliance/Digital Product R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "10000200110000", + "parentCode": "9000700000000", + "name": "空调工程/设计", + "en_name": "Air-conditioner Design", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100040000", + "parentCode": "9000700000000", + "name": "电声/音响工程师/技术员", + "en_name": "Electroacoustic/acoustics Engineer/Technician", + "deleted": false, + "sublist": [] + }, + { + "code": "9000700110000", + "parentCode": "9000700000000", + "name": "电子元器件销售工程师", + "en_name": "Sales Engineer Of Electronic Components", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "9000800000000", + "parentCode": "9000000000000", + "name": "半导体/芯片", + "en_name": "Semiconductor or Chip", + "deleted": false, + "sublist": [ + { + "code": "9000500320000", + "parentCode": "9000800000000", + "name": "集成电路IC设计", + "en_name": "Integrated Circuit Ic Design", + "deleted": false, + "sublist": [] + }, + { + "code": "9000700030000", + "parentCode": "9000800000000", + "name": "数字前端工程师", + "en_name": "Digital front end Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000700040000", + "parentCode": "9000800000000", + "name": "数字后端工程师", + "en_name": "Digital backend Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500340000", + "parentCode": "9000800000000", + "name": "模拟芯片设计", + "en_name": "Analog Chip Design", + "deleted": false, + "sublist": [] + }, + { + "code": "9000800010000", + "parentCode": "9000800000000", + "name": "模拟版图工程师", + "en_name": "Analog Layout Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500100000", + "parentCode": "9000800000000", + "name": "版图设计工程师", + "en_name": "Layout Design Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500060000", + "parentCode": "9000800000000", + "name": "IC验证工程师", + "en_name": "IC Verification Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000700050000", + "parentCode": "9000800000000", + "name": "芯片测试工程师", + "en_name": "Chip test engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000800020000", + "parentCode": "9000800000000", + "name": "工艺整合工程师(PIE)", + "en_name": "Process Integration Engineer (Pie)", + "deleted": false, + "sublist": [] + }, + { + "code": "9000700090000", + "parentCode": "9000800000000", + "name": "封装工程师", + "en_name": "Packaging Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000700070000", + "parentCode": "9000800000000", + "name": "半导体设备工程师", + "en_name": "Semiconductor Equipment Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000700060000", + "parentCode": "9000800000000", + "name": "半导体工艺工程师", + "en_name": "Semiconductor process engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500110000", + "parentCode": "9000800000000", + "name": "半导体技术", + "en_name": "Semi-conduct Technique", + "deleted": false, + "sublist": [] + }, + { + "code": "9000700100000", + "parentCode": "9000800000000", + "name": "芯片销售工程师", + "en_name": "Chip sales engineer", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "20000000000000", + "parentCode": null, + "name": "运维/测试", + "en_name": "Maintenance/Testing", + "deleted": false, + "sublist": [ + { + "code": "20000200000000", + "parentCode": "20000000000000", + "name": "运维支持", + "en_name": "Technical Support & Maintenance", + "deleted": false, + "sublist": [ + { + "code": "20000200320000", + "parentCode": "20000200000000", + "name": "运维工程师", + "en_name": "Operation & Maintenance Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200330000", + "parentCode": "20000200000000", + "name": "运维开发工程师", + "en_name": "Server Operations Develop Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200340000", + "parentCode": "20000200000000", + "name": "运维总监", + "en_name": "Operation And Maintenance Director", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200260000", + "parentCode": "20000200000000", + "name": "系统工程师", + "en_name": "System Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200040000", + "parentCode": "20000200000000", + "name": "Helpdesk", + "en_name": "Helpdesk", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200220000", + "parentCode": "20000200000000", + "name": "网络运维", + "en_name": "Network Maintenance", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200020000", + "parentCode": "20000200000000", + "name": "ERP实施顾问", + "en_name": "ERP Implementation Consultant", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200270000", + "parentCode": "20000200000000", + "name": "现场应用工程师(FAE)", + "en_name": "FAE", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200180000", + "parentCode": "20000200000000", + "name": "DBA", + "en_name": "Dba", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200190000", + "parentCode": "20000200000000", + "name": "网络工程师", + "en_name": "Network Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200200000", + "parentCode": "20000200000000", + "name": "网络管理员", + "en_name": "Network Admin", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200250000", + "parentCode": "20000200000000", + "name": "系统管理员", + "en_name": "System Admin", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200210000", + "parentCode": "20000200000000", + "name": "网络信息安全工程师", + "en_name": "Network Information Security Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200350000", + "parentCode": "20000200000000", + "name": "系统安全", + "en_name": "System Safety", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200060000", + "parentCode": "20000200000000", + "name": "IT技术支持", + "en_name": "It Technical Support", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200070000", + "parentCode": "20000200000000", + "name": "IT文档工程师", + "en_name": "IT Documentary Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200080000", + "parentCode": "20000200000000", + "name": "IT质量管理", + "en_name": "IT Quality Management", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200090000", + "parentCode": "20000200000000", + "name": "IT质量管理经理/主管", + "en_name": "IT QC Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200100000", + "parentCode": "20000200000000", + "name": "标准化工程师", + "en_name": "Standardization Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200150000", + "parentCode": "20000200000000", + "name": "计算机硬件维护工程师", + "en_name": "Computer Hardware Maintenance Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200170000", + "parentCode": "20000200000000", + "name": "设备维护工程师", + "en_name": "Equipment Maintenance Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200230000", + "parentCode": "20000200000000", + "name": "网优工程师", + "en_name": "Network Optimization Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200280000", + "parentCode": "20000200000000", + "name": "信息技术标准化工程师", + "en_name": "IT Standardization Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200290000", + "parentCode": "20000200000000", + "name": "信息技术经理/主管", + "en_name": "IT Manager/Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200300000", + "parentCode": "20000200000000", + "name": "信息技术专员", + "en_name": "IT Specialist", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "20000100000000", + "parentCode": "20000000000000", + "name": "测试工程师", + "en_name": "Test Engineer", + "deleted": false, + "sublist": [ + { + "code": "20000100010000", + "parentCode": "20000100000000", + "name": "测试工程师", + "en_name": "Test engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100170000", + "parentCode": "20000100000000", + "name": "测试经理", + "en_name": "Test Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100060000", + "parentCode": "20000100000000", + "name": "软件测试", + "en_name": "Software Testing", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100130000", + "parentCode": "20000100000000", + "name": "硬件测试", + "en_name": "Hardware Testing", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100150000", + "parentCode": "20000100000000", + "name": "自动化测试", + "en_name": "Automated testing", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100120000", + "parentCode": "20000100000000", + "name": "移动端测试", + "en_name": "Mobile Terminal Testing", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100140000", + "parentCode": "20000100000000", + "name": "游戏测试", + "en_name": "Game Testing", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100090000", + "parentCode": "20000100000000", + "name": "系统测试", + "en_name": "System Testing", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100100000", + "parentCode": "20000100000000", + "name": "协议测试", + "en_name": "Protocol Testing", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100110000", + "parentCode": "20000100000000", + "name": "性能测试", + "en_name": "Performance Testing", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100030000", + "parentCode": "20000100000000", + "name": "测试开发", + "en_name": "Testing Development", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100050000", + "parentCode": "20000100000000", + "name": "功能测试", + "en_name": "Function Testing", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100160000", + "parentCode": "20000100000000", + "name": "渗透测试", + "en_name": "Penetration test", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100020000", + "parentCode": "20000100000000", + "name": "通信测试", + "en_name": "Communication test", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100070000", + "parentCode": "20000100000000", + "name": "无人机组装测试", + "en_name": "UAV Assembly/Testing", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "13000200000000", + "parentCode": "20000000000000", + "name": "售前售后工程师", + "en_name": "Engineer, Technical Support", + "deleted": false, + "sublist": [ + { + "code": "13000200020000", + "parentCode": "13000200000000", + "name": "技术支持工程师", + "en_name": "Technical Support Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "13000200040000", + "parentCode": "13000200000000", + "name": "现场技术支持", + "en_name": "On site technical support", + "deleted": false, + "sublist": [] + }, + { + "code": "13000200060000", + "parentCode": "13000200000000", + "name": "实施顾问", + "en_name": "Execution Advisor", + "deleted": false, + "sublist": [] + }, + { + "code": "13000200130000", + "parentCode": "13000200000000", + "name": "实施工程师", + "en_name": "Implementation Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "13000200080000", + "parentCode": "13000200000000", + "name": "售前技术支持", + "en_name": "Pre Sales Technical Support", + "deleted": false, + "sublist": [] + }, + { + "code": "13000200070000", + "parentCode": "13000200000000", + "name": "售后技术支持", + "en_name": "After Sales Technical Support", + "deleted": false, + "sublist": [] + }, + { + "code": "13000200050000", + "parentCode": "13000200000000", + "name": "软件售后技术支持", + "en_name": "Software After-Sales Technical Support", + "deleted": false, + "sublist": [] + }, + { + "code": "13000200120000", + "parentCode": "13000200000000", + "name": "硬件售后技术支持", + "en_name": "Hardware After-Sales Technical Support", + "deleted": false, + "sublist": [] + }, + { + "code": "13000200090000", + "parentCode": "13000200000000", + "name": "售前顾问", + "en_name": "Pre-sales Advisor", + "deleted": false, + "sublist": [] + }, + { + "code": "13000200030000", + "parentCode": "13000200000000", + "name": "解决方案工程师", + "en_name": "Solution Engineer", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "17000000000000", + "parentCode": null, + "name": "视觉/交互/设计", + "en_name": "Visual Design/UX", + "deleted": false, + "sublist": [ + { + "code": "17000500000000", + "parentCode": "17000000000000", + "name": "视觉/交互设计", + "en_name": "Visual interactive design", + "deleted": false, + "sublist": [ + { + "code": "17000500120000", + "parentCode": "17000500000000", + "name": "平面设计", + "en_name": "Graphic Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500210000", + "parentCode": "17000500000000", + "name": "广告设计", + "en_name": "Advertising design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500100000", + "parentCode": "17000500000000", + "name": "美工", + "en_name": "Graphic Designer", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500040000", + "parentCode": "17000500000000", + "name": "UE设计师", + "en_name": "Ue Designer", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500050000", + "parentCode": "17000500000000", + "name": "UI设计师", + "en_name": "Ui Designer", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500060000", + "parentCode": "17000500000000", + "name": "UX设计师", + "en_name": "UX Designer", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500090000", + "parentCode": "17000500000000", + "name": "交互设计师", + "en_name": "Interaction Designer", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500130000", + "parentCode": "17000500000000", + "name": "网页设计师", + "en_name": "Web Designer", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500030000", + "parentCode": "17000500000000", + "name": "CAD设计/制图", + "en_name": "CAD Design/Drawing", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500010000", + "parentCode": "17000500000000", + "name": "3D设计师", + "en_name": "3D Designer", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500110000", + "parentCode": "17000500000000", + "name": "美术设计师(2D/3D)", + "en_name": "Art Designer(2D/3D)", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500180000", + "parentCode": "17000500000000", + "name": "主笔设计师", + "en_name": "Project Designer", + "deleted": false, + "sublist": [] + }, + { + "code": "14000700090000", + "parentCode": "17000500000000", + "name": "设计师助理", + "en_name": "Designer Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500190000", + "parentCode": "17000500000000", + "name": "设计经理/主管", + "en_name": "Design manager or supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500200000", + "parentCode": "17000500000000", + "name": "设计总监", + "en_name": "Design director", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500150000", + "parentCode": "17000500000000", + "name": "游戏界面设计师", + "en_name": "Game Interface Designer", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500160000", + "parentCode": "17000500000000", + "name": "游戏设计开发", + "en_name": "Game Design & Development", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500250000", + "parentCode": "17000500000000", + "name": "游戏动作", + "en_name": "Game Action", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500070000", + "parentCode": "17000500000000", + "name": "插画师", + "en_name": "Illustrator", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500170000", + "parentCode": "17000500000000", + "name": "原画师", + "en_name": "Concept Designer", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500220000", + "parentCode": "17000500000000", + "name": "手绘师", + "en_name": "hand painter", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500230000", + "parentCode": "17000500000000", + "name": "漫画师", + "en_name": "Comic Artist", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500240000", + "parentCode": "17000500000000", + "name": "人像修图师", + "en_name": "Portrait Painter", + "deleted": false, + "sublist": [] + }, + { + "code": "17000500080000", + "parentCode": "17000500000000", + "name": "计算机辅助设计师", + "en_name": "CAD Designer", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "17000200000000", + "parentCode": "17000000000000", + "name": "动画动效设计", + "en_name": "Animation Design", + "deleted": false, + "sublist": [ + { + "code": "17000200010000", + "parentCode": "17000200000000", + "name": "Flash设计师", + "en_name": "Flash Designer", + "deleted": false, + "sublist": [] + }, + { + "code": "17000200020000", + "parentCode": "17000200000000", + "name": "动画设计", + "en_name": "Animation Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000200050000", + "parentCode": "17000200000000", + "name": "特效设计", + "en_name": "VFX Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000200060000", + "parentCode": "17000200000000", + "name": "游戏原画师", + "en_name": "Game Concept Designer", + "deleted": false, + "sublist": [] + }, + { + "code": "17000200040000", + "parentCode": "17000200000000", + "name": "角色模型师", + "en_name": "Role Modeler", + "deleted": false, + "sublist": [] + }, + { + "code": "17000200030000", + "parentCode": "17000200000000", + "name": "动漫手绘", + "en_name": "Hand-drawn Animation", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "17000300000000", + "parentCode": "17000000000000", + "name": "工业设计", + "en_name": "Industrial Design", + "deleted": false, + "sublist": [ + { + "code": "17000300060000", + "parentCode": "17000300000000", + "name": "工业设计", + "en_name": "Industrial Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000300010000", + "parentCode": "17000300000000", + "name": "包装设计", + "en_name": "Packaging Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000300030000", + "parentCode": "17000300000000", + "name": "橱柜设计", + "en_name": "Cabinet Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000300080000", + "parentCode": "17000300000000", + "name": "家具设计", + "en_name": "Furniture Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000300090000", + "parentCode": "17000300000000", + "name": "玩具设计", + "en_name": "Toy Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000300070000", + "parentCode": "17000300000000", + "name": "珠宝设计", + "en_name": "Jewelry Design", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "17000100000000", + "parentCode": "17000000000000", + "name": "陈列展示设计", + "en_name": "Exhibition & Merchandising Designer", + "deleted": false, + "sublist": [ + { + "code": "17000100010000", + "parentCode": "17000100000000", + "name": "陈列设计", + "en_name": "Display Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000100030000", + "parentCode": "17000100000000", + "name": "展览/展示设计", + "en_name": "Exhibition / Exhibition Design", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "5000000000000", + "parentCode": null, + "name": "运营/客服", + "en_name": "Operations/Customer service", + "deleted": false, + "sublist": [ + { + "code": "5000100000000", + "parentCode": "5000000000000", + "name": "电商运营", + "en_name": "E-commerce Ops", + "deleted": false, + "sublist": [ + { + "code": "5000100020000", + "parentCode": "5000100000000", + "name": "国内电商运营", + "en_name": "Domestic E-commerce Operations", + "deleted": false, + "sublist": [] + }, + { + "code": "5000100040000", + "parentCode": "5000100000000", + "name": "电商专员/助理", + "en_name": "Specialist of E-commerce", + "deleted": false, + "sublist": [] + }, + { + "code": "5000100010000", + "parentCode": "5000100000000", + "name": "电商经理/主管", + "en_name": "Manager of E-commerce", + "deleted": false, + "sublist": [] + }, + { + "code": "5000100060000", + "parentCode": "5000100000000", + "name": "品类运营", + "en_name": "Product Category Ops", + "deleted": false, + "sublist": [] + }, + { + "code": "5000100070000", + "parentCode": "5000100000000", + "name": "淘宝/天猫运营", + "en_name": "Taobao or tmall operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000100050000", + "parentCode": "5000100000000", + "name": "跨境电商运营", + "en_name": "E-commerce Ops across borders", + "deleted": false, + "sublist": [] + }, + { + "code": "5000100030000", + "parentCode": "5000100000000", + "name": "电商站长", + "en_name": "E-commerce Station Head", + "deleted": false, + "sublist": [] + }, + { + "code": "5000100080000", + "parentCode": "5000100000000", + "name": "网店管理员", + "en_name": "Online Store Admin", + "deleted": false, + "sublist": [] + }, + { + "code": "50001000100000", + "parentCode": "5000100000000", + "name": "选品师", + "en_name": "Selection division", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "5000200000000", + "parentCode": "5000000000000", + "name": "新媒体运营", + "en_name": "New Media Ops", + "deleted": false, + "sublist": [ + { + "code": "5000200050000", + "parentCode": "5000200000000", + "name": "新媒体编辑", + "en_name": "New Media Editor", + "deleted": false, + "sublist": [] + }, + { + "code": "5000200060000", + "parentCode": "5000200000000", + "name": "新媒体运营", + "en_name": "New Media Ops", + "deleted": false, + "sublist": [] + }, + { + "code": "5000200030000", + "parentCode": "5000200000000", + "name": "微博运营", + "en_name": "Weibo Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000200040000", + "parentCode": "5000200000000", + "name": "微信运营", + "en_name": "WeChat Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000200080000", + "parentCode": "5000200000000", + "name": "短视频运营", + "en_name": "short video operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300190000", + "parentCode": "5000200000000", + "name": "直播运营", + "en_name": "Operation of Online Broadcast", + "deleted": false, + "sublist": [] + }, + { + "code": "5000200070000", + "parentCode": "5000200000000", + "name": "自媒体运营", + "en_name": "we media operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000200020000", + "parentCode": "5000200000000", + "name": "内容运营", + "en_name": "Content Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300080000", + "parentCode": "5000200000000", + "name": "社交媒体运营", + "en_name": "Social Media Ops", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "5000300000000", + "parentCode": "5000000000000", + "name": "业务运营", + "en_name": "Operation", + "deleted": false, + "sublist": [ + { + "code": "5000300180000", + "parentCode": "5000300000000", + "name": "运营助理/专员", + "en_name": "Operations Assistant / Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300170000", + "parentCode": "5000300000000", + "name": "运营主管", + "en_name": "Operation Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300090000", + "parentCode": "5000300000000", + "name": "社区运营", + "en_name": "Online Community Ops", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300210000", + "parentCode": "5000300000000", + "name": "社群运营", + "en_name": "Community operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300010000", + "parentCode": "5000300000000", + "name": "策略运营", + "en_name": "Managing Strategy", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300020000", + "parentCode": "5000300000000", + "name": "产品运营", + "en_name": "Product Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300070000", + "parentCode": "5000300000000", + "name": "平台运营", + "en_name": "Platform Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300220000", + "parentCode": "5000300000000", + "name": "商家运营", + "en_name": "Business operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300150000", + "parentCode": "5000300000000", + "name": "用户运营", + "en_name": "User Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300040000", + "parentCode": "5000300000000", + "name": "会员运营", + "en_name": "Member Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300050000", + "parentCode": "5000300000000", + "name": "活动运营", + "en_name": "Campaign Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300160000", + "parentCode": "5000300000000", + "name": "游戏运营", + "en_name": "Game Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000500050000", + "parentCode": "5000300000000", + "name": "数据运营", + "en_name": "Data Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300110000", + "parentCode": "5000300000000", + "name": "网络运营专员/助理", + "en_name": "Specialist of Internet Ops", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300120000", + "parentCode": "5000300000000", + "name": "网站运营", + "en_name": "Website Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300200000", + "parentCode": "5000300000000", + "name": "信息流优化师", + "en_name": "Information Flow Optimizer", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300030000", + "parentCode": "5000300000000", + "name": "互联网运营", + "en_name": "Internet Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000200010000", + "parentCode": "5000300000000", + "name": "内容审核", + "en_name": "Content Auditing", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300230000", + "parentCode": "5000300000000", + "name": "数据标注/AI训练师", + "en_name": "Data annotation/AI trainer", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300240000", + "parentCode": "5000300000000", + "name": "AI训练师", + "en_name": "ai trainer", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300140000", + "parentCode": "5000300000000", + "name": "销售运营", + "en_name": "Sales Operation", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "5000400000000", + "parentCode": "5000000000000", + "name": "运营管理", + "en_name": "Operation Management", + "deleted": false, + "sublist": [ + { + "code": "5000400050000", + "parentCode": "5000400000000", + "name": "运营经理/主管", + "en_name": "Operations Manager/Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "5000400060000", + "parentCode": "5000400000000", + "name": "运营总监", + "en_name": "Operation Director", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300100000", + "parentCode": "5000400000000", + "name": "网络运营经理/主管", + "en_name": "Network operations manager or supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "5000400030000", + "parentCode": "5000400000000", + "name": "网站运营总监", + "en_name": "Director of Web Ops", + "deleted": false, + "sublist": [] + }, + { + "code": "5000400010000", + "parentCode": "5000400000000", + "name": "城市运营", + "en_name": "Regional Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "5000400040000", + "parentCode": "5000400000000", + "name": "督导巡店", + "en_name": "Supervise Shop Inspection", + "deleted": false, + "sublist": [] + }, + { + "code": "5000400070000", + "parentCode": "5000400000000", + "name": "站长", + "en_name": "Station Head", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "5000500000000", + "parentCode": "5000000000000", + "name": "专业分析", + "en_name": "Business Analysis", + "deleted": false, + "sublist": [ + { + "code": "5000500040000", + "parentCode": "5000500000000", + "name": "商业数据分析", + "en_name": "Business Data Analysis", + "deleted": false, + "sublist": [] + }, + { + "code": "5000500070000", + "parentCode": "5000500000000", + "name": "销售数据分析", + "en_name": "Sales Data Analysis", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "13000100000000", + "parentCode": "5000000000000", + "name": "客服", + "en_name": "Customer service", + "deleted": false, + "sublist": [ + { + "code": "13000100100000", + "parentCode": "13000100000000", + "name": "客服专员", + "en_name": "Customer Service Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "13000100090000", + "parentCode": "13000100000000", + "name": "客服主管", + "en_name": "Customer Service Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "13000100080000", + "parentCode": "13000100000000", + "name": "客服经理", + "en_name": "Customer Service Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "13000100110000", + "parentCode": "13000100000000", + "name": "客服总监", + "en_name": "Customer Service Director", + "deleted": false, + "sublist": [] + }, + { + "code": "13000100020000", + "parentCode": "13000100000000", + "name": "电话客服", + "en_name": "Call Center Customer Service", + "deleted": false, + "sublist": [] + }, + { + "code": "13000100030000", + "parentCode": "13000100000000", + "name": "售后客服", + "en_name": "After sales customer service", + "deleted": false, + "sublist": [] + }, + { + "code": "13000100190000", + "parentCode": "13000100000000", + "name": "网络客服", + "en_name": "Network Customer Service", + "deleted": false, + "sublist": [] + }, + { + "code": "13000100210000", + "parentCode": "13000100000000", + "name": "咨询热线/呼叫中心客服", + "en_name": "Hotline / Call Center Customer Service", + "deleted": false, + "sublist": [] + }, + { + "code": "13000100010000", + "parentCode": "13000100000000", + "name": "VIP专员", + "en_name": "VIP Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "13000100120000", + "parentCode": "13000100000000", + "name": "客户关系/投诉协调", + "en_name": "Customer Relationship/Complaint Handling", + "deleted": false, + "sublist": [] + }, + { + "code": "13000100130000", + "parentCode": "13000100000000", + "name": "客户续期管理", + "en_name": "Renewal Management", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "5000600000000", + "parentCode": "5000000000000", + "name": "线下运营", + "en_name": "Offline operations", + "deleted": false, + "sublist": [ + { + "code": "5000300130000", + "parentCode": "5000600000000", + "name": "线下拓展运营", + "en_name": "Offline BD Ops", + "deleted": false, + "sublist": [] + }, + { + "code": "5000300060000", + "parentCode": "5000600000000", + "name": "楼面管理", + "en_name": "Commercial Floor Management", + "deleted": false, + "sublist": [] + }, + { + "code": "5000600100000", + "parentCode": "5000600000000", + "name": "车辆运营", + "en_name": "Vehicle operation", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "3000000000000", + "parentCode": null, + "name": "产品/项目/高级管理", + "en_name": "Product Manager/Project Manager/Executive", + "deleted": false, + "sublist": [ + { + "code": "3000100000000", + "parentCode": "3000000000000", + "name": "产品经理", + "en_name": "Product Manager", + "deleted": false, + "sublist": [ + { + "code": "3000100040000", + "parentCode": "3000100000000", + "name": "产品经理", + "en_name": "Product Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "14000700020000", + "parentCode": "3000100000000", + "name": "产品助理", + "en_name": "Product Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100050000", + "parentCode": "3000100000000", + "name": "产品专员/助理", + "en_name": "Product Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100070000", + "parentCode": "3000100000000", + "name": "产品总监/VP", + "en_name": "Product Director / Vp", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100110000", + "parentCode": "3000100000000", + "name": "软件产品经理", + "en_name": "Softerware Product Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100150000", + "parentCode": "3000100000000", + "name": "网页产品经理", + "en_name": "Web Product Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100170000", + "parentCode": "3000100000000", + "name": "移动产品经理", + "en_name": "Mobile Product Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100180000", + "parentCode": "3000100000000", + "name": "硬件产品经理", + "en_name": "Hardware Product Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100020000", + "parentCode": "3000100000000", + "name": "策略产品经理", + "en_name": "Product Manager of Strategy", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100210000", + "parentCode": "3000100000000", + "name": "AI产品经理", + "en_name": "AI Product Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100010000", + "parentCode": "3000100000000", + "name": "安全产品经理", + "en_name": "Product Manager of Security", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100080000", + "parentCode": "3000100000000", + "name": "电商产品经理", + "en_name": "Product Manager of E-commerce", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100090000", + "parentCode": "3000100000000", + "name": "供应链产品经理", + "en_name": "Product Manager of Supply Chain", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100140000", + "parentCode": "3000100000000", + "name": "数据产品经理", + "en_name": "Data Product Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100120000", + "parentCode": "3000100000000", + "name": "商业化产品经理", + "en_name": "Product Manager of Commercialization", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100160000", + "parentCode": "3000100000000", + "name": "增长产品经理", + "en_name": "Growth Product Manager", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "3000300000000", + "parentCode": "3000000000000", + "name": "项目管理", + "en_name": "Project Manager", + "deleted": false, + "sublist": [ + { + "code": "3000300160000", + "parentCode": "3000300000000", + "name": "项目专员", + "en_name": "Project Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300150000", + "parentCode": "3000300000000", + "name": "项目专员/助理", + "en_name": "Project Specialist/Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300130000", + "parentCode": "3000300000000", + "name": "项目执行", + "en_name": "Project Execution", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300120000", + "parentCode": "3000300000000", + "name": "项目经理/主管", + "en_name": "Project Manager or Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300170000", + "parentCode": "3000300000000", + "name": "项目总监", + "en_name": "Project Director", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300010000", + "parentCode": "3000300000000", + "name": "IT项目经理", + "en_name": "IT Project Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300020000", + "parentCode": "3000300000000", + "name": "IT项目执行", + "en_name": "IT Project Execution", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300030000", + "parentCode": "3000300000000", + "name": "IT项目总监", + "en_name": "IT Project Director", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300040000", + "parentCode": "3000300000000", + "name": "电子/电器项目经理", + "en_name": "Electronic / Electrical Project Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300090000", + "parentCode": "3000300000000", + "name": "通信项目经理", + "en_name": "Communication project manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300230000", + "parentCode": "3000300000000", + "name": "硬件项目经理", + "en_name": "Hardware Project Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300180000", + "parentCode": "3000300000000", + "name": "化工项目经理", + "en_name": "Chemical Project Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300190000", + "parentCode": "3000300000000", + "name": "医药项目经理", + "en_name": "Pharmaceutical Project Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300220000", + "parentCode": "3000300000000", + "name": "临床项目经理", + "en_name": "Clinical Project Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300080000", + "parentCode": "3000300000000", + "name": "生产项目经理/主管", + "en_name": "Production Project Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300070000", + "parentCode": "3000300000000", + "name": "汽车项目经理", + "en_name": "Automotive Project Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300100000", + "parentCode": "3000300000000", + "name": "物流/仓储项目经理", + "en_name": "Logistics / Warehousing Project Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300200000", + "parentCode": "3000300000000", + "name": "证券/投资项目经理", + "en_name": "Securities / Investment Project Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300060000", + "parentCode": "3000300000000", + "name": "广告/会展项目经理", + "en_name": "Advertising / Exhibition Project Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300050000", + "parentCode": "3000300000000", + "name": "服装/纺织/皮革项目经理", + "en_name": "Garment / Textile / Leather Project Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300210000", + "parentCode": "3000300000000", + "name": "咨询项目经理", + "en_name": "Consulting Project Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000300240000", + "parentCode": "3000300000000", + "name": "装修项目经理", + "en_name": "Decoration Project Manager", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "3000200000000", + "parentCode": "3000000000000", + "name": "高级管理", + "en_name": "Executive", + "deleted": false, + "sublist": [ + { + "code": "3000200020000", + "parentCode": "3000200000000", + "name": "总裁/总经理/CEO", + "en_name": "President / General Manager / Ceo", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200030000", + "parentCode": "3000200000000", + "name": "CFO", + "en_name": "CFO", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200040000", + "parentCode": "3000200000000", + "name": "CIO", + "en_name": "CIO", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200050000", + "parentCode": "3000200000000", + "name": "CMO", + "en_name": "CMO", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200060000", + "parentCode": "3000200000000", + "name": "COO", + "en_name": "COO", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200070000", + "parentCode": "3000200000000", + "name": "CTO", + "en_name": "CTO", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200140000", + "parentCode": "3000200000000", + "name": "副总裁/副总经理/VP", + "en_name": "Vice President / Vice General Manager / Vp", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200120000", + "parentCode": "3000200000000", + "name": "联合创始人", + "en_name": "Co Founder", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200180000", + "parentCode": "3000200000000", + "name": "投资合伙人", + "en_name": "Venture Partner", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200080000", + "parentCode": "3000200000000", + "name": "办事处首席代表", + "en_name": "Chief Representative", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200130000", + "parentCode": "3000200000000", + "name": "分公司/代表处负责人", + "en_name": "Branch Office Head", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200270000", + "parentCode": "3000200000000", + "name": "区域负责人(辖多个分公司)", + "en_name": "Regional Head (Governing Multiple Branches)", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200090000", + "parentCode": "3000200000000", + "name": "事业部管理", + "en_name": "BU Head", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200260000", + "parentCode": "3000200000000", + "name": "董事会秘书", + "en_name": "Secretary of the board of directors", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200190000", + "parentCode": "3000200000000", + "name": "企业秘书", + "en_name": "Enterprise secretary", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200240000", + "parentCode": "3000200000000", + "name": "总助/CEO助理/董事长助理", + "en_name": "General Assistant / Ceo Assistant / Chairman Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200150000", + "parentCode": "3000200000000", + "name": "高级管理职位", + "en_name": "Executive", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "3000400000000", + "parentCode": "3000000000000", + "name": "游戏策划/制作", + "en_name": "Game Planning OR Production", + "deleted": false, + "sublist": [ + { + "code": "17000500140000", + "parentCode": "3000400000000", + "name": "游戏策划", + "en_name": "Game Planning", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100200000", + "parentCode": "3000400000000", + "name": "游戏制作人", + "en_name": "Game Producer", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100190000", + "parentCode": "3000400000000", + "name": "游戏数值策划", + "en_name": "game value planning", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "16000000000000", + "parentCode": null, + "name": "市场/公关/广告", + "en_name": "Marketing/Public relations/Advertising", + "deleted": false, + "sublist": [ + { + "code": "16000500000000", + "parentCode": "16000000000000", + "name": "市场/品牌推广", + "en_name": "Branding", + "deleted": false, + "sublist": [ + { + "code": "16000500250000", + "parentCode": "16000500000000", + "name": "市场专员", + "en_name": "Marketing Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500220000", + "parentCode": "16000500000000", + "name": "市场主管", + "en_name": "Marketing director", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500170000", + "parentCode": "16000500000000", + "name": "市场经理/主管", + "en_name": "Marketing Manager/Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500260000", + "parentCode": "16000500000000", + "name": "市场总监", + "en_name": "Marketing Director", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500190000", + "parentCode": "16000500000000", + "name": "市场拓展", + "en_name": "Market Development", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500200000", + "parentCode": "16000500000000", + "name": "市场营销", + "en_name": "Marketing", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500240000", + "parentCode": "16000500000000", + "name": "市场运营", + "en_name": "Marketing Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500150000", + "parentCode": "16000500000000", + "name": "市场策划经理", + "en_name": "Marketing Planning Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500160000", + "parentCode": "16000500000000", + "name": "市场策划专员", + "en_name": "Marketing Planning Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "3000100130000", + "parentCode": "16000500000000", + "name": "产品市场经理", + "en_name": "Product marketing manager", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500180000", + "parentCode": "16000500000000", + "name": "市场通路专员", + "en_name": "Marketing Channel Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500010000", + "parentCode": "16000500000000", + "name": "SEM", + "en_name": "SEM", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500020000", + "parentCode": "16000500000000", + "name": "SEO", + "en_name": "SEO", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500290000", + "parentCode": "16000500000000", + "name": "网络推广", + "en_name": "Online Marketing", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500300000", + "parentCode": "16000500000000", + "name": "网站推广", + "en_name": "Web Marketing", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500310000", + "parentCode": "16000500000000", + "name": "微信推广", + "en_name": "WeChat Marketing", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500350000", + "parentCode": "16000500000000", + "name": "游戏推广", + "en_name": "Game Promotion", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500370000", + "parentCode": "16000500000000", + "name": "APP推广", + "en_name": "App Promotion", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500360000", + "parentCode": "16000500000000", + "name": "海外市场", + "en_name": "Overseas Market", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500270000", + "parentCode": "16000500000000", + "name": "数字营销专员", + "en_name": "Digital Marketing Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500380000", + "parentCode": "16000500000000", + "name": "网络营销", + "en_name": "Network Marketing", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500090000", + "parentCode": "16000500000000", + "name": "品牌策划", + "en_name": "Branding Planning", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500100000", + "parentCode": "16000500000000", + "name": "品牌公关", + "en_name": "Branding", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500130000", + "parentCode": "16000500000000", + "name": "品牌专员", + "en_name": "Branding Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500120000", + "parentCode": "16000500000000", + "name": "品牌主管", + "en_name": "Branding Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500110000", + "parentCode": "16000500000000", + "name": "品牌经理/主管", + "en_name": "Brand Manager/Supervisor", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "16000600000000", + "parentCode": "16000000000000", + "name": "市场调研", + "en_name": "Marketing Research", + "deleted": false, + "sublist": [ + { + "code": "16000600030000", + "parentCode": "16000600000000", + "name": "市场调研", + "en_name": "Market Research", + "deleted": false, + "sublist": [] + }, + { + "code": "16000600050000", + "parentCode": "16000600000000", + "name": "用户研究员", + "en_name": "User Researcher", + "deleted": false, + "sublist": [] + }, + { + "code": "16000600070000", + "parentCode": "16000600000000", + "name": "用户研究经理", + "en_name": "User Research Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "16000600080000", + "parentCode": "16000600000000", + "name": "用户研究总监", + "en_name": "User Research Director", + "deleted": false, + "sublist": [] + }, + { + "code": "16000600010000", + "parentCode": "16000600000000", + "name": "竞品分析", + "en_name": "Competitive Analysis", + "deleted": false, + "sublist": [] + }, + { + "code": "16000600060000", + "parentCode": "16000600000000", + "name": "舆情分析", + "en_name": "Public Opinion Analysis", + "deleted": false, + "sublist": [] + }, + { + "code": "16000600020000", + "parentCode": "16000600000000", + "name": "情报信息分析", + "en_name": "Information Analysis", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "16000100000000", + "parentCode": "16000000000000", + "name": "策划", + "en_name": "Marketing Planning", + "deleted": false, + "sublist": [ + { + "code": "16000100080000", + "parentCode": "16000100000000", + "name": "文案策划", + "en_name": "Copywriter Planning", + "deleted": false, + "sublist": [] + }, + { + "code": "16000100040000", + "parentCode": "16000100000000", + "name": "活动策划", + "en_name": "Event Planning", + "deleted": false, + "sublist": [] + }, + { + "code": "16000100050000", + "parentCode": "16000100000000", + "name": "活动执行", + "en_name": "Event Executive", + "deleted": false, + "sublist": [] + }, + { + "code": "16000100060000", + "parentCode": "16000100000000", + "name": "企划", + "en_name": "Planning", + "deleted": false, + "sublist": [] + }, + { + "code": "16000100090000", + "parentCode": "16000100000000", + "name": "宣传策划", + "en_name": "Publicity Planning", + "deleted": false, + "sublist": [] + }, + { + "code": "16000100100000", + "parentCode": "16000100000000", + "name": "营销策划", + "en_name": "Marketing Planning", + "deleted": false, + "sublist": [] + }, + { + "code": "16000100110000", + "parentCode": "16000100000000", + "name": "展厅策划", + "en_name": "Exhibition Planning", + "deleted": false, + "sublist": [] + }, + { + "code": "16000100030000", + "parentCode": "16000100000000", + "name": "婚礼策划", + "en_name": "Wedding Planning", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "16000300000000", + "parentCode": "16000000000000", + "name": "公关媒介", + "en_name": "PR", + "deleted": false, + "sublist": [ + { + "code": "16000300020000", + "parentCode": "16000300000000", + "name": "公关专员", + "en_name": "PR Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "16000300010000", + "parentCode": "16000300000000", + "name": "公关经理/主管", + "en_name": "PR Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "16000300030000", + "parentCode": "16000300000000", + "name": "公关总监", + "en_name": "PR Director", + "deleted": false, + "sublist": [] + }, + { + "code": "16000300040000", + "parentCode": "16000300000000", + "name": "活动公关", + "en_name": "PR Events", + "deleted": false, + "sublist": [] + }, + { + "code": "16000300120000", + "parentCode": "16000300000000", + "name": "商务公关", + "en_name": "Business PR", + "deleted": false, + "sublist": [] + }, + { + "code": "16000300100000", + "parentCode": "16000300000000", + "name": "媒介专员", + "en_name": "Media Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "16000300050000", + "parentCode": "16000300000000", + "name": "媒介策划", + "en_name": "Media Planning", + "deleted": false, + "sublist": [] + }, + { + "code": "16000300060000", + "parentCode": "16000300000000", + "name": "媒介顾问", + "en_name": "Media Consultant", + "deleted": false, + "sublist": [] + }, + { + "code": "16000300080000", + "parentCode": "16000300000000", + "name": "媒介商务BD", + "en_name": "Media Business Development", + "deleted": false, + "sublist": [] + }, + { + "code": "16000300090000", + "parentCode": "16000300000000", + "name": "媒介投放", + "en_name": "Media Delivery", + "deleted": false, + "sublist": [] + }, + { + "code": "16000300070000", + "parentCode": "16000300000000", + "name": "媒介经理/总监", + "en_name": "Media Manager/Director", + "deleted": false, + "sublist": [] + }, + { + "code": "16000300110000", + "parentCode": "16000300000000", + "name": "媒介总监", + "en_name": "Media Director", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "16000200000000", + "parentCode": "16000000000000", + "name": "发行", + "en_name": "Distributor", + "deleted": false, + "sublist": [ + { + "code": "16000200010000", + "parentCode": "16000200000000", + "name": "发行管理", + "en_name": "Issuance Management", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "16000400000000", + "parentCode": "16000000000000", + "name": "会展会务", + "en_name": "Exhibition/Conference", + "deleted": false, + "sublist": [ + { + "code": "16000400020000", + "parentCode": "16000400000000", + "name": "会务专员", + "en_name": "Event Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "16000400010000", + "parentCode": "16000400000000", + "name": "会务经理", + "en_name": "Event Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "16000400040000", + "parentCode": "16000400000000", + "name": "会展/展厅策划", + "en_name": "Exhibition/Showroom Planning", + "deleted": false, + "sublist": [] + }, + { + "code": "16000400030000", + "parentCode": "16000400000000", + "name": "会议活动执行", + "en_name": "Implementation Of Conference Activities", + "deleted": false, + "sublist": [] + }, + { + "code": "16000400050000", + "parentCode": "16000400000000", + "name": "会议/会展活动销售", + "en_name": "Conference/Exhibition Sales", + "deleted": false, + "sublist": [] + }, + { + "code": "16000400060000", + "parentCode": "16000400000000", + "name": "会议活动销售", + "en_name": "Conference Event Sales", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "16000700000000", + "parentCode": "16000000000000", + "name": "政府事务", + "en_name": "Government affairs", + "deleted": false, + "sublist": [ + { + "code": "16000300130000", + "parentCode": "16000700000000", + "name": "政府关系", + "en_name": "Governmental Relations", + "deleted": false, + "sublist": [] + }, + { + "code": "6001400150000", + "parentCode": "16000700000000", + "name": "政策研究", + "en_name": "Policy Research", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300470000", + "parentCode": "16000700000000", + "name": "项目申报专员", + "en_name": "Project Application Specialist", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "12000000000000", + "parentCode": null, + "name": "金融/保险", + "en_name": "Finance/Insurance", + "deleted": false, + "sublist": [ + { + "code": "12000200000000", + "parentCode": "12000000000000", + "name": "投融资", + "en_name": "Investment & Financing", + "deleted": false, + "sublist": [ + { + "code": "12000200140000", + "parentCode": "12000200000000", + "name": "投融资", + "en_name": "Investing & Financing", + "deleted": false, + "sublist": [] + }, + { + "code": "14000700120000", + "parentCode": "12000200000000", + "name": "投资助理", + "en_name": "Investment Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200150000", + "parentCode": "12000200000000", + "name": "投资经理", + "en_name": "Investment Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200170000", + "parentCode": "12000200000000", + "name": "投资总监/VP", + "en_name": "Director of Investment/VP", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200220000", + "parentCode": "12000200000000", + "name": "投资VP", + "en_name": "Investment Vp", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200090000", + "parentCode": "12000200000000", + "name": "融资", + "en_name": "Fundraising", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200100000", + "parentCode": "12000200000000", + "name": "融资总监", + "en_name": "Fundraising Director", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200030000", + "parentCode": "12000200000000", + "name": "股权投资", + "en_name": "Equity Investment", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200160000", + "parentCode": "12000200000000", + "name": "投资者关系", + "en_name": "Investor Relations", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200020000", + "parentCode": "12000200000000", + "name": "股票/期货操盘手", + "en_name": "Stock / Futures Trader", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200070000", + "parentCode": "12000200000000", + "name": "交易员", + "en_name": "Trader", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200010000", + "parentCode": "12000200000000", + "name": "并购", + "en_name": "M&A", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200040000", + "parentCode": "12000200000000", + "name": "行业研究", + "en_name": "Industry Study", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200230000", + "parentCode": "12000200000000", + "name": "量化研究", + "en_name": "Quantitative Study", + "deleted": false, + "sublist": [] + }, + { + "code": "5000500030000", + "parentCode": "12000200000000", + "name": "金融数据分析", + "en_name": "Financial Data Analysis", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200210000", + "parentCode": "12000200000000", + "name": "金融研究", + "en_name": "Financial research", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200200000", + "parentCode": "12000200000000", + "name": "资产评估", + "en_name": "Property Appraisal", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200110000", + "parentCode": "12000200000000", + "name": "投行财务分析", + "en_name": "Financial Analysis of Investment Bank", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200120000", + "parentCode": "12000200000000", + "name": "投资银行业务", + "en_name": "Investment Banking", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200130000", + "parentCode": "12000200000000", + "name": "投后管理", + "en_name": "Post-investment Activities", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "12000100000000", + "parentCode": "12000000000000", + "name": "保险", + "en_name": "Insurance", + "deleted": false, + "sublist": [ + { + "code": "12000100110000", + "parentCode": "12000100000000", + "name": "保险顾问", + "en_name": "Insurance Consultant", + "deleted": false, + "sublist": [] + }, + { + "code": "12000100140000", + "parentCode": "12000100000000", + "name": "车险专员", + "en_name": "Car Insurance Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "12000100150000", + "parentCode": "12000100000000", + "name": "保险理赔", + "en_name": "Insurance Claim Settlement", + "deleted": false, + "sublist": [] + }, + { + "code": "12000100180000", + "parentCode": "12000100000000", + "name": "续保专员", + "en_name": "Renewal Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "12000100090000", + "parentCode": "12000100000000", + "name": "保险受理员", + "en_name": "Insurance Operator", + "deleted": false, + "sublist": [] + }, + { + "code": "12000100070000", + "parentCode": "12000100000000", + "name": "保险培训师", + "en_name": "Insurance Trainer", + "deleted": false, + "sublist": [] + }, + { + "code": "12000100100000", + "parentCode": "12000100000000", + "name": "保险项目策划", + "en_name": "Insurance Project Planning", + "deleted": false, + "sublist": [] + }, + { + "code": "12000100010000", + "parentCode": "12000100000000", + "name": "保险产品开发", + "en_name": "Insurance Product Development", + "deleted": false, + "sublist": [] + }, + { + "code": "12000100050000", + "parentCode": "12000100000000", + "name": "保险精算师", + "en_name": "Actuary", + "deleted": false, + "sublist": [] + }, + { + "code": "12000100120000", + "parentCode": "12000100000000", + "name": "保险公估师", + "en_name": "Insurance Adjuster", + "deleted": false, + "sublist": [] + }, + { + "code": "12000100040000", + "parentCode": "12000100000000", + "name": "保险核安", + "en_name": "Insurance Adjuster", + "deleted": false, + "sublist": [] + }, + { + "code": "12000100080000", + "parentCode": "12000100000000", + "name": "保险契约管理", + "en_name": "Insurance Contract Management", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "12000300000000", + "parentCode": "12000000000000", + "name": "银行及金融服务", + "en_name": "Bank & Financial Services", + "deleted": false, + "sublist": [ + { + "code": "12000300070000", + "parentCode": "12000300000000", + "name": "理财顾问", + "en_name": "Investment Advisor", + "deleted": false, + "sublist": [] + }, + { + "code": "12000300140000", + "parentCode": "12000300000000", + "name": "信用卡销售", + "en_name": "Credit card sales", + "deleted": false, + "sublist": [] + }, + { + "code": "12000300110000", + "parentCode": "12000300000000", + "name": "金融产品经理", + "en_name": "Financial products manager", + "deleted": false, + "sublist": [] + }, + { + "code": "12000300090000", + "parentCode": "12000300000000", + "name": "信贷管理", + "en_name": "Credit Management", + "deleted": false, + "sublist": [] + }, + { + "code": "12000300130000", + "parentCode": "12000300000000", + "name": "信贷审核", + "en_name": "Credit audit", + "deleted": false, + "sublist": [] + }, + { + "code": "12000300010000", + "parentCode": "12000300000000", + "name": "保理人", + "en_name": "Factor", + "deleted": false, + "sublist": [] + }, + { + "code": "12000300020000", + "parentCode": "12000300000000", + "name": "催收员", + "en_name": "Collector", + "deleted": false, + "sublist": [] + }, + { + "code": "12000300080000", + "parentCode": "12000300000000", + "name": "清算", + "en_name": "Liquidation", + "deleted": false, + "sublist": [] + }, + { + "code": "12000300120000", + "parentCode": "12000300000000", + "name": "金融风控", + "en_name": "Financial risk control", + "deleted": false, + "sublist": [] + }, + { + "code": "12000300030000", + "parentCode": "12000300000000", + "name": "大堂经理", + "en_name": "Lobby Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "12000300050000", + "parentCode": "12000300000000", + "name": "柜员", + "en_name": "Bank Teller", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200170000", + "parentCode": "12000300000000", + "name": "行长/副行长", + "en_name": "Bank President/Vice", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "12000400000000", + "parentCode": "12000000000000", + "name": "证券/基金/期货", + "en_name": "securities or funds or futures", + "deleted": false, + "sublist": [ + { + "code": "12000200180000", + "parentCode": "12000400000000", + "name": "证券分析", + "en_name": "Securities Analysis", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200190000", + "parentCode": "12000400000000", + "name": "证券/期货经纪人", + "en_name": "Securities/Futures Broker", + "deleted": false, + "sublist": [] + }, + { + "code": "14000700160000", + "parentCode": "12000400000000", + "name": "证券事务代表", + "en_name": "Securities Affairs Representative", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200060000", + "parentCode": "12000400000000", + "name": "基金销售", + "en_name": "Fund Sales", + "deleted": false, + "sublist": [] + }, + { + "code": "12000200050000", + "parentCode": "12000400000000", + "name": "基金经理", + "en_name": "Fund Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "5000500060000", + "parentCode": "12000400000000", + "name": "外汇分析师", + "en_name": "Currency Analyst", + "deleted": false, + "sublist": [] + }, + { + "code": "12000400020000", + "parentCode": "12000400000000", + "name": "外汇经纪人", + "en_name": "foreign exchange broker", + "deleted": false, + "sublist": [] + }, + { + "code": "12000400010000", + "parentCode": "12000400000000", + "name": "期货经纪人", + "en_name": "futures broker", + "deleted": false, + "sublist": [] + }, + { + "code": "14000700150000", + "parentCode": "12000400000000", + "name": "信息披露专员", + "en_name": "Information Disclosure Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "12000400030000", + "parentCode": "12000400000000", + "name": "证券交易员", + "en_name": "Securities Trader", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "12000500000000", + "parentCode": "12000000000000", + "name": "信托/担保/拍卖/典当", + "en_name": "trust or auction guarantee or pawn", + "deleted": false, + "sublist": [ + { + "code": "12000500020000", + "parentCode": "12000500000000", + "name": "信托业务", + "en_name": "trust business", + "deleted": false, + "sublist": [] + }, + { + "code": "12000500010000", + "parentCode": "12000500000000", + "name": "担保业务", + "en_name": "guarantee business", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300110000", + "parentCode": "12000500000000", + "name": "拍卖师", + "en_name": "Auctioneer", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300190000", + "parentCode": "12000500000000", + "name": "典当业务", + "en_name": "Pawn Service", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300180000", + "parentCode": "12000500000000", + "name": "珠宝/收藏品鉴定", + "en_name": "Jewelry Appraisal", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "7000000000000", + "parentCode": null, + "name": "房地产/工程/建筑", + "en_name": "Real estate, engineering or construction", + "deleted": false, + "sublist": [ + { + "code": "7000700000000", + "parentCode": "7000000000000", + "name": "房地产规划开发", + "en_name": "Real estate planning and development", + "deleted": false, + "sublist": [ + { + "code": "7000200040000", + "parentCode": "7000700000000", + "name": "地产项目管理", + "en_name": "Real Estate Project Management", + "deleted": false, + "sublist": [] + }, + { + "code": "7000700020000", + "parentCode": "7000700000000", + "name": "地产项目总监", + "en_name": "Real Estate Project Director", + "deleted": false, + "sublist": [] + }, + { + "code": "7000200050000", + "parentCode": "7000700000000", + "name": "房地产项目开发报建", + "en_name": "Real Estate Project Development", + "deleted": false, + "sublist": [] + }, + { + "code": "7000200060000", + "parentCode": "7000700000000", + "name": "房地产项目配套工程师", + "en_name": "Engineer of Real Estate Project Supporting Facilities", + "deleted": false, + "sublist": [] + }, + { + "code": "14000100110000", + "parentCode": "7000700000000", + "name": "房地产资产管理", + "en_name": "Real Estate Property Management", + "deleted": false, + "sublist": [] + }, + { + "code": "7000200020000", + "parentCode": "7000700000000", + "name": "房地产策划", + "en_name": "Real Estate Planning", + "deleted": false, + "sublist": [] + }, + { + "code": "7000700030000", + "parentCode": "7000700000000", + "name": "地产策划总监", + "en_name": "Real Estate Planning Director", + "deleted": false, + "sublist": [] + }, + { + "code": "7000700010000", + "parentCode": "7000700000000", + "name": "物业招商管理", + "en_name": "Property Investment Management", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100030000", + "parentCode": "7000700000000", + "name": "工程招投标", + "en_name": "Engineering Project Bidding", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "7000800000000", + "parentCode": "7000000000000", + "name": "房地产交易", + "en_name": "Real estate transactions", + "deleted": false, + "sublist": [ + { + "code": "19000200040000", + "parentCode": "7000800000000", + "name": "房产销售", + "en_name": "Real Estate Sales", + "deleted": false, + "sublist": [] + }, + { + "code": "7000800010000", + "parentCode": "7000800000000", + "name": "房地产销售总监", + "en_name": "Real Estate Sales Director", + "deleted": false, + "sublist": [] + }, + { + "code": "7000800020000", + "parentCode": "7000800000000", + "name": "地产招投标总监", + "en_name": "Real Estate Bidding Director", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300060000", + "parentCode": "7000800000000", + "name": "房产评估师", + "en_name": "Real Estate Appraiser", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100020000", + "parentCode": "7000800000000", + "name": "地产招投标", + "en_name": "Real Estate Bidding", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "17000400000000", + "parentCode": "7000000000000", + "name": "建筑/室内设计", + "en_name": "Interior Designer", + "deleted": false, + "sublist": [ + { + "code": "17000400050000", + "parentCode": "17000400000000", + "name": "建筑工程师", + "en_name": "Architectural Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300240000", + "parentCode": "17000400000000", + "name": "建筑设计师", + "en_name": "Architect", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400060000", + "parentCode": "17000400000000", + "name": "建筑模型师", + "en_name": "Architectural Modeler", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400070000", + "parentCode": "17000400000000", + "name": "建筑制图", + "en_name": "Architectural Graphing", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400090000", + "parentCode": "17000400000000", + "name": "幕墙设计", + "en_name": "Curtain Wall Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400170000", + "parentCode": "17000400000000", + "name": "智能化设计", + "en_name": "Intelligent Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400110000", + "parentCode": "17000400000000", + "name": "施工图设计", + "en_name": "Construction Drawing Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400010000", + "parentCode": "17000400000000", + "name": "城市规划设计", + "en_name": "Urban Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400020000", + "parentCode": "17000400000000", + "name": "环境艺术设计", + "en_name": "Environmental Art Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400150000", + "parentCode": "17000400000000", + "name": "园林/景观设计", + "en_name": "Landscape Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400120000", + "parentCode": "17000400000000", + "name": "室内设计", + "en_name": "Interior Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400100000", + "parentCode": "17000400000000", + "name": "软装设计", + "en_name": "Soft Decoration", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400140000", + "parentCode": "17000400000000", + "name": "硬装设计", + "en_name": "Solid Decoration", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400030000", + "parentCode": "17000400000000", + "name": "家居设计", + "en_name": "Home Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400040000", + "parentCode": "17000400000000", + "name": "家装设计", + "en_name": "Decoration Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400160000", + "parentCode": "17000400000000", + "name": "照明设计", + "en_name": "Lightening Design", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300280000", + "parentCode": "17000400000000", + "name": "空间设计师", + "en_name": "Space Designer", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400130000", + "parentCode": "17000400000000", + "name": "效果图设计", + "en_name": "Rendering Design", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300200000", + "parentCode": "17000400000000", + "name": "绘图员", + "en_name": "Draftsman", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400190000", + "parentCode": "17000400000000", + "name": "深化设计", + "en_name": "Deepening design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000400200000", + "parentCode": "17000400000000", + "name": "家装顾问", + "en_name": "Home decoration Consultant", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "7000300000000", + "parentCode": "7000000000000", + "name": "工程开发技术人员", + "en_name": "Engineering Technicians", + "deleted": false, + "sublist": [ + { + "code": "7000300460000", + "parentCode": "7000300000000", + "name": "土木/土建工程师", + "en_name": "Civil or civil engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300380000", + "parentCode": "7000300000000", + "name": "市政工程", + "en_name": "Civil Engineering", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300090000", + "parentCode": "7000300000000", + "name": "给排水工程师", + "en_name": "Water Supply And Drainage Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300320000", + "parentCode": "7000300000000", + "name": "暖通工程师", + "en_name": "HVAC Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300020000", + "parentCode": "7000300000000", + "name": "道路工程师", + "en_name": "Road engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300340000", + "parentCode": "7000300000000", + "name": "桥梁工程师", + "en_name": "Bridge Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300430000", + "parentCode": "7000300000000", + "name": "隧道工程师", + "en_name": "Tunnel Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300510000", + "parentCode": "7000300000000", + "name": "爆破工程师", + "en_name": "Blasting engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300450000", + "parentCode": "7000300000000", + "name": "土建勘察", + "en_name": "Construction Surveying", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300490000", + "parentCode": "7000300000000", + "name": "岩土工程", + "en_name": "Geotechnical Engineering", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300030000", + "parentCode": "7000300000000", + "name": "地铁轨道设计", + "en_name": "Railway Designing", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300140000", + "parentCode": "7000300000000", + "name": "轨道交通工程师/技术员", + "en_name": "Railway Engineer/Technician", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300220000", + "parentCode": "7000300000000", + "name": "架线和管道工程技术", + "en_name": "Stringing & Tunnel Engineering", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300250000", + "parentCode": "7000300000000", + "name": "建筑结构工程师", + "en_name": "Architectural Structural Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300260000", + "parentCode": "7000300000000", + "name": "结构设计师", + "en_name": "Structural Designer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300520000", + "parentCode": "7000300000000", + "name": "钢结构工程师", + "en_name": "Steel structure engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500030000", + "parentCode": "7000300000000", + "name": "BIM工程师", + "en_name": "BIM Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300230000", + "parentCode": "7000300000000", + "name": "测绘员", + "en_name": "surveyor", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300210000", + "parentCode": "7000300000000", + "name": "建筑机电工程师", + "en_name": "building mechanical and electrical engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300060000", + "parentCode": "7000300000000", + "name": "电力工程师", + "en_name": "Power Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300050000", + "parentCode": "7000300000000", + "name": "电力电子研发工程师", + "en_name": "Electrical/Electronic R&D Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300070000", + "parentCode": "7000300000000", + "name": "电力系统研发工程师", + "en_name": "Electric System R&D Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300330000", + "parentCode": "7000300000000", + "name": "配电设计", + "en_name": "Power Distribution Design", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300350000", + "parentCode": "7000300000000", + "name": "弱电工程师", + "en_name": "Low Voltage Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300130000", + "parentCode": "7000300000000", + "name": "光源/照明工程师", + "en_name": "Lightening Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300290000", + "parentCode": "7000300000000", + "name": "空调工程师", + "en_name": "Air-conditioning Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300300000", + "parentCode": "7000300000000", + "name": "控制保护研发工程师", + "en_name": "Control Protection R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300410000", + "parentCode": "7000300000000", + "name": "水利/港口工程技术", + "en_name": "Hydraulic Engineering", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300420000", + "parentCode": "7000300000000", + "name": "水利/水电工程师", + "en_name": "Water Conservancy/Hydroelectricity Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300500000", + "parentCode": "7000300000000", + "name": "装修工程师", + "en_name": "Renovation Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300010000", + "parentCode": "7000300000000", + "name": "安装工程师", + "en_name": "Installation Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300400000", + "parentCode": "7000300000000", + "name": "水电安装工程师", + "en_name": "Hydropower Installation Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300100000", + "parentCode": "7000300000000", + "name": "工程设计工程师", + "en_name": "Engineering Design Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300530000", + "parentCode": "7000300000000", + "name": "民航工程设计", + "en_name": "Civil aviation engineering design", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300310000", + "parentCode": "7000300000000", + "name": "内业技术员", + "en_name": "Internal Technician", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300110000", + "parentCode": "7000300000000", + "name": "工程资料员", + "en_name": "Engineering Document Clerk", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300540000", + "parentCode": "7000300000000", + "name": "海洋工程", + "en_name": "oceanographic engineering", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300550000", + "parentCode": "7000300000000", + "name": "房修工程师", + "en_name": "Room repair engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300560000", + "parentCode": "7000300000000", + "name": "材料员", + "en_name": "Material clerk", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300570000", + "parentCode": "7000300000000", + "name": "道路设计", + "en_name": "Road Design", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300580000", + "parentCode": "7000300000000", + "name": "给排水设计", + "en_name": "Water Supply And Drainage Design", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "7000100000000", + "parentCode": "7000000000000", + "name": "工程安全/工程质检", + "en_name": "Engineering Safety/Quality Control", + "deleted": false, + "sublist": [ + { + "code": "7000100010000", + "parentCode": "7000100000000", + "name": "安防", + "en_name": "Safe Guard", + "deleted": false, + "sublist": [] + }, + { + "code": "7000100020000", + "parentCode": "7000100000000", + "name": "安全管理", + "en_name": "Security Management", + "deleted": false, + "sublist": [] + }, + { + "code": "7000100040000", + "parentCode": "7000100000000", + "name": "安全主管", + "en_name": "Security Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "7000100050000", + "parentCode": "7000100000000", + "name": "工程质量管理", + "en_name": "Project Quality Management", + "deleted": false, + "sublist": [] + }, + { + "code": "7000100110000", + "parentCode": "7000100000000", + "name": "建筑工程安全管理", + "en_name": "Construction Project Safety Management", + "deleted": false, + "sublist": [] + }, + { + "code": "7000100120000", + "parentCode": "7000100000000", + "name": "施工安全员", + "en_name": "Construction Safety Officer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000100030000", + "parentCode": "7000100000000", + "name": "消防中控员", + "en_name": "Fire Central Controller", + "deleted": false, + "sublist": [] + }, + { + "code": "7000100130000", + "parentCode": "7000100000000", + "name": "消防工程师", + "en_name": "Fire Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000100060000", + "parentCode": "7000100000000", + "name": "化验员质检员", + "en_name": "Lab Technician/Quality Inspector", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "7000200000000", + "parentCode": "7000000000000", + "name": "工程管理", + "en_name": "Engineering Management", + "deleted": false, + "sublist": [ + { + "code": "7000200100000", + "parentCode": "7000200000000", + "name": "工程项目经理", + "en_name": "Engineering Project Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "7000200130000", + "parentCode": "7000200000000", + "name": "工程总监", + "en_name": "Engineering Director", + "deleted": false, + "sublist": [] + }, + { + "code": "7000200090000", + "parentCode": "7000200000000", + "name": "工程监理", + "en_name": "Project Supervising Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000200070000", + "parentCode": "7000200000000", + "name": "高级建筑工程师/建筑总工", + "en_name": "Senior Construction Engineer OR Chief Construction Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000200150000", + "parentCode": "7000200000000", + "name": "建筑施工现场管理", + "en_name": "Field Management", + "deleted": false, + "sublist": [] + }, + { + "code": "7000200110000", + "parentCode": "7000200000000", + "name": "工程预算", + "en_name": "Project Budget", + "deleted": false, + "sublist": [] + }, + { + "code": "7000200170000", + "parentCode": "7000200000000", + "name": "工程造价", + "en_name": "Engineering cost", + "deleted": false, + "sublist": [] + }, + { + "code": "7000200010000", + "parentCode": "7000200000000", + "name": "报价工程师", + "en_name": "Quotation Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000200120000", + "parentCode": "7000200000000", + "name": "工程资料管理", + "en_name": "Construction Material Management", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "7000400000000", + "parentCode": "7000000000000", + "name": "施工员", + "en_name": "Construction Worker", + "deleted": false, + "sublist": [ + { + "code": "7000400010000", + "parentCode": "7000400000000", + "name": "测绘/测量", + "en_name": "Surveyor", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400140000", + "parentCode": "7000400000000", + "name": "施工员", + "en_name": "Construction Worker", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400130000", + "parentCode": "7000400000000", + "name": "施工队长", + "en_name": "Construction Team Leader", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400210000", + "parentCode": "7000400000000", + "name": "绿化工", + "en_name": "Green Chemical Industry", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400220000", + "parentCode": "7000400000000", + "name": "智能大厦/布线", + "en_name": "Smart building or cabling", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400070000", + "parentCode": "7000400000000", + "name": "电梯工", + "en_name": "Elevator Worker", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400100000", + "parentCode": "7000400000000", + "name": "空调工", + "en_name": "Air-conditioning Technician", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400080000", + "parentCode": "7000400000000", + "name": "钢筋工", + "en_name": "Steel Fixer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400160000", + "parentCode": "7000400000000", + "name": "土建工", + "en_name": "Civil Engineering Worker", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400250000", + "parentCode": "7000400000000", + "name": "木工", + "en_name": "Carpentry", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400240000", + "parentCode": "7000400000000", + "name": "水工", + "en_name": "Hydraulic engineering", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400180000", + "parentCode": "7000400000000", + "name": "瓦工", + "en_name": "Bricklayer", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "2000000000000", + "parentCode": null, + "name": "物流/采购/供应链", + "en_name": "Logistics/Procurement/Supply Chain", + "deleted": false, + "sublist": [ + { + "code": "2000100000000", + "parentCode": "2000000000000", + "name": "采购/供应链/材料管理", + "en_name": "Procurement/Supply Chain/Materials Management", + "deleted": false, + "sublist": [ + { + "code": "2000100010000", + "parentCode": "2000100000000", + "name": "采购专员/助理", + "en_name": "Purchasing specialist or assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100140000", + "parentCode": "2000100000000", + "name": "采购经理/主管", + "en_name": "Purchasing manager or supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100130000", + "parentCode": "2000100000000", + "name": "采购总监", + "en_name": "Purchasing director", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100190000", + "parentCode": "2000100000000", + "name": "采购工程师", + "en_name": "Procurement Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100080000", + "parentCode": "2000100000000", + "name": "商务采购", + "en_name": "Business Procurement", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100060000", + "parentCode": "2000100000000", + "name": "买手", + "en_name": "Buyer", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100040000", + "parentCode": "2000100000000", + "name": "供应链专员", + "en_name": "Supply Chain Coordinator", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100160000", + "parentCode": "2000100000000", + "name": "供应链经理", + "en_name": "Supply Chain Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100150000", + "parentCode": "2000100000000", + "name": "供应链总监", + "en_name": "Supply chain director", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100050000", + "parentCode": "2000100000000", + "name": "供应商/采购质量管理", + "en_name": "Vendor Management", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100170000", + "parentCode": "2000100000000", + "name": "供应商开发", + "en_name": "Supplier development", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100100000", + "parentCode": "2000100000000", + "name": "物料专员", + "en_name": "Specialist of Materials", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100090000", + "parentCode": "2000100000000", + "name": "物料经理", + "en_name": "Manager of Materials", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100110000", + "parentCode": "2000100000000", + "name": "项目招投标", + "en_name": "Project Bidding", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100180000", + "parentCode": "2000100000000", + "name": "贸易跟单", + "en_name": "trade documentary", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100200000", + "parentCode": "2000100000000", + "name": "商品经理", + "en_name": "Commodity manager", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "2000500000000", + "parentCode": "2000000000000", + "name": "物流管理", + "en_name": "Physical distribution management", + "deleted": false, + "sublist": [ + { + "code": "2000500120000", + "parentCode": "2000500000000", + "name": "物流专员", + "en_name": "Logistics Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "2000500110000", + "parentCode": "2000500000000", + "name": "物流经理", + "en_name": "Logistics Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "2000500130000", + "parentCode": "2000500000000", + "name": "物流总监", + "en_name": "Director of Logistics", + "deleted": false, + "sublist": [] + }, + { + "code": "2000500080000", + "parentCode": "2000500000000", + "name": "物流跟单", + "en_name": "Logistics Merchandiser", + "deleted": false, + "sublist": [] + }, + { + "code": "2000500090000", + "parentCode": "2000500000000", + "name": "物流规划", + "en_name": "Logistics Planning", + "deleted": false, + "sublist": [] + }, + { + "code": "2000500100000", + "parentCode": "2000500000000", + "name": "物流监管", + "en_name": "Logistics Supervision", + "deleted": false, + "sublist": [] + }, + { + "code": "2000500160000", + "parentCode": "2000500000000", + "name": "物流运营", + "en_name": "Logistics operation", + "deleted": false, + "sublist": [] + }, + { + "code": "2000500070000", + "parentCode": "2000500000000", + "name": "调度员", + "en_name": "Dispatcher", + "deleted": false, + "sublist": [] + }, + { + "code": "2000500150000", + "parentCode": "2000500000000", + "name": "运输经理/主管", + "en_name": "Transportation Manager or Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "2000500020000", + "parentCode": "2000500000000", + "name": "货运代理专员", + "en_name": "Freight forwarding specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "2000500170000", + "parentCode": "2000500000000", + "name": "货运代理经理", + "en_name": "Freight Forwarding Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "2000500040000", + "parentCode": "2000500000000", + "name": "集装箱管理", + "en_name": "Container Management", + "deleted": false, + "sublist": [] + }, + { + "code": "2000500060000", + "parentCode": "2000500000000", + "name": "水/空/陆运操作", + "en_name": "Freight Operation", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "2000200000000", + "parentCode": "2000000000000", + "name": "仓储管理", + "en_name": "Warehouse Management", + "deleted": false, + "sublist": [ + { + "code": "2000200020000", + "parentCode": "2000200000000", + "name": "仓库管理员", + "en_name": "Godown Keeper", + "deleted": false, + "sublist": [] + }, + { + "code": "2000200040000", + "parentCode": "2000200000000", + "name": "仓库文员", + "en_name": "Secretary of Warehouse", + "deleted": false, + "sublist": [] + }, + { + "code": "2000200030000", + "parentCode": "2000200000000", + "name": "仓库经理", + "en_name": "Warehouse Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "2000200010000", + "parentCode": "2000200000000", + "name": "仓储物料管理", + "en_name": "Warehouse & Material Management", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "2000300000000", + "parentCode": "2000000000000", + "name": "交通运输", + "en_name": "Transportation", + "deleted": false, + "sublist": [ + { + "code": "6000700080000", + "parentCode": "2000300000000", + "name": "商务司机", + "en_name": "Business Driver", + "deleted": false, + "sublist": [] + }, + { + "code": "6000700100000", + "parentCode": "2000300000000", + "name": "网约车司机", + "en_name": "Taxi/Ride-hailing Driver", + "deleted": false, + "sublist": [] + }, + { + "code": "6000700010000", + "parentCode": "2000300000000", + "name": "出租车驾驶员", + "en_name": "Taxi Driver", + "deleted": false, + "sublist": [] + }, + { + "code": "6000700090000", + "parentCode": "2000300000000", + "name": "通勤司机", + "en_name": "Shuttle Bus Driver", + "deleted": false, + "sublist": [] + }, + { + "code": "6000700060000", + "parentCode": "2000300000000", + "name": "机动车司机/驾驶", + "en_name": "Automobile Driver", + "deleted": false, + "sublist": [] + }, + { + "code": "6000700040000", + "parentCode": "2000300000000", + "name": "代驾司机", + "en_name": "Valet Driver", + "deleted": false, + "sublist": [] + }, + { + "code": "2000300030000", + "parentCode": "2000300000000", + "name": "货运司机", + "en_name": "Freight Driver", + "deleted": false, + "sublist": [] + }, + { + "code": "2000300050000", + "parentCode": "2000300000000", + "name": "危化品司机", + "en_name": "Truck Man of Dangerous Goods", + "deleted": false, + "sublist": [] + }, + { + "code": "6000700110000", + "parentCode": "2000300000000", + "name": "校车司机", + "en_name": "School Bus Driver", + "deleted": false, + "sublist": [] + }, + { + "code": "6000700030000", + "parentCode": "2000300000000", + "name": "客运司机", + "en_name": "Bus Driver", + "deleted": false, + "sublist": [] + }, + { + "code": "2000300040000", + "parentCode": "2000300000000", + "name": "卡车司机", + "en_name": "Truck Driver", + "deleted": false, + "sublist": [] + }, + { + "code": "2000300010000", + "parentCode": "2000300000000", + "name": "吊车司机", + "en_name": "Crane Driver", + "deleted": false, + "sublist": [] + }, + { + "code": "2000300060000", + "parentCode": "2000300000000", + "name": "油罐车司机", + "en_name": "Fuel Tanker Driver", + "deleted": false, + "sublist": [] + }, + { + "code": "6000700050000", + "parentCode": "2000300000000", + "name": "飞机驾驶/操作", + "en_name": "Aircraft Pilot", + "deleted": false, + "sublist": [] + }, + { + "code": "6000700070000", + "parentCode": "2000300000000", + "name": "列车驾驶/操作", + "en_name": "Train Driver", + "deleted": false, + "sublist": [] + }, + { + "code": "6000700020000", + "parentCode": "2000300000000", + "name": "船舶驾驶/操作", + "en_name": "Ship Operator", + "deleted": false, + "sublist": [] + }, + { + "code": "6000300020000", + "parentCode": "2000300000000", + "name": "船员/水手", + "en_name": "Sailor", + "deleted": false, + "sublist": [] + }, + { + "code": "6000300030000", + "parentCode": "2000300000000", + "name": "地铁站务员", + "en_name": "Subway crew", + "deleted": false, + "sublist": [] + }, + { + "code": "6000300050000", + "parentCode": "2000300000000", + "name": "公交/地铁乘务", + "en_name": "Bus/Subway Steward", + "deleted": false, + "sublist": [] + }, + { + "code": "6000300040000", + "parentCode": "2000300000000", + "name": "高铁乘务", + "en_name": "Train Steward", + "deleted": false, + "sublist": [] + }, + { + "code": "6000300080000", + "parentCode": "2000300000000", + "name": "乘务员", + "en_name": "Flight Attendant", + "deleted": false, + "sublist": [] + }, + { + "code": "6000300060000", + "parentCode": "2000300000000", + "name": "航空乘务", + "en_name": "Air Steward", + "deleted": false, + "sublist": [] + }, + { + "code": "6000300070000", + "parentCode": "2000300000000", + "name": "机场地勤人员", + "en_name": "Ground Staff", + "deleted": false, + "sublist": [] + }, + { + "code": "6000300010000", + "parentCode": "2000300000000", + "name": "船舶乘务", + "en_name": "Cruise Crew", + "deleted": false, + "sublist": [] + }, + { + "code": "6000300090000", + "parentCode": "2000300000000", + "name": "邮轮乘务", + "en_name": "Cruise Steward", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "2000400000000", + "parentCode": "2000000000000", + "name": "配送理货", + "en_name": "Delivery Services", + "deleted": false, + "sublist": [ + { + "code": "2000400020000", + "parentCode": "2000400000000", + "name": "快递员", + "en_name": "Courier", + "deleted": false, + "sublist": [] + }, + { + "code": "2000400070000", + "parentCode": "2000400000000", + "name": "配送站长", + "en_name": "Distribution webmaster", + "deleted": false, + "sublist": [] + }, + { + "code": "2000400050000", + "parentCode": "2000400000000", + "name": "送餐员", + "en_name": "Waiter", + "deleted": false, + "sublist": [] + }, + { + "code": "2000400030000", + "parentCode": "2000400000000", + "name": "配/理/拣/发货", + "en_name": "Delivery Services", + "deleted": false, + "sublist": [] + }, + { + "code": "2000400040000", + "parentCode": "2000400000000", + "name": "配送员", + "en_name": "Delivery Clerk", + "deleted": false, + "sublist": [] + }, + { + "code": "2000400010000", + "parentCode": "2000400000000", + "name": "搬运/装卸工", + "en_name": "Porter/Stevedore", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "14000300000000", + "parentCode": "2000000000000", + "name": "关务", + "en_name": "Custom Management", + "deleted": false, + "sublist": [ + { + "code": "14000700030000", + "parentCode": "14000300000000", + "name": "单证员", + "en_name": "Documentation Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "14000300020000", + "parentCode": "14000300000000", + "name": "关务管理", + "en_name": "Custom Affairs", + "deleted": false, + "sublist": [] + }, + { + "code": "14000300030000", + "parentCode": "14000300000000", + "name": "关务", + "en_name": "Custom Affairs Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "14000300040000", + "parentCode": "14000300000000", + "name": "报关/报检员", + "en_name": "Customs or Inspector", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "22000000000000", + "parentCode": null, + "name": "汽车", + "en_name": "Car", + "deleted": false, + "sublist": [ + { + "code": "15000800000000", + "parentCode": "22000000000000", + "name": "汽车制造", + "en_name": "Automobile manufacturing", + "deleted": false, + "sublist": [ + { + "code": "10000200010000", + "parentCode": "15000800000000", + "name": "车身/造型设计", + "en_name": "Body / styling", + "deleted": false, + "sublist": [] + }, + { + "code": "10000200140000", + "parentCode": "15000800000000", + "name": "汽车零部件设计", + "en_name": "Vehicle Parts Design", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300120000", + "parentCode": "15000800000000", + "name": "动力系统设计", + "en_name": "Power System Design", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300390000", + "parentCode": "15000800000000", + "name": "动力系统工程师", + "en_name": "Power System Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300600000", + "parentCode": "15000800000000", + "name": "总装工程师", + "en_name": "Final-Assembly Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300320000", + "parentCode": "15000800000000", + "name": "内外饰设计工程师", + "en_name": "Interior/Exterior Designer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300370000", + "parentCode": "15000800000000", + "name": "底盘工程师", + "en_name": "Chassis Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300380000", + "parentCode": "15000800000000", + "name": "汽车电子工程师", + "en_name": "Automobile Electronics Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300410000", + "parentCode": "15000800000000", + "name": "汽车机械工程师", + "en_name": "Automobile Mechanical Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300400000", + "parentCode": "15000800000000", + "name": "汽车结构工程师", + "en_name": "Automotive structural engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000800010000", + "parentCode": "15000800000000", + "name": "线束设计", + "en_name": "Harness Design", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300420000", + "parentCode": "15000800000000", + "name": "汽车装配工艺工程师", + "en_name": "Automobile Assembly Process Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400120000", + "parentCode": "15000800000000", + "name": "汽车质量工程师", + "en_name": "Automotive Quality Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000800020000", + "parentCode": "15000800000000", + "name": "充电桩设计", + "en_name": "Charging Pile Design", + "deleted": false, + "sublist": [] + }, + { + "code": "15000800030000", + "parentCode": "15000800000000", + "name": "汽车安全性能工程师", + "en_name": "Automotive Safety Performance Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000800050000", + "parentCode": "15000800000000", + "name": "汽车试验工程师", + "en_name": "Automotive Test Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000800070000", + "parentCode": "15000800000000", + "name": "汽车标定工程师", + "en_name": "Automotive Calibration Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000800080000", + "parentCode": "15000800000000", + "name": "汽车发动机工程师", + "en_name": "Automotive Engine Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000800090000", + "parentCode": "15000800000000", + "name": "车联网工程师", + "en_name": "Internet Of Vehicles Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000800110000", + "parentCode": "15000800000000", + "name": "汽车总布置工程师", + "en_name": "Automotive General Arrangement Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000800120000", + "parentCode": "15000800000000", + "name": "汽车试制工程师", + "en_name": "Automotive Trial Production Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000800130000", + "parentCode": "15000800000000", + "name": "汽车NVH工程师", + "en_name": "Automotive NVH Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000800140000", + "parentCode": "15000800000000", + "name": "汽车CAE/CFD工程师", + "en_name": "Automotive CAE OR CFD Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000800150000", + "parentCode": "15000800000000", + "name": "变速箱/传动系统工程师", + "en_name": "Transmission OR Transmission System Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000800160000", + "parentCode": "15000800000000", + "name": "悬架/车桥/车架系统工程师", + "en_name": "Suspension OR Axle OR Frame System Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000800170000", + "parentCode": "15000800000000", + "name": "转向/制动系统工程师", + "en_name": "Steering OR Brake System Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300360000", + "parentCode": "15000800000000", + "name": "汽车/摩托车工程师", + "en_name": "Automobile Engineer", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "22000100000000", + "parentCode": "22000000000000", + "name": "新能源汽车", + "en_name": "New Energy Vehicle", + "deleted": false, + "sublist": [ + { + "code": "22000100010000", + "parentCode": "22000100000000", + "name": "新能源电池工程师", + "en_name": "New Energy Battery Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "22000100020000", + "parentCode": "22000100000000", + "name": "新能源电控工程师", + "en_name": "New Energy Electric Control Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "22000100030000", + "parentCode": "22000100000000", + "name": "新能源电机工程师", + "en_name": "New Energy Motor Engineer", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "15000900000000", + "parentCode": "22000000000000", + "name": "汽车销售与服务", + "en_name": "Automobile sales and service", + "deleted": false, + "sublist": [ + { + "code": "19000200150000", + "parentCode": "15000900000000", + "name": "汽车销售", + "en_name": "Automobile Sales", + "deleted": false, + "sublist": [] + }, + { + "code": "15000900010000", + "parentCode": "15000900000000", + "name": "汽车配件销售", + "en_name": "Auto Parts Sales", + "deleted": false, + "sublist": [] + }, + { + "code": "13000100140000", + "parentCode": "15000900000000", + "name": "汽车售后服务", + "en_name": "Automobile Aftersales Services", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200010000", + "parentCode": "15000900000000", + "name": "4S店店长", + "en_name": "4S Store Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "12000100130000", + "parentCode": "15000900000000", + "name": "车险勘查", + "en_name": "Car Insurance Survey", + "deleted": false, + "sublist": [] + }, + { + "code": "12000100170000", + "parentCode": "15000900000000", + "name": "汽车查勘定损", + "en_name": "Automotive Claims Adjuster", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300050000", + "parentCode": "15000900000000", + "name": "二手车评估师", + "en_name": "Used Car Appraiser", + "deleted": false, + "sublist": [] + }, + { + "code": "15000900020000", + "parentCode": "15000900000000", + "name": "二手车经纪人", + "en_name": "Used car broker", + "deleted": false, + "sublist": [] + }, + { + "code": "6001100010000", + "parentCode": "15000900000000", + "name": "抛光工", + "en_name": "Polisher", + "deleted": false, + "sublist": [] + }, + { + "code": "6001100090000", + "parentCode": "15000900000000", + "name": "汽车维修/保养", + "en_name": "Automotive Technician/Mechanic", + "deleted": false, + "sublist": [] + }, + { + "code": "6001100020000", + "parentCode": "15000900000000", + "name": "汽车保养", + "en_name": "Car Maintenance", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100290000", + "parentCode": "15000900000000", + "name": "汽车电工", + "en_name": "Automobile Electrician", + "deleted": false, + "sublist": [] + }, + { + "code": "6001100040000", + "parentCode": "15000900000000", + "name": "汽车改装", + "en_name": "Auto Modification", + "deleted": false, + "sublist": [] + }, + { + "code": "6001100060000", + "parentCode": "15000900000000", + "name": "汽车美容/装潢", + "en_name": "Automotive Detailer/Decorator", + "deleted": false, + "sublist": [] + }, + { + "code": "6001100070000", + "parentCode": "15000900000000", + "name": "汽车喷漆", + "en_name": "Car Painting", + "deleted": false, + "sublist": [] + }, + { + "code": "6001100080000", + "parentCode": "15000900000000", + "name": "汽车贴膜", + "en_name": "Auto Foil", + "deleted": false, + "sublist": [] + }, + { + "code": "6001100100000", + "parentCode": "15000900000000", + "name": "汽车装潢", + "en_name": "Car Decoration", + "deleted": false, + "sublist": [] + }, + { + "code": "6001100050000", + "parentCode": "15000900000000", + "name": "汽车轮胎服务", + "en_name": "Tire Service", + "deleted": false, + "sublist": [] + }, + { + "code": "6001100110000", + "parentCode": "15000900000000", + "name": "洗车工", + "en_name": "Car Washer", + "deleted": false, + "sublist": [] + }, + { + "code": "6000500050000", + "parentCode": "15000900000000", + "name": "加油/加气/充电站工作人员", + "en_name": "Gas Station Staff", + "deleted": false, + "sublist": [] + }, + { + "code": "15000900030000", + "parentCode": "15000900000000", + "name": "汽车金融销售", + "en_name": "Auto Finance Sales", + "deleted": false, + "sublist": [] + }, + { + "code": "15000900040000", + "parentCode": "15000900000000", + "name": "汽车钣金工", + "en_name": "Automotive Sheet Metal", + "deleted": false, + "sublist": [] + }, + { + "code": "15000900050000", + "parentCode": "15000900000000", + "name": "汽车救援员", + "en_name": "Car Rescue Worker", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "15000000000000", + "parentCode": null, + "name": "生产制造/营运管理", + "en_name": "Manufacture & Operation", + "deleted": false, + "sublist": [ + { + "code": "15000200000000", + "parentCode": "15000000000000", + "name": "生产管理", + "en_name": "Production Management", + "deleted": false, + "sublist": [ + { + "code": "15000200210000", + "parentCode": "15000200000000", + "name": "生产跟单", + "en_name": "Order Follow-up", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200090000", + "parentCode": "15000200000000", + "name": "生产计划/物料管理(PMC)", + "en_name": "Production Plan Management", + "deleted": false, + "sublist": [] + }, + { + "code": "14000700100000", + "parentCode": "15000200000000", + "name": "生产跟单/文员", + "en_name": "Production Follow-up Clerk", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200180000", + "parentCode": "15000200000000", + "name": "生产组长/拉长", + "en_name": "Production Line Leader", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200160000", + "parentCode": "15000200000000", + "name": "生产经理/主管", + "en_name": "Production supervisor or supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200100000", + "parentCode": "15000200000000", + "name": "生产经理", + "en_name": "Production Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200170000", + "parentCode": "15000200000000", + "name": "生产总监", + "en_name": "Production Director", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200010000", + "parentCode": "15000200000000", + "name": "车间主任", + "en_name": "Plant Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200160000", + "parentCode": "15000200000000", + "name": "厂长", + "en_name": "Factory Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200250000", + "parentCode": "15000200000000", + "name": "总工程师/副总工程师", + "en_name": "Chief Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200140000", + "parentCode": "15000200000000", + "name": "生产项目工程师", + "en_name": "Production Project Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200120000", + "parentCode": "15000200000000", + "name": "生产物料管理(PMC)", + "en_name": "PMC", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200150000", + "parentCode": "15000200000000", + "name": "生产运营管理", + "en_name": "Production Operation Management", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200080000", + "parentCode": "15000200000000", + "name": "生产产品管理", + "en_name": "Product Management", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200110000", + "parentCode": "15000200000000", + "name": "生产设备管理", + "en_name": "Production Equipment Management", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200050000", + "parentCode": "15000200000000", + "name": "机械设备经理", + "en_name": "Mechanical Equipment Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200020000", + "parentCode": "15000200000000", + "name": "工程机械经理/主管", + "en_name": "Mechanical Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200030000", + "parentCode": "15000200000000", + "name": "工程机械主管", + "en_name": "Mechanical Leader", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200190000", + "parentCode": "15000200000000", + "name": "生产营运经理", + "en_name": "Production And Operation Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200200000", + "parentCode": "15000200000000", + "name": "生产营运主管", + "en_name": "Production And Operation Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "15000200220000", + "parentCode": "15000200000000", + "name": "生产安全员", + "en_name": "Safety Officer", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "15000300000000", + "parentCode": "15000000000000", + "name": "机械设计/制造", + "en_name": "Mechanical design or manufacturing", + "deleted": false, + "sublist": [ + { + "code": "10000200070000", + "parentCode": "15000300000000", + "name": "机械设计", + "en_name": "Mechanical Design", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300270000", + "parentCode": "15000300000000", + "name": "机械制图", + "en_name": "Mechanical Drawing", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300230000", + "parentCode": "15000300000000", + "name": "机械工程师", + "en_name": "Mechanical Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300240000", + "parentCode": "15000300000000", + "name": "机械工艺工程师", + "en_name": "Mechanical Process Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300250000", + "parentCode": "15000300000000", + "name": "机械结构工程师", + "en_name": "Mechanical Structure Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300260000", + "parentCode": "15000300000000", + "name": "机械设备工程师", + "en_name": "Mechanical Equipment Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "10000200080000", + "parentCode": "15000300000000", + "name": "机械研发工程师", + "en_name": "Mechanical R&D Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300220000", + "parentCode": "15000300000000", + "name": "机电工程师", + "en_name": "Electrical & Mechanical Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300610000", + "parentCode": "15000300000000", + "name": "电机工程师", + "en_name": "electrical engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300310000", + "parentCode": "15000300000000", + "name": "模具工程师", + "en_name": "Tooling Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "10000200130000", + "parentCode": "15000300000000", + "name": "模具设计", + "en_name": "Mold Design", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300290000", + "parentCode": "15000300000000", + "name": "夹具工程师", + "en_name": "Clamp Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300070000", + "parentCode": "15000300000000", + "name": "材料工程师", + "en_name": "Material Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300140000", + "parentCode": "15000300000000", + "name": "高分子材料工程师", + "en_name": "High Polymer Material Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300170000", + "parentCode": "15000300000000", + "name": "材料工艺工程师", + "en_name": "Material Process Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300530000", + "parentCode": "15000300000000", + "name": "工艺/制程工程师", + "en_name": "Process or process engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300540000", + "parentCode": "15000300000000", + "name": "制造工程师", + "en_name": "Manufacturing Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300150000", + "parentCode": "15000300000000", + "name": "工业工程师", + "en_name": "Industrial Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300160000", + "parentCode": "15000300000000", + "name": "工业机器人工程师", + "en_name": "Industrial Robert Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300640000", + "parentCode": "15000300000000", + "name": "机器人调试工程师", + "en_name": "Robot Debugging Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300580000", + "parentCode": "15000300000000", + "name": "装配工程师", + "en_name": "Assembly engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300550000", + "parentCode": "15000300000000", + "name": "注塑工程师", + "en_name": "Injection Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300090000", + "parentCode": "15000300000000", + "name": "冲压工程师", + "en_name": "Stamping Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300510000", + "parentCode": "15000300000000", + "name": "液压工程师", + "en_name": "Hydraulic Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300200000", + "parentCode": "15000300000000", + "name": "锅炉工程师/技师", + "en_name": "Boiler Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300210000", + "parentCode": "15000300000000", + "name": "焊接工程师", + "en_name": "Welding Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300180000", + "parentCode": "15000300000000", + "name": "工装工程师", + "en_name": "Fixture Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300050000", + "parentCode": "15000300000000", + "name": "包装工程师", + "en_name": "Packaging Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300040000", + "parentCode": "15000300000000", + "name": "安全性能工程师", + "en_name": "Safety Performance Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300280000", + "parentCode": "15000300000000", + "name": "激光工程师", + "en_name": "Laser Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "9000500330000", + "parentCode": "15000300000000", + "name": "精益工程师", + "en_name": "Lean Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300300000", + "parentCode": "15000300000000", + "name": "零部件设计", + "en_name": "Parts Design", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300340000", + "parentCode": "15000300000000", + "name": "喷涂技术员", + "en_name": "Painting Technician", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300620000", + "parentCode": "15000300000000", + "name": "涂装工程师", + "en_name": "Painting Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300560000", + "parentCode": "15000300000000", + "name": "铸造/锻造工程师", + "en_name": "Casting / Forging Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300350000", + "parentCode": "15000300000000", + "name": "气动工程师", + "en_name": "Utility Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300430000", + "parentCode": "15000300000000", + "name": "热能工程师", + "en_name": "Thermal Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300440000", + "parentCode": "15000300000000", + "name": "生产工程师", + "en_name": "Production Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300450000", + "parentCode": "15000300000000", + "name": "生产技术员", + "en_name": "Production Technician", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300460000", + "parentCode": "15000300000000", + "name": "试验工程师", + "en_name": "Testing Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300520000", + "parentCode": "15000300000000", + "name": "仪器/仪表工程师", + "en_name": "Instrument or instrumentation engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "10000200120000", + "parentCode": "15000300000000", + "name": "列车设计制造", + "en_name": "Train Design & Production", + "deleted": false, + "sublist": [] + }, + { + "code": "10000200020000", + "parentCode": "15000300000000", + "name": "船舶设计制造", + "en_name": "Ship Design & Production", + "deleted": false, + "sublist": [] + }, + { + "code": "10000200050000", + "parentCode": "15000300000000", + "name": "发动机工程师", + "en_name": "Engine engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "10000200060000", + "parentCode": "15000300000000", + "name": "飞机设计制造", + "en_name": "Aircraft Design & Production", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300010000", + "parentCode": "15000300000000", + "name": "CNC/数控编程", + "en_name": "Cnc or nc programming", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300630000", + "parentCode": "15000300000000", + "name": "热设计工程师", + "en_name": "Thermal Design Engineer", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "15001000000000", + "parentCode": "15000000000000", + "name": "机械设备维修", + "en_name": "Mechanical equipment maintenance", + "deleted": false, + "sublist": [ + { + "code": "20000200140000", + "parentCode": "15001000000000", + "name": "机械维修/保养", + "en_name": "Mechanical Repair/Maintenance", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200160000", + "parentCode": "15001000000000", + "name": "列车维修/保养", + "en_name": "Train Repair/Maintenance", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200110000", + "parentCode": "15001000000000", + "name": "船舶维修/保养", + "en_name": "Ship Repair/Maintenance", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200130000", + "parentCode": "15001000000000", + "name": "飞机维修/保养", + "en_name": "Aircraft Repair/Maintenance", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "15000400000000", + "parentCode": "15000000000000", + "name": "生产质量管理", + "en_name": "Production Quality Control", + "deleted": false, + "sublist": [ + { + "code": "15000400150000", + "parentCode": "15000400000000", + "name": "质检员", + "en_name": "Quality Inspector", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400030000", + "parentCode": "15000400000000", + "name": "QC质检", + "en_name": "QC", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400020000", + "parentCode": "15000400000000", + "name": "QA专员", + "en_name": "QA Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400010000", + "parentCode": "15000400000000", + "name": "QA工程师", + "en_name": "QA Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400060000", + "parentCode": "15000400000000", + "name": "化验工程师", + "en_name": "Assay Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400090000", + "parentCode": "15000400000000", + "name": "检测", + "en_name": "Monitoring", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400040000", + "parentCode": "15000400000000", + "name": "采样技术员", + "en_name": "Sampling Technician", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400110000", + "parentCode": "15000400000000", + "name": "品控/品质主管", + "en_name": "QC Supervisor", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400160000", + "parentCode": "15000400000000", + "name": "质量管理/测试", + "en_name": "Quality Management/Testing", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300190000", + "parentCode": "15000400000000", + "name": "故障分析师", + "en_name": "Fault Analyst", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400170000", + "parentCode": "15000400000000", + "name": "质量体系工程师", + "en_name": "QS Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400180000", + "parentCode": "15000400000000", + "name": "认证工程师", + "en_name": "Certified engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400190000", + "parentCode": "15000400000000", + "name": "认证审核员", + "en_name": "certified auditor", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400100000", + "parentCode": "15000400000000", + "name": "可靠度工程师", + "en_name": "Reliability Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "20000100080000", + "parentCode": "15000400000000", + "name": "无损检测工程师", + "en_name": "Nondestructive testing", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400070000", + "parentCode": "15000400000000", + "name": "计量工程师", + "en_name": "Metrology Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400080000", + "parentCode": "15000400000000", + "name": "技术文档工程师", + "en_name": "Technical Documentation Engineer", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "15000700000000", + "parentCode": "15000000000000", + "name": "化工", + "en_name": "Chemical industry", + "deleted": false, + "sublist": [ + { + "code": "10000100020000", + "parentCode": "15000700000000", + "name": "化工研发", + "en_name": "Chemical Engineering R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100080000", + "parentCode": "15000700000000", + "name": "化验/检验", + "en_name": "Laboratory Testing", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100040000", + "parentCode": "15000700000000", + "name": "化学操作工", + "en_name": "Chemical Operating", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100050000", + "parentCode": "15000700000000", + "name": "化学分析", + "en_name": "Chemical Analysis", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100060000", + "parentCode": "15000700000000", + "name": "化学技术应用", + "en_name": "Chemical Technical Application", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100030000", + "parentCode": "15000700000000", + "name": "化学/化工技术总监", + "en_name": "Chemical Technical Director", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100070000", + "parentCode": "15000700000000", + "name": "化学制剂研发", + "en_name": "Chemical R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300490000", + "parentCode": "15000700000000", + "name": "涂料研发", + "en_name": "Coating Products R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100240000", + "parentCode": "15000700000000", + "name": "油漆/化工涂料研发", + "en_name": "Chemical Coating R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100250000", + "parentCode": "15000700000000", + "name": "造纸研发", + "en_name": "Papermaking R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100090000", + "parentCode": "15000700000000", + "name": "化妆品研发", + "en_name": "Cosmetic R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100150000", + "parentCode": "15000700000000", + "name": "食品/饮料检验", + "en_name": "Food & Beverage Inspection", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100160000", + "parentCode": "15000700000000", + "name": "食品/饮料研发", + "en_name": "Food & Beverage R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300480000", + "parentCode": "15000700000000", + "name": "塑料工程师", + "en_name": "Plastics Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300500000", + "parentCode": "15000700000000", + "name": "橡胶工程师", + "en_name": "Rubber Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100010000", + "parentCode": "15000700000000", + "name": "化学合成", + "en_name": "Chemical Synthesis", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "15000500000000", + "parentCode": "15000000000000", + "name": "服装/纺织/皮革", + "en_name": "Clothing , textile or leather", + "deleted": false, + "sublist": [ + { + "code": "2000100070000", + "parentCode": "15000500000000", + "name": "面料辅料采购", + "en_name": "Fabric accessories procurement", + "deleted": false, + "sublist": [] + }, + { + "code": "15000500010000", + "parentCode": "15000500000000", + "name": "面料辅料开发", + "en_name": "Development of fabric accessories", + "deleted": false, + "sublist": [] + }, + { + "code": "15000300130000", + "parentCode": "15000500000000", + "name": "服装/纺织/皮革工艺师", + "en_name": "Clothing/Textile/Leather Technologist", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100150000", + "parentCode": "15000500000000", + "name": "打样/制版", + "en_name": "Proofing / Plate Making", + "deleted": false, + "sublist": [] + }, + { + "code": "17000300020000", + "parentCode": "15000500000000", + "name": "布艺设计", + "en_name": "Home Textile Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000300100000", + "parentCode": "15000500000000", + "name": "鞋子设计", + "en_name": "Shoe Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000300040000", + "parentCode": "15000500000000", + "name": "服装/纺织设计", + "en_name": "Clothing / Textile Design", + "deleted": false, + "sublist": [] + }, + { + "code": "15000500020000", + "parentCode": "15000500000000", + "name": "服装/纺织/皮革跟单", + "en_name": "clothing or textile or leather merchandiser", + "deleted": false, + "sublist": [] + }, + { + "code": "17000300050000", + "parentCode": "15000500000000", + "name": "服装/纺织设计总监", + "en_name": "Textile/Fashion Design Director", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400050000", + "parentCode": "15000500000000", + "name": "服装/纺织品/皮革质量管理", + "en_name": "Clothing/Textile/Leather Quality Management", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100210000", + "parentCode": "15000500000000", + "name": "浆纱工", + "en_name": "Slashing Worker", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100260000", + "parentCode": "15000500000000", + "name": "漂染工", + "en_name": "Bleaching & Dyeing Worker", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100400000", + "parentCode": "15000500000000", + "name": "细纱工", + "en_name": "Spinner", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100430000", + "parentCode": "15000500000000", + "name": "样衣工", + "en_name": "Apparel Sample Maker", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100450000", + "parentCode": "15000500000000", + "name": "印染工", + "en_name": "Printing & Dyeing Worker", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100500000", + "parentCode": "15000500000000", + "name": "整经工", + "en_name": "Warper", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100050000", + "parentCode": "15000500000000", + "name": "裁剪工", + "en_name": "Cutter", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100090000", + "parentCode": "15000500000000", + "name": "挡车工", + "en_name": "Knitter", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100120000", + "parentCode": "15000500000000", + "name": "电脑放码员", + "en_name": "Grading", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100140000", + "parentCode": "15000500000000", + "name": "缝纫工", + "en_name": "Sewer", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "15000600000000", + "parentCode": "15000000000000", + "name": "印刷包装", + "en_name": "Printing and packaging", + "deleted": false, + "sublist": [ + { + "code": "15000100480000", + "parentCode": "15000600000000", + "name": "印刷排版", + "en_name": "Printing And Typesetting", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100470000", + "parentCode": "15000600000000", + "name": "印刷机械机长", + "en_name": "Printing Machine Operator", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100460000", + "parentCode": "15000600000000", + "name": "印刷操作", + "en_name": "Printing Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100080000", + "parentCode": "15000600000000", + "name": "打稿机操作员", + "en_name": "Typewriter Operator", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100100000", + "parentCode": "15000600000000", + "name": "电分操作员", + "en_name": "Electronic Scanning Operator", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100310000", + "parentCode": "15000600000000", + "name": "切纸机操作工", + "en_name": "Paper Cutter Operator", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100030000", + "parentCode": "15000600000000", + "name": "包装工", + "en_name": "Packer", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100170000", + "parentCode": "15000600000000", + "name": "复卷工", + "en_name": "Rewinderman", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100320000", + "parentCode": "15000600000000", + "name": "晒版员", + "en_name": "Plate Burning Operator", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100360000", + "parentCode": "15000600000000", + "name": "烫金工", + "en_name": "Gilding Worker", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100410000", + "parentCode": "15000600000000", + "name": "压痕工", + "en_name": "Indenter", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100530000", + "parentCode": "15000600000000", + "name": "装订工", + "en_name": "Bookbinder", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100370000", + "parentCode": "15000600000000", + "name": "调墨技师", + "en_name": "Ink Technician", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100350000", + "parentCode": "15000600000000", + "name": "数码直印/菲林输出", + "en_name": "Digital/Film Printing", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "15000100000000", + "parentCode": "15000000000000", + "name": "普工/技工", + "en_name": "Production Worker/Technician", + "deleted": false, + "sublist": [ + { + "code": "15000100570000", + "parentCode": "15000100000000", + "name": "CNC/数控操作", + "en_name": "Cnc Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100270000", + "parentCode": "15000100000000", + "name": "普工/操作工", + "en_name": "Worker", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100190000", + "parentCode": "15000100000000", + "name": "技工", + "en_name": "Technician", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100180000", + "parentCode": "15000100000000", + "name": "机电技师", + "en_name": "Electrical Technician", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100300000", + "parentCode": "15000100000000", + "name": "钳工", + "en_name": "Fitter", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400020000", + "parentCode": "15000100000000", + "name": "叉车工", + "en_name": "Forklift Operator", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400030000", + "parentCode": "15000100000000", + "name": "铲车司机", + "en_name": "Forklift Driver", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100550000", + "parentCode": "15000100000000", + "name": "挖掘机司机", + "en_name": "Excavator driver", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100070000", + "parentCode": "15000100000000", + "name": "车工", + "en_name": "Lathe Worker", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400040000", + "parentCode": "15000100000000", + "name": "电镀工", + "en_name": "Galvanizer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400050000", + "parentCode": "15000100000000", + "name": "电工", + "en_name": "Electrician", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100110000", + "parentCode": "15000100000000", + "name": "焊工", + "en_name": "Welder", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400060000", + "parentCode": "15000100000000", + "name": "电力线路工", + "en_name": "Wireman", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400090000", + "parentCode": "15000100000000", + "name": "锅炉工", + "en_name": "Boiler Operator", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100040000", + "parentCode": "15000100000000", + "name": "裱胶工", + "en_name": "Lamination Operator", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100220000", + "parentCode": "15000100000000", + "name": "铆工", + "en_name": "Riveter", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100230000", + "parentCode": "15000100000000", + "name": "模具工", + "en_name": "Tool Maker", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100240000", + "parentCode": "15000100000000", + "name": "磨工", + "en_name": "Grinder", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100520000", + "parentCode": "15000100000000", + "name": "注塑工", + "en_name": "Molder", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400110000", + "parentCode": "15000100000000", + "name": "喷塑工", + "en_name": "Spraying working", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100280000", + "parentCode": "15000100000000", + "name": "油漆工", + "en_name": "Painter", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100250000", + "parentCode": "15000100000000", + "name": "配色", + "en_name": "Color Matching", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100380000", + "parentCode": "15000100000000", + "name": "调色/配色技术员", + "en_name": "Color mixing/matching technician", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400170000", + "parentCode": "15000100000000", + "name": "拖压工", + "en_name": "Compression Worker", + "deleted": false, + "sublist": [] + }, + { + "code": "7000400190000", + "parentCode": "15000100000000", + "name": "万能工", + "en_name": "Fitting-up Worker", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100390000", + "parentCode": "15000100000000", + "name": "铣工", + "en_name": "Miller", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100440000", + "parentCode": "15000100000000", + "name": "仪表工", + "en_name": "Instrument Technician", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100490000", + "parentCode": "15000100000000", + "name": "折弯工", + "en_name": "Bender", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100540000", + "parentCode": "15000100000000", + "name": "组装工", + "en_name": "Assembling Worker", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100020000", + "parentCode": "15000100000000", + "name": "钻工", + "en_name": "Driller", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100160000", + "parentCode": "15000100000000", + "name": "浮法操作工(玻璃技术)", + "en_name": "Float Glass Process Operator", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100560000", + "parentCode": "15000100000000", + "name": "切割工", + "en_name": "Cutter", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100420000", + "parentCode": "15000100000000", + "name": "氩弧焊工", + "en_name": "Argon Arc Welder", + "deleted": false, + "sublist": [] + }, + { + "code": "15000100580000", + "parentCode": "15000100000000", + "name": "钣金工", + "en_name": "Sheet Metal Worker", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "21000000000000", + "parentCode": null, + "name": "农业/能源/环保", + "en_name": "Agriculture, energy or environmental protection", + "deleted": false, + "sublist": [ + { + "code": "21000100000000", + "parentCode": "21000000000000", + "name": "农/林/牧/渔", + "en_name": "Agriculture, forestry, animal husbandry or fishing", + "deleted": false, + "sublist": [ + { + "code": "17000100020000", + "parentCode": "21000100000000", + "name": "花艺师", + "en_name": "Florist", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300170000", + "parentCode": "21000100000000", + "name": "园艺师", + "en_name": "Horticulturist", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300100000", + "parentCode": "21000100000000", + "name": "农艺师", + "en_name": "Agronomist", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300090000", + "parentCode": "21000100000000", + "name": "林业技术人员", + "en_name": "Forestry Technician", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300020000", + "parentCode": "21000100000000", + "name": "畜牧师", + "en_name": "Livestock Professional", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300030000", + "parentCode": "21000100000000", + "name": "动物营养/饲料研发", + "en_name": "Feed Development", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300040000", + "parentCode": "21000100000000", + "name": "动物育种/养殖", + "en_name": "Animal Breeding", + "deleted": false, + "sublist": [] + }, + { + "code": "10000400070000", + "parentCode": "21000100000000", + "name": "作物育种", + "en_name": "Crop Breeding", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200160000", + "parentCode": "21000100000000", + "name": "兽医", + "en_name": "Veterinarian", + "deleted": false, + "sublist": [] + }, + { + "code": "21000000010000", + "parentCode": "21000100000000", + "name": "场长(农/林/牧/渔)", + "en_name": "Field leader (Agriculture or forestry or animal husbandry or fishery)", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "21000200000000", + "parentCode": "21000000000000", + "name": "环境科学/环保", + "en_name": "Environmental science or environmental protection", + "deleted": false, + "sublist": [ + { + "code": "7000300160000", + "parentCode": "21000200000000", + "name": "环保工程师", + "en_name": "Environmental Protection Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300170000", + "parentCode": "21000200000000", + "name": "环保技术工程师", + "en_name": "Engineer of Environmental Protection", + "deleted": false, + "sublist": [] + }, + { + "code": "7000100070000", + "parentCode": "21000200000000", + "name": "环境/健康/安全工程师(EHS)", + "en_name": "environmental or health or safety engineer (ehs)", + "deleted": false, + "sublist": [] + }, + { + "code": "7000100080000", + "parentCode": "21000200000000", + "name": "环境/健康/安全经理(EHS)", + "en_name": "environmental or health or safety manager or supervisor (ehs)", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300180000", + "parentCode": "21000200000000", + "name": "环境管理/园林景区保护", + "en_name": "Environmental Management", + "deleted": false, + "sublist": [] + }, + { + "code": "7000100090000", + "parentCode": "21000200000000", + "name": "环境监测工程师", + "en_name": "Engineer of Environmental Monitoring", + "deleted": false, + "sublist": [] + }, + { + "code": "7000100100000", + "parentCode": "21000200000000", + "name": "环境评价工程师", + "en_name": "Engineer of Environmental Appraisal", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300190000", + "parentCode": "21000200000000", + "name": "环评技术员", + "en_name": "Environment Assessment Technician", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300360000", + "parentCode": "21000200000000", + "name": "生态治理/规划", + "en_name": "Ecological Governance", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300390000", + "parentCode": "21000200000000", + "name": "水处理工程师", + "en_name": "Water Treatment Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300080000", + "parentCode": "21000200000000", + "name": "废气处理工程师", + "en_name": "Engineer of Waste Gas Treatment", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300120000", + "parentCode": "21000200000000", + "name": "固废处理工程师", + "en_name": "Engineer of Solid Waste", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "21000300000000", + "parentCode": "21000000000000", + "name": "能源/矿产/地质", + "en_name": "Energy , minerals or seology", + "deleted": false, + "sublist": [ + { + "code": "7000300370000", + "parentCode": "21000300000000", + "name": "石油工程师", + "en_name": "Petroleum Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300440000", + "parentCode": "21000300000000", + "name": "天然气技术人员", + "en_name": "Gas Technician", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300150000", + "parentCode": "21000300000000", + "name": "核力/火力工程师", + "en_name": "Nuclear/Fire-power Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "21000300010000", + "parentCode": "21000300000000", + "name": "节能/能源工程师", + "en_name": "Energy saving or energy engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "21000300030000", + "parentCode": "21000300000000", + "name": "风电工程师", + "en_name": "Wind power engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "7000200160000", + "parentCode": "21000300000000", + "name": "能源/矿产项目", + "en_name": "Energy/Mineral Exploration Project", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300480000", + "parentCode": "21000300000000", + "name": "选矿/采矿", + "en_name": "Mineral Processing/Mining", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300040000", + "parentCode": "21000300000000", + "name": "地质工程师", + "en_name": "Geological Engineering", + "deleted": false, + "sublist": [] + }, + { + "code": "7000300270000", + "parentCode": "21000300000000", + "name": "勘探工程师", + "en_name": "Geological Survey Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "21000300020000", + "parentCode": "21000300000000", + "name": "考古技术员", + "en_name": "archaeologist", + "deleted": false, + "sublist": [] + }, + { + "code": "21000300040000", + "parentCode": "21000300000000", + "name": "冶金工程师", + "en_name": "Metallurgical Engineer", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "18000000000000", + "parentCode": null, + "name": "医疗/医美/医务", + "en_name": "Medical Service", + "deleted": false, + "sublist": [ + { + "code": "18000500000000", + "parentCode": "18000000000000", + "name": "生物/医药研发", + "en_name": "Biological or pharmaceutical R & D", + "deleted": false, + "sublist": [ + { + "code": "10000100170000", + "parentCode": "18000500000000", + "name": "试剂研发", + "en_name": "Reagent R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100260000", + "parentCode": "18000500000000", + "name": "制剂研发", + "en_name": "Preparation R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100180000", + "parentCode": "18000500000000", + "name": "药品研发", + "en_name": "Pharmaceutical R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500060000", + "parentCode": "18000500000000", + "name": "药物合成", + "en_name": "Drug synthesis", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500070000", + "parentCode": "18000500000000", + "name": "药理研究", + "en_name": "Pharmacological research", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500150000", + "parentCode": "18000500000000", + "name": "计算机辅助药物设计", + "en_name": "Computer Aided Drug Design", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100210000", + "parentCode": "18000500000000", + "name": "医药技术研发", + "en_name": "Pharmaceutical Technology R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500160000", + "parentCode": "18000500000000", + "name": "中药研发", + "en_name": "Research And Development Of Traditional Chinese Medicine", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100230000", + "parentCode": "18000500000000", + "name": "医药研发管理", + "en_name": "Pharmaceutical Technology R&D management", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100200000", + "parentCode": "18000500000000", + "name": "医药化学分析", + "en_name": "Pharmaceutical & Chemical Analysis", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500140000", + "parentCode": "18000500000000", + "name": "药物质量研究", + "en_name": "Drug Quality Research", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400130000", + "parentCode": "18000500000000", + "name": "药品生产/质量管理", + "en_name": "Pharmaceutical Production/QC", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100270000", + "parentCode": "18000500000000", + "name": "制药工程师", + "en_name": "Pharmaceutical Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500080000", + "parentCode": "18000500000000", + "name": "细胞培养技术员", + "en_name": "Cell culture technician", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500090000", + "parentCode": "18000500000000", + "name": "动物实验技术员", + "en_name": "Animal experiment technician", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100140000", + "parentCode": "18000500000000", + "name": "生物信息工程师", + "en_name": "Bioinformation Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100130000", + "parentCode": "18000500000000", + "name": "生物工程/生物制药", + "en_name": "Bioengineering/Biopharmacy", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500030000", + "parentCode": "18000500000000", + "name": "生物技术员", + "en_name": "Biotechnologist", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500100000", + "parentCode": "18000500000000", + "name": "医学检验", + "en_name": "Medical examination", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500120000", + "parentCode": "18000500000000", + "name": "医药产品经理", + "en_name": "Pharmaceutical Product Manager", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "18000700000000", + "parentCode": "18000000000000", + "name": "临床研究/试验", + "en_name": "Clinical Research or Trials", + "deleted": false, + "sublist": [ + { + "code": "10000100120000", + "parentCode": "18000700000000", + "name": "临床研究", + "en_name": "Clinical Research", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100110000", + "parentCode": "18000700000000", + "name": "临床协调", + "en_name": "Clinical Trial Coordination", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500020000", + "parentCode": "18000700000000", + "name": "临床监查员", + "en_name": "Clinical monitor", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100100000", + "parentCode": "18000700000000", + "name": "临床数据分析", + "en_name": "Clinical Data Analysis", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500050000", + "parentCode": "18000700000000", + "name": "药物警戒", + "en_name": "Pharmacovigilance", + "deleted": false, + "sublist": [] + }, + { + "code": "6001400050000", + "parentCode": "18000700000000", + "name": "药品注册", + "en_name": "Drug Registration", + "deleted": false, + "sublist": [] + }, + { + "code": "18000700010000", + "parentCode": "18000700000000", + "name": "医学经理", + "en_name": "Medical Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500130000", + "parentCode": "18000700000000", + "name": "医学总监", + "en_name": "Medical Director", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "18000800000000", + "parentCode": "18000000000000", + "name": "医药市场/销售", + "en_name": "Pharmaceutical Marketing or Sales", + "deleted": false, + "sublist": [ + { + "code": "19000200120000", + "parentCode": "18000800000000", + "name": "医药代表", + "en_name": "Medical representatives", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500110000", + "parentCode": "18000800000000", + "name": "医药招商", + "en_name": "Pharmaceutical investment", + "deleted": false, + "sublist": [] + }, + { + "code": "2000100120000", + "parentCode": "18000800000000", + "name": "医药项目招投标", + "en_name": "Medical Project Bidding", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500320000", + "parentCode": "18000800000000", + "name": "学术推广", + "en_name": "Academic Marketing", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100190000", + "parentCode": "18000800000000", + "name": "医学信息专员", + "en_name": "Medical Information Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "10000100220000", + "parentCode": "18000800000000", + "name": "医药信息沟通专员", + "en_name": "Medical Information Communication Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500010000", + "parentCode": "18000800000000", + "name": "医学联络员(MSL)", + "en_name": "Medical liaison (msl)", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500330000", + "parentCode": "18000800000000", + "name": "药品市场推广经理/主管", + "en_name": "Pharmaceutical Marketing Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500340000", + "parentCode": "18000800000000", + "name": "药品市场推广专员/助理", + "en_name": "Pharmaceutical Marketing Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "18000500040000", + "parentCode": "18000800000000", + "name": "医学编辑", + "en_name": "Medical editors", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "18000400000000", + "parentCode": "18000000000000", + "name": "医疗器械", + "en_name": "Medical apparatus and instruments", + "deleted": false, + "sublist": [ + { + "code": "19000200110000", + "parentCode": "18000400000000", + "name": "医疗器械销售", + "en_name": "Medical Instrument Sales", + "deleted": false, + "sublist": [] + }, + { + "code": "10000200150000", + "parentCode": "18000400000000", + "name": "医疗器械研发", + "en_name": "Medical Instrument R&D", + "deleted": false, + "sublist": [] + }, + { + "code": "6001400060000", + "parentCode": "18000400000000", + "name": "医疗器械注册", + "en_name": "Medical Appliance Registration", + "deleted": false, + "sublist": [] + }, + { + "code": "13000200110000", + "parentCode": "18000400000000", + "name": "医疗器械售后工程师", + "en_name": "Medical Appliance Aftersales Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "20000200310000", + "parentCode": "18000400000000", + "name": "医疗器械维修/保养", + "en_name": "Medical Instrument Repair/Maintenance", + "deleted": false, + "sublist": [] + }, + { + "code": "15000400140000", + "parentCode": "18000400000000", + "name": "医疗器械生产/质量管理", + "en_name": "Medical Instrument Production/QC", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "18000200000000", + "parentCode": "18000000000000", + "name": "医生/药剂师", + "en_name": "Doctor/Pharmacist", + "deleted": false, + "sublist": [ + { + "code": "18000200140000", + "parentCode": "18000200000000", + "name": "内科医生", + "en_name": "Physician", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200170000", + "parentCode": "18000200000000", + "name": "外科医生", + "en_name": "Surgeon", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200300000", + "parentCode": "18000200000000", + "name": "妇产科医生", + "en_name": "Obstetrician and gynecologist", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200040000", + "parentCode": "18000200000000", + "name": "儿科医生", + "en_name": "Pediatrician", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200200000", + "parentCode": "18000200000000", + "name": "口腔科医生", + "en_name": "Stomatologist", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200210000", + "parentCode": "18000200000000", + "name": "眼科医生", + "en_name": "Ophthalmologist", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200320000", + "parentCode": "18000200000000", + "name": "幼儿园保健医", + "en_name": "Kindergarten Health Care Doctor", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200080000", + "parentCode": "18000200000000", + "name": "急诊医生", + "en_name": "Emergency Physician", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200110000", + "parentCode": "18000200000000", + "name": "临床医生", + "en_name": "Clinician", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200280000", + "parentCode": "18000200000000", + "name": "中医", + "en_name": "Chinese Medicine", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200130000", + "parentCode": "18000200000000", + "name": "整形师", + "en_name": "Plastic Surgeon", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200330000", + "parentCode": "18000200000000", + "name": "皮肤科医生", + "en_name": "Dermatologist", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200190000", + "parentCode": "18000200000000", + "name": "心理医生", + "en_name": "Psychologist", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200310000", + "parentCode": "18000200000000", + "name": "医生助理", + "en_name": "Doctor Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200240000", + "parentCode": "18000200000000", + "name": "医学影像医师", + "en_name": "Medical imaging physician", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200050000", + "parentCode": "18000200000000", + "name": "放射科医师", + "en_name": "Radiologist", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200010000", + "parentCode": "18000200000000", + "name": "超声科医生", + "en_name": "Ultrasonography Doctor", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200020000", + "parentCode": "18000200000000", + "name": "ICU医生", + "en_name": "ICU Doctor", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200180000", + "parentCode": "18000200000000", + "name": "心电图医生", + "en_name": "ECG Doctor", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200070000", + "parentCode": "18000200000000", + "name": "核磁共振医生", + "en_name": "MRI Doctor", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200090000", + "parentCode": "18000200000000", + "name": "检验科医师", + "en_name": "Lab Physician", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200120000", + "parentCode": "18000200000000", + "name": "麻醉医生", + "en_name": "Anesthetist", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200150000", + "parentCode": "18000200000000", + "name": "全科医生", + "en_name": "General Practitioner", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200290000", + "parentCode": "18000200000000", + "name": "专科医生", + "en_name": "Specialist Physician", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200030000", + "parentCode": "18000200000000", + "name": "病理医师", + "en_name": "Pathologist", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200060000", + "parentCode": "18000200000000", + "name": "辅诊医生", + "en_name": "Auxiliary Medical Diagnosis", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200100000", + "parentCode": "18000200000000", + "name": "接诊医生", + "en_name": "Doctor", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200220000", + "parentCode": "18000200000000", + "name": "验光师", + "en_name": "Oculist", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200270000", + "parentCode": "18000200000000", + "name": "中药师", + "en_name": "Traditional Chinese Pharmacist", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200230000", + "parentCode": "18000200000000", + "name": "药剂师", + "en_name": "Pharmacist", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "18000100000000", + "parentCode": "18000000000000", + "name": "护士/医助", + "en_name": "Nurse/Doctor Assistant", + "deleted": false, + "sublist": [ + { + "code": "18000100050000", + "parentCode": "18000100000000", + "name": "护士", + "en_name": "Nurse", + "deleted": false, + "sublist": [] + }, + { + "code": "18000100060000", + "parentCode": "18000100000000", + "name": "护士长", + "en_name": "Head Nurse", + "deleted": false, + "sublist": [] + }, + { + "code": "18000100010000", + "parentCode": "18000100000000", + "name": "导医", + "en_name": "Medical Guiding Services", + "deleted": false, + "sublist": [] + }, + { + "code": "18000100030000", + "parentCode": "18000100000000", + "name": "护工", + "en_name": "Nursing Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "18000100070000", + "parentCode": "18000100000000", + "name": "核酸检测员", + "en_name": "nucleic acid detector", + "deleted": false, + "sublist": [] + }, + { + "code": "18000100080000", + "parentCode": "18000100000000", + "name": "患教", + "en_name": "patient education", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "18000300000000", + "parentCode": "18000000000000", + "name": "医务管理", + "en_name": "Medical Management", + "deleted": false, + "sublist": [ + { + "code": "18000300020000", + "parentCode": "18000300000000", + "name": "医务干事", + "en_name": "Medical Staff", + "deleted": false, + "sublist": [] + }, + { + "code": "18000300010000", + "parentCode": "18000300000000", + "name": "医疗管理", + "en_name": "Medical Management", + "deleted": false, + "sublist": [] + }, + { + "code": "18000300030000", + "parentCode": "18000300000000", + "name": "医务管理", + "en_name": "Health Administration", + "deleted": false, + "sublist": [] + }, + { + "code": "18000300040000", + "parentCode": "18000300000000", + "name": "医院院长/副院长", + "en_name": "Hospital Director/Vice", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "18000600000000", + "parentCode": "18000000000000", + "name": "药店", + "en_name": "Pharmacy", + "deleted": false, + "sublist": [ + { + "code": "18000600020000", + "parentCode": "18000600000000", + "name": "药店店长", + "en_name": "Drugstore Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "18000600030000", + "parentCode": "18000600000000", + "name": "药店店员", + "en_name": "Drugstore clerk", + "deleted": false, + "sublist": [] + }, + { + "code": "18000600010000", + "parentCode": "18000600000000", + "name": "执业药师/驻店药师", + "en_name": "Licensed pharmacist or resident pharmacist", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "11000000000000", + "parentCode": null, + "name": "教育/培训/科研", + "en_name": "Education, training or scientific research", + "deleted": false, + "sublist": [ + { + "code": "11000300000000", + "parentCode": "11000000000000", + "name": "教务管理", + "en_name": "Educational Administration", + "deleted": false, + "sublist": [ + { + "code": "11000300160000", + "parentCode": "11000300000000", + "name": "班主任/辅导员", + "en_name": "Headteacher or Counselor", + "deleted": false, + "sublist": [] + }, + { + "code": "11000300120000", + "parentCode": "11000300000000", + "name": "学习管理师", + "en_name": "Learning Tutor", + "deleted": false, + "sublist": [] + }, + { + "code": "11000300040000", + "parentCode": "11000300000000", + "name": "教务管理", + "en_name": "Academic Administration", + "deleted": false, + "sublist": [] + }, + { + "code": "11000300100000", + "parentCode": "11000300000000", + "name": "校区主任", + "en_name": "Campus Head", + "deleted": false, + "sublist": [] + }, + { + "code": "11000300110000", + "parentCode": "11000300000000", + "name": "校长/副校长", + "en_name": "Headmaster/Vice", + "deleted": false, + "sublist": [] + }, + { + "code": "11000300130000", + "parentCode": "11000300000000", + "name": "园长/副园长", + "en_name": "Principal or deputy director", + "deleted": false, + "sublist": [] + }, + { + "code": "11000300140000", + "parentCode": "11000300000000", + "name": "课程编辑", + "en_name": "Course editor", + "deleted": false, + "sublist": [] + }, + { + "code": "19000200170000", + "parentCode": "11000300000000", + "name": "课程顾问", + "en_name": "Course consultant", + "deleted": false, + "sublist": [] + }, + { + "code": "11000300060000", + "parentCode": "11000300000000", + "name": "课程设计", + "en_name": "Curriculum Design", + "deleted": false, + "sublist": [] + }, + { + "code": "11000300050000", + "parentCode": "11000300000000", + "name": "教育产品研发", + "en_name": "Research And Development Of Educational Products", + "deleted": false, + "sublist": [] + }, + { + "code": "11000300070000", + "parentCode": "11000300000000", + "name": "培训策划", + "en_name": "Training Planning", + "deleted": false, + "sublist": [] + }, + { + "code": "11000300080000", + "parentCode": "11000300000000", + "name": "培训督导", + "en_name": "Training Supervision", + "deleted": false, + "sublist": [] + }, + { + "code": "14000700070000", + "parentCode": "11000300000000", + "name": "助教", + "en_name": "Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "11000300150000", + "parentCode": "11000300000000", + "name": "就业老师", + "en_name": "Employment teacher", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "11000800000000", + "parentCode": "11000000000000", + "name": "早教/幼教", + "en_name": "Pre-school Education", + "deleted": false, + "sublist": [ + { + "code": "11000800080000", + "parentCode": "11000800000000", + "name": "幼教", + "en_name": "Preschool Education", + "deleted": false, + "sublist": [] + }, + { + "code": "11000800020000", + "parentCode": "11000800000000", + "name": "儿童教育老师", + "en_name": "Teacher of Child Education", + "deleted": false, + "sublist": [] + }, + { + "code": "11000800040000", + "parentCode": "11000800000000", + "name": "亲子老师", + "en_name": "Parent-child Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000800030000", + "parentCode": "11000800000000", + "name": "绘本老师", + "en_name": "Picture Book Instructor", + "deleted": false, + "sublist": [] + }, + { + "code": "11000800010000", + "parentCode": "11000800000000", + "name": "保育员", + "en_name": "Nurseryperson", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "11001000000000", + "parentCode": "11000000000000", + "name": "中小学课程辅导", + "en_name": "After-school Tutoring", + "deleted": false, + "sublist": [ + { + "code": "11001000050000", + "parentCode": "11001000000000", + "name": "理科教师", + "en_name": "Science Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11001000060000", + "parentCode": "11001000000000", + "name": "文科教师", + "en_name": "Liberal Arts Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11001000070000", + "parentCode": "11001000000000", + "name": "小学教师", + "en_name": "Primary School Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11001000010000", + "parentCode": "11001000000000", + "name": "初中教师", + "en_name": "Middle School Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11001000020000", + "parentCode": "11001000000000", + "name": "高中教师", + "en_name": "High School Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11001000090000", + "parentCode": "11001000000000", + "name": "数学教师", + "en_name": "Mathematics Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11001000100000", + "parentCode": "11001000000000", + "name": "语文教师", + "en_name": "Chinese Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11001000110000", + "parentCode": "11001000000000", + "name": "物理教师", + "en_name": "Physics Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11001000120000", + "parentCode": "11001000000000", + "name": "化学教师", + "en_name": "Chemistry Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11001000130000", + "parentCode": "11001000000000", + "name": "生物教师", + "en_name": "Biology Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11001000080000", + "parentCode": "11001000000000", + "name": "托管老师", + "en_name": "trusteeship teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11001000030000", + "parentCode": "11001000000000", + "name": "家教", + "en_name": "Home Tutor", + "deleted": false, + "sublist": [] + }, + { + "code": "11001000040000", + "parentCode": "11001000000000", + "name": "兼职教师", + "en_name": "Part-time Teacher", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "11001100000000", + "parentCode": "11000000000000", + "name": "高等教育", + "en_name": "Higher education", + "deleted": false, + "sublist": [ + { + "code": "10000400020000", + "parentCode": "11001100000000", + "name": "大学教师", + "en_name": "Faculty", + "deleted": false, + "sublist": [] + }, + { + "code": "10000400030000", + "parentCode": "11001100000000", + "name": "大学教授", + "en_name": "Professor", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "11000100000000", + "parentCode": "11000000000000", + "name": "IT培训", + "en_name": "IT Training", + "deleted": false, + "sublist": [ + { + "code": "11000100060000", + "parentCode": "11000100000000", + "name": "JAVA培训讲师", + "en_name": "JAVA Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000100070000", + "parentCode": "11000100000000", + "name": "PHP培训讲师", + "en_name": "PHP Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000100030000", + "parentCode": "11000100000000", + "name": "C++培训讲师", + "en_name": "C++ Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000100100000", + "parentCode": "11000100000000", + "name": "web前端培训讲师", + "en_name": "Web Front-end Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000100020000", + "parentCode": "11000100000000", + "name": "Android培训讲师", + "en_name": "Android Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000100040000", + "parentCode": "11000100000000", + "name": "iOS培训讲师", + "en_name": "iOS Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000100010000", + "parentCode": "11000100000000", + "name": ".NET培训讲师", + "en_name": ".NET Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000100080000", + "parentCode": "11000100000000", + "name": "UI设计培训讲师", + "en_name": "UI Design Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000100090000", + "parentCode": "11000100000000", + "name": "Unity3D培训讲师", + "en_name": "Unity3D Training Instructor", + "deleted": false, + "sublist": [] + }, + { + "code": "11000100050000", + "parentCode": "11000100000000", + "name": "IT培训", + "en_name": "IT Training", + "deleted": false, + "sublist": [] + }, + { + "code": "11000100110000", + "parentCode": "11000100000000", + "name": "编程教师", + "en_name": "Programming Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000100120000", + "parentCode": "11000100000000", + "name": "大数据讲师", + "en_name": "Big Data Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000100130000", + "parentCode": "11000100000000", + "name": "机器学习讲师", + "en_name": "Machine Learning Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000100140000", + "parentCode": "11000100000000", + "name": "人工智能讲师", + "en_name": "AI Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000100160000", + "parentCode": "11000100000000", + "name": "云计算讲师", + "en_name": "Cloud Computing Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000100150000", + "parentCode": "11000100000000", + "name": "软件测试培训讲师", + "en_name": "Software Testing Training Lecturer", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "11000900000000", + "parentCode": "11000000000000", + "name": "职业培训", + "en_name": "Professional Training", + "deleted": false, + "sublist": [ + { + "code": "11000900010000", + "parentCode": "11000900000000", + "name": "HR培训讲师", + "en_name": "HR Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000900020000", + "parentCode": "11000900000000", + "name": "财会培训讲师", + "en_name": "Accounting Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000900040000", + "parentCode": "11000900000000", + "name": "法律培训讲师", + "en_name": "Legal Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000900080000", + "parentCode": "11000900000000", + "name": "职业技术教师", + "en_name": "Professional Training Instructor", + "deleted": false, + "sublist": [] + }, + { + "code": "11000900090000", + "parentCode": "11000900000000", + "name": "专升本讲师", + "en_name": "Training Teacher for Bachelor's Degree", + "deleted": false, + "sublist": [] + }, + { + "code": "11000900030000", + "parentCode": "11000900000000", + "name": "动漫培训讲师", + "en_name": "Animation Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000900070000", + "parentCode": "11000900000000", + "name": "培训师", + "en_name": "Trainer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000900050000", + "parentCode": "11000900000000", + "name": "美发培训师", + "en_name": "Hair Styling Training Instructor", + "deleted": false, + "sublist": [] + }, + { + "code": "11000900060000", + "parentCode": "11000900000000", + "name": "美容导师", + "en_name": "Beauty Training Instructor", + "deleted": false, + "sublist": [] + }, + { + "code": "11000900100000", + "parentCode": "11000900000000", + "name": "播音主持教师", + "en_name": "Broadcasting Host Teacher", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "11000700000000", + "parentCode": "11000000000000", + "name": "语言培训", + "en_name": "Language Training", + "deleted": false, + "sublist": [ + { + "code": "11000700080000", + "parentCode": "11000700000000", + "name": "英语教师", + "en_name": "English Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000700050000", + "parentCode": "11000700000000", + "name": "少儿英语教师", + "en_name": "English Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000700090000", + "parentCode": "11000700000000", + "name": "中文教师", + "en_name": "Chinese Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000700030000", + "parentCode": "11000700000000", + "name": "对外汉语教师", + "en_name": "Chinese Language Teacher (for Foreigners)", + "deleted": false, + "sublist": [] + }, + { + "code": "11000700020000", + "parentCode": "11000700000000", + "name": "韩语教师", + "en_name": "Korean Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000700040000", + "parentCode": "11000700000000", + "name": "日语教师", + "en_name": "Japanese Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000700010000", + "parentCode": "11000700000000", + "name": "德语教师", + "en_name": "German Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000700070000", + "parentCode": "11000700000000", + "name": "外语教师", + "en_name": "Teacher of Foreign Languages", + "deleted": false, + "sublist": [] + }, + { + "code": "11000700060000", + "parentCode": "11000700000000", + "name": "外籍教师", + "en_name": "Foreign Teacher", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "11000200000000", + "parentCode": "11000000000000", + "name": "才艺特长培训", + "en_name": "Talent Training", + "deleted": false, + "sublist": [ + { + "code": "11000200040000", + "parentCode": "11000200000000", + "name": "机器人教师", + "en_name": "Robot Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200170000", + "parentCode": "11000200000000", + "name": "乐高教师", + "en_name": "Lego Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200010000", + "parentCode": "11000200000000", + "name": "表演教师", + "en_name": "Acting Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200140000", + "parentCode": "11000200000000", + "name": "舞蹈老师", + "en_name": "Dance Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200070000", + "parentCode": "11000200000000", + "name": "乐器教师", + "en_name": "Musical Instrument Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200200000", + "parentCode": "11000200000000", + "name": "吉他教师", + "en_name": "Guitar Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200210000", + "parentCode": "11000200000000", + "name": "古筝教师", + "en_name": "Guzheng Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200100000", + "parentCode": "11000200000000", + "name": "音乐教师", + "en_name": "Music Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200180000", + "parentCode": "11000200000000", + "name": "钢琴教师", + "en_name": "Piano Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200190000", + "parentCode": "11000200000000", + "name": "围棋教师", + "en_name": "Go Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200080000", + "parentCode": "11000200000000", + "name": "美术教师", + "en_name": "Art Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200110000", + "parentCode": "11000200000000", + "name": "书法教师", + "en_name": "Writing Master", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200130000", + "parentCode": "11000200000000", + "name": "体育教师", + "en_name": "Pe Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300150000", + "parentCode": "11000200000000", + "name": "拓展培训", + "en_name": "Outward-bound", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200050000", + "parentCode": "11000200000000", + "name": "驾校教练", + "en_name": "Driving School Coach", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "11000400000000", + "parentCode": "11000000000000", + "name": "考研辅导", + "en_name": "NEEP Training", + "deleted": false, + "sublist": [ + { + "code": "11000400010000", + "parentCode": "11000400000000", + "name": "考研培训讲师", + "en_name": "NEEP Training Lecturer", + "deleted": false, + "sublist": [] + }, + { + "code": "11000400020000", + "parentCode": "11000400000000", + "name": "考研英语教师", + "en_name": "Postgraduate Entrance Examination English Teacher", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "11000500000000", + "parentCode": "11000000000000", + "name": "留学辅导", + "en_name": "Training for TOEFL/IELTS/SAT/etc", + "deleted": false, + "sublist": [ + { + "code": "11000500030000", + "parentCode": "11000500000000", + "name": "托福教师", + "en_name": "TOEFL Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000500040000", + "parentCode": "11000500000000", + "name": "雅思英语教师", + "en_name": "Ielts English Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000500010000", + "parentCode": "11000500000000", + "name": "SAT教师", + "en_name": "SAT Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000500050000", + "parentCode": "11000500000000", + "name": "留学顾问", + "en_name": "Study abroad consultant", + "deleted": false, + "sublist": [] + }, + { + "code": "11000500020000", + "parentCode": "11000500000000", + "name": "出国留学考试培训讲师", + "en_name": "Teacher of TOEFL/IELTS/SAT/etc.", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "11000600000000", + "parentCode": "11000000000000", + "name": "特殊教育", + "en_name": "Special Education", + "deleted": false, + "sublist": [ + { + "code": "11000600010000", + "parentCode": "11000600000000", + "name": "感统教师", + "en_name": "Sensory Integration Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000600020000", + "parentCode": "11000600000000", + "name": "康复训练教师", + "en_name": "Rehabilitation Training Teacher", + "deleted": false, + "sublist": [] + }, + { + "code": "11000600030000", + "parentCode": "11000600000000", + "name": "特教教师", + "en_name": "Special Education Teacher", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "11001200000000", + "parentCode": "11000000000000", + "name": "科研/学术研究", + "en_name": "Scientific research or academic research", + "deleted": false, + "sublist": [ + { + "code": "10000400040000", + "parentCode": "11001200000000", + "name": "科研人员", + "en_name": "Research Staff", + "deleted": false, + "sublist": [] + }, + { + "code": "10000300020000", + "parentCode": "11001200000000", + "name": "科研管理人员", + "en_name": "R&D Management", + "deleted": false, + "sublist": [] + }, + { + "code": "10000400050000", + "parentCode": "11001200000000", + "name": "实验室技术员", + "en_name": "Laboratory Technician", + "deleted": false, + "sublist": [] + }, + { + "code": "10000400060000", + "parentCode": "11001200000000", + "name": "研究助理", + "en_name": "Research Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "10000300030000", + "parentCode": "11001200000000", + "name": "实验室负责人", + "en_name": "Laboratory director", + "deleted": false, + "sublist": [] + }, + { + "code": "10000400010000", + "parentCode": "11001200000000", + "name": "安全研究", + "en_name": "Security Research", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "1000000000000", + "parentCode": null, + "name": "编辑/记者/翻译", + "en_name": "Editor/Journalist/Translator", + "deleted": false, + "sublist": [ + { + "code": "1000100000000", + "parentCode": "1000000000000", + "name": "编辑/编校/作家", + "en_name": "Editor/Proofreader/Writer", + "deleted": false, + "sublist": [ + { + "code": "1000100010000", + "parentCode": "1000100000000", + "name": "编辑", + "en_name": "Edit", + "deleted": false, + "sublist": [] + }, + { + "code": "1000100080000", + "parentCode": "1000100000000", + "name": "文案编辑", + "en_name": "Copywriter", + "deleted": false, + "sublist": [] + }, + { + "code": "1000100030000", + "parentCode": "1000100000000", + "name": "美编", + "en_name": "Art Editor", + "deleted": false, + "sublist": [] + }, + { + "code": "1000100070000", + "parentCode": "1000100000000", + "name": "撰稿人", + "en_name": "Writer", + "deleted": false, + "sublist": [] + }, + { + "code": "1000100020000", + "parentCode": "1000100000000", + "name": "出版发行", + "en_name": "Publication And Distribution", + "deleted": false, + "sublist": [] + }, + { + "code": "1000100040000", + "parentCode": "1000100000000", + "name": "排版设计", + "en_name": "Typographic design", + "deleted": false, + "sublist": [] + }, + { + "code": "1000100050000", + "parentCode": "1000100000000", + "name": "校对录入", + "en_name": "Proofreading And Entry", + "deleted": false, + "sublist": [] + }, + { + "code": "1000100060000", + "parentCode": "1000100000000", + "name": "主编/副主编", + "en_name": "Managing Editor/Vice", + "deleted": false, + "sublist": [] + }, + { + "code": "3000200230000", + "parentCode": "1000100000000", + "name": "总编/副总编", + "en_name": "Editor in Chief/Vice", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1000300000000", + "parentCode": "1000000000000", + "name": "记者/采编", + "en_name": "Journalist", + "deleted": false, + "sublist": [ + { + "code": "1000300010000", + "parentCode": "1000300000000", + "name": "采编", + "en_name": "Reporting Staff", + "deleted": false, + "sublist": [] + }, + { + "code": "1000300020000", + "parentCode": "1000300000000", + "name": "记者", + "en_name": "Journalist", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1000200000000", + "parentCode": "1000000000000", + "name": "翻译", + "en_name": "Translator/Interpreter", + "deleted": false, + "sublist": [ + { + "code": "1000200120000", + "parentCode": "1000200000000", + "name": "英语翻译", + "en_name": "English Translator", + "deleted": false, + "sublist": [] + }, + { + "code": "1000200080000", + "parentCode": "1000200000000", + "name": "日语翻译", + "en_name": "Japanese Translator", + "deleted": false, + "sublist": [] + }, + { + "code": "1000200040000", + "parentCode": "1000200000000", + "name": "法语翻译", + "en_name": "French Translator", + "deleted": false, + "sublist": [] + }, + { + "code": "1000200060000", + "parentCode": "1000200000000", + "name": "韩语/朝鲜语翻译", + "en_name": "Korean Translator", + "deleted": false, + "sublist": [] + }, + { + "code": "1000200030000", + "parentCode": "1000200000000", + "name": "俄语翻译", + "en_name": "Russian Translator", + "deleted": false, + "sublist": [] + }, + { + "code": "1000200020000", + "parentCode": "1000200000000", + "name": "德语翻译", + "en_name": "German Translator", + "deleted": false, + "sublist": [] + }, + { + "code": "1000200070000", + "parentCode": "1000200000000", + "name": "葡萄牙语翻译", + "en_name": "Portuguese Translator", + "deleted": false, + "sublist": [] + }, + { + "code": "1000200090000", + "parentCode": "1000200000000", + "name": "西班牙语翻译", + "en_name": "Spanish Translator", + "deleted": false, + "sublist": [] + }, + { + "code": "1000200110000", + "parentCode": "1000200000000", + "name": "意大利语翻译", + "en_name": "Italian Translator", + "deleted": false, + "sublist": [] + }, + { + "code": "1000200010000", + "parentCode": "1000200000000", + "name": "阿拉伯语翻译", + "en_name": "Arabic Translator", + "deleted": false, + "sublist": [] + }, + { + "code": "1000200100000", + "parentCode": "1000200000000", + "name": "小语种翻译", + "en_name": "Minority Language Translator", + "deleted": false, + "sublist": [] + }, + { + "code": "1000200050000", + "parentCode": "1000200000000", + "name": "高级翻译", + "en_name": "Senior Translator/Interpreter", + "deleted": false, + "sublist": [] + }, + { + "code": "1000200130000", + "parentCode": "1000200000000", + "name": "驻外翻译", + "en_name": "Translator oversea", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "4000000000000", + "parentCode": null, + "name": "直播/影视/传媒", + "en_name": "Live Streaming/Film and Television/Media", + "deleted": false, + "sublist": [ + { + "code": "4000400000000", + "parentCode": "4000000000000", + "name": "广告", + "en_name": "Advertisement", + "deleted": false, + "sublist": [ + { + "code": "16000500040000", + "parentCode": "4000400000000", + "name": "广告创意设计", + "en_name": "Advertising Creative Design", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500030000", + "parentCode": "4000400000000", + "name": "创意总监", + "en_name": "Creative Director", + "deleted": false, + "sublist": [] + }, + { + "code": "16000100010000", + "parentCode": "4000400000000", + "name": "广告创意/设计总监", + "en_name": "Advertising Creative/Creative Director", + "deleted": false, + "sublist": [] + }, + { + "code": "16000100020000", + "parentCode": "4000400000000", + "name": "广告文案", + "en_name": "Advertising Copy", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500050000", + "parentCode": "4000400000000", + "name": "广告美术指导", + "en_name": "Advertising Art Direction", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500060000", + "parentCode": "4000400000000", + "name": "广告审核", + "en_name": "Advertising Moderation", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500070000", + "parentCode": "4000400000000", + "name": "广告协调", + "en_name": "Advertising Coordinator", + "deleted": false, + "sublist": [] + }, + { + "code": "16000500080000", + "parentCode": "4000400000000", + "name": "广告制作", + "en_name": "Advertising Production", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "4000300000000", + "parentCode": "4000000000000", + "name": "影视制作", + "en_name": "Entertainment Production", + "deleted": false, + "sublist": [ + { + "code": "4000300040000", + "parentCode": "4000300000000", + "name": "视频剪辑师", + "en_name": "Editor", + "deleted": false, + "sublist": [] + }, + { + "code": "4000300080000", + "parentCode": "4000300000000", + "name": "视频编辑", + "en_name": "Video Editing", + "deleted": false, + "sublist": [] + }, + { + "code": "4000300100000", + "parentCode": "4000300000000", + "name": "音频编辑", + "en_name": "Audio Editing", + "deleted": false, + "sublist": [] + }, + { + "code": "4000300050000", + "parentCode": "4000300000000", + "name": "录音/音效", + "en_name": "Recording/Audio Production", + "deleted": false, + "sublist": [] + }, + { + "code": "4000300070000", + "parentCode": "4000300000000", + "name": "摄影/摄像", + "en_name": "Photographer/Cinemaman", + "deleted": false, + "sublist": [] + }, + { + "code": "4000300110000", + "parentCode": "4000300000000", + "name": "影视策划", + "en_name": "Entertainment Planning", + "deleted": false, + "sublist": [] + }, + { + "code": "4000300120000", + "parentCode": "4000300000000", + "name": "影视制作", + "en_name": "Entertainment Production", + "deleted": false, + "sublist": [] + }, + { + "code": "4000300150000", + "parentCode": "4000300000000", + "name": "影视发行", + "en_name": "Film And Television Distribution", + "deleted": false, + "sublist": [] + }, + { + "code": "4000300060000", + "parentCode": "4000300000000", + "name": "美术指导", + "en_name": "Art Director", + "deleted": false, + "sublist": [] + }, + { + "code": "4000300010000", + "parentCode": "4000300000000", + "name": "编导", + "en_name": "TV Director", + "deleted": false, + "sublist": [] + }, + { + "code": "4000300020000", + "parentCode": "4000300000000", + "name": "编剧", + "en_name": "Screen Writer", + "deleted": false, + "sublist": [] + }, + { + "code": "4000300030000", + "parentCode": "4000300000000", + "name": "导演", + "en_name": "Director", + "deleted": false, + "sublist": [] + }, + { + "code": "4000300140000", + "parentCode": "4000300000000", + "name": "制片人", + "en_name": "Producer", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "17000600000000", + "parentCode": "4000000000000", + "name": "舞美设计", + "en_name": "Stage Design", + "deleted": false, + "sublist": [ + { + "code": "17000600040000", + "parentCode": "17000600000000", + "name": "舞美设计", + "en_name": "Stage Art Design", + "deleted": false, + "sublist": [] + }, + { + "code": "17000600050000", + "parentCode": "17000600000000", + "name": "舞台艺术指导", + "en_name": "Stage Art Direction", + "deleted": false, + "sublist": [] + }, + { + "code": "17000600010000", + "parentCode": "17000600000000", + "name": "灯光师", + "en_name": "Lighting Engineer", + "deleted": false, + "sublist": [] + }, + { + "code": "17000600030000", + "parentCode": "17000600000000", + "name": "化妆师", + "en_name": "Makeup Artist", + "deleted": false, + "sublist": [] + }, + { + "code": "17000600060000", + "parentCode": "17000600000000", + "name": "造型师", + "en_name": "Stylist", + "deleted": false, + "sublist": [] + }, + { + "code": "17000600020000", + "parentCode": "17000600000000", + "name": "服装道具", + "en_name": "Costumes", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "4000200000000", + "parentCode": "4000000000000", + "name": "主播/演艺人员/经纪人", + "en_name": "Streamer/Artist/Agent", + "deleted": false, + "sublist": [ + { + "code": "4000200050000", + "parentCode": "4000200000000", + "name": "主播", + "en_name": "Anchor", + "deleted": false, + "sublist": [] + }, + { + "code": "4000200070000", + "parentCode": "4000200000000", + "name": "带货主播", + "en_name": "Anchor With Goods", + "deleted": false, + "sublist": [] + }, + { + "code": "4000200100000", + "parentCode": "4000200000000", + "name": "中控/场控/助播", + "en_name": "Central control/Field control/Assistant broadcast", + "deleted": false, + "sublist": [] + }, + { + "code": "4000200060000", + "parentCode": "4000200000000", + "name": "主持人", + "en_name": "Host", + "deleted": false, + "sublist": [] + }, + { + "code": "4000200040000", + "parentCode": "4000200000000", + "name": "演员/模特", + "en_name": "Actor/Actress/Model", + "deleted": false, + "sublist": [] + }, + { + "code": "4000200090000", + "parentCode": "4000200000000", + "name": "服装/试衣模特", + "en_name": "Costume or fitting model", + "deleted": false, + "sublist": [] + }, + { + "code": "4000200010000", + "parentCode": "4000200000000", + "name": "经纪人", + "en_name": "Celebrity Agent", + "deleted": false, + "sublist": [] + }, + { + "code": "4000200020000", + "parentCode": "4000200000000", + "name": "配音", + "en_name": "Voice Actor", + "deleted": false, + "sublist": [] + }, + { + "code": "4000200080000", + "parentCode": "4000200000000", + "name": "DJ", + "en_name": "Disc jockey", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "4000100000000", + "parentCode": "4000000000000", + "name": "场务/剧务", + "en_name": "Stage Management", + "deleted": false, + "sublist": [ + { + "code": "4000100040000", + "parentCode": "4000100000000", + "name": "化妆助理", + "en_name": "Makeup Artist Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "4000100070000", + "parentCode": "4000100000000", + "name": "摄影助理", + "en_name": "Photographer Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "4000100080000", + "parentCode": "4000100000000", + "name": "艺人助理", + "en_name": "Artist Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "4000100010000", + "parentCode": "4000100000000", + "name": "导演助理", + "en_name": "Director Assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "4000100060000", + "parentCode": "4000100000000", + "name": "群演/跟组演员", + "en_name": "Figurant", + "deleted": false, + "sublist": [] + }, + { + "code": "4000100050000", + "parentCode": "4000100000000", + "name": "剧务", + "en_name": "Stage Services", + "deleted": false, + "sublist": [] + }, + { + "code": "4000100020000", + "parentCode": "4000100000000", + "name": "放映员", + "en_name": "Projectionist", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "6000000000000", + "parentCode": null, + "name": "商务服务/生活服务", + "en_name": "Business Services/Residential Services", + "deleted": false, + "sublist": [ + { + "code": "6001400000000", + "parentCode": "6000000000000", + "name": "商务服务", + "en_name": "Business Services", + "deleted": false, + "sublist": [ + { + "code": "6001400100000", + "parentCode": "6001400000000", + "name": "咨询顾问", + "en_name": "Consultant", + "deleted": false, + "sublist": [] + }, + { + "code": "6001400120000", + "parentCode": "6001400000000", + "name": "咨询经理", + "en_name": "Consulting manager", + "deleted": false, + "sublist": [] + }, + { + "code": "6001400130000", + "parentCode": "6001400000000", + "name": "咨询总监", + "en_name": "Consulting Director", + "deleted": false, + "sublist": [] + }, + { + "code": "6001400030000", + "parentCode": "6001400000000", + "name": "猎头顾问", + "en_name": "Hunter", + "deleted": false, + "sublist": [] + }, + { + "code": "14000600080000", + "parentCode": "6001400000000", + "name": "人力资源咨询顾问", + "en_name": "Human Resources Consultant", + "deleted": false, + "sublist": [] + }, + { + "code": "6001400040000", + "parentCode": "6001400000000", + "name": "企业管理咨询", + "en_name": "Managing Consultancy", + "deleted": false, + "sublist": [] + }, + { + "code": "6001400070000", + "parentCode": "6001400000000", + "name": "战略咨询", + "en_name": "Strategy Consultancy", + "deleted": false, + "sublist": [] + }, + { + "code": "6001400020000", + "parentCode": "6001400000000", + "name": "财务咨询顾问", + "en_name": "Financial Consultant", + "deleted": false, + "sublist": [] + }, + { + "code": "6001400010000", + "parentCode": "6001400000000", + "name": "IT咨询顾问", + "en_name": "IT Consultant", + "deleted": false, + "sublist": [] + }, + { + "code": "6001400080000", + "parentCode": "6001400000000", + "name": "知识产权代理", + "en_name": "Intellectual property agency", + "deleted": false, + "sublist": [] + }, + { + "code": "6001400090000", + "parentCode": "6001400000000", + "name": "知识产权/专利代理", + "en_name": "Patent Agent", + "deleted": false, + "sublist": [] + }, + { + "code": "6001400140000", + "parentCode": "6001400000000", + "name": "婚恋咨询师", + "en_name": "Marriage Counselor", + "deleted": false, + "sublist": [] + }, + { + "code": "6001400160000", + "parentCode": "6001400000000", + "name": "心理咨询师", + "en_name": "Psychological Consultation Teacher", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "6001300000000", + "parentCode": "6000000000000", + "name": "专业服务", + "en_name": "Professional Services", + "deleted": false, + "sublist": [ + { + "code": "6001300010000", + "parentCode": "6001300000000", + "name": "宠物美容", + "en_name": "Pet Beauty", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300210000", + "parentCode": "6001300000000", + "name": "宠物医生", + "en_name": "Paws & claws pet vet", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300160000", + "parentCode": "6001300000000", + "name": "训犬师", + "en_name": "Dog Trainer", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300140000", + "parentCode": "6001300000000", + "name": "水族馆表演演员", + "en_name": "Aquarium Performer", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300120000", + "parentCode": "6001300000000", + "name": "品酒师", + "en_name": "Sommelier", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300200000", + "parentCode": "6001300000000", + "name": "权证/过户", + "en_name": "Warrant", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "6001500000000", + "parentCode": "6000000000000", + "name": "零售百货", + "en_name": "Retail department stores", + "deleted": false, + "sublist": [ + { + "code": "5000400020000", + "parentCode": "6001500000000", + "name": "门店店长", + "en_name": "Store Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "6001500030000", + "parentCode": "6001500000000", + "name": "店员/营业员", + "en_name": "Shop assistant", + "deleted": false, + "sublist": [] + }, + { + "code": "19000200010000", + "parentCode": "6001500000000", + "name": "促销员/导购", + "en_name": "Promoter", + "deleted": false, + "sublist": [] + }, + { + "code": "6001500020000", + "parentCode": "6001500000000", + "name": "陈列员", + "en_name": "Exhibitor", + "deleted": false, + "sublist": [] + }, + { + "code": "6001500010000", + "parentCode": "6001500000000", + "name": "理货/陈列员", + "en_name": "Tallyman", + "deleted": false, + "sublist": [] + }, + { + "code": "6000100050000", + "parentCode": "6001500000000", + "name": "防损员", + "en_name": "Loss Prevention Officer", + "deleted": false, + "sublist": [] + }, + { + "code": "6000500100000", + "parentCode": "6001500000000", + "name": "售票员", + "en_name": "Ticket Seller", + "deleted": false, + "sublist": [] + }, + { + "code": "6001500040000", + "parentCode": "6001500000000", + "name": "卖场经理", + "en_name": "Store Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "6001500050000", + "parentCode": "6001500000000", + "name": "服装销售", + "en_name": "Clothing Sales", + "deleted": false, + "sublist": [] + }, + { + "code": "6001500060000", + "parentCode": "6001500000000", + "name": "珠宝销售", + "en_name": "Jewelry Sales", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "6000400000000", + "parentCode": "6000000000000", + "name": "餐饮服务", + "en_name": "Catering service", + "deleted": false, + "sublist": [ + { + "code": "6000400270000", + "parentCode": "6000400000000", + "name": "餐饮店长", + "en_name": "restaurant manager", + "deleted": false, + "sublist": [] + }, + { + "code": "6000500010000", + "parentCode": "6000400000000", + "name": "餐饮前厅经理/领班", + "en_name": "Dining Room Leader", + "deleted": false, + "sublist": [] + }, + { + "code": "6000500070000", + "parentCode": "6000400000000", + "name": "餐饮领班", + "en_name": "Maitre d 'in the catering industry", + "deleted": false, + "sublist": [] + }, + { + "code": "6000500040000", + "parentCode": "6000400000000", + "name": "服务员", + "en_name": "Attendant", + "deleted": false, + "sublist": [] + }, + { + "code": "6000500030000", + "parentCode": "6000400000000", + "name": "传菜员", + "en_name": "Restaurant Runner", + "deleted": false, + "sublist": [] + }, + { + "code": "6000500090000", + "parentCode": "6000400000000", + "name": "收银员", + "en_name": "Cashier", + "deleted": false, + "sublist": [] + }, + { + "code": "6000500060000", + "parentCode": "6000400000000", + "name": "礼仪/迎宾/接待", + "en_name": "Etiquette / Welcome / Reception", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400040000", + "parentCode": "6000400000000", + "name": "厨师", + "en_name": "Cook", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400240000", + "parentCode": "6000400000000", + "name": "中餐厨师", + "en_name": "Cook of Chinese Cuisine", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400200000", + "parentCode": "6000400000000", + "name": "西餐厨师", + "en_name": "Cook of Western Cuisine", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400140000", + "parentCode": "6000400000000", + "name": "日料厨师", + "en_name": "Daily Food Chef", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400070000", + "parentCode": "6000400000000", + "name": "厨师长", + "en_name": "Head cook", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400290000", + "parentCode": "6000400000000", + "name": "行政总厨", + "en_name": "Executive Chef", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400120000", + "parentCode": "6000400000000", + "name": "配菜打荷", + "en_name": "Garnish With Lotus", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400230000", + "parentCode": "6000400000000", + "name": "洗碗工", + "en_name": "Dish Washer", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400030000", + "parentCode": "6000400000000", + "name": "厨工/帮厨", + "en_name": "Kitchen Helper", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400250000", + "parentCode": "6000400000000", + "name": "中餐厨工", + "en_name": "Apprentice Cook of Chinese Cuisine", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400210000", + "parentCode": "6000400000000", + "name": "西餐厨工", + "en_name": "Apprentice Cook of Western Cuisine", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400130000", + "parentCode": "6000400000000", + "name": "日餐厨工", + "en_name": "Apprentice Cook of Japanese Cuisine", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400090000", + "parentCode": "6000400000000", + "name": "烘焙厨工", + "en_name": "Baker Trainee", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400080000", + "parentCode": "6000400000000", + "name": "烘焙师", + "en_name": "Baker", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400060000", + "parentCode": "6000400000000", + "name": "蛋糕师", + "en_name": "Cake Maker", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400180000", + "parentCode": "6000400000000", + "name": "甜品师", + "en_name": "Desert Cook", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400010000", + "parentCode": "6000400000000", + "name": "蛋糕/裱花师", + "en_name": "Cake/Pastry Decorator", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400110000", + "parentCode": "6000400000000", + "name": "面点师", + "en_name": "Pastry Cook", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400220000", + "parentCode": "6000400000000", + "name": "西点师", + "en_name": "Western Pastry Cook", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400190000", + "parentCode": "6000400000000", + "name": "调酒师", + "en_name": "Bartender", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400100000", + "parentCode": "6000400000000", + "name": "咖啡师", + "en_name": "Barista", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400020000", + "parentCode": "6000400000000", + "name": "茶艺师", + "en_name": "Tea Artist", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400280000", + "parentCode": "6000400000000", + "name": "奶茶店店员", + "en_name": "Milk tea shop clerk", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400150000", + "parentCode": "6000400000000", + "name": "烧烤师傅", + "en_name": "Barbecue Master", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400160000", + "parentCode": "6000400000000", + "name": "生鲜食品加工/处理", + "en_name": "Raw Food Processing", + "deleted": false, + "sublist": [] + }, + { + "code": "6000400170000", + "parentCode": "6000400000000", + "name": "食品加工/处理", + "en_name": "Food Processing", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "6000800000000", + "parentCode": "6000000000000", + "name": "酒店服务", + "en_name": "Hotel Services", + "deleted": false, + "sublist": [ + { + "code": "6000800060000", + "parentCode": "6000800000000", + "name": "酒店前台", + "en_name": "Hotel Front-desk", + "deleted": false, + "sublist": [] + }, + { + "code": "6000800040000", + "parentCode": "6000800000000", + "name": "酒店大堂", + "en_name": "Hotel Lobby Staff", + "deleted": false, + "sublist": [] + }, + { + "code": "6000800110000", + "parentCode": "6000800000000", + "name": "酒店大堂经理", + "en_name": "Front Office Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "6000800050000", + "parentCode": "6000800000000", + "name": "酒店经理", + "en_name": "Hotel Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "6000800070000", + "parentCode": "6000800000000", + "name": "客房服务员", + "en_name": "Room Attendant", + "deleted": false, + "sublist": [] + }, + { + "code": "6000800080000", + "parentCode": "6000800000000", + "name": "客房经理", + "en_name": "Executive Housekeeper", + "deleted": false, + "sublist": [] + }, + { + "code": "6000800010000", + "parentCode": "6000800000000", + "name": "公寓管家", + "en_name": "Apartment Housekeeper", + "deleted": false, + "sublist": [] + }, + { + "code": "6000800100000", + "parentCode": "6000800000000", + "name": "民宿运营", + "en_name": "Homestay Operation", + "deleted": false, + "sublist": [] + }, + { + "code": "6000800030000", + "parentCode": "6000800000000", + "name": "行李员", + "en_name": "Doorman", + "deleted": false, + "sublist": [] + }, + { + "code": "6000800020000", + "parentCode": "6000800000000", + "name": "贵宾服务主任", + "en_name": "VIP Services", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "6000900000000", + "parentCode": "6000000000000", + "name": "旅游服务", + "en_name": "Tourism Service", + "deleted": false, + "sublist": [ + { + "code": "6000900050000", + "parentCode": "6000900000000", + "name": "旅游顾问", + "en_name": "Tourism Consultant", + "deleted": false, + "sublist": [] + }, + { + "code": "6000900040000", + "parentCode": "6000900000000", + "name": "旅游策划师", + "en_name": "Tourism Planner", + "deleted": false, + "sublist": [] + }, + { + "code": "6000900110000", + "parentCode": "6000900000000", + "name": "旅游产品经理", + "en_name": "Tourism Product Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "6000900020000", + "parentCode": "6000900000000", + "name": "导游", + "en_name": "Tour Guide", + "deleted": false, + "sublist": [] + }, + { + "code": "6000900100000", + "parentCode": "6000900000000", + "name": "讲解员", + "en_name": "Commentator", + "deleted": false, + "sublist": [] + }, + { + "code": "6000900010000", + "parentCode": "6000900000000", + "name": "出境操作", + "en_name": "Outbound Tour Ops", + "deleted": false, + "sublist": [] + }, + { + "code": "6000900080000", + "parentCode": "6000900000000", + "name": "签证专员", + "en_name": "Visa Specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "6000900120000", + "parentCode": "6000900000000", + "name": "预定票务", + "en_name": "Book A Ticket", + "deleted": false, + "sublist": [] + }, + { + "code": "6000900060000", + "parentCode": "6000900000000", + "name": "计调", + "en_name": "Planning And Adjustment", + "deleted": false, + "sublist": [] + }, + { + "code": "6000900030000", + "parentCode": "6000900000000", + "name": "酒店试睡员", + "en_name": "Hotel Connoisseur", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "6001000000000", + "parentCode": "6000000000000", + "name": "健康/美容", + "en_name": "Health or cosmetology", + "deleted": false, + "sublist": [ + { + "code": "6001000100000", + "parentCode": "6001000000000", + "name": "医美咨询", + "en_name": "Medical and aesthetic consultation", + "deleted": false, + "sublist": [] + }, + { + "code": "19000200160000", + "parentCode": "6001000000000", + "name": "健康顾问", + "en_name": "Health consultant", + "deleted": false, + "sublist": [] + }, + { + "code": "6001000110000", + "parentCode": "6001000000000", + "name": "康复治疗师", + "en_name": "Rehabilitation therapists", + "deleted": false, + "sublist": [] + }, + { + "code": "6001000040000", + "parentCode": "6001000000000", + "name": "理疗师", + "en_name": "Therapist", + "deleted": false, + "sublist": [] + }, + { + "code": "6001000120000", + "parentCode": "6001000000000", + "name": "产后康复师", + "en_name": "postpartum rehabilitation specialist", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200250000", + "parentCode": "6001000000000", + "name": "营养师", + "en_name": "Nutritionist", + "deleted": false, + "sublist": [] + }, + { + "code": "6001000020000", + "parentCode": "6001000000000", + "name": "按摩师", + "en_name": "Massagist", + "deleted": false, + "sublist": [] + }, + { + "code": "18000200260000", + "parentCode": "6001000000000", + "name": "针灸推拿", + "en_name": "Acupuncture And Massage", + "deleted": false, + "sublist": [] + }, + { + "code": "6001000090000", + "parentCode": "6001000000000", + "name": "足疗师", + "en_name": "Foot Massager", + "deleted": false, + "sublist": [] + }, + { + "code": "6001000070000", + "parentCode": "6001000000000", + "name": "美容师", + "en_name": "Beautician", + "deleted": false, + "sublist": [] + }, + { + "code": "6001000030000", + "parentCode": "6001000000000", + "name": "发型师", + "en_name": "Hair Stylist", + "deleted": false, + "sublist": [] + }, + { + "code": "6001000060000", + "parentCode": "6001000000000", + "name": "美甲师", + "en_name": "Manicurist", + "deleted": false, + "sublist": [] + }, + { + "code": "6001000080000", + "parentCode": "6001000000000", + "name": "美体师", + "en_name": "Therapist", + "deleted": false, + "sublist": [] + }, + { + "code": "6001000130000", + "parentCode": "6001000000000", + "name": "纹绣师", + "en_name": "Embroiderer", + "deleted": false, + "sublist": [] + }, + { + "code": "6001000140000", + "parentCode": "6001000000000", + "name": "美容顾问", + "en_name": "Beauty Consultant", + "deleted": false, + "sublist": [] + }, + { + "code": "6001000160000", + "parentCode": "6001000000000", + "name": "彩妆顾问", + "en_name": "Makeup Consultant", + "deleted": false, + "sublist": [] + }, + { + "code": "6001000150000", + "parentCode": "6001000000000", + "name": "会籍顾问", + "en_name": "Membership Consultant", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "6001600000000", + "parentCode": "6000000000000", + "name": "运动健身", + "en_name": "Exercise and fitness", + "deleted": false, + "sublist": [ + { + "code": "6001600010000", + "parentCode": "6001600000000", + "name": "健身顾问", + "en_name": "fitness consultant", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200160000", + "parentCode": "6001600000000", + "name": "瑜伽教练", + "en_name": "Yoga Instructor", + "deleted": false, + "sublist": [] + }, + { + "code": "6001600040000", + "parentCode": "6001600000000", + "name": "美体教练", + "en_name": "Beauty Coach", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200060000", + "parentCode": "6001600000000", + "name": "健身教练", + "en_name": "Fitness Coach", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200120000", + "parentCode": "6001600000000", + "name": "跆拳道教练", + "en_name": "Taekwondo Coach", + "deleted": false, + "sublist": [] + }, + { + "code": "6001600020000", + "parentCode": "6001600000000", + "name": "武术教练", + "en_name": "Martial Arts Coach", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200150000", + "parentCode": "6001600000000", + "name": "游泳教练", + "en_name": "Swimming Coach", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200020000", + "parentCode": "6001600000000", + "name": "高尔夫教练", + "en_name": "Golf Coach", + "deleted": false, + "sublist": [] + }, + { + "code": "11000200090000", + "parentCode": "6001600000000", + "name": "球类运动教练", + "en_name": "Ball Sports Coach", + "deleted": false, + "sublist": [] + }, + { + "code": "6001600050000", + "parentCode": "6001600000000", + "name": "篮球/羽毛球教练", + "en_name": "Basketball / Badminton Coach", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300070000", + "parentCode": "6001600000000", + "name": "户外/游戏教练", + "en_name": "Outdoor Coach", + "deleted": false, + "sublist": [] + }, + { + "code": "6001600030000", + "parentCode": "6001600000000", + "name": "轮滑教练", + "en_name": "Roller Skating Coach", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300080000", + "parentCode": "6001600000000", + "name": "救生员", + "en_name": "Lifeguard", + "deleted": false, + "sublist": [] + }, + { + "code": "6001300130000", + "parentCode": "6001600000000", + "name": "潜水员", + "en_name": "Diver", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "6000100000000", + "parentCode": "6000000000000", + "name": "物业/安保", + "en_name": "Property Services/Security", + "deleted": false, + "sublist": [ + { + "code": "6000100030000", + "parentCode": "6000100000000", + "name": "保安", + "en_name": "Security", + "deleted": false, + "sublist": [] + }, + { + "code": "6000100040000", + "parentCode": "6000100000000", + "name": "保安经理", + "en_name": "Security Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "6000100110000", + "parentCode": "6000100000000", + "name": "物业经理", + "en_name": "Property Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "6000100120000", + "parentCode": "6000100000000", + "name": "物业管理员", + "en_name": "Property Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "6000100130000", + "parentCode": "6000100000000", + "name": "物业维修", + "en_name": "Property Maintenance", + "deleted": false, + "sublist": [] + }, + { + "code": "6000100160000", + "parentCode": "6000100000000", + "name": "物业工程经理", + "en_name": "Property Engineering Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "6000100010000", + "parentCode": "6000100000000", + "name": "安检员", + "en_name": "Security inspector", + "deleted": false, + "sublist": [] + }, + { + "code": "6000100020000", + "parentCode": "6000100000000", + "name": "安全员", + "en_name": "Safety Officer", + "deleted": false, + "sublist": [] + }, + { + "code": "6000100060000", + "parentCode": "6000100000000", + "name": "辅警/协警", + "en_name": "Auxiliary Police", + "deleted": false, + "sublist": [] + }, + { + "code": "6000100140000", + "parentCode": "6000100000000", + "name": "协警", + "en_name": "Auxiliary Police", + "deleted": false, + "sublist": [] + }, + { + "code": "6000100080000", + "parentCode": "6000100000000", + "name": "交通管理员", + "en_name": "Traffic Controller", + "deleted": false, + "sublist": [] + }, + { + "code": "6000100150000", + "parentCode": "6000100000000", + "name": "押运员", + "en_name": "Escort", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "6001200000000", + "parentCode": "6000000000000", + "name": "家政/维修", + "en_name": "Domestic service or maintenance", + "deleted": false, + "sublist": [ + { + "code": "6000600010000", + "parentCode": "6001200000000", + "name": "保洁", + "en_name": "Cleaning Staff", + "deleted": false, + "sublist": [] + }, + { + "code": "6001200090000", + "parentCode": "6001200000000", + "name": "保洁经理", + "en_name": "Cleaning Manager", + "deleted": false, + "sublist": [] + }, + { + "code": "6000200010000", + "parentCode": "6001200000000", + "name": "保姆", + "en_name": "Nanny/Baby Sitter", + "deleted": false, + "sublist": [] + }, + { + "code": "6000200030000", + "parentCode": "6001200000000", + "name": "月嫂", + "en_name": "Maternity Matron", + "deleted": false, + "sublist": [] + }, + { + "code": "6001200100000", + "parentCode": "6001200000000", + "name": "催乳师", + "en_name": "Prolactinist", + "deleted": false, + "sublist": [] + }, + { + "code": "6000600020000", + "parentCode": "6001200000000", + "name": "家政", + "en_name": "Housekeeper", + "deleted": false, + "sublist": [] + }, + { + "code": "6000600030000", + "parentCode": "6001200000000", + "name": "钟点工", + "en_name": "Hourly Worker", + "deleted": false, + "sublist": [] + }, + { + "code": "6001200020000", + "parentCode": "6001200000000", + "name": "电子/电器维修/保养", + "en_name": "Electronic/Electrical Appliance Repair", + "deleted": false, + "sublist": [] + }, + { + "code": "6001200030000", + "parentCode": "6001200000000", + "name": "计算机维修", + "en_name": "Computer Repair", + "deleted": false, + "sublist": [] + }, + { + "code": "6001200040000", + "parentCode": "6001200000000", + "name": "家电维修", + "en_name": "Home Appliance Repair", + "deleted": false, + "sublist": [] + }, + { + "code": "6001200050000", + "parentCode": "6001200000000", + "name": "纱窗维修", + "en_name": "Screen Window Repair", + "deleted": false, + "sublist": [] + }, + { + "code": "6001200060000", + "parentCode": "6001200000000", + "name": "手机维修", + "en_name": "Cell Phone Repair", + "deleted": false, + "sublist": [] + }, + { + "code": "6001200070000", + "parentCode": "6001200000000", + "name": "水电工", + "en_name": "Plumber", + "deleted": false, + "sublist": [] + }, + { + "code": "6001200080000", + "parentCode": "6001200000000", + "name": "维修经理/主管", + "en_name": "Maintenance Supervisor", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "8000000000000", + "parentCode": null, + "name": "管培生/非企业从业者", + "en_name": "Trainee/Civil Servants/NPO", + "deleted": false, + "sublist": [ + { + "code": "8000100000000", + "parentCode": "8000000000000", + "name": "管培生/储备干部", + "en_name": "Trainee", + "deleted": false, + "sublist": [ + { + "code": "8000100010000", + "parentCode": "8000100000000", + "name": "储备干部", + "en_name": "Management Trainee", + "deleted": false, + "sublist": [] + }, + { + "code": "8000100020000", + "parentCode": "8000100000000", + "name": "储备经理人", + "en_name": "Manager Trainee", + "deleted": false, + "sublist": [] + }, + { + "code": "8000100030000", + "parentCode": "8000100000000", + "name": "管培生/储备干部", + "en_name": "Management Trainee/Executive Trainee", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "8000200000000", + "parentCode": "8000000000000", + "name": "社工", + "en_name": "Social Worker", + "deleted": false, + "sublist": [ + { + "code": "8000200010000", + "parentCode": "8000200000000", + "name": "社工", + "en_name": "Social Worker", + "deleted": false, + "sublist": [] + }, + { + "code": "8000200020000", + "parentCode": "8000200000000", + "name": "志愿者/义工", + "en_name": "Volunteer", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "8000400000000", + "parentCode": "8000000000000", + "name": "政府及非盈利机构从业者", + "en_name": "Civil Servant/NPO", + "deleted": false, + "sublist": [ + { + "code": "8000400020000", + "parentCode": "8000400000000", + "name": "公务员", + "en_name": "Civil Servant", + "deleted": false, + "sublist": [] + }, + { + "code": "8000400040000", + "parentCode": "8000400000000", + "name": "事业单位人员", + "en_name": "Public Institution Employee", + "deleted": false, + "sublist": [] + }, + { + "code": "8000400010000", + "parentCode": "8000400000000", + "name": "公共卫生/疾病控制", + "en_name": "Public Health/Disease Control", + "deleted": false, + "sublist": [] + }, + { + "code": "8000400030000", + "parentCode": "8000400000000", + "name": "疾病控制", + "en_name": "Disease Control", + "deleted": false, + "sublist": [] + } + ] + } + ] + } + ], + "industry": [ + { + "code": "-1", + "parentCode": null, + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "100000000", + "parentCode": null, + "name": "互联网/IT/电子/通信", + "en_name": "Information", + "deleted": false, + "sublist": [ + { + "code": "100000000", + "parentCode": "100000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "100020000", + "parentCode": "100000000", + "name": "电子商务", + "en_name": "E-commerce", + "deleted": false, + "sublist": [] + }, + { + "code": "100070000", + "parentCode": "100000000", + "name": "企业服务", + "en_name": "Corporate Services", + "deleted": false, + "sublist": [] + }, + { + "code": "100080000", + "parentCode": "100000000", + "name": "人工智能", + "en_name": "AI/Artificial Intelligence", + "deleted": false, + "sublist": [] + }, + { + "code": "100000220", + "parentCode": "100000000", + "name": "智能硬件", + "en_name": "Intelligent Hardware", + "deleted": false, + "sublist": [] + }, + { + "code": "100160000", + "parentCode": "100000000", + "name": "在线教育", + "en_name": "Online education", + "deleted": false, + "sublist": [] + }, + { + "code": "100150000", + "parentCode": "100000000", + "name": "在线医疗", + "en_name": "Online healthcare", + "deleted": false, + "sublist": [] + }, + { + "code": "100110000", + "parentCode": "100000000", + "name": "新媒体", + "en_name": "New Media", + "deleted": false, + "sublist": [] + }, + { + "code": "100180000", + "parentCode": "100000000", + "name": "物联网", + "en_name": "Internet of things", + "deleted": false, + "sublist": [] + }, + { + "code": "100190000", + "parentCode": "100000000", + "name": "新零售", + "en_name": "New retail", + "deleted": false, + "sublist": [] + }, + { + "code": "100170000", + "parentCode": "100000000", + "name": "区块链", + "en_name": "Blockchain", + "deleted": false, + "sublist": [] + }, + { + "code": "100120000", + "parentCode": "100000000", + "name": "游戏", + "en_name": "Game", + "deleted": false, + "sublist": [] + }, + { + "code": "100000250", + "parentCode": "100000000", + "name": "社交网络", + "en_name": "Social Networks", + "deleted": false, + "sublist": [] + }, + { + "code": "100000260", + "parentCode": "100000000", + "name": "在线招聘/求职", + "en_name": "Online Recruitment or Job Search", + "deleted": false, + "sublist": [] + }, + { + "code": "100130000", + "parentCode": "100000000", + "name": "云计算/大数据", + "en_name": "Cloud Computing/Big Data", + "deleted": false, + "sublist": [] + }, + { + "code": "100100000", + "parentCode": "100000000", + "name": "网络/信息安全", + "en_name": "Network/Information Security", + "deleted": false, + "sublist": [] + }, + { + "code": "100210000", + "parentCode": "100000000", + "name": "在线生活服务(O2O)", + "en_name": "Online life service (o2o)", + "deleted": false, + "sublist": [] + }, + { + "code": "100200000", + "parentCode": "100000000", + "name": "在线音乐/视频/阅读", + "en_name": "Online music or video or reading", + "deleted": false, + "sublist": [] + }, + { + "code": "100030000", + "parentCode": "100000000", + "name": "互联网", + "en_name": "Internet", + "deleted": false, + "sublist": [] + }, + { + "code": "100040000", + "parentCode": "100000000", + "name": "IT服务", + "en_name": "IT Services", + "deleted": false, + "sublist": [] + }, + { + "code": "100050000", + "parentCode": "100000000", + "name": "计算机软件", + "en_name": "Computer Software", + "deleted": false, + "sublist": [] + }, + { + "code": "100060000", + "parentCode": "100000000", + "name": "计算机硬件", + "en_name": "Computer Hardware", + "deleted": false, + "sublist": [] + }, + { + "code": "100090000", + "parentCode": "100000000", + "name": "通信/网络设备", + "en_name": "Telecom & Network Equipment", + "deleted": false, + "sublist": [] + }, + { + "code": "100140000", + "parentCode": "100000000", + "name": "运营商/增值服务", + "en_name": "Telecom Operators/Service Providers", + "deleted": false, + "sublist": [] + }, + { + "code": "100010000", + "parentCode": "100000000", + "name": "电子/半导体/集成电路", + "en_name": "Electronics/Semiconductor/IC", + "deleted": false, + "sublist": [] + }, + { + "code": "100000230", + "parentCode": "100000000", + "name": "消费电子产品", + "en_name": "Consumer Electronics", + "deleted": false, + "sublist": [] + }, + { + "code": "100000240", + "parentCode": "100000000", + "name": "光电子行业", + "en_name": "Optoelectronics Industry", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "400000000", + "parentCode": null, + "name": "房地产/建筑", + "en_name": "Real estate or construction", + "deleted": false, + "sublist": [ + { + "code": "400000000", + "parentCode": "400000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "200010000", + "parentCode": "400000000", + "name": "房地产开发", + "en_name": "Real Estate Development", + "deleted": false, + "sublist": [] + }, + { + "code": "200040000", + "parentCode": "400000000", + "name": "土地与公共设施管理", + "en_name": "Management of L& & Public Properties", + "deleted": false, + "sublist": [] + }, + { + "code": "200020000", + "parentCode": "400000000", + "name": "房地产中介", + "en_name": "Real Estate Agency & Leasing", + "deleted": false, + "sublist": [] + }, + { + "code": "200050000", + "parentCode": "400000000", + "name": "物业管理", + "en_name": "Property Management", + "deleted": false, + "sublist": [] + }, + { + "code": "400050000", + "parentCode": "400000000", + "name": "建筑设计", + "en_name": "Architectural design", + "deleted": false, + "sublist": [] + }, + { + "code": "400030000", + "parentCode": "400000000", + "name": "工程施工", + "en_name": "Engineering construction", + "deleted": false, + "sublist": [] + }, + { + "code": "400010000", + "parentCode": "400000000", + "name": "建筑设备安装", + "en_name": "Construction equipment installation", + "deleted": false, + "sublist": [] + }, + { + "code": "400040000", + "parentCode": "400000000", + "name": "装饰装修", + "en_name": "Decoration", + "deleted": false, + "sublist": [] + }, + { + "code": "400060000", + "parentCode": "400000000", + "name": "建材", + "en_name": "Building material", + "deleted": false, + "sublist": [] + }, + { + "code": "400070000", + "parentCode": "400000000", + "name": "建筑工程检测", + "en_name": "Construction engineering inspection", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "300000000", + "parentCode": null, + "name": "金融业", + "en_name": "Finance", + "deleted": false, + "sublist": [ + { + "code": "300000000", + "parentCode": "300000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "300070000", + "parentCode": "300000000", + "name": "银行", + "en_name": "Bank", + "deleted": false, + "sublist": [] + }, + { + "code": "300010000", + "parentCode": "300000000", + "name": "保险", + "en_name": "Insurance", + "deleted": false, + "sublist": [] + }, + { + "code": "300040000", + "parentCode": "300000000", + "name": "基金", + "en_name": "Fund", + "deleted": false, + "sublist": [] + }, + { + "code": "300060000", + "parentCode": "300000000", + "name": "信托", + "en_name": "Trust", + "deleted": false, + "sublist": [] + }, + { + "code": "300080000", + "parentCode": "300000000", + "name": "证券/期货", + "en_name": "Securities & Futures", + "deleted": false, + "sublist": [] + }, + { + "code": "300090000", + "parentCode": "300000000", + "name": "投资/融资", + "en_name": "Investment or financing", + "deleted": false, + "sublist": [] + }, + { + "code": "300050000", + "parentCode": "300000000", + "name": "汽车金融", + "en_name": "Auto Finance", + "deleted": false, + "sublist": [] + }, + { + "code": "300030000", + "parentCode": "300000000", + "name": "互联网金融/小额贷款", + "en_name": "Internet Finance", + "deleted": false, + "sublist": [] + }, + { + "code": "300020000", + "parentCode": "300000000", + "name": "租赁/拍卖/典当/担保", + "en_name": "Lease or auction or pawn or guarantee", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1200000000", + "parentCode": null, + "name": "教育培训/科研", + "en_name": "Educational & Scientific Research", + "deleted": false, + "sublist": [ + { + "code": "1200000000", + "parentCode": "1200000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "1200020000", + "parentCode": "1200000000", + "name": "培训/辅导服务", + "en_name": "Training or Counselling", + "deleted": false, + "sublist": [] + }, + { + "code": "1200040000", + "parentCode": "1200000000", + "name": "学校/学历教育", + "en_name": "School or Academic Education", + "deleted": false, + "sublist": [] + }, + { + "code": "1200030000", + "parentCode": "1200000000", + "name": "学术/科研", + "en_name": "Research & experimental development", + "deleted": false, + "sublist": [] + }, + { + "code": "1200010000", + "parentCode": "1200000000", + "name": "科学技术推广", + "en_name": "Science and Technology Promotion", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "900000000", + "parentCode": null, + "name": "广告/传媒/文化/体育", + "en_name": "Advertising or media or culture or sports", + "deleted": false, + "sublist": [ + { + "code": "900000000", + "parentCode": "900000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "800040000", + "parentCode": "900000000", + "name": "广告/营销", + "en_name": "Advertising or Marketing", + "deleted": false, + "sublist": [] + }, + { + "code": "900010000", + "parentCode": "900000000", + "name": "广播/影视", + "en_name": "Broadcast or Movies", + "deleted": false, + "sublist": [] + }, + { + "code": "800050000", + "parentCode": "900000000", + "name": "会议/展览", + "en_name": "Exhibition Services", + "deleted": false, + "sublist": [] + }, + { + "code": "900030000", + "parentCode": "900000000", + "name": "文化艺术/娱乐", + "en_name": "Arts/Entertainment", + "deleted": false, + "sublist": [] + }, + { + "code": "900020000", + "parentCode": "900000000", + "name": "体育", + "en_name": "Sports", + "deleted": false, + "sublist": [] + }, + { + "code": "900040000", + "parentCode": "900000000", + "name": "新闻/出版", + "en_name": "News/Publishing", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1300000000", + "parentCode": null, + "name": "生物医药/医疗", + "en_name": "Biomedical or medical", + "deleted": false, + "sublist": [ + { + "code": "1300000000", + "parentCode": "1300000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "1300030000", + "parentCode": "1300000000", + "name": "医院", + "en_name": "Hospitals", + "deleted": false, + "sublist": [] + }, + { + "code": "1300010000", + "parentCode": "1300000000", + "name": "卫生服务", + "en_name": "Health care", + "deleted": false, + "sublist": [] + }, + { + "code": "1300040000", + "parentCode": "1300000000", + "name": "生物工程", + "en_name": "bioengineering", + "deleted": false, + "sublist": [] + }, + { + "code": "500170000", + "parentCode": "1300000000", + "name": "医药制造", + "en_name": "Manufacture of Pharmaceutical Products", + "deleted": false, + "sublist": [] + }, + { + "code": "1300050000", + "parentCode": "1300000000", + "name": "医疗检测", + "en_name": "Medical tests", + "deleted": false, + "sublist": [] + }, + { + "code": "1300060000", + "parentCode": "1300000000", + "name": "医药批发/零售", + "en_name": "Pharmaceutical wholesale or retail", + "deleted": false, + "sublist": [] + }, + { + "code": "500210000", + "parentCode": "1300000000", + "name": "医疗设备/器械", + "en_name": "Manufacture of Medical Equipment & Facilities", + "deleted": false, + "sublist": [] + }, + { + "code": "1300000800", + "parentCode": "1300000000", + "name": "IVD", + "en_name": "IVD", + "deleted": false, + "sublist": [] + }, + { + "code": "1300000700", + "parentCode": "1300000000", + "name": "医美/健康服务", + "en_name": "Medical Beauty or Health Services", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "700000000", + "parentCode": null, + "name": "批发/零售/贸易", + "en_name": "Wholesale & Retail Trade", + "deleted": false, + "sublist": [ + { + "code": "700000000", + "parentCode": "700000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "700010000", + "parentCode": "700000000", + "name": "快速消费品", + "en_name": "Fast Moving Consumer Goods", + "deleted": false, + "sublist": [] + }, + { + "code": "700030000", + "parentCode": "700000000", + "name": "耐用消费品", + "en_name": "Durable Goods", + "deleted": false, + "sublist": [] + }, + { + "code": "700040000", + "parentCode": "700000000", + "name": "零售/批发", + "en_name": "Retail/Wholesale", + "deleted": false, + "sublist": [] + }, + { + "code": "700050000", + "parentCode": "700000000", + "name": "食品/饮料", + "en_name": "Food or beverage", + "deleted": false, + "sublist": [] + }, + { + "code": "700060000", + "parentCode": "700000000", + "name": "烟草/酒业", + "en_name": "Tobacco and Wine", + "deleted": false, + "sublist": [] + }, + { + "code": "700070000", + "parentCode": "700000000", + "name": "日化", + "en_name": "Chemicals for daily use", + "deleted": false, + "sublist": [] + }, + { + "code": "700110000", + "parentCode": "700000000", + "name": "服装/纺织/皮革", + "en_name": "Clothing or textile or leather", + "deleted": false, + "sublist": [] + }, + { + "code": "700000140", + "parentCode": "700000000", + "name": "奢侈品", + "en_name": "Luxury", + "deleted": false, + "sublist": [] + }, + { + "code": "700080000", + "parentCode": "700000000", + "name": "玩具/礼品", + "en_name": "Toys or gifts", + "deleted": false, + "sublist": [] + }, + { + "code": "700090000", + "parentCode": "700000000", + "name": "珠宝/首饰", + "en_name": "Jewelry or jewelry", + "deleted": false, + "sublist": [] + }, + { + "code": "700130000", + "parentCode": "700000000", + "name": "办公用品/设备", + "en_name": "Office Supplies or Equipment", + "deleted": false, + "sublist": [] + }, + { + "code": "700100000", + "parentCode": "700000000", + "name": "工艺品/收藏品/艺术品", + "en_name": "Crafts or Collectibles or Artworks", + "deleted": false, + "sublist": [] + }, + { + "code": "700120000", + "parentCode": "700000000", + "name": "家具/家居/家电", + "en_name": "Furniture or home or home appliances", + "deleted": false, + "sublist": [] + }, + { + "code": "700020000", + "parentCode": "700000000", + "name": "贸易/进出口", + "en_name": "Trading or Import or Export", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "500000000", + "parentCode": null, + "name": "制造业", + "en_name": "Manufacturing", + "deleted": false, + "sublist": [ + { + "code": "500000000", + "parentCode": "500000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "500010000", + "parentCode": "500000000", + "name": "船舶/航空/航天/火车制造", + "en_name": "Manufacture of Other Transport Equipment", + "deleted": false, + "sublist": [] + }, + { + "code": "500020000", + "parentCode": "500000000", + "name": "电气机械/电力设备", + "en_name": "Electrical Machinery or Electrical Equipment", + "deleted": false, + "sublist": [] + }, + { + "code": "500030000", + "parentCode": "500000000", + "name": "电子设备制造", + "en_name": "Manufacture of Electronic Products", + "deleted": false, + "sublist": [] + }, + { + "code": "500000280", + "parentCode": "500000000", + "name": "机器人", + "en_name": "Robot", + "deleted": false, + "sublist": [] + }, + { + "code": "500060000", + "parentCode": "500000000", + "name": "钢铁/有色金属冶炼及加工", + "en_name": "Metallurgy", + "deleted": false, + "sublist": [] + }, + { + "code": "500220000", + "parentCode": "500000000", + "name": "专用设备制造", + "en_name": "Manufacture of Specific-purpose Machinery", + "deleted": false, + "sublist": [] + }, + { + "code": "500000270", + "parentCode": "500000000", + "name": "军工制造", + "en_name": "Military Manufacturing", + "deleted": false, + "sublist": [] + }, + { + "code": "500090000", + "parentCode": "500000000", + "name": "金属制品业", + "en_name": "Manufacture of Metallic Mineral Products", + "deleted": false, + "sublist": [] + }, + { + "code": "500140000", + "parentCode": "500000000", + "name": "通用设备制造", + "en_name": "Manufacture of General-purpose Machinery", + "deleted": false, + "sublist": [] + }, + { + "code": "500180000", + "parentCode": "500000000", + "name": "仪器仪表制造", + "en_name": "Manufacture of Instruments & Appliances", + "deleted": false, + "sublist": [] + }, + { + "code": "500000260", + "parentCode": "500000000", + "name": "摩托车/自行车制造", + "en_name": "Motorcycle or Bicycle Manufacturing", + "deleted": false, + "sublist": [] + }, + { + "code": "500050000", + "parentCode": "500000000", + "name": "非金属矿物制品业", + "en_name": "Manufacture of Non-metallic Mineral Products", + "deleted": false, + "sublist": [] + }, + { + "code": "500000250", + "parentCode": "500000000", + "name": "新材料", + "en_name": "New Materials", + "deleted": false, + "sublist": [] + }, + { + "code": "500070000", + "parentCode": "500000000", + "name": "化学纤维制造业", + "en_name": "Manufacture of Chemical Fiber", + "deleted": false, + "sublist": [] + }, + { + "code": "500080000", + "parentCode": "500000000", + "name": "化学原料/化学制品", + "en_name": "Manufacture of Chemicals & Chemical Products ", + "deleted": false, + "sublist": [] + }, + { + "code": "500130000", + "parentCode": "500000000", + "name": "日化产品制造", + "en_name": "Manufacture of Household Chemical Products", + "deleted": false, + "sublist": [] + }, + { + "code": "500040000", + "parentCode": "500000000", + "name": "纺织业/服饰产品加工制造", + "en_name": "Manufacture of Textiles & Wearing Apparel", + "deleted": false, + "sublist": [] + }, + { + "code": "500100000", + "parentCode": "500000000", + "name": "农副产品加工制造", + "en_name": "Agricultural Processing", + "deleted": false, + "sublist": [] + }, + { + "code": "500120000", + "parentCode": "500000000", + "name": "燃料资源加工制造", + "en_name": "Fuel Manufacturing & Processing", + "deleted": false, + "sublist": [] + }, + { + "code": "500150000", + "parentCode": "500000000", + "name": "橡胶和塑料制品", + "en_name": "Manufacture of Rubber & Plastic Products", + "deleted": false, + "sublist": [] + }, + { + "code": "500240000", + "parentCode": "500000000", + "name": "文体/办公设备制造", + "en_name": "Sports or office equipment manufacturing", + "deleted": false, + "sublist": [] + }, + { + "code": "500200000", + "parentCode": "500000000", + "name": "家具制造", + "en_name": "Furniture manufacturing", + "deleted": false, + "sublist": [] + }, + { + "code": "500190000", + "parentCode": "500000000", + "name": "印刷/包装/造纸", + "en_name": "Printing or packaging or paper making", + "deleted": false, + "sublist": [] + }, + { + "code": "500230000", + "parentCode": "500000000", + "name": "工业自动化", + "en_name": "Tndustrial automation", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1600000000", + "parentCode": null, + "name": "汽车", + "en_name": "Automobile", + "deleted": false, + "sublist": [ + { + "code": "1600000000", + "parentCode": "1600000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "500110000", + "parentCode": "1600000000", + "name": "汽车研发/制造", + "en_name": "Automotive Research or Manufacturing", + "deleted": false, + "sublist": [] + }, + { + "code": "1600030000", + "parentCode": "1600000000", + "name": "新能源汽车", + "en_name": "New energy vehicle", + "deleted": false, + "sublist": [] + }, + { + "code": "1600000400", + "parentCode": "1600000000", + "name": "汽车智能互联", + "en_name": "Automotive Intelligent Iinterconnection", + "deleted": false, + "sublist": [] + }, + { + "code": "1600020000", + "parentCode": "1600000000", + "name": "汽车零部件", + "en_name": "Auto parts", + "deleted": false, + "sublist": [] + }, + { + "code": "1600010000", + "parentCode": "1600000000", + "name": "汽车4S店/经销商", + "en_name": "Automotive 4S Stores or Dealers", + "deleted": false, + "sublist": [] + }, + { + "code": "1600000500", + "parentCode": "1600000000", + "name": "汽车后市场", + "en_name": "Automotive Aftermarket", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1000000000", + "parentCode": null, + "name": "交通运输/仓储/物流", + "en_name": "Transportation & Warehousing", + "deleted": false, + "sublist": [ + { + "code": "1000000000", + "parentCode": "1000000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "1000010000", + "parentCode": "1000000000", + "name": "火车站/港口/汽车站/路政", + "en_name": "L& Transportation for Passengers", + "deleted": false, + "sublist": [] + }, + { + "code": "1000030000", + "parentCode": "1000000000", + "name": "客运服务", + "en_name": "Passenger Services", + "deleted": false, + "sublist": [] + }, + { + "code": "1000020000", + "parentCode": "1000000000", + "name": "货运/物流/仓储", + "en_name": "Freight or Logistics or Warehousing", + "deleted": false, + "sublist": [] + }, + { + "code": "1000040000", + "parentCode": "1000000000", + "name": "邮政/快递", + "en_name": "Postal & Express Services", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "800000000", + "parentCode": null, + "name": "专业服务", + "en_name": "Professional Services", + "deleted": false, + "sublist": [ + { + "code": "800000000", + "parentCode": "800000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "800020000", + "parentCode": "800000000", + "name": "法律服务", + "en_name": "Legal Services", + "deleted": false, + "sublist": [] + }, + { + "code": "800120000", + "parentCode": "800000000", + "name": "咨询服务", + "en_name": "Consultancy", + "deleted": false, + "sublist": [] + }, + { + "code": "800140000", + "parentCode": "800000000", + "name": "翻译服务", + "en_name": "Translation services", + "deleted": false, + "sublist": [] + }, + { + "code": "800080000", + "parentCode": "800000000", + "name": "人力资源服务", + "en_name": "Human Resources", + "deleted": false, + "sublist": [] + }, + { + "code": "800010000", + "parentCode": "800000000", + "name": "财务/审计/税务", + "en_name": "Accounting/Auditing/Tax", + "deleted": false, + "sublist": [] + }, + { + "code": "800030000", + "parentCode": "800000000", + "name": "工程技术与设计服务", + "en_name": "Engineering & Related Technical Services", + "deleted": false, + "sublist": [] + }, + { + "code": "800060000", + "parentCode": "800000000", + "name": "检测/认证", + "en_name": "Testing/Certification", + "deleted": false, + "sublist": [] + }, + { + "code": "800070000", + "parentCode": "800000000", + "name": "景区/商业/市场等综合管理", + "en_name": "Public Property Management", + "deleted": false, + "sublist": [] + }, + { + "code": "800090000", + "parentCode": "800000000", + "name": "商业代理服务", + "en_name": "Agency Services", + "deleted": false, + "sublist": [] + }, + { + "code": "800100000", + "parentCode": "800000000", + "name": "专利/商标/知识产权", + "en_name": "Patents/Trademarks/Copyrights", + "deleted": false, + "sublist": [] + }, + { + "code": "800130000", + "parentCode": "800000000", + "name": "租赁服务", + "en_name": "Leasing Services", + "deleted": false, + "sublist": [] + }, + { + "code": "800110000", + "parentCode": "800000000", + "name": "专业技术服务", + "en_name": "Technique Services", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1500000000", + "parentCode": null, + "name": "生活服务", + "en_name": "Residential Services", + "deleted": false, + "sublist": [ + { + "code": "1500000000", + "parentCode": "1500000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "1500010000", + "parentCode": "1500000000", + "name": "餐饮服务", + "en_name": "Catering Services", + "deleted": false, + "sublist": [] + }, + { + "code": "1500020000", + "parentCode": "1500000000", + "name": "酒店/民宿", + "en_name": "Accommodation", + "deleted": false, + "sublist": [] + }, + { + "code": "1500040000", + "parentCode": "1500000000", + "name": "旅游服务", + "en_name": "Tourism Services", + "deleted": false, + "sublist": [] + }, + { + "code": "1500060000", + "parentCode": "1500000000", + "name": "婚庆/摄影", + "en_name": "Wedding or photography", + "deleted": false, + "sublist": [] + }, + { + "code": "1500050000", + "parentCode": "1500000000", + "name": "美发/美容/保健", + "en_name": "Hairdressing or Beauty or Healthcare", + "deleted": false, + "sublist": [] + }, + { + "code": "1500000900", + "parentCode": "1500000000", + "name": "宠物服务", + "en_name": "Pet Services", + "deleted": false, + "sublist": [] + }, + { + "code": "1500001000", + "parentCode": "1500000000", + "name": "家政服务", + "en_name": "Housekeeping Services", + "deleted": false, + "sublist": [] + }, + { + "code": "1500001100", + "parentCode": "1500000000", + "name": "回收/维修", + "en_name": "Recycling or Maintenance", + "deleted": false, + "sublist": [] + }, + { + "code": "1500000800", + "parentCode": "1500000000", + "name": "休闲/娱乐", + "en_name": "Leisure or Amusement", + "deleted": false, + "sublist": [] + }, + { + "code": "1500000700", + "parentCode": "1500000000", + "name": "搬家/生活配送", + "en_name": "Moving or Living Delivery", + "deleted": false, + "sublist": [] + }, + { + "code": "1500030000", + "parentCode": "1500000000", + "name": "居民服务", + "en_name": "Personal Care & Services", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1100000000", + "parentCode": null, + "name": "能源/环保/矿产", + "en_name": "Mining, Quarrying, & Environmental Protection", + "deleted": false, + "sublist": [ + { + "code": "1100000000", + "parentCode": "1100000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "1100040000", + "parentCode": "1100000000", + "name": "石油/石化", + "en_name": "Petroleum & Petrochemical", + "deleted": false, + "sublist": [] + }, + { + "code": "1100060000", + "parentCode": "1100000000", + "name": "化工", + "en_name": "Chemical industry", + "deleted": false, + "sublist": [] + }, + { + "code": "1100010000", + "parentCode": "1100000000", + "name": "电力/水利/热力/燃气", + "en_name": "Electricity/Water Supply/Gas/Heating", + "deleted": false, + "sublist": [] + }, + { + "code": "1100050000", + "parentCode": "1100000000", + "name": "新能源", + "en_name": "Clean Energy", + "deleted": false, + "sublist": [] + }, + { + "code": "1100020000", + "parentCode": "1100000000", + "name": "环保", + "en_name": "Environmental Protection", + "deleted": false, + "sublist": [] + }, + { + "code": "1100030000", + "parentCode": "1100000000", + "name": "矿产/采掘", + "en_name": "Mining", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "1400000000", + "parentCode": null, + "name": "政府/非盈利机构", + "en_name": "Government or nonprofit", + "deleted": false, + "sublist": [ + { + "code": "1400000000", + "parentCode": "1400000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "1400010000", + "parentCode": "1400000000", + "name": "政府/公共事业", + "en_name": "Government or Public Services", + "deleted": false, + "sublist": [] + }, + { + "code": "1400020000", + "parentCode": "1400000000", + "name": "社团/组织/社会保障", + "en_name": "Non-profit Organizations", + "deleted": false, + "sublist": [] + }, + { + "code": "1300020000", + "parentCode": "1400000000", + "name": "养老/孤儿/看护", + "en_name": "Social Assistance", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "600000000", + "parentCode": null, + "name": "农/林/牧/渔", + "en_name": "Agriculture or forestry or animal husbandry or fishing", + "deleted": false, + "sublist": [ + { + "code": "600000000", + "parentCode": "600000000", + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "600010000", + "parentCode": "600000000", + "name": "农/林/牧/渔", + "en_name": "Agriculture or forestry or animal husbandry or fishing", + "deleted": false, + "sublist": [] + } + ] + } + ], + "allCity": [ + { + "code": "541", + "parentCode": "489", + "name": "安徽", + "en_name": "ANHUI", + "deleted": false, + "sublist": [ + { + "code": "671", + "parentCode": "541", + "name": "安庆", + "en_name": "ANQING", + "deleted": false, + "sublist": [ + { + "code": "10182", + "parentCode": "671", + "name": "宿松县", + "en_name": "SUSONG", + "deleted": false, + "sublist": [] + }, + { + "code": "2600", + "parentCode": "671", + "name": "大观区", + "en_name": "Daguan", + "deleted": false, + "sublist": [] + }, + { + "code": "2603", + "parentCode": "671", + "name": "怀宁县", + "en_name": "Huaining", + "deleted": false, + "sublist": [] + }, + { + "code": "2605", + "parentCode": "671", + "name": "潜山市", + "en_name": "Qianshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2606", + "parentCode": "671", + "name": "太湖县", + "en_name": "Taihu", + "deleted": false, + "sublist": [] + }, + { + "code": "2602", + "parentCode": "671", + "name": "桐城市", + "en_name": "Tongcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2608", + "parentCode": "671", + "name": "望江县", + "en_name": "Wangjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2599", + "parentCode": "671", + "name": "迎江区", + "en_name": "Yingjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2601", + "parentCode": "671", + "name": "宜秀区", + "en_name": "Yixiu", + "deleted": false, + "sublist": [] + }, + { + "code": "2609", + "parentCode": "671", + "name": "岳西县", + "en_name": "Yuexi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "666", + "parentCode": "541", + "name": "蚌埠", + "en_name": "BENGBU", + "deleted": false, + "sublist": [ + { + "code": "2577", + "parentCode": "666", + "name": "蚌山区", + "en_name": "Bangshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2575", + "parentCode": "666", + "name": "固镇县", + "en_name": "Guzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "2574", + "parentCode": "666", + "name": "淮上区", + "en_name": "Huaishang", + "deleted": false, + "sublist": [] + }, + { + "code": "2578", + "parentCode": "666", + "name": "怀远县", + "en_name": "Huaiyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2572", + "parentCode": "666", + "name": "龙子湖区", + "en_name": "Longzihu", + "deleted": false, + "sublist": [] + }, + { + "code": "2576", + "parentCode": "666", + "name": "五河县", + "en_name": "Wuhe", + "deleted": false, + "sublist": [] + }, + { + "code": "2573", + "parentCode": "666", + "name": "禹会区", + "en_name": "Yuhui", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "678", + "parentCode": "541", + "name": "亳州", + "en_name": "BOZHOU", + "deleted": false, + "sublist": [ + { + "code": "2648", + "parentCode": "678", + "name": "利辛县", + "en_name": "Lixin", + "deleted": false, + "sublist": [] + }, + { + "code": "2646", + "parentCode": "678", + "name": "蒙城县", + "en_name": "Mengcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2645", + "parentCode": "678", + "name": "谯城区", + "en_name": "Qiaocheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2647", + "parentCode": "678", + "name": "涡阳县", + "en_name": "Guoyang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "679", + "parentCode": "541", + "name": "池州", + "en_name": "CHIZHOU", + "deleted": false, + "sublist": [ + { + "code": "2649", + "parentCode": "679", + "name": "东至县", + "en_name": "Dongzhi", + "deleted": false, + "sublist": [] + }, + { + "code": "2652", + "parentCode": "679", + "name": "贵池区", + "en_name": "Guichi", + "deleted": false, + "sublist": [] + }, + { + "code": "2651", + "parentCode": "679", + "name": "青阳县", + "en_name": "Qingyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2650", + "parentCode": "679", + "name": "石台县", + "en_name": "Shitai", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "673", + "parentCode": "541", + "name": "滁州", + "en_name": "CHUZHOU", + "deleted": false, + "sublist": [ + { + "code": "10069", + "parentCode": "673", + "name": "凤阳县", + "en_name": "FENGYANG", + "deleted": false, + "sublist": [] + }, + { + "code": "2621", + "parentCode": "673", + "name": "定远县", + "en_name": "Dingyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2619", + "parentCode": "673", + "name": "来安县", + "en_name": "Laian", + "deleted": false, + "sublist": [] + }, + { + "code": "2624", + "parentCode": "673", + "name": "琅琊区", + "en_name": "Langya", + "deleted": false, + "sublist": [] + }, + { + "code": "2618", + "parentCode": "673", + "name": "明光市", + "en_name": "Mingguang", + "deleted": false, + "sublist": [] + }, + { + "code": "2623", + "parentCode": "673", + "name": "南谯区", + "en_name": "Nanzuo", + "deleted": false, + "sublist": [] + }, + { + "code": "2620", + "parentCode": "673", + "name": "全椒县", + "en_name": "Quanjiao", + "deleted": false, + "sublist": [] + }, + { + "code": "2617", + "parentCode": "673", + "name": "天长市", + "en_name": "Tianchang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "674", + "parentCode": "541", + "name": "阜阳", + "en_name": "FUYANG", + "deleted": false, + "sublist": [ + { + "code": "2631", + "parentCode": "674", + "name": "阜南县", + "en_name": "Funan", + "deleted": false, + "sublist": [] + }, + { + "code": "2628", + "parentCode": "674", + "name": "界首市", + "en_name": "Jieshou", + "deleted": false, + "sublist": [] + }, + { + "code": "2632", + "parentCode": "674", + "name": "临泉县", + "en_name": "Linquan", + "deleted": false, + "sublist": [] + }, + { + "code": "2629", + "parentCode": "674", + "name": "太和县", + "en_name": "Taihe", + "deleted": false, + "sublist": [] + }, + { + "code": "2627", + "parentCode": "674", + "name": "颍东区", + "en_name": "Yidong", + "deleted": false, + "sublist": [] + }, + { + "code": "2625", + "parentCode": "674", + "name": "颍泉区", + "en_name": "Yingquan", + "deleted": false, + "sublist": [] + }, + { + "code": "2630", + "parentCode": "674", + "name": "颍上县", + "en_name": "Yingshang", + "deleted": false, + "sublist": [] + }, + { + "code": "2626", + "parentCode": "674", + "name": "颍州区", + "en_name": "Yingzhou", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "664", + "parentCode": "541", + "name": "合肥", + "en_name": "HEFEI", + "deleted": false, + "sublist": [ + { + "code": "2355", + "parentCode": "664", + "name": "包河区", + "en_name": "Baohe", + "deleted": false, + "sublist": [] + }, + { + "code": "2438", + "parentCode": "664", + "name": "北城新区", + "en_name": "Beichengxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2357", + "parentCode": "664", + "name": "滨湖新区", + "en_name": "Binhuxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3277", + "parentCode": "664", + "name": "巢湖市", + "en_name": "Chaohushi", + "deleted": false, + "sublist": [] + }, + { + "code": "3273", + "parentCode": "664", + "name": "肥东县", + "en_name": "Feidongxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3274", + "parentCode": "664", + "name": "肥西县", + "en_name": "Feixixian", + "deleted": false, + "sublist": [] + }, + { + "code": "2359", + "parentCode": "664", + "name": "高新区", + "en_name": "Gaoxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2356", + "parentCode": "664", + "name": "经济技术开发区", + "en_name": "Jingjijishukaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3276", + "parentCode": "664", + "name": "庐江县", + "en_name": "Lujiangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "2352", + "parentCode": "664", + "name": "庐阳区", + "en_name": "Luyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2354", + "parentCode": "664", + "name": "蜀山区", + "en_name": "Shushan", + "deleted": false, + "sublist": [] + }, + { + "code": "2358", + "parentCode": "664", + "name": "新站综合开发试验区", + "en_name": "Xinzhanzonghekaifashiyanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2353", + "parentCode": "664", + "name": "瑶海区", + "en_name": "Yaohai", + "deleted": false, + "sublist": [] + }, + { + "code": "3275", + "parentCode": "664", + "name": "长丰县", + "en_name": "Changfengxian", + "deleted": false, + "sublist": [] + }, + { + "code": "2437", + "parentCode": "664", + "name": "政务文化新区", + "en_name": "Zhengwuwenhuaqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "669", + "parentCode": "541", + "name": "淮北", + "en_name": "HUAIBEI", + "deleted": false, + "sublist": [ + { + "code": "2594", + "parentCode": "669", + "name": "杜集区", + "en_name": "Duji", + "deleted": false, + "sublist": [] + }, + { + "code": "2593", + "parentCode": "669", + "name": "烈山区", + "en_name": "Lieshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2591", + "parentCode": "669", + "name": "濉溪县", + "en_name": "Suixi", + "deleted": false, + "sublist": [] + }, + { + "code": "2592", + "parentCode": "669", + "name": "相山区", + "en_name": "Xiangshan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "667", + "parentCode": "541", + "name": "淮南", + "en_name": "HUAINAN", + "deleted": false, + "sublist": [ + { + "code": "2584", + "parentCode": "667", + "name": "八公山区", + "en_name": "Bagongshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2582", + "parentCode": "667", + "name": "大通区", + "en_name": "Datong", + "deleted": false, + "sublist": [] + }, + { + "code": "2579", + "parentCode": "667", + "name": "凤台县", + "en_name": "Fengtai", + "deleted": false, + "sublist": [] + }, + { + "code": "2583", + "parentCode": "667", + "name": "潘集区", + "en_name": "Panji", + "deleted": false, + "sublist": [] + }, + { + "code": "2640", + "parentCode": "667", + "name": "寿县", + "en_name": "Shou", + "deleted": false, + "sublist": [] + }, + { + "code": "2580", + "parentCode": "667", + "name": "田家庵区", + "en_name": "Tianjiaan", + "deleted": false, + "sublist": [] + }, + { + "code": "2581", + "parentCode": "667", + "name": "谢家集区", + "en_name": "Xiejiaan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "672", + "parentCode": "541", + "name": "黄山", + "en_name": "HUANGSHAN", + "deleted": false, + "sublist": [ + { + "code": "2611", + "parentCode": "672", + "name": "黄山区", + "en_name": "Huangshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2612", + "parentCode": "672", + "name": "徽州区", + "en_name": "Huizhou", + "deleted": false, + "sublist": [] + }, + { + "code": "2616", + "parentCode": "672", + "name": "祁门县", + "en_name": "Qimen", + "deleted": false, + "sublist": [] + }, + { + "code": "2613", + "parentCode": "672", + "name": "歙县", + "en_name": "Zuo", + "deleted": false, + "sublist": [] + }, + { + "code": "2610", + "parentCode": "672", + "name": "屯溪区", + "en_name": "Tunxi", + "deleted": false, + "sublist": [] + }, + { + "code": "2614", + "parentCode": "672", + "name": "休宁县", + "en_name": "Xiuning", + "deleted": false, + "sublist": [] + }, + { + "code": "2615", + "parentCode": "672", + "name": "黟县", + "en_name": "Yi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "677", + "parentCode": "541", + "name": "六安", + "en_name": "LIUAN", + "deleted": false, + "sublist": [ + { + "code": "2641", + "parentCode": "677", + "name": "霍邱县", + "en_name": "Huoqiu", + "deleted": false, + "sublist": [] + }, + { + "code": "2644", + "parentCode": "677", + "name": "霍山县", + "en_name": "Huoshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2638", + "parentCode": "677", + "name": "金安区", + "en_name": "Jinan", + "deleted": false, + "sublist": [] + }, + { + "code": "2643", + "parentCode": "677", + "name": "金寨县", + "en_name": "Jinzhai", + "deleted": false, + "sublist": [] + }, + { + "code": "2642", + "parentCode": "677", + "name": "舒城县", + "en_name": "Shucheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2639", + "parentCode": "677", + "name": "裕安区", + "en_name": "Yuan", + "deleted": false, + "sublist": [] + }, + { + "code": "104021", + "parentCode": "677", + "name": "叶集区", + "en_name": "yejiqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "668", + "parentCode": "541", + "name": "马鞍山", + "en_name": "MAANSHAN", + "deleted": false, + "sublist": [ + { + "code": "2587", + "parentCode": "668", + "name": "博望区", + "en_name": "Bowang", + "deleted": false, + "sublist": [] + }, + { + "code": "2588", + "parentCode": "668", + "name": "当涂县", + "en_name": "Dangtu", + "deleted": false, + "sublist": [] + }, + { + "code": "2589", + "parentCode": "668", + "name": "含山县", + "en_name": "Hanshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2590", + "parentCode": "668", + "name": "和县", + "en_name": "He", + "deleted": false, + "sublist": [] + }, + { + "code": "2585", + "parentCode": "668", + "name": "花山区", + "en_name": "Huashan", + "deleted": false, + "sublist": [] + }, + { + "code": "2586", + "parentCode": "668", + "name": "雨山区", + "en_name": "Yushan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "675", + "parentCode": "541", + "name": "宿州", + "en_name": "SUZHOU", + "deleted": false, + "sublist": [ + { + "code": "2634", + "parentCode": "675", + "name": "砀山县", + "en_name": "Dangshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2636", + "parentCode": "675", + "name": "灵璧县", + "en_name": "Lingbi", + "deleted": false, + "sublist": [] + }, + { + "code": "2637", + "parentCode": "675", + "name": "泗县", + "en_name": "Si", + "deleted": false, + "sublist": [] + }, + { + "code": "2635", + "parentCode": "675", + "name": "萧县", + "en_name": "Xiao", + "deleted": false, + "sublist": [] + }, + { + "code": "2633", + "parentCode": "675", + "name": "埇桥区", + "en_name": "Yongqiao", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "670", + "parentCode": "541", + "name": "铜陵", + "en_name": "TONGLING", + "deleted": false, + "sublist": [ + { + "code": "2597", + "parentCode": "670", + "name": "郊区", + "en_name": "Jiao", + "deleted": false, + "sublist": [] + }, + { + "code": "2595", + "parentCode": "670", + "name": "铜官区", + "en_name": "Tongguanshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2598", + "parentCode": "670", + "name": "义安区", + "en_name": "Tongling", + "deleted": false, + "sublist": [] + }, + { + "code": "2604", + "parentCode": "670", + "name": "枞阳县", + "en_name": "Zuoyang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "665", + "parentCode": "541", + "name": "芜湖", + "en_name": "WUHU", + "deleted": false, + "sublist": [ + { + "code": "2569", + "parentCode": "665", + "name": "繁昌区", + "en_name": "fanchangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2564", + "parentCode": "665", + "name": "镜湖区", + "en_name": "Jinghu", + "deleted": false, + "sublist": [] + }, + { + "code": "2566", + "parentCode": "665", + "name": "鸠江区", + "en_name": "Jiujiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2570", + "parentCode": "665", + "name": "南陵县", + "en_name": "Nanling", + "deleted": false, + "sublist": [] + }, + { + "code": "2567", + "parentCode": "665", + "name": "三山区", + "en_name": "Sanshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2568", + "parentCode": "665", + "name": "湾沚区", + "en_name": "wanzhiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2571", + "parentCode": "665", + "name": "无为市", + "en_name": "Wuwei", + "deleted": false, + "sublist": [] + }, + { + "code": "2565", + "parentCode": "665", + "name": "弋江区", + "en_name": "Yijiang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "680", + "parentCode": "541", + "name": "宣城", + "en_name": "XUANCHENG", + "deleted": false, + "sublist": [ + { + "code": "10181", + "parentCode": "680", + "name": "广德市", + "en_name": "GUANGDE", + "deleted": false, + "sublist": [] + }, + { + "code": "2655", + "parentCode": "680", + "name": "旌德县", + "en_name": "Jingde", + "deleted": false, + "sublist": [] + }, + { + "code": "2658", + "parentCode": "680", + "name": "泾县", + "en_name": "Jing", + "deleted": false, + "sublist": [] + }, + { + "code": "2659", + "parentCode": "680", + "name": "绩溪县", + "en_name": "Jixi", + "deleted": false, + "sublist": [] + }, + { + "code": "2656", + "parentCode": "680", + "name": "郎溪县", + "en_name": "Langxi", + "deleted": false, + "sublist": [] + }, + { + "code": "2654", + "parentCode": "680", + "name": "宁国市", + "en_name": "Ningguo", + "deleted": false, + "sublist": [] + }, + { + "code": "2653", + "parentCode": "680", + "name": "宣州区", + "en_name": "Xuanzhou", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "562", + "parentCode": "489", + "name": "澳门", + "en_name": "MACAO", + "deleted": false, + "sublist": [] + }, + { + "code": "530", + "parentCode": "489", + "name": "北京", + "en_name": "BEIJING", + "deleted": false, + "sublist": [ + { + "code": "2013", + "parentCode": "530", + "name": "昌平区", + "en_name": "Changping", + "deleted": false, + "sublist": [] + }, + { + "code": "2006", + "parentCode": "530", + "name": "朝阳区", + "en_name": "CHAOYANGQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2012", + "parentCode": "530", + "name": "大兴区", + "en_name": "Daxing", + "deleted": false, + "sublist": [] + }, + { + "code": "2001", + "parentCode": "530", + "name": "东城区", + "en_name": "Dongcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2011", + "parentCode": "530", + "name": "房山区", + "en_name": "Fangshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2007", + "parentCode": "530", + "name": "丰台区", + "en_name": "Fengtai", + "deleted": false, + "sublist": [] + }, + { + "code": "2005", + "parentCode": "530", + "name": "海淀区", + "en_name": "Haidian", + "deleted": false, + "sublist": [] + }, + { + "code": "2014", + "parentCode": "530", + "name": "怀柔区", + "en_name": "Huairou", + "deleted": false, + "sublist": [] + }, + { + "code": "2016", + "parentCode": "530", + "name": "门头沟区", + "en_name": "Mentougou", + "deleted": false, + "sublist": [] + }, + { + "code": "2017", + "parentCode": "530", + "name": "密云区", + "en_name": "Miyun", + "deleted": false, + "sublist": [] + }, + { + "code": "2015", + "parentCode": "530", + "name": "平谷区", + "en_name": "Pinggu", + "deleted": false, + "sublist": [] + }, + { + "code": "2008", + "parentCode": "530", + "name": "石景山区", + "en_name": "Shijingshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2010", + "parentCode": "530", + "name": "顺义区", + "en_name": "Shunyi", + "deleted": false, + "sublist": [] + }, + { + "code": "2009", + "parentCode": "530", + "name": "通州区", + "en_name": "Tongzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "2002", + "parentCode": "530", + "name": "西城区", + "en_name": "Xicheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2018", + "parentCode": "530", + "name": "延庆区", + "en_name": "Yanqing", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "551", + "parentCode": "489", + "name": "重庆", + "en_name": "CHONGQING", + "deleted": false, + "sublist": [ + { + "code": "2319", + "parentCode": "551", + "name": "巴南区", + "en_name": "Banan", + "deleted": false, + "sublist": [] + }, + { + "code": "2320", + "parentCode": "551", + "name": "北碚区", + "en_name": "Beibei", + "deleted": false, + "sublist": [] + }, + { + "code": "2360", + "parentCode": "551", + "name": "北部新区", + "en_name": "Beibuxin", + "deleted": false, + "sublist": [] + }, + { + "code": "2333", + "parentCode": "551", + "name": "璧山区", + "en_name": "Bishan", + "deleted": false, + "sublist": [] + }, + { + "code": "2346", + "parentCode": "551", + "name": "城口县", + "en_name": "Chengkou", + "deleted": false, + "sublist": [] + }, + { + "code": "2317", + "parentCode": "551", + "name": "大渡口区", + "en_name": "Dadukou", + "deleted": false, + "sublist": [] + }, + { + "code": "2332", + "parentCode": "551", + "name": "大足区", + "en_name": "Dazu", + "deleted": false, + "sublist": [] + }, + { + "code": "2341", + "parentCode": "551", + "name": "垫江县", + "en_name": "Dianjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2342", + "parentCode": "551", + "name": "丰都县", + "en_name": "Fengdu", + "deleted": false, + "sublist": [] + }, + { + "code": "2343", + "parentCode": "551", + "name": "奉节县", + "en_name": "Fengjie", + "deleted": false, + "sublist": [] + }, + { + "code": "2324", + "parentCode": "551", + "name": "涪陵区", + "en_name": "Fuling", + "deleted": false, + "sublist": [] + }, + { + "code": "2327", + "parentCode": "551", + "name": "合川区", + "en_name": "Hechuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2313", + "parentCode": "551", + "name": "江北区", + "en_name": "Jiangbei", + "deleted": false, + "sublist": [] + }, + { + "code": "2326", + "parentCode": "551", + "name": "江津区", + "en_name": "Jiangjin", + "deleted": false, + "sublist": [] + }, + { + "code": "2316", + "parentCode": "551", + "name": "九龙坡区", + "en_name": "Jiulongpo", + "deleted": false, + "sublist": [] + }, + { + "code": "2338", + "parentCode": "551", + "name": "开州区", + "en_name": "Kai", + "deleted": false, + "sublist": [] + }, + { + "code": "2340", + "parentCode": "551", + "name": "梁平区", + "en_name": "Liangping", + "deleted": false, + "sublist": [] + }, + { + "code": "2314", + "parentCode": "551", + "name": "南岸区", + "en_name": "Nanan", + "deleted": false, + "sublist": [] + }, + { + "code": "2330", + "parentCode": "551", + "name": "南川区", + "en_name": "Nanchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2436", + "parentCode": "551", + "name": "彭水苗族土家族自治县", + "en_name": "Pengshui", + "deleted": false, + "sublist": [] + }, + { + "code": "2322", + "parentCode": "551", + "name": "黔江区", + "en_name": "Qianjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2336", + "parentCode": "551", + "name": "綦江区", + "en_name": "Qijiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2331", + "parentCode": "551", + "name": "荣昌区", + "en_name": "Rongchang", + "deleted": false, + "sublist": [] + }, + { + "code": "2315", + "parentCode": "551", + "name": "沙坪坝区", + "en_name": "Shapingba", + "deleted": false, + "sublist": [] + }, + { + "code": "2433", + "parentCode": "551", + "name": "石柱土家族自治县", + "en_name": "Shizhu", + "deleted": false, + "sublist": [] + }, + { + "code": "2328", + "parentCode": "551", + "name": "双桥区", + "en_name": "Shuangqiao", + "deleted": false, + "sublist": [] + }, + { + "code": "2334", + "parentCode": "551", + "name": "铜梁区", + "en_name": "Tongliang", + "deleted": false, + "sublist": [] + }, + { + "code": "2335", + "parentCode": "551", + "name": "潼南区", + "en_name": "Tongnan", + "deleted": false, + "sublist": [] + }, + { + "code": "2329", + "parentCode": "551", + "name": "万盛区", + "en_name": "Wansheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2321", + "parentCode": "551", + "name": "万州区", + "en_name": "Wanzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "2347", + "parentCode": "551", + "name": "武隆区", + "en_name": "Wulong", + "deleted": false, + "sublist": [] + }, + { + "code": "2344", + "parentCode": "551", + "name": "巫山县", + "en_name": "Wushan", + "deleted": false, + "sublist": [] + }, + { + "code": "2345", + "parentCode": "551", + "name": "巫溪县", + "en_name": "Wuxi", + "deleted": false, + "sublist": [] + }, + { + "code": "2434", + "parentCode": "551", + "name": "秀山土家族苗族自治县", + "en_name": "Xiushan", + "deleted": false, + "sublist": [] + }, + { + "code": "2323", + "parentCode": "551", + "name": "永川区", + "en_name": "Yongchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2435", + "parentCode": "551", + "name": "酉阳土家族苗族自治县", + "en_name": "Youyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2318", + "parentCode": "551", + "name": "渝北区", + "en_name": "Yubei", + "deleted": false, + "sublist": [] + }, + { + "code": "2339", + "parentCode": "551", + "name": "云阳县", + "en_name": "Yunyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2312", + "parentCode": "551", + "name": "渝中区", + "en_name": "Yuzhong", + "deleted": false, + "sublist": [] + }, + { + "code": "2325", + "parentCode": "551", + "name": "长寿区", + "en_name": "Changshou", + "deleted": false, + "sublist": [] + }, + { + "code": "2337", + "parentCode": "551", + "name": "忠县", + "en_name": "Zhong", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "542", + "parentCode": "489", + "name": "福建", + "en_name": "FUJIAN", + "deleted": false, + "sublist": [ + { + "code": "681", + "parentCode": "542", + "name": "福州", + "en_name": "FUZHOU", + "deleted": false, + "sublist": [ + { + "code": "2253", + "parentCode": "681", + "name": "仓山区", + "en_name": "Cangshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2473", + "parentCode": "681", + "name": "福清市", + "en_name": "FUQING", + "deleted": false, + "sublist": [] + }, + { + "code": "2251", + "parentCode": "681", + "name": "鼓楼区", + "en_name": "Gulou", + "deleted": false, + "sublist": [] + }, + { + "code": "2255", + "parentCode": "681", + "name": "晋安区", + "en_name": "Jinan", + "deleted": false, + "sublist": [] + }, + { + "code": "2258", + "parentCode": "681", + "name": "连江县", + "en_name": "Lianjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2257", + "parentCode": "681", + "name": "罗源县", + "en_name": "Luoyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2254", + "parentCode": "681", + "name": "马尾区", + "en_name": "Mawei", + "deleted": false, + "sublist": [] + }, + { + "code": "2256", + "parentCode": "681", + "name": "闽侯县", + "en_name": "Minhou", + "deleted": false, + "sublist": [] + }, + { + "code": "2260", + "parentCode": "681", + "name": "闽清县", + "en_name": "Minqing", + "deleted": false, + "sublist": [] + }, + { + "code": "2261", + "parentCode": "681", + "name": "平潭县", + "en_name": "Pingtan", + "deleted": false, + "sublist": [] + }, + { + "code": "2252", + "parentCode": "681", + "name": "台江区", + "en_name": "Taijiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2259", + "parentCode": "681", + "name": "永泰县", + "en_name": "Yongtai", + "deleted": false, + "sublist": [] + }, + { + "code": "2472", + "parentCode": "681", + "name": "长乐区", + "en_name": "CHANGLE", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "689", + "parentCode": "542", + "name": "龙岩", + "en_name": "LONGYAN", + "deleted": false, + "sublist": [ + { + "code": "3603", + "parentCode": "689", + "name": "连城县", + "en_name": "Liancheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3601", + "parentCode": "689", + "name": "上杭县", + "en_name": "Shanghang", + "deleted": false, + "sublist": [] + }, + { + "code": "3602", + "parentCode": "689", + "name": "武平县", + "en_name": "Wuping", + "deleted": false, + "sublist": [] + }, + { + "code": "3597", + "parentCode": "689", + "name": "新罗区", + "en_name": "Xinluo", + "deleted": false, + "sublist": [] + }, + { + "code": "3600", + "parentCode": "689", + "name": "永定区", + "en_name": "Yongding", + "deleted": false, + "sublist": [] + }, + { + "code": "3598", + "parentCode": "689", + "name": "漳平市", + "en_name": "Zhangping", + "deleted": false, + "sublist": [] + }, + { + "code": "3599", + "parentCode": "689", + "name": "长汀县", + "en_name": "Changding", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "688", + "parentCode": "542", + "name": "南平", + "en_name": "NANPING", + "deleted": false, + "sublist": [ + { + "code": "3594", + "parentCode": "688", + "name": "光泽县", + "en_name": "Guangze", + "deleted": false, + "sublist": [] + }, + { + "code": "3590", + "parentCode": "688", + "name": "建瓯市", + "en_name": "Jianou", + "deleted": false, + "sublist": [] + }, + { + "code": "3591", + "parentCode": "688", + "name": "建阳区", + "en_name": "Jianyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3593", + "parentCode": "688", + "name": "浦城县", + "en_name": "Pucheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3588", + "parentCode": "688", + "name": "邵武市", + "en_name": "Shaowu", + "deleted": false, + "sublist": [] + }, + { + "code": "3592", + "parentCode": "688", + "name": "顺昌县", + "en_name": "Shunchang", + "deleted": false, + "sublist": [] + }, + { + "code": "3595", + "parentCode": "688", + "name": "松溪县", + "en_name": "Songxi", + "deleted": false, + "sublist": [] + }, + { + "code": "3589", + "parentCode": "688", + "name": "武夷山市", + "en_name": "Wuyishan", + "deleted": false, + "sublist": [] + }, + { + "code": "3587", + "parentCode": "688", + "name": "延平区", + "en_name": "Yanping", + "deleted": false, + "sublist": [] + }, + { + "code": "3596", + "parentCode": "688", + "name": "政和县", + "en_name": "Zhenghe", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "690", + "parentCode": "542", + "name": "宁德", + "en_name": "NINGDE", + "deleted": false, + "sublist": [ + { + "code": "3401", + "parentCode": "690", + "name": "福安市", + "en_name": "Fuan", + "deleted": false, + "sublist": [] + }, + { + "code": "3606", + "parentCode": "690", + "name": "福鼎市", + "en_name": "Fuding", + "deleted": false, + "sublist": [] + }, + { + "code": "3608", + "parentCode": "690", + "name": "古田县", + "en_name": "Gutian", + "deleted": false, + "sublist": [] + }, + { + "code": "3604", + "parentCode": "690", + "name": "蕉城区", + "en_name": "Jiaocheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3609", + "parentCode": "690", + "name": "屏南县", + "en_name": "Pingnan", + "deleted": false, + "sublist": [] + }, + { + "code": "3610", + "parentCode": "690", + "name": "寿宁县", + "en_name": "Shouning", + "deleted": false, + "sublist": [] + }, + { + "code": "3607", + "parentCode": "690", + "name": "霞浦县", + "en_name": "Xiapu", + "deleted": false, + "sublist": [] + }, + { + "code": "3612", + "parentCode": "690", + "name": "柘荣县", + "en_name": "Zherong", + "deleted": false, + "sublist": [] + }, + { + "code": "3611", + "parentCode": "690", + "name": "周宁县", + "en_name": "Zhouning", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "683", + "parentCode": "542", + "name": "莆田", + "en_name": "PUTIAN", + "deleted": false, + "sublist": [ + { + "code": "3546", + "parentCode": "683", + "name": "城厢区", + "en_name": "Chengxiang", + "deleted": false, + "sublist": [] + }, + { + "code": "3547", + "parentCode": "683", + "name": "涵江区", + "en_name": "Hanjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "3548", + "parentCode": "683", + "name": "荔城区", + "en_name": "Zhicheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3550", + "parentCode": "683", + "name": "仙游县", + "en_name": "Xianyou", + "deleted": false, + "sublist": [] + }, + { + "code": "3549", + "parentCode": "683", + "name": "秀屿区", + "en_name": "Xiuyu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "685", + "parentCode": "542", + "name": "泉州", + "en_name": "QUANZHOU", + "deleted": false, + "sublist": [ + { + "code": "3097", + "parentCode": "685", + "name": "安溪县", + "en_name": "Anxixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3099", + "parentCode": "685", + "name": "德化县", + "en_name": "Dehuaxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3094", + "parentCode": "685", + "name": "丰泽区", + "en_name": "Fengzequ", + "deleted": false, + "sublist": [] + }, + { + "code": "3096", + "parentCode": "685", + "name": "惠安县", + "en_name": "Huianxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3101", + "parentCode": "685", + "name": "晋江市", + "en_name": "Jinjiangshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3574", + "parentCode": "685", + "name": "金门县", + "en_name": "Jinmen", + "deleted": false, + "sublist": [] + }, + { + "code": "3093", + "parentCode": "685", + "name": "鲤城区", + "en_name": "Lichengqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3095", + "parentCode": "685", + "name": "洛江区", + "en_name": "Luojiangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3102", + "parentCode": "685", + "name": "南安市", + "en_name": "Nananshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3400", + "parentCode": "685", + "name": "泉港区", + "en_name": "Quangang", + "deleted": false, + "sublist": [] + }, + { + "code": "3100", + "parentCode": "685", + "name": "石狮市", + "en_name": "Shishishi", + "deleted": false, + "sublist": [] + }, + { + "code": "3098", + "parentCode": "685", + "name": "永春县", + "en_name": "Yongchunxian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "684", + "parentCode": "542", + "name": "三明", + "en_name": "SANMING", + "deleted": false, + "sublist": [ + { + "code": "3557", + "parentCode": "684", + "name": "大田县", + "en_name": "Datian", + "deleted": false, + "sublist": [] + }, + { + "code": "3560", + "parentCode": "684", + "name": "将乐县", + "en_name": "Jiangle", + "deleted": false, + "sublist": [] + }, + { + "code": "3562", + "parentCode": "684", + "name": "建宁县", + "en_name": "Jianning", + "deleted": false, + "sublist": [] + }, + { + "code": "3551", + "parentCode": "684", + "name": "梅列区", + "en_name": "Meilie", + "deleted": false, + "sublist": [] + }, + { + "code": "3554", + "parentCode": "684", + "name": "明溪县", + "en_name": "Mingxi", + "deleted": false, + "sublist": [] + }, + { + "code": "3556", + "parentCode": "684", + "name": "宁化县", + "en_name": "Ninghua", + "deleted": false, + "sublist": [] + }, + { + "code": "3555", + "parentCode": "684", + "name": "清流县", + "en_name": "Qingliu", + "deleted": false, + "sublist": [] + }, + { + "code": "3552", + "parentCode": "684", + "name": "三元区", + "en_name": "Sanyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "3559", + "parentCode": "684", + "name": "沙县区", + "en_name": "SHAXIANQU", + "deleted": false, + "sublist": [] + }, + { + "code": "3561", + "parentCode": "684", + "name": "泰宁县", + "en_name": "Taining", + "deleted": false, + "sublist": [] + }, + { + "code": "3553", + "parentCode": "684", + "name": "永安市", + "en_name": "Yongan", + "deleted": false, + "sublist": [] + }, + { + "code": "3558", + "parentCode": "684", + "name": "尤溪县", + "en_name": "Youxi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "682", + "parentCode": "542", + "name": "厦门", + "en_name": "XIAMEN", + "deleted": false, + "sublist": [ + { + "code": "2267", + "parentCode": "682", + "name": "海沧区", + "en_name": "Haicang", + "deleted": false, + "sublist": [] + }, + { + "code": "2265", + "parentCode": "682", + "name": "湖里区", + "en_name": "Huli", + "deleted": false, + "sublist": [] + }, + { + "code": "2266", + "parentCode": "682", + "name": "集美区", + "en_name": "Jimei", + "deleted": false, + "sublist": [] + }, + { + "code": "2264", + "parentCode": "682", + "name": "思明区", + "en_name": "Siming", + "deleted": false, + "sublist": [] + }, + { + "code": "2268", + "parentCode": "682", + "name": "同安区", + "en_name": "Tongan", + "deleted": false, + "sublist": [] + }, + { + "code": "2269", + "parentCode": "682", + "name": "翔安区", + "en_name": "Xiangan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "687", + "parentCode": "542", + "name": "漳州", + "en_name": "ZHANGZHOU", + "deleted": false, + "sublist": [ + { + "code": "3583", + "parentCode": "687", + "name": "东山县", + "en_name": "Dongshan", + "deleted": false, + "sublist": [] + }, + { + "code": "3586", + "parentCode": "687", + "name": "华安县", + "en_name": "Huaan", + "deleted": false, + "sublist": [] + }, + { + "code": "3578", + "parentCode": "687", + "name": "龙海区", + "en_name": "longhaiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3577", + "parentCode": "687", + "name": "龙文区", + "en_name": "Longwen", + "deleted": false, + "sublist": [] + }, + { + "code": "3584", + "parentCode": "687", + "name": "南靖县", + "en_name": "Nanjing", + "deleted": false, + "sublist": [] + }, + { + "code": "3585", + "parentCode": "687", + "name": "平和县", + "en_name": "Pinghe", + "deleted": false, + "sublist": [] + }, + { + "code": "3576", + "parentCode": "687", + "name": "芗城区", + "en_name": "Xiangcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3579", + "parentCode": "687", + "name": "云霄县", + "en_name": "Yunxiao", + "deleted": false, + "sublist": [] + }, + { + "code": "3580", + "parentCode": "687", + "name": "漳浦县", + "en_name": "Zhangpu", + "deleted": false, + "sublist": [] + }, + { + "code": "3582", + "parentCode": "687", + "name": "长泰区", + "en_name": "zhangtaiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3581", + "parentCode": "687", + "name": "诏安县", + "en_name": "Zhaoan", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "557", + "parentCode": "489", + "name": "甘肃", + "en_name": "GANSU", + "deleted": false, + "sublist": [ + { + "code": "867", + "parentCode": "557", + "name": "白银", + "en_name": "BAIYIN", + "deleted": false, + "sublist": [ + { + "code": "5062", + "parentCode": "867", + "name": "白银区", + "en_name": "Baiyin", + "deleted": false, + "sublist": [] + }, + { + "code": "5065", + "parentCode": "867", + "name": "会宁县", + "en_name": "Huining", + "deleted": false, + "sublist": [] + }, + { + "code": "5066", + "parentCode": "867", + "name": "景泰县", + "en_name": "Jingtai", + "deleted": false, + "sublist": [] + }, + { + "code": "5064", + "parentCode": "867", + "name": "靖远县", + "en_name": "Jingyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "5063", + "parentCode": "867", + "name": "平川区", + "en_name": "Pingchuan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "874", + "parentCode": "557", + "name": "定西", + "en_name": "DINGXI", + "deleted": false, + "sublist": [ + { + "code": "5106", + "parentCode": "874", + "name": "安定区", + "en_name": "Anding", + "deleted": false, + "sublist": [] + }, + { + "code": "5110", + "parentCode": "874", + "name": "临洮县", + "en_name": "Lintao", + "deleted": false, + "sublist": [] + }, + { + "code": "5108", + "parentCode": "874", + "name": "陇西县", + "en_name": "Longxi", + "deleted": false, + "sublist": [] + }, + { + "code": "5112", + "parentCode": "874", + "name": "岷县", + "en_name": "Min", + "deleted": false, + "sublist": [] + }, + { + "code": "5107", + "parentCode": "874", + "name": "通渭县", + "en_name": "Tongwei", + "deleted": false, + "sublist": [] + }, + { + "code": "5109", + "parentCode": "874", + "name": "渭源县", + "en_name": "Weiyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "5111", + "parentCode": "874", + "name": "漳县", + "en_name": "Zhang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "877", + "parentCode": "557", + "name": "甘南", + "en_name": "GANNAN", + "deleted": false, + "sublist": [ + { + "code": "5134", + "parentCode": "877", + "name": "迭部县", + "en_name": "Diebu", + "deleted": false, + "sublist": [] + }, + { + "code": "5130", + "parentCode": "877", + "name": "合作市", + "en_name": "Hezuo", + "deleted": false, + "sublist": [] + }, + { + "code": "5131", + "parentCode": "877", + "name": "临潭县", + "en_name": "Lintan", + "deleted": false, + "sublist": [] + }, + { + "code": "5136", + "parentCode": "877", + "name": "碌曲县", + "en_name": "Luqu", + "deleted": false, + "sublist": [] + }, + { + "code": "5135", + "parentCode": "877", + "name": "玛曲县", + "en_name": "Maqu", + "deleted": false, + "sublist": [] + }, + { + "code": "5137", + "parentCode": "877", + "name": "夏河县", + "en_name": "Xiahe", + "deleted": false, + "sublist": [] + }, + { + "code": "5133", + "parentCode": "877", + "name": "舟曲县", + "en_name": "Zhouqu", + "deleted": false, + "sublist": [] + }, + { + "code": "5132", + "parentCode": "877", + "name": "卓尼县", + "en_name": "Zhuoni", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "865", + "parentCode": "557", + "name": "嘉峪关", + "en_name": "JIAYUGUAN", + "deleted": false, + "sublist": [] + }, + { + "code": "866", + "parentCode": "557", + "name": "金昌", + "en_name": "JINCHANG", + "deleted": false, + "sublist": [ + { + "code": "5060", + "parentCode": "866", + "name": "金川区", + "en_name": "Jinchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "5061", + "parentCode": "866", + "name": "永昌县", + "en_name": "Yongchang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "872", + "parentCode": "557", + "name": "酒泉", + "en_name": "JIUQUAN", + "deleted": false, + "sublist": [ + { + "code": "5097", + "parentCode": "872", + "name": "阿克塞哈萨克族自治县", + "en_name": "Akesaihasakezuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5093", + "parentCode": "872", + "name": "敦煌市", + "en_name": "Dunhuang", + "deleted": false, + "sublist": [] + }, + { + "code": "5095", + "parentCode": "872", + "name": "瓜州县", + "en_name": "Guazhou", + "deleted": false, + "sublist": [] + }, + { + "code": "5094", + "parentCode": "872", + "name": "金塔县", + "en_name": "Jinta", + "deleted": false, + "sublist": [] + }, + { + "code": "5096", + "parentCode": "872", + "name": "肃北蒙古族自治县", + "en_name": "Subeineimengguzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5091", + "parentCode": "872", + "name": "肃州区", + "en_name": "Suzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "5092", + "parentCode": "872", + "name": "玉门市", + "en_name": "Yumen", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "864", + "parentCode": "557", + "name": "兰州", + "en_name": "LANZHOU", + "deleted": false, + "sublist": [ + { + "code": "3352", + "parentCode": "864", + "name": "安宁区", + "en_name": "Anningqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3350", + "parentCode": "864", + "name": "城关区", + "en_name": "Chengguanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3356", + "parentCode": "864", + "name": "皋兰县", + "en_name": "Gaolanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3354", + "parentCode": "864", + "name": "红古区", + "en_name": "Hongguqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3358", + "parentCode": "864", + "name": "兰州新区", + "en_name": "Lanzhouxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3351", + "parentCode": "864", + "name": "七里河区", + "en_name": "Qilihequ", + "deleted": false, + "sublist": [] + }, + { + "code": "3353", + "parentCode": "864", + "name": "西固区", + "en_name": "Xiguqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3355", + "parentCode": "864", + "name": "永登县", + "en_name": "Yongdengxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3357", + "parentCode": "864", + "name": "榆中县", + "en_name": "Yuzhongxian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "876", + "parentCode": "557", + "name": "临夏", + "en_name": "LINXIA", + "deleted": false, + "sublist": [ + { + "code": "5128", + "parentCode": "876", + "name": "东乡族自治县", + "en_name": "Donggzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5126", + "parentCode": "876", + "name": "广河县", + "en_name": "Guanghe", + "deleted": false, + "sublist": [] + }, + { + "code": "5127", + "parentCode": "876", + "name": "和政县", + "en_name": "Hezheng", + "deleted": false, + "sublist": [] + }, + { + "code": "5129", + "parentCode": "876", + "name": "积石山保安族东乡族撒拉族自治县", + "en_name": "Jishishanbaoanzudonggzusalazuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5124", + "parentCode": "876", + "name": "康乐县", + "en_name": "Kangle", + "deleted": false, + "sublist": [] + }, + { + "code": "5122", + "parentCode": "876", + "name": "临夏市", + "en_name": "Linxia", + "deleted": false, + "sublist": [] + }, + { + "code": "5123", + "parentCode": "876", + "name": "临夏县", + "en_name": "Linxia", + "deleted": false, + "sublist": [] + }, + { + "code": "5125", + "parentCode": "876", + "name": "永靖县", + "en_name": "Yongjing", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "875", + "parentCode": "557", + "name": "陇南", + "en_name": "LONGNAN", + "deleted": false, + "sublist": [ + { + "code": "5114", + "parentCode": "875", + "name": "成县", + "en_name": "Cheng", + "deleted": false, + "sublist": [] + }, + { + "code": "5116", + "parentCode": "875", + "name": "宕昌县", + "en_name": "Dangchang", + "deleted": false, + "sublist": [] + }, + { + "code": "5120", + "parentCode": "875", + "name": "徽县", + "en_name": "Hui", + "deleted": false, + "sublist": [] + }, + { + "code": "5117", + "parentCode": "875", + "name": "康县", + "en_name": "Kang", + "deleted": false, + "sublist": [] + }, + { + "code": "5121", + "parentCode": "875", + "name": "两当县", + "en_name": "Liangdang", + "deleted": false, + "sublist": [] + }, + { + "code": "5119", + "parentCode": "875", + "name": "礼县", + "en_name": "Li", + "deleted": false, + "sublist": [] + }, + { + "code": "5115", + "parentCode": "875", + "name": "文县", + "en_name": "Wen", + "deleted": false, + "sublist": [] + }, + { + "code": "5113", + "parentCode": "875", + "name": "武都区", + "en_name": "Wudu", + "deleted": false, + "sublist": [] + }, + { + "code": "5118", + "parentCode": "875", + "name": "西和县", + "en_name": "Xihe", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "871", + "parentCode": "557", + "name": "平凉", + "en_name": "PINGLIANG", + "deleted": false, + "sublist": [ + { + "code": "5087", + "parentCode": "871", + "name": "崇信县", + "en_name": "Chongxin", + "deleted": false, + "sublist": [] + }, + { + "code": "5088", + "parentCode": "871", + "name": "华亭市", + "en_name": "Huating", + "deleted": false, + "sublist": [] + }, + { + "code": "5085", + "parentCode": "871", + "name": "泾川县", + "en_name": "Jingchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "5090", + "parentCode": "871", + "name": "静宁县", + "en_name": "Jingning", + "deleted": false, + "sublist": [] + }, + { + "code": "5084", + "parentCode": "871", + "name": "崆峒区", + "en_name": "Kongtong", + "deleted": false, + "sublist": [] + }, + { + "code": "5086", + "parentCode": "871", + "name": "灵台县", + "en_name": "Lingtai", + "deleted": false, + "sublist": [] + }, + { + "code": "5089", + "parentCode": "871", + "name": "庄浪县", + "en_name": "Zhuanglang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "873", + "parentCode": "557", + "name": "庆阳", + "en_name": "QINGYANG", + "deleted": false, + "sublist": [ + { + "code": "5102", + "parentCode": "873", + "name": "合水县", + "en_name": "Heshui", + "deleted": false, + "sublist": [] + }, + { + "code": "5101", + "parentCode": "873", + "name": "华池县", + "en_name": "Huachi", + "deleted": false, + "sublist": [] + }, + { + "code": "5100", + "parentCode": "873", + "name": "环县", + "en_name": "Huan", + "deleted": false, + "sublist": [] + }, + { + "code": "5104", + "parentCode": "873", + "name": "宁县", + "en_name": "Ning", + "deleted": false, + "sublist": [] + }, + { + "code": "5099", + "parentCode": "873", + "name": "庆城县", + "en_name": "Qingcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "5098", + "parentCode": "873", + "name": "西峰区", + "en_name": "Xifeng", + "deleted": false, + "sublist": [] + }, + { + "code": "5103", + "parentCode": "873", + "name": "正宁县", + "en_name": "Zhengning", + "deleted": false, + "sublist": [] + }, + { + "code": "5105", + "parentCode": "873", + "name": "镇原县", + "en_name": "Zhenyuan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "868", + "parentCode": "557", + "name": "天水", + "en_name": "TIANSHUI", + "deleted": false, + "sublist": [ + { + "code": "5071", + "parentCode": "868", + "name": "甘谷县", + "en_name": "Gangu", + "deleted": false, + "sublist": [] + }, + { + "code": "5068", + "parentCode": "868", + "name": "麦积区", + "en_name": "Maiji", + "deleted": false, + "sublist": [] + }, + { + "code": "5070", + "parentCode": "868", + "name": "秦安县", + "en_name": "Qinan", + "deleted": false, + "sublist": [] + }, + { + "code": "5069", + "parentCode": "868", + "name": "清水县", + "en_name": "Qingshui", + "deleted": false, + "sublist": [] + }, + { + "code": "5067", + "parentCode": "868", + "name": "秦州区", + "en_name": "Qinzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "5072", + "parentCode": "868", + "name": "武山县", + "en_name": "Wushan", + "deleted": false, + "sublist": [] + }, + { + "code": "5073", + "parentCode": "868", + "name": "张家川回族自治县", + "en_name": "Zhangjiachuanhuizuzizhi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "869", + "parentCode": "557", + "name": "武威", + "en_name": "WUWEI", + "deleted": false, + "sublist": [ + { + "code": "5076", + "parentCode": "869", + "name": "古浪县", + "en_name": "Gulang", + "deleted": false, + "sublist": [] + }, + { + "code": "5074", + "parentCode": "869", + "name": "凉州区", + "en_name": "Liangzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "5075", + "parentCode": "869", + "name": "民勤县", + "en_name": "Minqin", + "deleted": false, + "sublist": [] + }, + { + "code": "5077", + "parentCode": "869", + "name": "天祝藏族自治县", + "en_name": "Tianzhuzangzuzizhizhou", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "870", + "parentCode": "557", + "name": "张掖", + "en_name": "ZHANGYE", + "deleted": false, + "sublist": [ + { + "code": "5078", + "parentCode": "870", + "name": "甘州区", + "en_name": "Ganzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "5082", + "parentCode": "870", + "name": "高台县", + "en_name": "Gaotai", + "deleted": false, + "sublist": [] + }, + { + "code": "5081", + "parentCode": "870", + "name": "临泽县", + "en_name": "Linze", + "deleted": false, + "sublist": [] + }, + { + "code": "5080", + "parentCode": "870", + "name": "民乐县", + "en_name": "Minle", + "deleted": false, + "sublist": [] + }, + { + "code": "5083", + "parentCode": "870", + "name": "山丹县", + "en_name": "Shandan", + "deleted": false, + "sublist": [] + }, + { + "code": "5079", + "parentCode": "870", + "name": "肃南裕固族自治县", + "en_name": "Sunanyuguzuzizhi", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "548", + "parentCode": "489", + "name": "广东", + "en_name": "GUANGDONG", + "deleted": false, + "sublist": [ + { + "code": "781", + "parentCode": "548", + "name": "潮州", + "en_name": "CHAOZHOU", + "deleted": false, + "sublist": [ + { + "code": "4358", + "parentCode": "781", + "name": "潮安区", + "en_name": "Chaoan", + "deleted": false, + "sublist": [] + }, + { + "code": "4359", + "parentCode": "781", + "name": "饶平县", + "en_name": "Raoping", + "deleted": false, + "sublist": [] + }, + { + "code": "4357", + "parentCode": "781", + "name": "湘桥区", + "en_name": "Xiangqiao", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "779", + "parentCode": "548", + "name": "东莞", + "en_name": "DONGGUAN", + "deleted": false, + "sublist": [ + { + "code": "104046", + "parentCode": "779", + "name": "东莞生态园", + "en_name": "dongguanshengtaiyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "104047", + "parentCode": "779", + "name": "虎门港管委会", + "en_name": "humenguangguanweihui", + "deleted": false, + "sublist": [] + }, + { + "code": "3233", + "parentCode": "779", + "name": "常平镇", + "en_name": "Changpingzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3226", + "parentCode": "779", + "name": "茶山镇", + "en_name": "Chashanzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3235", + "parentCode": "779", + "name": "大朗镇", + "en_name": "Dalangzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3240", + "parentCode": "779", + "name": "大岭山镇", + "en_name": "Dalingshanzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3246", + "parentCode": "779", + "name": "道滘镇", + "en_name": "Daojiaozhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3222", + "parentCode": "779", + "name": "东城街道", + "en_name": "dongchengjiedao", + "deleted": false, + "sublist": [] + }, + { + "code": "3232", + "parentCode": "779", + "name": "东坑镇", + "en_name": "Dongkengzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3250", + "parentCode": "779", + "name": "凤岗镇", + "en_name": "Fenggangzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3238", + "parentCode": "779", + "name": "高埗镇", + "en_name": "Gaobuzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3220", + "parentCode": "779", + "name": "莞城街道", + "en_name": "guanchengjiedao", + "deleted": false, + "sublist": [] + }, + { + "code": "3229", + "parentCode": "779", + "name": "横沥镇", + "en_name": "Henglizhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3243", + "parentCode": "779", + "name": "洪梅镇", + "en_name": "Hongmeizhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3249", + "parentCode": "779", + "name": "厚街镇", + "en_name": "Houjiezhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3242", + "parentCode": "779", + "name": "黄江镇", + "en_name": "Huangjiangzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3248", + "parentCode": "779", + "name": "虎门镇", + "en_name": "Humenzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3234", + "parentCode": "779", + "name": "寮步镇", + "en_name": "Liaobuzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3236", + "parentCode": "779", + "name": "麻涌镇", + "en_name": "Machongzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3221", + "parentCode": "779", + "name": "南城街道", + "en_name": "nanchengjiedao", + "deleted": false, + "sublist": [] + }, + { + "code": "3230", + "parentCode": "779", + "name": "桥头镇", + "en_name": "Qiaotouzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3244", + "parentCode": "779", + "name": "清溪镇", + "en_name": "Qingxizhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3228", + "parentCode": "779", + "name": "企石镇", + "en_name": "Qishizhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3245", + "parentCode": "779", + "name": "沙田镇", + "en_name": "Shatianzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3224", + "parentCode": "779", + "name": "石碣镇", + "en_name": "Shijiezhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3225", + "parentCode": "779", + "name": "石龙镇", + "en_name": "Shilongzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3227", + "parentCode": "779", + "name": "石排镇", + "en_name": "Shipaizhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3252", + "parentCode": "779", + "name": "松山湖管委会", + "en_name": "songshanhuguanweihui", + "deleted": false, + "sublist": [] + }, + { + "code": "3247", + "parentCode": "779", + "name": "塘厦镇", + "en_name": "Tangshazhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3241", + "parentCode": "779", + "name": "望牛墩镇", + "en_name": "Wangniudunzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3223", + "parentCode": "779", + "name": "万江街道", + "en_name": "wanjiangjiedao", + "deleted": false, + "sublist": [] + }, + { + "code": "3231", + "parentCode": "779", + "name": "谢岗镇", + "en_name": "Xiegangzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3251", + "parentCode": "779", + "name": "长安镇", + "en_name": "Changanzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3239", + "parentCode": "779", + "name": "樟木头镇", + "en_name": "Zhangmutouzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3237", + "parentCode": "779", + "name": "中堂镇", + "en_name": "Zhongtangzhen", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "768", + "parentCode": "548", + "name": "佛山", + "en_name": "FOSHAN", + "deleted": false, + "sublist": [ + { + "code": "2535", + "parentCode": "768", + "name": "高明区", + "en_name": "GAOMINGQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2562", + "parentCode": "768", + "name": "南海区", + "en_name": "NANHAIQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2534", + "parentCode": "768", + "name": "三水区", + "en_name": "SANSHUIQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2531", + "parentCode": "768", + "name": "禅城区", + "en_name": "CHANCHENGQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2560", + "parentCode": "768", + "name": "顺德区", + "en_name": "SHUNDEQU", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "763", + "parentCode": "548", + "name": "广州", + "en_name": "GUANGZHOU", + "deleted": false, + "sublist": [ + { + "code": "2049", + "parentCode": "763", + "name": "白云区", + "en_name": "Baiyun", + "deleted": false, + "sublist": [] + }, + { + "code": "2474", + "parentCode": "763", + "name": "从化区", + "en_name": "Conghua", + "deleted": false, + "sublist": [] + }, + { + "code": "2046", + "parentCode": "763", + "name": "海珠区", + "en_name": "Haizhu", + "deleted": false, + "sublist": [] + }, + { + "code": "2051", + "parentCode": "763", + "name": "花都区", + "en_name": "Huadu", + "deleted": false, + "sublist": [] + }, + { + "code": "2050", + "parentCode": "763", + "name": "黄埔区", + "en_name": "Huangpu", + "deleted": false, + "sublist": [] + }, + { + "code": "2047", + "parentCode": "763", + "name": "荔湾区", + "en_name": "Liwan", + "deleted": false, + "sublist": [] + }, + { + "code": "2054", + "parentCode": "763", + "name": "南沙区", + "en_name": "Nansha", + "deleted": false, + "sublist": [] + }, + { + "code": "2052", + "parentCode": "763", + "name": "番禺区", + "en_name": "Fanyu", + "deleted": false, + "sublist": [] + }, + { + "code": "2048", + "parentCode": "763", + "name": "天河区", + "en_name": "Tianhe", + "deleted": false, + "sublist": [] + }, + { + "code": "2045", + "parentCode": "763", + "name": "越秀区", + "en_name": "Yuexiu", + "deleted": false, + "sublist": [] + }, + { + "code": "2475", + "parentCode": "763", + "name": "增城区", + "en_name": "Zengcheng", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "776", + "parentCode": "548", + "name": "河源", + "en_name": "HEYUAN", + "deleted": false, + "sublist": [ + { + "code": "4340", + "parentCode": "776", + "name": "东源县", + "en_name": "Dongyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4339", + "parentCode": "776", + "name": "和平县", + "en_name": "Heping", + "deleted": false, + "sublist": [] + }, + { + "code": "4338", + "parentCode": "776", + "name": "连平县", + "en_name": "Lianping", + "deleted": false, + "sublist": [] + }, + { + "code": "3405", + "parentCode": "776", + "name": "龙川县", + "en_name": "longchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4335", + "parentCode": "776", + "name": "源城区", + "en_name": "Yuancheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4336", + "parentCode": "776", + "name": "紫金县", + "en_name": "Zijin", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "773", + "parentCode": "548", + "name": "惠州", + "en_name": "HUIZHOU", + "deleted": false, + "sublist": [ + { + "code": "3255", + "parentCode": "773", + "name": "博罗县", + "en_name": "Boluoxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3254", + "parentCode": "773", + "name": "大亚湾区", + "en_name": "Dayawanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2246", + "parentCode": "773", + "name": "惠城区", + "en_name": "Huicheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3256", + "parentCode": "773", + "name": "惠东县", + "en_name": "Huidongxian", + "deleted": false, + "sublist": [] + }, + { + "code": "2247", + "parentCode": "773", + "name": "惠阳区", + "en_name": "Huiyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3257", + "parentCode": "773", + "name": "龙门县", + "en_name": "Longmenxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3253", + "parentCode": "773", + "name": "仲恺区", + "en_name": "Zhongkaiqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "769", + "parentCode": "548", + "name": "江门", + "en_name": "JIANGMEN", + "deleted": false, + "sublist": [ + { + "code": "3215", + "parentCode": "769", + "name": "恩平市", + "en_name": "Enpingshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3214", + "parentCode": "769", + "name": "鹤山市", + "en_name": "Heshanshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3211", + "parentCode": "769", + "name": "江海区", + "en_name": "Jianghaiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3213", + "parentCode": "769", + "name": "开平市", + "en_name": "Kaipingshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3210", + "parentCode": "769", + "name": "蓬江区", + "en_name": "Pengjiangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3403", + "parentCode": "769", + "name": "台山市", + "en_name": "Taishan", + "deleted": false, + "sublist": [] + }, + { + "code": "3212", + "parentCode": "769", + "name": "新会区", + "en_name": "Xinhuiqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "782", + "parentCode": "548", + "name": "揭阳", + "en_name": "JIEYANG", + "deleted": false, + "sublist": [ + { + "code": "4365", + "parentCode": "782", + "name": "惠来县", + "en_name": "Huilai", + "deleted": false, + "sublist": [] + }, + { + "code": "4361", + "parentCode": "782", + "name": "揭东区", + "en_name": "Jiedong", + "deleted": false, + "sublist": [] + }, + { + "code": "4364", + "parentCode": "782", + "name": "揭西县", + "en_name": "Jiexi", + "deleted": false, + "sublist": [] + }, + { + "code": "3404", + "parentCode": "782", + "name": "普宁市", + "en_name": "Puning", + "deleted": false, + "sublist": [] + }, + { + "code": "4360", + "parentCode": "782", + "name": "榕城区", + "en_name": "Rongcheng", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "771", + "parentCode": "548", + "name": "茂名", + "en_name": "MAOMING", + "deleted": false, + "sublist": [ + { + "code": "4306", + "parentCode": "771", + "name": "高州市", + "en_name": "Gaozhou", + "deleted": false, + "sublist": [] + }, + { + "code": "4307", + "parentCode": "771", + "name": "化州市", + "en_name": "Huazhou", + "deleted": false, + "sublist": [] + }, + { + "code": "4305", + "parentCode": "771", + "name": "电白区", + "en_name": "Maogang", + "deleted": false, + "sublist": [] + }, + { + "code": "4304", + "parentCode": "771", + "name": "茂南区", + "en_name": "Maonan", + "deleted": false, + "sublist": [] + }, + { + "code": "4308", + "parentCode": "771", + "name": "信宜市", + "en_name": "Xinyi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "774", + "parentCode": "548", + "name": "梅州", + "en_name": "MEIZHOU", + "deleted": false, + "sublist": [ + { + "code": "4326", + "parentCode": "774", + "name": "大埔县", + "en_name": "Dapu", + "deleted": false, + "sublist": [] + }, + { + "code": "4327", + "parentCode": "774", + "name": "丰顺县", + "en_name": "Fengshun", + "deleted": false, + "sublist": [] + }, + { + "code": "4330", + "parentCode": "774", + "name": "蕉岭县", + "en_name": "Jiaoling", + "deleted": false, + "sublist": [] + }, + { + "code": "4323", + "parentCode": "774", + "name": "梅江区", + "en_name": "Meijiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4325", + "parentCode": "774", + "name": "梅县区", + "en_name": "Meixian", + "deleted": false, + "sublist": [] + }, + { + "code": "4329", + "parentCode": "774", + "name": "平远县", + "en_name": "Pingyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4328", + "parentCode": "774", + "name": "五华县", + "en_name": "Wuhua", + "deleted": false, + "sublist": [] + }, + { + "code": "4324", + "parentCode": "774", + "name": "兴宁市", + "en_name": "Xingning", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "778", + "parentCode": "548", + "name": "清远", + "en_name": "QINGYUAN", + "deleted": false, + "sublist": [ + { + "code": "4349", + "parentCode": "778", + "name": "佛冈县", + "en_name": "Fogang", + "deleted": false, + "sublist": [] + }, + { + "code": "4352", + "parentCode": "778", + "name": "连南瑶族自治县", + "en_name": "Liannanyaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4351", + "parentCode": "778", + "name": "连山壮族瑶族自治县", + "en_name": "Lianshanzhuangzuyaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4348", + "parentCode": "778", + "name": "连州市", + "en_name": "Lianzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "4345", + "parentCode": "778", + "name": "清城区", + "en_name": "Qingcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4346", + "parentCode": "778", + "name": "清新区", + "en_name": "Qingxin", + "deleted": false, + "sublist": [] + }, + { + "code": "4350", + "parentCode": "778", + "name": "阳山县", + "en_name": "Yangshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4347", + "parentCode": "778", + "name": "英德市", + "en_name": "Yingde", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "767", + "parentCode": "548", + "name": "汕头", + "en_name": "SHANTOU", + "deleted": false, + "sublist": [ + { + "code": "4279", + "parentCode": "767", + "name": "潮南区", + "en_name": "Chaonan", + "deleted": false, + "sublist": [] + }, + { + "code": "3219", + "parentCode": "767", + "name": "潮阳区", + "en_name": "Chaoyangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4280", + "parentCode": "767", + "name": "澄海区", + "en_name": "Chenghai", + "deleted": false, + "sublist": [] + }, + { + "code": "3218", + "parentCode": "767", + "name": "濠江区", + "en_name": "Haojiangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3217", + "parentCode": "767", + "name": "金平区", + "en_name": "Jinpingqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3216", + "parentCode": "767", + "name": "龙湖区", + "en_name": "Longhuqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4281", + "parentCode": "767", + "name": "南澳县", + "en_name": "Nanao", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "775", + "parentCode": "548", + "name": "汕尾", + "en_name": "SHANWEI", + "deleted": false, + "sublist": [ + { + "code": "4331", + "parentCode": "775", + "name": "城区", + "en_name": "Cheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4333", + "parentCode": "775", + "name": "海丰县", + "en_name": "Haifeng", + "deleted": false, + "sublist": [] + }, + { + "code": "4332", + "parentCode": "775", + "name": "陆丰市", + "en_name": "Lufeng", + "deleted": false, + "sublist": [] + }, + { + "code": "4334", + "parentCode": "775", + "name": "陆河县", + "en_name": "Luhe", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "764", + "parentCode": "548", + "name": "韶关", + "en_name": "SHAOGUAN", + "deleted": false, + "sublist": [ + { + "code": "4255", + "parentCode": "764", + "name": "乐昌市", + "en_name": "Lechang", + "deleted": false, + "sublist": [] + }, + { + "code": "4256", + "parentCode": "764", + "name": "南雄市", + "en_name": "Nanxiong", + "deleted": false, + "sublist": [] + }, + { + "code": "4254", + "parentCode": "764", + "name": "曲江区", + "en_name": "Qujiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4259", + "parentCode": "764", + "name": "仁化县", + "en_name": "Renhua", + "deleted": false, + "sublist": [] + }, + { + "code": "4260", + "parentCode": "764", + "name": "乳源瑶族自治县", + "en_name": "Ruyuanyaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4257", + "parentCode": "764", + "name": "始兴县", + "en_name": "Shixing", + "deleted": false, + "sublist": [] + }, + { + "code": "4258", + "parentCode": "764", + "name": "翁源县", + "en_name": "Wengyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4252", + "parentCode": "764", + "name": "武江区", + "en_name": "Wujiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4261", + "parentCode": "764", + "name": "新丰县", + "en_name": "Xinfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "4253", + "parentCode": "764", + "name": "浈江区", + "en_name": "Zhenjiang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "765", + "parentCode": "548", + "name": "深圳", + "en_name": "SHENZHEN", + "deleted": false, + "sublist": [ + { + "code": "2041", + "parentCode": "765", + "name": "宝安区", + "en_name": "Baoan", + "deleted": false, + "sublist": [] + }, + { + "code": "2362", + "parentCode": "765", + "name": "大鹏新区", + "en_name": "Dapeng", + "deleted": false, + "sublist": [] + }, + { + "code": "2037", + "parentCode": "765", + "name": "福田区", + "en_name": "Futian", + "deleted": false, + "sublist": [] + }, + { + "code": "2044", + "parentCode": "765", + "name": "光明区", + "en_name": "Guangmingxin", + "deleted": false, + "sublist": [] + }, + { + "code": "2042", + "parentCode": "765", + "name": "龙岗区", + "en_name": "Longgang", + "deleted": false, + "sublist": [] + }, + { + "code": "2361", + "parentCode": "765", + "name": "龙华区", + "en_name": "Longhua", + "deleted": false, + "sublist": [] + }, + { + "code": "2038", + "parentCode": "765", + "name": "罗湖区", + "en_name": "Luohu", + "deleted": false, + "sublist": [] + }, + { + "code": "2039", + "parentCode": "765", + "name": "南山区", + "en_name": "Nanshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2043", + "parentCode": "765", + "name": "坪山区", + "en_name": "Pingshanxin", + "deleted": false, + "sublist": [] + }, + { + "code": "2040", + "parentCode": "765", + "name": "盐田区", + "en_name": "Yantian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "777", + "parentCode": "548", + "name": "阳江", + "en_name": "YANGJIANG", + "deleted": false, + "sublist": [ + { + "code": "4341", + "parentCode": "777", + "name": "江城区", + "en_name": "Jiangcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4342", + "parentCode": "777", + "name": "阳春市", + "en_name": "Yangchun", + "deleted": false, + "sublist": [] + }, + { + "code": "4344", + "parentCode": "777", + "name": "阳东区", + "en_name": "Yangdong", + "deleted": false, + "sublist": [] + }, + { + "code": "4343", + "parentCode": "777", + "name": "阳西县", + "en_name": "Yangxi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "783", + "parentCode": "548", + "name": "云浮", + "en_name": "YUNFU", + "deleted": false, + "sublist": [ + { + "code": "4367", + "parentCode": "783", + "name": "罗定市", + "en_name": "Luoding", + "deleted": false, + "sublist": [] + }, + { + "code": "4368", + "parentCode": "783", + "name": "新兴县", + "en_name": "Xinxing", + "deleted": false, + "sublist": [] + }, + { + "code": "4369", + "parentCode": "783", + "name": "郁南县", + "en_name": "Yunan", + "deleted": false, + "sublist": [] + }, + { + "code": "4370", + "parentCode": "783", + "name": "云安区", + "en_name": "Yunan", + "deleted": false, + "sublist": [] + }, + { + "code": "4366", + "parentCode": "783", + "name": "云城区", + "en_name": "Yuncheng", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "770", + "parentCode": "548", + "name": "湛江", + "en_name": "ZHANJIANG", + "deleted": false, + "sublist": [ + { + "code": "4295", + "parentCode": "770", + "name": "赤坎区", + "en_name": "Chikan", + "deleted": false, + "sublist": [] + }, + { + "code": "4300", + "parentCode": "770", + "name": "雷州市", + "en_name": "Leizhou", + "deleted": false, + "sublist": [] + }, + { + "code": "4299", + "parentCode": "770", + "name": "廉江市", + "en_name": "Lianjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4298", + "parentCode": "770", + "name": "麻章区", + "en_name": "Mazhang", + "deleted": false, + "sublist": [] + }, + { + "code": "4297", + "parentCode": "770", + "name": "坡头区", + "en_name": "Potou", + "deleted": false, + "sublist": [] + }, + { + "code": "4302", + "parentCode": "770", + "name": "遂溪县", + "en_name": "Suixi", + "deleted": false, + "sublist": [] + }, + { + "code": "4301", + "parentCode": "770", + "name": "吴川市", + "en_name": "Wuchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4296", + "parentCode": "770", + "name": "霞山区", + "en_name": "Xiashan", + "deleted": false, + "sublist": [] + }, + { + "code": "4303", + "parentCode": "770", + "name": "徐闻县", + "en_name": "Xuwen", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "772", + "parentCode": "548", + "name": "肇庆", + "en_name": "ZHAOQING", + "deleted": false, + "sublist": [ + { + "code": "4317", + "parentCode": "772", + "name": "德庆县", + "en_name": "Deqing", + "deleted": false, + "sublist": [] + }, + { + "code": "4311", + "parentCode": "772", + "name": "鼎湖区", + "en_name": "Dinghu", + "deleted": false, + "sublist": [] + }, + { + "code": "4310", + "parentCode": "772", + "name": "端州区", + "en_name": "Duanzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "4316", + "parentCode": "772", + "name": "封开县", + "en_name": "Fengkai", + "deleted": false, + "sublist": [] + }, + { + "code": "4312", + "parentCode": "772", + "name": "高要区", + "en_name": "Gaoyao", + "deleted": false, + "sublist": [] + }, + { + "code": "4314", + "parentCode": "772", + "name": "广宁县", + "en_name": "Guangning", + "deleted": false, + "sublist": [] + }, + { + "code": "4315", + "parentCode": "772", + "name": "怀集县", + "en_name": "Huaiji", + "deleted": false, + "sublist": [] + }, + { + "code": "4313", + "parentCode": "772", + "name": "四会市", + "en_name": "Sihui", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "780", + "parentCode": "548", + "name": "中山", + "en_name": "ZHONGSHAN", + "deleted": false, + "sublist": [ + { + "code": "104048", + "parentCode": "780", + "name": "中山港街道", + "en_name": "zhongshangangjiedao", + "deleted": false, + "sublist": [] + }, + { + "code": "3192", + "parentCode": "780", + "name": "板芙镇", + "en_name": "Banfuzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3205", + "parentCode": "780", + "name": "大涌镇", + "en_name": "Dayongzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3195", + "parentCode": "780", + "name": "东凤镇", + "en_name": "Dongfengzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3186", + "parentCode": "780", + "name": "东区街道", + "en_name": "dongqujiedao", + "deleted": false, + "sublist": [] + }, + { + "code": "3198", + "parentCode": "780", + "name": "东升镇", + "en_name": "Dongshengzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3196", + "parentCode": "780", + "name": "阜沙镇", + "en_name": "Fushazhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3204", + "parentCode": "780", + "name": "港口镇", + "en_name": "Gangkouzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3199", + "parentCode": "780", + "name": "古镇镇", + "en_name": "Guzhenzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3200", + "parentCode": "780", + "name": "横栏镇", + "en_name": "Henglanzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3193", + "parentCode": "780", + "name": "黄圃镇", + "en_name": "Huangpuzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3191", + "parentCode": "780", + "name": "火炬开发区街道", + "en_name": "huojukaifaqujiedao", + "deleted": false, + "sublist": [] + }, + { + "code": "3202", + "parentCode": "780", + "name": "民众街道", + "en_name": "Minzhongzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3203", + "parentCode": "780", + "name": "南朗街道", + "en_name": "Nanlangzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3187", + "parentCode": "780", + "name": "南区街道", + "en_name": "nanqujiedao", + "deleted": false, + "sublist": [] + }, + { + "code": "3194", + "parentCode": "780", + "name": "南头镇", + "en_name": "Nantouzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3201", + "parentCode": "780", + "name": "三角镇", + "en_name": "Sanjiaozhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3207", + "parentCode": "780", + "name": "三乡镇", + "en_name": "Sanxiangzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3206", + "parentCode": "780", + "name": "沙溪镇", + "en_name": "Shaxizhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3208", + "parentCode": "780", + "name": "神湾镇", + "en_name": "Shenwanzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3188", + "parentCode": "780", + "name": "石岐街道", + "en_name": "shiqiqujiedao", + "deleted": false, + "sublist": [] + }, + { + "code": "3209", + "parentCode": "780", + "name": "坦洲镇", + "en_name": "Tanzhouzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3190", + "parentCode": "780", + "name": "五桂山街道", + "en_name": "wuguishanjiedao", + "deleted": false, + "sublist": [] + }, + { + "code": "3197", + "parentCode": "780", + "name": "小榄镇", + "en_name": "Xiaolanzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "3189", + "parentCode": "780", + "name": "西区街道", + "en_name": "xiqujiedao", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "766", + "parentCode": "548", + "name": "珠海", + "en_name": "ZHUHAI", + "deleted": false, + "sublist": [ + { + "code": "3185", + "parentCode": "766", + "name": "保税区", + "en_name": "Baoshuiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3181", + "parentCode": "766", + "name": "斗门区", + "en_name": "Doumenqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3184", + "parentCode": "766", + "name": "高新区", + "en_name": "Gaoxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3183", + "parentCode": "766", + "name": "横琴新区", + "en_name": "Hengqinxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3182", + "parentCode": "766", + "name": "金湾区", + "en_name": "Jinwanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3180", + "parentCode": "766", + "name": "香洲区", + "en_name": "Xiangzhouqu", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "549", + "parentCode": "489", + "name": "广西", + "en_name": "GUANGXI", + "deleted": false, + "sublist": [ + { + "code": "794", + "parentCode": "549", + "name": "百色", + "en_name": "BAISE", + "deleted": false, + "sublist": [ + { + "code": "4445", + "parentCode": "794", + "name": "德保县", + "en_name": "Debao", + "deleted": false, + "sublist": [] + }, + { + "code": "4446", + "parentCode": "794", + "name": "靖西市", + "en_name": "Jingxi", + "deleted": false, + "sublist": [] + }, + { + "code": "4449", + "parentCode": "794", + "name": "乐业县", + "en_name": "Leye", + "deleted": false, + "sublist": [] + }, + { + "code": "4448", + "parentCode": "794", + "name": "凌云县", + "en_name": "Lingyun", + "deleted": false, + "sublist": [] + }, + { + "code": "4452", + "parentCode": "794", + "name": "隆林各族自治县", + "en_name": "Longlingezuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4447", + "parentCode": "794", + "name": "那坡县", + "en_name": "Napo", + "deleted": false, + "sublist": [] + }, + { + "code": "4444", + "parentCode": "794", + "name": "平果市", + "en_name": "Pingguo", + "deleted": false, + "sublist": [] + }, + { + "code": "4443", + "parentCode": "794", + "name": "田东县", + "en_name": "Tiandong", + "deleted": false, + "sublist": [] + }, + { + "code": "4450", + "parentCode": "794", + "name": "田林县", + "en_name": "Tianlin", + "deleted": false, + "sublist": [] + }, + { + "code": "4442", + "parentCode": "794", + "name": "田阳区", + "en_name": "Tianyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4451", + "parentCode": "794", + "name": "西林县", + "en_name": "Xilin", + "deleted": false, + "sublist": [] + }, + { + "code": "4441", + "parentCode": "794", + "name": "右江区", + "en_name": "Youjiang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "789", + "parentCode": "549", + "name": "北海", + "en_name": "BEIHAI", + "deleted": false, + "sublist": [ + { + "code": "3169", + "parentCode": "789", + "name": "海城区", + "en_name": "Haichengqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3172", + "parentCode": "789", + "name": "合浦县", + "en_name": "Hepuxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3171", + "parentCode": "789", + "name": "铁山港区", + "en_name": "Tieshangangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3170", + "parentCode": "789", + "name": "银海区", + "en_name": "Yinhaiqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "905", + "parentCode": "549", + "name": "崇左", + "en_name": "CHONGZUO", + "deleted": false, + "sublist": [ + { + "code": "5307", + "parentCode": "905", + "name": "大新县", + "en_name": "Daxin", + "deleted": false, + "sublist": [] + }, + { + "code": "5304", + "parentCode": "905", + "name": "扶绥县", + "en_name": "Fusui", + "deleted": false, + "sublist": [] + }, + { + "code": "5302", + "parentCode": "905", + "name": "江州区", + "en_name": "Jiangzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "5306", + "parentCode": "905", + "name": "龙州县", + "en_name": "Longzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "5305", + "parentCode": "905", + "name": "宁明县", + "en_name": "Ningming", + "deleted": false, + "sublist": [] + }, + { + "code": "5303", + "parentCode": "905", + "name": "凭祥市", + "en_name": "Pingxiang", + "deleted": false, + "sublist": [] + }, + { + "code": "5308", + "parentCode": "905", + "name": "天等县", + "en_name": "Tiandeng", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "790", + "parentCode": "549", + "name": "防城港", + "en_name": "FANGCHENGGANG", + "deleted": false, + "sublist": [ + { + "code": "4423", + "parentCode": "790", + "name": "东兴市", + "en_name": "Dongxing", + "deleted": false, + "sublist": [] + }, + { + "code": "4422", + "parentCode": "790", + "name": "防城区", + "en_name": "Fangcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4421", + "parentCode": "790", + "name": "港口区", + "en_name": "Gangkou", + "deleted": false, + "sublist": [] + }, + { + "code": "4424", + "parentCode": "790", + "name": "上思县", + "en_name": "Shangsi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "792", + "parentCode": "549", + "name": "贵港", + "en_name": "GUIGANG", + "deleted": false, + "sublist": [ + { + "code": "4429", + "parentCode": "792", + "name": "港北区", + "en_name": "Gangbei", + "deleted": false, + "sublist": [] + }, + { + "code": "4430", + "parentCode": "792", + "name": "港南区", + "en_name": "Gangnan", + "deleted": false, + "sublist": [] + }, + { + "code": "4432", + "parentCode": "792", + "name": "桂平市", + "en_name": "Guiping", + "deleted": false, + "sublist": [] + }, + { + "code": "4433", + "parentCode": "792", + "name": "平南县", + "en_name": "Pingnan", + "deleted": false, + "sublist": [] + }, + { + "code": "4431", + "parentCode": "792", + "name": "覃塘区", + "en_name": "Qintang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "787", + "parentCode": "549", + "name": "桂林", + "en_name": "GUILIN", + "deleted": false, + "sublist": [ + { + "code": "3164", + "parentCode": "787", + "name": "叠彩区", + "en_name": "Diecaiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4409", + "parentCode": "787", + "name": "恭城瑶族自治县", + "en_name": "Gongchengyaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4404", + "parentCode": "787", + "name": "灌阳县", + "en_name": "Guanyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4400", + "parentCode": "787", + "name": "灵川县", + "en_name": "Lingchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "3168", + "parentCode": "787", + "name": "临桂区", + "en_name": "Linguiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4408", + "parentCode": "787", + "name": "荔浦市", + "en_name": "Lipu", + "deleted": false, + "sublist": [] + }, + { + "code": "4405", + "parentCode": "787", + "name": "龙胜各族自治县", + "en_name": "Longshenggezuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4407", + "parentCode": "787", + "name": "平乐县", + "en_name": "Pingle", + "deleted": false, + "sublist": [] + }, + { + "code": "3166", + "parentCode": "787", + "name": "七星区", + "en_name": "Qixingqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4401", + "parentCode": "787", + "name": "全州县", + "en_name": "Quanzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "3165", + "parentCode": "787", + "name": "象山区", + "en_name": "Xiangshanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4402", + "parentCode": "787", + "name": "兴安县", + "en_name": "Xingan", + "deleted": false, + "sublist": [] + }, + { + "code": "3163", + "parentCode": "787", + "name": "秀峰区", + "en_name": "Xiufengqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4399", + "parentCode": "787", + "name": "阳朔县", + "en_name": "Yangshuo", + "deleted": false, + "sublist": [] + }, + { + "code": "3167", + "parentCode": "787", + "name": "雁山区", + "en_name": "Yanshanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4403", + "parentCode": "787", + "name": "永福县", + "en_name": "Yongfu", + "deleted": false, + "sublist": [] + }, + { + "code": "4406", + "parentCode": "787", + "name": "资源县", + "en_name": "Ziyuan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "796", + "parentCode": "549", + "name": "河池", + "en_name": "HECHI", + "deleted": false, + "sublist": [ + { + "code": "4465", + "parentCode": "796", + "name": "巴马瑶族自治县", + "en_name": "Bamayaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4467", + "parentCode": "796", + "name": "大化瑶族自治县", + "en_name": "Dahuayaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4462", + "parentCode": "796", + "name": "东兰县", + "en_name": "Donglan", + "deleted": false, + "sublist": [] + }, + { + "code": "4466", + "parentCode": "796", + "name": "都安瑶族自治县", + "en_name": "Duanyaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4461", + "parentCode": "796", + "name": "凤山县", + "en_name": "Fengshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4464", + "parentCode": "796", + "name": "环江毛南族自治县", + "en_name": "Huanjiangmaonanzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4457", + "parentCode": "796", + "name": "金城江区", + "en_name": "Jinchengjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4463", + "parentCode": "796", + "name": "罗城仫佬族自治县", + "en_name": "Luochengmulaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4459", + "parentCode": "796", + "name": "南丹县", + "en_name": "Nandan", + "deleted": false, + "sublist": [] + }, + { + "code": "4460", + "parentCode": "796", + "name": "天峨县", + "en_name": "Tiane", + "deleted": false, + "sublist": [] + }, + { + "code": "4458", + "parentCode": "796", + "name": "宜州区", + "en_name": "Yizhou", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "795", + "parentCode": "549", + "name": "贺州", + "en_name": "HEZHOU", + "deleted": false, + "sublist": [ + { + "code": "4453", + "parentCode": "795", + "name": "八步区", + "en_name": "Babu", + "deleted": false, + "sublist": [] + }, + { + "code": "4456", + "parentCode": "795", + "name": "富川瑶族自治县", + "en_name": "Fuchuanyaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4454", + "parentCode": "795", + "name": "昭平县", + "en_name": "Zhaoping", + "deleted": false, + "sublist": [] + }, + { + "code": "4455", + "parentCode": "795", + "name": "钟山县", + "en_name": "Zhongshan", + "deleted": false, + "sublist": [] + }, + { + "code": "104022", + "parentCode": "795", + "name": "平桂区", + "en_name": "pingguiqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "904", + "parentCode": "549", + "name": "来宾", + "en_name": "LAIBIN", + "deleted": false, + "sublist": [ + { + "code": "5297", + "parentCode": "904", + "name": "合山市", + "en_name": "Heshan", + "deleted": false, + "sublist": [] + }, + { + "code": "5301", + "parentCode": "904", + "name": "金秀瑶族自治县", + "en_name": "Jinxiuyaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5300", + "parentCode": "904", + "name": "武宣县", + "en_name": "Wuxuan", + "deleted": false, + "sublist": [] + }, + { + "code": "5299", + "parentCode": "904", + "name": "象州县", + "en_name": "Xiangzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "5298", + "parentCode": "904", + "name": "忻城县", + "en_name": "Xincheng", + "deleted": false, + "sublist": [] + }, + { + "code": "5296", + "parentCode": "904", + "name": "兴宾区", + "en_name": "Xingbin", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "786", + "parentCode": "549", + "name": "柳州", + "en_name": "LIUZHOU", + "deleted": false, + "sublist": [ + { + "code": "3159", + "parentCode": "786", + "name": "城中区", + "en_name": "Chengzhongqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3162", + "parentCode": "786", + "name": "柳北区", + "en_name": "Liubeiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4388", + "parentCode": "786", + "name": "柳城县", + "en_name": "Liucheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4387", + "parentCode": "786", + "name": "柳江区", + "en_name": "Liujiang", + "deleted": false, + "sublist": [] + }, + { + "code": "3161", + "parentCode": "786", + "name": "柳南区", + "en_name": "Liunanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4389", + "parentCode": "786", + "name": "鹿寨县", + "en_name": "Luzhai", + "deleted": false, + "sublist": [] + }, + { + "code": "4390", + "parentCode": "786", + "name": "融安县", + "en_name": "Rongan", + "deleted": false, + "sublist": [] + }, + { + "code": "4391", + "parentCode": "786", + "name": "融水苗族自治县", + "en_name": "Rongshuimiaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4392", + "parentCode": "786", + "name": "三江侗族自治县", + "en_name": "Sanjiangdongzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "3160", + "parentCode": "786", + "name": "鱼峰区", + "en_name": "Yufengqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "785", + "parentCode": "549", + "name": "南宁", + "en_name": "NANNING", + "deleted": false, + "sublist": [ + { + "code": "3157", + "parentCode": "785", + "name": "宾阳县", + "en_name": "Binyangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3158", + "parentCode": "785", + "name": "横州市", + "en_name": "hengzhoushi", + "deleted": false, + "sublist": [] + }, + { + "code": "3149", + "parentCode": "785", + "name": "江南区", + "en_name": "Jiangnanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3151", + "parentCode": "785", + "name": "良庆区", + "en_name": "Liangqingqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3154", + "parentCode": "785", + "name": "隆安县", + "en_name": "Longanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3155", + "parentCode": "785", + "name": "马山县", + "en_name": "Mashanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3148", + "parentCode": "785", + "name": "青秀区", + "en_name": "Qingxiuqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3156", + "parentCode": "785", + "name": "上林县", + "en_name": "Shanglinxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3153", + "parentCode": "785", + "name": "武鸣区", + "en_name": "Wumingqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3147", + "parentCode": "785", + "name": "兴宁区", + "en_name": "Xingningqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3150", + "parentCode": "785", + "name": "西乡塘区", + "en_name": "Xixiangtangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3152", + "parentCode": "785", + "name": "邕宁区", + "en_name": "Yongningqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "791", + "parentCode": "549", + "name": "钦州", + "en_name": "QINZHOU", + "deleted": false, + "sublist": [ + { + "code": "4427", + "parentCode": "791", + "name": "灵山县", + "en_name": "Lingshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4428", + "parentCode": "791", + "name": "浦北县", + "en_name": "Pubei", + "deleted": false, + "sublist": [] + }, + { + "code": "4426", + "parentCode": "791", + "name": "钦北区", + "en_name": "Qinbei", + "deleted": false, + "sublist": [] + }, + { + "code": "4425", + "parentCode": "791", + "name": "钦南区", + "en_name": "Qinnan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "788", + "parentCode": "549", + "name": "梧州", + "en_name": "WUZHOU", + "deleted": false, + "sublist": [ + { + "code": "4414", + "parentCode": "788", + "name": "苍梧县", + "en_name": "Cangwu", + "deleted": false, + "sublist": [] + }, + { + "code": "4413", + "parentCode": "788", + "name": "岑溪市", + "en_name": "Cenxi", + "deleted": false, + "sublist": [] + }, + { + "code": "4412", + "parentCode": "788", + "name": "龙圩区", + "en_name": "Longxu", + "deleted": false, + "sublist": [] + }, + { + "code": "4416", + "parentCode": "788", + "name": "蒙山县", + "en_name": "Mengshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4415", + "parentCode": "788", + "name": "藤县", + "en_name": "Teng", + "deleted": false, + "sublist": [] + }, + { + "code": "4410", + "parentCode": "788", + "name": "万秀区", + "en_name": "Wanxiu", + "deleted": false, + "sublist": [] + }, + { + "code": "4411", + "parentCode": "788", + "name": "长洲区", + "en_name": "Changzhou", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "793", + "parentCode": "549", + "name": "玉林", + "en_name": "YULIN", + "deleted": false, + "sublist": [ + { + "code": "3175", + "parentCode": "793", + "name": "北流市", + "en_name": "Beiliushi", + "deleted": false, + "sublist": [] + }, + { + "code": "3178", + "parentCode": "793", + "name": "博白县", + "en_name": "Bobaixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3174", + "parentCode": "793", + "name": "福绵区", + "en_name": "Fumianqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3177", + "parentCode": "793", + "name": "陆川县", + "en_name": "Luchuanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3176", + "parentCode": "793", + "name": "容县", + "en_name": "Rongxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3179", + "parentCode": "793", + "name": "兴业县", + "en_name": "Xingyexian", + "deleted": false, + "sublist": [] + }, + { + "code": "3173", + "parentCode": "793", + "name": "玉州区", + "en_name": "Yuzhouqu", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "553", + "parentCode": "489", + "name": "贵州", + "en_name": "GUIZHOU", + "deleted": false, + "sublist": [ + { + "code": "825", + "parentCode": "553", + "name": "安顺", + "en_name": "ANSHUN", + "deleted": false, + "sublist": [ + { + "code": "4687", + "parentCode": "825", + "name": "关岭布依族苗族自治县", + "en_name": "Guanlingbuyizumiaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4684", + "parentCode": "825", + "name": "平坝区", + "en_name": "Pingba", + "deleted": false, + "sublist": [] + }, + { + "code": "4685", + "parentCode": "825", + "name": "普定县", + "en_name": "Puding", + "deleted": false, + "sublist": [] + }, + { + "code": "4683", + "parentCode": "825", + "name": "西秀区", + "en_name": "Xixiu", + "deleted": false, + "sublist": [] + }, + { + "code": "4686", + "parentCode": "825", + "name": "镇宁布依族苗族自治县", + "en_name": "Zhenningbuyizumiaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4688", + "parentCode": "825", + "name": "紫云苗族布依族自治县", + "en_name": "Ziyunmiaozubuyizuzizhi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "828", + "parentCode": "553", + "name": "毕节", + "en_name": "BIJIE", + "deleted": false, + "sublist": [ + { + "code": "4708", + "parentCode": "828", + "name": "大方县", + "en_name": "Dafang", + "deleted": false, + "sublist": [] + }, + { + "code": "4714", + "parentCode": "828", + "name": "赫章县", + "en_name": "Hezhang", + "deleted": false, + "sublist": [] + }, + { + "code": "4710", + "parentCode": "828", + "name": "金沙县", + "en_name": "Jinsha", + "deleted": false, + "sublist": [] + }, + { + "code": "4712", + "parentCode": "828", + "name": "纳雍县", + "en_name": "Nayong", + "deleted": false, + "sublist": [] + }, + { + "code": "4709", + "parentCode": "828", + "name": "黔西市", + "en_name": "qianxishi", + "deleted": false, + "sublist": [] + }, + { + "code": "4707", + "parentCode": "828", + "name": "七星关区", + "en_name": "Qixingguan", + "deleted": false, + "sublist": [] + }, + { + "code": "4713", + "parentCode": "828", + "name": "威宁彝族回族苗族自治县", + "en_name": "Weiningyizuhuizumiaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4711", + "parentCode": "828", + "name": "织金县", + "en_name": "Zhijin", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "822", + "parentCode": "553", + "name": "贵阳", + "en_name": "GUIYANG", + "deleted": false, + "sublist": [ + { + "code": "2525", + "parentCode": "822", + "name": "白云区", + "en_name": "BAIYUNQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2524", + "parentCode": "822", + "name": "观山湖区", + "en_name": "GUANSHANHUQU(JINYANGXINQU)", + "deleted": false, + "sublist": [] + }, + { + "code": "2523", + "parentCode": "822", + "name": "花溪区", + "en_name": "HUAXIQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2530", + "parentCode": "822", + "name": "开阳县", + "en_name": "KAIYANGXIAN", + "deleted": false, + "sublist": [] + }, + { + "code": "2522", + "parentCode": "822", + "name": "南明区", + "en_name": "NANMINGQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2527", + "parentCode": "822", + "name": "清镇市", + "en_name": "QINGZHENSHI", + "deleted": false, + "sublist": [] + }, + { + "code": "2526", + "parentCode": "822", + "name": "乌当区", + "en_name": "WUDANGQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2529", + "parentCode": "822", + "name": "息烽县", + "en_name": "XIFENGXIAN", + "deleted": false, + "sublist": [] + }, + { + "code": "2528", + "parentCode": "822", + "name": "修文县", + "en_name": "XIUWENXIAN", + "deleted": false, + "sublist": [] + }, + { + "code": "2521", + "parentCode": "822", + "name": "云岩区", + "en_name": "YUNYANQU", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "823", + "parentCode": "553", + "name": "六盘水", + "en_name": "LIUPANSHUI", + "deleted": false, + "sublist": [ + { + "code": "4666", + "parentCode": "823", + "name": "六枝特区", + "en_name": "Huazhite", + "deleted": false, + "sublist": [] + }, + { + "code": "4668", + "parentCode": "823", + "name": "盘州市", + "en_name": "Pan", + "deleted": false, + "sublist": [] + }, + { + "code": "4667", + "parentCode": "823", + "name": "水城区", + "en_name": "shuichengqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4665", + "parentCode": "823", + "name": "钟山区", + "en_name": "Zhongshan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "829", + "parentCode": "553", + "name": "黔东南", + "en_name": "QIANDONGNAN", + "deleted": false, + "sublist": [ + { + "code": "4720", + "parentCode": "829", + "name": "岑巩县", + "en_name": "Cengong", + "deleted": false, + "sublist": [] + }, + { + "code": "4727", + "parentCode": "829", + "name": "从江县", + "en_name": "Congjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4730", + "parentCode": "829", + "name": "丹寨县", + "en_name": "Danzhai", + "deleted": false, + "sublist": [] + }, + { + "code": "4716", + "parentCode": "829", + "name": "黄平县", + "en_name": "Huangping", + "deleted": false, + "sublist": [] + }, + { + "code": "4723", + "parentCode": "829", + "name": "剑河县", + "en_name": "Jianhe", + "deleted": false, + "sublist": [] + }, + { + "code": "4722", + "parentCode": "829", + "name": "锦屏县", + "en_name": "Jinping", + "deleted": false, + "sublist": [] + }, + { + "code": "4715", + "parentCode": "829", + "name": "凯里市", + "en_name": "Kaili", + "deleted": false, + "sublist": [] + }, + { + "code": "4728", + "parentCode": "829", + "name": "雷山县", + "en_name": "Leishan", + "deleted": false, + "sublist": [] + }, + { + "code": "4725", + "parentCode": "829", + "name": "黎平县", + "en_name": "Liping", + "deleted": false, + "sublist": [] + }, + { + "code": "4729", + "parentCode": "829", + "name": "麻江县", + "en_name": "Majiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4726", + "parentCode": "829", + "name": "榕江县", + "en_name": "Rongjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4718", + "parentCode": "829", + "name": "三穗县", + "en_name": "Sansui", + "deleted": false, + "sublist": [] + }, + { + "code": "4717", + "parentCode": "829", + "name": "施秉县", + "en_name": "Shibing", + "deleted": false, + "sublist": [] + }, + { + "code": "4724", + "parentCode": "829", + "name": "台江县", + "en_name": "Taijiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4721", + "parentCode": "829", + "name": "天柱县", + "en_name": "Tianzhu", + "deleted": false, + "sublist": [] + }, + { + "code": "4719", + "parentCode": "829", + "name": "镇远县", + "en_name": "Zhenyuan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "830", + "parentCode": "553", + "name": "黔南", + "en_name": "QIANNAN", + "deleted": false, + "sublist": [ + { + "code": "4731", + "parentCode": "830", + "name": "都匀市", + "en_name": "Duyun", + "deleted": false, + "sublist": [] + }, + { + "code": "4736", + "parentCode": "830", + "name": "独山县", + "en_name": "Dushan", + "deleted": false, + "sublist": [] + }, + { + "code": "4732", + "parentCode": "830", + "name": "福泉市", + "en_name": "Fuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4734", + "parentCode": "830", + "name": "贵定县", + "en_name": "Guiding", + "deleted": false, + "sublist": [] + }, + { + "code": "4741", + "parentCode": "830", + "name": "惠水县", + "en_name": "Huishui", + "deleted": false, + "sublist": [] + }, + { + "code": "4733", + "parentCode": "830", + "name": "荔波县", + "en_name": "Libo", + "deleted": false, + "sublist": [] + }, + { + "code": "4740", + "parentCode": "830", + "name": "龙里县", + "en_name": "Longli", + "deleted": false, + "sublist": [] + }, + { + "code": "4738", + "parentCode": "830", + "name": "罗甸县", + "en_name": "Luodian", + "deleted": false, + "sublist": [] + }, + { + "code": "4737", + "parentCode": "830", + "name": "平塘县", + "en_name": "Pingtang", + "deleted": false, + "sublist": [] + }, + { + "code": "4742", + "parentCode": "830", + "name": "三都水族自治县", + "en_name": "Sandushuizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4735", + "parentCode": "830", + "name": "瓮安县", + "en_name": "Wengan", + "deleted": false, + "sublist": [] + }, + { + "code": "4739", + "parentCode": "830", + "name": "长顺县", + "en_name": "Changshun", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "827", + "parentCode": "553", + "name": "黔西南", + "en_name": "QIANXINAN", + "deleted": false, + "sublist": [ + { + "code": "4706", + "parentCode": "827", + "name": "安龙县", + "en_name": "Anlong", + "deleted": false, + "sublist": [] + }, + { + "code": "4705", + "parentCode": "827", + "name": "册亨县", + "en_name": "Ceheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4701", + "parentCode": "827", + "name": "普安县", + "en_name": "Puan", + "deleted": false, + "sublist": [] + }, + { + "code": "4702", + "parentCode": "827", + "name": "晴隆县", + "en_name": "Qinglong", + "deleted": false, + "sublist": [] + }, + { + "code": "4704", + "parentCode": "827", + "name": "望谟县", + "en_name": "Wangmo", + "deleted": false, + "sublist": [] + }, + { + "code": "4700", + "parentCode": "827", + "name": "兴仁市", + "en_name": "Xingren", + "deleted": false, + "sublist": [] + }, + { + "code": "4699", + "parentCode": "827", + "name": "兴义市", + "en_name": "Xingyi", + "deleted": false, + "sublist": [] + }, + { + "code": "4703", + "parentCode": "827", + "name": "贞丰县", + "en_name": "Zhenfeng", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "826", + "parentCode": "553", + "name": "铜仁", + "en_name": "TONGREN", + "deleted": false, + "sublist": [ + { + "code": "4689", + "parentCode": "826", + "name": "碧江区", + "en_name": "Bijiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4696", + "parentCode": "826", + "name": "德江县", + "en_name": "Dejiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4691", + "parentCode": "826", + "name": "江口县", + "en_name": "Jiangkou", + "deleted": false, + "sublist": [] + }, + { + "code": "4694", + "parentCode": "826", + "name": "石阡县", + "en_name": "Shiqian", + "deleted": false, + "sublist": [] + }, + { + "code": "4693", + "parentCode": "826", + "name": "思南县", + "en_name": "Sinan", + "deleted": false, + "sublist": [] + }, + { + "code": "4698", + "parentCode": "826", + "name": "松桃苗族自治县", + "en_name": "Songtaomiaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4690", + "parentCode": "826", + "name": "万山区", + "en_name": "Wanshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4697", + "parentCode": "826", + "name": "沿河土家族自治县", + "en_name": "Yanhetujiazuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4695", + "parentCode": "826", + "name": "印江土家族苗族自治县", + "en_name": "Yinjiangtujiazumiaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4692", + "parentCode": "826", + "name": "玉屏侗族自治县", + "en_name": "Yupingdongzuzizhi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "824", + "parentCode": "553", + "name": "遵义", + "en_name": "ZUNYI", + "deleted": false, + "sublist": [ + { + "code": "4671", + "parentCode": "824", + "name": "赤水市", + "en_name": "Chishui", + "deleted": false, + "sublist": [] + }, + { + "code": "4677", + "parentCode": "824", + "name": "道真仡佬族苗族自治县", + "en_name": "Daozhenmulaozumiaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4679", + "parentCode": "824", + "name": "凤冈县", + "en_name": "Fenggang", + "deleted": false, + "sublist": [] + }, + { + "code": "4669", + "parentCode": "824", + "name": "红花岗区", + "en_name": "Honghuagang", + "deleted": false, + "sublist": [] + }, + { + "code": "4670", + "parentCode": "824", + "name": "汇川区", + "en_name": "Huichuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4680", + "parentCode": "824", + "name": "湄潭县", + "en_name": "Meitan", + "deleted": false, + "sublist": [] + }, + { + "code": "4672", + "parentCode": "824", + "name": "仁怀市", + "en_name": "Renhuai", + "deleted": false, + "sublist": [] + }, + { + "code": "4675", + "parentCode": "824", + "name": "绥阳县", + "en_name": "Suiyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4674", + "parentCode": "824", + "name": "桐梓县", + "en_name": "Tongzi", + "deleted": false, + "sublist": [] + }, + { + "code": "4678", + "parentCode": "824", + "name": "务川仡佬族苗族自治县", + "en_name": "Wuchuanmulaozumiaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4682", + "parentCode": "824", + "name": "习水县", + "en_name": "Xishui", + "deleted": false, + "sublist": [] + }, + { + "code": "4681", + "parentCode": "824", + "name": "余庆县", + "en_name": "Yuqing", + "deleted": false, + "sublist": [] + }, + { + "code": "4676", + "parentCode": "824", + "name": "正安县", + "en_name": "Zhengan", + "deleted": false, + "sublist": [] + }, + { + "code": "4673", + "parentCode": "824", + "name": "播州区", + "en_name": "Zunyi", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "550", + "parentCode": "489", + "name": "海南", + "en_name": "HAINAN", + "deleted": false, + "sublist": [ + { + "code": "10194", + "parentCode": "550", + "name": "白沙", + "en_name": "BAISHALIZUZIZHI", + "deleted": false, + "sublist": [] + }, + { + "code": "10193", + "parentCode": "550", + "name": "保亭", + "en_name": "BAOTINGLIZUMIAOZUZIZHI", + "deleted": false, + "sublist": [] + }, + { + "code": "10195", + "parentCode": "550", + "name": "昌江", + "en_name": "CHANGJIANGLIZUZIZHI", + "deleted": false, + "sublist": [] + }, + { + "code": "10190", + "parentCode": "550", + "name": "澄迈", + "en_name": "CHENGMAI", + "deleted": false, + "sublist": [] + }, + { + "code": "10183", + "parentCode": "550", + "name": "儋州", + "en_name": "DANZHOU", + "deleted": false, + "sublist": [] + }, + { + "code": "10188", + "parentCode": "550", + "name": "定安", + "en_name": "ANDING", + "deleted": false, + "sublist": [] + }, + { + "code": "10187", + "parentCode": "550", + "name": "东方", + "en_name": "DONGFANG", + "deleted": false, + "sublist": [] + }, + { + "code": "799", + "parentCode": "550", + "name": "海口", + "en_name": "HAIKOU", + "deleted": false, + "sublist": [ + { + "code": "3144", + "parentCode": "799", + "name": "龙华区", + "en_name": "Longhuaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3146", + "parentCode": "799", + "name": "美兰区", + "en_name": "Meilanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3145", + "parentCode": "799", + "name": "琼山区", + "en_name": "Qiongshanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3143", + "parentCode": "799", + "name": "秀英区", + "en_name": "Xiuyingqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "10196", + "parentCode": "550", + "name": "乐东", + "en_name": "LEDONGLIZUZIZHI", + "deleted": false, + "sublist": [] + }, + { + "code": "10191", + "parentCode": "550", + "name": "临高", + "en_name": "LINGAO", + "deleted": false, + "sublist": [] + }, + { + "code": "10197", + "parentCode": "550", + "name": "陵水", + "en_name": "LINGSHUILIZUZIZHI", + "deleted": false, + "sublist": [] + }, + { + "code": "10153", + "parentCode": "550", + "name": "琼海", + "en_name": "QIONGHAI", + "deleted": false, + "sublist": [] + }, + { + "code": "10192", + "parentCode": "550", + "name": "琼中", + "en_name": "QIONGZHONGLIZUMIAOZUZIZHI", + "deleted": false, + "sublist": [] + }, + { + "code": "10303", + "parentCode": "550", + "name": "三沙", + "en_name": "SANSHA", + "deleted": false, + "sublist": [ + { + "code": "104043", + "parentCode": "10303", + "name": "西沙区", + "en_name": "xishaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "104044", + "parentCode": "10303", + "name": "南沙区", + "en_name": "nanshaqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "800", + "parentCode": "550", + "name": "三亚", + "en_name": "SANYA", + "deleted": false, + "sublist": [ + { + "code": "104023", + "parentCode": "800", + "name": "海棠区", + "en_name": "haitangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "104024", + "parentCode": "800", + "name": "吉阳区", + "en_name": "jiyangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "104025", + "parentCode": "800", + "name": "天涯区", + "en_name": "tianyaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "104026", + "parentCode": "800", + "name": "崖州区", + "en_name": "yazhouqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "10189", + "parentCode": "550", + "name": "屯昌", + "en_name": "TUNCHANG", + "deleted": false, + "sublist": [] + }, + { + "code": "10186", + "parentCode": "550", + "name": "万宁", + "en_name": "WANNING", + "deleted": false, + "sublist": [] + }, + { + "code": "10185", + "parentCode": "550", + "name": "文昌", + "en_name": "WENCHANG", + "deleted": false, + "sublist": [] + }, + { + "code": "10184", + "parentCode": "550", + "name": "五指山", + "en_name": "WUZHISHAN", + "deleted": false, + "sublist": [] + }, + { + "code": "907", + "parentCode": "550", + "name": "洋浦市/洋浦经济开发区", + "en_name": "YANGPU", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "532", + "parentCode": "489", + "name": "河北", + "en_name": "HEBEI", + "deleted": false, + "sublist": [ + { + "code": "570", + "parentCode": "532", + "name": "保定", + "en_name": "BAODING", + "deleted": false, + "sublist": [ + { + "code": "3319", + "parentCode": "570", + "name": "安国市", + "en_name": "Anguoshi", + "deleted": false, + "sublist": [] + }, + { + "code": "2761", + "parentCode": "570", + "name": "安新县", + "en_name": "Anxin", + "deleted": false, + "sublist": [] + }, + { + "code": "2766", + "parentCode": "570", + "name": "博野县", + "en_name": "Boye", + "deleted": false, + "sublist": [] + }, + { + "code": "2755", + "parentCode": "570", + "name": "定兴县", + "en_name": "Dingxing", + "deleted": false, + "sublist": [] + }, + { + "code": "3323", + "parentCode": "570", + "name": "定州市", + "en_name": "Dingzhoushi", + "deleted": false, + "sublist": [] + }, + { + "code": "2753", + "parentCode": "570", + "name": "阜平县", + "en_name": "Fuping", + "deleted": false, + "sublist": [] + }, + { + "code": "3320", + "parentCode": "570", + "name": "高碑店市", + "en_name": "Gaobeidianshi", + "deleted": false, + "sublist": [] + }, + { + "code": "2757", + "parentCode": "570", + "name": "高阳县", + "en_name": "Gaoyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2752", + "parentCode": "570", + "name": "涞水县", + "en_name": "Laishui", + "deleted": false, + "sublist": [] + }, + { + "code": "2759", + "parentCode": "570", + "name": "涞源县", + "en_name": "Laiyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2764", + "parentCode": "570", + "name": "蠡县", + "en_name": "Li", + "deleted": false, + "sublist": [] + }, + { + "code": "3321", + "parentCode": "570", + "name": "满城区", + "en_name": "Manchengqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2744", + "parentCode": "570", + "name": "莲池区", + "en_name": "Nanshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3322", + "parentCode": "570", + "name": "清苑区", + "en_name": "Qingyuanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2763", + "parentCode": "570", + "name": "曲阳县", + "en_name": "Yang", + "deleted": false, + "sublist": [] + }, + { + "code": "2758", + "parentCode": "570", + "name": "容城县", + "en_name": "Rongcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2765", + "parentCode": "570", + "name": "顺平县", + "en_name": "Shunping", + "deleted": false, + "sublist": [] + }, + { + "code": "2756", + "parentCode": "570", + "name": "唐县", + "en_name": "Tang", + "deleted": false, + "sublist": [] + }, + { + "code": "2760", + "parentCode": "570", + "name": "望都县", + "en_name": "Wangdu", + "deleted": false, + "sublist": [] + }, + { + "code": "2743", + "parentCode": "570", + "name": "竞秀区", + "en_name": "Xinshi", + "deleted": false, + "sublist": [] + }, + { + "code": "2767", + "parentCode": "570", + "name": "雄县", + "en_name": "Xiong", + "deleted": false, + "sublist": [] + }, + { + "code": "2754", + "parentCode": "570", + "name": "徐水区", + "en_name": "Xushui", + "deleted": false, + "sublist": [] + }, + { + "code": "2762", + "parentCode": "570", + "name": "易县", + "en_name": "Yi", + "deleted": false, + "sublist": [] + }, + { + "code": "3318", + "parentCode": "570", + "name": "涿州市", + "en_name": "Zhuozhoushi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "573", + "parentCode": "532", + "name": "沧州", + "en_name": "CANGZHOU", + "deleted": false, + "sublist": [ + { + "code": "2798", + "parentCode": "573", + "name": "泊头市", + "en_name": "Botou", + "deleted": false, + "sublist": [] + }, + { + "code": "2802", + "parentCode": "573", + "name": "沧县", + "en_name": "Cang", + "deleted": false, + "sublist": [] + }, + { + "code": "2805", + "parentCode": "573", + "name": "东光县", + "en_name": "Dongguang", + "deleted": false, + "sublist": [] + }, + { + "code": "2806", + "parentCode": "573", + "name": "海兴县", + "en_name": "Haixing", + "deleted": false, + "sublist": [] + }, + { + "code": "2801", + "parentCode": "573", + "name": "河间市", + "en_name": "Hejian", + "deleted": false, + "sublist": [] + }, + { + "code": "2800", + "parentCode": "573", + "name": "黄骅市", + "en_name": "Huanghua", + "deleted": false, + "sublist": [] + }, + { + "code": "2811", + "parentCode": "573", + "name": "孟村回族自治县", + "en_name": "Mengcunhuzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "2809", + "parentCode": "573", + "name": "南皮县", + "en_name": "Nanpi", + "deleted": false, + "sublist": [] + }, + { + "code": "2803", + "parentCode": "573", + "name": "青县", + "en_name": "Qing", + "deleted": false, + "sublist": [] + }, + { + "code": "2799", + "parentCode": "573", + "name": "任丘市", + "en_name": "Renqiu", + "deleted": false, + "sublist": [] + }, + { + "code": "2808", + "parentCode": "573", + "name": "肃宁县", + "en_name": "Suning", + "deleted": false, + "sublist": [] + }, + { + "code": "2810", + "parentCode": "573", + "name": "吴桥县", + "en_name": "Wuqiao", + "deleted": false, + "sublist": [] + }, + { + "code": "2804", + "parentCode": "573", + "name": "献县", + "en_name": "Xian", + "deleted": false, + "sublist": [] + }, + { + "code": "2797", + "parentCode": "573", + "name": "新华区", + "en_name": "Xinhua", + "deleted": false, + "sublist": [] + }, + { + "code": "2807", + "parentCode": "573", + "name": "盐山县", + "en_name": "Yanshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2796", + "parentCode": "573", + "name": "运河区", + "en_name": "Yunhe", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "572", + "parentCode": "532", + "name": "承德", + "en_name": "CHENGDE", + "deleted": false, + "sublist": [ + { + "code": "2788", + "parentCode": "572", + "name": "承德县", + "en_name": "Chengde", + "deleted": false, + "sublist": [] + }, + { + "code": "2793", + "parentCode": "572", + "name": "丰宁满族自治县", + "en_name": "Fengningmanzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "2795", + "parentCode": "572", + "name": "宽城满族自治县", + "en_name": "Kuanchengmanzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "2790", + "parentCode": "572", + "name": "隆化县", + "en_name": "Longhua", + "deleted": false, + "sublist": [] + }, + { + "code": "2792", + "parentCode": "572", + "name": "滦平县", + "en_name": "Luanping", + "deleted": false, + "sublist": [] + }, + { + "code": "2791", + "parentCode": "572", + "name": "平泉市", + "en_name": "Pingquan", + "deleted": false, + "sublist": [] + }, + { + "code": "2786", + "parentCode": "572", + "name": "双滦区", + "en_name": "Shuangluan", + "deleted": false, + "sublist": [] + }, + { + "code": "2785", + "parentCode": "572", + "name": "双桥区", + "en_name": "Shuangqiao", + "deleted": false, + "sublist": [] + }, + { + "code": "2794", + "parentCode": "572", + "name": "围场满族蒙古族自治县", + "en_name": "Weichangmanzumengguzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "2789", + "parentCode": "572", + "name": "兴隆县", + "en_name": "Xinglong", + "deleted": false, + "sublist": [] + }, + { + "code": "2787", + "parentCode": "572", + "name": "鹰手营子矿区", + "en_name": "Yingshouyingzikuang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "568", + "parentCode": "532", + "name": "邯郸", + "en_name": "HANDAN", + "deleted": false, + "sublist": [ + { + "code": "2715", + "parentCode": "568", + "name": "成安县", + "en_name": "Chengan", + "deleted": false, + "sublist": [] + }, + { + "code": "2723", + "parentCode": "568", + "name": "磁县", + "en_name": "Ci", + "deleted": false, + "sublist": [] + }, + { + "code": "2705", + "parentCode": "568", + "name": "丛台区", + "en_name": "Congtai", + "deleted": false, + "sublist": [] + }, + { + "code": "2716", + "parentCode": "568", + "name": "大名县", + "en_name": "Daming", + "deleted": false, + "sublist": [] + }, + { + "code": "2721", + "parentCode": "568", + "name": "肥乡区", + "en_name": "Feixiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2708", + "parentCode": "568", + "name": "峰峰矿区", + "en_name": "Fengfengkuang", + "deleted": false, + "sublist": [] + }, + { + "code": "2706", + "parentCode": "568", + "name": "复兴区", + "en_name": "Fuxing", + "deleted": false, + "sublist": [] + }, + { + "code": "2720", + "parentCode": "568", + "name": "广平县", + "en_name": "Guangping", + "deleted": false, + "sublist": [] + }, + { + "code": "2713", + "parentCode": "568", + "name": "馆陶县", + "en_name": "Guantao", + "deleted": false, + "sublist": [] + }, + { + "code": "2707", + "parentCode": "568", + "name": "邯山区", + "en_name": "Hanshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2718", + "parentCode": "568", + "name": "鸡泽县", + "en_name": "Jize", + "deleted": false, + "sublist": [] + }, + { + "code": "2722", + "parentCode": "568", + "name": "临漳县", + "en_name": "Linzhang", + "deleted": false, + "sublist": [] + }, + { + "code": "2719", + "parentCode": "568", + "name": "邱县", + "en_name": "Qiu", + "deleted": false, + "sublist": [] + }, + { + "code": "2712", + "parentCode": "568", + "name": "曲周县", + "en_name": "Quzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "2717", + "parentCode": "568", + "name": "涉县", + "en_name": "She", + "deleted": false, + "sublist": [] + }, + { + "code": "2714", + "parentCode": "568", + "name": "魏县", + "en_name": "Wei", + "deleted": false, + "sublist": [] + }, + { + "code": "2709", + "parentCode": "568", + "name": "武安市", + "en_name": "Wuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2711", + "parentCode": "568", + "name": "永年区", + "en_name": "Yongnian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "575", + "parentCode": "532", + "name": "衡水", + "en_name": "HENGSHUI", + "deleted": false, + "sublist": [ + { + "code": "2829", + "parentCode": "575", + "name": "安平县", + "en_name": "Anping", + "deleted": false, + "sublist": [] + }, + { + "code": "2828", + "parentCode": "575", + "name": "阜城县", + "en_name": "Fucheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2827", + "parentCode": "575", + "name": "故城县", + "en_name": "Gucheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2831", + "parentCode": "575", + "name": "景县", + "en_name": "Jing", + "deleted": false, + "sublist": [] + }, + { + "code": "2823", + "parentCode": "575", + "name": "冀州区", + "en_name": "Jizhou", + "deleted": false, + "sublist": [] + }, + { + "code": "2825", + "parentCode": "575", + "name": "饶阳县", + "en_name": "Raoyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2824", + "parentCode": "575", + "name": "深州市", + "en_name": "Shenzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "2822", + "parentCode": "575", + "name": "桃城区", + "en_name": "Taocheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2832", + "parentCode": "575", + "name": "武强县", + "en_name": "Wuqiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2830", + "parentCode": "575", + "name": "武邑县", + "en_name": "Wuyi", + "deleted": false, + "sublist": [] + }, + { + "code": "2826", + "parentCode": "575", + "name": "枣强县", + "en_name": "Zaoqiang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "574", + "parentCode": "532", + "name": "廊坊", + "en_name": "LANGFANG", + "deleted": false, + "sublist": [ + { + "code": "2813", + "parentCode": "574", + "name": "安次区", + "en_name": "Anci", + "deleted": false, + "sublist": [] + }, + { + "code": "3309", + "parentCode": "574", + "name": "霸州市", + "en_name": "Bazhoushi", + "deleted": false, + "sublist": [] + }, + { + "code": "3316", + "parentCode": "574", + "name": "大厂回族自治县", + "en_name": "Dachanghuizuzizhixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3314", + "parentCode": "574", + "name": "大城县", + "en_name": "Dachengxian", + "deleted": false, + "sublist": [] + }, + { + "code": "2812", + "parentCode": "574", + "name": "广阳区", + "en_name": "Guangyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3312", + "parentCode": "574", + "name": "固安县", + "en_name": "Guanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3310", + "parentCode": "574", + "name": "三河市", + "en_name": "Sanheshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3315", + "parentCode": "574", + "name": "文安县", + "en_name": "Wenanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3313", + "parentCode": "574", + "name": "香河县", + "en_name": "Xianghexian", + "deleted": false, + "sublist": [] + }, + { + "code": "3317", + "parentCode": "574", + "name": "燕郊开发区", + "en_name": "Yanjiaokaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3311", + "parentCode": "574", + "name": "永清县", + "en_name": "Yongqingxian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "567", + "parentCode": "532", + "name": "秦皇岛", + "en_name": "QINHUANGDAO", + "deleted": false, + "sublist": [ + { + "code": "2700", + "parentCode": "567", + "name": "北戴河区", + "en_name": "Beidaihe", + "deleted": false, + "sublist": [] + }, + { + "code": "2701", + "parentCode": "567", + "name": "昌黎县", + "en_name": "Changli", + "deleted": false, + "sublist": [] + }, + { + "code": "2703", + "parentCode": "567", + "name": "抚宁区", + "en_name": "Funing", + "deleted": false, + "sublist": [] + }, + { + "code": "2698", + "parentCode": "567", + "name": "海港区", + "en_name": "海港区", + "deleted": false, + "sublist": [] + }, + { + "code": "2702", + "parentCode": "567", + "name": "卢龙县", + "en_name": "Lulong", + "deleted": false, + "sublist": [] + }, + { + "code": "2704", + "parentCode": "567", + "name": "青龙满族自治县", + "en_name": "Qinglongmanzu", + "deleted": false, + "sublist": [] + }, + { + "code": "2699", + "parentCode": "567", + "name": "山海关区", + "en_name": "Shanhaiguan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "565", + "parentCode": "532", + "name": "石家庄", + "en_name": "SHIJIAZHUANG", + "deleted": false, + "sublist": [ + { + "code": "2293", + "parentCode": "565", + "name": "东开发区", + "en_name": "Dongkaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2296", + "parentCode": "565", + "name": "藁城区", + "en_name": "gaochengqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2418", + "parentCode": "565", + "name": "高邑县", + "en_name": "Gaoyi", + "deleted": false, + "sublist": [] + }, + { + "code": "2294", + "parentCode": "565", + "name": "井陉矿区", + "en_name": "Jingxingkuangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2420", + "parentCode": "565", + "name": "井陉县", + "en_name": "Jingxing", + "deleted": false, + "sublist": [] + }, + { + "code": "2297", + "parentCode": "565", + "name": "晋州市", + "en_name": "Jinzhoushi", + "deleted": false, + "sublist": [] + }, + { + "code": "2414", + "parentCode": "565", + "name": "灵寿县", + "en_name": "Lingshou", + "deleted": false, + "sublist": [] + }, + { + "code": "2412", + "parentCode": "565", + "name": "栾城区", + "en_name": "Luanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2299", + "parentCode": "565", + "name": "鹿泉区", + "en_name": "Luquanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2301", + "parentCode": "565", + "name": "平山县", + "en_name": "Pingshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2290", + "parentCode": "565", + "name": "桥西区", + "en_name": "Qiaoxi", + "deleted": false, + "sublist": [] + }, + { + "code": "2415", + "parentCode": "565", + "name": "深泽县", + "en_name": "Shenze", + "deleted": false, + "sublist": [] + }, + { + "code": "2416", + "parentCode": "565", + "name": "无极县", + "en_name": "Wuji", + "deleted": false, + "sublist": [] + }, + { + "code": "2413", + "parentCode": "565", + "name": "行唐县", + "en_name": "Xingtang", + "deleted": false, + "sublist": [] + }, + { + "code": "2291", + "parentCode": "565", + "name": "新华区", + "en_name": "Xinhua", + "deleted": false, + "sublist": [] + }, + { + "code": "2295", + "parentCode": "565", + "name": "辛集市", + "en_name": "Xinjishi", + "deleted": false, + "sublist": [] + }, + { + "code": "2298", + "parentCode": "565", + "name": "新乐市", + "en_name": "Xinleshi", + "deleted": false, + "sublist": [] + }, + { + "code": "2302", + "parentCode": "565", + "name": "元氏县", + "en_name": "Yuanshi", + "deleted": false, + "sublist": [] + }, + { + "code": "2292", + "parentCode": "565", + "name": "裕华区", + "en_name": "Yuhua", + "deleted": false, + "sublist": [] + }, + { + "code": "2419", + "parentCode": "565", + "name": "赞皇县", + "en_name": "Zanhuang", + "deleted": false, + "sublist": [] + }, + { + "code": "2288", + "parentCode": "565", + "name": "长安区", + "en_name": "Changan", + "deleted": false, + "sublist": [] + }, + { + "code": "2417", + "parentCode": "565", + "name": "赵县", + "en_name": "Zhaoxian", + "deleted": false, + "sublist": [] + }, + { + "code": "2300", + "parentCode": "565", + "name": "正定县", + "en_name": "Zhengding", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "566", + "parentCode": "532", + "name": "唐山", + "en_name": "TANGSHAN", + "deleted": false, + "sublist": [ + { + "code": "10143", + "parentCode": "566", + "name": "遵化市", + "en_name": "ZUNHUA", + "deleted": false, + "sublist": [] + }, + { + "code": "2690", + "parentCode": "566", + "name": "曹妃甸区", + "en_name": "Caofeidian", + "deleted": false, + "sublist": [] + }, + { + "code": "2688", + "parentCode": "566", + "name": "丰南区", + "en_name": "Fengnan", + "deleted": false, + "sublist": [] + }, + { + "code": "2689", + "parentCode": "566", + "name": "丰润区", + "en_name": "Fengrun", + "deleted": false, + "sublist": [] + }, + { + "code": "2686", + "parentCode": "566", + "name": "古冶区", + "en_name": "Guye", + "deleted": false, + "sublist": [] + }, + { + "code": "2687", + "parentCode": "566", + "name": "开平区", + "en_name": "Kaiping", + "deleted": false, + "sublist": [] + }, + { + "code": "2696", + "parentCode": "566", + "name": "乐亭县", + "en_name": "Leting", + "deleted": false, + "sublist": [] + }, + { + "code": "2694", + "parentCode": "566", + "name": "滦南县", + "en_name": "Luannan", + "deleted": false, + "sublist": [] + }, + { + "code": "2697", + "parentCode": "566", + "name": "滦州市", + "en_name": "Luan", + "deleted": false, + "sublist": [] + }, + { + "code": "2684", + "parentCode": "566", + "name": "路北区", + "en_name": "Lubei", + "deleted": false, + "sublist": [] + }, + { + "code": "2685", + "parentCode": "566", + "name": "路南区", + "en_name": "Lunan", + "deleted": false, + "sublist": [] + }, + { + "code": "2692", + "parentCode": "566", + "name": "迁安市", + "en_name": "Qianan", + "deleted": false, + "sublist": [] + }, + { + "code": "2693", + "parentCode": "566", + "name": "迁西县", + "en_name": "Qianxi", + "deleted": false, + "sublist": [] + }, + { + "code": "2695", + "parentCode": "566", + "name": "玉田县", + "en_name": "Yutian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "569", + "parentCode": "532", + "name": "邢台", + "en_name": "XINGTAI", + "deleted": false, + "sublist": [ + { + "code": "2729", + "parentCode": "569", + "name": "柏乡县", + "en_name": "Baixiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2736", + "parentCode": "569", + "name": "广宗县", + "en_name": "Guangzong", + "deleted": false, + "sublist": [] + }, + { + "code": "2740", + "parentCode": "569", + "name": "巨鹿县", + "en_name": "Julu", + "deleted": false, + "sublist": [] + }, + { + "code": "2735", + "parentCode": "569", + "name": "临城县", + "en_name": "Lincheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2737", + "parentCode": "569", + "name": "临西县", + "en_name": "Linxi", + "deleted": false, + "sublist": [] + }, + { + "code": "2734", + "parentCode": "569", + "name": "隆尧县", + "en_name": "Longyao", + "deleted": false, + "sublist": [] + }, + { + "code": "2726", + "parentCode": "569", + "name": "南宫市", + "en_name": "Nangong", + "deleted": false, + "sublist": [] + }, + { + "code": "2742", + "parentCode": "569", + "name": "南和区", + "en_name": "nanhequ", + "deleted": false, + "sublist": [] + }, + { + "code": "2738", + "parentCode": "569", + "name": "内丘县", + "en_name": "Neiqiu", + "deleted": false, + "sublist": [] + }, + { + "code": "2732", + "parentCode": "569", + "name": "宁晋县", + "en_name": "Ningjin", + "deleted": false, + "sublist": [] + }, + { + "code": "2739", + "parentCode": "569", + "name": "平乡县", + "en_name": "Pingxiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2724", + "parentCode": "569", + "name": "襄都区", + "en_name": "xiangdouqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2725", + "parentCode": "569", + "name": "信都区", + "en_name": "xindouqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2731", + "parentCode": "569", + "name": "清河县", + "en_name": "Qinghe", + "deleted": false, + "sublist": [] + }, + { + "code": "2730", + "parentCode": "569", + "name": "任泽区", + "en_name": "renzequ", + "deleted": false, + "sublist": [] + }, + { + "code": "2727", + "parentCode": "569", + "name": "沙河市", + "en_name": "Shahe", + "deleted": false, + "sublist": [] + }, + { + "code": "2733", + "parentCode": "569", + "name": "威县", + "en_name": "Wei", + "deleted": false, + "sublist": [] + }, + { + "code": "2728", + "parentCode": "569", + "name": "邢台县", + "en_name": "Xingtai", + "deleted": false, + "sublist": [] + }, + { + "code": "2741", + "parentCode": "569", + "name": "新河县", + "en_name": "Xinhe", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "571", + "parentCode": "532", + "name": "张家口", + "en_name": "ZHANGJIAKOU", + "deleted": false, + "sublist": [ + { + "code": "2776", + "parentCode": "571", + "name": "赤城县", + "en_name": "Chicheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2780", + "parentCode": "571", + "name": "崇礼区", + "en_name": "Chongli", + "deleted": false, + "sublist": [] + }, + { + "code": "2777", + "parentCode": "571", + "name": "沽源县", + "en_name": "Guyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2778", + "parentCode": "571", + "name": "怀安县", + "en_name": "Huaian", + "deleted": false, + "sublist": [] + }, + { + "code": "2779", + "parentCode": "571", + "name": "怀来县", + "en_name": "Huailai", + "deleted": false, + "sublist": [] + }, + { + "code": "2773", + "parentCode": "571", + "name": "康保县", + "en_name": "Kangbao", + "deleted": false, + "sublist": [] + }, + { + "code": "2769", + "parentCode": "571", + "name": "桥东区", + "en_name": "Qiaodong", + "deleted": false, + "sublist": [] + }, + { + "code": "2768", + "parentCode": "571", + "name": "桥西区", + "en_name": "Qiaoxi", + "deleted": false, + "sublist": [] + }, + { + "code": "2781", + "parentCode": "571", + "name": "尚义县", + "en_name": "Shangyi", + "deleted": false, + "sublist": [] + }, + { + "code": "2784", + "parentCode": "571", + "name": "万全区", + "en_name": "Wanquan", + "deleted": false, + "sublist": [] + }, + { + "code": "2771", + "parentCode": "571", + "name": "下花园区", + "en_name": "Xiahuayuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2770", + "parentCode": "571", + "name": "宣化区", + "en_name": "Xuanhua", + "deleted": false, + "sublist": [] + }, + { + "code": "2775", + "parentCode": "571", + "name": "阳原县", + "en_name": "Yangyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2782", + "parentCode": "571", + "name": "蔚县", + "en_name": "Wei", + "deleted": false, + "sublist": [] + }, + { + "code": "2774", + "parentCode": "571", + "name": "张北县", + "en_name": "Zhangbei", + "deleted": false, + "sublist": [] + }, + { + "code": "2783", + "parentCode": "571", + "name": "涿鹿县", + "en_name": "Zhuolu", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "537", + "parentCode": "489", + "name": "黑龙江", + "en_name": "HEILONGJIANG", + "deleted": false, + "sublist": [ + { + "code": "627", + "parentCode": "537", + "name": "大庆", + "en_name": "DAQING", + "deleted": false, + "sublist": [ + { + "code": "103283", + "parentCode": "627", + "name": "大同区", + "en_name": "Datong", + "deleted": false, + "sublist": [] + }, + { + "code": "103287", + "parentCode": "627", + "name": "杜尔伯特蒙古族自治县", + "en_name": "Duerbotemengguzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "103281", + "parentCode": "627", + "name": "红岗区", + "en_name": "Honggang", + "deleted": false, + "sublist": [] + }, + { + "code": "103286", + "parentCode": "627", + "name": "林甸县", + "en_name": "Lindian", + "deleted": false, + "sublist": [] + }, + { + "code": "103280", + "parentCode": "627", + "name": "龙凤区", + "en_name": "Longfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "103282", + "parentCode": "627", + "name": "让胡路区", + "en_name": "Ronghulu", + "deleted": false, + "sublist": [] + }, + { + "code": "103279", + "parentCode": "627", + "name": "萨尔图区", + "en_name": "Saertu", + "deleted": false, + "sublist": [] + }, + { + "code": "103285", + "parentCode": "627", + "name": "肇源县", + "en_name": "Zhaoyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "103284", + "parentCode": "627", + "name": "肇州县", + "en_name": "Zhaozhou", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "634", + "parentCode": "537", + "name": "大兴安岭", + "en_name": "DAXINGANLING", + "deleted": false, + "sublist": [ + { + "code": "103345", + "parentCode": "634", + "name": "呼玛县", + "en_name": "Huma", + "deleted": false, + "sublist": [] + }, + { + "code": "103347", + "parentCode": "634", + "name": "漠河市", + "en_name": "Mohe", + "deleted": false, + "sublist": [] + }, + { + "code": "103346", + "parentCode": "634", + "name": "塔河县", + "en_name": "Tahe", + "deleted": false, + "sublist": [] + }, + { + "code": "104045", + "parentCode": "634", + "name": "加格达奇区", + "en_name": "JIAGEDAQIQU", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "622", + "parentCode": "537", + "name": "哈尔滨", + "en_name": "HAERBIN", + "deleted": false, + "sublist": [ + { + "code": "10160", + "parentCode": "622", + "name": "尚志市", + "en_name": "SHANGZHI", + "deleted": false, + "sublist": [] + }, + { + "code": "10159", + "parentCode": "622", + "name": "双城区", + "en_name": "SHUANGCHENG", + "deleted": false, + "sublist": [] + }, + { + "code": "2277", + "parentCode": "622", + "name": "阿城区", + "en_name": "Acheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2429", + "parentCode": "622", + "name": "巴彦县", + "en_name": "Bayan", + "deleted": false, + "sublist": [] + }, + { + "code": "2428", + "parentCode": "622", + "name": "宾县", + "en_name": "Binxian", + "deleted": false, + "sublist": [] + }, + { + "code": "2271", + "parentCode": "622", + "name": "道里区", + "en_name": "Daoli", + "deleted": false, + "sublist": [] + }, + { + "code": "2272", + "parentCode": "622", + "name": "道外区", + "en_name": "Daowai", + "deleted": false, + "sublist": [] + }, + { + "code": "2426", + "parentCode": "622", + "name": "方正县", + "en_name": "Fangzheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2276", + "parentCode": "622", + "name": "呼兰区", + "en_name": "Hulan", + "deleted": false, + "sublist": [] + }, + { + "code": "2430", + "parentCode": "622", + "name": "木兰县", + "en_name": "Mulan", + "deleted": false, + "sublist": [] + }, + { + "code": "2270", + "parentCode": "622", + "name": "南岗区", + "en_name": "Nangang", + "deleted": false, + "sublist": [] + }, + { + "code": "2275", + "parentCode": "622", + "name": "平房区", + "en_name": "Pingfang", + "deleted": false, + "sublist": [] + }, + { + "code": "2274", + "parentCode": "622", + "name": "松北区", + "en_name": "Songbei", + "deleted": false, + "sublist": [] + }, + { + "code": "2431", + "parentCode": "622", + "name": "通河县", + "en_name": "Tonghe", + "deleted": false, + "sublist": [] + }, + { + "code": "2424", + "parentCode": "622", + "name": "五常市", + "en_name": "Wuchang", + "deleted": false, + "sublist": [] + }, + { + "code": "2273", + "parentCode": "622", + "name": "香坊区", + "en_name": "Xiangfang", + "deleted": false, + "sublist": [] + }, + { + "code": "2432", + "parentCode": "622", + "name": "延寿县", + "en_name": "Yanshou", + "deleted": false, + "sublist": [] + }, + { + "code": "2427", + "parentCode": "622", + "name": "依兰县", + "en_name": "Yilan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "625", + "parentCode": "537", + "name": "鹤岗", + "en_name": "HEGANG", + "deleted": false, + "sublist": [ + { + "code": "103267", + "parentCode": "625", + "name": "东山区", + "en_name": "Dongshan", + "deleted": false, + "sublist": [] + }, + { + "code": "103264", + "parentCode": "625", + "name": "工农区", + "en_name": "Gongnong", + "deleted": false, + "sublist": [] + }, + { + "code": "103269", + "parentCode": "625", + "name": "萝北县", + "en_name": "Luobei", + "deleted": false, + "sublist": [] + }, + { + "code": "103265", + "parentCode": "625", + "name": "南山区", + "en_name": "Nanshan", + "deleted": false, + "sublist": [] + }, + { + "code": "103270", + "parentCode": "625", + "name": "绥滨县", + "en_name": "Suibin", + "deleted": false, + "sublist": [] + }, + { + "code": "103263", + "parentCode": "625", + "name": "向阳区", + "en_name": "Xiangyang", + "deleted": false, + "sublist": [] + }, + { + "code": "103266", + "parentCode": "625", + "name": "兴安区", + "en_name": "Xingan", + "deleted": false, + "sublist": [] + }, + { + "code": "103268", + "parentCode": "625", + "name": "兴山区", + "en_name": "Xingshan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "632", + "parentCode": "537", + "name": "黑河", + "en_name": "HEIHE", + "deleted": false, + "sublist": [ + { + "code": "103329", + "parentCode": "632", + "name": "爱辉区", + "en_name": "Aihui", + "deleted": false, + "sublist": [] + }, + { + "code": "103330", + "parentCode": "632", + "name": "北安市", + "en_name": "Beian", + "deleted": false, + "sublist": [] + }, + { + "code": "103332", + "parentCode": "632", + "name": "嫩江市", + "en_name": "Nenjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "103334", + "parentCode": "632", + "name": "孙吴县", + "en_name": "Sunwu", + "deleted": false, + "sublist": [] + }, + { + "code": "103331", + "parentCode": "632", + "name": "五大连池市", + "en_name": "Wudalianchi", + "deleted": false, + "sublist": [] + }, + { + "code": "103333", + "parentCode": "632", + "name": "逊克县", + "en_name": "Xunke", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "629", + "parentCode": "537", + "name": "佳木斯", + "en_name": "JIAMUSI", + "deleted": false, + "sublist": [ + { + "code": "103307", + "parentCode": "629", + "name": "东风区", + "en_name": "Dongfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "103310", + "parentCode": "629", + "name": "富锦市", + "en_name": "Fujin", + "deleted": false, + "sublist": [] + }, + { + "code": "103314", + "parentCode": "629", + "name": "抚远市", + "en_name": "Fuyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "103312", + "parentCode": "629", + "name": "桦川县", + "en_name": "Yechuan", + "deleted": false, + "sublist": [] + }, + { + "code": "103311", + "parentCode": "629", + "name": "桦南县", + "en_name": "Yenan", + "deleted": false, + "sublist": [] + }, + { + "code": "103308", + "parentCode": "629", + "name": "郊区", + "en_name": "Jiao", + "deleted": false, + "sublist": [] + }, + { + "code": "103306", + "parentCode": "629", + "name": "前进区", + "en_name": "Qianjin", + "deleted": false, + "sublist": [] + }, + { + "code": "103313", + "parentCode": "629", + "name": "汤原县", + "en_name": "Tangyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "103309", + "parentCode": "629", + "name": "同江市", + "en_name": "Tongjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "103305", + "parentCode": "629", + "name": "向阳区", + "en_name": "Xiangyang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "624", + "parentCode": "537", + "name": "鸡西", + "en_name": "JIXI", + "deleted": false, + "sublist": [ + { + "code": "3258", + "parentCode": "624", + "name": "城子河区", + "en_name": "Chengzihe", + "deleted": false, + "sublist": [] + }, + { + "code": "103256", + "parentCode": "624", + "name": "滴道区", + "en_name": "Didao", + "deleted": false, + "sublist": [] + }, + { + "code": "103255", + "parentCode": "624", + "name": "恒山区", + "en_name": "Hengshan", + "deleted": false, + "sublist": [] + }, + { + "code": "103260", + "parentCode": "624", + "name": "虎林市", + "en_name": "Hulin", + "deleted": false, + "sublist": [] + }, + { + "code": "103262", + "parentCode": "624", + "name": "鸡东县", + "en_name": "Jidong", + "deleted": false, + "sublist": [] + }, + { + "code": "103254", + "parentCode": "624", + "name": "鸡冠区", + "en_name": "Jiguan", + "deleted": false, + "sublist": [] + }, + { + "code": "103257", + "parentCode": "624", + "name": "梨树区", + "en_name": "Lishu", + "deleted": false, + "sublist": [] + }, + { + "code": "103259", + "parentCode": "624", + "name": "麻山区", + "en_name": "Mashan", + "deleted": false, + "sublist": [] + }, + { + "code": "103261", + "parentCode": "624", + "name": "密山市", + "en_name": "Mishan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "631", + "parentCode": "537", + "name": "牡丹江", + "en_name": "MUDANJIANG", + "deleted": false, + "sublist": [ + { + "code": "10161", + "parentCode": "631", + "name": "绥芬河市", + "en_name": "SUIFENHE", + "deleted": false, + "sublist": [] + }, + { + "code": "103321", + "parentCode": "631", + "name": "爱民区", + "en_name": "Aimin", + "deleted": false, + "sublist": [] + }, + { + "code": "103319", + "parentCode": "631", + "name": "东安区", + "en_name": "Dongan", + "deleted": false, + "sublist": [] + }, + { + "code": "103327", + "parentCode": "631", + "name": "东宁市", + "en_name": "Dongning", + "deleted": false, + "sublist": [] + }, + { + "code": "103324", + "parentCode": "631", + "name": "海林市", + "en_name": "Hailin", + "deleted": false, + "sublist": [] + }, + { + "code": "103328", + "parentCode": "631", + "name": "林口县", + "en_name": "Linkou", + "deleted": false, + "sublist": [] + }, + { + "code": "103326", + "parentCode": "631", + "name": "穆棱市", + "en_name": "Muling", + "deleted": false, + "sublist": [] + }, + { + "code": "103325", + "parentCode": "631", + "name": "宁安市", + "en_name": "Ningan", + "deleted": false, + "sublist": [] + }, + { + "code": "103322", + "parentCode": "631", + "name": "西安区", + "en_name": "xian", + "deleted": false, + "sublist": [] + }, + { + "code": "103320", + "parentCode": "631", + "name": "阳明区", + "en_name": "Yangming", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "623", + "parentCode": "537", + "name": "齐齐哈尔", + "en_name": "QIQIHAER", + "deleted": false, + "sublist": [ + { + "code": "103241", + "parentCode": "623", + "name": "昂昂溪区", + "en_name": "Angangxi", + "deleted": false, + "sublist": [] + }, + { + "code": "103253", + "parentCode": "623", + "name": "拜泉县", + "en_name": "Baiquan", + "deleted": false, + "sublist": [] + }, + { + "code": "103242", + "parentCode": "623", + "name": "富拉尔基区", + "en_name": "Fulaerji", + "deleted": false, + "sublist": [] + }, + { + "code": "103250", + "parentCode": "623", + "name": "富裕县", + "en_name": "Fuyu", + "deleted": false, + "sublist": [] + }, + { + "code": "103249", + "parentCode": "623", + "name": "甘南县", + "en_name": "Gannan", + "deleted": false, + "sublist": [] + }, + { + "code": "103239", + "parentCode": "623", + "name": "建华区", + "en_name": "Jianhua", + "deleted": false, + "sublist": [] + }, + { + "code": "103252", + "parentCode": "623", + "name": "克东县", + "en_name": "Kedong", + "deleted": false, + "sublist": [] + }, + { + "code": "103251", + "parentCode": "623", + "name": "克山县", + "en_name": "Keshan", + "deleted": false, + "sublist": [] + }, + { + "code": "103246", + "parentCode": "623", + "name": "龙江县", + "en_name": "Longjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "103238", + "parentCode": "623", + "name": "龙沙区", + "en_name": "Longsha", + "deleted": false, + "sublist": [] + }, + { + "code": "103244", + "parentCode": "623", + "name": "梅里斯达斡尔族区", + "en_name": "Meilisidawoerzu", + "deleted": false, + "sublist": [] + }, + { + "code": "103245", + "parentCode": "623", + "name": "讷河市", + "en_name": "Nehe", + "deleted": false, + "sublist": [] + }, + { + "code": "103243", + "parentCode": "623", + "name": "碾子山区", + "en_name": "Nianzishan", + "deleted": false, + "sublist": [] + }, + { + "code": "103248", + "parentCode": "623", + "name": "泰来县", + "en_name": "Tailai", + "deleted": false, + "sublist": [] + }, + { + "code": "103240", + "parentCode": "623", + "name": "铁锋区", + "en_name": "Tiefeng", + "deleted": false, + "sublist": [] + }, + { + "code": "103247", + "parentCode": "623", + "name": "依安县", + "en_name": "Yian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "630", + "parentCode": "537", + "name": "七台河", + "en_name": "QITAIHE", + "deleted": false, + "sublist": [ + { + "code": "103318", + "parentCode": "630", + "name": "勃利县", + "en_name": "Boli", + "deleted": false, + "sublist": [] + }, + { + "code": "103317", + "parentCode": "630", + "name": "茄子河区", + "en_name": "Qiezihe", + "deleted": false, + "sublist": [] + }, + { + "code": "103316", + "parentCode": "630", + "name": "桃山区", + "en_name": "Taoshan", + "deleted": false, + "sublist": [] + }, + { + "code": "103315", + "parentCode": "630", + "name": "新兴区", + "en_name": "Xinxing", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "626", + "parentCode": "537", + "name": "双鸭山", + "en_name": "SHUANGYASHAN", + "deleted": false, + "sublist": [ + { + "code": "103277", + "parentCode": "626", + "name": "宝清县", + "en_name": "Baoqing", + "deleted": false, + "sublist": [] + }, + { + "code": "103274", + "parentCode": "626", + "name": "宝山区", + "en_name": "Baoshan", + "deleted": false, + "sublist": [] + }, + { + "code": "103271", + "parentCode": "626", + "name": "尖山区", + "en_name": "Jianshan", + "deleted": false, + "sublist": [] + }, + { + "code": "103275", + "parentCode": "626", + "name": "集贤县", + "en_name": "Jixian", + "deleted": false, + "sublist": [] + }, + { + "code": "103272", + "parentCode": "626", + "name": "岭东区", + "en_name": "Lingdong", + "deleted": false, + "sublist": [] + }, + { + "code": "103278", + "parentCode": "626", + "name": "饶河县", + "en_name": "Raohe", + "deleted": false, + "sublist": [] + }, + { + "code": "103273", + "parentCode": "626", + "name": "四方台区", + "en_name": "Sifangtai", + "deleted": false, + "sublist": [] + }, + { + "code": "103276", + "parentCode": "626", + "name": "友谊县", + "en_name": "Youyi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "633", + "parentCode": "537", + "name": "绥化", + "en_name": "SUIHUA", + "deleted": false, + "sublist": [ + { + "code": "10081", + "parentCode": "633", + "name": "安达市", + "en_name": "ANDA", + "deleted": false, + "sublist": [] + }, + { + "code": "10510", + "parentCode": "633", + "name": "肇东市", + "en_name": "ZHAODONGSHI", + "deleted": false, + "sublist": [] + }, + { + "code": "103335", + "parentCode": "633", + "name": "北林区", + "en_name": "Beilin", + "deleted": false, + "sublist": [] + }, + { + "code": "103338", + "parentCode": "633", + "name": "海伦市", + "en_name": "Hailun", + "deleted": false, + "sublist": [] + }, + { + "code": "103340", + "parentCode": "633", + "name": "兰西县", + "en_name": "Lanxi", + "deleted": false, + "sublist": [] + }, + { + "code": "103343", + "parentCode": "633", + "name": "明水县", + "en_name": "Mingshui", + "deleted": false, + "sublist": [] + }, + { + "code": "103342", + "parentCode": "633", + "name": "庆安县", + "en_name": "Qingan", + "deleted": false, + "sublist": [] + }, + { + "code": "103341", + "parentCode": "633", + "name": "青冈县", + "en_name": "Qinggang", + "deleted": false, + "sublist": [] + }, + { + "code": "103344", + "parentCode": "633", + "name": "绥棱县", + "en_name": "Suileng", + "deleted": false, + "sublist": [] + }, + { + "code": "103339", + "parentCode": "633", + "name": "望奎县", + "en_name": "Wangkui", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "628", + "parentCode": "537", + "name": "伊春", + "en_name": "YICHUN", + "deleted": false, + "sublist": [ + { + "code": "103304", + "parentCode": "628", + "name": "嘉荫县", + "en_name": "Jiayin", + "deleted": false, + "sublist": [] + }, + { + "code": "103289", + "parentCode": "628", + "name": "南岔县", + "en_name": "Nancha", + "deleted": false, + "sublist": [] + }, + { + "code": "103303", + "parentCode": "628", + "name": "铁力市", + "en_name": "Tielishi", + "deleted": false, + "sublist": [] + }, + { + "code": "103290", + "parentCode": "628", + "name": "友好区", + "en_name": "Youhao", + "deleted": false, + "sublist": [] + }, + { + "code": "104033", + "parentCode": "628", + "name": "乌翠区", + "en_name": "wucuiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "104034", + "parentCode": "628", + "name": "大箐山县", + "en_name": "daqingshanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "104035", + "parentCode": "628", + "name": "汤旺县", + "en_name": "tangwangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "104036", + "parentCode": "628", + "name": "金林区", + "en_name": "jinlinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "104037", + "parentCode": "628", + "name": "丰林县", + "en_name": "fenglinxian", + "deleted": false, + "sublist": [] + }, + { + "code": "104038", + "parentCode": "628", + "name": "伊美区", + "en_name": "yimeiqu", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "545", + "parentCode": "489", + "name": "河南", + "en_name": "HENAN", + "deleted": false, + "sublist": [ + { + "code": "723", + "parentCode": "545", + "name": "安阳", + "en_name": "ANYANG", + "deleted": false, + "sublist": [ + { + "code": "3911", + "parentCode": "723", + "name": "安阳县", + "en_name": "Anyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3907", + "parentCode": "723", + "name": "北关区", + "en_name": "Beiguan", + "deleted": false, + "sublist": [] + }, + { + "code": "3912", + "parentCode": "723", + "name": "滑县", + "en_name": "Hua", + "deleted": false, + "sublist": [] + }, + { + "code": "3910", + "parentCode": "723", + "name": "林州市", + "en_name": "Linzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "3909", + "parentCode": "723", + "name": "龙安区", + "en_name": "Longan", + "deleted": false, + "sublist": [] + }, + { + "code": "3913", + "parentCode": "723", + "name": "内黄县", + "en_name": "Neihuang", + "deleted": false, + "sublist": [] + }, + { + "code": "3914", + "parentCode": "723", + "name": "汤阴县", + "en_name": "Tangyin", + "deleted": false, + "sublist": [] + }, + { + "code": "3906", + "parentCode": "723", + "name": "文峰区", + "en_name": "Wenfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "3908", + "parentCode": "723", + "name": "殷都区", + "en_name": "Yindu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "724", + "parentCode": "545", + "name": "鹤壁", + "en_name": "HEBI", + "deleted": false, + "sublist": [ + { + "code": "3915", + "parentCode": "724", + "name": "鹤山区", + "en_name": "Heshan", + "deleted": false, + "sublist": [] + }, + { + "code": "3918", + "parentCode": "724", + "name": "浚县", + "en_name": "Jun", + "deleted": false, + "sublist": [] + }, + { + "code": "3917", + "parentCode": "724", + "name": "淇滨区", + "en_name": "Qibin", + "deleted": false, + "sublist": [] + }, + { + "code": "3919", + "parentCode": "724", + "name": "淇县", + "en_name": "Qi", + "deleted": false, + "sublist": [] + }, + { + "code": "3916", + "parentCode": "724", + "name": "山城区", + "en_name": "Shancheng", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "726", + "parentCode": "545", + "name": "焦作", + "en_name": "JIAOZUO", + "deleted": false, + "sublist": [ + { + "code": "3939", + "parentCode": "726", + "name": "博爱县", + "en_name": "Boai", + "deleted": false, + "sublist": [] + }, + { + "code": "3932", + "parentCode": "726", + "name": "解放区", + "en_name": "Jiefang", + "deleted": false, + "sublist": [] + }, + { + "code": "3934", + "parentCode": "726", + "name": "马村区", + "en_name": "Macun", + "deleted": false, + "sublist": [] + }, + { + "code": "3937", + "parentCode": "726", + "name": "孟州市", + "en_name": "Mengzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "3936", + "parentCode": "726", + "name": "沁阳市", + "en_name": "Qinyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3935", + "parentCode": "726", + "name": "山阳区", + "en_name": "Shanyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3941", + "parentCode": "726", + "name": "温县", + "en_name": "Wen", + "deleted": false, + "sublist": [] + }, + { + "code": "3940", + "parentCode": "726", + "name": "武陟县", + "en_name": "Wudou", + "deleted": false, + "sublist": [] + }, + { + "code": "3938", + "parentCode": "726", + "name": "修武县", + "en_name": "Xiuwu", + "deleted": false, + "sublist": [] + }, + { + "code": "3933", + "parentCode": "726", + "name": "中站区", + "en_name": "Zhongzhan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "10044", + "parentCode": "545", + "name": "济源市", + "en_name": "JIYUAN", + "deleted": false, + "sublist": [] + }, + { + "code": "720", + "parentCode": "545", + "name": "开封", + "en_name": "KAIFENG", + "deleted": false, + "sublist": [ + { + "code": "3873", + "parentCode": "720", + "name": "鼓楼区", + "en_name": "Gulou", + "deleted": false, + "sublist": [] + }, + { + "code": "3879", + "parentCode": "720", + "name": "祥符区", + "en_name": "Kaifeng", + "deleted": false, + "sublist": [] + }, + { + "code": "3880", + "parentCode": "720", + "name": "兰考县", + "en_name": "Lankao", + "deleted": false, + "sublist": [] + }, + { + "code": "3871", + "parentCode": "720", + "name": "龙亭区", + "en_name": "Longting", + "deleted": false, + "sublist": [] + }, + { + "code": "3876", + "parentCode": "720", + "name": "杞县", + "en_name": "Qi", + "deleted": false, + "sublist": [] + }, + { + "code": "3872", + "parentCode": "720", + "name": "顺河回族区", + "en_name": "Shunhehuizu", + "deleted": false, + "sublist": [] + }, + { + "code": "3877", + "parentCode": "720", + "name": "通许县", + "en_name": "Tongxu", + "deleted": false, + "sublist": [] + }, + { + "code": "3878", + "parentCode": "720", + "name": "尉氏县", + "en_name": "Weishi", + "deleted": false, + "sublist": [] + }, + { + "code": "3874", + "parentCode": "720", + "name": "禹王台区", + "en_name": "Yuwangtai", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "721", + "parentCode": "545", + "name": "洛阳", + "en_name": "LUOYANG", + "deleted": false, + "sublist": [ + { + "code": "3883", + "parentCode": "721", + "name": "瀍河回族区", + "en_name": "Chanhehuizu", + "deleted": false, + "sublist": [] + }, + { + "code": "3330", + "parentCode": "721", + "name": "高新区", + "en_name": "Gaoxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3324", + "parentCode": "721", + "name": "涧西区", + "en_name": "Jianxiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3328", + "parentCode": "721", + "name": "吉利区", + "en_name": "Jiliqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3326", + "parentCode": "721", + "name": "老城区", + "en_name": "Laochengqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3334", + "parentCode": "721", + "name": "栾川县", + "en_name": "Luanchuanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3325", + "parentCode": "721", + "name": "洛龙区", + "en_name": "Luolongqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3333", + "parentCode": "721", + "name": "洛宁县", + "en_name": "Luoningxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3335", + "parentCode": "721", + "name": "孟津区", + "en_name": "mengjinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3337", + "parentCode": "721", + "name": "汝阳县", + "en_name": "Ruyangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3332", + "parentCode": "721", + "name": "嵩县", + "en_name": "Songxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3327", + "parentCode": "721", + "name": "西工区", + "en_name": "Xigongqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3338", + "parentCode": "721", + "name": "新安县", + "en_name": "Xinanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3331", + "parentCode": "721", + "name": "偃师区", + "en_name": "Yanshishi", + "deleted": false, + "sublist": [] + }, + { + "code": "3329", + "parentCode": "721", + "name": "伊滨区", + "en_name": "Yibinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3336", + "parentCode": "721", + "name": "伊川县", + "en_name": "Yichuanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3339", + "parentCode": "721", + "name": "宜阳县", + "en_name": "Yiyangxian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "731", + "parentCode": "545", + "name": "南阳", + "en_name": "NANYANG", + "deleted": false, + "sublist": [ + { + "code": "3967", + "parentCode": "731", + "name": "邓州市", + "en_name": "Dengzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "3969", + "parentCode": "731", + "name": "方城县", + "en_name": "Fangcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3968", + "parentCode": "731", + "name": "南召县", + "en_name": "Nanzhao", + "deleted": false, + "sublist": [] + }, + { + "code": "3972", + "parentCode": "731", + "name": "内乡县", + "en_name": "Neixiang", + "deleted": false, + "sublist": [] + }, + { + "code": "3974", + "parentCode": "731", + "name": "社旗县", + "en_name": "Sheqi", + "deleted": false, + "sublist": [] + }, + { + "code": "3975", + "parentCode": "731", + "name": "唐河县", + "en_name": "Tanghe", + "deleted": false, + "sublist": [] + }, + { + "code": "3977", + "parentCode": "731", + "name": "桐柏县", + "en_name": "Tongbo", + "deleted": false, + "sublist": [] + }, + { + "code": "3965", + "parentCode": "731", + "name": "宛城区", + "en_name": "Wancheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3966", + "parentCode": "731", + "name": "卧龙区", + "en_name": "Wolong", + "deleted": false, + "sublist": [] + }, + { + "code": "3973", + "parentCode": "731", + "name": "淅川县", + "en_name": "Xichuan", + "deleted": false, + "sublist": [] + }, + { + "code": "3976", + "parentCode": "731", + "name": "新野县", + "en_name": "Xinye", + "deleted": false, + "sublist": [] + }, + { + "code": "3970", + "parentCode": "731", + "name": "西峡县", + "en_name": "Xixia", + "deleted": false, + "sublist": [] + }, + { + "code": "3971", + "parentCode": "731", + "name": "镇平县", + "en_name": "Zhenping", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "722", + "parentCode": "545", + "name": "平顶山", + "en_name": "PINGDINGSHAN", + "deleted": false, + "sublist": [ + { + "code": "3902", + "parentCode": "722", + "name": "宝丰县", + "en_name": "Baofeng", + "deleted": false, + "sublist": [] + }, + { + "code": "3905", + "parentCode": "722", + "name": "郏县", + "en_name": "Jia", + "deleted": false, + "sublist": [] + }, + { + "code": "3904", + "parentCode": "722", + "name": "鲁山县", + "en_name": "Lushan", + "deleted": false, + "sublist": [] + }, + { + "code": "3901", + "parentCode": "722", + "name": "汝州市", + "en_name": "Ruzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "3898", + "parentCode": "722", + "name": "石龙区", + "en_name": "Shilong", + "deleted": false, + "sublist": [] + }, + { + "code": "3897", + "parentCode": "722", + "name": "卫东区", + "en_name": "Weidong", + "deleted": false, + "sublist": [] + }, + { + "code": "3900", + "parentCode": "722", + "name": "舞钢市", + "en_name": "Wugang", + "deleted": false, + "sublist": [] + }, + { + "code": "3896", + "parentCode": "722", + "name": "新华区", + "en_name": "Xinhua", + "deleted": false, + "sublist": [] + }, + { + "code": "3903", + "parentCode": "722", + "name": "叶县", + "en_name": "Ye", + "deleted": false, + "sublist": [] + }, + { + "code": "3899", + "parentCode": "722", + "name": "湛河区", + "en_name": "Zhanhe", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "727", + "parentCode": "545", + "name": "濮阳", + "en_name": "PUYANG", + "deleted": false, + "sublist": [ + { + "code": "3945", + "parentCode": "727", + "name": "范县", + "en_name": "Fan", + "deleted": false, + "sublist": [] + }, + { + "code": "3942", + "parentCode": "727", + "name": "华龙区", + "en_name": "Hualong", + "deleted": false, + "sublist": [] + }, + { + "code": "3944", + "parentCode": "727", + "name": "南乐县", + "en_name": "Nanle", + "deleted": false, + "sublist": [] + }, + { + "code": "3947", + "parentCode": "727", + "name": "濮阳县", + "en_name": "Puyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3943", + "parentCode": "727", + "name": "清丰县", + "en_name": "Qingfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "3946", + "parentCode": "727", + "name": "台前县", + "en_name": "Taiqian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "730", + "parentCode": "545", + "name": "三门峡", + "en_name": "SANMENXIA", + "deleted": false, + "sublist": [ + { + "code": "3959", + "parentCode": "730", + "name": "湖滨区", + "en_name": "Hubin", + "deleted": false, + "sublist": [] + }, + { + "code": "3961", + "parentCode": "730", + "name": "灵宝市", + "en_name": "Lingbao", + "deleted": false, + "sublist": [] + }, + { + "code": "3964", + "parentCode": "730", + "name": "卢氏县", + "en_name": "Lushi", + "deleted": false, + "sublist": [] + }, + { + "code": "3962", + "parentCode": "730", + "name": "渑池县", + "en_name": "Mianchi", + "deleted": false, + "sublist": [] + }, + { + "code": "3963", + "parentCode": "730", + "name": "陕州区", + "en_name": "Shan", + "deleted": false, + "sublist": [] + }, + { + "code": "3960", + "parentCode": "730", + "name": "义马市", + "en_name": "Yima", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "732", + "parentCode": "545", + "name": "商丘", + "en_name": "SHANGQIU", + "deleted": false, + "sublist": [ + { + "code": "3978", + "parentCode": "732", + "name": "梁园区", + "en_name": "Liangyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "3981", + "parentCode": "732", + "name": "民权县", + "en_name": "Minan", + "deleted": false, + "sublist": [] + }, + { + "code": "3983", + "parentCode": "732", + "name": "宁陵县", + "en_name": "Ningling", + "deleted": false, + "sublist": [] + }, + { + "code": "3982", + "parentCode": "732", + "name": "睢县", + "en_name": "Sui", + "deleted": false, + "sublist": [] + }, + { + "code": "3979", + "parentCode": "732", + "name": "睢阳区", + "en_name": "Suiyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3986", + "parentCode": "732", + "name": "夏邑县", + "en_name": "Xiayi", + "deleted": false, + "sublist": [] + }, + { + "code": "3980", + "parentCode": "732", + "name": "永城市", + "en_name": "Yongcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3985", + "parentCode": "732", + "name": "虞城县", + "en_name": "Yucheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3984", + "parentCode": "732", + "name": "柘城县", + "en_name": "Zhecheng", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "729", + "parentCode": "545", + "name": "漯河", + "en_name": "LUOHE", + "deleted": false, + "sublist": [ + { + "code": "3958", + "parentCode": "729", + "name": "临颍县", + "en_name": "Linying", + "deleted": false, + "sublist": [] + }, + { + "code": "3957", + "parentCode": "729", + "name": "舞阳县", + "en_name": "Wuyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3955", + "parentCode": "729", + "name": "郾城区", + "en_name": "Yancheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3954", + "parentCode": "729", + "name": "源汇区", + "en_name": "Yuanhui", + "deleted": false, + "sublist": [] + }, + { + "code": "3956", + "parentCode": "729", + "name": "召陵区", + "en_name": "Zhaoling", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "725", + "parentCode": "545", + "name": "新乡", + "en_name": "XINXIANG", + "deleted": false, + "sublist": [ + { + "code": "3930", + "parentCode": "725", + "name": "封丘县", + "en_name": "Fengqiu", + "deleted": false, + "sublist": [] + }, + { + "code": "3922", + "parentCode": "725", + "name": "凤泉区", + "en_name": "Fengan", + "deleted": false, + "sublist": [] + }, + { + "code": "3920", + "parentCode": "725", + "name": "红旗区", + "en_name": "Hongqi", + "deleted": false, + "sublist": [] + }, + { + "code": "3925", + "parentCode": "725", + "name": "辉县市", + "en_name": "Huixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3927", + "parentCode": "725", + "name": "获嘉县", + "en_name": "Huojia", + "deleted": false, + "sublist": [] + }, + { + "code": "3923", + "parentCode": "725", + "name": "牧野区", + "en_name": "Wuye", + "deleted": false, + "sublist": [] + }, + { + "code": "3921", + "parentCode": "725", + "name": "卫滨区", + "en_name": "Weibin", + "deleted": false, + "sublist": [] + }, + { + "code": "3924", + "parentCode": "725", + "name": "卫辉市", + "en_name": "Weihui", + "deleted": false, + "sublist": [] + }, + { + "code": "3926", + "parentCode": "725", + "name": "新乡县", + "en_name": "Xinxiang", + "deleted": false, + "sublist": [] + }, + { + "code": "3929", + "parentCode": "725", + "name": "延津县", + "en_name": "Yanjin", + "deleted": false, + "sublist": [] + }, + { + "code": "3928", + "parentCode": "725", + "name": "原阳县", + "en_name": "Yuanyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3931", + "parentCode": "725", + "name": "长垣市", + "en_name": "Changyuan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "733", + "parentCode": "545", + "name": "信阳", + "en_name": "XINYANG", + "deleted": false, + "sublist": [ + { + "code": "3990", + "parentCode": "733", + "name": "光山县", + "en_name": "Guangshan", + "deleted": false, + "sublist": [] + }, + { + "code": "3993", + "parentCode": "733", + "name": "固始县", + "en_name": "Gushi", + "deleted": false, + "sublist": [] + }, + { + "code": "3995", + "parentCode": "733", + "name": "淮滨县", + "en_name": "Huaibin", + "deleted": false, + "sublist": [] + }, + { + "code": "3994", + "parentCode": "733", + "name": "潢川县", + "en_name": "Huangchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "3989", + "parentCode": "733", + "name": "罗山县", + "en_name": "Luoshan", + "deleted": false, + "sublist": [] + }, + { + "code": "3988", + "parentCode": "733", + "name": "平桥区", + "en_name": "Pingqiao", + "deleted": false, + "sublist": [] + }, + { + "code": "3992", + "parentCode": "733", + "name": "商城县", + "en_name": "Shangcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3987", + "parentCode": "733", + "name": "浉河区", + "en_name": "Shihe", + "deleted": false, + "sublist": [] + }, + { + "code": "3991", + "parentCode": "733", + "name": "新县", + "en_name": "Xin", + "deleted": false, + "sublist": [] + }, + { + "code": "3996", + "parentCode": "733", + "name": "息县", + "en_name": "Xi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "728", + "parentCode": "545", + "name": "许昌", + "en_name": "XUCHANG", + "deleted": false, + "sublist": [ + { + "code": "3340", + "parentCode": "728", + "name": "魏都区", + "en_name": "Weiduqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3344", + "parentCode": "728", + "name": "襄城县", + "en_name": "Xiangchengxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3341", + "parentCode": "728", + "name": "建安区", + "en_name": "Xuchangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3343", + "parentCode": "728", + "name": "鄢陵县", + "en_name": "Yanlingxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3342", + "parentCode": "728", + "name": "禹州市", + "en_name": "Yuzhouxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3406", + "parentCode": "728", + "name": "长葛市", + "en_name": "Changge", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "719", + "parentCode": "545", + "name": "郑州", + "en_name": "ZHENGZHOU", + "deleted": false, + "sublist": [ + { + "code": "2400", + "parentCode": "719", + "name": "登封市", + "en_name": "Dengfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "2195", + "parentCode": "719", + "name": "二七区", + "en_name": "Erqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2204", + "parentCode": "719", + "name": "高新区", + "en_name": "Gaoxin", + "deleted": false, + "sublist": [] + }, + { + "code": "2444", + "parentCode": "719", + "name": "巩义市", + "en_name": "Gongyi", + "deleted": false, + "sublist": [] + }, + { + "code": "2196", + "parentCode": "719", + "name": "管城回族区", + "en_name": "Guancheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2445", + "parentCode": "719", + "name": "航空港区", + "en_name": "Hangkonggangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2198", + "parentCode": "719", + "name": "惠济区", + "en_name": "Huiji", + "deleted": false, + "sublist": [] + }, + { + "code": "2203", + "parentCode": "719", + "name": "经开区", + "en_name": "Jingkai", + "deleted": false, + "sublist": [] + }, + { + "code": "2197", + "parentCode": "719", + "name": "金水区", + "en_name": "Jinshui", + "deleted": false, + "sublist": [] + }, + { + "code": "2205", + "parentCode": "719", + "name": "上街区", + "en_name": "Shangjie", + "deleted": false, + "sublist": [] + }, + { + "code": "2401", + "parentCode": "719", + "name": "新密市", + "en_name": "Xinmei", + "deleted": false, + "sublist": [] + }, + { + "code": "2399", + "parentCode": "719", + "name": "新郑市", + "en_name": "Xinzheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2402", + "parentCode": "719", + "name": "荥阳市", + "en_name": "Xingyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2199", + "parentCode": "719", + "name": "郑东新区", + "en_name": "Zhengdongxin", + "deleted": false, + "sublist": [] + }, + { + "code": "2403", + "parentCode": "719", + "name": "中牟县", + "en_name": "Zhongmu", + "deleted": false, + "sublist": [] + }, + { + "code": "2194", + "parentCode": "719", + "name": "中原区", + "en_name": "Zhongyuan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "734", + "parentCode": "545", + "name": "周口", + "en_name": "ZHOUKOU", + "deleted": false, + "sublist": [ + { + "code": "3997", + "parentCode": "734", + "name": "川汇区", + "en_name": "Chuanhui", + "deleted": false, + "sublist": [] + }, + { + "code": "104003", + "parentCode": "734", + "name": "郸城县", + "en_name": "Dancheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3999", + "parentCode": "734", + "name": "扶沟县", + "en_name": "Fugou", + "deleted": false, + "sublist": [] + }, + { + "code": "104004", + "parentCode": "734", + "name": "淮阳区", + "en_name": "Huaiyang", + "deleted": false, + "sublist": [] + }, + { + "code": "104006", + "parentCode": "734", + "name": "鹿邑县", + "en_name": "Luyi", + "deleted": false, + "sublist": [] + }, + { + "code": "104001", + "parentCode": "734", + "name": "商水县", + "en_name": "Shangshui", + "deleted": false, + "sublist": [] + }, + { + "code": "104002", + "parentCode": "734", + "name": "沈丘县", + "en_name": "Shenqiu", + "deleted": false, + "sublist": [] + }, + { + "code": "104005", + "parentCode": "734", + "name": "太康县", + "en_name": "Taikang", + "deleted": false, + "sublist": [] + }, + { + "code": "3998", + "parentCode": "734", + "name": "项城市", + "en_name": "Gcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4000", + "parentCode": "734", + "name": "西华县", + "en_name": "Xihua", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "735", + "parentCode": "545", + "name": "驻马店", + "en_name": "ZHUMADIAN", + "deleted": false, + "sublist": [ + { + "code": "10059", + "parentCode": "735", + "name": "西平县", + "en_name": "XIPING", + "deleted": false, + "sublist": [] + }, + { + "code": "104013", + "parentCode": "735", + "name": "泌阳县", + "en_name": "Biyang", + "deleted": false, + "sublist": [] + }, + { + "code": "104010", + "parentCode": "735", + "name": "平舆县", + "en_name": "Pingyu", + "deleted": false, + "sublist": [] + }, + { + "code": "104012", + "parentCode": "735", + "name": "确山县", + "en_name": "Queshan", + "deleted": false, + "sublist": [] + }, + { + "code": "104014", + "parentCode": "735", + "name": "汝南县", + "en_name": "Runan", + "deleted": false, + "sublist": [] + }, + { + "code": "104009", + "parentCode": "735", + "name": "上蔡县", + "en_name": "Shangcai", + "deleted": false, + "sublist": [] + }, + { + "code": "104015", + "parentCode": "735", + "name": "遂平县", + "en_name": "Suiping", + "deleted": false, + "sublist": [] + }, + { + "code": "4016", + "parentCode": "735", + "name": "新蔡县", + "en_name": "Xincai", + "deleted": false, + "sublist": [] + }, + { + "code": "104007", + "parentCode": "735", + "name": "驿城区", + "en_name": "Yicheng", + "deleted": false, + "sublist": [] + }, + { + "code": "104011", + "parentCode": "735", + "name": "正阳县", + "en_name": "Zhengyang", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "546", + "parentCode": "489", + "name": "湖北", + "en_name": "HUBEI", + "deleted": false, + "sublist": [ + { + "code": "748", + "parentCode": "546", + "name": "恩施", + "en_name": "ENSHITUJIAZUMIAOZUZIZHIZHOU", + "deleted": false, + "sublist": [ + { + "code": "4113", + "parentCode": "748", + "name": "巴东县", + "en_name": "Badong", + "deleted": false, + "sublist": [] + }, + { + "code": "4110", + "parentCode": "748", + "name": "恩施市", + "en_name": "Enshi", + "deleted": false, + "sublist": [] + }, + { + "code": "4117", + "parentCode": "748", + "name": "鹤峰县", + "en_name": "Hefeng", + "deleted": false, + "sublist": [] + }, + { + "code": "4112", + "parentCode": "748", + "name": "建始县", + "en_name": "Jianshi", + "deleted": false, + "sublist": [] + }, + { + "code": "4116", + "parentCode": "748", + "name": "来凤县", + "en_name": "Laifeng", + "deleted": false, + "sublist": [] + }, + { + "code": "4111", + "parentCode": "748", + "name": "利川市", + "en_name": "Lichuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4115", + "parentCode": "748", + "name": "咸丰县", + "en_name": "Xianfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "4114", + "parentCode": "748", + "name": "宣恩县", + "en_name": "Xuanen", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "741", + "parentCode": "546", + "name": "鄂州", + "en_name": "EZHOU", + "deleted": false, + "sublist": [ + { + "code": "4070", + "parentCode": "741", + "name": "鄂城区", + "en_name": "Echeng", + "deleted": false, + "sublist": [] + }, + { + "code": "4069", + "parentCode": "741", + "name": "华容区", + "en_name": "Huarong", + "deleted": false, + "sublist": [] + }, + { + "code": "4068", + "parentCode": "741", + "name": "梁子湖区", + "en_name": "Liangzihu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "745", + "parentCode": "546", + "name": "黄冈", + "en_name": "HUANGGANG", + "deleted": false, + "sublist": [ + { + "code": "10139", + "parentCode": "745", + "name": "武穴市", + "en_name": "WUXUE", + "deleted": false, + "sublist": [] + }, + { + "code": "4095", + "parentCode": "745", + "name": "红安县", + "en_name": "Hongan", + "deleted": false, + "sublist": [] + }, + { + "code": "4100", + "parentCode": "745", + "name": "黄梅县", + "en_name": "Huangmei", + "deleted": false, + "sublist": [] + }, + { + "code": "4091", + "parentCode": "745", + "name": "黄州区", + "en_name": "Huangzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "4096", + "parentCode": "745", + "name": "罗田县", + "en_name": "Luotian", + "deleted": false, + "sublist": [] + }, + { + "code": "4092", + "parentCode": "745", + "name": "麻城市", + "en_name": "Macheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4099", + "parentCode": "745", + "name": "蕲春县", + "en_name": "Qichun", + "deleted": false, + "sublist": [] + }, + { + "code": "4093", + "parentCode": "745", + "name": "团风县", + "en_name": "Tuanfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "4098", + "parentCode": "745", + "name": "浠水县", + "en_name": "Xishui", + "deleted": false, + "sublist": [] + }, + { + "code": "4097", + "parentCode": "745", + "name": "英山县", + "en_name": "Yingshan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "737", + "parentCode": "546", + "name": "黄石", + "en_name": "HUANGSHI", + "deleted": false, + "sublist": [ + { + "code": "4037", + "parentCode": "737", + "name": "大冶市", + "en_name": "Daye", + "deleted": false, + "sublist": [] + }, + { + "code": "4033", + "parentCode": "737", + "name": "黄石港区", + "en_name": "Huangshigang", + "deleted": false, + "sublist": [] + }, + { + "code": "4036", + "parentCode": "737", + "name": "铁山区", + "en_name": "Tieshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4035", + "parentCode": "737", + "name": "下陆区", + "en_name": "Xialu", + "deleted": false, + "sublist": [] + }, + { + "code": "4034", + "parentCode": "737", + "name": "西塞山区", + "en_name": "Xisaishan", + "deleted": false, + "sublist": [] + }, + { + "code": "4038", + "parentCode": "737", + "name": "阳新县", + "en_name": "Yangxin", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "742", + "parentCode": "546", + "name": "荆门", + "en_name": "JINGMEN", + "deleted": false, + "sublist": [ + { + "code": "4071", + "parentCode": "742", + "name": "东宝区", + "en_name": "Dongbao", + "deleted": false, + "sublist": [] + }, + { + "code": "4072", + "parentCode": "742", + "name": "掇刀区", + "en_name": "Duodao", + "deleted": false, + "sublist": [] + }, + { + "code": "4074", + "parentCode": "742", + "name": "京山市", + "en_name": "Jingshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4075", + "parentCode": "742", + "name": "沙洋县", + "en_name": "Shayang", + "deleted": false, + "sublist": [] + }, + { + "code": "4073", + "parentCode": "742", + "name": "钟祥市", + "en_name": "Zhongxiang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "744", + "parentCode": "546", + "name": "荆州", + "en_name": "JINGZHOU", + "deleted": false, + "sublist": [ + { + "code": "10057", + "parentCode": "744", + "name": "公安县", + "en_name": "GONGAN", + "deleted": false, + "sublist": [] + }, + { + "code": "4086", + "parentCode": "744", + "name": "洪湖市", + "en_name": "Honghu", + "deleted": false, + "sublist": [] + }, + { + "code": "4090", + "parentCode": "744", + "name": "江陵县", + "en_name": "Jiangling", + "deleted": false, + "sublist": [] + }, + { + "code": "4089", + "parentCode": "744", + "name": "监利市", + "en_name": "jianlishi", + "deleted": false, + "sublist": [] + }, + { + "code": "4084", + "parentCode": "744", + "name": "荆州区", + "en_name": "Jingzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "4083", + "parentCode": "744", + "name": "沙市区", + "en_name": "Shashi", + "deleted": false, + "sublist": [] + }, + { + "code": "4085", + "parentCode": "744", + "name": "石首市", + "en_name": "Shishou", + "deleted": false, + "sublist": [] + }, + { + "code": "4087", + "parentCode": "744", + "name": "松滋市", + "en_name": "Songzi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "10169", + "parentCode": "546", + "name": "潜江市", + "en_name": "QIANJIANG", + "deleted": false, + "sublist": [] + }, + { + "code": "10179", + "parentCode": "546", + "name": "神农架林区", + "en_name": "SHENNONGJIA", + "deleted": false, + "sublist": [] + }, + { + "code": "738", + "parentCode": "546", + "name": "十堰", + "en_name": "SHIYAN", + "deleted": false, + "sublist": [ + { + "code": "104017", + "parentCode": "738", + "name": "张湾区", + "en_name": "Zhangwanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4040", + "parentCode": "738", + "name": "丹江口市", + "en_name": "Danjiangkou", + "deleted": false, + "sublist": [] + }, + { + "code": "4045", + "parentCode": "738", + "name": "房县", + "en_name": "Fang", + "deleted": false, + "sublist": [] + }, + { + "code": "4039", + "parentCode": "738", + "name": "茅箭区", + "en_name": "Maojian", + "deleted": false, + "sublist": [] + }, + { + "code": "4041", + "parentCode": "738", + "name": "郧阳区", + "en_name": "Yun", + "deleted": false, + "sublist": [] + }, + { + "code": "4042", + "parentCode": "738", + "name": "郧西县", + "en_name": "Yunxi", + "deleted": false, + "sublist": [] + }, + { + "code": "4043", + "parentCode": "738", + "name": "竹山县", + "en_name": "Zhushan", + "deleted": false, + "sublist": [] + }, + { + "code": "4044", + "parentCode": "738", + "name": "竹溪县", + "en_name": "Zhuxi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "747", + "parentCode": "546", + "name": "随州", + "en_name": "SUIZHOU", + "deleted": false, + "sublist": [ + { + "code": "4107", + "parentCode": "747", + "name": "曾都区", + "en_name": "Zengdu", + "deleted": false, + "sublist": [] + }, + { + "code": "4108", + "parentCode": "747", + "name": "广水市", + "en_name": "Guangshui", + "deleted": false, + "sublist": [] + }, + { + "code": "4109", + "parentCode": "747", + "name": "随县", + "en_name": "Sui", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "10140", + "parentCode": "546", + "name": "天门市", + "en_name": "TIANMEN", + "deleted": false, + "sublist": [] + }, + { + "code": "736", + "parentCode": "546", + "name": "武汉", + "en_name": "WUHAN", + "deleted": false, + "sublist": [ + { + "code": "2064", + "parentCode": "736", + "name": "蔡甸区", + "en_name": "Caidian", + "deleted": false, + "sublist": [] + }, + { + "code": "2366", + "parentCode": "736", + "name": "东湖新技术开发区", + "en_name": "Donghuxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2065", + "parentCode": "736", + "name": "东西湖区", + "en_name": "Dongxihu", + "deleted": false, + "sublist": [] + }, + { + "code": "2066", + "parentCode": "736", + "name": "汉南区", + "en_name": "Hannan", + "deleted": false, + "sublist": [] + }, + { + "code": "2060", + "parentCode": "736", + "name": "汉阳区", + "en_name": "Hanyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2063", + "parentCode": "736", + "name": "洪山区", + "en_name": "Hongshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2068", + "parentCode": "736", + "name": "黄陂区", + "en_name": "Huangpo", + "deleted": false, + "sublist": [] + }, + { + "code": "2057", + "parentCode": "736", + "name": "江岸区", + "en_name": "Jiangan", + "deleted": false, + "sublist": [] + }, + { + "code": "2058", + "parentCode": "736", + "name": "江汉区", + "en_name": "Jianghan", + "deleted": false, + "sublist": [] + }, + { + "code": "2067", + "parentCode": "736", + "name": "江夏区", + "en_name": "Jiangxia", + "deleted": false, + "sublist": [] + }, + { + "code": "2059", + "parentCode": "736", + "name": "硚口区", + "en_name": "Changkou", + "deleted": false, + "sublist": [] + }, + { + "code": "2062", + "parentCode": "736", + "name": "青山区", + "en_name": "Qingshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2061", + "parentCode": "736", + "name": "武昌区", + "en_name": "Wuchang", + "deleted": false, + "sublist": [] + }, + { + "code": "2365", + "parentCode": "736", + "name": "武汉经济技术开发区", + "en_name": "Jingjikaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2367", + "parentCode": "736", + "name": "武汉吴家山经济技术开发区", + "en_name": "Wujiashan", + "deleted": false, + "sublist": [] + }, + { + "code": "2069", + "parentCode": "736", + "name": "新洲区", + "en_name": "Xinzhou", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "740", + "parentCode": "546", + "name": "襄阳", + "en_name": "XIANGYANG", + "deleted": false, + "sublist": [ + { + "code": "10171", + "parentCode": "740", + "name": "宜城市", + "en_name": "YICHENG", + "deleted": false, + "sublist": [] + }, + { + "code": "4067", + "parentCode": "740", + "name": "保康县", + "en_name": "Baokang", + "deleted": false, + "sublist": [] + }, + { + "code": "4060", + "parentCode": "740", + "name": "樊城区", + "en_name": "Fancheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4066", + "parentCode": "740", + "name": "谷城县", + "en_name": "Gucheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4062", + "parentCode": "740", + "name": "老河口市", + "en_name": "Laohekou", + "deleted": false, + "sublist": [] + }, + { + "code": "4065", + "parentCode": "740", + "name": "南漳县", + "en_name": "Nanzhang", + "deleted": false, + "sublist": [] + }, + { + "code": "4059", + "parentCode": "740", + "name": "襄城区", + "en_name": "Xiangcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4061", + "parentCode": "740", + "name": "襄州区", + "en_name": "Xiangzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "4063", + "parentCode": "740", + "name": "枣阳市", + "en_name": "Zaoyang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "746", + "parentCode": "546", + "name": "咸宁", + "en_name": "XIANNING", + "deleted": false, + "sublist": [ + { + "code": "4102", + "parentCode": "746", + "name": "赤壁市", + "en_name": "Chibi", + "deleted": false, + "sublist": [] + }, + { + "code": "4105", + "parentCode": "746", + "name": "崇阳县", + "en_name": "Chongyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4103", + "parentCode": "746", + "name": "嘉鱼县", + "en_name": "Jiayu", + "deleted": false, + "sublist": [] + }, + { + "code": "4104", + "parentCode": "746", + "name": "通城县", + "en_name": "Tongcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4106", + "parentCode": "746", + "name": "通山县", + "en_name": "Tongshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4101", + "parentCode": "746", + "name": "咸安区", + "en_name": "Xianan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "10168", + "parentCode": "546", + "name": "仙桃市", + "en_name": "XIANTAO", + "deleted": false, + "sublist": [] + }, + { + "code": "743", + "parentCode": "546", + "name": "孝感", + "en_name": "XIAOGAN", + "deleted": false, + "sublist": [ + { + "code": "4078", + "parentCode": "743", + "name": "安陆市", + "en_name": "Anlu", + "deleted": false, + "sublist": [] + }, + { + "code": "4081", + "parentCode": "743", + "name": "大悟县", + "en_name": "Dawu", + "deleted": false, + "sublist": [] + }, + { + "code": "4079", + "parentCode": "743", + "name": "汉川市", + "en_name": "Hanchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4080", + "parentCode": "743", + "name": "孝昌县", + "en_name": "Xiaochang", + "deleted": false, + "sublist": [] + }, + { + "code": "4076", + "parentCode": "743", + "name": "孝南区", + "en_name": "Xiaonan", + "deleted": false, + "sublist": [] + }, + { + "code": "4077", + "parentCode": "743", + "name": "应城市", + "en_name": "Yingcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4082", + "parentCode": "743", + "name": "云梦县", + "en_name": "Yunmeng", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "739", + "parentCode": "546", + "name": "宜昌", + "en_name": "YICHANG", + "deleted": false, + "sublist": [ + { + "code": "4052", + "parentCode": "739", + "name": "当阳市", + "en_name": "Dangyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3347", + "parentCode": "739", + "name": "点军区", + "en_name": "Dianjunqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4058", + "parentCode": "739", + "name": "五峰土家族自治县", + "en_name": "Wufengtujiazuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "3346", + "parentCode": "739", + "name": "伍家岗区", + "en_name": "Wujiagangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3345", + "parentCode": "739", + "name": "西陵区", + "en_name": "Xilingqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4055", + "parentCode": "739", + "name": "兴山县", + "en_name": "Xingshan", + "deleted": false, + "sublist": [] + }, + { + "code": "3348", + "parentCode": "739", + "name": "猇亭区", + "en_name": "Xiaotingqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4051", + "parentCode": "739", + "name": "宜都市", + "en_name": "Yidu", + "deleted": false, + "sublist": [] + }, + { + "code": "3349", + "parentCode": "739", + "name": "夷陵区", + "en_name": "Yilingqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4054", + "parentCode": "739", + "name": "远安县", + "en_name": "Yuanan", + "deleted": false, + "sublist": [] + }, + { + "code": "4057", + "parentCode": "739", + "name": "长阳土家族自治县", + "en_name": "Changyangtujiazuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4053", + "parentCode": "739", + "name": "枝江市", + "en_name": "Zhijiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4056", + "parentCode": "739", + "name": "秭归县", + "en_name": "Zigui", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "547", + "parentCode": "489", + "name": "湖南", + "en_name": "HUNAN", + "deleted": false, + "sublist": [ + { + "code": "755", + "parentCode": "547", + "name": "常德", + "en_name": "CHANGDE", + "deleted": false, + "sublist": [ + { + "code": "4177", + "parentCode": "755", + "name": "安乡县", + "en_name": "Anxiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4175", + "parentCode": "755", + "name": "鼎城区", + "en_name": "Dingcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4178", + "parentCode": "755", + "name": "汉寿县", + "en_name": "Hanshou", + "deleted": false, + "sublist": [] + }, + { + "code": "4176", + "parentCode": "755", + "name": "津市市", + "en_name": "Jinshi", + "deleted": false, + "sublist": [] + }, + { + "code": "4180", + "parentCode": "755", + "name": "临澧县", + "en_name": "Linli", + "deleted": false, + "sublist": [] + }, + { + "code": "4179", + "parentCode": "755", + "name": "澧县", + "en_name": "Li", + "deleted": false, + "sublist": [] + }, + { + "code": "4182", + "parentCode": "755", + "name": "石门县", + "en_name": "Shimen", + "deleted": false, + "sublist": [] + }, + { + "code": "4181", + "parentCode": "755", + "name": "桃源县", + "en_name": "Taoyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4174", + "parentCode": "755", + "name": "武陵区", + "en_name": "Wuling", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "758", + "parentCode": "547", + "name": "郴州", + "en_name": "CHENZHOU", + "deleted": false, + "sublist": [ + { + "code": "4203", + "parentCode": "758", + "name": "安仁县", + "en_name": "Anren", + "deleted": false, + "sublist": [] + }, + { + "code": "4193", + "parentCode": "758", + "name": "北湖区", + "en_name": "Beihu", + "deleted": false, + "sublist": [] + }, + { + "code": "4202", + "parentCode": "758", + "name": "桂东县", + "en_name": "Guidong", + "deleted": false, + "sublist": [] + }, + { + "code": "4196", + "parentCode": "758", + "name": "桂阳县", + "en_name": "Guiyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4199", + "parentCode": "758", + "name": "嘉禾县", + "en_name": "Jiahe", + "deleted": false, + "sublist": [] + }, + { + "code": "4200", + "parentCode": "758", + "name": "临武县", + "en_name": "Linwu", + "deleted": false, + "sublist": [] + }, + { + "code": "4201", + "parentCode": "758", + "name": "汝城县", + "en_name": "Rucheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4194", + "parentCode": "758", + "name": "苏仙区", + "en_name": "Suxian", + "deleted": false, + "sublist": [] + }, + { + "code": "4197", + "parentCode": "758", + "name": "宜章县", + "en_name": "Yizhang", + "deleted": false, + "sublist": [] + }, + { + "code": "4198", + "parentCode": "758", + "name": "永兴县", + "en_name": "Yongxing", + "deleted": false, + "sublist": [] + }, + { + "code": "4195", + "parentCode": "758", + "name": "资兴市", + "en_name": "Zixing", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "752", + "parentCode": "547", + "name": "衡阳", + "en_name": "HENGYANG", + "deleted": false, + "sublist": [ + { + "code": "4147", + "parentCode": "752", + "name": "常宁市", + "en_name": "Changning", + "deleted": false, + "sublist": [] + }, + { + "code": "4151", + "parentCode": "752", + "name": "衡东县", + "en_name": "Hengdong", + "deleted": false, + "sublist": [] + }, + { + "code": "4149", + "parentCode": "752", + "name": "衡南县", + "en_name": "Hengnan", + "deleted": false, + "sublist": [] + }, + { + "code": "4150", + "parentCode": "752", + "name": "衡山县", + "en_name": "Hengshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4148", + "parentCode": "752", + "name": "衡阳县", + "en_name": "Hengyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4146", + "parentCode": "752", + "name": "耒阳市", + "en_name": "Leiyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4145", + "parentCode": "752", + "name": "南岳区", + "en_name": "Nanyue", + "deleted": false, + "sublist": [] + }, + { + "code": "4152", + "parentCode": "752", + "name": "祁东县", + "en_name": "Qidong", + "deleted": false, + "sublist": [] + }, + { + "code": "4143", + "parentCode": "752", + "name": "石鼓区", + "en_name": "Shigu", + "deleted": false, + "sublist": [] + }, + { + "code": "4142", + "parentCode": "752", + "name": "雁峰区", + "en_name": "Yanfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "4144", + "parentCode": "752", + "name": "蒸湘区", + "en_name": "Zhengxiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4141", + "parentCode": "752", + "name": "珠晖区", + "en_name": "Zhuhui", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "760", + "parentCode": "547", + "name": "怀化", + "en_name": "HUAIHUA", + "deleted": false, + "sublist": [ + { + "code": "4219", + "parentCode": "760", + "name": "辰溪县", + "en_name": "Chenxi", + "deleted": false, + "sublist": [] + }, + { + "code": "4215", + "parentCode": "760", + "name": "鹤城区", + "en_name": "Hecheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4216", + "parentCode": "760", + "name": "洪江市", + "en_name": "Hongjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4221", + "parentCode": "760", + "name": "会同县", + "en_name": "Huitong", + "deleted": false, + "sublist": [] + }, + { + "code": "4225", + "parentCode": "760", + "name": "靖州苗族侗族自治县", + "en_name": "Jingzhoumiaozudongzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4222", + "parentCode": "760", + "name": "麻阳苗族自治县", + "en_name": "Mayangmiaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4226", + "parentCode": "760", + "name": "通道侗族自治县", + "en_name": "Tongdaodongzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4223", + "parentCode": "760", + "name": "新晃侗族自治县", + "en_name": "Xinhuangdongzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4220", + "parentCode": "760", + "name": "溆浦县", + "en_name": "Xupu", + "deleted": false, + "sublist": [] + }, + { + "code": "4218", + "parentCode": "760", + "name": "沅陵县", + "en_name": "Yuanling", + "deleted": false, + "sublist": [] + }, + { + "code": "4224", + "parentCode": "760", + "name": "芷江侗族自治县", + "en_name": "Zhijiangdongzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4217", + "parentCode": "760", + "name": "中方县", + "en_name": "Zhongfang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "761", + "parentCode": "547", + "name": "娄底", + "en_name": "LOUDI", + "deleted": false, + "sublist": [ + { + "code": "4228", + "parentCode": "761", + "name": "冷水江市", + "en_name": "Lengshuijiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4229", + "parentCode": "761", + "name": "涟源市", + "en_name": "Lianyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4227", + "parentCode": "761", + "name": "娄星区", + "en_name": "Louxing", + "deleted": false, + "sublist": [] + }, + { + "code": "4230", + "parentCode": "761", + "name": "双峰县", + "en_name": "Shuangfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "4231", + "parentCode": "761", + "name": "新化县", + "en_name": "Xinhua", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "753", + "parentCode": "547", + "name": "邵阳", + "en_name": "SHAOYANG", + "deleted": false, + "sublist": [ + { + "code": "4155", + "parentCode": "753", + "name": "北塔区", + "en_name": "Beita", + "deleted": false, + "sublist": [] + }, + { + "code": "4164", + "parentCode": "753", + "name": "城步苗族自治县", + "en_name": "Chengbumiaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4154", + "parentCode": "753", + "name": "大祥区", + "en_name": "Daxiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4161", + "parentCode": "753", + "name": "洞口县", + "en_name": "Dongkou", + "deleted": false, + "sublist": [] + }, + { + "code": "4160", + "parentCode": "753", + "name": "隆回县", + "en_name": "Longhui", + "deleted": false, + "sublist": [] + }, + { + "code": "4157", + "parentCode": "753", + "name": "邵东市", + "en_name": "Shaodong", + "deleted": false, + "sublist": [] + }, + { + "code": "4159", + "parentCode": "753", + "name": "邵阳县", + "en_name": "Shaoyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4153", + "parentCode": "753", + "name": "双清区", + "en_name": "Shuangqing", + "deleted": false, + "sublist": [] + }, + { + "code": "4162", + "parentCode": "753", + "name": "绥宁县", + "en_name": "Suining", + "deleted": false, + "sublist": [] + }, + { + "code": "4156", + "parentCode": "753", + "name": "武冈市", + "en_name": "Wugang", + "deleted": false, + "sublist": [] + }, + { + "code": "4163", + "parentCode": "753", + "name": "新宁县", + "en_name": "Xinning", + "deleted": false, + "sublist": [] + }, + { + "code": "4158", + "parentCode": "753", + "name": "新邵县", + "en_name": "Xinshao", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "751", + "parentCode": "547", + "name": "湘潭", + "en_name": "XIANGTAN", + "deleted": false, + "sublist": [ + { + "code": "4139", + "parentCode": "751", + "name": "韶山市", + "en_name": "Shaoshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4140", + "parentCode": "751", + "name": "湘潭县", + "en_name": "Xiangtan", + "deleted": false, + "sublist": [] + }, + { + "code": "4138", + "parentCode": "751", + "name": "湘乡市", + "en_name": "Xiangxiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4137", + "parentCode": "751", + "name": "岳塘区", + "en_name": "Yuetang", + "deleted": false, + "sublist": [] + }, + { + "code": "4136", + "parentCode": "751", + "name": "雨湖区", + "en_name": "Yuhu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "762", + "parentCode": "547", + "name": "湘西", + "en_name": "XIANGXI", + "deleted": false, + "sublist": [ + { + "code": "4236", + "parentCode": "762", + "name": "保靖县", + "en_name": "Baojing", + "deleted": false, + "sublist": [] + }, + { + "code": "4234", + "parentCode": "762", + "name": "凤凰县", + "en_name": "Fenghuang", + "deleted": false, + "sublist": [] + }, + { + "code": "4237", + "parentCode": "762", + "name": "古丈县", + "en_name": "Guzhang", + "deleted": false, + "sublist": [] + }, + { + "code": "4235", + "parentCode": "762", + "name": "花垣县", + "en_name": "Huayuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4232", + "parentCode": "762", + "name": "吉首市", + "en_name": "Jishou", + "deleted": false, + "sublist": [] + }, + { + "code": "4239", + "parentCode": "762", + "name": "龙山县", + "en_name": "Longshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4233", + "parentCode": "762", + "name": "泸溪县", + "en_name": "Luxi", + "deleted": false, + "sublist": [] + }, + { + "code": "4238", + "parentCode": "762", + "name": "永顺县", + "en_name": "Yongshun", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "757", + "parentCode": "547", + "name": "益阳", + "en_name": "YIYANG", + "deleted": false, + "sublist": [ + { + "code": "4192", + "parentCode": "757", + "name": "安化县", + "en_name": "Anhua", + "deleted": false, + "sublist": [] + }, + { + "code": "4188", + "parentCode": "757", + "name": "赫山区", + "en_name": "Heshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4190", + "parentCode": "757", + "name": "南县", + "en_name": "Nan", + "deleted": false, + "sublist": [] + }, + { + "code": "4191", + "parentCode": "757", + "name": "桃江县", + "en_name": "Taojiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4189", + "parentCode": "757", + "name": "沅江市", + "en_name": "Yuanjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4187", + "parentCode": "757", + "name": "资阳区", + "en_name": "Ziyang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "759", + "parentCode": "547", + "name": "永州", + "en_name": "YONGZHOU", + "deleted": false, + "sublist": [ + { + "code": "4209", + "parentCode": "759", + "name": "道县", + "en_name": "Dao", + "deleted": false, + "sublist": [] + }, + { + "code": "4207", + "parentCode": "759", + "name": "东安县", + "en_name": "Dongan", + "deleted": false, + "sublist": [] + }, + { + "code": "4214", + "parentCode": "759", + "name": "江华瑶族自治县", + "en_name": "Jianghuayaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4210", + "parentCode": "759", + "name": "江永县", + "en_name": "Jiangyong", + "deleted": false, + "sublist": [] + }, + { + "code": "4212", + "parentCode": "759", + "name": "蓝山县", + "en_name": "Lanshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4205", + "parentCode": "759", + "name": "冷水滩区", + "en_name": "Lengshuitan", + "deleted": false, + "sublist": [] + }, + { + "code": "4204", + "parentCode": "759", + "name": "零陵区", + "en_name": "Lingling", + "deleted": false, + "sublist": [] + }, + { + "code": "4211", + "parentCode": "759", + "name": "宁远县", + "en_name": "Ningyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4206", + "parentCode": "759", + "name": "祁阳市", + "en_name": "Qiyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4208", + "parentCode": "759", + "name": "双牌县", + "en_name": "Shuangpai", + "deleted": false, + "sublist": [] + }, + { + "code": "4213", + "parentCode": "759", + "name": "新田县", + "en_name": "Xintian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "754", + "parentCode": "547", + "name": "岳阳", + "en_name": "YUEYANG", + "deleted": false, + "sublist": [ + { + "code": "4171", + "parentCode": "754", + "name": "华容县", + "en_name": "Huarong", + "deleted": false, + "sublist": [] + }, + { + "code": "4167", + "parentCode": "754", + "name": "君山区", + "en_name": "Junshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4169", + "parentCode": "754", + "name": "临湘市", + "en_name": "Linxiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4168", + "parentCode": "754", + "name": "汨罗市", + "en_name": "Miluo", + "deleted": false, + "sublist": [] + }, + { + "code": "4173", + "parentCode": "754", + "name": "平江县", + "en_name": "Pingjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4172", + "parentCode": "754", + "name": "湘阴县", + "en_name": "Xiangyin", + "deleted": false, + "sublist": [] + }, + { + "code": "4165", + "parentCode": "754", + "name": "岳阳楼区", + "en_name": "Yueyanglou", + "deleted": false, + "sublist": [] + }, + { + "code": "4170", + "parentCode": "754", + "name": "岳阳县", + "en_name": "Yueyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4166", + "parentCode": "754", + "name": "云溪区", + "en_name": "Yunxi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "756", + "parentCode": "547", + "name": "张家界", + "en_name": "ZHANGJIAJIE", + "deleted": false, + "sublist": [ + { + "code": "4185", + "parentCode": "756", + "name": "慈利县", + "en_name": "Cili", + "deleted": false, + "sublist": [] + }, + { + "code": "4186", + "parentCode": "756", + "name": "桑植县", + "en_name": "Sangzhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4184", + "parentCode": "756", + "name": "武陵源区", + "en_name": "Wulingyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4183", + "parentCode": "756", + "name": "永定区", + "en_name": "Yongding", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "749", + "parentCode": "547", + "name": "长沙", + "en_name": "CHANGSHA", + "deleted": false, + "sublist": [ + { + "code": "2224", + "parentCode": "749", + "name": "芙蓉区", + "en_name": "Furong", + "deleted": false, + "sublist": [] + }, + { + "code": "2227", + "parentCode": "749", + "name": "开福区", + "en_name": "Kaifu", + "deleted": false, + "sublist": [] + }, + { + "code": "2408", + "parentCode": "749", + "name": "浏阳市", + "en_name": "Liuyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2407", + "parentCode": "749", + "name": "宁乡市", + "en_name": "Ningxiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2225", + "parentCode": "749", + "name": "天心区", + "en_name": "Tianxin", + "deleted": false, + "sublist": [] + }, + { + "code": "2405", + "parentCode": "749", + "name": "望城区", + "en_name": "Wangcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2226", + "parentCode": "749", + "name": "岳麓区", + "en_name": "Yuelu", + "deleted": false, + "sublist": [] + }, + { + "code": "2228", + "parentCode": "749", + "name": "雨花区", + "en_name": "Yuhua", + "deleted": false, + "sublist": [] + }, + { + "code": "2406", + "parentCode": "749", + "name": "长沙县", + "en_name": "Changsha", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "750", + "parentCode": "547", + "name": "株洲", + "en_name": "ZHUZHOU", + "deleted": false, + "sublist": [ + { + "code": "4134", + "parentCode": "750", + "name": "茶陵县", + "en_name": "Chaling", + "deleted": false, + "sublist": [] + }, + { + "code": "4127", + "parentCode": "750", + "name": "荷塘区", + "en_name": "Hetang", + "deleted": false, + "sublist": [] + }, + { + "code": "4131", + "parentCode": "750", + "name": "醴陵市", + "en_name": "Liling", + "deleted": false, + "sublist": [] + }, + { + "code": "4128", + "parentCode": "750", + "name": "芦淞区", + "en_name": "Lusong", + "deleted": false, + "sublist": [] + }, + { + "code": "4129", + "parentCode": "750", + "name": "石峰区", + "en_name": "Shifeng", + "deleted": false, + "sublist": [] + }, + { + "code": "4130", + "parentCode": "750", + "name": "天元区", + "en_name": "Tianyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4135", + "parentCode": "750", + "name": "炎陵县", + "en_name": "Yanling", + "deleted": false, + "sublist": [] + }, + { + "code": "4133", + "parentCode": "750", + "name": "攸县", + "en_name": "You", + "deleted": false, + "sublist": [] + }, + { + "code": "4132", + "parentCode": "750", + "name": "渌口区", + "en_name": "Zhuzhou", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "539", + "parentCode": "489", + "name": "江苏", + "en_name": "JIANGSU", + "deleted": false, + "sublist": [ + { + "code": "638", + "parentCode": "539", + "name": "常州", + "en_name": "CHANGZHOU", + "deleted": false, + "sublist": [ + { + "code": "3043", + "parentCode": "638", + "name": "金坛区", + "en_name": "Jintanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3042", + "parentCode": "638", + "name": "溧阳市", + "en_name": "Liyangshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3038", + "parentCode": "638", + "name": "天宁区", + "en_name": "Tianningqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3041", + "parentCode": "638", + "name": "武进区", + "en_name": "Wujinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3040", + "parentCode": "638", + "name": "新北区", + "en_name": "Xinbeiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3039", + "parentCode": "638", + "name": "钟楼区", + "en_name": "Zhonglouqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "643", + "parentCode": "539", + "name": "淮安", + "en_name": "HUAIAN", + "deleted": false, + "sublist": [ + { + "code": "3085", + "parentCode": "643", + "name": "洪泽区", + "en_name": "Hongzexian", + "deleted": false, + "sublist": [] + }, + { + "code": "3412", + "parentCode": "643", + "name": "淮安区", + "en_name": "Huaian", + "deleted": false, + "sublist": [] + }, + { + "code": "3082", + "parentCode": "643", + "name": "淮阴区", + "en_name": "Huaiyinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3086", + "parentCode": "643", + "name": "金湖县", + "en_name": "Jinhuxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3084", + "parentCode": "643", + "name": "涟水县", + "en_name": "Lianshuixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3080", + "parentCode": "643", + "name": "清江浦区", + "en_name": "Qinghequ", + "deleted": false, + "sublist": [] + }, + { + "code": "3398", + "parentCode": "643", + "name": "盱眙县", + "en_name": "Xuyi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "642", + "parentCode": "539", + "name": "连云港", + "en_name": "LIANYUNGANG", + "deleted": false, + "sublist": [ + { + "code": "3090", + "parentCode": "642", + "name": "东海县", + "en_name": "Donghaixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3089", + "parentCode": "642", + "name": "赣榆区", + "en_name": "Ganyuxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3092", + "parentCode": "642", + "name": "灌南县", + "en_name": "Guannanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3091", + "parentCode": "642", + "name": "灌云县", + "en_name": "Guanyunxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3088", + "parentCode": "642", + "name": "海州区", + "en_name": "Haizhouqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3087", + "parentCode": "642", + "name": "连云区", + "en_name": "Lianyunqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "635", + "parentCode": "539", + "name": "南京", + "en_name": "NANJING", + "deleted": false, + "sublist": [ + { + "code": "2096", + "parentCode": "635", + "name": "高淳区", + "en_name": "Gaochun", + "deleted": false, + "sublist": [] + }, + { + "code": "2088", + "parentCode": "635", + "name": "鼓楼区", + "en_name": "Gulou", + "deleted": false, + "sublist": [] + }, + { + "code": "2094", + "parentCode": "635", + "name": "江宁区", + "en_name": "Jiangning", + "deleted": false, + "sublist": [] + }, + { + "code": "2087", + "parentCode": "635", + "name": "建邺区", + "en_name": "JianYe", + "deleted": false, + "sublist": [] + }, + { + "code": "2095", + "parentCode": "635", + "name": "溧水区", + "en_name": "Lishui", + "deleted": false, + "sublist": [] + }, + { + "code": "2091", + "parentCode": "635", + "name": "六合区", + "en_name": "Liuhe", + "deleted": false, + "sublist": [] + }, + { + "code": "2090", + "parentCode": "635", + "name": "浦口区", + "en_name": "Pukou", + "deleted": false, + "sublist": [] + }, + { + "code": "2086", + "parentCode": "635", + "name": "秦淮区", + "en_name": "Qinhuai", + "deleted": false, + "sublist": [] + }, + { + "code": "2092", + "parentCode": "635", + "name": "栖霞区", + "en_name": "Qixia", + "deleted": false, + "sublist": [] + }, + { + "code": "2084", + "parentCode": "635", + "name": "玄武区", + "en_name": "Xuanwu", + "deleted": false, + "sublist": [] + }, + { + "code": "2093", + "parentCode": "635", + "name": "雨花台区", + "en_name": "Yuhuatai", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "641", + "parentCode": "539", + "name": "南通", + "en_name": "NANTONG", + "deleted": false, + "sublist": [ + { + "code": "3044", + "parentCode": "641", + "name": "崇川区", + "en_name": "Chongchuanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3045", + "parentCode": "641", + "name": "港闸区", + "en_name": "Gangzhaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3046", + "parentCode": "641", + "name": "海安市", + "en_name": "Haianxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3048", + "parentCode": "641", + "name": "海门区", + "en_name": "haimenqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3395", + "parentCode": "641", + "name": "启东市", + "en_name": "Qidong", + "deleted": false, + "sublist": [] + }, + { + "code": "3047", + "parentCode": "641", + "name": "如东县", + "en_name": "Rudongxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3394", + "parentCode": "641", + "name": "如皋市", + "en_name": "Rugao", + "deleted": false, + "sublist": [] + }, + { + "code": "3399", + "parentCode": "641", + "name": "通州区", + "en_name": "Tongzhou", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "648", + "parentCode": "539", + "name": "宿迁", + "en_name": "SUQIAN", + "deleted": false, + "sublist": [ + { + "code": "3448", + "parentCode": "648", + "name": "沭阳县", + "en_name": "Muyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3450", + "parentCode": "648", + "name": "泗洪县", + "en_name": "Sihong", + "deleted": false, + "sublist": [] + }, + { + "code": "3449", + "parentCode": "648", + "name": "泗阳县", + "en_name": "Siyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3446", + "parentCode": "648", + "name": "宿城区", + "en_name": "Sucheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3447", + "parentCode": "648", + "name": "宿豫区", + "en_name": "Suyu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "639", + "parentCode": "539", + "name": "苏州", + "en_name": "SUZHOU", + "deleted": false, + "sublist": [ + { + "code": "650", + "parentCode": "639", + "name": "常熟市", + "en_name": "CHANGSHU", + "deleted": false, + "sublist": [] + }, + { + "code": "640", + "parentCode": "639", + "name": "昆山市", + "en_name": "KUNSHAN", + "deleted": false, + "sublist": [] + }, + { + "code": "911", + "parentCode": "639", + "name": "太仓市", + "en_name": "TAICANG", + "deleted": false, + "sublist": [] + }, + { + "code": "652", + "parentCode": "639", + "name": "张家港市", + "en_name": "ZHANGJIAGANG", + "deleted": false, + "sublist": [] + }, + { + "code": "2404", + "parentCode": "639", + "name": "高新区", + "en_name": "Gaoxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2218", + "parentCode": "639", + "name": "工业园区", + "en_name": "Gongyeyuang", + "deleted": false, + "sublist": [] + }, + { + "code": "2511", + "parentCode": "639", + "name": "姑苏区", + "en_name": "GUSUQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2215", + "parentCode": "639", + "name": "虎丘区", + "en_name": "Huqiu", + "deleted": false, + "sublist": [] + }, + { + "code": "2561", + "parentCode": "639", + "name": "吴江区", + "en_name": "WUJIANGQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2216", + "parentCode": "639", + "name": "吴中区", + "en_name": "Wuzhong", + "deleted": false, + "sublist": [] + }, + { + "code": "2217", + "parentCode": "639", + "name": "相城区", + "en_name": "Xiangcheng", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "647", + "parentCode": "539", + "name": "泰州", + "en_name": "TAIZHOU", + "deleted": false, + "sublist": [ + { + "code": "3068", + "parentCode": "647", + "name": "高港区", + "en_name": "Gaogangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3067", + "parentCode": "647", + "name": "海陵区", + "en_name": "Hailingqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3069", + "parentCode": "647", + "name": "姜堰区", + "en_name": "Jiangyanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3390", + "parentCode": "647", + "name": "靖江市", + "en_name": "Jingjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "3397", + "parentCode": "647", + "name": "泰兴市", + "en_name": "Taixing", + "deleted": false, + "sublist": [] + }, + { + "code": "3070", + "parentCode": "647", + "name": "兴化市", + "en_name": "Xinghuashi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "636", + "parentCode": "539", + "name": "无锡", + "en_name": "WUXI", + "deleted": false, + "sublist": [ + { + "code": "104016", + "parentCode": "636", + "name": "梁溪区", + "en_name": "LIANGXIQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2517", + "parentCode": "636", + "name": "滨湖区", + "en_name": "BINHUQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2519", + "parentCode": "636", + "name": "惠山区", + "en_name": "HUISHANQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2512", + "parentCode": "636", + "name": "江阴市", + "en_name": "JIANGYINSHI", + "deleted": false, + "sublist": [] + }, + { + "code": "2518", + "parentCode": "636", + "name": "新吴区", + "en_name": "WUXIXINQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2520", + "parentCode": "636", + "name": "锡山区", + "en_name": "XISHANQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2513", + "parentCode": "636", + "name": "宜兴市", + "en_name": "YIXINGSHI", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "637", + "parentCode": "539", + "name": "徐州", + "en_name": "XUZHOU", + "deleted": false, + "sublist": [ + { + "code": "3054", + "parentCode": "637", + "name": "丰县", + "en_name": "Fengxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3049", + "parentCode": "637", + "name": "鼓楼区", + "en_name": "Gulouqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3051", + "parentCode": "637", + "name": "贾汪区", + "en_name": "Jiawangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3055", + "parentCode": "637", + "name": "沛县", + "en_name": "Peixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3058", + "parentCode": "637", + "name": "邳州市", + "en_name": "Pizhoushi", + "deleted": false, + "sublist": [] + }, + { + "code": "3052", + "parentCode": "637", + "name": "泉山区", + "en_name": "Quanshanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3056", + "parentCode": "637", + "name": "睢宁县", + "en_name": "Suiningxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3053", + "parentCode": "637", + "name": "铜山区", + "en_name": "Tongshanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3057", + "parentCode": "637", + "name": "新沂市", + "en_name": "Xinyishi", + "deleted": false, + "sublist": [] + }, + { + "code": "3050", + "parentCode": "637", + "name": "云龙区", + "en_name": "Yunlongqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "644", + "parentCode": "539", + "name": "盐城", + "en_name": "YANCHENG", + "deleted": false, + "sublist": [ + { + "code": "3075", + "parentCode": "644", + "name": "滨海县", + "en_name": "Binhaixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3073", + "parentCode": "644", + "name": "大丰区", + "en_name": "Dafengqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3079", + "parentCode": "644", + "name": "东台市", + "en_name": "Dongtaishi", + "deleted": false, + "sublist": [] + }, + { + "code": "3076", + "parentCode": "644", + "name": "阜宁县", + "en_name": "Funingxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3078", + "parentCode": "644", + "name": "建湖县", + "en_name": "Jianhuxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3077", + "parentCode": "644", + "name": "射阳县", + "en_name": "Sheyangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3071", + "parentCode": "644", + "name": "亭湖区", + "en_name": "Tinghuqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3074", + "parentCode": "644", + "name": "响水县", + "en_name": "Xiangshuixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3072", + "parentCode": "644", + "name": "盐都区", + "en_name": "Yanduqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "645", + "parentCode": "539", + "name": "扬州", + "en_name": "YANGZHOU", + "deleted": false, + "sublist": [ + { + "code": "3066", + "parentCode": "645", + "name": "宝应县", + "en_name": "Baoyingxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3396", + "parentCode": "645", + "name": "高邮市", + "en_name": "Gaoyou", + "deleted": false, + "sublist": [] + }, + { + "code": "3063", + "parentCode": "645", + "name": "广陵区", + "en_name": "Guanglingqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3062", + "parentCode": "645", + "name": "邗江区", + "en_name": "Hanjiangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3064", + "parentCode": "645", + "name": "江都区", + "en_name": "Jiangduqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3065", + "parentCode": "645", + "name": "仪征市", + "en_name": "Yizhengshi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "646", + "parentCode": "539", + "name": "镇江", + "en_name": "ZHENJIANG", + "deleted": false, + "sublist": [ + { + "code": "3061", + "parentCode": "646", + "name": "丹徒区", + "en_name": "Dantuqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3392", + "parentCode": "646", + "name": "丹阳市", + "en_name": "Danyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3059", + "parentCode": "646", + "name": "京口区", + "en_name": "Jingkouqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3391", + "parentCode": "646", + "name": "句容市", + "en_name": "Jurong", + "deleted": false, + "sublist": [] + }, + { + "code": "3060", + "parentCode": "646", + "name": "润州区", + "en_name": "Runzhouqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3393", + "parentCode": "646", + "name": "扬中市", + "en_name": "Yangzhong", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "543", + "parentCode": "489", + "name": "江西", + "en_name": "JIANGXI", + "deleted": false, + "sublist": [ + { + "code": "700", + "parentCode": "543", + "name": "抚州", + "en_name": "FUZHOU", + "deleted": false, + "sublist": [ + { + "code": "3694", + "parentCode": "700", + "name": "崇仁县", + "en_name": "Chongren", + "deleted": false, + "sublist": [] + }, + { + "code": "3699", + "parentCode": "700", + "name": "东乡区", + "en_name": "Dongxiang", + "deleted": false, + "sublist": [] + }, + { + "code": "3700", + "parentCode": "700", + "name": "广昌县", + "en_name": "Guangchang", + "deleted": false, + "sublist": [] + }, + { + "code": "3697", + "parentCode": "700", + "name": "金溪县", + "en_name": "Jinxi", + "deleted": false, + "sublist": [] + }, + { + "code": "3695", + "parentCode": "700", + "name": "乐安县", + "en_name": "Lean", + "deleted": false, + "sublist": [] + }, + { + "code": "3692", + "parentCode": "700", + "name": "黎川县", + "en_name": "Lichuan", + "deleted": false, + "sublist": [] + }, + { + "code": "3690", + "parentCode": "700", + "name": "临川区", + "en_name": "Linchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "3691", + "parentCode": "700", + "name": "南城县", + "en_name": "Nancheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3693", + "parentCode": "700", + "name": "南丰县", + "en_name": "Nanfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "3696", + "parentCode": "700", + "name": "宜黄县", + "en_name": "Yihuang", + "deleted": false, + "sublist": [] + }, + { + "code": "3698", + "parentCode": "700", + "name": "资溪县", + "en_name": "Zixi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "697", + "parentCode": "543", + "name": "赣州", + "en_name": "GANZHOU", + "deleted": false, + "sublist": [ + { + "code": "3657", + "parentCode": "697", + "name": "安远县", + "en_name": "Anyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "3656", + "parentCode": "697", + "name": "崇义县", + "en_name": "Chongyi", + "deleted": false, + "sublist": [] + }, + { + "code": "3654", + "parentCode": "697", + "name": "大余县", + "en_name": "Dayu", + "deleted": false, + "sublist": [] + }, + { + "code": "3659", + "parentCode": "697", + "name": "定南县", + "en_name": "Dingnan", + "deleted": false, + "sublist": [] + }, + { + "code": "3652", + "parentCode": "697", + "name": "赣县区", + "en_name": "Gan", + "deleted": false, + "sublist": [] + }, + { + "code": "3664", + "parentCode": "697", + "name": "会昌县", + "en_name": "Huichang", + "deleted": false, + "sublist": [] + }, + { + "code": "3658", + "parentCode": "697", + "name": "龙南市", + "en_name": "longnanshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3651", + "parentCode": "697", + "name": "南康区", + "en_name": "Nankang", + "deleted": false, + "sublist": [] + }, + { + "code": "3661", + "parentCode": "697", + "name": "宁都县", + "en_name": "Ningdu", + "deleted": false, + "sublist": [] + }, + { + "code": "3660", + "parentCode": "697", + "name": "全南县", + "en_name": "Annan", + "deleted": false, + "sublist": [] + }, + { + "code": "3650", + "parentCode": "697", + "name": "瑞金市", + "en_name": "Ruijin", + "deleted": false, + "sublist": [] + }, + { + "code": "3655", + "parentCode": "697", + "name": "上犹县", + "en_name": "Shangyou", + "deleted": false, + "sublist": [] + }, + { + "code": "3666", + "parentCode": "697", + "name": "石城县", + "en_name": "Shicheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3653", + "parentCode": "697", + "name": "信丰县", + "en_name": "Xinfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "3663", + "parentCode": "697", + "name": "兴国县", + "en_name": "Xingguo", + "deleted": false, + "sublist": [] + }, + { + "code": "3665", + "parentCode": "697", + "name": "寻乌县", + "en_name": "Xunwu", + "deleted": false, + "sublist": [] + }, + { + "code": "3662", + "parentCode": "697", + "name": "于都县", + "en_name": "Yudu", + "deleted": false, + "sublist": [] + }, + { + "code": "3649", + "parentCode": "697", + "name": "章贡区", + "en_name": "Zhanggong", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "698", + "parentCode": "543", + "name": "吉安", + "en_name": "JIAN", + "deleted": false, + "sublist": [ + { + "code": "3678", + "parentCode": "698", + "name": "安福县", + "en_name": "Anfu", + "deleted": false, + "sublist": [] + }, + { + "code": "3670", + "parentCode": "698", + "name": "吉安县", + "en_name": "Jian", + "deleted": false, + "sublist": [] + }, + { + "code": "3669", + "parentCode": "698", + "name": "井冈山市", + "en_name": "JGS", + "deleted": false, + "sublist": [] + }, + { + "code": "3671", + "parentCode": "698", + "name": "吉水县", + "en_name": "Jishui", + "deleted": false, + "sublist": [] + }, + { + "code": "3667", + "parentCode": "698", + "name": "吉州区", + "en_name": "Jizhou", + "deleted": false, + "sublist": [] + }, + { + "code": "3668", + "parentCode": "698", + "name": "青原区", + "en_name": "Qingyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "3676", + "parentCode": "698", + "name": "遂川县", + "en_name": "Suichuan", + "deleted": false, + "sublist": [] + }, + { + "code": "3675", + "parentCode": "698", + "name": "泰和县", + "en_name": "Taihe", + "deleted": false, + "sublist": [] + }, + { + "code": "3677", + "parentCode": "698", + "name": "万安县", + "en_name": "Wanan", + "deleted": false, + "sublist": [] + }, + { + "code": "3672", + "parentCode": "698", + "name": "峡江县", + "en_name": "Xiajiang", + "deleted": false, + "sublist": [] + }, + { + "code": "3673", + "parentCode": "698", + "name": "新干县", + "en_name": "Xingan", + "deleted": false, + "sublist": [] + }, + { + "code": "3674", + "parentCode": "698", + "name": "永丰县", + "en_name": "Yongfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "3679", + "parentCode": "698", + "name": "永新县", + "en_name": "Yongxin", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "692", + "parentCode": "543", + "name": "景德镇", + "en_name": "JINGDEZHEN", + "deleted": false, + "sublist": [ + { + "code": "3622", + "parentCode": "692", + "name": "昌江区", + "en_name": "Changjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "3625", + "parentCode": "692", + "name": "浮梁县", + "en_name": "Fuliang", + "deleted": false, + "sublist": [] + }, + { + "code": "3624", + "parentCode": "692", + "name": "乐平市", + "en_name": "Leping", + "deleted": false, + "sublist": [] + }, + { + "code": "3623", + "parentCode": "692", + "name": "珠山区", + "en_name": "Zhushan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "694", + "parentCode": "543", + "name": "九江", + "en_name": "JIUJIANG", + "deleted": false, + "sublist": [ + { + "code": "3639", + "parentCode": "694", + "name": "德安县", + "en_name": "Dean", + "deleted": false, + "sublist": [] + }, + { + "code": "3641", + "parentCode": "694", + "name": "都昌县", + "en_name": "Duchang", + "deleted": false, + "sublist": [] + }, + { + "code": "3634", + "parentCode": "694", + "name": "共青城市", + "en_name": "Gongqingcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3642", + "parentCode": "694", + "name": "湖口县", + "en_name": "Hukou", + "deleted": false, + "sublist": [] + }, + { + "code": "3635", + "parentCode": "694", + "name": "柴桑区", + "en_name": "Jiujiang", + "deleted": false, + "sublist": [] + }, + { + "code": "3631", + "parentCode": "694", + "name": "濂溪区", + "en_name": "lianxi", + "deleted": false, + "sublist": [] + }, + { + "code": "3643", + "parentCode": "694", + "name": "彭泽县", + "en_name": "Pengze", + "deleted": false, + "sublist": [] + }, + { + "code": "3633", + "parentCode": "694", + "name": "瑞昌市", + "en_name": "Ruichang", + "deleted": false, + "sublist": [] + }, + { + "code": "3636", + "parentCode": "694", + "name": "武宁县", + "en_name": "Wuning", + "deleted": false, + "sublist": [] + }, + { + "code": "3640", + "parentCode": "694", + "name": "庐山市", + "en_name": "Xingzi", + "deleted": false, + "sublist": [] + }, + { + "code": "3637", + "parentCode": "694", + "name": "修水县", + "en_name": "Xiushui", + "deleted": false, + "sublist": [] + }, + { + "code": "3632", + "parentCode": "694", + "name": "浔阳区", + "en_name": "Xunyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3638", + "parentCode": "694", + "name": "永修县", + "en_name": "Yongxiu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "691", + "parentCode": "543", + "name": "南昌", + "en_name": "NANCHANG", + "deleted": false, + "sublist": [ + { + "code": "2544", + "parentCode": "691", + "name": "安义县", + "en_name": "Anyixian", + "deleted": false, + "sublist": [] + }, + { + "code": "2536", + "parentCode": "691", + "name": "东湖区", + "en_name": "Donghuqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3302", + "parentCode": "691", + "name": "红谷滩区", + "en_name": "Honggutanxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2543", + "parentCode": "691", + "name": "进贤县", + "en_name": "Jinxianxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3305", + "parentCode": "691", + "name": "南昌高新区", + "en_name": "Nanchanggaoxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3304", + "parentCode": "691", + "name": "南昌经济开发区", + "en_name": "Nanchangjingjikaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3303", + "parentCode": "691", + "name": "南昌临空经济区", + "en_name": "Nanchanglinkongjingjiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3308", + "parentCode": "691", + "name": "南昌望城新区", + "en_name": "Wangchengxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2541", + "parentCode": "691", + "name": "南昌县", + "en_name": "Nanchangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3306", + "parentCode": "691", + "name": "南昌小蓝经济技术开发区", + "en_name": "Nanchangxiaolanjingjijishukaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2539", + "parentCode": "691", + "name": "青山湖区", + "en_name": "Qingshanhuqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2538", + "parentCode": "691", + "name": "青云谱区", + "en_name": "Qingyunpuqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3307", + "parentCode": "691", + "name": "桑海经济技术开发区", + "en_name": "Sanghaijingjijishukaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2537", + "parentCode": "691", + "name": "西湖区", + "en_name": "Xihuqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2542", + "parentCode": "691", + "name": "新建区", + "en_name": "Xinjianqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "693", + "parentCode": "543", + "name": "萍乡", + "en_name": "PINGXIANG", + "deleted": false, + "sublist": [ + { + "code": "3626", + "parentCode": "693", + "name": "安源区", + "en_name": "Anyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "3628", + "parentCode": "693", + "name": "莲花县", + "en_name": "Lianhua", + "deleted": false, + "sublist": [] + }, + { + "code": "3630", + "parentCode": "693", + "name": "芦溪县", + "en_name": "Luxi", + "deleted": false, + "sublist": [] + }, + { + "code": "3629", + "parentCode": "693", + "name": "上栗县", + "en_name": "Shangli", + "deleted": false, + "sublist": [] + }, + { + "code": "3627", + "parentCode": "693", + "name": "湘东区", + "en_name": "Xiangdong", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "701", + "parentCode": "543", + "name": "上饶", + "en_name": "SHANGRAO", + "deleted": false, + "sublist": [ + { + "code": "3702", + "parentCode": "701", + "name": "德兴市", + "en_name": "Dexing", + "deleted": false, + "sublist": [] + }, + { + "code": "3704", + "parentCode": "701", + "name": "广丰区", + "en_name": "Guangfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "3707", + "parentCode": "701", + "name": "横峰县", + "en_name": "Hengfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "3710", + "parentCode": "701", + "name": "鄱阳县", + "en_name": "Poyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3703", + "parentCode": "701", + "name": "广信区", + "en_name": "Shangrao", + "deleted": false, + "sublist": [] + }, + { + "code": "3711", + "parentCode": "701", + "name": "万年县", + "en_name": "Wannian", + "deleted": false, + "sublist": [] + }, + { + "code": "3712", + "parentCode": "701", + "name": "婺源县", + "en_name": "Wuyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "3701", + "parentCode": "701", + "name": "信州区", + "en_name": "Xinzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "3706", + "parentCode": "701", + "name": "铅山县", + "en_name": "Qianshan", + "deleted": false, + "sublist": [] + }, + { + "code": "3708", + "parentCode": "701", + "name": "弋阳县", + "en_name": "Yeyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3709", + "parentCode": "701", + "name": "余干县", + "en_name": "Yugan", + "deleted": false, + "sublist": [] + }, + { + "code": "3705", + "parentCode": "701", + "name": "玉山县", + "en_name": "Yushan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "695", + "parentCode": "543", + "name": "新余", + "en_name": "XINYU", + "deleted": false, + "sublist": [ + { + "code": "3645", + "parentCode": "695", + "name": "分宜县", + "en_name": "Fenyi", + "deleted": false, + "sublist": [] + }, + { + "code": "3644", + "parentCode": "695", + "name": "渝水区", + "en_name": "Yushui", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "699", + "parentCode": "543", + "name": "宜春", + "en_name": "YICHUN", + "deleted": false, + "sublist": [ + { + "code": "3681", + "parentCode": "699", + "name": "丰城市", + "en_name": "Fengcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3684", + "parentCode": "699", + "name": "奉新县", + "en_name": "Fengxin", + "deleted": false, + "sublist": [] + }, + { + "code": "3683", + "parentCode": "699", + "name": "高安市", + "en_name": "Gaoan", + "deleted": false, + "sublist": [] + }, + { + "code": "3688", + "parentCode": "699", + "name": "靖安县", + "en_name": "Jingan", + "deleted": false, + "sublist": [] + }, + { + "code": "3686", + "parentCode": "699", + "name": "上高县", + "en_name": "Shanggao", + "deleted": false, + "sublist": [] + }, + { + "code": "3689", + "parentCode": "699", + "name": "铜鼓县", + "en_name": "Tonggu", + "deleted": false, + "sublist": [] + }, + { + "code": "3685", + "parentCode": "699", + "name": "万载县", + "en_name": "Wanzai", + "deleted": false, + "sublist": [] + }, + { + "code": "3687", + "parentCode": "699", + "name": "宜丰县", + "en_name": "Yifeng", + "deleted": false, + "sublist": [] + }, + { + "code": "3680", + "parentCode": "699", + "name": "袁州区", + "en_name": "Yuanzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "3682", + "parentCode": "699", + "name": "樟树市", + "en_name": "Zhangshu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "696", + "parentCode": "543", + "name": "鹰潭", + "en_name": "YINGTAN", + "deleted": false, + "sublist": [ + { + "code": "3647", + "parentCode": "696", + "name": "贵溪市", + "en_name": "Guixi", + "deleted": false, + "sublist": [] + }, + { + "code": "3646", + "parentCode": "696", + "name": "月湖区", + "en_name": "Yuehu", + "deleted": false, + "sublist": [] + }, + { + "code": "3648", + "parentCode": "696", + "name": "余江区", + "en_name": "Yujiang", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "536", + "parentCode": "489", + "name": "吉林", + "en_name": "JILIN", + "deleted": false, + "sublist": [ + { + "code": "620", + "parentCode": "536", + "name": "白城", + "en_name": "BAICHENG", + "deleted": false, + "sublist": [ + { + "code": "103209", + "parentCode": "620", + "name": "大安市", + "en_name": "Daan", + "deleted": false, + "sublist": [] + }, + { + "code": "103207", + "parentCode": "620", + "name": "洮北区", + "en_name": "Taobei", + "deleted": false, + "sublist": [] + }, + { + "code": "103208", + "parentCode": "620", + "name": "洮南市", + "en_name": "Taonan", + "deleted": false, + "sublist": [] + }, + { + "code": "103211", + "parentCode": "620", + "name": "通榆县", + "en_name": "Tongyu", + "deleted": false, + "sublist": [] + }, + { + "code": "103210", + "parentCode": "620", + "name": "镇赉县", + "en_name": "Zhenlai", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "618", + "parentCode": "536", + "name": "白山", + "en_name": "BAISHAN", + "deleted": false, + "sublist": [ + { + "code": "103199", + "parentCode": "618", + "name": "抚松县", + "en_name": "Fusong", + "deleted": false, + "sublist": [] + }, + { + "code": "103196", + "parentCode": "618", + "name": "浑江区", + "en_name": "Hunjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "103197", + "parentCode": "618", + "name": "江源区", + "en_name": "Jiangyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "103200", + "parentCode": "618", + "name": "靖宇县", + "en_name": "Jingyu", + "deleted": false, + "sublist": [] + }, + { + "code": "103198", + "parentCode": "618", + "name": "临江市", + "en_name": "Linjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "103201", + "parentCode": "618", + "name": "长白朝鲜族自治县", + "en_name": "Changbaichaoxianzuzizhi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "613", + "parentCode": "536", + "name": "长春", + "en_name": "CHANGCHUN", + "deleted": false, + "sublist": [ + { + "code": "10122", + "parentCode": "613", + "name": "公主岭市", + "en_name": "GONGZHULING", + "deleted": false, + "sublist": [] + }, + { + "code": "2142", + "parentCode": "613", + "name": "朝阳区", + "en_name": "CHAOYANGQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2389", + "parentCode": "613", + "name": "德惠市", + "en_name": "Dehui", + "deleted": false, + "sublist": [] + }, + { + "code": "2143", + "parentCode": "613", + "name": "二道区", + "en_name": "Erdao", + "deleted": false, + "sublist": [] + }, + { + "code": "2145", + "parentCode": "613", + "name": "高新开发区", + "en_name": "Gaoxinkaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2146", + "parentCode": "613", + "name": "经济开发区", + "en_name": "Jingjikaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2388", + "parentCode": "613", + "name": "九台区", + "en_name": "Jiutai", + "deleted": false, + "sublist": [] + }, + { + "code": "2141", + "parentCode": "613", + "name": "宽城区", + "en_name": "Kuancheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2144", + "parentCode": "613", + "name": "绿园区", + "en_name": "Lvyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2140", + "parentCode": "613", + "name": "南关区", + "en_name": "Nanguan", + "deleted": false, + "sublist": [] + }, + { + "code": "2390", + "parentCode": "613", + "name": "农安县", + "en_name": "Nongan", + "deleted": false, + "sublist": [] + }, + { + "code": "2147", + "parentCode": "613", + "name": "汽车产业开发区", + "en_name": "Qichechanyekaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2148", + "parentCode": "613", + "name": "双阳区", + "en_name": "Shuangyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2387", + "parentCode": "613", + "name": "榆树市", + "en_name": "Yushu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "614", + "parentCode": "536", + "name": "吉林市", + "en_name": "JILINSHI", + "deleted": false, + "sublist": [ + { + "code": "103170", + "parentCode": "614", + "name": "昌邑区", + "en_name": "Changyi", + "deleted": false, + "sublist": [] + }, + { + "code": "103172", + "parentCode": "614", + "name": "船营区", + "en_name": "Chuanying", + "deleted": false, + "sublist": [] + }, + { + "code": "103173", + "parentCode": "614", + "name": "丰满区", + "en_name": "Fengman", + "deleted": false, + "sublist": [] + }, + { + "code": "103175", + "parentCode": "614", + "name": "桦甸市", + "en_name": "Huadian", + "deleted": false, + "sublist": [] + }, + { + "code": "103174", + "parentCode": "614", + "name": "蛟河市", + "en_name": "Jiaohe", + "deleted": false, + "sublist": [] + }, + { + "code": "103171", + "parentCode": "614", + "name": "龙潭区", + "en_name": "Longtan", + "deleted": false, + "sublist": [] + }, + { + "code": "103177", + "parentCode": "614", + "name": "磐石市", + "en_name": "Panshi", + "deleted": false, + "sublist": [] + }, + { + "code": "103176", + "parentCode": "614", + "name": "舒兰市", + "en_name": "Shulan", + "deleted": false, + "sublist": [] + }, + { + "code": "103178", + "parentCode": "614", + "name": "永吉县", + "en_name": "Yongji", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "616", + "parentCode": "536", + "name": "辽源", + "en_name": "LIAOYUAN", + "deleted": false, + "sublist": [ + { + "code": "103187", + "parentCode": "616", + "name": "东丰县", + "en_name": "Dongfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "103188", + "parentCode": "616", + "name": "东辽县", + "en_name": "Dongliao", + "deleted": false, + "sublist": [] + }, + { + "code": "103185", + "parentCode": "616", + "name": "龙山区", + "en_name": "Longshan", + "deleted": false, + "sublist": [] + }, + { + "code": "103186", + "parentCode": "616", + "name": "西安区", + "en_name": "Xian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "615", + "parentCode": "536", + "name": "四平", + "en_name": "SIPING", + "deleted": false, + "sublist": [ + { + "code": "103183", + "parentCode": "615", + "name": "梨树县", + "en_name": "Lishu", + "deleted": false, + "sublist": [] + }, + { + "code": "103182", + "parentCode": "615", + "name": "双辽市", + "en_name": "Shuangliao", + "deleted": false, + "sublist": [] + }, + { + "code": "103180", + "parentCode": "615", + "name": "铁东区", + "en_name": "Tiedong", + "deleted": false, + "sublist": [] + }, + { + "code": "103179", + "parentCode": "615", + "name": "铁西区", + "en_name": "Tiexi", + "deleted": false, + "sublist": [] + }, + { + "code": "103184", + "parentCode": "615", + "name": "伊通满族自治县", + "en_name": "Yitongmanzuzizhi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "619", + "parentCode": "536", + "name": "松原", + "en_name": "SONGYUAN", + "deleted": false, + "sublist": [ + { + "code": "103203", + "parentCode": "619", + "name": "扶余市", + "en_name": "Fuyu", + "deleted": false, + "sublist": [] + }, + { + "code": "103202", + "parentCode": "619", + "name": "宁江区", + "en_name": "Ningjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "103206", + "parentCode": "619", + "name": "乾安县", + "en_name": "Qianan", + "deleted": false, + "sublist": [] + }, + { + "code": "103204", + "parentCode": "619", + "name": "前郭尔罗斯蒙古族自治县", + "en_name": "Qianguoerluosimengguzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "103205", + "parentCode": "619", + "name": "长岭县", + "en_name": "Changling", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "617", + "parentCode": "536", + "name": "通化", + "en_name": "TONGHUA", + "deleted": false, + "sublist": [ + { + "code": "4001", + "parentCode": "617", + "name": "东昌区", + "en_name": "DONGCHANGQU", + "deleted": false, + "sublist": [] + }, + { + "code": "4002", + "parentCode": "617", + "name": "二道江区", + "en_name": "ERDAOJIANGQU", + "deleted": false, + "sublist": [] + }, + { + "code": "4004", + "parentCode": "617", + "name": "辉南县", + "en_name": "HUINANXIAN", + "deleted": false, + "sublist": [] + }, + { + "code": "4007", + "parentCode": "617", + "name": "集安市", + "en_name": "JIANSHI", + "deleted": false, + "sublist": [] + }, + { + "code": "4005", + "parentCode": "617", + "name": "柳河县", + "en_name": "LIUHEXIAN", + "deleted": false, + "sublist": [] + }, + { + "code": "4006", + "parentCode": "617", + "name": "梅河口市", + "en_name": "MEIHEKOUSHI", + "deleted": false, + "sublist": [] + }, + { + "code": "4003", + "parentCode": "617", + "name": "通化县", + "en_name": "TONGHUAXIAN", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "621", + "parentCode": "536", + "name": "延边", + "en_name": "YANBIAN", + "deleted": false, + "sublist": [ + { + "code": "10198", + "parentCode": "621", + "name": "珲春市", + "en_name": "HUNCHUN", + "deleted": false, + "sublist": [] + }, + { + "code": "103219", + "parentCode": "621", + "name": "安图县", + "en_name": "Antu", + "deleted": false, + "sublist": [] + }, + { + "code": "103214", + "parentCode": "621", + "name": "敦化市", + "en_name": "Dunhua", + "deleted": false, + "sublist": [] + }, + { + "code": "103217", + "parentCode": "621", + "name": "和龙市", + "en_name": "Helong", + "deleted": false, + "sublist": [] + }, + { + "code": "103216", + "parentCode": "621", + "name": "龙井市", + "en_name": "Longjing", + "deleted": false, + "sublist": [] + }, + { + "code": "103213", + "parentCode": "621", + "name": "图们市", + "en_name": "Tumen", + "deleted": false, + "sublist": [] + }, + { + "code": "103218", + "parentCode": "621", + "name": "汪清县", + "en_name": "Wangqing", + "deleted": false, + "sublist": [] + }, + { + "code": "103212", + "parentCode": "621", + "name": "延吉市", + "en_name": "Yanji", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "535", + "parentCode": "489", + "name": "辽宁", + "en_name": "LIAONING", + "deleted": false, + "sublist": [ + { + "code": "601", + "parentCode": "535", + "name": "鞍山", + "en_name": "ANSHAN", + "deleted": false, + "sublist": [ + { + "code": "10070", + "parentCode": "601", + "name": "海城市", + "en_name": "HAICHENG", + "deleted": false, + "sublist": [] + }, + { + "code": "103082", + "parentCode": "601", + "name": "立山区", + "en_name": "Lishan", + "deleted": false, + "sublist": [] + }, + { + "code": "103083", + "parentCode": "601", + "name": "千山区", + "en_name": "Qianshan", + "deleted": false, + "sublist": [] + }, + { + "code": "103085", + "parentCode": "601", + "name": "台安县", + "en_name": "Taian", + "deleted": false, + "sublist": [] + }, + { + "code": "103080", + "parentCode": "601", + "name": "铁东区", + "en_name": "Tiedong", + "deleted": false, + "sublist": [] + }, + { + "code": "103081", + "parentCode": "601", + "name": "铁西区", + "en_name": "Tiexi", + "deleted": false, + "sublist": [] + }, + { + "code": "103086", + "parentCode": "601", + "name": "岫岩满族自治县", + "en_name": "Xiuyanmanzuzizhi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "603", + "parentCode": "535", + "name": "本溪", + "en_name": "BENXI", + "deleted": false, + "sublist": [ + { + "code": "103098", + "parentCode": "603", + "name": "本溪满族自治县", + "en_name": "Benximanzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "103099", + "parentCode": "603", + "name": "桓仁满族自治县", + "en_name": "Huanrenmanzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "103096", + "parentCode": "603", + "name": "明山区", + "en_name": "Mingshan", + "deleted": false, + "sublist": [] + }, + { + "code": "103097", + "parentCode": "603", + "name": "南芬区", + "en_name": "Nanfen", + "deleted": false, + "sublist": [] + }, + { + "code": "103094", + "parentCode": "603", + "name": "平山区", + "en_name": "Pingshan", + "deleted": false, + "sublist": [] + }, + { + "code": "103095", + "parentCode": "603", + "name": "溪湖区", + "en_name": "Xihu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "600", + "parentCode": "535", + "name": "大连", + "en_name": "DALIAN", + "deleted": false, + "sublist": [ + { + "code": "2398", + "parentCode": "600", + "name": "长兴岛", + "en_name": "Changxing", + "deleted": false, + "sublist": [] + }, + { + "code": "2184", + "parentCode": "600", + "name": "甘井子区", + "en_name": "Ganjingzi", + "deleted": false, + "sublist": [] + }, + { + "code": "2185", + "parentCode": "600", + "name": "高新园区", + "en_name": "Gaoxinyuanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2188", + "parentCode": "600", + "name": "金州区", + "en_name": "Jinzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "2186", + "parentCode": "600", + "name": "开发区", + "en_name": "Kaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2187", + "parentCode": "600", + "name": "旅顺口区", + "en_name": "Lvshunkou", + "deleted": false, + "sublist": [] + }, + { + "code": "2394", + "parentCode": "600", + "name": "普兰店区", + "en_name": "Pulandian", + "deleted": false, + "sublist": [] + }, + { + "code": "2183", + "parentCode": "600", + "name": "沙河口区", + "en_name": "Shahekou", + "deleted": false, + "sublist": [] + }, + { + "code": "2395", + "parentCode": "600", + "name": "瓦房店市", + "en_name": "Wafangdian", + "deleted": false, + "sublist": [] + }, + { + "code": "2181", + "parentCode": "600", + "name": "西岗区", + "en_name": "Xigang", + "deleted": false, + "sublist": [] + }, + { + "code": "2397", + "parentCode": "600", + "name": "长海县", + "en_name": "Changhai", + "deleted": false, + "sublist": [] + }, + { + "code": "2182", + "parentCode": "600", + "name": "中山区", + "en_name": "Zhongshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2396", + "parentCode": "600", + "name": "庄河市", + "en_name": "Zhuanghe", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "604", + "parentCode": "535", + "name": "丹东", + "en_name": "DANDONG", + "deleted": false, + "sublist": [ + { + "code": "931", + "parentCode": "604", + "name": "东港市", + "en_name": "DONGGANG", + "deleted": false, + "sublist": [] + }, + { + "code": "103104", + "parentCode": "604", + "name": "凤城市", + "en_name": "Fengcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "103105", + "parentCode": "604", + "name": "宽甸满族自治县", + "en_name": "Kuandianmanzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "103100", + "parentCode": "604", + "name": "元宝区", + "en_name": "Yuanbao", + "deleted": false, + "sublist": [] + }, + { + "code": "103102", + "parentCode": "604", + "name": "振安区", + "en_name": "Zhenan", + "deleted": false, + "sublist": [] + }, + { + "code": "103101", + "parentCode": "604", + "name": "振兴区", + "en_name": "Zhenxing", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "602", + "parentCode": "535", + "name": "抚顺", + "en_name": "FUSHUN", + "deleted": false, + "sublist": [ + { + "code": "103088", + "parentCode": "602", + "name": "东洲区", + "en_name": "Dongzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "103091", + "parentCode": "602", + "name": "抚顺县", + "en_name": "Fushun", + "deleted": false, + "sublist": [] + }, + { + "code": "103092", + "parentCode": "602", + "name": "清原满族自治县", + "en_name": "Qingyuanmanzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "103090", + "parentCode": "602", + "name": "顺城区", + "en_name": "Shuncheng", + "deleted": false, + "sublist": [] + }, + { + "code": "103089", + "parentCode": "602", + "name": "望花区", + "en_name": "Wanghua", + "deleted": false, + "sublist": [] + }, + { + "code": "103093", + "parentCode": "602", + "name": "新宾满族自治县", + "en_name": "Xinbinmanzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "103087", + "parentCode": "602", + "name": "新抚区", + "en_name": "Xinfu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "607", + "parentCode": "535", + "name": "阜新", + "en_name": "FUXIN", + "deleted": false, + "sublist": [ + { + "code": "103124", + "parentCode": "607", + "name": "阜新蒙古族自治县", + "en_name": "Huxinmengguzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "103119", + "parentCode": "607", + "name": "海州区", + "en_name": "Haizhou", + "deleted": false, + "sublist": [] + }, + { + "code": "103122", + "parentCode": "607", + "name": "清河门区", + "en_name": "Qinghemen", + "deleted": false, + "sublist": [] + }, + { + "code": "103121", + "parentCode": "607", + "name": "太平区", + "en_name": "Taiping", + "deleted": false, + "sublist": [] + }, + { + "code": "103123", + "parentCode": "607", + "name": "细河区", + "en_name": "Xihe", + "deleted": false, + "sublist": [] + }, + { + "code": "103120", + "parentCode": "607", + "name": "新邱区", + "en_name": "Xinqiu", + "deleted": false, + "sublist": [] + }, + { + "code": "103125", + "parentCode": "607", + "name": "彰武县", + "en_name": "Zhangwu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "612", + "parentCode": "535", + "name": "葫芦岛", + "en_name": "HULUDAO", + "deleted": false, + "sublist": [ + { + "code": "10023", + "parentCode": "612", + "name": "兴城市", + "en_name": "XINGCHENG", + "deleted": false, + "sublist": [] + }, + { + "code": "103156", + "parentCode": "612", + "name": "建昌县", + "en_name": "Jianchang", + "deleted": false, + "sublist": [] + }, + { + "code": "103151", + "parentCode": "612", + "name": "连山区", + "en_name": "Lianshan", + "deleted": false, + "sublist": [] + }, + { + "code": "103152", + "parentCode": "612", + "name": "龙港区", + "en_name": "Longgang", + "deleted": false, + "sublist": [] + }, + { + "code": "103153", + "parentCode": "612", + "name": "南票区", + "en_name": "Nanpiao", + "deleted": false, + "sublist": [] + }, + { + "code": "103155", + "parentCode": "612", + "name": "绥中县", + "en_name": "Suizhong", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "605", + "parentCode": "535", + "name": "锦州", + "en_name": "JINZHOU", + "deleted": false, + "sublist": [ + { + "code": "103109", + "parentCode": "605", + "name": "北镇市", + "en_name": "Beizhen", + "deleted": false, + "sublist": [] + }, + { + "code": "103106", + "parentCode": "605", + "name": "古塔区", + "en_name": "Guta", + "deleted": false, + "sublist": [] + }, + { + "code": "103111", + "parentCode": "605", + "name": "黑山县", + "en_name": "Heishan", + "deleted": false, + "sublist": [] + }, + { + "code": "103107", + "parentCode": "605", + "name": "凌海市", + "en_name": "Linghai", + "deleted": false, + "sublist": [] + }, + { + "code": "103108", + "parentCode": "605", + "name": "凌河区", + "en_name": "Linghe", + "deleted": false, + "sublist": [] + }, + { + "code": "103110", + "parentCode": "605", + "name": "太和区", + "en_name": "Taihe", + "deleted": false, + "sublist": [] + }, + { + "code": "103112", + "parentCode": "605", + "name": "义县", + "en_name": "Yi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "608", + "parentCode": "535", + "name": "辽阳", + "en_name": "LIAOYANG", + "deleted": false, + "sublist": [ + { + "code": "103126", + "parentCode": "608", + "name": "白塔区", + "en_name": "Baita", + "deleted": false, + "sublist": [] + }, + { + "code": "103131", + "parentCode": "608", + "name": "灯塔市", + "en_name": "Dengta", + "deleted": false, + "sublist": [] + }, + { + "code": "103129", + "parentCode": "608", + "name": "弓长岭区", + "en_name": "Gongchangling", + "deleted": false, + "sublist": [] + }, + { + "code": "103128", + "parentCode": "608", + "name": "宏伟区", + "en_name": "Hongwei", + "deleted": false, + "sublist": [] + }, + { + "code": "103132", + "parentCode": "608", + "name": "辽阳县", + "en_name": "Liaoyang", + "deleted": false, + "sublist": [] + }, + { + "code": "103130", + "parentCode": "608", + "name": "太子河区", + "en_name": "Taizihe", + "deleted": false, + "sublist": [] + }, + { + "code": "103127", + "parentCode": "608", + "name": "文圣区", + "en_name": "Wensheng", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "609", + "parentCode": "535", + "name": "盘锦", + "en_name": "PANJIN", + "deleted": false, + "sublist": [ + { + "code": "103135", + "parentCode": "609", + "name": "大洼区", + "en_name": "Dawa", + "deleted": false, + "sublist": [] + }, + { + "code": "103136", + "parentCode": "609", + "name": "盘山县", + "en_name": "Panshan", + "deleted": false, + "sublist": [] + }, + { + "code": "103133", + "parentCode": "609", + "name": "双台子区", + "en_name": "Shuangtaizi", + "deleted": false, + "sublist": [] + }, + { + "code": "103134", + "parentCode": "609", + "name": "兴隆台区", + "en_name": "Xinglongtai", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "599", + "parentCode": "535", + "name": "沈阳", + "en_name": "SHENYANG", + "deleted": false, + "sublist": [ + { + "code": "2129", + "parentCode": "599", + "name": "大东区", + "en_name": "Dadong", + "deleted": false, + "sublist": [] + }, + { + "code": "2132", + "parentCode": "599", + "name": "浑南区", + "en_name": "hunnan", + "deleted": false, + "sublist": [] + }, + { + "code": "2386", + "parentCode": "599", + "name": "法库县", + "en_name": "Faku", + "deleted": false, + "sublist": [] + }, + { + "code": "2126", + "parentCode": "599", + "name": "和平区", + "en_name": "Heping", + "deleted": false, + "sublist": [] + }, + { + "code": "2128", + "parentCode": "599", + "name": "皇姑区", + "en_name": "Huanggu", + "deleted": false, + "sublist": [] + }, + { + "code": "3033", + "parentCode": "599", + "name": "经济技术开发区", + "en_name": "Jingjijishukaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2385", + "parentCode": "599", + "name": "康平县", + "en_name": "Kangping", + "deleted": false, + "sublist": [] + }, + { + "code": "2384", + "parentCode": "599", + "name": "辽中区", + "en_name": "Liaozhong", + "deleted": false, + "sublist": [] + }, + { + "code": "2382", + "parentCode": "599", + "name": "棋盘山开发区", + "en_name": "Qipanshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2134", + "parentCode": "599", + "name": "沈北新区", + "en_name": "Shenbeixinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2127", + "parentCode": "599", + "name": "沈河区", + "en_name": "Shenhe", + "deleted": false, + "sublist": [] + }, + { + "code": "2135", + "parentCode": "599", + "name": "苏家屯区", + "en_name": "Sujiatun", + "deleted": false, + "sublist": [] + }, + { + "code": "2130", + "parentCode": "599", + "name": "铁西区", + "en_name": "Tiexi", + "deleted": false, + "sublist": [] + }, + { + "code": "2383", + "parentCode": "599", + "name": "新民市", + "en_name": "Xinmin", + "deleted": false, + "sublist": [] + }, + { + "code": "2133", + "parentCode": "599", + "name": "于洪区", + "en_name": "Yuhong", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "610", + "parentCode": "535", + "name": "铁岭", + "en_name": "TIELING", + "deleted": false, + "sublist": [ + { + "code": "10080", + "parentCode": "610", + "name": "昌图县", + "en_name": "CHANGTU", + "deleted": false, + "sublist": [] + }, + { + "code": "10144", + "parentCode": "610", + "name": "开原市", + "en_name": "KAIYUAN", + "deleted": false, + "sublist": [] + }, + { + "code": "103139", + "parentCode": "610", + "name": "调兵山市", + "en_name": "Diaobingshan", + "deleted": false, + "sublist": [] + }, + { + "code": "103138", + "parentCode": "610", + "name": "清河区", + "en_name": "Qinghe", + "deleted": false, + "sublist": [] + }, + { + "code": "103141", + "parentCode": "610", + "name": "铁岭县", + "en_name": "Tieling", + "deleted": false, + "sublist": [] + }, + { + "code": "103142", + "parentCode": "610", + "name": "西丰县", + "en_name": "Xifeng", + "deleted": false, + "sublist": [] + }, + { + "code": "103137", + "parentCode": "610", + "name": "银州区", + "en_name": "Yinzhou", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "606", + "parentCode": "535", + "name": "营口", + "en_name": "YINGKOU", + "deleted": false, + "sublist": [ + { + "code": "3034", + "parentCode": "606", + "name": "鲅鱼圈区", + "en_name": "Bayuquan", + "deleted": false, + "sublist": [] + }, + { + "code": "3035", + "parentCode": "606", + "name": "大石桥市", + "en_name": "Dashiqiao", + "deleted": false, + "sublist": [] + }, + { + "code": "103117", + "parentCode": "606", + "name": "盖州市", + "en_name": "Gaizhou", + "deleted": false, + "sublist": [] + }, + { + "code": "103116", + "parentCode": "606", + "name": "老边区", + "en_name": "Laobian", + "deleted": false, + "sublist": [] + }, + { + "code": "103114", + "parentCode": "606", + "name": "西市区", + "en_name": "Xishi", + "deleted": false, + "sublist": [] + }, + { + "code": "103113", + "parentCode": "606", + "name": "站前区", + "en_name": "Zhanqian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "611", + "parentCode": "535", + "name": "朝阳", + "en_name": "CHAOYANG", + "deleted": false, + "sublist": [ + { + "code": "103146", + "parentCode": "611", + "name": "北票市", + "en_name": "Beipiao", + "deleted": false, + "sublist": [] + }, + { + "code": "103149", + "parentCode": "611", + "name": "建平县", + "en_name": "Jianping", + "deleted": false, + "sublist": [] + }, + { + "code": "103150", + "parentCode": "611", + "name": "喀喇沁左翼蒙古族自治县", + "en_name": "Kalaqinzuoyimengguzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "103147", + "parentCode": "611", + "name": "凌源市", + "en_name": "Lingyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "103145", + "parentCode": "611", + "name": "龙城区", + "en_name": "Longcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "103144", + "parentCode": "611", + "name": "双塔区", + "en_name": "Shuangta", + "deleted": false, + "sublist": [] + }, + { + "code": "103148", + "parentCode": "611", + "name": "朝阳县", + "en_name": "Zhaoyang", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "534", + "parentCode": "489", + "name": "内蒙古", + "en_name": "NEIMENGGU", + "deleted": false, + "sublist": [ + { + "code": "598", + "parentCode": "534", + "name": "阿拉善盟", + "en_name": "ALASHAN", + "deleted": false, + "sublist": [ + { + "code": "103051", + "parentCode": "598", + "name": "阿拉善右旗", + "en_name": "Alashanyouqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103050", + "parentCode": "598", + "name": "阿拉善左旗", + "en_name": "Alashanzuoqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103052", + "parentCode": "598", + "name": "额济纳旗", + "en_name": "Ejinaqi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "588", + "parentCode": "534", + "name": "包头", + "en_name": "BAOTOU", + "deleted": false, + "sublist": [ + { + "code": "2964", + "parentCode": "588", + "name": "白云鄂博矿区", + "en_name": "Baiyunebokuang", + "deleted": false, + "sublist": [] + }, + { + "code": "2968", + "parentCode": "588", + "name": "达尔罕茂明安联合旗", + "en_name": "Daerhanmaominganlianheqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2960", + "parentCode": "588", + "name": "东河区", + "en_name": "Donghe", + "deleted": false, + "sublist": [] + }, + { + "code": "2967", + "parentCode": "588", + "name": "固阳县", + "en_name": "Guyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2965", + "parentCode": "588", + "name": "九原区", + "en_name": "Jiuyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2961", + "parentCode": "588", + "name": "昆都仑区", + "en_name": "Kundulun", + "deleted": false, + "sublist": [] + }, + { + "code": "2962", + "parentCode": "588", + "name": "青山区", + "en_name": "Qingshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2963", + "parentCode": "588", + "name": "石拐区", + "en_name": "Shiguai", + "deleted": false, + "sublist": [] + }, + { + "code": "2966", + "parentCode": "588", + "name": "土默特右旗", + "en_name": "Tumoteyouqi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "597", + "parentCode": "534", + "name": "巴彦淖尔", + "en_name": "BAYANNAOER", + "deleted": false, + "sublist": [ + { + "code": "103045", + "parentCode": "597", + "name": "磴口县", + "en_name": "Dengkou", + "deleted": false, + "sublist": [] + }, + { + "code": "103049", + "parentCode": "597", + "name": "杭锦后旗", + "en_name": "Hangjinhouqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103043", + "parentCode": "597", + "name": "临河区", + "en_name": "Linhe", + "deleted": false, + "sublist": [] + }, + { + "code": "103048", + "parentCode": "597", + "name": "乌拉特后旗", + "en_name": "Wulatehouqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103046", + "parentCode": "597", + "name": "乌拉特前旗", + "en_name": "Wulateqianqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103047", + "parentCode": "597", + "name": "乌拉特中旗", + "en_name": "Wulatezhongqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103044", + "parentCode": "597", + "name": "五原县", + "en_name": "Wuyuan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "590", + "parentCode": "534", + "name": "赤峰", + "en_name": "CHIFENG", + "deleted": false, + "sublist": [ + { + "code": "2975", + "parentCode": "590", + "name": "阿鲁科尔沁旗", + "en_name": "Alukeerqinqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2983", + "parentCode": "590", + "name": "敖汉旗", + "en_name": "Aohanqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2977", + "parentCode": "590", + "name": "巴林右旗", + "en_name": "Balinyouqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2976", + "parentCode": "590", + "name": "巴林左旗", + "en_name": "Balinzuoqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2972", + "parentCode": "590", + "name": "红山区", + "en_name": "Hongshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2981", + "parentCode": "590", + "name": "喀喇沁旗", + "en_name": "Kelaqinqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2979", + "parentCode": "590", + "name": "克什克腾旗", + "en_name": "Keshiketengqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2978", + "parentCode": "590", + "name": "林西县", + "en_name": "Linxi", + "deleted": false, + "sublist": [] + }, + { + "code": "2982", + "parentCode": "590", + "name": "宁城县", + "en_name": "Ningcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2974", + "parentCode": "590", + "name": "松山区", + "en_name": "Songshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2980", + "parentCode": "590", + "name": "翁牛特旗", + "en_name": "Wongniuteqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2973", + "parentCode": "590", + "name": "元宝山区", + "en_name": "Yuanbao", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "592", + "parentCode": "534", + "name": "鄂尔多斯", + "en_name": "EERDUOSI", + "deleted": false, + "sublist": [ + { + "code": "104018", + "parentCode": "592", + "name": "康巴什区", + "en_name": "Kangbashiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "104019", + "parentCode": "592", + "name": "乌审旗", + "en_name": "Wushenqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2993", + "parentCode": "592", + "name": "达拉特旗", + "en_name": "Dalateqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2992", + "parentCode": "592", + "name": "东胜区", + "en_name": "Dongsheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2996", + "parentCode": "592", + "name": "鄂托克旗", + "en_name": "Etuokeqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2995", + "parentCode": "592", + "name": "鄂托克前旗", + "en_name": "Etuokeqianqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2997", + "parentCode": "592", + "name": "杭锦旗", + "en_name": "Hangjinqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2999", + "parentCode": "592", + "name": "伊金霍洛旗", + "en_name": "Yijinhuoluoqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2994", + "parentCode": "592", + "name": "准格尔旗", + "en_name": "Zhungeerqi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "587", + "parentCode": "534", + "name": "呼和浩特", + "en_name": "HUHEHAOTE", + "deleted": false, + "sublist": [ + { + "code": "2957", + "parentCode": "587", + "name": "和林格尔县", + "en_name": "Helingeer", + "deleted": false, + "sublist": [] + }, + { + "code": "2952", + "parentCode": "587", + "name": "回民区", + "en_name": "Huimin", + "deleted": false, + "sublist": [] + }, + { + "code": "2958", + "parentCode": "587", + "name": "清水河县", + "en_name": "Qingshuihe", + "deleted": false, + "sublist": [] + }, + { + "code": "2954", + "parentCode": "587", + "name": "赛罕区", + "en_name": "Saihan", + "deleted": false, + "sublist": [] + }, + { + "code": "2955", + "parentCode": "587", + "name": "土默特左旗", + "en_name": "Tumotezuqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2956", + "parentCode": "587", + "name": "托克托县", + "en_name": "Tuoketuo", + "deleted": false, + "sublist": [] + }, + { + "code": "2959", + "parentCode": "587", + "name": "武川县", + "en_name": "Wuchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2951", + "parentCode": "587", + "name": "新城区", + "en_name": "Xincheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2953", + "parentCode": "587", + "name": "玉泉区", + "en_name": "Yuquan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "593", + "parentCode": "534", + "name": "呼伦贝尔", + "en_name": "HULUNBEIER", + "deleted": false, + "sublist": [ + { + "code": "10157", + "parentCode": "593", + "name": "满洲里市", + "en_name": "MANZHOULI", + "deleted": false, + "sublist": [] + }, + { + "code": "103012", + "parentCode": "593", + "name": "阿荣旗", + "en_name": "Arongqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103009", + "parentCode": "593", + "name": "陈巴尔虎旗", + "en_name": "Chenbaerhuqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103004", + "parentCode": "593", + "name": "额尔古纳市", + "en_name": "Ererguna", + "deleted": false, + "sublist": [] + }, + { + "code": "103007", + "parentCode": "593", + "name": "鄂伦春自治旗", + "en_name": "Elunchunzizhiqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103008", + "parentCode": "593", + "name": "鄂温克族自治旗", + "en_name": "Ewenkezuzizhiqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103005", + "parentCode": "593", + "name": "根河市", + "en_name": "Genhe", + "deleted": false, + "sublist": [] + }, + { + "code": "3000", + "parentCode": "593", + "name": "海拉尔区", + "en_name": "Hailaer", + "deleted": false, + "sublist": [] + }, + { + "code": "103006", + "parentCode": "593", + "name": "莫力达瓦达斡尔族自治旗", + "en_name": "Molidawadawoerzuzizhiqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103011", + "parentCode": "593", + "name": "新巴尔虎右旗", + "en_name": "Xinbaerhuyouqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103010", + "parentCode": "593", + "name": "新巴尔虎左旗", + "en_name": "Xinbaerhuzuoqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103002", + "parentCode": "593", + "name": "牙克石市", + "en_name": "Yakeshi", + "deleted": false, + "sublist": [] + }, + { + "code": "103013", + "parentCode": "593", + "name": "扎赉诺尔区", + "en_name": "Zhalainuoer", + "deleted": false, + "sublist": [] + }, + { + "code": "103003", + "parentCode": "593", + "name": "扎兰屯市", + "en_name": "Zhalantun", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "591", + "parentCode": "534", + "name": "通辽", + "en_name": "TONGLIAO", + "deleted": false, + "sublist": [ + { + "code": "2985", + "parentCode": "591", + "name": "霍林郭勒市", + "en_name": "Huolinguole", + "deleted": false, + "sublist": [] + }, + { + "code": "2988", + "parentCode": "591", + "name": "开鲁县", + "en_name": "Kailu", + "deleted": false, + "sublist": [] + }, + { + "code": "2984", + "parentCode": "591", + "name": "科尔沁区", + "en_name": "Keerqin", + "deleted": false, + "sublist": [] + }, + { + "code": "2987", + "parentCode": "591", + "name": "科尔沁左翼后旗", + "en_name": "Keerqinzuoyihouqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2986", + "parentCode": "591", + "name": "科尔沁左翼中旗", + "en_name": "Keerqinzuoyizhongqi", + "deleted": false, + "sublist": [] + }, + { + "code": "2989", + "parentCode": "591", + "name": "库伦旗", + "en_name": "Kulun", + "deleted": false, + "sublist": [] + }, + { + "code": "2990", + "parentCode": "591", + "name": "奈曼旗", + "en_name": "Naiman", + "deleted": false, + "sublist": [] + }, + { + "code": "2991", + "parentCode": "591", + "name": "扎鲁特旗", + "en_name": "Zhaluteqi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "589", + "parentCode": "534", + "name": "乌海", + "en_name": "WUHAI", + "deleted": false, + "sublist": [ + { + "code": "2969", + "parentCode": "589", + "name": "海勃湾区", + "en_name": "Haibowan", + "deleted": false, + "sublist": [] + }, + { + "code": "2970", + "parentCode": "589", + "name": "海南区", + "en_name": "Hainan", + "deleted": false, + "sublist": [] + }, + { + "code": "2971", + "parentCode": "589", + "name": "乌达区", + "en_name": "Wuda", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "596", + "parentCode": "534", + "name": "乌兰察布", + "en_name": "WULANCHABU", + "deleted": false, + "sublist": [ + { + "code": "103041", + "parentCode": "596", + "name": "察哈尔右翼后旗", + "en_name": "Chahaeryouqihouqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103039", + "parentCode": "596", + "name": "察哈尔右翼前旗", + "en_name": "Chahaeryouyiqianqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103040", + "parentCode": "596", + "name": "察哈尔右翼中旗", + "en_name": "Chahaeryouqizhongqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103033", + "parentCode": "596", + "name": "丰镇市", + "en_name": "Fengzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "103035", + "parentCode": "596", + "name": "化德县", + "en_name": "Huade", + "deleted": false, + "sublist": [] + }, + { + "code": "103032", + "parentCode": "596", + "name": "集宁区", + "en_name": "Jining", + "deleted": false, + "sublist": [] + }, + { + "code": "103038", + "parentCode": "596", + "name": "凉城县", + "en_name": "Liangcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3036", + "parentCode": "596", + "name": "商都县", + "en_name": "Shangdu", + "deleted": false, + "sublist": [] + }, + { + "code": "103042", + "parentCode": "596", + "name": "四子王旗", + "en_name": "Siziwangqi", + "deleted": false, + "sublist": [] + }, + { + "code": "3037", + "parentCode": "596", + "name": "兴和县", + "en_name": "Xinghe", + "deleted": false, + "sublist": [] + }, + { + "code": "103034", + "parentCode": "596", + "name": "卓资县", + "en_name": "Zhuozi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "595", + "parentCode": "534", + "name": "锡林郭勒盟", + "en_name": "XILINGUOLE", + "deleted": false, + "sublist": [ + { + "code": "103022", + "parentCode": "595", + "name": "阿巴嘎旗", + "en_name": "Abagaqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103025", + "parentCode": "595", + "name": "东乌珠穆沁旗", + "en_name": "Dongwuzhumuqinqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103031", + "parentCode": "595", + "name": "多伦县", + "en_name": "Duolun", + "deleted": false, + "sublist": [] + }, + { + "code": "103020", + "parentCode": "595", + "name": "二连浩特市", + "en_name": "Erlianhaote", + "deleted": false, + "sublist": [] + }, + { + "code": "103024", + "parentCode": "595", + "name": "苏尼特右旗", + "en_name": "Suniteyouqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103023", + "parentCode": "595", + "name": "苏尼特左旗", + "en_name": "Sunitezuoqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103027", + "parentCode": "595", + "name": "太仆寺旗", + "en_name": "Taipusiqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103028", + "parentCode": "595", + "name": "镶黄旗", + "en_name": "Xianghuangqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103021", + "parentCode": "595", + "name": "锡林浩特市", + "en_name": "Xilinhaote", + "deleted": false, + "sublist": [] + }, + { + "code": "103026", + "parentCode": "595", + "name": "西乌珠穆沁旗", + "en_name": "Xiwuzhumuqinqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103030", + "parentCode": "595", + "name": "正蓝旗", + "en_name": "Zhenglanqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103029", + "parentCode": "595", + "name": "正镶白旗", + "en_name": "Zhengxiangbaiqi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "594", + "parentCode": "534", + "name": "兴安盟", + "en_name": "XINGAN", + "deleted": false, + "sublist": [ + { + "code": "103015", + "parentCode": "594", + "name": "阿尔山市", + "en_name": "Aershan", + "deleted": false, + "sublist": [] + }, + { + "code": "103016", + "parentCode": "594", + "name": "科尔沁右翼前旗", + "en_name": "Keerqinyouyiqianqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103017", + "parentCode": "594", + "name": "科尔沁右翼中旗", + "en_name": "Keerqinyouyizhongqi", + "deleted": false, + "sublist": [] + }, + { + "code": "103019", + "parentCode": "594", + "name": "突泉县", + "en_name": "Tuquan", + "deleted": false, + "sublist": [] + }, + { + "code": "103014", + "parentCode": "594", + "name": "乌兰浩特市", + "en_name": "Wulanhaoteshi", + "deleted": false, + "sublist": [] + }, + { + "code": "103018", + "parentCode": "594", + "name": "扎赉特旗", + "en_name": "Zhalaiteqi", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "559", + "parentCode": "489", + "name": "宁夏", + "en_name": "NINGXIA", + "deleted": false, + "sublist": [ + { + "code": "889", + "parentCode": "559", + "name": "固原", + "en_name": "GUYUAN", + "deleted": false, + "sublist": [ + { + "code": "5198", + "parentCode": "889", + "name": "泾源县", + "en_name": "Jingyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "5197", + "parentCode": "889", + "name": "隆德县", + "en_name": "Longde", + "deleted": false, + "sublist": [] + }, + { + "code": "5199", + "parentCode": "889", + "name": "彭阳县", + "en_name": "Pengyang", + "deleted": false, + "sublist": [] + }, + { + "code": "5196", + "parentCode": "889", + "name": "西吉县", + "en_name": "Xiji", + "deleted": false, + "sublist": [] + }, + { + "code": "5195", + "parentCode": "889", + "name": "原州区", + "en_name": "Yuanzhou", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "887", + "parentCode": "559", + "name": "石嘴山", + "en_name": "SHIZUISHAN", + "deleted": false, + "sublist": [ + { + "code": "5187", + "parentCode": "887", + "name": "大武口区", + "en_name": "Dawukou", + "deleted": false, + "sublist": [] + }, + { + "code": "5188", + "parentCode": "887", + "name": "惠农区", + "en_name": "Huinong", + "deleted": false, + "sublist": [] + }, + { + "code": "5189", + "parentCode": "887", + "name": "平罗县", + "en_name": "Pingluo", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "888", + "parentCode": "559", + "name": "吴忠", + "en_name": "WUZHONG", + "deleted": false, + "sublist": [ + { + "code": "5191", + "parentCode": "888", + "name": "红寺堡区", + "en_name": "Hongsibu", + "deleted": false, + "sublist": [] + }, + { + "code": "5190", + "parentCode": "888", + "name": "利通区", + "en_name": "Litong", + "deleted": false, + "sublist": [] + }, + { + "code": "5192", + "parentCode": "888", + "name": "青铜峡市", + "en_name": "Qingtongxia", + "deleted": false, + "sublist": [] + }, + { + "code": "5194", + "parentCode": "888", + "name": "同心县", + "en_name": "Tongxin", + "deleted": false, + "sublist": [] + }, + { + "code": "5193", + "parentCode": "888", + "name": "盐池县", + "en_name": "Yanchi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "886", + "parentCode": "559", + "name": "银川", + "en_name": "YINCHUAN", + "deleted": false, + "sublist": [ + { + "code": "5186", + "parentCode": "886", + "name": "贺兰县", + "en_name": "Helan", + "deleted": false, + "sublist": [] + }, + { + "code": "5183", + "parentCode": "886", + "name": "金凤区", + "en_name": "Jinfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "5184", + "parentCode": "886", + "name": "灵武市", + "en_name": "Lingwu", + "deleted": false, + "sublist": [] + }, + { + "code": "5181", + "parentCode": "886", + "name": "兴庆区", + "en_name": "Xingqing", + "deleted": false, + "sublist": [] + }, + { + "code": "5182", + "parentCode": "886", + "name": "西夏区", + "en_name": "Xixia", + "deleted": false, + "sublist": [] + }, + { + "code": "5185", + "parentCode": "886", + "name": "永宁县", + "en_name": "Yongning", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "906", + "parentCode": "559", + "name": "中卫", + "en_name": "ZHONGWEI", + "deleted": false, + "sublist": [ + { + "code": "5311", + "parentCode": "906", + "name": "海原县", + "en_name": "Haiyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "5309", + "parentCode": "906", + "name": "沙坡头区", + "en_name": "Shapotou", + "deleted": false, + "sublist": [] + }, + { + "code": "5310", + "parentCode": "906", + "name": "中宁县", + "en_name": "Zhongning", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "558", + "parentCode": "489", + "name": "青海", + "en_name": "QINGHAI", + "deleted": false, + "sublist": [ + { + "code": "883", + "parentCode": "558", + "name": "果洛", + "en_name": "GUOLUO", + "deleted": false, + "sublist": [ + { + "code": "5165", + "parentCode": "883", + "name": "班玛县", + "en_name": "Banma", + "deleted": false, + "sublist": [] + }, + { + "code": "5167", + "parentCode": "883", + "name": "达日县", + "en_name": "Dari", + "deleted": false, + "sublist": [] + }, + { + "code": "5166", + "parentCode": "883", + "name": "甘德县", + "en_name": "Gande", + "deleted": false, + "sublist": [] + }, + { + "code": "5168", + "parentCode": "883", + "name": "久治县", + "en_name": "Jiuzhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5169", + "parentCode": "883", + "name": "玛多县", + "en_name": "Maduo", + "deleted": false, + "sublist": [] + }, + { + "code": "5164", + "parentCode": "883", + "name": "玛沁县", + "en_name": "Maqin", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "880", + "parentCode": "558", + "name": "海北", + "en_name": "HAIBEI", + "deleted": false, + "sublist": [ + { + "code": "5154", + "parentCode": "880", + "name": "刚察县", + "en_name": "Gangcha", + "deleted": false, + "sublist": [] + }, + { + "code": "5153", + "parentCode": "880", + "name": "海晏县", + "en_name": "Haiyan", + "deleted": false, + "sublist": [] + }, + { + "code": "5151", + "parentCode": "880", + "name": "门源回族自治县", + "en_name": "Menyuanhuizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5152", + "parentCode": "880", + "name": "祁连县", + "en_name": "Qilian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "879", + "parentCode": "558", + "name": "海东", + "en_name": "HAIDONG", + "deleted": false, + "sublist": [ + { + "code": "5149", + "parentCode": "879", + "name": "化隆回族自治县", + "en_name": "Hualonghuizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5148", + "parentCode": "879", + "name": "互助土族自治县", + "en_name": "Huzhutuzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5147", + "parentCode": "879", + "name": "乐都区", + "en_name": "Ledu", + "deleted": false, + "sublist": [] + }, + { + "code": "5146", + "parentCode": "879", + "name": "民和回族土族自治县", + "en_name": "Minhehuizutuzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5145", + "parentCode": "879", + "name": "平安区", + "en_name": "Pingan", + "deleted": false, + "sublist": [] + }, + { + "code": "5150", + "parentCode": "879", + "name": "循化撒拉族自治县", + "en_name": "Dunhuasalazuzizhi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "882", + "parentCode": "558", + "name": "海南州", + "en_name": "HAINAN", + "deleted": false, + "sublist": [ + { + "code": "5159", + "parentCode": "882", + "name": "共和县", + "en_name": "Gonghe", + "deleted": false, + "sublist": [] + }, + { + "code": "5161", + "parentCode": "882", + "name": "贵德县", + "en_name": "Guide", + "deleted": false, + "sublist": [] + }, + { + "code": "5163", + "parentCode": "882", + "name": "贵南县", + "en_name": "Guinan", + "deleted": false, + "sublist": [] + }, + { + "code": "5160", + "parentCode": "882", + "name": "同德县", + "en_name": "Tongde", + "deleted": false, + "sublist": [] + }, + { + "code": "5162", + "parentCode": "882", + "name": "兴海县", + "en_name": "Xinghai", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "885", + "parentCode": "558", + "name": "海西", + "en_name": "HAIXI", + "deleted": false, + "sublist": [ + { + "code": "5177", + "parentCode": "885", + "name": "德令哈市", + "en_name": "Delingha", + "deleted": false, + "sublist": [] + }, + { + "code": "5179", + "parentCode": "885", + "name": "都兰县", + "en_name": "Dulan", + "deleted": false, + "sublist": [] + }, + { + "code": "5176", + "parentCode": "885", + "name": "格尔木市", + "en_name": "Geermu", + "deleted": false, + "sublist": [] + }, + { + "code": "5180", + "parentCode": "885", + "name": "天峻县", + "en_name": "Tianjun", + "deleted": false, + "sublist": [] + }, + { + "code": "5178", + "parentCode": "885", + "name": "乌兰县", + "en_name": "Wulan", + "deleted": false, + "sublist": [] + }, + { + "code": "104027", + "parentCode": "885", + "name": "茫崖市", + "en_name": "mangyashi", + "deleted": false, + "sublist": [] + }, + { + "code": "104040", + "parentCode": "885", + "name": "海西蒙古族藏族自治州直辖", + "en_name": "haiximengguzucangzuzizhizhouzhixia", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "881", + "parentCode": "558", + "name": "黄南", + "en_name": "HUANGNAN", + "deleted": false, + "sublist": [ + { + "code": "5158", + "parentCode": "881", + "name": "河南蒙古族自治县", + "en_name": "Henanmengguzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5156", + "parentCode": "881", + "name": "尖扎县", + "en_name": "Jianzha", + "deleted": false, + "sublist": [] + }, + { + "code": "5155", + "parentCode": "881", + "name": "同仁市", + "en_name": "tongrenshi", + "deleted": false, + "sublist": [] + }, + { + "code": "5157", + "parentCode": "881", + "name": "泽库县", + "en_name": "Zeku", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "878", + "parentCode": "558", + "name": "西宁", + "en_name": "XINING", + "deleted": false, + "sublist": [ + { + "code": "5141", + "parentCode": "878", + "name": "城北区", + "en_name": "Chengbei", + "deleted": false, + "sublist": [] + }, + { + "code": "5138", + "parentCode": "878", + "name": "城东区", + "en_name": "Chengdong", + "deleted": false, + "sublist": [] + }, + { + "code": "5140", + "parentCode": "878", + "name": "城西区", + "en_name": "Chengxi", + "deleted": false, + "sublist": [] + }, + { + "code": "5139", + "parentCode": "878", + "name": "城中区", + "en_name": "Chengzhong", + "deleted": false, + "sublist": [] + }, + { + "code": "5142", + "parentCode": "878", + "name": "大通回族土族自治县", + "en_name": "Datonghuizutuzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5144", + "parentCode": "878", + "name": "湟源县", + "en_name": "Huangyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "5143", + "parentCode": "878", + "name": "湟中区", + "en_name": "Huangzhong", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "884", + "parentCode": "558", + "name": "玉树", + "en_name": "YUSHU", + "deleted": false, + "sublist": [ + { + "code": "5172", + "parentCode": "884", + "name": "称多县", + "en_name": "Chengduo", + "deleted": false, + "sublist": [] + }, + { + "code": "5174", + "parentCode": "884", + "name": "囊谦县", + "en_name": "Nangqian", + "deleted": false, + "sublist": [] + }, + { + "code": "5175", + "parentCode": "884", + "name": "曲麻莱县", + "en_name": "Qumalai", + "deleted": false, + "sublist": [] + }, + { + "code": "5170", + "parentCode": "884", + "name": "玉树市", + "en_name": "Yushu", + "deleted": false, + "sublist": [] + }, + { + "code": "5171", + "parentCode": "884", + "name": "杂多县", + "en_name": "Zaduo", + "deleted": false, + "sublist": [] + }, + { + "code": "5173", + "parentCode": "884", + "name": "治多县", + "en_name": "Zhiduo", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "544", + "parentCode": "489", + "name": "山东", + "en_name": "SHANDONG", + "deleted": false, + "sublist": [ + { + "code": "717", + "parentCode": "544", + "name": "滨州", + "en_name": "BINZHOU", + "deleted": false, + "sublist": [ + { + "code": "3840", + "parentCode": "717", + "name": "滨城区", + "en_name": "Bincheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3845", + "parentCode": "717", + "name": "博兴县", + "en_name": "Boxing", + "deleted": false, + "sublist": [] + }, + { + "code": "3841", + "parentCode": "717", + "name": "惠民县", + "en_name": "Huimin", + "deleted": false, + "sublist": [] + }, + { + "code": "3843", + "parentCode": "717", + "name": "无棣县", + "en_name": "Wudi", + "deleted": false, + "sublist": [] + }, + { + "code": "3842", + "parentCode": "717", + "name": "阳信县", + "en_name": "Yangxin", + "deleted": false, + "sublist": [] + }, + { + "code": "3844", + "parentCode": "717", + "name": "沾化区", + "en_name": "Zhanhua", + "deleted": false, + "sublist": [] + }, + { + "code": "3846", + "parentCode": "717", + "name": "邹平市", + "en_name": "Zouping", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "715", + "parentCode": "544", + "name": "德州", + "en_name": "DEZHOU", + "deleted": false, + "sublist": [ + { + "code": "3821", + "parentCode": "715", + "name": "德城区", + "en_name": "Decheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3822", + "parentCode": "715", + "name": "乐陵市", + "en_name": "Leling", + "deleted": false, + "sublist": [] + }, + { + "code": "3824", + "parentCode": "715", + "name": "陵城区", + "en_name": "Ling", + "deleted": false, + "sublist": [] + }, + { + "code": "3827", + "parentCode": "715", + "name": "临邑县", + "en_name": "Linyi", + "deleted": false, + "sublist": [] + }, + { + "code": "3825", + "parentCode": "715", + "name": "宁津县", + "en_name": "Ningjin", + "deleted": false, + "sublist": [] + }, + { + "code": "3829", + "parentCode": "715", + "name": "平原县", + "en_name": "Pingyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "3828", + "parentCode": "715", + "name": "齐河县", + "en_name": "Qihe", + "deleted": false, + "sublist": [] + }, + { + "code": "3826", + "parentCode": "715", + "name": "庆云县", + "en_name": "Qingyun", + "deleted": false, + "sublist": [] + }, + { + "code": "3831", + "parentCode": "715", + "name": "武城县", + "en_name": "Wucheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3830", + "parentCode": "715", + "name": "夏津县", + "en_name": "Xiajin", + "deleted": false, + "sublist": [] + }, + { + "code": "3823", + "parentCode": "715", + "name": "禹城市", + "en_name": "Yucheng", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "706", + "parentCode": "544", + "name": "东营", + "en_name": "DONGYING", + "deleted": false, + "sublist": [ + { + "code": "3751", + "parentCode": "706", + "name": "东营区", + "en_name": "Dongying", + "deleted": false, + "sublist": [] + }, + { + "code": "3755", + "parentCode": "706", + "name": "广饶县", + "en_name": "Guangrao", + "deleted": false, + "sublist": [] + }, + { + "code": "3752", + "parentCode": "706", + "name": "河口区", + "en_name": "Hekou", + "deleted": false, + "sublist": [] + }, + { + "code": "3753", + "parentCode": "706", + "name": "垦利区", + "en_name": "Kenli", + "deleted": false, + "sublist": [] + }, + { + "code": "3754", + "parentCode": "706", + "name": "利津县", + "en_name": "Lijin", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "718", + "parentCode": "544", + "name": "菏泽", + "en_name": "HEZE", + "deleted": false, + "sublist": [ + { + "code": "3848", + "parentCode": "718", + "name": "曹县", + "en_name": "Cao", + "deleted": false, + "sublist": [] + }, + { + "code": "3850", + "parentCode": "718", + "name": "成武县", + "en_name": "Chengwu", + "deleted": false, + "sublist": [] + }, + { + "code": "3849", + "parentCode": "718", + "name": "单县", + "en_name": "Shan", + "deleted": false, + "sublist": [] + }, + { + "code": "3854", + "parentCode": "718", + "name": "定陶区", + "en_name": "Dingtao", + "deleted": false, + "sublist": [] + }, + { + "code": "3853", + "parentCode": "718", + "name": "鄄城县", + "en_name": "Juancheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3851", + "parentCode": "718", + "name": "巨野县", + "en_name": "Juye", + "deleted": false, + "sublist": [] + }, + { + "code": "3847", + "parentCode": "718", + "name": "牡丹区", + "en_name": "Mudan", + "deleted": false, + "sublist": [] + }, + { + "code": "3852", + "parentCode": "718", + "name": "郓城县", + "en_name": "Yuncheng", + "deleted": false, + "sublist": [] + }, + { + "code": "104020", + "parentCode": "718", + "name": "东明县", + "en_name": "dongmingxian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "702", + "parentCode": "544", + "name": "济南", + "en_name": "JINAN", + "deleted": false, + "sublist": [ + { + "code": "3808", + "parentCode": "702", + "name": "钢城区", + "en_name": "Gangcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2376", + "parentCode": "702", + "name": "高新区", + "en_name": "Gaoxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2100", + "parentCode": "702", + "name": "槐荫区", + "en_name": "Huaiyin", + "deleted": false, + "sublist": [] + }, + { + "code": "2104", + "parentCode": "702", + "name": "济阳区", + "en_name": "Jiyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3807", + "parentCode": "702", + "name": "莱芜区", + "en_name": "Laicheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2101", + "parentCode": "702", + "name": "历城区", + "en_name": "Licheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2098", + "parentCode": "702", + "name": "历下区", + "en_name": "Lixia", + "deleted": false, + "sublist": [] + }, + { + "code": "2103", + "parentCode": "702", + "name": "平阴县", + "en_name": "Pingyin", + "deleted": false, + "sublist": [] + }, + { + "code": "2105", + "parentCode": "702", + "name": "商河县", + "en_name": "Shanghe", + "deleted": false, + "sublist": [] + }, + { + "code": "2097", + "parentCode": "702", + "name": "市中区", + "en_name": "Shizhong", + "deleted": false, + "sublist": [] + }, + { + "code": "2099", + "parentCode": "702", + "name": "天桥区", + "en_name": "Tianqiao", + "deleted": false, + "sublist": [] + }, + { + "code": "2102", + "parentCode": "702", + "name": "长清区", + "en_name": "Changqing", + "deleted": false, + "sublist": [] + }, + { + "code": "2471", + "parentCode": "702", + "name": "章丘区", + "en_name": "Zhangqiu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "709", + "parentCode": "544", + "name": "济宁", + "en_name": "JINING", + "deleted": false, + "sublist": [ + { + "code": "3024", + "parentCode": "709", + "name": "嘉祥县", + "en_name": "Jiaxiangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3023", + "parentCode": "709", + "name": "金乡县", + "en_name": "Jinxiangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3027", + "parentCode": "709", + "name": "梁山县", + "en_name": "Liangshanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3019", + "parentCode": "709", + "name": "曲阜市", + "en_name": "Qufushi", + "deleted": false, + "sublist": [] + }, + { + "code": "3018", + "parentCode": "709", + "name": "任城区", + "en_name": "Renchengqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3026", + "parentCode": "709", + "name": "泗水县", + "en_name": "Sishuixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3021", + "parentCode": "709", + "name": "微山县", + "en_name": "Weishanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3025", + "parentCode": "709", + "name": "汶上县", + "en_name": "Wenshangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3366", + "parentCode": "709", + "name": "兖州区", + "en_name": "Yanzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "3022", + "parentCode": "709", + "name": "鱼台县", + "en_name": "Yutaixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3020", + "parentCode": "709", + "name": "邹城市", + "en_name": "Zouchengshi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "716", + "parentCode": "544", + "name": "聊城", + "en_name": "LIAOCHENG", + "deleted": false, + "sublist": [ + { + "code": "3836", + "parentCode": "716", + "name": "茌平区", + "en_name": "Renping", + "deleted": false, + "sublist": [] + }, + { + "code": "3832", + "parentCode": "716", + "name": "东昌府区", + "en_name": "Dongchangfu", + "deleted": false, + "sublist": [] + }, + { + "code": "3837", + "parentCode": "716", + "name": "东阿县", + "en_name": "Donge", + "deleted": false, + "sublist": [] + }, + { + "code": "3839", + "parentCode": "716", + "name": "高唐县", + "en_name": "Gaotang", + "deleted": false, + "sublist": [] + }, + { + "code": "3838", + "parentCode": "716", + "name": "冠县", + "en_name": "Guan", + "deleted": false, + "sublist": [] + }, + { + "code": "3833", + "parentCode": "716", + "name": "临清市", + "en_name": "Linqing", + "deleted": false, + "sublist": [] + }, + { + "code": "3835", + "parentCode": "716", + "name": "莘县", + "en_name": "Shen", + "deleted": false, + "sublist": [] + }, + { + "code": "3834", + "parentCode": "716", + "name": "阳谷县", + "en_name": "Yanggu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "714", + "parentCode": "544", + "name": "临沂", + "en_name": "LINYI", + "deleted": false, + "sublist": [ + { + "code": "3816", + "parentCode": "714", + "name": "费县", + "en_name": "Fei", + "deleted": false, + "sublist": [] + }, + { + "code": "3811", + "parentCode": "714", + "name": "河东区", + "en_name": "Hedong", + "deleted": false, + "sublist": [] + }, + { + "code": "3818", + "parentCode": "714", + "name": "莒南县", + "en_name": "Junan", + "deleted": false, + "sublist": [] + }, + { + "code": "3815", + "parentCode": "714", + "name": "兰陵县", + "en_name": "Lanling", + "deleted": false, + "sublist": [] + }, + { + "code": "3809", + "parentCode": "714", + "name": "兰山区", + "en_name": "Lanshan", + "deleted": false, + "sublist": [] + }, + { + "code": "3820", + "parentCode": "714", + "name": "临沭县", + "en_name": "Linshu", + "deleted": false, + "sublist": [] + }, + { + "code": "3810", + "parentCode": "714", + "name": "罗庄区", + "en_name": "Luozhuang", + "deleted": false, + "sublist": [] + }, + { + "code": "3819", + "parentCode": "714", + "name": "蒙阴县", + "en_name": "Mengyin", + "deleted": false, + "sublist": [] + }, + { + "code": "3817", + "parentCode": "714", + "name": "平邑县", + "en_name": "Pingyi", + "deleted": false, + "sublist": [] + }, + { + "code": "3813", + "parentCode": "714", + "name": "郯城县", + "en_name": "Tancheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3812", + "parentCode": "714", + "name": "沂南县", + "en_name": "Yinan", + "deleted": false, + "sublist": [] + }, + { + "code": "3814", + "parentCode": "714", + "name": "沂水县", + "en_name": "Yishui", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "703", + "parentCode": "544", + "name": "青岛", + "en_name": "QINGDAO", + "deleted": false, + "sublist": [ + { + "code": "2391", + "parentCode": "703", + "name": "保税区", + "en_name": "Baoshuiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2159", + "parentCode": "703", + "name": "城阳区", + "en_name": "Chengyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2157", + "parentCode": "703", + "name": "黄岛区", + "en_name": "Huangdao", + "deleted": false, + "sublist": [] + }, + { + "code": "2160", + "parentCode": "703", + "name": "胶州市", + "en_name": "Jiaozhoushi", + "deleted": false, + "sublist": [] + }, + { + "code": "2161", + "parentCode": "703", + "name": "即墨区", + "en_name": "Jimoshi", + "deleted": false, + "sublist": [] + }, + { + "code": "2164", + "parentCode": "703", + "name": "莱西市", + "en_name": "Laixishi", + "deleted": false, + "sublist": [] + }, + { + "code": "2158", + "parentCode": "703", + "name": "崂山区", + "en_name": "Laoshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2156", + "parentCode": "703", + "name": "李沧区", + "en_name": "Licang", + "deleted": false, + "sublist": [] + }, + { + "code": "2162", + "parentCode": "703", + "name": "平度市", + "en_name": "Pingdushi", + "deleted": false, + "sublist": [] + }, + { + "code": "2393", + "parentCode": "703", + "name": "青岛高新技术产业开发区", + "en_name": "Chanyekaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2392", + "parentCode": "703", + "name": "青岛经济技术开发区", + "en_name": "Jingjikaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2154", + "parentCode": "703", + "name": "市北区", + "en_name": "Shibei", + "deleted": false, + "sublist": [] + }, + { + "code": "2153", + "parentCode": "703", + "name": "市南区", + "en_name": "Shinan", + "deleted": false, + "sublist": [] + }, + { + "code": "3009", + "parentCode": "703", + "name": "西海岸新区", + "en_name": "Xihaianxinqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "712", + "parentCode": "544", + "name": "日照", + "en_name": "RIZHAO", + "deleted": false, + "sublist": [ + { + "code": "3803", + "parentCode": "712", + "name": "东港区", + "en_name": "Donggang", + "deleted": false, + "sublist": [] + }, + { + "code": "3806", + "parentCode": "712", + "name": "莒县", + "en_name": "Ju", + "deleted": false, + "sublist": [] + }, + { + "code": "3804", + "parentCode": "712", + "name": "岚山区", + "en_name": "Lanshan", + "deleted": false, + "sublist": [] + }, + { + "code": "3805", + "parentCode": "712", + "name": "五莲县", + "en_name": "Wulian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "710", + "parentCode": "544", + "name": "泰安", + "en_name": "TAIAN", + "deleted": false, + "sublist": [ + { + "code": "3794", + "parentCode": "710", + "name": "岱岳区", + "en_name": "Daiyue", + "deleted": false, + "sublist": [] + }, + { + "code": "3798", + "parentCode": "710", + "name": "东平县", + "en_name": "Dongping", + "deleted": false, + "sublist": [] + }, + { + "code": "3369", + "parentCode": "710", + "name": "肥城市", + "en_name": "Feichengshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3797", + "parentCode": "710", + "name": "宁阳县", + "en_name": "Ningyang", + "deleted": false, + "sublist": [] + }, + { + "code": "3793", + "parentCode": "710", + "name": "泰山区", + "en_name": "Taishan", + "deleted": false, + "sublist": [] + }, + { + "code": "3795", + "parentCode": "710", + "name": "新泰市", + "en_name": "Xintai", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "708", + "parentCode": "544", + "name": "潍坊", + "en_name": "WEIFANG", + "deleted": false, + "sublist": [ + { + "code": "3017", + "parentCode": "708", + "name": "安丘市", + "en_name": "Anqiushi", + "deleted": false, + "sublist": [] + }, + { + "code": "3011", + "parentCode": "708", + "name": "滨海经济开发区", + "en_name": "Binhaijingjikaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3014", + "parentCode": "708", + "name": "昌乐县", + "en_name": "Changlexian", + "deleted": false, + "sublist": [] + }, + { + "code": "3779", + "parentCode": "708", + "name": "昌邑市", + "en_name": "Changyi", + "deleted": false, + "sublist": [] + }, + { + "code": "3772", + "parentCode": "708", + "name": "坊子区", + "en_name": "Fangzi", + "deleted": false, + "sublist": [] + }, + { + "code": "3012", + "parentCode": "708", + "name": "高密市", + "en_name": "Gaomishi", + "deleted": false, + "sublist": [] + }, + { + "code": "3010", + "parentCode": "708", + "name": "高新技术开发区", + "en_name": "Gaoxinjishukaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3771", + "parentCode": "708", + "name": "寒亭区", + "en_name": "Hanting", + "deleted": false, + "sublist": [] + }, + { + "code": "3773", + "parentCode": "708", + "name": "奎文区", + "en_name": "Kuiwen", + "deleted": false, + "sublist": [] + }, + { + "code": "4008", + "parentCode": "708", + "name": "临朐县", + "en_name": "Linquxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3015", + "parentCode": "708", + "name": "青州市", + "en_name": "Qingzhoushi", + "deleted": false, + "sublist": [] + }, + { + "code": "3013", + "parentCode": "708", + "name": "寿光市", + "en_name": "Shouguangshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3770", + "parentCode": "708", + "name": "潍城区", + "en_name": "Weicheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3016", + "parentCode": "708", + "name": "诸城市", + "en_name": "Zhuchengshi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "711", + "parentCode": "544", + "name": "威海", + "en_name": "WEIHAI", + "deleted": false, + "sublist": [ + { + "code": "4009", + "parentCode": "711", + "name": "环翠区", + "en_name": "huancuiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4012", + "parentCode": "711", + "name": "火炬高技术产业区", + "en_name": "huojugaojishuchanyequ", + "deleted": false, + "sublist": [] + }, + { + "code": "4013", + "parentCode": "711", + "name": "进出口加工保税区", + "en_name": "jinchukoujiagongbaoshuiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4011", + "parentCode": "711", + "name": "经济开发区", + "en_name": "jingjikaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4014", + "parentCode": "711", + "name": "临港经济技术开发区", + "en_name": "lingangjingjijishukaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4015", + "parentCode": "711", + "name": "南海新区", + "en_name": "nanhaixinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3368", + "parentCode": "711", + "name": "荣成市", + "en_name": "Rongchengshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3367", + "parentCode": "711", + "name": "乳山市", + "en_name": "Rushanshi", + "deleted": false, + "sublist": [] + }, + { + "code": "4010", + "parentCode": "711", + "name": "文登区", + "en_name": "wendengqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "707", + "parentCode": "544", + "name": "烟台", + "en_name": "YANTAI", + "deleted": false, + "sublist": [ + { + "code": "2546", + "parentCode": "707", + "name": "福山区", + "en_name": "FUSHANQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2557", + "parentCode": "707", + "name": "高新区", + "en_name": "GAOXINQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2556", + "parentCode": "707", + "name": "海阳市", + "en_name": "HAIYANGSHI", + "deleted": false, + "sublist": [] + }, + { + "code": "2558", + "parentCode": "707", + "name": "开发区", + "en_name": "KAIFAQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2548", + "parentCode": "707", + "name": "莱山区", + "en_name": "LAISHANQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2551", + "parentCode": "707", + "name": "莱阳市", + "en_name": "LAIYANGSHI", + "deleted": false, + "sublist": [] + }, + { + "code": "2552", + "parentCode": "707", + "name": "莱州市", + "en_name": "LAIZHAOUSHI", + "deleted": false, + "sublist": [] + }, + { + "code": "2550", + "parentCode": "707", + "name": "龙口市", + "en_name": "LONGKOUSHI", + "deleted": false, + "sublist": [] + }, + { + "code": "2547", + "parentCode": "707", + "name": "牟平区", + "en_name": "MUSHANQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2553", + "parentCode": "707", + "name": "蓬莱区", + "en_name": "penglaiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2555", + "parentCode": "707", + "name": "栖霞市", + "en_name": "QIXIASHI", + "deleted": false, + "sublist": [] + }, + { + "code": "2549", + "parentCode": "707", + "name": "长岛县", + "en_name": "CHANGDAOXIAN", + "deleted": false, + "sublist": [] + }, + { + "code": "2554", + "parentCode": "707", + "name": "招远市", + "en_name": "ZHAOYUANSHI", + "deleted": false, + "sublist": [] + }, + { + "code": "2545", + "parentCode": "707", + "name": "芝罘区", + "en_name": "ZHIFUQU", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "705", + "parentCode": "544", + "name": "枣庄", + "en_name": "ZAOZHUANG", + "deleted": false, + "sublist": [ + { + "code": "3749", + "parentCode": "705", + "name": "山亭区", + "en_name": "Shanting", + "deleted": false, + "sublist": [] + }, + { + "code": "3745", + "parentCode": "705", + "name": "市中区", + "en_name": "Shizhong", + "deleted": false, + "sublist": [] + }, + { + "code": "3748", + "parentCode": "705", + "name": "台儿庄区", + "en_name": "Taierzhuang", + "deleted": false, + "sublist": [] + }, + { + "code": "3750", + "parentCode": "705", + "name": "滕州市", + "en_name": "Tengzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "3746", + "parentCode": "705", + "name": "薛城区", + "en_name": "Xuecheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3747", + "parentCode": "705", + "name": "峄城区", + "en_name": "Yicheng", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "704", + "parentCode": "544", + "name": "淄博", + "en_name": "ZIBO", + "deleted": false, + "sublist": [ + { + "code": "3030", + "parentCode": "704", + "name": "博山区", + "en_name": "Boshanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3743", + "parentCode": "704", + "name": "高青县", + "en_name": "Gaoqing", + "deleted": false, + "sublist": [] + }, + { + "code": "3742", + "parentCode": "704", + "name": "桓台县", + "en_name": "Huantai", + "deleted": false, + "sublist": [] + }, + { + "code": "3031", + "parentCode": "704", + "name": "临淄区", + "en_name": "Linziqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3744", + "parentCode": "704", + "name": "沂源县", + "en_name": "Yiyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "3028", + "parentCode": "704", + "name": "张店区", + "en_name": "Zhangdianqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3741", + "parentCode": "704", + "name": "周村区", + "en_name": "Zhoucun", + "deleted": false, + "sublist": [] + }, + { + "code": "3032", + "parentCode": "704", + "name": "淄博高新区", + "en_name": "Zibogaoxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3029", + "parentCode": "704", + "name": "淄川区", + "en_name": "Zichuanqu", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "538", + "parentCode": "489", + "name": "上海", + "en_name": "SHANGHAI", + "deleted": false, + "sublist": [ + { + "code": "2029", + "parentCode": "538", + "name": "宝山区", + "en_name": "Baoshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2036", + "parentCode": "538", + "name": "崇明区", + "en_name": "Chongming", + "deleted": false, + "sublist": [] + }, + { + "code": "2035", + "parentCode": "538", + "name": "奉贤区", + "en_name": "Fengxian", + "deleted": false, + "sublist": [] + }, + { + "code": "2026", + "parentCode": "538", + "name": "虹口区", + "en_name": "Hongkou", + "deleted": false, + "sublist": [] + }, + { + "code": "2019", + "parentCode": "538", + "name": "黄浦区", + "en_name": "Huangpu", + "deleted": false, + "sublist": [] + }, + { + "code": "2030", + "parentCode": "538", + "name": "嘉定区", + "en_name": "Jiading", + "deleted": false, + "sublist": [] + }, + { + "code": "2023", + "parentCode": "538", + "name": "静安区", + "en_name": "Jingan", + "deleted": false, + "sublist": [] + }, + { + "code": "2032", + "parentCode": "538", + "name": "金山区", + "en_name": "Jinshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2028", + "parentCode": "538", + "name": "闵行区", + "en_name": "Minxing", + "deleted": false, + "sublist": [] + }, + { + "code": "2031", + "parentCode": "538", + "name": "浦东新区", + "en_name": "Pudongxin", + "deleted": false, + "sublist": [] + }, + { + "code": "2024", + "parentCode": "538", + "name": "普陀区", + "en_name": "Putuo", + "deleted": false, + "sublist": [] + }, + { + "code": "2034", + "parentCode": "538", + "name": "青浦区", + "en_name": "Qingpu", + "deleted": false, + "sublist": [] + }, + { + "code": "2033", + "parentCode": "538", + "name": "松江区", + "en_name": "Songjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2021", + "parentCode": "538", + "name": "徐汇区", + "en_name": "Xuhui", + "deleted": false, + "sublist": [] + }, + { + "code": "2027", + "parentCode": "538", + "name": "杨浦区", + "en_name": "Yangpu", + "deleted": false, + "sublist": [] + }, + { + "code": "2022", + "parentCode": "538", + "name": "长宁区", + "en_name": "Changning", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "556", + "parentCode": "489", + "name": "陕西", + "en_name": "SHANXI", + "deleted": false, + "sublist": [ + { + "code": "862", + "parentCode": "556", + "name": "安康", + "en_name": "ANKANG", + "deleted": false, + "sublist": [ + { + "code": "5043", + "parentCode": "862", + "name": "白河县", + "en_name": "Baihe", + "deleted": false, + "sublist": [] + }, + { + "code": "5034", + "parentCode": "862", + "name": "汉滨区", + "en_name": "Hanbin", + "deleted": false, + "sublist": [] + }, + { + "code": "5035", + "parentCode": "862", + "name": "汉阴县", + "en_name": "Hanyin", + "deleted": false, + "sublist": [] + }, + { + "code": "5039", + "parentCode": "862", + "name": "岚皋县", + "en_name": "Langao", + "deleted": false, + "sublist": [] + }, + { + "code": "5037", + "parentCode": "862", + "name": "宁陕县", + "en_name": "Ningshan", + "deleted": false, + "sublist": [] + }, + { + "code": "5040", + "parentCode": "862", + "name": "平利县", + "en_name": "Pingli", + "deleted": false, + "sublist": [] + }, + { + "code": "5036", + "parentCode": "862", + "name": "石泉县", + "en_name": "Shiquan", + "deleted": false, + "sublist": [] + }, + { + "code": "5042", + "parentCode": "862", + "name": "旬阳市", + "en_name": "xunyangshi", + "deleted": false, + "sublist": [] + }, + { + "code": "5041", + "parentCode": "862", + "name": "镇坪县", + "en_name": "Zhenping", + "deleted": false, + "sublist": [] + }, + { + "code": "5038", + "parentCode": "862", + "name": "紫阳县", + "en_name": "Ziyang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "856", + "parentCode": "556", + "name": "宝鸡", + "en_name": "BAOJI", + "deleted": false, + "sublist": [ + { + "code": "4964", + "parentCode": "856", + "name": "陈仓区", + "en_name": "Chencang", + "deleted": false, + "sublist": [] + }, + { + "code": "4972", + "parentCode": "856", + "name": "凤县", + "en_name": "Feng", + "deleted": false, + "sublist": [] + }, + { + "code": "4965", + "parentCode": "856", + "name": "凤翔区", + "en_name": "fengxiangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4967", + "parentCode": "856", + "name": "扶风县", + "en_name": "Fufeng", + "deleted": false, + "sublist": [] + }, + { + "code": "4963", + "parentCode": "856", + "name": "金台区", + "en_name": "Jintai", + "deleted": false, + "sublist": [] + }, + { + "code": "4971", + "parentCode": "856", + "name": "麟游县", + "en_name": "Linyou", + "deleted": false, + "sublist": [] + }, + { + "code": "4969", + "parentCode": "856", + "name": "陇县", + "en_name": "Long", + "deleted": false, + "sublist": [] + }, + { + "code": "4968", + "parentCode": "856", + "name": "眉县", + "en_name": "Mei", + "deleted": false, + "sublist": [] + }, + { + "code": "4970", + "parentCode": "856", + "name": "千阳县", + "en_name": "Qianyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4966", + "parentCode": "856", + "name": "岐山县", + "en_name": "Qishan", + "deleted": false, + "sublist": [] + }, + { + "code": "4973", + "parentCode": "856", + "name": "太白县", + "en_name": "Taibai", + "deleted": false, + "sublist": [] + }, + { + "code": "4962", + "parentCode": "856", + "name": "渭滨区", + "en_name": "Weibin", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "860", + "parentCode": "556", + "name": "汉中", + "en_name": "HANZHONG", + "deleted": false, + "sublist": [ + { + "code": "5013", + "parentCode": "860", + "name": "城固县", + "en_name": "Chenggu", + "deleted": false, + "sublist": [] + }, + { + "code": "5021", + "parentCode": "860", + "name": "佛坪县", + "en_name": "Foping", + "deleted": false, + "sublist": [] + }, + { + "code": "5011", + "parentCode": "860", + "name": "汉台区", + "en_name": "Hantai", + "deleted": false, + "sublist": [] + }, + { + "code": "5020", + "parentCode": "860", + "name": "留坝县", + "en_name": "Liuba", + "deleted": false, + "sublist": [] + }, + { + "code": "5018", + "parentCode": "860", + "name": "略阳县", + "en_name": "Lueyang", + "deleted": false, + "sublist": [] + }, + { + "code": "5016", + "parentCode": "860", + "name": "勉县", + "en_name": "Mian", + "deleted": false, + "sublist": [] + }, + { + "code": "5012", + "parentCode": "860", + "name": "南郑区", + "en_name": "Nanzheng", + "deleted": false, + "sublist": [] + }, + { + "code": "5017", + "parentCode": "860", + "name": "宁强县", + "en_name": "Ningqiang", + "deleted": false, + "sublist": [] + }, + { + "code": "5015", + "parentCode": "860", + "name": "西乡县", + "en_name": "Xixiang", + "deleted": false, + "sublist": [] + }, + { + "code": "5014", + "parentCode": "860", + "name": "洋县", + "en_name": "Yang", + "deleted": false, + "sublist": [] + }, + { + "code": "5019", + "parentCode": "860", + "name": "镇巴县", + "en_name": "Zhenba", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "863", + "parentCode": "556", + "name": "商洛", + "en_name": "SHANGLUO", + "deleted": false, + "sublist": [ + { + "code": "5046", + "parentCode": "863", + "name": "丹凤县", + "en_name": "Danfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "5045", + "parentCode": "863", + "name": "洛南县", + "en_name": "Luonan", + "deleted": false, + "sublist": [] + }, + { + "code": "5047", + "parentCode": "863", + "name": "商南县", + "en_name": "Shangnan", + "deleted": false, + "sublist": [] + }, + { + "code": "5044", + "parentCode": "863", + "name": "商州区", + "en_name": "Shangzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "5048", + "parentCode": "863", + "name": "山阳县", + "en_name": "Shanyang", + "deleted": false, + "sublist": [] + }, + { + "code": "5050", + "parentCode": "863", + "name": "柞水县", + "en_name": "Zhashui", + "deleted": false, + "sublist": [] + }, + { + "code": "5049", + "parentCode": "863", + "name": "镇安县", + "en_name": "Zhenan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "855", + "parentCode": "556", + "name": "铜川", + "en_name": "TONGCHUAN", + "deleted": false, + "sublist": [ + { + "code": "4958", + "parentCode": "855", + "name": "王益区", + "en_name": "Wangyi", + "deleted": false, + "sublist": [] + }, + { + "code": "4960", + "parentCode": "855", + "name": "耀州区", + "en_name": "Yaozhou", + "deleted": false, + "sublist": [] + }, + { + "code": "4961", + "parentCode": "855", + "name": "宜君县", + "en_name": "Yijun", + "deleted": false, + "sublist": [] + }, + { + "code": "4959", + "parentCode": "855", + "name": "印台区", + "en_name": "Yintai", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "858", + "parentCode": "556", + "name": "渭南", + "en_name": "WEINAN", + "deleted": false, + "sublist": [ + { + "code": "4996", + "parentCode": "858", + "name": "白水县", + "en_name": "Baishui", + "deleted": false, + "sublist": [] + }, + { + "code": "4994", + "parentCode": "858", + "name": "澄城县", + "en_name": "Chengcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4992", + "parentCode": "858", + "name": "大荔县", + "en_name": "Dali", + "deleted": false, + "sublist": [] + }, + { + "code": "4997", + "parentCode": "858", + "name": "富平县", + "en_name": "Fuping", + "deleted": false, + "sublist": [] + }, + { + "code": "4988", + "parentCode": "858", + "name": "韩城市", + "en_name": "Hancheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4993", + "parentCode": "858", + "name": "合阳县", + "en_name": "Heyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4990", + "parentCode": "858", + "name": "华州区", + "en_name": "Hua", + "deleted": false, + "sublist": [] + }, + { + "code": "4989", + "parentCode": "858", + "name": "华阴市", + "en_name": "Huayin", + "deleted": false, + "sublist": [] + }, + { + "code": "4987", + "parentCode": "858", + "name": "临渭区", + "en_name": "Linwei", + "deleted": false, + "sublist": [] + }, + { + "code": "4995", + "parentCode": "858", + "name": "蒲城县", + "en_name": "Pucheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4991", + "parentCode": "858", + "name": "潼关县", + "en_name": "Tongguan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "854", + "parentCode": "556", + "name": "西安", + "en_name": "XIAN", + "deleted": false, + "sublist": [ + { + "code": "2075", + "parentCode": "854", + "name": "灞桥区", + "en_name": "Baqiao", + "deleted": false, + "sublist": [] + }, + { + "code": "2071", + "parentCode": "854", + "name": "碑林区", + "en_name": "Beilin", + "deleted": false, + "sublist": [] + }, + { + "code": "2371", + "parentCode": "854", + "name": "浐灞生态区", + "en_name": "Chanba", + "deleted": false, + "sublist": [] + }, + { + "code": "2083", + "parentCode": "854", + "name": "沣渭新区", + "en_name": "fengweixin", + "deleted": false, + "sublist": [] + }, + { + "code": "2082", + "parentCode": "854", + "name": "高陵区", + "en_name": "Gaoling", + "deleted": false, + "sublist": [] + }, + { + "code": "2368", + "parentCode": "854", + "name": "高新技术产业开发区", + "en_name": "Gaoxinkaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2374", + "parentCode": "854", + "name": "国际港务区", + "en_name": "Guojigangwu", + "deleted": false, + "sublist": [] + }, + { + "code": "2081", + "parentCode": "854", + "name": "鄠邑区", + "en_name": "Hu", + "deleted": false, + "sublist": [] + }, + { + "code": "2369", + "parentCode": "854", + "name": "经济技术开发区", + "en_name": "Jingjikaifaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2079", + "parentCode": "854", + "name": "蓝田县", + "en_name": "Lantian", + "deleted": false, + "sublist": [] + }, + { + "code": "2072", + "parentCode": "854", + "name": "莲湖区", + "en_name": "Lianhu", + "deleted": false, + "sublist": [] + }, + { + "code": "2078", + "parentCode": "854", + "name": "临潼区", + "en_name": "Lintong", + "deleted": false, + "sublist": [] + }, + { + "code": "2370", + "parentCode": "854", + "name": "曲江新区", + "en_name": "Qujiang", + "deleted": false, + "sublist": [] + }, + { + "code": "3278", + "parentCode": "854", + "name": "渭北工业区", + "en_name": "Weibeigongyequ", + "deleted": false, + "sublist": [] + }, + { + "code": "2074", + "parentCode": "854", + "name": "未央区", + "en_name": "Weiyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2373", + "parentCode": "854", + "name": "西安国家民用航天产业基地", + "en_name": "Minyonghangtian", + "deleted": false, + "sublist": [] + }, + { + "code": "2070", + "parentCode": "854", + "name": "新城区", + "en_name": "Xincheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2372", + "parentCode": "854", + "name": "阎良国家航空高新技术产业基地", + "en_name": "Yanlianghangkong", + "deleted": false, + "sublist": [] + }, + { + "code": "2077", + "parentCode": "854", + "name": "阎良区", + "en_name": "Yanliang", + "deleted": false, + "sublist": [] + }, + { + "code": "2073", + "parentCode": "854", + "name": "雁塔区", + "en_name": "Yanta", + "deleted": false, + "sublist": [] + }, + { + "code": "2076", + "parentCode": "854", + "name": "长安区", + "en_name": "Changan", + "deleted": false, + "sublist": [] + }, + { + "code": "2080", + "parentCode": "854", + "name": "周至县", + "en_name": "Zhouzhi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "857", + "parentCode": "556", + "name": "咸阳", + "en_name": "XIANYANG", + "deleted": false, + "sublist": [ + { + "code": "10058", + "parentCode": "857", + "name": "兴平市", + "en_name": "XINGPING", + "deleted": false, + "sublist": [] + }, + { + "code": "10470", + "parentCode": "857", + "name": "杨陵区", + "en_name": "YANGLING", + "deleted": false, + "sublist": [] + }, + { + "code": "4982", + "parentCode": "857", + "name": "彬州市", + "en_name": "Bin", + "deleted": false, + "sublist": [] + }, + { + "code": "4985", + "parentCode": "857", + "name": "淳化县", + "en_name": "Chunhua", + "deleted": false, + "sublist": [] + }, + { + "code": "4978", + "parentCode": "857", + "name": "泾阳县", + "en_name": "Jingyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4980", + "parentCode": "857", + "name": "礼泉县", + "en_name": "Liquan", + "deleted": false, + "sublist": [] + }, + { + "code": "4979", + "parentCode": "857", + "name": "乾县", + "en_name": "Qian", + "deleted": false, + "sublist": [] + }, + { + "code": "4974", + "parentCode": "857", + "name": "秦都区", + "en_name": "Qindu", + "deleted": false, + "sublist": [] + }, + { + "code": "4977", + "parentCode": "857", + "name": "三原县", + "en_name": "Sanyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4975", + "parentCode": "857", + "name": "渭城区", + "en_name": "Weicheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4986", + "parentCode": "857", + "name": "武功县", + "en_name": "Wugong", + "deleted": false, + "sublist": [] + }, + { + "code": "4984", + "parentCode": "857", + "name": "旬邑县", + "en_name": "Xunyi", + "deleted": false, + "sublist": [] + }, + { + "code": "4981", + "parentCode": "857", + "name": "永寿县", + "en_name": "Yongshou", + "deleted": false, + "sublist": [] + }, + { + "code": "4983", + "parentCode": "857", + "name": "长武县", + "en_name": "Changwu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "933", + "parentCode": "556", + "name": "西咸新区", + "en_name": "XIXIANXINQU", + "deleted": false, + "sublist": [ + { + "code": "3360", + "parentCode": "933", + "name": "沣东新城", + "en_name": "Fengdongxincheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3361", + "parentCode": "933", + "name": "沣西新城", + "en_name": "Fengxixincheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3363", + "parentCode": "933", + "name": "泾河新城", + "en_name": "Jinghexincheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3364", + "parentCode": "933", + "name": "空港新城", + "en_name": "Konggangxincheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3362", + "parentCode": "933", + "name": "秦汉新城", + "en_name": "Qinhanxincheng", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "859", + "parentCode": "556", + "name": "延安", + "en_name": "YANAN", + "deleted": false, + "sublist": [ + { + "code": "5002", + "parentCode": "859", + "name": "安塞区", + "en_name": "Ansai", + "deleted": false, + "sublist": [] + }, + { + "code": "4998", + "parentCode": "859", + "name": "宝塔区", + "en_name": "Baota", + "deleted": false, + "sublist": [] + }, + { + "code": "5006", + "parentCode": "859", + "name": "富县", + "en_name": "Fu", + "deleted": false, + "sublist": [] + }, + { + "code": "5005", + "parentCode": "859", + "name": "甘泉县", + "en_name": "Ganquan", + "deleted": false, + "sublist": [] + }, + { + "code": "5010", + "parentCode": "859", + "name": "黄陵县", + "en_name": "Huangling", + "deleted": false, + "sublist": [] + }, + { + "code": "5009", + "parentCode": "859", + "name": "黄龙县", + "en_name": "Huanglong", + "deleted": false, + "sublist": [] + }, + { + "code": "5007", + "parentCode": "859", + "name": "洛川县", + "en_name": "Luochuan", + "deleted": false, + "sublist": [] + }, + { + "code": "5004", + "parentCode": "859", + "name": "吴起县", + "en_name": "Wuqi", + "deleted": false, + "sublist": [] + }, + { + "code": "5000", + "parentCode": "859", + "name": "延川县", + "en_name": "Yanchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4999", + "parentCode": "859", + "name": "延长县", + "en_name": "Yanchang", + "deleted": false, + "sublist": [] + }, + { + "code": "5008", + "parentCode": "859", + "name": "宜川县", + "en_name": "Yichuan", + "deleted": false, + "sublist": [] + }, + { + "code": "5003", + "parentCode": "859", + "name": "志丹县", + "en_name": "Zhidan", + "deleted": false, + "sublist": [] + }, + { + "code": "5001", + "parentCode": "859", + "name": "子长市", + "en_name": "Zizhang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "861", + "parentCode": "556", + "name": "榆林", + "en_name": "YULIN", + "deleted": false, + "sublist": [ + { + "code": "5027", + "parentCode": "861", + "name": "定边县", + "en_name": "Dingbian", + "deleted": false, + "sublist": [] + }, + { + "code": "5024", + "parentCode": "861", + "name": "府谷县", + "en_name": "Fugu", + "deleted": false, + "sublist": [] + }, + { + "code": "5025", + "parentCode": "861", + "name": "横山区", + "en_name": "Hengshan", + "deleted": false, + "sublist": [] + }, + { + "code": "5030", + "parentCode": "861", + "name": "佳县", + "en_name": "Jia", + "deleted": false, + "sublist": [] + }, + { + "code": "5026", + "parentCode": "861", + "name": "靖边县", + "en_name": "Jingbian", + "deleted": false, + "sublist": [] + }, + { + "code": "5029", + "parentCode": "861", + "name": "米脂县", + "en_name": "Mizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5032", + "parentCode": "861", + "name": "清涧县", + "en_name": "Qingjian", + "deleted": false, + "sublist": [] + }, + { + "code": "5023", + "parentCode": "861", + "name": "神木市", + "en_name": "Shenmu", + "deleted": false, + "sublist": [] + }, + { + "code": "5028", + "parentCode": "861", + "name": "绥德县", + "en_name": "Suide", + "deleted": false, + "sublist": [] + }, + { + "code": "5031", + "parentCode": "861", + "name": "吴堡县", + "en_name": "Wubu", + "deleted": false, + "sublist": [] + }, + { + "code": "5022", + "parentCode": "861", + "name": "榆阳区", + "en_name": "Yuyang", + "deleted": false, + "sublist": [] + }, + { + "code": "5033", + "parentCode": "861", + "name": "子洲县", + "en_name": "Zizhou", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "533", + "parentCode": "489", + "name": "山西", + "en_name": "SHANXI", + "deleted": false, + "sublist": [ + { + "code": "577", + "parentCode": "533", + "name": "大同", + "en_name": "DATONG", + "deleted": false, + "sublist": [ + { + "code": "2847", + "parentCode": "577", + "name": "云州区", + "en_name": "Datong", + "deleted": false, + "sublist": [] + }, + { + "code": "2852", + "parentCode": "577", + "name": "广灵县", + "en_name": "Guangling", + "deleted": false, + "sublist": [] + }, + { + "code": "2853", + "parentCode": "577", + "name": "浑源县", + "en_name": "Hunyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2849", + "parentCode": "577", + "name": "灵丘县", + "en_name": "Lingqiu", + "deleted": false, + "sublist": [] + }, + { + "code": "2848", + "parentCode": "577", + "name": "天镇县", + "en_name": "Tianzhen", + "deleted": false, + "sublist": [] + }, + { + "code": "2846", + "parentCode": "577", + "name": "新荣区", + "en_name": "Xinrong", + "deleted": false, + "sublist": [] + }, + { + "code": "2850", + "parentCode": "577", + "name": "阳高县", + "en_name": "Yanggao", + "deleted": false, + "sublist": [] + }, + { + "code": "2851", + "parentCode": "577", + "name": "左云县", + "en_name": "Zuoyun", + "deleted": false, + "sublist": [] + }, + { + "code": "104031", + "parentCode": "577", + "name": "平城区", + "en_name": "pingchengqu", + "deleted": false, + "sublist": [] + }, + { + "code": "104032", + "parentCode": "577", + "name": "云冈区", + "en_name": "yungangqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "580", + "parentCode": "533", + "name": "晋城", + "en_name": "JINCHENG", + "deleted": false, + "sublist": [ + { + "code": "2872", + "parentCode": "580", + "name": "城区", + "en_name": "Cheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2873", + "parentCode": "580", + "name": "高平市", + "en_name": "Gaoping", + "deleted": false, + "sublist": [] + }, + { + "code": "2875", + "parentCode": "580", + "name": "陵川县", + "en_name": "Lingchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2877", + "parentCode": "580", + "name": "沁水县", + "en_name": "Qinshui", + "deleted": false, + "sublist": [] + }, + { + "code": "2876", + "parentCode": "580", + "name": "阳城县", + "en_name": "Yangcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2874", + "parentCode": "580", + "name": "泽州县", + "en_name": "Zezhou", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "582", + "parentCode": "533", + "name": "晋中", + "en_name": "JINZHONG", + "deleted": false, + "sublist": [ + { + "code": "3283", + "parentCode": "582", + "name": "和顺县", + "en_name": "Heshunxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3290", + "parentCode": "582", + "name": "介休市", + "en_name": "Jiexiushi", + "deleted": false, + "sublist": [] + }, + { + "code": "3289", + "parentCode": "582", + "name": "灵石县", + "en_name": "Lingshixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3288", + "parentCode": "582", + "name": "平遥县", + "en_name": "Pingyaoxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3287", + "parentCode": "582", + "name": "祁县", + "en_name": "Qixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3285", + "parentCode": "582", + "name": "寿阳县", + "en_name": "Shouyangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3286", + "parentCode": "582", + "name": "太谷区", + "en_name": "Taiguxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3284", + "parentCode": "582", + "name": "昔阳县", + "en_name": "Xiyangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3280", + "parentCode": "582", + "name": "榆次区", + "en_name": "Yuciqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3281", + "parentCode": "582", + "name": "榆社县", + "en_name": "Yushexian", + "deleted": false, + "sublist": [] + }, + { + "code": "3282", + "parentCode": "582", + "name": "左权县", + "en_name": "Zuoquanxian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "585", + "parentCode": "533", + "name": "临汾", + "en_name": "LINFEN", + "deleted": false, + "sublist": [ + { + "code": "2926", + "parentCode": "585", + "name": "安泽县", + "en_name": "Anze", + "deleted": false, + "sublist": [] + }, + { + "code": "2927", + "parentCode": "585", + "name": "大宁县", + "en_name": "Daning", + "deleted": false, + "sublist": [] + }, + { + "code": "2924", + "parentCode": "585", + "name": "汾西县", + "en_name": "Fenxi", + "deleted": false, + "sublist": [] + }, + { + "code": "2928", + "parentCode": "585", + "name": "浮山县", + "en_name": "Fushan", + "deleted": false, + "sublist": [] + }, + { + "code": "2929", + "parentCode": "585", + "name": "古县", + "en_name": "Gu", + "deleted": false, + "sublist": [] + }, + { + "code": "2936", + "parentCode": "585", + "name": "洪洞县", + "en_name": "Hongdong", + "deleted": false, + "sublist": [] + }, + { + "code": "3279", + "parentCode": "585", + "name": "侯马市", + "en_name": "Houma", + "deleted": false, + "sublist": [] + }, + { + "code": "2923", + "parentCode": "585", + "name": "霍州市", + "en_name": "Huozhou", + "deleted": false, + "sublist": [] + }, + { + "code": "2925", + "parentCode": "585", + "name": "吉县", + "en_name": "Ji", + "deleted": false, + "sublist": [] + }, + { + "code": "2937", + "parentCode": "585", + "name": "蒲县", + "en_name": "Pu", + "deleted": false, + "sublist": [] + }, + { + "code": "2935", + "parentCode": "585", + "name": "曲沃县", + "en_name": "Quwo", + "deleted": false, + "sublist": [] + }, + { + "code": "2931", + "parentCode": "585", + "name": "襄汾县", + "en_name": "Xiangfen", + "deleted": false, + "sublist": [] + }, + { + "code": "2934", + "parentCode": "585", + "name": "乡宁县", + "en_name": "Xiangning", + "deleted": false, + "sublist": [] + }, + { + "code": "2930", + "parentCode": "585", + "name": "隰县", + "en_name": "Xi", + "deleted": false, + "sublist": [] + }, + { + "code": "2921", + "parentCode": "585", + "name": "尧都区", + "en_name": "Raodu", + "deleted": false, + "sublist": [] + }, + { + "code": "2932", + "parentCode": "585", + "name": "翼城县", + "en_name": "Yicheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2933", + "parentCode": "585", + "name": "永和县", + "en_name": "Yonghe", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "586", + "parentCode": "533", + "name": "吕梁", + "en_name": "LVLIANG", + "deleted": false, + "sublist": [ + { + "code": "2945", + "parentCode": "586", + "name": "方山县", + "en_name": "Fangshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2940", + "parentCode": "586", + "name": "汾阳市", + "en_name": "Fenyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2949", + "parentCode": "586", + "name": "交城县", + "en_name": "Jiaocheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2948", + "parentCode": "586", + "name": "交口县", + "en_name": "Jiaokou", + "deleted": false, + "sublist": [] + }, + { + "code": "2947", + "parentCode": "586", + "name": "岚县", + "en_name": "Lan", + "deleted": false, + "sublist": [] + }, + { + "code": "2944", + "parentCode": "586", + "name": "临县", + "en_name": "Lin", + "deleted": false, + "sublist": [] + }, + { + "code": "2938", + "parentCode": "586", + "name": "离石区", + "en_name": "Lishi", + "deleted": false, + "sublist": [] + }, + { + "code": "2946", + "parentCode": "586", + "name": "柳林县", + "en_name": "Liulin", + "deleted": false, + "sublist": [] + }, + { + "code": "2950", + "parentCode": "586", + "name": "石楼县", + "en_name": "Shilou", + "deleted": false, + "sublist": [] + }, + { + "code": "2941", + "parentCode": "586", + "name": "文水县", + "en_name": "Wenshui", + "deleted": false, + "sublist": [] + }, + { + "code": "2939", + "parentCode": "586", + "name": "孝义市", + "en_name": "Xiaoyi", + "deleted": false, + "sublist": [] + }, + { + "code": "2943", + "parentCode": "586", + "name": "兴县", + "en_name": "Xing", + "deleted": false, + "sublist": [] + }, + { + "code": "2942", + "parentCode": "586", + "name": "中阳县", + "en_name": "Zhongyang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "581", + "parentCode": "533", + "name": "朔州", + "en_name": "SHUOZHOU", + "deleted": false, + "sublist": [ + { + "code": "2883", + "parentCode": "581", + "name": "怀仁市", + "en_name": "Huairen", + "deleted": false, + "sublist": [] + }, + { + "code": "2879", + "parentCode": "581", + "name": "平鲁区", + "en_name": "Pinglu", + "deleted": false, + "sublist": [] + }, + { + "code": "2880", + "parentCode": "581", + "name": "山阴县", + "en_name": "Shanyin", + "deleted": false, + "sublist": [] + }, + { + "code": "2878", + "parentCode": "581", + "name": "朔城区", + "en_name": "Shuocheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2882", + "parentCode": "581", + "name": "应县", + "en_name": "Ying", + "deleted": false, + "sublist": [] + }, + { + "code": "2881", + "parentCode": "581", + "name": "右玉县", + "en_name": "Youyu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "576", + "parentCode": "533", + "name": "太原", + "en_name": "TAIYUAN", + "deleted": false, + "sublist": [ + { + "code": "2510", + "parentCode": "576", + "name": "古交市", + "en_name": "GUJIAOSHI", + "deleted": false, + "sublist": [] + }, + { + "code": "2504", + "parentCode": "576", + "name": "尖草坪区", + "en_name": "JIANCAOPINGQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2506", + "parentCode": "576", + "name": "晋源区", + "en_name": "JINYUANQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2509", + "parentCode": "576", + "name": "娄烦县", + "en_name": "LOUFANXIAN", + "deleted": false, + "sublist": [] + }, + { + "code": "2507", + "parentCode": "576", + "name": "清徐县", + "en_name": "QINGXUXIAN", + "deleted": false, + "sublist": [] + }, + { + "code": "2505", + "parentCode": "576", + "name": "万柏林区", + "en_name": "WANBAILINQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2501", + "parentCode": "576", + "name": "小店区", + "en_name": "XIAODIANQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2503", + "parentCode": "576", + "name": "杏花岭区", + "en_name": "XINGHUALINGQU", + "deleted": false, + "sublist": [] + }, + { + "code": "2508", + "parentCode": "576", + "name": "阳曲县", + "en_name": "YANGQUXIAN", + "deleted": false, + "sublist": [] + }, + { + "code": "2502", + "parentCode": "576", + "name": "迎泽区", + "en_name": "YINGZEQU", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "584", + "parentCode": "533", + "name": "忻州", + "en_name": "XINZHOU", + "deleted": false, + "sublist": [ + { + "code": "2918", + "parentCode": "584", + "name": "保德县", + "en_name": "Baode", + "deleted": false, + "sublist": [] + }, + { + "code": "2909", + "parentCode": "584", + "name": "代县", + "en_name": "Dai", + "deleted": false, + "sublist": [] + }, + { + "code": "2919", + "parentCode": "584", + "name": "定襄县", + "en_name": "Dingxiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2916", + "parentCode": "584", + "name": "繁峙县", + "en_name": "Fanzhi", + "deleted": false, + "sublist": [] + }, + { + "code": "2917", + "parentCode": "584", + "name": "河曲县", + "en_name": "Hequ", + "deleted": false, + "sublist": [] + }, + { + "code": "2915", + "parentCode": "584", + "name": "静乐县", + "en_name": "Jingle", + "deleted": false, + "sublist": [] + }, + { + "code": "2920", + "parentCode": "584", + "name": "岢岚县", + "en_name": "Kelan", + "deleted": false, + "sublist": [] + }, + { + "code": "2914", + "parentCode": "584", + "name": "宁武县", + "en_name": "Ningwu", + "deleted": false, + "sublist": [] + }, + { + "code": "2913", + "parentCode": "584", + "name": "偏关县", + "en_name": "Pianguan", + "deleted": false, + "sublist": [] + }, + { + "code": "2910", + "parentCode": "584", + "name": "神池县", + "en_name": "Shenchi", + "deleted": false, + "sublist": [] + }, + { + "code": "2912", + "parentCode": "584", + "name": "五台县", + "en_name": "Wutai", + "deleted": false, + "sublist": [] + }, + { + "code": "2911", + "parentCode": "584", + "name": "五寨县", + "en_name": "Wuzhai", + "deleted": false, + "sublist": [] + }, + { + "code": "2907", + "parentCode": "584", + "name": "忻府区", + "en_name": "Xinfu", + "deleted": false, + "sublist": [] + }, + { + "code": "2908", + "parentCode": "584", + "name": "原平市", + "en_name": "Pingyuan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "578", + "parentCode": "533", + "name": "阳泉", + "en_name": "YANGQUAN", + "deleted": false, + "sublist": [ + { + "code": "2854", + "parentCode": "578", + "name": "城区", + "en_name": "Cheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2856", + "parentCode": "578", + "name": "郊区", + "en_name": "Jiao", + "deleted": false, + "sublist": [] + }, + { + "code": "2855", + "parentCode": "578", + "name": "矿区", + "en_name": "Kuang", + "deleted": false, + "sublist": [] + }, + { + "code": "2857", + "parentCode": "578", + "name": "平定县", + "en_name": "Pingding", + "deleted": false, + "sublist": [] + }, + { + "code": "2858", + "parentCode": "578", + "name": "盂县", + "en_name": "Yu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "583", + "parentCode": "533", + "name": "运城", + "en_name": "YUNCHENG", + "deleted": false, + "sublist": [ + { + "code": "910", + "parentCode": "583", + "name": "永济市", + "en_name": "YONGJI", + "deleted": false, + "sublist": [] + }, + { + "code": "2895", + "parentCode": "583", + "name": "河津市", + "en_name": "Hejin", + "deleted": false, + "sublist": [] + }, + { + "code": "2901", + "parentCode": "583", + "name": "绛县", + "en_name": "Jiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2902", + "parentCode": "583", + "name": "稷山县", + "en_name": "Jishan", + "deleted": false, + "sublist": [] + }, + { + "code": "2906", + "parentCode": "583", + "name": "临猗县", + "en_name": "Linyi", + "deleted": false, + "sublist": [] + }, + { + "code": "2899", + "parentCode": "583", + "name": "平陆县", + "en_name": "Pinglu", + "deleted": false, + "sublist": [] + }, + { + "code": "2903", + "parentCode": "583", + "name": "芮城县", + "en_name": "Ruicheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2905", + "parentCode": "583", + "name": "万荣县", + "en_name": "Wanrong", + "deleted": false, + "sublist": [] + }, + { + "code": "2897", + "parentCode": "583", + "name": "闻喜县", + "en_name": "Wenxi", + "deleted": false, + "sublist": [] + }, + { + "code": "2904", + "parentCode": "583", + "name": "夏县", + "en_name": "Xia", + "deleted": false, + "sublist": [] + }, + { + "code": "2898", + "parentCode": "583", + "name": "新绛县", + "en_name": "Xinjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2894", + "parentCode": "583", + "name": "盐湖区", + "en_name": "Yanhu", + "deleted": false, + "sublist": [] + }, + { + "code": "2900", + "parentCode": "583", + "name": "垣曲县", + "en_name": "Yuan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "579", + "parentCode": "533", + "name": "长治", + "en_name": "CHANGZHI", + "deleted": false, + "sublist": [ + { + "code": "2859", + "parentCode": "579", + "name": "潞州区", + "en_name": "Cheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2871", + "parentCode": "579", + "name": "壶关县", + "en_name": "Huguan", + "deleted": false, + "sublist": [] + }, + { + "code": "2868", + "parentCode": "579", + "name": "黎城县", + "en_name": "Licheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2861", + "parentCode": "579", + "name": "潞城区", + "en_name": "Lucheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2864", + "parentCode": "579", + "name": "平顺县", + "en_name": "Pingshun", + "deleted": false, + "sublist": [] + }, + { + "code": "2870", + "parentCode": "579", + "name": "沁县", + "en_name": "Qin", + "deleted": false, + "sublist": [] + }, + { + "code": "2866", + "parentCode": "579", + "name": "沁源县", + "en_name": "Qinyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2867", + "parentCode": "579", + "name": "屯留区", + "en_name": "Tunliu", + "deleted": false, + "sublist": [] + }, + { + "code": "2869", + "parentCode": "579", + "name": "武乡县", + "en_name": "Wuxiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2865", + "parentCode": "579", + "name": "襄垣县", + "en_name": "Xiangyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "2862", + "parentCode": "579", + "name": "上党区", + "en_name": "Changzhi", + "deleted": false, + "sublist": [] + }, + { + "code": "2863", + "parentCode": "579", + "name": "长子县", + "en_name": "Changzi", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "552", + "parentCode": "489", + "name": "四川", + "en_name": "SICHUAN", + "deleted": false, + "sublist": [ + { + "code": "819", + "parentCode": "552", + "name": "阿坝", + "en_name": "ABA", + "deleted": false, + "sublist": [ + { + "code": "4617", + "parentCode": "819", + "name": "阿坝县", + "en_name": "Aba", + "deleted": false, + "sublist": [] + }, + { + "code": "4614", + "parentCode": "819", + "name": "黑水县", + "en_name": "Heishui", + "deleted": false, + "sublist": [] + }, + { + "code": "4619", + "parentCode": "819", + "name": "红原县", + "en_name": "Hongyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4612", + "parentCode": "819", + "name": "金川县", + "en_name": "Jinchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4611", + "parentCode": "819", + "name": "九寨沟县", + "en_name": "Jiuzhaigou", + "deleted": false, + "sublist": [] + }, + { + "code": "4608", + "parentCode": "819", + "name": "理县", + "en_name": "Li", + "deleted": false, + "sublist": [] + }, + { + "code": "4615", + "parentCode": "819", + "name": "马尔康市", + "en_name": "Maerkang", + "deleted": false, + "sublist": [] + }, + { + "code": "4609", + "parentCode": "819", + "name": "茂县", + "en_name": "Mao", + "deleted": false, + "sublist": [] + }, + { + "code": "4616", + "parentCode": "819", + "name": "壤塘县", + "en_name": "Rangtang", + "deleted": false, + "sublist": [] + }, + { + "code": "4618", + "parentCode": "819", + "name": "若尔盖县", + "en_name": "Ruoergai", + "deleted": false, + "sublist": [] + }, + { + "code": "4610", + "parentCode": "819", + "name": "松潘县", + "en_name": "Songpan", + "deleted": false, + "sublist": [] + }, + { + "code": "4607", + "parentCode": "819", + "name": "汶川县", + "en_name": "Wenchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4613", + "parentCode": "819", + "name": "小金县", + "en_name": "Xiaojin", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "817", + "parentCode": "552", + "name": "巴中", + "en_name": "BAZHONG", + "deleted": false, + "sublist": [ + { + "code": "4598", + "parentCode": "817", + "name": "巴州区", + "en_name": "Bazhou", + "deleted": false, + "sublist": [] + }, + { + "code": "4599", + "parentCode": "817", + "name": "恩阳区", + "en_name": "Enyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4601", + "parentCode": "817", + "name": "南江县", + "en_name": "Nanjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4602", + "parentCode": "817", + "name": "平昌县", + "en_name": "Pingchang", + "deleted": false, + "sublist": [] + }, + { + "code": "4600", + "parentCode": "817", + "name": "通江县", + "en_name": "Tongjiang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "801", + "parentCode": "552", + "name": "成都", + "en_name": "CHENGDU", + "deleted": false, + "sublist": [ + { + "code": "10201", + "parentCode": "801", + "name": "简阳市", + "en_name": "JIANYANG", + "deleted": false, + "sublist": [] + }, + { + "code": "2111", + "parentCode": "801", + "name": "成华区", + "en_name": "Chenghua", + "deleted": false, + "sublist": [] + }, + { + "code": "2378", + "parentCode": "801", + "name": "崇州市", + "en_name": "Chongzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "2119", + "parentCode": "801", + "name": "大邑县", + "en_name": "Dayi", + "deleted": false, + "sublist": [] + }, + { + "code": "2380", + "parentCode": "801", + "name": "都江堰市", + "en_name": "Dujiangyan", + "deleted": false, + "sublist": [] + }, + { + "code": "2381", + "parentCode": "801", + "name": "高新区", + "en_name": "Gaoxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3301", + "parentCode": "801", + "name": "高新西区", + "en_name": "Gaoxinxiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2108", + "parentCode": "801", + "name": "锦江区", + "en_name": "Jinjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2109", + "parentCode": "801", + "name": "金牛区", + "en_name": "Jinniu", + "deleted": false, + "sublist": [] + }, + { + "code": "2118", + "parentCode": "801", + "name": "金堂县", + "en_name": "Jintang", + "deleted": false, + "sublist": [] + }, + { + "code": "2112", + "parentCode": "801", + "name": "龙泉驿区", + "en_name": "Longquanyi", + "deleted": false, + "sublist": [] + }, + { + "code": "2379", + "parentCode": "801", + "name": "彭州市", + "en_name": "Pengzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "2117", + "parentCode": "801", + "name": "郫都区", + "en_name": "pi", + "deleted": false, + "sublist": [] + }, + { + "code": "2120", + "parentCode": "801", + "name": "蒲江县", + "en_name": "Pujiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2113", + "parentCode": "801", + "name": "青白江区", + "en_name": "Qingbaijiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2107", + "parentCode": "801", + "name": "青羊区", + "en_name": "Qingyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2377", + "parentCode": "801", + "name": "邛崃市", + "en_name": "Qionglai", + "deleted": false, + "sublist": [] + }, + { + "code": "2116", + "parentCode": "801", + "name": "双流区", + "en_name": "Shuangliu", + "deleted": false, + "sublist": [] + }, + { + "code": "3300", + "parentCode": "801", + "name": "天府新区", + "en_name": "Tianfuxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "2115", + "parentCode": "801", + "name": "温江区", + "en_name": "Wenjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2110", + "parentCode": "801", + "name": "武侯区", + "en_name": "Wuhou", + "deleted": false, + "sublist": [] + }, + { + "code": "2114", + "parentCode": "801", + "name": "新都区", + "en_name": "Xindu", + "deleted": false, + "sublist": [] + }, + { + "code": "2121", + "parentCode": "801", + "name": "新津区", + "en_name": "xinjinqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "815", + "parentCode": "552", + "name": "达州", + "en_name": "DAZHOU", + "deleted": false, + "sublist": [ + { + "code": "4584", + "parentCode": "815", + "name": "达川区", + "en_name": "Dachuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4588", + "parentCode": "815", + "name": "大竹县", + "en_name": "Dazhu", + "deleted": false, + "sublist": [] + }, + { + "code": "4587", + "parentCode": "815", + "name": "开江县", + "en_name": "Kaijiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4589", + "parentCode": "815", + "name": "渠县", + "en_name": "Qu", + "deleted": false, + "sublist": [] + }, + { + "code": "4583", + "parentCode": "815", + "name": "通川区", + "en_name": "Tongchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4585", + "parentCode": "815", + "name": "万源市", + "en_name": "Wanyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4586", + "parentCode": "815", + "name": "宣汉县", + "en_name": "Xuanhan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "805", + "parentCode": "552", + "name": "德阳", + "en_name": "DEYANG", + "deleted": false, + "sublist": [ + { + "code": "4510", + "parentCode": "805", + "name": "广汉市", + "en_name": "Guanghan", + "deleted": false, + "sublist": [] + }, + { + "code": "4509", + "parentCode": "805", + "name": "旌阳区", + "en_name": "Jingyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4514", + "parentCode": "805", + "name": "罗江区", + "en_name": "Luojiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4512", + "parentCode": "805", + "name": "绵竹市", + "en_name": "Mianzhu", + "deleted": false, + "sublist": [] + }, + { + "code": "4511", + "parentCode": "805", + "name": "什邡市", + "en_name": "Shifang", + "deleted": false, + "sublist": [] + }, + { + "code": "4513", + "parentCode": "805", + "name": "中江县", + "en_name": "Zhongjiang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "820", + "parentCode": "552", + "name": "甘孜", + "en_name": "GANZI", + "deleted": false, + "sublist": [ + { + "code": "4630", + "parentCode": "820", + "name": "白玉县", + "en_name": "Baiyu", + "deleted": false, + "sublist": [] + }, + { + "code": "4634", + "parentCode": "820", + "name": "巴塘县", + "en_name": "Batang", + "deleted": false, + "sublist": [] + }, + { + "code": "4622", + "parentCode": "820", + "name": "丹巴县", + "en_name": "Danba", + "deleted": false, + "sublist": [] + }, + { + "code": "4636", + "parentCode": "820", + "name": "稻城县", + "en_name": "Daocheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4625", + "parentCode": "820", + "name": "道孚县", + "en_name": "Daofu", + "deleted": false, + "sublist": [] + }, + { + "code": "4629", + "parentCode": "820", + "name": "德格县", + "en_name": "Dege", + "deleted": false, + "sublist": [] + }, + { + "code": "4637", + "parentCode": "820", + "name": "得荣县", + "en_name": "Derong", + "deleted": false, + "sublist": [] + }, + { + "code": "4627", + "parentCode": "820", + "name": "甘孜县", + "en_name": "Ganzi", + "deleted": false, + "sublist": [] + }, + { + "code": "4623", + "parentCode": "820", + "name": "九龙县", + "en_name": "Jiulong", + "deleted": false, + "sublist": [] + }, + { + "code": "4620", + "parentCode": "820", + "name": "康定市", + "en_name": "Kangding", + "deleted": false, + "sublist": [] + }, + { + "code": "4633", + "parentCode": "820", + "name": "理塘县", + "en_name": "Litang", + "deleted": false, + "sublist": [] + }, + { + "code": "4621", + "parentCode": "820", + "name": "泸定县", + "en_name": "Luding", + "deleted": false, + "sublist": [] + }, + { + "code": "4626", + "parentCode": "820", + "name": "炉霍县", + "en_name": "Luhuo", + "deleted": false, + "sublist": [] + }, + { + "code": "4631", + "parentCode": "820", + "name": "色达县", + "en_name": "Seda", + "deleted": false, + "sublist": [] + }, + { + "code": "4632", + "parentCode": "820", + "name": "石渠县", + "en_name": "Shiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4635", + "parentCode": "820", + "name": "乡城县", + "en_name": "Xiangcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4628", + "parentCode": "820", + "name": "新龙县", + "en_name": "Xinlong", + "deleted": false, + "sublist": [] + }, + { + "code": "4624", + "parentCode": "820", + "name": "雅江县", + "en_name": "Yajiang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "814", + "parentCode": "552", + "name": "广安", + "en_name": "GUANGAN", + "deleted": false, + "sublist": [ + { + "code": "4577", + "parentCode": "814", + "name": "广安区", + "en_name": "Guangan", + "deleted": false, + "sublist": [] + }, + { + "code": "4579", + "parentCode": "814", + "name": "华蓥市", + "en_name": "Huaying", + "deleted": false, + "sublist": [] + }, + { + "code": "4582", + "parentCode": "814", + "name": "邻水县", + "en_name": "Linshui", + "deleted": false, + "sublist": [] + }, + { + "code": "4578", + "parentCode": "814", + "name": "前锋区", + "en_name": "Qianfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "4581", + "parentCode": "814", + "name": "武胜县", + "en_name": "Wusheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4580", + "parentCode": "814", + "name": "岳池县", + "en_name": "Yuechi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "807", + "parentCode": "552", + "name": "广元", + "en_name": "GUANGYUAN", + "deleted": false, + "sublist": [ + { + "code": "4530", + "parentCode": "807", + "name": "苍溪县", + "en_name": "Cangxi", + "deleted": false, + "sublist": [] + }, + { + "code": "4526", + "parentCode": "807", + "name": "朝天区", + "en_name": "Chaotian", + "deleted": false, + "sublist": [] + }, + { + "code": "4529", + "parentCode": "807", + "name": "剑阁县", + "en_name": "Jiange", + "deleted": false, + "sublist": [] + }, + { + "code": "4524", + "parentCode": "807", + "name": "利州区", + "en_name": "Lizhou", + "deleted": false, + "sublist": [] + }, + { + "code": "4528", + "parentCode": "807", + "name": "青川县", + "en_name": "Qingchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4527", + "parentCode": "807", + "name": "旺苍县", + "en_name": "Wangcang", + "deleted": false, + "sublist": [] + }, + { + "code": "4525", + "parentCode": "807", + "name": "昭化区", + "en_name": "Zhaohua", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "810", + "parentCode": "552", + "name": "乐山", + "en_name": "LESHAN", + "deleted": false, + "sublist": [ + { + "code": "10065", + "parentCode": "810", + "name": "峨眉山市", + "en_name": "EMEI", + "deleted": false, + "sublist": [] + }, + { + "code": "4550", + "parentCode": "810", + "name": "峨边彝族自治县", + "en_name": "Ebianyizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4548", + "parentCode": "810", + "name": "夹江县", + "en_name": "Jiajiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4547", + "parentCode": "810", + "name": "井研县", + "en_name": "Jingyan", + "deleted": false, + "sublist": [] + }, + { + "code": "4544", + "parentCode": "810", + "name": "金口河区", + "en_name": "Jinkouhe", + "deleted": false, + "sublist": [] + }, + { + "code": "4551", + "parentCode": "810", + "name": "马边彝族自治县", + "en_name": "Mabianyizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4549", + "parentCode": "810", + "name": "沐川县", + "en_name": "Muchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4546", + "parentCode": "810", + "name": "犍为县", + "en_name": "Qianwei", + "deleted": false, + "sublist": [] + }, + { + "code": "4542", + "parentCode": "810", + "name": "沙湾区", + "en_name": "Shawan", + "deleted": false, + "sublist": [] + }, + { + "code": "4541", + "parentCode": "810", + "name": "市中区", + "en_name": "Shizhong", + "deleted": false, + "sublist": [] + }, + { + "code": "4543", + "parentCode": "810", + "name": "五通桥区", + "en_name": "Wutongqiao", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "821", + "parentCode": "552", + "name": "凉山", + "en_name": "LIANGSHAN", + "deleted": false, + "sublist": [ + { + "code": "10104", + "parentCode": "821", + "name": "西昌市", + "en_name": "XICHANG", + "deleted": false, + "sublist": [] + }, + { + "code": "4646", + "parentCode": "821", + "name": "布拖县", + "en_name": "Butuo", + "deleted": false, + "sublist": [] + }, + { + "code": "4641", + "parentCode": "821", + "name": "德昌县", + "en_name": "Dechang", + "deleted": false, + "sublist": [] + }, + { + "code": "4652", + "parentCode": "821", + "name": "甘洛县", + "en_name": "Ganluo", + "deleted": false, + "sublist": [] + }, + { + "code": "4643", + "parentCode": "821", + "name": "会东县", + "en_name": "Huidong", + "deleted": false, + "sublist": [] + }, + { + "code": "4642", + "parentCode": "821", + "name": "会理市", + "en_name": "huilishi", + "deleted": false, + "sublist": [] + }, + { + "code": "4647", + "parentCode": "821", + "name": "金阳县", + "en_name": "Jinyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4654", + "parentCode": "821", + "name": "雷波县", + "en_name": "Leibo", + "deleted": false, + "sublist": [] + }, + { + "code": "4653", + "parentCode": "821", + "name": "美姑县", + "en_name": "Meigu", + "deleted": false, + "sublist": [] + }, + { + "code": "4650", + "parentCode": "821", + "name": "冕宁县", + "en_name": "Mianning", + "deleted": false, + "sublist": [] + }, + { + "code": "4639", + "parentCode": "821", + "name": "木里藏族自治县", + "en_name": "Mulizangzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4644", + "parentCode": "821", + "name": "宁南县", + "en_name": "Ningnan", + "deleted": false, + "sublist": [] + }, + { + "code": "4645", + "parentCode": "821", + "name": "普格县", + "en_name": "Puge", + "deleted": false, + "sublist": [] + }, + { + "code": "4649", + "parentCode": "821", + "name": "喜德县", + "en_name": "Xide", + "deleted": false, + "sublist": [] + }, + { + "code": "4640", + "parentCode": "821", + "name": "盐源县", + "en_name": "Yanyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4651", + "parentCode": "821", + "name": "越西县", + "en_name": "Yuexi", + "deleted": false, + "sublist": [] + }, + { + "code": "4648", + "parentCode": "821", + "name": "昭觉县", + "en_name": "Zhaojue", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "804", + "parentCode": "552", + "name": "泸州", + "en_name": "LUZHOU", + "deleted": false, + "sublist": [ + { + "code": "4507", + "parentCode": "804", + "name": "古蔺县", + "en_name": "Gulin", + "deleted": false, + "sublist": [] + }, + { + "code": "4506", + "parentCode": "804", + "name": "合江县", + "en_name": "Hejiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4502", + "parentCode": "804", + "name": "江阳区", + "en_name": "Jiangyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4504", + "parentCode": "804", + "name": "龙马潭区", + "en_name": "Longmatan", + "deleted": false, + "sublist": [] + }, + { + "code": "4505", + "parentCode": "804", + "name": "泸县", + "en_name": "Lu", + "deleted": false, + "sublist": [] + }, + { + "code": "4503", + "parentCode": "804", + "name": "纳溪区", + "en_name": "Naxi", + "deleted": false, + "sublist": [] + }, + { + "code": "4508", + "parentCode": "804", + "name": "叙永县", + "en_name": "Xuyong", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "812", + "parentCode": "552", + "name": "眉山", + "en_name": "MEISHAN", + "deleted": false, + "sublist": [ + { + "code": "4565", + "parentCode": "812", + "name": "丹棱县", + "en_name": "Danling", + "deleted": false, + "sublist": [] + }, + { + "code": "4561", + "parentCode": "812", + "name": "东坡区", + "en_name": "Dongpo", + "deleted": false, + "sublist": [] + }, + { + "code": "4564", + "parentCode": "812", + "name": "洪雅县", + "en_name": "Hongya", + "deleted": false, + "sublist": [] + }, + { + "code": "4563", + "parentCode": "812", + "name": "彭山区", + "en_name": "Pengshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4566", + "parentCode": "812", + "name": "青神县", + "en_name": "Qingshen", + "deleted": false, + "sublist": [] + }, + { + "code": "4562", + "parentCode": "812", + "name": "仁寿县", + "en_name": "Renshou", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "806", + "parentCode": "552", + "name": "绵阳", + "en_name": "MIANYANG", + "deleted": false, + "sublist": [ + { + "code": "4520", + "parentCode": "806", + "name": "安州区", + "en_name": "An", + "deleted": false, + "sublist": [] + }, + { + "code": "4522", + "parentCode": "806", + "name": "北川羌族自治县", + "en_name": "Beichuanqiangzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4515", + "parentCode": "806", + "name": "涪城区", + "en_name": "Fucheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4517", + "parentCode": "806", + "name": "江油市", + "en_name": "Jiangyou", + "deleted": false, + "sublist": [] + }, + { + "code": "4523", + "parentCode": "806", + "name": "平武县", + "en_name": "Pingwu", + "deleted": false, + "sublist": [] + }, + { + "code": "4518", + "parentCode": "806", + "name": "三台县", + "en_name": "Santai", + "deleted": false, + "sublist": [] + }, + { + "code": "4519", + "parentCode": "806", + "name": "盐亭县", + "en_name": "Yanting", + "deleted": false, + "sublist": [] + }, + { + "code": "4516", + "parentCode": "806", + "name": "游仙区", + "en_name": "Youxian", + "deleted": false, + "sublist": [] + }, + { + "code": "4521", + "parentCode": "806", + "name": "梓潼县", + "en_name": "Zitong", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "811", + "parentCode": "552", + "name": "南充", + "en_name": "NANCHONG", + "deleted": false, + "sublist": [ + { + "code": "4553", + "parentCode": "811", + "name": "高坪区", + "en_name": "Gaoping", + "deleted": false, + "sublist": [] + }, + { + "code": "4554", + "parentCode": "811", + "name": "嘉陵区", + "en_name": "Jialing", + "deleted": false, + "sublist": [] + }, + { + "code": "4555", + "parentCode": "811", + "name": "阆中市", + "en_name": "Langzhong", + "deleted": false, + "sublist": [] + }, + { + "code": "4556", + "parentCode": "811", + "name": "南部县", + "en_name": "Nanbu", + "deleted": false, + "sublist": [] + }, + { + "code": "4558", + "parentCode": "811", + "name": "蓬安县", + "en_name": "Pengan", + "deleted": false, + "sublist": [] + }, + { + "code": "4552", + "parentCode": "811", + "name": "顺庆区", + "en_name": "Shunqing", + "deleted": false, + "sublist": [] + }, + { + "code": "4560", + "parentCode": "811", + "name": "西充县", + "en_name": "Xichong", + "deleted": false, + "sublist": [] + }, + { + "code": "4559", + "parentCode": "811", + "name": "仪陇县", + "en_name": "Yilong", + "deleted": false, + "sublist": [] + }, + { + "code": "4557", + "parentCode": "811", + "name": "营山县", + "en_name": "Yingshan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "809", + "parentCode": "552", + "name": "内江", + "en_name": "NEIJIANG", + "deleted": false, + "sublist": [ + { + "code": "4537", + "parentCode": "809", + "name": "东兴区", + "en_name": "Dongxing", + "deleted": false, + "sublist": [] + }, + { + "code": "4540", + "parentCode": "809", + "name": "隆昌市", + "en_name": "Longchang", + "deleted": false, + "sublist": [] + }, + { + "code": "4536", + "parentCode": "809", + "name": "市中区", + "en_name": "Shizhong", + "deleted": false, + "sublist": [] + }, + { + "code": "4538", + "parentCode": "809", + "name": "威远县", + "en_name": "Weiyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4539", + "parentCode": "809", + "name": "资中县", + "en_name": "Zizhong", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "803", + "parentCode": "552", + "name": "攀枝花", + "en_name": "PANZHIHUA", + "deleted": false, + "sublist": [ + { + "code": "4497", + "parentCode": "803", + "name": "东区", + "en_name": "Dong", + "deleted": false, + "sublist": [] + }, + { + "code": "4500", + "parentCode": "803", + "name": "米易县", + "en_name": "Miyi", + "deleted": false, + "sublist": [] + }, + { + "code": "4499", + "parentCode": "803", + "name": "仁和区", + "en_name": "Renhe", + "deleted": false, + "sublist": [] + }, + { + "code": "4498", + "parentCode": "803", + "name": "西区", + "en_name": "Xi", + "deleted": false, + "sublist": [] + }, + { + "code": "4501", + "parentCode": "803", + "name": "盐边县", + "en_name": "Yanbian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "808", + "parentCode": "552", + "name": "遂宁", + "en_name": "SUINING", + "deleted": false, + "sublist": [ + { + "code": "4532", + "parentCode": "808", + "name": "安居区", + "en_name": "Anju", + "deleted": false, + "sublist": [] + }, + { + "code": "4531", + "parentCode": "808", + "name": "船山区", + "en_name": "Chuanshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4535", + "parentCode": "808", + "name": "大英县", + "en_name": "Daying", + "deleted": false, + "sublist": [] + }, + { + "code": "4533", + "parentCode": "808", + "name": "蓬溪县", + "en_name": "Pengxi", + "deleted": false, + "sublist": [] + }, + { + "code": "4534", + "parentCode": "808", + "name": "射洪市", + "en_name": "Shehong", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "816", + "parentCode": "552", + "name": "雅安", + "en_name": "YAAN", + "deleted": false, + "sublist": [ + { + "code": "4597", + "parentCode": "816", + "name": "宝兴县", + "en_name": "Baoxing", + "deleted": false, + "sublist": [] + }, + { + "code": "4593", + "parentCode": "816", + "name": "汉源县", + "en_name": "Hanyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4596", + "parentCode": "816", + "name": "芦山县", + "en_name": "Lushan", + "deleted": false, + "sublist": [] + }, + { + "code": "4591", + "parentCode": "816", + "name": "名山区", + "en_name": "Mingshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4594", + "parentCode": "816", + "name": "石棉县", + "en_name": "Shimian", + "deleted": false, + "sublist": [] + }, + { + "code": "4595", + "parentCode": "816", + "name": "天全县", + "en_name": "Tianquan", + "deleted": false, + "sublist": [] + }, + { + "code": "4592", + "parentCode": "816", + "name": "荥经县", + "en_name": "Yingjing", + "deleted": false, + "sublist": [] + }, + { + "code": "4590", + "parentCode": "816", + "name": "雨城区", + "en_name": "Yucheng", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "813", + "parentCode": "552", + "name": "宜宾", + "en_name": "YIBIN", + "deleted": false, + "sublist": [ + { + "code": "4567", + "parentCode": "813", + "name": "翠屏区", + "en_name": "Cuiping", + "deleted": false, + "sublist": [] + }, + { + "code": "4572", + "parentCode": "813", + "name": "高县", + "en_name": "Gao", + "deleted": false, + "sublist": [] + }, + { + "code": "4573", + "parentCode": "813", + "name": "珙县", + "en_name": "Gong", + "deleted": false, + "sublist": [] + }, + { + "code": "4570", + "parentCode": "813", + "name": "江安县", + "en_name": "Jiangan", + "deleted": false, + "sublist": [] + }, + { + "code": "4568", + "parentCode": "813", + "name": "南溪区", + "en_name": "Nanxi", + "deleted": false, + "sublist": [] + }, + { + "code": "4576", + "parentCode": "813", + "name": "屏山县", + "en_name": "Pingshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4575", + "parentCode": "813", + "name": "兴文县", + "en_name": "Xingwen", + "deleted": false, + "sublist": [] + }, + { + "code": "4569", + "parentCode": "813", + "name": "叙州区", + "en_name": "Yibin", + "deleted": false, + "sublist": [] + }, + { + "code": "4574", + "parentCode": "813", + "name": "筠连县", + "en_name": "Junlian", + "deleted": false, + "sublist": [] + }, + { + "code": "4571", + "parentCode": "813", + "name": "长宁县", + "en_name": "Changning", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "802", + "parentCode": "552", + "name": "自贡", + "en_name": "ZIGONG", + "deleted": false, + "sublist": [ + { + "code": "4493", + "parentCode": "802", + "name": "大安区", + "en_name": "Daan", + "deleted": false, + "sublist": [] + }, + { + "code": "4496", + "parentCode": "802", + "name": "富顺县", + "en_name": "Fushun", + "deleted": false, + "sublist": [] + }, + { + "code": "4492", + "parentCode": "802", + "name": "贡井区", + "en_name": "Gongjing", + "deleted": false, + "sublist": [] + }, + { + "code": "4495", + "parentCode": "802", + "name": "荣县", + "en_name": "Rong", + "deleted": false, + "sublist": [] + }, + { + "code": "4494", + "parentCode": "802", + "name": "沿滩区", + "en_name": "Yantan", + "deleted": false, + "sublist": [] + }, + { + "code": "4491", + "parentCode": "802", + "name": "自流井区", + "en_name": "Ziliujing", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "818", + "parentCode": "552", + "name": "资阳", + "en_name": "ZIYANG", + "deleted": false, + "sublist": [ + { + "code": "4605", + "parentCode": "818", + "name": "安岳县", + "en_name": "Anyue", + "deleted": false, + "sublist": [] + }, + { + "code": "4606", + "parentCode": "818", + "name": "乐至县", + "en_name": "Lezhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4603", + "parentCode": "818", + "name": "雁江区", + "en_name": "Yanjiang", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "563", + "parentCode": "489", + "name": "台湾", + "en_name": "TAIWAN", + "deleted": false, + "sublist": [] + }, + { + "code": "531", + "parentCode": "489", + "name": "天津", + "en_name": "TIANJIN", + "deleted": false, + "sublist": [ + { + "code": "2177", + "parentCode": "531", + "name": "宝坻区", + "en_name": "Baodi", + "deleted": false, + "sublist": [] + }, + { + "code": "2175", + "parentCode": "531", + "name": "北辰区", + "en_name": "Beichen", + "deleted": false, + "sublist": [] + }, + { + "code": "2171", + "parentCode": "531", + "name": "滨海新区", + "en_name": "Binhaixin", + "deleted": false, + "sublist": [] + }, + { + "code": "2172", + "parentCode": "531", + "name": "东丽区", + "en_name": "Dongli", + "deleted": false, + "sublist": [] + }, + { + "code": "2169", + "parentCode": "531", + "name": "河北区", + "en_name": "Hebei", + "deleted": false, + "sublist": [] + }, + { + "code": "2166", + "parentCode": "531", + "name": "河东区", + "en_name": "Hedong", + "deleted": false, + "sublist": [] + }, + { + "code": "2165", + "parentCode": "531", + "name": "和平区", + "en_name": "Heping", + "deleted": false, + "sublist": [] + }, + { + "code": "2167", + "parentCode": "531", + "name": "河西区", + "en_name": "Hexi", + "deleted": false, + "sublist": [] + }, + { + "code": "2170", + "parentCode": "531", + "name": "红桥区", + "en_name": "Hongqiao", + "deleted": false, + "sublist": [] + }, + { + "code": "2178", + "parentCode": "531", + "name": "静海区", + "en_name": "Jinghai", + "deleted": false, + "sublist": [] + }, + { + "code": "2174", + "parentCode": "531", + "name": "津南区", + "en_name": "Jinnan", + "deleted": false, + "sublist": [] + }, + { + "code": "2180", + "parentCode": "531", + "name": "蓟州区", + "en_name": "Ji", + "deleted": false, + "sublist": [] + }, + { + "code": "2168", + "parentCode": "531", + "name": "南开区", + "en_name": "Nankai", + "deleted": false, + "sublist": [] + }, + { + "code": "2179", + "parentCode": "531", + "name": "宁河区", + "en_name": "Ninghe", + "deleted": false, + "sublist": [] + }, + { + "code": "2176", + "parentCode": "531", + "name": "武清区", + "en_name": "Wuqing", + "deleted": false, + "sublist": [] + }, + { + "code": "2173", + "parentCode": "531", + "name": "西青区", + "en_name": "Xiqing", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "561", + "parentCode": "489", + "name": "香港", + "en_name": "HONGKONG", + "deleted": false, + "sublist": [] + }, + { + "code": "560", + "parentCode": "489", + "name": "新疆", + "en_name": "XINJIANG", + "deleted": false, + "sublist": [ + { + "code": "897", + "parentCode": "560", + "name": "阿克苏", + "en_name": "AKESU", + "deleted": false, + "sublist": [ + { + "code": "5238", + "parentCode": "897", + "name": "阿克苏市", + "en_name": "Akesu", + "deleted": false, + "sublist": [] + }, + { + "code": "5245", + "parentCode": "897", + "name": "阿瓦提县", + "en_name": "Awati", + "deleted": false, + "sublist": [] + }, + { + "code": "5243", + "parentCode": "897", + "name": "拜城县", + "en_name": "Baicheng", + "deleted": false, + "sublist": [] + }, + { + "code": "5246", + "parentCode": "897", + "name": "柯坪县", + "en_name": "Keping", + "deleted": false, + "sublist": [] + }, + { + "code": "5240", + "parentCode": "897", + "name": "库车市", + "en_name": "Kuche", + "deleted": false, + "sublist": [] + }, + { + "code": "5241", + "parentCode": "897", + "name": "沙雅县", + "en_name": "Shaya", + "deleted": false, + "sublist": [] + }, + { + "code": "5239", + "parentCode": "897", + "name": "温宿县", + "en_name": "Wensu", + "deleted": false, + "sublist": [] + }, + { + "code": "5244", + "parentCode": "897", + "name": "乌什县", + "en_name": "Wushi", + "deleted": false, + "sublist": [] + }, + { + "code": "5242", + "parentCode": "897", + "name": "新和县", + "en_name": "Xinhe", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "10176", + "parentCode": "560", + "name": "阿拉尔市", + "en_name": "ALAER", + "deleted": false, + "sublist": [] + }, + { + "code": "903", + "parentCode": "560", + "name": "阿勒泰", + "en_name": "ALETAI", + "deleted": false, + "sublist": [ + { + "code": "5289", + "parentCode": "903", + "name": "阿勒泰市", + "en_name": "Aletai", + "deleted": false, + "sublist": [] + }, + { + "code": "5290", + "parentCode": "903", + "name": "布尔津县", + "en_name": "Buerjin", + "deleted": false, + "sublist": [] + }, + { + "code": "5292", + "parentCode": "903", + "name": "福海县", + "en_name": "Fuhai", + "deleted": false, + "sublist": [] + }, + { + "code": "5291", + "parentCode": "903", + "name": "富蕴县", + "en_name": "Fuyun", + "deleted": false, + "sublist": [] + }, + { + "code": "5293", + "parentCode": "903", + "name": "哈巴河县", + "en_name": "Habahe", + "deleted": false, + "sublist": [] + }, + { + "code": "5295", + "parentCode": "903", + "name": "吉木乃县", + "en_name": "Jimunai", + "deleted": false, + "sublist": [] + }, + { + "code": "5294", + "parentCode": "903", + "name": "青河县", + "en_name": "Qinghe", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "896", + "parentCode": "560", + "name": "巴音郭楞", + "en_name": "BAYINGUOLENG", + "deleted": false, + "sublist": [ + { + "code": "5237", + "parentCode": "896", + "name": "博湖县", + "en_name": "Bohu", + "deleted": false, + "sublist": [] + }, + { + "code": "5235", + "parentCode": "896", + "name": "和静县", + "en_name": "Hejing", + "deleted": false, + "sublist": [] + }, + { + "code": "5236", + "parentCode": "896", + "name": "和硕县", + "en_name": "Heshuo", + "deleted": false, + "sublist": [] + }, + { + "code": "5229", + "parentCode": "896", + "name": "库尔勒市", + "en_name": "Kuerle", + "deleted": false, + "sublist": [] + }, + { + "code": "5230", + "parentCode": "896", + "name": "轮台县", + "en_name": "Luntai", + "deleted": false, + "sublist": [] + }, + { + "code": "5233", + "parentCode": "896", + "name": "且末县", + "en_name": "Qiemo", + "deleted": false, + "sublist": [] + }, + { + "code": "5232", + "parentCode": "896", + "name": "若羌县", + "en_name": "Ruoqiang", + "deleted": false, + "sublist": [] + }, + { + "code": "5234", + "parentCode": "896", + "name": "焉耆回族自治县", + "en_name": "Yanqihuizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5231", + "parentCode": "896", + "name": "尉犁县", + "en_name": "Yuli", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "932", + "parentCode": "560", + "name": "北屯市", + "en_name": "BEITUNQU", + "deleted": false, + "sublist": [] + }, + { + "code": "895", + "parentCode": "560", + "name": "博尔塔拉", + "en_name": "BOERTALA", + "deleted": false, + "sublist": [ + { + "code": "5226", + "parentCode": "895", + "name": "阿拉山口市", + "en_name": "Alashankou", + "deleted": false, + "sublist": [] + }, + { + "code": "5225", + "parentCode": "895", + "name": "博乐市", + "en_name": "Bole", + "deleted": false, + "sublist": [] + }, + { + "code": "5227", + "parentCode": "895", + "name": "精河县", + "en_name": "Jinghe", + "deleted": false, + "sublist": [] + }, + { + "code": "5228", + "parentCode": "895", + "name": "温泉县", + "en_name": "Wenqan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "894", + "parentCode": "560", + "name": "昌吉", + "en_name": "CHANGJI", + "deleted": false, + "sublist": [ + { + "code": "5218", + "parentCode": "894", + "name": "昌吉市", + "en_name": "Changji", + "deleted": false, + "sublist": [] + }, + { + "code": "5219", + "parentCode": "894", + "name": "阜康市", + "en_name": "Fukang", + "deleted": false, + "sublist": [] + }, + { + "code": "5220", + "parentCode": "894", + "name": "呼图壁县", + "en_name": "Hutubi", + "deleted": false, + "sublist": [] + }, + { + "code": "5223", + "parentCode": "894", + "name": "吉木萨尔县", + "en_name": "Jimusaer", + "deleted": false, + "sublist": [] + }, + { + "code": "5221", + "parentCode": "894", + "name": "玛纳斯县", + "en_name": "Manasi", + "deleted": false, + "sublist": [] + }, + { + "code": "5224", + "parentCode": "894", + "name": "木垒哈萨克自治县", + "en_name": "Muleihasakezizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5222", + "parentCode": "894", + "name": "奇台县", + "en_name": "Qitai", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "893", + "parentCode": "560", + "name": "哈密", + "en_name": "HAMI", + "deleted": false, + "sublist": [ + { + "code": "5216", + "parentCode": "893", + "name": "巴里坤哈萨克自治县", + "en_name": "Balikunhasakezizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5215", + "parentCode": "893", + "name": "伊州区", + "en_name": "Hami", + "deleted": false, + "sublist": [] + }, + { + "code": "5217", + "parentCode": "893", + "name": "伊吾县", + "en_name": "Yiwu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "900", + "parentCode": "560", + "name": "和田", + "en_name": "HETIAN", + "deleted": false, + "sublist": [ + { + "code": "5268", + "parentCode": "900", + "name": "策勒县", + "en_name": "Cele", + "deleted": false, + "sublist": [] + }, + { + "code": "5263", + "parentCode": "900", + "name": "和田市", + "en_name": "Hetian", + "deleted": false, + "sublist": [] + }, + { + "code": "5264", + "parentCode": "900", + "name": "和田县", + "en_name": "Hetian", + "deleted": false, + "sublist": [] + }, + { + "code": "5267", + "parentCode": "900", + "name": "洛浦县", + "en_name": "Luopu", + "deleted": false, + "sublist": [] + }, + { + "code": "5270", + "parentCode": "900", + "name": "民丰县", + "en_name": "Minfeng", + "deleted": false, + "sublist": [] + }, + { + "code": "5265", + "parentCode": "900", + "name": "墨玉县", + "en_name": "Moyu", + "deleted": false, + "sublist": [] + }, + { + "code": "5266", + "parentCode": "900", + "name": "皮山县", + "en_name": "Pishan", + "deleted": false, + "sublist": [] + }, + { + "code": "5269", + "parentCode": "900", + "name": "于田县", + "en_name": "Yutian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "899", + "parentCode": "560", + "name": "喀什", + "en_name": "KESHEN", + "deleted": false, + "sublist": [ + { + "code": "5261", + "parentCode": "899", + "name": "巴楚县", + "en_name": "Bachu", + "deleted": false, + "sublist": [] + }, + { + "code": "5260", + "parentCode": "899", + "name": "伽师县", + "en_name": "Jiashi", + "deleted": false, + "sublist": [] + }, + { + "code": "5251", + "parentCode": "899", + "name": "喀什市", + "en_name": "Kashi", + "deleted": false, + "sublist": [] + }, + { + "code": "5258", + "parentCode": "899", + "name": "麦盖提县", + "en_name": "Maigaiti", + "deleted": false, + "sublist": [] + }, + { + "code": "5256", + "parentCode": "899", + "name": "莎车县", + "en_name": "Shache", + "deleted": false, + "sublist": [] + }, + { + "code": "5252", + "parentCode": "899", + "name": "疏附县", + "en_name": "Shufu", + "deleted": false, + "sublist": [] + }, + { + "code": "5253", + "parentCode": "899", + "name": "疏勒县", + "en_name": "Shule", + "deleted": false, + "sublist": [] + }, + { + "code": "5262", + "parentCode": "899", + "name": "塔什库尔干塔吉克自治县", + "en_name": "Tashikuergantajikezizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5257", + "parentCode": "899", + "name": "叶城县", + "en_name": "Yecheng", + "deleted": false, + "sublist": [] + }, + { + "code": "5254", + "parentCode": "899", + "name": "英吉沙县", + "en_name": "Yingjisha", + "deleted": false, + "sublist": [] + }, + { + "code": "5259", + "parentCode": "899", + "name": "岳普湖县", + "en_name": "Yuepuhu", + "deleted": false, + "sublist": [] + }, + { + "code": "5255", + "parentCode": "899", + "name": "泽普县", + "en_name": "Zepu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "891", + "parentCode": "560", + "name": "克拉玛依", + "en_name": "KELAMAYI", + "deleted": false, + "sublist": [ + { + "code": "5210", + "parentCode": "891", + "name": "白碱滩区", + "en_name": "Baijiantan", + "deleted": false, + "sublist": [] + }, + { + "code": "5208", + "parentCode": "891", + "name": "独山子区", + "en_name": "Dushanzi", + "deleted": false, + "sublist": [] + }, + { + "code": "5209", + "parentCode": "891", + "name": "克拉玛依区", + "en_name": "Kelamayi", + "deleted": false, + "sublist": [] + }, + { + "code": "5211", + "parentCode": "891", + "name": "乌尔禾区", + "en_name": "Wuerhe", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "898", + "parentCode": "560", + "name": "克孜勒苏柯尔克孜", + "en_name": "KEZILESUKEERKEZI", + "deleted": false, + "sublist": [ + { + "code": "5249", + "parentCode": "898", + "name": "阿合奇县", + "en_name": "Aheqi", + "deleted": false, + "sublist": [] + }, + { + "code": "5248", + "parentCode": "898", + "name": "阿克陶县", + "en_name": "Aketao", + "deleted": false, + "sublist": [] + }, + { + "code": "5247", + "parentCode": "898", + "name": "阿图什市", + "en_name": "Atushi", + "deleted": false, + "sublist": [] + }, + { + "code": "5250", + "parentCode": "898", + "name": "乌恰县", + "en_name": "Wuqia", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "10061", + "parentCode": "560", + "name": "石河子市", + "en_name": "SHIHEZI", + "deleted": false, + "sublist": [] + }, + { + "code": "10301", + "parentCode": "560", + "name": "双河市", + "en_name": "SHUANGHE", + "deleted": false, + "sublist": [] + }, + { + "code": "902", + "parentCode": "560", + "name": "塔城", + "en_name": "TACHENG", + "deleted": false, + "sublist": [ + { + "code": "10166", + "parentCode": "902", + "name": "乌苏市", + "en_name": "WUSU", + "deleted": false, + "sublist": [] + }, + { + "code": "5284", + "parentCode": "902", + "name": "额敏县", + "en_name": "Emin", + "deleted": false, + "sublist": [] + }, + { + "code": "5288", + "parentCode": "902", + "name": "和布克赛尔蒙古自治县", + "en_name": "Hebukesaiermengguzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5285", + "parentCode": "902", + "name": "沙湾市", + "en_name": "shawanshi", + "deleted": false, + "sublist": [] + }, + { + "code": "5282", + "parentCode": "902", + "name": "塔城市", + "en_name": "Tacheng", + "deleted": false, + "sublist": [] + }, + { + "code": "5286", + "parentCode": "902", + "name": "托里县", + "en_name": "Tuoli", + "deleted": false, + "sublist": [] + }, + { + "code": "5287", + "parentCode": "902", + "name": "裕民县", + "en_name": "Yumin", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "10302", + "parentCode": "560", + "name": "铁门关市", + "en_name": "TIEMENGUAN", + "deleted": false, + "sublist": [] + }, + { + "code": "892", + "parentCode": "560", + "name": "吐鲁番", + "en_name": "TULUFAN", + "deleted": false, + "sublist": [ + { + "code": "5213", + "parentCode": "892", + "name": "鄯善县", + "en_name": "Shanshan", + "deleted": false, + "sublist": [] + }, + { + "code": "5212", + "parentCode": "892", + "name": "高昌区", + "en_name": "Tulufan", + "deleted": false, + "sublist": [] + }, + { + "code": "5214", + "parentCode": "892", + "name": "托克逊县", + "en_name": "Tuokexun", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "10177", + "parentCode": "560", + "name": "图木舒克市", + "en_name": "TUMUSHUKE", + "deleted": false, + "sublist": [] + }, + { + "code": "10178", + "parentCode": "560", + "name": "五家渠市", + "en_name": "WUJIAQU", + "deleted": false, + "sublist": [] + }, + { + "code": "890", + "parentCode": "560", + "name": "乌鲁木齐", + "en_name": "WULUMUQI", + "deleted": false, + "sublist": [ + { + "code": "3298", + "parentCode": "890", + "name": "达坂城区", + "en_name": "Dabanchengqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3297", + "parentCode": "890", + "name": "米东区", + "en_name": "Midongqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3294", + "parentCode": "890", + "name": "沙依巴克区", + "en_name": "Shayibakequ", + "deleted": false, + "sublist": [] + }, + { + "code": "3293", + "parentCode": "890", + "name": "水磨沟区", + "en_name": "Shuimogouqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3295", + "parentCode": "890", + "name": "天山区", + "en_name": "Tianshanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3296", + "parentCode": "890", + "name": "头屯河区", + "en_name": "Toutunhequ", + "deleted": false, + "sublist": [] + }, + { + "code": "3299", + "parentCode": "890", + "name": "乌鲁木齐县", + "en_name": "Wulumuqixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3292", + "parentCode": "890", + "name": "新市区", + "en_name": "Xinshiqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "901", + "parentCode": "560", + "name": "伊犁", + "en_name": "YILI", + "deleted": false, + "sublist": [ + { + "code": "10164", + "parentCode": "901", + "name": "奎屯市", + "en_name": "KUITUNSHI", + "deleted": false, + "sublist": [] + }, + { + "code": "5275", + "parentCode": "901", + "name": "察布查尔锡伯自治县", + "en_name": "Chabuchaerxibozizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5277", + "parentCode": "901", + "name": "巩留县", + "en_name": "Gongliu", + "deleted": false, + "sublist": [] + }, + { + "code": "5276", + "parentCode": "901", + "name": "霍城县", + "en_name": "Huocheng", + "deleted": false, + "sublist": [] + }, + { + "code": "5273", + "parentCode": "901", + "name": "霍尔果斯市", + "en_name": "Huoerguosishi", + "deleted": false, + "sublist": [] + }, + { + "code": "5281", + "parentCode": "901", + "name": "尼勒克县", + "en_name": "Nileke", + "deleted": false, + "sublist": [] + }, + { + "code": "5280", + "parentCode": "901", + "name": "特克斯县", + "en_name": "Tekesi", + "deleted": false, + "sublist": [] + }, + { + "code": "5278", + "parentCode": "901", + "name": "新源县", + "en_name": "Xinyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "5271", + "parentCode": "901", + "name": "伊宁市", + "en_name": "Yining", + "deleted": false, + "sublist": [] + }, + { + "code": "5274", + "parentCode": "901", + "name": "伊宁县", + "en_name": "Yining", + "deleted": false, + "sublist": [] + }, + { + "code": "5279", + "parentCode": "901", + "name": "昭苏县", + "en_name": "Zhaosu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "104028", + "parentCode": "560", + "name": "可克达拉市", + "en_name": "kekedalashi", + "deleted": false, + "sublist": [] + }, + { + "code": "104029", + "parentCode": "560", + "name": "昆玉市", + "en_name": "kunyushi", + "deleted": false, + "sublist": [] + }, + { + "code": "104030", + "parentCode": "560", + "name": "胡杨河市", + "en_name": "huyangheshi", + "deleted": false, + "sublist": [] + }, + { + "code": "104049", + "parentCode": "560", + "name": "新星市", + "en_name": "xinxingshi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "555", + "parentCode": "489", + "name": "西藏", + "en_name": "TIBET", + "deleted": false, + "sublist": [ + { + "code": "852", + "parentCode": "555", + "name": "阿里", + "en_name": "ALI", + "deleted": false, + "sublist": [ + { + "code": "4929", + "parentCode": "852", + "name": "措勤县", + "en_name": "Cuoqin", + "deleted": false, + "sublist": [] + }, + { + "code": "4925", + "parentCode": "852", + "name": "噶尔县", + "en_name": "Gaer", + "deleted": false, + "sublist": [] + }, + { + "code": "4928", + "parentCode": "852", + "name": "改则县", + "en_name": "Gaize", + "deleted": false, + "sublist": [] + }, + { + "code": "4927", + "parentCode": "852", + "name": "革吉县", + "en_name": "Geji", + "deleted": false, + "sublist": [] + }, + { + "code": "4923", + "parentCode": "852", + "name": "普兰县", + "en_name": "Pulan", + "deleted": false, + "sublist": [] + }, + { + "code": "4926", + "parentCode": "852", + "name": "日土县", + "en_name": "Ritu", + "deleted": false, + "sublist": [] + }, + { + "code": "4924", + "parentCode": "852", + "name": "札达县", + "en_name": "Zhada", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "848", + "parentCode": "555", + "name": "昌都", + "en_name": "CHANGDU", + "deleted": false, + "sublist": [ + { + "code": "4877", + "parentCode": "848", + "name": "八宿县", + "en_name": "Basu", + "deleted": false, + "sublist": [] + }, + { + "code": "4881", + "parentCode": "848", + "name": "边坝县", + "en_name": "Bianba", + "deleted": false, + "sublist": [] + }, + { + "code": "4871", + "parentCode": "848", + "name": "卡若区", + "en_name": "Changdu", + "deleted": false, + "sublist": [] + }, + { + "code": "4876", + "parentCode": "848", + "name": "察雅县", + "en_name": "Chaya", + "deleted": false, + "sublist": [] + }, + { + "code": "4875", + "parentCode": "848", + "name": "丁青县", + "en_name": "Dingqing", + "deleted": false, + "sublist": [] + }, + { + "code": "4873", + "parentCode": "848", + "name": "贡觉县", + "en_name": "Gongjue", + "deleted": false, + "sublist": [] + }, + { + "code": "4872", + "parentCode": "848", + "name": "江达县", + "en_name": "Jiangda", + "deleted": false, + "sublist": [] + }, + { + "code": "4874", + "parentCode": "848", + "name": "类乌齐县", + "en_name": "Leiwuqi", + "deleted": false, + "sublist": [] + }, + { + "code": "4880", + "parentCode": "848", + "name": "洛隆县", + "en_name": "Luolong", + "deleted": false, + "sublist": [] + }, + { + "code": "4879", + "parentCode": "848", + "name": "芒康县", + "en_name": "Mangkang", + "deleted": false, + "sublist": [] + }, + { + "code": "4878", + "parentCode": "848", + "name": "左贡县", + "en_name": "Zuogong", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "847", + "parentCode": "555", + "name": "拉萨", + "en_name": "LASA", + "deleted": false, + "sublist": [ + { + "code": "4863", + "parentCode": "847", + "name": "城关区", + "en_name": "Chengguan", + "deleted": false, + "sublist": [] + }, + { + "code": "4865", + "parentCode": "847", + "name": "当雄县", + "en_name": "Dangxiong", + "deleted": false, + "sublist": [] + }, + { + "code": "4869", + "parentCode": "847", + "name": "达孜区", + "en_name": "Dazi", + "deleted": false, + "sublist": [] + }, + { + "code": "4868", + "parentCode": "847", + "name": "堆龙德庆区", + "en_name": "Duilongdeqing", + "deleted": false, + "sublist": [] + }, + { + "code": "4864", + "parentCode": "847", + "name": "林周县", + "en_name": "Linzhou", + "deleted": false, + "sublist": [] + }, + { + "code": "4870", + "parentCode": "847", + "name": "墨竹工卡县", + "en_name": "Mozhugongka", + "deleted": false, + "sublist": [] + }, + { + "code": "4866", + "parentCode": "847", + "name": "尼木县", + "en_name": "Nimu", + "deleted": false, + "sublist": [] + }, + { + "code": "4867", + "parentCode": "847", + "name": "曲水县", + "en_name": "Qushui", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "853", + "parentCode": "555", + "name": "林芝", + "en_name": "LINZHI", + "deleted": false, + "sublist": [ + { + "code": "4934", + "parentCode": "853", + "name": "波密县", + "en_name": "Bomi", + "deleted": false, + "sublist": [] + }, + { + "code": "4935", + "parentCode": "853", + "name": "察隅县", + "en_name": "Chayu", + "deleted": false, + "sublist": [] + }, + { + "code": "4931", + "parentCode": "853", + "name": "工布江达县", + "en_name": "Gongbujiangda", + "deleted": false, + "sublist": [] + }, + { + "code": "4936", + "parentCode": "853", + "name": "朗县", + "en_name": "Lang", + "deleted": false, + "sublist": [] + }, + { + "code": "4930", + "parentCode": "853", + "name": "巴宜区", + "en_name": "Linzhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4932", + "parentCode": "853", + "name": "米林市", + "en_name": null, + "deleted": false, + "sublist": [] + }, + { + "code": "4933", + "parentCode": "853", + "name": "墨脱县", + "en_name": "Motuo", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "851", + "parentCode": "555", + "name": "那曲", + "en_name": "NAQU", + "deleted": false, + "sublist": [ + { + "code": "4916", + "parentCode": "851", + "name": "安多县", + "en_name": "Anduo", + "deleted": false, + "sublist": [] + }, + { + "code": "4919", + "parentCode": "851", + "name": "班戈县", + "en_name": "Bange", + "deleted": false, + "sublist": [] + }, + { + "code": "4920", + "parentCode": "851", + "name": "巴青县", + "en_name": "Baqing", + "deleted": false, + "sublist": [] + }, + { + "code": "4914", + "parentCode": "851", + "name": "比如县", + "en_name": "Biru", + "deleted": false, + "sublist": [] + }, + { + "code": "4913", + "parentCode": "851", + "name": "嘉黎县", + "en_name": "Jiali", + "deleted": false, + "sublist": [] + }, + { + "code": "4912", + "parentCode": "851", + "name": "色尼区", + "en_name": "Naqu", + "deleted": false, + "sublist": [] + }, + { + "code": "4915", + "parentCode": "851", + "name": "聂荣县", + "en_name": "Nierong", + "deleted": false, + "sublist": [] + }, + { + "code": "4921", + "parentCode": "851", + "name": "尼玛县", + "en_name": "Nima", + "deleted": false, + "sublist": [] + }, + { + "code": "4917", + "parentCode": "851", + "name": "申扎县", + "en_name": "Shenzha", + "deleted": false, + "sublist": [] + }, + { + "code": "4922", + "parentCode": "851", + "name": "双湖县", + "en_name": "Shuanghu", + "deleted": false, + "sublist": [] + }, + { + "code": "4918", + "parentCode": "851", + "name": "索县", + "en_name": "Suo", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "850", + "parentCode": "555", + "name": "日喀则", + "en_name": "RIKEZE", + "deleted": false, + "sublist": [ + { + "code": "4900", + "parentCode": "850", + "name": "昂仁县", + "en_name": "Angren", + "deleted": false, + "sublist": [] + }, + { + "code": "4902", + "parentCode": "850", + "name": "白朗县", + "en_name": "Bailang", + "deleted": false, + "sublist": [] + }, + { + "code": "4905", + "parentCode": "850", + "name": "定结县", + "en_name": "Dingjie", + "deleted": false, + "sublist": [] + }, + { + "code": "4897", + "parentCode": "850", + "name": "定日县", + "en_name": "Dingri", + "deleted": false, + "sublist": [] + }, + { + "code": "4911", + "parentCode": "850", + "name": "岗巴县", + "en_name": "Gangba", + "deleted": false, + "sublist": [] + }, + { + "code": "4896", + "parentCode": "850", + "name": "江孜县", + "en_name": "Jiangzi", + "deleted": false, + "sublist": [] + }, + { + "code": "4908", + "parentCode": "850", + "name": "吉隆县", + "en_name": "Jilong", + "deleted": false, + "sublist": [] + }, + { + "code": "4904", + "parentCode": "850", + "name": "康马县", + "en_name": "Kangma", + "deleted": false, + "sublist": [] + }, + { + "code": "4899", + "parentCode": "850", + "name": "拉孜县", + "en_name": "Lazi", + "deleted": false, + "sublist": [] + }, + { + "code": "4895", + "parentCode": "850", + "name": "南木林县", + "en_name": "Nanmulin", + "deleted": false, + "sublist": [] + }, + { + "code": "4909", + "parentCode": "850", + "name": "聂拉木县", + "en_name": "Nielamu", + "deleted": false, + "sublist": [] + }, + { + "code": "4903", + "parentCode": "850", + "name": "仁布县", + "en_name": "Renbu", + "deleted": false, + "sublist": [] + }, + { + "code": "4910", + "parentCode": "850", + "name": "萨嘎县", + "en_name": "Saga", + "deleted": false, + "sublist": [] + }, + { + "code": "4898", + "parentCode": "850", + "name": "萨迦县", + "en_name": "Sajia", + "deleted": false, + "sublist": [] + }, + { + "code": "4894", + "parentCode": "850", + "name": "桑珠孜区", + "en_name": "Sangzhuzi", + "deleted": false, + "sublist": [] + }, + { + "code": "4901", + "parentCode": "850", + "name": "谢通门县", + "en_name": "Xietongmen", + "deleted": false, + "sublist": [] + }, + { + "code": "4907", + "parentCode": "850", + "name": "亚东县", + "en_name": "Yadong", + "deleted": false, + "sublist": [] + }, + { + "code": "4906", + "parentCode": "850", + "name": "仲巴县", + "en_name": "Zhongba", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "849", + "parentCode": "555", + "name": "山南", + "en_name": "SHANNAN", + "deleted": false, + "sublist": [ + { + "code": "4888", + "parentCode": "849", + "name": "措美县", + "en_name": "Cuomei", + "deleted": false, + "sublist": [] + }, + { + "code": "4892", + "parentCode": "849", + "name": "错那市", + "en_name": "Cuona", + "deleted": false, + "sublist": [] + }, + { + "code": "4884", + "parentCode": "849", + "name": "贡嘎县", + "en_name": "Gongga", + "deleted": false, + "sublist": [] + }, + { + "code": "4890", + "parentCode": "849", + "name": "加查县", + "en_name": "Jiacha", + "deleted": false, + "sublist": [] + }, + { + "code": "4893", + "parentCode": "849", + "name": "浪卡子县", + "en_name": "Langkazi", + "deleted": false, + "sublist": [] + }, + { + "code": "4891", + "parentCode": "849", + "name": "隆子县", + "en_name": "Longzi", + "deleted": false, + "sublist": [] + }, + { + "code": "4889", + "parentCode": "849", + "name": "洛扎县", + "en_name": "Luozha", + "deleted": false, + "sublist": [] + }, + { + "code": "4882", + "parentCode": "849", + "name": "乃东区", + "en_name": "Naidong", + "deleted": false, + "sublist": [] + }, + { + "code": "4886", + "parentCode": "849", + "name": "琼结县", + "en_name": "Qiongjie", + "deleted": false, + "sublist": [] + }, + { + "code": "4887", + "parentCode": "849", + "name": "曲松县", + "en_name": "Qusong", + "deleted": false, + "sublist": [] + }, + { + "code": "4885", + "parentCode": "849", + "name": "桑日县", + "en_name": "Sangri", + "deleted": false, + "sublist": [] + }, + { + "code": "4883", + "parentCode": "849", + "name": "扎囊县", + "en_name": "Zhanang", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "554", + "parentCode": "489", + "name": "云南", + "en_name": "YUNNAN", + "deleted": false, + "sublist": [ + { + "code": "834", + "parentCode": "554", + "name": "保山", + "en_name": "BAOSHAN", + "deleted": false, + "sublist": [ + { + "code": "4779", + "parentCode": "834", + "name": "昌宁县", + "en_name": "Changning", + "deleted": false, + "sublist": [] + }, + { + "code": "4778", + "parentCode": "834", + "name": "龙陵县", + "en_name": "Longling", + "deleted": false, + "sublist": [] + }, + { + "code": "4775", + "parentCode": "834", + "name": "隆阳区", + "en_name": "Longyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4776", + "parentCode": "834", + "name": "施甸县", + "en_name": "Shidian", + "deleted": false, + "sublist": [] + }, + { + "code": "4777", + "parentCode": "834", + "name": "腾冲市", + "en_name": "Tengchong", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "836", + "parentCode": "554", + "name": "楚雄", + "en_name": "CHUXIONG", + "deleted": false, + "sublist": [ + { + "code": "4791", + "parentCode": "836", + "name": "楚雄市", + "en_name": "Chuxiong", + "deleted": false, + "sublist": [] + }, + { + "code": "4796", + "parentCode": "836", + "name": "大姚县", + "en_name": "Dayao", + "deleted": false, + "sublist": [] + }, + { + "code": "4800", + "parentCode": "836", + "name": "禄丰市", + "en_name": "lufengshi", + "deleted": false, + "sublist": [] + }, + { + "code": "4793", + "parentCode": "836", + "name": "牟定县", + "en_name": "Mouding", + "deleted": false, + "sublist": [] + }, + { + "code": "4794", + "parentCode": "836", + "name": "南华县", + "en_name": "Nanhua", + "deleted": false, + "sublist": [] + }, + { + "code": "4792", + "parentCode": "836", + "name": "双柏县", + "en_name": "Shuangbai", + "deleted": false, + "sublist": [] + }, + { + "code": "4799", + "parentCode": "836", + "name": "武定县", + "en_name": "Wuding", + "deleted": false, + "sublist": [] + }, + { + "code": "4795", + "parentCode": "836", + "name": "姚安县", + "en_name": "Yaoan", + "deleted": false, + "sublist": [] + }, + { + "code": "4797", + "parentCode": "836", + "name": "永仁县", + "en_name": "Yongren", + "deleted": false, + "sublist": [] + }, + { + "code": "4798", + "parentCode": "836", + "name": "元谋县", + "en_name": "Yuanmou", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "841", + "parentCode": "554", + "name": "大理", + "en_name": "DALI", + "deleted": false, + "sublist": [ + { + "code": "4829", + "parentCode": "841", + "name": "宾川县", + "en_name": "Binchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4826", + "parentCode": "841", + "name": "大理市", + "en_name": "Dali", + "deleted": false, + "sublist": [] + }, + { + "code": "4835", + "parentCode": "841", + "name": "洱源县", + "en_name": "Eryuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4837", + "parentCode": "841", + "name": "鹤庆县", + "en_name": "Heqing", + "deleted": false, + "sublist": [] + }, + { + "code": "4836", + "parentCode": "841", + "name": "剑川县", + "en_name": "Jianchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4830", + "parentCode": "841", + "name": "弥渡县", + "en_name": "Midu", + "deleted": false, + "sublist": [] + }, + { + "code": "4831", + "parentCode": "841", + "name": "南涧彝族自治县", + "en_name": "Nanjianyizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4832", + "parentCode": "841", + "name": "巍山彝族回族自治县", + "en_name": "Weishanyizuhuizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4828", + "parentCode": "841", + "name": "祥云县", + "en_name": "Xiangyun", + "deleted": false, + "sublist": [] + }, + { + "code": "4827", + "parentCode": "841", + "name": "漾濞彝族自治县", + "en_name": "Yangbiyizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4833", + "parentCode": "841", + "name": "永平县", + "en_name": "Yongping", + "deleted": false, + "sublist": [] + }, + { + "code": "4834", + "parentCode": "841", + "name": "云龙县", + "en_name": "Yunlong", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "842", + "parentCode": "554", + "name": "德宏", + "en_name": "DEHONG", + "deleted": false, + "sublist": [ + { + "code": "4840", + "parentCode": "842", + "name": "梁河县", + "en_name": "Lianghe", + "deleted": false, + "sublist": [] + }, + { + "code": "4842", + "parentCode": "842", + "name": "陇川县", + "en_name": "Longchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4839", + "parentCode": "842", + "name": "芒市", + "en_name": "Mang", + "deleted": false, + "sublist": [] + }, + { + "code": "4838", + "parentCode": "842", + "name": "瑞丽市", + "en_name": "Ruili", + "deleted": false, + "sublist": [] + }, + { + "code": "4841", + "parentCode": "842", + "name": "盈江县", + "en_name": "Yingjiang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "845", + "parentCode": "554", + "name": "迪庆", + "en_name": "DIQING", + "deleted": false, + "sublist": [ + { + "code": "4853", + "parentCode": "845", + "name": "德钦县", + "en_name": "Deqin", + "deleted": false, + "sublist": [] + }, + { + "code": "4854", + "parentCode": "845", + "name": "维西傈僳族自治县", + "en_name": "Weixilisuzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4852", + "parentCode": "845", + "name": "香格里拉市", + "en_name": "Xianggelila", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "837", + "parentCode": "554", + "name": "红河", + "en_name": "HONGHE", + "deleted": false, + "sublist": [ + { + "code": "4801", + "parentCode": "837", + "name": "个旧市", + "en_name": "Gejiu", + "deleted": false, + "sublist": [] + }, + { + "code": "4813", + "parentCode": "837", + "name": "河口瑶族自治县", + "en_name": "Hekouyaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4810", + "parentCode": "837", + "name": "红河县", + "en_name": "Honghe", + "deleted": false, + "sublist": [] + }, + { + "code": "4806", + "parentCode": "837", + "name": "建水县", + "en_name": "Jianshui", + "deleted": false, + "sublist": [] + }, + { + "code": "4811", + "parentCode": "837", + "name": "金平苗族瑶族傣族自治县", + "en_name": "Jinpingmiaozuyaozudaizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4802", + "parentCode": "837", + "name": "开远市", + "en_name": "Kaiyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4808", + "parentCode": "837", + "name": "泸西县", + "en_name": "Luxi", + "deleted": false, + "sublist": [] + }, + { + "code": "4812", + "parentCode": "837", + "name": "绿春县", + "en_name": "Lvchun", + "deleted": false, + "sublist": [] + }, + { + "code": "4803", + "parentCode": "837", + "name": "蒙自市", + "en_name": "Mengzi", + "deleted": false, + "sublist": [] + }, + { + "code": "4804", + "parentCode": "837", + "name": "弥勒市", + "en_name": "Mile", + "deleted": false, + "sublist": [] + }, + { + "code": "4805", + "parentCode": "837", + "name": "屏边苗族自治县", + "en_name": "Pingbianmiaozuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4807", + "parentCode": "837", + "name": "石屏县", + "en_name": "Shiping", + "deleted": false, + "sublist": [] + }, + { + "code": "4809", + "parentCode": "837", + "name": "元阳县", + "en_name": "Yuanyang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "831", + "parentCode": "554", + "name": "昆明", + "en_name": "KUNMING", + "deleted": false, + "sublist": [ + { + "code": "3272", + "parentCode": "831", + "name": "安宁市", + "en_name": "Anningshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3264", + "parentCode": "831", + "name": "呈贡区", + "en_name": "Chenggongqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3263", + "parentCode": "831", + "name": "东川区", + "en_name": "Dongchuanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3266", + "parentCode": "831", + "name": "富民县", + "en_name": "Fuminxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3261", + "parentCode": "831", + "name": "官渡区", + "en_name": "Guanduqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3265", + "parentCode": "831", + "name": "晋宁区", + "en_name": "jinningxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3270", + "parentCode": "831", + "name": "禄劝彝族苗族自治县", + "en_name": "Luquanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3260", + "parentCode": "831", + "name": "盘龙区", + "en_name": "Panlongqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3268", + "parentCode": "831", + "name": "石林彝族自治县", + "en_name": "Shilinyizuzizhixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3269", + "parentCode": "831", + "name": "嵩明县", + "en_name": "Songmingxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3259", + "parentCode": "831", + "name": "五华区", + "en_name": "Wuhuaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3262", + "parentCode": "831", + "name": "西山区", + "en_name": "Xishanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3271", + "parentCode": "831", + "name": "寻甸回族彝族自治县", + "en_name": "Xundianxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3267", + "parentCode": "831", + "name": "宜良县", + "en_name": "Yiliangxian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "843", + "parentCode": "554", + "name": "丽江", + "en_name": "LIJIANG", + "deleted": false, + "sublist": [ + { + "code": "4843", + "parentCode": "843", + "name": "古城区", + "en_name": "Gucheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4846", + "parentCode": "843", + "name": "华坪县", + "en_name": "Huaping", + "deleted": false, + "sublist": [] + }, + { + "code": "4847", + "parentCode": "843", + "name": "宁蒗彝族自治县", + "en_name": "Ninglangyizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4845", + "parentCode": "843", + "name": "永胜县", + "en_name": "Yongsheng", + "deleted": false, + "sublist": [] + }, + { + "code": "4844", + "parentCode": "843", + "name": "玉龙纳西族自治县", + "en_name": "Yulongnaxizuzizhi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "846", + "parentCode": "554", + "name": "临沧", + "en_name": "LINCANG", + "deleted": false, + "sublist": [ + { + "code": "4862", + "parentCode": "846", + "name": "沧源佤族自治县", + "en_name": "Cangyuanwazuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4856", + "parentCode": "846", + "name": "凤庆县", + "en_name": "Fengqing", + "deleted": false, + "sublist": [] + }, + { + "code": "4861", + "parentCode": "846", + "name": "耿马傣族佤族自治县", + "en_name": "Gengmadaizuwazuzhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4855", + "parentCode": "846", + "name": "临翔区", + "en_name": "Linxiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4860", + "parentCode": "846", + "name": "双江拉祜族佤族布朗族傣族自治县", + "en_name": "Shuangjianglahuzuwazubulangzudaizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4858", + "parentCode": "846", + "name": "永德县", + "en_name": "Yongde", + "deleted": false, + "sublist": [] + }, + { + "code": "4857", + "parentCode": "846", + "name": "云县", + "en_name": "Yun", + "deleted": false, + "sublist": [] + }, + { + "code": "4859", + "parentCode": "846", + "name": "镇康县", + "en_name": "Zhenkang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "844", + "parentCode": "554", + "name": "怒江", + "en_name": "NUJIANG", + "deleted": false, + "sublist": [ + { + "code": "4849", + "parentCode": "844", + "name": "福贡县", + "en_name": "Fugong", + "deleted": false, + "sublist": [] + }, + { + "code": "4850", + "parentCode": "844", + "name": "贡山独龙族怒族自治县", + "en_name": "Gongshandulongzunuzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4851", + "parentCode": "844", + "name": "兰坪白族普米族自治县", + "en_name": "Langpingbaizupumizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4848", + "parentCode": "844", + "name": "泸水市", + "en_name": "Lushui", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "10163", + "parentCode": "554", + "name": "普洱", + "en_name": "PUER", + "deleted": false, + "sublist": [ + { + "code": "5318", + "parentCode": "10163", + "name": "江城哈尼族彝族自治县", + "en_name": "Jiangchenghanizuyizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5315", + "parentCode": "10163", + "name": "景东彝族自治县", + "en_name": "Jingdongyizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5316", + "parentCode": "10163", + "name": "景谷傣族彝族自治县", + "en_name": "Jingguyizudaizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5320", + "parentCode": "10163", + "name": "澜沧拉祜族自治县", + "en_name": "Lancanglahuzuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5319", + "parentCode": "10163", + "name": "孟连傣族拉祜族佤族自治县", + "en_name": "Mengliandaizulahuzuwazuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5314", + "parentCode": "10163", + "name": "墨江哈尼族自治县", + "en_name": "Mojianghanizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5313", + "parentCode": "10163", + "name": "宁洱哈尼族彝族自治县", + "en_name": "Ningerhanizuyizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "3402", + "parentCode": "10163", + "name": "思茅区", + "en_name": "Simao", + "deleted": false, + "sublist": [] + }, + { + "code": "5321", + "parentCode": "10163", + "name": "西盟佤族自治县", + "en_name": "Ximengwazuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "5317", + "parentCode": "10163", + "name": "镇沅彝族哈尼族拉祜族自治县", + "en_name": "Zhenyuanyizuhanizulahuzuzizhi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "832", + "parentCode": "554", + "name": "曲靖", + "en_name": "QUJING", + "deleted": false, + "sublist": [ + { + "code": "4763", + "parentCode": "832", + "name": "富源县", + "en_name": "Fuyuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4764", + "parentCode": "832", + "name": "会泽县", + "en_name": "Huize", + "deleted": false, + "sublist": [] + }, + { + "code": "4760", + "parentCode": "832", + "name": "陆良县", + "en_name": "Luliang", + "deleted": false, + "sublist": [] + }, + { + "code": "4762", + "parentCode": "832", + "name": "罗平县", + "en_name": "Luoping", + "deleted": false, + "sublist": [] + }, + { + "code": "4759", + "parentCode": "832", + "name": "马龙区", + "en_name": "Malong", + "deleted": false, + "sublist": [] + }, + { + "code": "4757", + "parentCode": "832", + "name": "麒麟区", + "en_name": "Qilin", + "deleted": false, + "sublist": [] + }, + { + "code": "4761", + "parentCode": "832", + "name": "师宗县", + "en_name": "Shizong", + "deleted": false, + "sublist": [] + }, + { + "code": "4758", + "parentCode": "832", + "name": "宣威市", + "en_name": "Xuanwei", + "deleted": false, + "sublist": [] + }, + { + "code": "4765", + "parentCode": "832", + "name": "沾益区", + "en_name": "Zhanyi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "838", + "parentCode": "554", + "name": "文山", + "en_name": "WENSHAN", + "deleted": false, + "sublist": [ + { + "code": "4821", + "parentCode": "838", + "name": "富宁县", + "en_name": "Funing", + "deleted": false, + "sublist": [] + }, + { + "code": "4820", + "parentCode": "838", + "name": "广南县", + "en_name": "Guangnan", + "deleted": false, + "sublist": [] + }, + { + "code": "4818", + "parentCode": "838", + "name": "马关县", + "en_name": "Maguan", + "deleted": false, + "sublist": [] + }, + { + "code": "4817", + "parentCode": "838", + "name": "麻栗坡县", + "en_name": "Malipo", + "deleted": false, + "sublist": [] + }, + { + "code": "4819", + "parentCode": "838", + "name": "丘北县", + "en_name": "Qiubei", + "deleted": false, + "sublist": [] + }, + { + "code": "4814", + "parentCode": "838", + "name": "文山市", + "en_name": "Wenshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4816", + "parentCode": "838", + "name": "西畴县", + "en_name": "Xichou", + "deleted": false, + "sublist": [] + }, + { + "code": "4815", + "parentCode": "838", + "name": "砚山县", + "en_name": "Yanshan", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "840", + "parentCode": "554", + "name": "西双版纳", + "en_name": "XISHUANGBANNA", + "deleted": false, + "sublist": [ + { + "code": "4823", + "parentCode": "840", + "name": "景洪市", + "en_name": "Jinghong", + "deleted": false, + "sublist": [] + }, + { + "code": "4824", + "parentCode": "840", + "name": "勐海县", + "en_name": "Menghai", + "deleted": false, + "sublist": [] + }, + { + "code": "4825", + "parentCode": "840", + "name": "勐腊县", + "en_name": "Mengla", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "833", + "parentCode": "554", + "name": "玉溪", + "en_name": "YUXI", + "deleted": false, + "sublist": [ + { + "code": "4768", + "parentCode": "833", + "name": "澄江市", + "en_name": "Chengjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4772", + "parentCode": "833", + "name": "峨山彝族自治县", + "en_name": "Eshanyizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4766", + "parentCode": "833", + "name": "红塔区", + "en_name": "Hongta", + "deleted": false, + "sublist": [] + }, + { + "code": "4770", + "parentCode": "833", + "name": "华宁县", + "en_name": "Huaning", + "deleted": false, + "sublist": [] + }, + { + "code": "4767", + "parentCode": "833", + "name": "江川区", + "en_name": "Jiangchuan", + "deleted": false, + "sublist": [] + }, + { + "code": "4769", + "parentCode": "833", + "name": "通海县", + "en_name": "Tonghai", + "deleted": false, + "sublist": [] + }, + { + "code": "4773", + "parentCode": "833", + "name": "新平彝族傣族自治县", + "en_name": "Xinpingyizudaizuzizhi", + "deleted": false, + "sublist": [] + }, + { + "code": "4771", + "parentCode": "833", + "name": "易门县", + "en_name": "Yimen", + "deleted": false, + "sublist": [] + }, + { + "code": "4774", + "parentCode": "833", + "name": "元江哈尼族彝族傣族自治县", + "en_name": "Yuanjianghanizuyizudaizuzizhi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "835", + "parentCode": "554", + "name": "昭通", + "en_name": "ZHAOTONG", + "deleted": false, + "sublist": [ + { + "code": "4784", + "parentCode": "835", + "name": "大关县", + "en_name": "Daguan", + "deleted": false, + "sublist": [] + }, + { + "code": "4781", + "parentCode": "835", + "name": "鲁甸县", + "en_name": "Ludian", + "deleted": false, + "sublist": [] + }, + { + "code": "4782", + "parentCode": "835", + "name": "巧家县", + "en_name": "Qiaojia", + "deleted": false, + "sublist": [] + }, + { + "code": "4790", + "parentCode": "835", + "name": "水富市", + "en_name": "Shuifu", + "deleted": false, + "sublist": [] + }, + { + "code": "4786", + "parentCode": "835", + "name": "绥江县", + "en_name": "Suijiang", + "deleted": false, + "sublist": [] + }, + { + "code": "4789", + "parentCode": "835", + "name": "威信县", + "en_name": "Weixin", + "deleted": false, + "sublist": [] + }, + { + "code": "4783", + "parentCode": "835", + "name": "盐津县", + "en_name": "Yanjin", + "deleted": false, + "sublist": [] + }, + { + "code": "4788", + "parentCode": "835", + "name": "彝良县", + "en_name": "Yiliang", + "deleted": false, + "sublist": [] + }, + { + "code": "4785", + "parentCode": "835", + "name": "永善县", + "en_name": "Yongshan", + "deleted": false, + "sublist": [] + }, + { + "code": "4780", + "parentCode": "835", + "name": "昭阳区", + "en_name": "Zhaoyang", + "deleted": false, + "sublist": [] + }, + { + "code": "4787", + "parentCode": "835", + "name": "镇雄县", + "en_name": "Zhenxiong", + "deleted": false, + "sublist": [] + } + ] + } + ] + }, + { + "code": "540", + "parentCode": "489", + "name": "浙江", + "en_name": "ZHEJIANG", + "deleted": false, + "sublist": [ + { + "code": "653", + "parentCode": "540", + "name": "杭州", + "en_name": "HANGZHOU", + "deleted": false, + "sublist": [ + { + "code": "2238", + "parentCode": "653", + "name": "滨江区", + "en_name": "Binjiang", + "deleted": false, + "sublist": [] + }, + { + "code": "2242", + "parentCode": "653", + "name": "淳安县", + "en_name": "Chunan", + "deleted": false, + "sublist": [] + }, + { + "code": "2478", + "parentCode": "653", + "name": "富阳区", + "en_name": "Fuyang", + "deleted": false, + "sublist": [] + }, + { + "code": "2236", + "parentCode": "653", + "name": "拱墅区", + "en_name": "Gongshu", + "deleted": false, + "sublist": [] + }, + { + "code": "2409", + "parentCode": "653", + "name": "建德市", + "en_name": "Jiande", + "deleted": false, + "sublist": [] + }, + { + "code": "2235", + "parentCode": "653", + "name": "江干区", + "en_name": "Jianggan", + "deleted": false, + "sublist": [] + }, + { + "code": "2479", + "parentCode": "653", + "name": "临安区", + "en_name": "Linan", + "deleted": false, + "sublist": [] + }, + { + "code": "2233", + "parentCode": "653", + "name": "上城区", + "en_name": "Shangcheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2241", + "parentCode": "653", + "name": "桐庐县", + "en_name": "Tonglu", + "deleted": false, + "sublist": [] + }, + { + "code": "2234", + "parentCode": "653", + "name": "下城区", + "en_name": "Xiacheng", + "deleted": false, + "sublist": [] + }, + { + "code": "2239", + "parentCode": "653", + "name": "萧山区", + "en_name": "Xiaoshan", + "deleted": false, + "sublist": [] + }, + { + "code": "2457", + "parentCode": "653", + "name": "下沙", + "en_name": "Xiasha", + "deleted": false, + "sublist": [] + }, + { + "code": "2237", + "parentCode": "653", + "name": "西湖区", + "en_name": "Xihu", + "deleted": false, + "sublist": [] + }, + { + "code": "2240", + "parentCode": "653", + "name": "余杭区", + "en_name": "Yuhang", + "deleted": false, + "sublist": [] + }, + { + "code": "104041", + "parentCode": "653", + "name": "钱塘区", + "en_name": "qiantangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "104042", + "parentCode": "653", + "name": "临平区", + "en_name": "linpingqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "657", + "parentCode": "540", + "name": "湖州", + "en_name": "HUZHOU", + "deleted": false, + "sublist": [ + { + "code": "3134", + "parentCode": "657", + "name": "安吉县", + "en_name": "Anjixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3133", + "parentCode": "657", + "name": "长兴县", + "en_name": "Changxingxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3388", + "parentCode": "657", + "name": "德清县", + "en_name": "Deqing", + "deleted": false, + "sublist": [] + }, + { + "code": "3132", + "parentCode": "657", + "name": "南浔区", + "en_name": "Nanxunqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3131", + "parentCode": "657", + "name": "吴兴区", + "en_name": "Wuxingqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "656", + "parentCode": "540", + "name": "嘉兴", + "en_name": "JIAXING", + "deleted": false, + "sublist": [ + { + "code": "3382", + "parentCode": "656", + "name": "海宁市", + "en_name": "Haining", + "deleted": false, + "sublist": [] + }, + { + "code": "3123", + "parentCode": "656", + "name": "海盐县", + "en_name": "Haiyanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3380", + "parentCode": "656", + "name": "嘉善县", + "en_name": "Jiashanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3121", + "parentCode": "656", + "name": "南湖区", + "en_name": "Nanhuqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3379", + "parentCode": "656", + "name": "平湖市", + "en_name": "Pinghu", + "deleted": false, + "sublist": [] + }, + { + "code": "3381", + "parentCode": "656", + "name": "桐乡市", + "en_name": "Tongxiangshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3122", + "parentCode": "656", + "name": "秀洲区", + "en_name": "Xiuzhouqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "659", + "parentCode": "540", + "name": "金华", + "en_name": "JINHUA", + "deleted": false, + "sublist": [ + { + "code": "3377", + "parentCode": "659", + "name": "东阳市", + "en_name": "Dongyangshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3116", + "parentCode": "659", + "name": "金东区", + "en_name": "Jindongqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3120", + "parentCode": "659", + "name": "兰溪市", + "en_name": "Lanxishi", + "deleted": false, + "sublist": [] + }, + { + "code": "3119", + "parentCode": "659", + "name": "磐安县", + "en_name": "Pananxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3118", + "parentCode": "659", + "name": "浦江县", + "en_name": "Pujiangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3115", + "parentCode": "659", + "name": "婺城区", + "en_name": "Wuchengqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3117", + "parentCode": "659", + "name": "武义县", + "en_name": "Wuyixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3376", + "parentCode": "659", + "name": "义乌市", + "en_name": "Yiwushi", + "deleted": false, + "sublist": [] + }, + { + "code": "3378", + "parentCode": "659", + "name": "永康市", + "en_name": "Yongkangshi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "663", + "parentCode": "540", + "name": "丽水", + "en_name": "LISHUI", + "deleted": false, + "sublist": [ + { + "code": "3142", + "parentCode": "663", + "name": "景宁畲族自治县", + "en_name": "Jingningxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3137", + "parentCode": "663", + "name": "缙云县", + "en_name": "Jinyunxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3135", + "parentCode": "663", + "name": "莲都区", + "en_name": "Lianduqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3389", + "parentCode": "663", + "name": "龙泉市", + "en_name": "longquan", + "deleted": false, + "sublist": [] + }, + { + "code": "3136", + "parentCode": "663", + "name": "青田县", + "en_name": "Qingtianxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3141", + "parentCode": "663", + "name": "庆元县", + "en_name": "Qingyuanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3139", + "parentCode": "663", + "name": "松阳县", + "en_name": "Songyangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3138", + "parentCode": "663", + "name": "遂昌县", + "en_name": "Suichangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3140", + "parentCode": "663", + "name": "云和县", + "en_name": "Yunhexian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "654", + "parentCode": "540", + "name": "宁波", + "en_name": "NINGBO", + "deleted": false, + "sublist": [ + { + "code": "3006", + "parentCode": "654", + "name": "北仑区", + "en_name": "Beilunqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3370", + "parentCode": "654", + "name": "慈溪市", + "en_name": "Cixishi", + "deleted": false, + "sublist": [] + }, + { + "code": "3001", + "parentCode": "654", + "name": "奉化区", + "en_name": "Fenghuaqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3008", + "parentCode": "654", + "name": "高新区", + "en_name": "Gaoxinqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3003", + "parentCode": "654", + "name": "海曙区", + "en_name": "Haishuqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3005", + "parentCode": "654", + "name": "江北区", + "en_name": "Jiangbeiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3372", + "parentCode": "654", + "name": "宁海县", + "en_name": "Ninghaixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3373", + "parentCode": "654", + "name": "象山县", + "en_name": "Xiangshanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3002", + "parentCode": "654", + "name": "鄞州区", + "en_name": "Yinzhouqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3371", + "parentCode": "654", + "name": "余姚市", + "en_name": "Yuyaoshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3007", + "parentCode": "654", + "name": "镇海区", + "en_name": "Zhenhaiqu", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "660", + "parentCode": "540", + "name": "衢州", + "en_name": "QUZHOU", + "deleted": false, + "sublist": [ + { + "code": "3521", + "parentCode": "660", + "name": "常山县", + "en_name": "Changshan", + "deleted": false, + "sublist": [] + }, + { + "code": "3520", + "parentCode": "660", + "name": "江山市", + "en_name": "Jiangshan", + "deleted": false, + "sublist": [] + }, + { + "code": "3522", + "parentCode": "660", + "name": "开化县", + "en_name": "Kaihua", + "deleted": false, + "sublist": [] + }, + { + "code": "3518", + "parentCode": "660", + "name": "柯城区", + "en_name": "Kecheng", + "deleted": false, + "sublist": [] + }, + { + "code": "3523", + "parentCode": "660", + "name": "龙游县", + "en_name": "Longyou", + "deleted": false, + "sublist": [] + }, + { + "code": "3519", + "parentCode": "660", + "name": "衢江区", + "en_name": "Qujiang", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "658", + "parentCode": "540", + "name": "绍兴", + "en_name": "SHAOXING", + "deleted": false, + "sublist": [ + { + "code": "3104", + "parentCode": "658", + "name": "柯桥区", + "en_name": "Keqiaoqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3386", + "parentCode": "658", + "name": "上虞区", + "en_name": "Shangyu", + "deleted": false, + "sublist": [] + }, + { + "code": "3106", + "parentCode": "658", + "name": "嵊州市", + "en_name": "Shengzhoushi", + "deleted": false, + "sublist": [] + }, + { + "code": "3105", + "parentCode": "658", + "name": "新昌县", + "en_name": "Xinchangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3103", + "parentCode": "658", + "name": "越城区", + "en_name": "Yuechengqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3387", + "parentCode": "658", + "name": "诸暨市", + "en_name": "Zhujishi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "662", + "parentCode": "540", + "name": "台州", + "en_name": "TAIZHOU", + "deleted": false, + "sublist": [ + { + "code": "3125", + "parentCode": "662", + "name": "黄岩区", + "en_name": "Huangyanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3124", + "parentCode": "662", + "name": "椒江区", + "en_name": "Jiaojiangqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3130", + "parentCode": "662", + "name": "临海市", + "en_name": "Linhaishi", + "deleted": false, + "sublist": [] + }, + { + "code": "3126", + "parentCode": "662", + "name": "路桥区", + "en_name": "Luqiaoqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3375", + "parentCode": "662", + "name": "三门县", + "en_name": "Sanmenxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3127", + "parentCode": "662", + "name": "天台县", + "en_name": "Tiantaixian", + "deleted": false, + "sublist": [] + }, + { + "code": "3129", + "parentCode": "662", + "name": "温岭市", + "en_name": "Wenlingshi", + "deleted": false, + "sublist": [] + }, + { + "code": "3128", + "parentCode": "662", + "name": "仙居县", + "en_name": "Xianjuxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3374", + "parentCode": "662", + "name": "玉环市", + "en_name": "Yuhuanxian", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "655", + "parentCode": "540", + "name": "温州", + "en_name": "WENZHOU", + "deleted": false, + "sublist": [ + { + "code": "3112", + "parentCode": "655", + "name": "苍南县", + "en_name": "Cangnanxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3110", + "parentCode": "655", + "name": "洞头区", + "en_name": "Dongtouqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3108", + "parentCode": "655", + "name": "龙湾区", + "en_name": "Longwanqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3107", + "parentCode": "655", + "name": "鹿城区", + "en_name": "Luchengqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3109", + "parentCode": "655", + "name": "瓯海区", + "en_name": "Ouhaiqu", + "deleted": false, + "sublist": [] + }, + { + "code": "3111", + "parentCode": "655", + "name": "平阳县", + "en_name": "Pingyangxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3385", + "parentCode": "655", + "name": "瑞安市", + "en_name": "Ruian", + "deleted": false, + "sublist": [] + }, + { + "code": "3114", + "parentCode": "655", + "name": "泰顺县", + "en_name": "Taishunxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3113", + "parentCode": "655", + "name": "文成县", + "en_name": "Wenchengxian", + "deleted": false, + "sublist": [] + }, + { + "code": "3384", + "parentCode": "655", + "name": "永嘉县", + "en_name": "Yongjia", + "deleted": false, + "sublist": [] + }, + { + "code": "3383", + "parentCode": "655", + "name": "乐清市", + "en_name": "Yueqing", + "deleted": false, + "sublist": [] + }, + { + "code": "104039", + "parentCode": "655", + "name": "龙港市", + "en_name": "longgangshi", + "deleted": false, + "sublist": [] + } + ] + }, + { + "code": "661", + "parentCode": "540", + "name": "舟山", + "en_name": "ZHOUSHAN", + "deleted": false, + "sublist": [ + { + "code": "3526", + "parentCode": "661", + "name": "岱山县", + "en_name": "Daishan", + "deleted": false, + "sublist": [] + }, + { + "code": "3524", + "parentCode": "661", + "name": "定海区", + "en_name": "Dinghai", + "deleted": false, + "sublist": [] + }, + { + "code": "3525", + "parentCode": "661", + "name": "普陀区", + "en_name": "Putuo", + "deleted": false, + "sublist": [] + }, + { + "code": "3527", + "parentCode": "661", + "name": "嵊泗县", + "en_name": "Shengsi", + "deleted": false, + "sublist": [] + } + ] + } + ] + } + ], + "educationType": [ + { + "code": "-1", + "parentCode": null, + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "9", + "parentCode": null, + "name": "初中及以下", + "en_name": "junior high", + "deleted": false, + "sublist": [] + }, + { + "code": "7", + "parentCode": null, + "name": "高中", + "en_name": " Senior High", + "deleted": false, + "sublist": [] + }, + { + "code": "12", + "parentCode": null, + "name": "中专/中技", + "en_name": "Secondary Specialized/Skilled Workers Training", + "deleted": false, + "sublist": [] + }, + { + "code": "5", + "parentCode": null, + "name": "大专", + "en_name": "Associate", + "deleted": false, + "sublist": [] + }, + { + "code": "4", + "parentCode": null, + "name": "本科", + "en_name": "Bachelor", + "deleted": false, + "sublist": [] + }, + { + "code": "3", + "parentCode": null, + "name": "硕士", + "en_name": "Master", + "deleted": false, + "sublist": [] + }, + { + "code": "10", + "parentCode": null, + "name": "MBA/EMBA", + "en_name": "MBA/EMBA", + "deleted": false, + "sublist": [] + }, + { + "code": "1", + "parentCode": null, + "name": "博士", + "en_name": "Doctor", + "deleted": false, + "sublist": [] + } + ], + "workExpType": [ + { + "code": "-1", + "parentCode": null, + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "0000", + "parentCode": null, + "name": "无经验", + "en_name": "No experience", + "deleted": false, + "sublist": [] + }, + { + "code": "0001", + "parentCode": null, + "name": "1年以下", + "en_name": "Below 1 Year", + "deleted": false, + "sublist": [] + }, + { + "code": "0103", + "parentCode": null, + "name": "1-3年", + "en_name": "1-3 Year", + "deleted": false, + "sublist": [] + }, + { + "code": "0305", + "parentCode": null, + "name": "3-5年", + "en_name": "3-5 Year", + "deleted": false, + "sublist": [] + }, + { + "code": "0510", + "parentCode": null, + "name": "5-10年", + "en_name": "5-10 Year", + "deleted": false, + "sublist": [] + }, + { + "code": "1099", + "parentCode": null, + "name": "10年以上", + "en_name": "Above 10 Year", + "deleted": false, + "sublist": [] + } + ], + "jobStatus": [ + { + "code": "-1", + "parentCode": null, + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "2", + "parentCode": null, + "name": "全职", + "en_name": "Full-time", + "deleted": false, + "sublist": [] + }, + { + "code": "1", + "parentCode": null, + "name": "兼职/临时", + "en_name": "Part-time", + "deleted": false, + "sublist": [] + }, + { + "code": "4", + "parentCode": null, + "name": "实习", + "en_name": "Intern", + "deleted": false, + "sublist": [] + }, + { + "code": "5", + "parentCode": null, + "name": "校园", + "en_name": "Campus", + "deleted": false, + "sublist": [] + } + ], + "companySize": [ + { + "code": "-1", + "parentCode": null, + "name": "不限", + "en_name": "", + "deleted": false, + "sublist": [] + }, + { + "code": "1", + "parentCode": null, + "name": "20人以下", + "en_name": "Less than 20", + "deleted": false, + "sublist": [] + }, + { + "code": "2", + "parentCode": null, + "name": "20-99人", + "en_name": "20-99", + "deleted": false, + "sublist": [] + }, + { + "code": "3", + "parentCode": null, + "name": "100-299人", + "en_name": "100-299", + "deleted": false, + "sublist": [] + }, + { + "code": "8", + "parentCode": null, + "name": "300-499人", + "en_name": "300-499", + "deleted": false, + "sublist": [] + }, + { + "code": "4", + "parentCode": null, + "name": "500-999人", + "en_name": "500-999", + "deleted": false, + "sublist": [] + }, + { + "code": "5", + "parentCode": null, + "name": "1000-9999人", + "en_name": "1000-9999", + "deleted": false, + "sublist": [] + }, + { + "code": "6", + "parentCode": null, + "name": "10000人以上", + "en_name": "More than 10000", + "deleted": false, + "sublist": [] + }, + { + "code": "7", + "parentCode": null, + "name": "", + "en_name": "Confidential", + "deleted": false, + "sublist": [] + } + ] + } +} \ No newline at end of file diff --git a/src/main/resources/images/AiSayHi.png b/src/main/resources/images/AiSayHi.png deleted file mode 100644 index c28f9cce..00000000 Binary files a/src/main/resources/images/AiSayHi.png and /dev/null differ diff --git a/src/main/resources/images/aliPay.jpg b/src/main/resources/images/aliPay.jpg deleted file mode 100644 index a532382e..00000000 Binary files a/src/main/resources/images/aliPay.jpg and /dev/null differ diff --git a/src/main/resources/images/boss.png b/src/main/resources/images/boss.png deleted file mode 100644 index a969f0ef..00000000 Binary files a/src/main/resources/images/boss.png and /dev/null differ diff --git a/src/main/resources/images/chatgpt&boss.png b/src/main/resources/images/chatgpt&boss.png deleted file mode 100644 index 84a8724c..00000000 Binary files a/src/main/resources/images/chatgpt&boss.png and /dev/null differ diff --git a/src/main/resources/images/companyWechat.png b/src/main/resources/images/companyWechat.png deleted file mode 100644 index d2cb936c..00000000 Binary files a/src/main/resources/images/companyWechat.png and /dev/null differ diff --git a/src/main/resources/images/driver.png b/src/main/resources/images/driver.png deleted file mode 100644 index 88b3d1a1..00000000 Binary files a/src/main/resources/images/driver.png and /dev/null differ diff --git a/src/main/resources/images/getCity.png b/src/main/resources/images/getCity.png deleted file mode 100644 index e9270e12..00000000 Binary files a/src/main/resources/images/getCity.png and /dev/null differ diff --git a/src/main/resources/images/img.png b/src/main/resources/images/img.png new file mode 100644 index 00000000..9b95b261 Binary files /dev/null and b/src/main/resources/images/img.png differ diff --git a/src/main/resources/images/img2.png b/src/main/resources/images/img2.png new file mode 100644 index 00000000..868f7dcf Binary files /dev/null and b/src/main/resources/images/img2.png differ diff --git a/src/main/resources/images/jdk17.png b/src/main/resources/images/jdk17.png deleted file mode 100644 index 188da07b..00000000 Binary files a/src/main/resources/images/jdk17.png and /dev/null differ diff --git a/src/main/resources/images/liepin.png b/src/main/resources/images/liepin.png deleted file mode 100644 index 3f4f2bc6..00000000 Binary files a/src/main/resources/images/liepin.png and /dev/null differ diff --git a/src/main/resources/images/maven.png b/src/main/resources/images/maven.png deleted file mode 100644 index 5e6c5cbd..00000000 Binary files a/src/main/resources/images/maven.png and /dev/null differ diff --git a/src/main/resources/images/myh3.png b/src/main/resources/images/myh3.png deleted file mode 100644 index 77dbe94d..00000000 Binary files a/src/main/resources/images/myh3.png and /dev/null differ diff --git a/src/main/resources/images/pian1.png b/src/main/resources/images/pian1.png deleted file mode 100644 index fddebfe8..00000000 Binary files a/src/main/resources/images/pian1.png and /dev/null differ diff --git a/src/main/resources/images/pian2.png b/src/main/resources/images/pian2.png deleted file mode 100644 index 0e27fdea..00000000 Binary files a/src/main/resources/images/pian2.png and /dev/null differ diff --git a/src/main/resources/images/pian3.png b/src/main/resources/images/pian3.png deleted file mode 100644 index 739496a6..00000000 Binary files a/src/main/resources/images/pian3.png and /dev/null differ diff --git a/src/main/resources/images/pian4.png b/src/main/resources/images/pian4.png deleted file mode 100644 index a54d78f8..00000000 Binary files a/src/main/resources/images/pian4.png and /dev/null differ diff --git a/src/main/resources/images/qq.jpg b/src/main/resources/images/qq.jpg deleted file mode 100644 index e1966231..00000000 Binary files a/src/main/resources/images/qq.jpg and /dev/null differ diff --git a/src/main/resources/images/run.png b/src/main/resources/images/run.png deleted file mode 100644 index 9c02584c..00000000 Binary files a/src/main/resources/images/run.png and /dev/null differ diff --git a/src/main/resources/images/run1.png b/src/main/resources/images/run1.png deleted file mode 100644 index bb59f4ff..00000000 Binary files a/src/main/resources/images/run1.png and /dev/null differ diff --git a/src/main/resources/images/temp/tmp.txt b/src/main/resources/images/temp/tmp.txt deleted file mode 100644 index f179d142..00000000 --- a/src/main/resources/images/temp/tmp.txt +++ /dev/null @@ -1,9 +0,0 @@ -### 本项目黑名单: - -- QQ:813851861,昵称:the one day 马永豪,山东枣庄 - -
    - myh1 - myh2 - myh3 -
    \ No newline at end of file diff --git a/src/main/resources/images/wechatPay.jpg b/src/main/resources/images/wechatPay.jpg deleted file mode 100644 index 09a3e486..00000000 Binary files a/src/main/resources/images/wechatPay.jpg and /dev/null differ diff --git a/src/main/resources/images/wgroup.jpg b/src/main/resources/images/wgroup.jpg deleted file mode 100644 index 9cc0734a..00000000 Binary files a/src/main/resources/images/wgroup.jpg and /dev/null differ diff --git "a/src/main/resources/images/\351\252\227\345\255\220\347\275\221\347\253\231.png" "b/src/main/resources/images/\351\252\227\345\255\220\347\275\221\347\253\231.png" deleted file mode 100644 index 12701693..00000000 Binary files "a/src/main/resources/images/\351\252\227\345\255\220\347\275\221\347\253\231.png" and /dev/null differ diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..bd1e560e --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + + + + + + + + + + + + + ${LOG_HOME}/${SERVER_NAME}-%d{yyyy-MM-dd}-%i.log + + 30 + 50MB + 1GB + + + ${CONSOLE_LOG_PATTERN} + + + + + + + 0 + + 512 + + + + + + + + ${CONSOLE_LOG_PATTERN} + + ${ACCESS_LOG_FILE} + + ${ACCESS_LOG_FILE}_%d{yyyy-MM-dd} + 100 + 1GB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${LOG_HOME}/${SERVER_NAME}-%d{yyyy-MM-dd}-%i.log + + 30 + 50MB + 20GB + + + ${CONSOLE_LOG_PATTERN} + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml deleted file mode 100644 index 187c85c8..00000000 --- a/src/main/resources/logback.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - UTF-8 - ${log.pattern} - - - - - - UTF-8 - ${log.pattern} - - - ${log.file} - 30 - - - true - - - - - - - diff --git a/src/main/resources/prompts/ greeting-v1.yml b/src/main/resources/prompts/ greeting-v1.yml new file mode 100644 index 00000000..fc2fab8c --- /dev/null +++ b/src/main/resources/prompts/ greeting-v1.yml @@ -0,0 +1,15 @@ +id: "greeting-v1" +description: "JD→一句打招呼:基础版" +segments: + - type: SYSTEM + content: | + 你是资深招聘文案生成器。目标:基于候选人资料与JD,生成一条中文打招呼句,≤{{max_chars}}字,语气{{tone}};覆盖“核心经验/量化成果/与JD贴合点{{#show_weakness}}/1个可控改进点及改进行动{{/show_weakness}}”。禁止虚构;技术名词≤3个。 + - type: GUIDELINES + content: | + 规则:1) 融入≥2个JD关键词;2) 避免空话和堆栈;3) 无数据用“动作+场景”;4) 仅输出一句中文。 + - type: USER + content: | + 【候选人资料】{{{profile_json}}} + 【JD原文】{{{jd_text}}} + 【JD关键词(可能为空)】{{{jd_keywords}}} + 请在≤{{max_chars}}字内生成一句话。 diff --git a/src/main/resources/settings.xml b/src/main/resources/settings.xml deleted file mode 100644 index 0f6e9064..00000000 --- a/src/main/resources/settings.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - H:\Maven3.6.2\repository - - - - - - - - aliyunmaven - central - 阿里云公共仓库 - https://maven.aliyun.com/repository/central - - - repo1 - central - central repo - http://repo1.maven.org/maven2/ - - - aliyunmaven - apache snapshots - 阿里云阿帕奇仓库 - https://maven.aliyun.com/repository/apache-snapshots - - - - - - - - - aliyunmaven - aliyunmaven - https://maven.aliyun.com/repository/public - default - - true - - - true - - - - MavenCentral - http://repo1.maven.org/maven2/ - - - aliyunmavenApache - https://maven.aliyun.com/repository/apache-snapshots - - - - - - \ No newline at end of file diff --git a/src/main/resources/sql/user_profile.sql b/src/main/resources/sql/user_profile.sql new file mode 100644 index 00000000..1c1d09f7 --- /dev/null +++ b/src/main/resources/sql/user_profile.sql @@ -0,0 +1,21 @@ +-- 用户求职信息表 +CREATE TABLE IF NOT EXISTS user_profile ( + id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', + role VARCHAR(100) NOT NULL COMMENT '职位角色', + years INT NOT NULL COMMENT '工作年限', + domains TEXT COMMENT '领域列表(JSON格式)', + core_stack TEXT COMMENT '核心技术栈列表(JSON格式)', + scale TEXT COMMENT '规模指标(JSON格式,包含qps_peak、sla等)', + achievements TEXT COMMENT '成就列表(JSON格式)', + strengths TEXT COMMENT '优势列表(JSON格式)', + improvements TEXT COMMENT '改进项列表(JSON格式)', + availability VARCHAR(50) COMMENT '到岗时间', + links TEXT COMMENT '链接信息(JSON格式,包含github、portfolio等)', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at DATETIME COMMENT '更新时间', + is_deleted BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否删除', + remark VARCHAR(500) COMMENT '备注', + INDEX idx_role (role), + INDEX idx_created_at (created_at) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户求职信息表'; + diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css new file mode 100644 index 00000000..5000f03b --- /dev/null +++ b/src/main/resources/static/css/style.css @@ -0,0 +1,1428 @@ +/* 全局样式 */ +:root { + --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + --success-gradient: linear-gradient(135deg, #48bb78 0%, #38a169 100%); + --card-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); + --border-radius: 12px; + --transition: all 0.3s ease; +} + +body { + background: linear-gradient(to bottom, #f8fafc, #e2e8f0); + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + min-height: 100vh; +} + +/* 头部样式 */ +.header-gradient { + background: var(--primary-gradient); + box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3); +} + +/* 卡片样式 */ +.card { + border: none; + border-radius: var(--border-radius); + box-shadow: var(--card-shadow); + transition: var(--transition); +} + +.card:hover { + transform: translateY(-2px); + box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15); +} + +.card-header { + border-bottom: 1px solid #e2e8f0; + border-radius: var(--border-radius) var(--border-radius) 0 0 !important; +} + +/* 配置卡片样式 */ +.config-card { + background: #f7fafc; + border: 1px solid #e2e8f0; + border-radius: 8px; + padding: 16px; + margin-bottom: 16px; +} + +.config-card .card-title { + color: #4a5568; + font-weight: 600; + margin-bottom: 16px; +} + +/* 表单控件样式 */ +.form-control, .form-select { + border: 1px solid #cbd5e0; + border-radius: 8px; + padding: 12px; + font-size: 14px; + transition: var(--transition); +} + +.form-control:focus, .form-select:focus { + border-color: #667eea; + box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25); +} + +.form-label { + color: #2d3748; + font-weight: 600; + margin-bottom: 8px; +} + +/* 复选框样式 */ +.form-check-input:checked { + background-color: #667eea; + border-color: #667eea; +} + +.form-check-label { + color: #2d3748; + font-weight: 500; +} + +/* 按钮样式 */ +.btn-success { + background: var(--success-gradient); + border: none; + border-radius: var(--border-radius); + padding: 16px; + font-weight: bold; + font-size: 16px; + box-shadow: 0 4px 15px rgba(72, 187, 120, 0.3); + transition: var(--transition); +} + +.btn-success:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(72, 187, 120, 0.4); + background: linear-gradient(135deg, #38a169 0%, #2f855a 100%); +} + +/* 手风琴样式 */ +.accordion-item { + border: 1px solid #e2e8f0; + border-radius: 8px; + margin-bottom: 8px; +} + +.accordion-button { + background-color: #edf2f7; + border-radius: 8px; + font-weight: 600; + color: #2d3748; +} + +.accordion-button:not(.collapsed) { + background-color: #e2e8f0; + color: #2d3748; +} + +.accordion-button:focus { + box-shadow: 0 0 0 0.25rem rgba(102, 126, 234, 0.25); +} + + +/* 滚动区域样式 */ +.scrollable-content { + max-height: 600px; + overflow-y: auto; + padding-right: 8px; +} + +.scrollable-content::-webkit-scrollbar { + width: 6px; +} + +.scrollable-content::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 3px; +} + +.scrollable-content::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 3px; +} + +.scrollable-content::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; +} + +/* 工具提示样式 */ +.tooltip { + font-size: 12px; +} + +.tooltip-inner { + background-color: #2d3748; + border-radius: 6px; + padding: 8px 12px; +} + +/* 徽章样式 */ +.badge { + font-size: 12px; + padding: 6px 12px; + border-radius: 12px; +} + +/* 响应式设计 */ +@media (max-width: 992px) { + .container-fluid { + padding: 16px; + } + + .card-body { + padding: 16px; + } + + .config-card { + padding: 12px; + } +} + +@media (max-width: 768px) { + .header-gradient .container-fluid { + padding: 16px; + } + + .header-gradient h1 { + font-size: 24px; + } + + .header-gradient p { + font-size: 14px; + } + + .btn-success { + padding: 12px; + font-size: 14px; + } + + .scrollable-content { + max-height: 400px; + } +} + +@media (max-width: 576px) { + .row.g-4 { + --bs-gutter-x: 1rem; + --bs-gutter-y: 1rem; + } + + .config-card { + margin-bottom: 12px; + } + + .form-control, .form-select { + padding: 10px; + font-size: 13px; + } +} + +/* 动画效果 */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.card { + animation: fadeIn 0.6s ease-out; +} + +.card:nth-child(1) { + animation-delay: 0.1s; +} + +.card:nth-child(2) { + animation-delay: 0.2s; +} + +.card:nth-child(3) { + animation-delay: 0.3s; +} + +/* 加载状态 */ +.loading { + position: relative; + overflow: hidden; +} + +.loading::after { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent); + animation: loading 1.5s infinite; +} + +@keyframes loading { + 0% { + left: -100%; + } + 100% { + left: 100%; + } +} + +/* 成功状态 */ +.success-flash { + animation: successFlash 0.6s ease-out; +} + +@keyframes successFlash { + 0% { + background-color: #68d391; + } + 100% { + background-color: transparent; + } +} + +/* 错误状态 */ +.error-flash { + animation: errorFlash 0.6s ease-out; +} + +@keyframes errorFlash { + 0% { + background-color: #fc8181; + } + 100% { + background-color: transparent; + } +} + +/* 顶部平台页签(提升与紫色背景的对比度) */ +.header-gradient .nav-tabs { + border-bottom: 0; + gap: 8px; +} + +.header-gradient .nav-tabs .nav-link { + color: rgba(255, 255, 255, 0.9); + background-color: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 999px; + font-weight: 600; + backdrop-filter: saturate(120%) blur(2px); + transition: var(--transition); +} + +.header-gradient .nav-tabs .nav-link:hover, +.header-gradient .nav-tabs .nav-link:focus { + color: #ffffff; + background-color: rgba(255, 255, 255, 0.18); + border-color: rgba(255, 255, 255, 0.3); +} + +.header-gradient .nav-tabs .nav-link.active { + color: #334155; + background-color: #ffffff; + border-color: transparent; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); +} + +.header-gradient .nav-tabs .nav-link i { + color: inherit; +} + +/* 岗位明细表格样式 */ +.job-records-table { + font-size: 14px; +} + +.job-records-table .table { + margin-bottom: 0; +} + +.job-records-table .table th { + background-color: #2d3748; + color: white; + border: none; + font-weight: 600; + font-size: 13px; + padding: 12px 8px; + white-space: nowrap; + position: sticky; + top: 0; + z-index: 10; +} + +.job-records-table .table td { + padding: 12px 8px; + vertical-align: top; + border-bottom: 1px solid #e2e8f0; +} + +.job-records-table .table tbody tr:hover { + background-color: #f7fafc; +} + +.job-records-table .table tbody tr.job-row { + transition: background-color 0.2s ease; +} + +/* 岗位信息列样式 */ +.job-title { + font-weight: 600; + color: #2b6cb0; + margin-bottom: 4px; +} + +.job-position { + font-size: 12px; + color: #718096; + margin-bottom: 8px; +} + +.job-labels { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.job-labels .badge { + font-size: 10px; + padding: 2px 6px; +} + +/* 公司信息列样式 */ +.company-name { + font-weight: 600; + color: #2d3748; + margin-bottom: 4px; +} + +.company-info { + font-size: 12px; + color: #718096; + line-height: 1.4; +} + +/* 工作地点列样式 */ +.location-city { + font-weight: 600; + color: #2d3748; + margin-bottom: 4px; +} + +.location-details { + font-size: 12px; + color: #718096; + line-height: 1.4; +} + +/* 薪资待遇列样式 */ +.salary-desc { + font-weight: 600; + color: #38a169; + margin-bottom: 4px; +} + +.job-requirements { + font-size: 12px; + color: #718096; + line-height: 1.4; +} + +/* 职位要求列样式 */ +.skills-container { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-bottom: 8px; +} + +.skills-container .badge { + font-size: 10px; + padding: 2px 6px; +} + +.job-description { + font-size: 12px; + color: #718096; + line-height: 1.4; + max-height: 60px; + overflow: hidden; + text-overflow: ellipsis; +} + +/* HR信息列样式 */ +.hr-info { + display: flex; + align-items: center; + margin-bottom: 4px; +} + +.hr-avatar { + width: 24px; + height: 24px; + border-radius: 50%; + margin-right: 8px; + object-fit: cover; +} + +.hr-name { + font-weight: 600; + color: #2d3748; +} + +.hr-details { + font-size: 12px; + color: #718096; + line-height: 1.4; +} + +.hr-status { + display: flex; + align-items: center; + font-size: 12px; +} + +.hr-status .bi-circle-fill { + font-size: 8px; + margin-right: 4px; +} + +/* 投递状态列样式 */ +.status-badge { + font-size: 11px; + padding: 4px 8px; + border-radius: 12px; + font-weight: 500; +} + +.status-time { + font-size: 11px; + color: #718096; + margin-top: 4px; + line-height: 1.3; +} + +/* 操作按钮列样式 */ +.action-buttons { + display: flex; + flex-direction: column; + gap: 4px; +} + +.action-buttons .btn { + font-size: 12px; + padding: 4px 8px; + border-radius: 6px; +} + +/* 统计卡片样式 */ +.stats-card { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-radius: 12px; + padding: 16px; + text-align: center; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); + transition: transform 0.2s ease; +} + +.stats-card:hover { + transform: translateY(-2px); +} + +.stats-card h6 { + font-size: 24px; + font-weight: bold; + margin-bottom: 4px; +} + +.stats-card small { + font-size: 12px; + opacity: 0.9; +} + +/* 分页样式 */ +.pagination { + margin-top: 20px; +} + +.pagination .page-link { + border-radius: 8px; + margin: 0 2px; + border: 1px solid #e2e8f0; + color: #4a5568; + font-weight: 500; +} + +.pagination .page-link:hover { + background-color: #f7fafc; + border-color: #cbd5e0; +} + +.pagination .page-item.active .page-link { + background-color: #667eea; + border-color: #667eea; +} + +.pagination .page-item.disabled .page-link { + color: #a0aec0; + background-color: #f7fafc; +} + +/* 响应式表格样式 */ +@media (max-width: 1200px) { + .job-records-table .table th, + .job-records-table .table td { + padding: 8px 6px; + font-size: 13px; + } + + .job-title { + font-size: 14px; + } + + .company-name { + font-size: 14px; + } +} + +@media (max-width: 992px) { + .job-records-table .table th, + .job-records-table .table td { + padding: 6px 4px; + font-size: 12px; + } + + .job-labels .badge, + .skills-container .badge { + font-size: 9px; + padding: 1px 4px; + } + + .action-buttons .btn { + font-size: 11px; + padding: 3px 6px; + } +} + +@media (max-width: 768px) { + .job-records-table { + font-size: 12px; + } + + .job-records-table .table th, + .job-records-table .table td { + padding: 4px 2px; + font-size: 11px; + } + + .job-title, + .company-name, + .location-city, + .salary-desc { + font-size: 13px; + } + + .job-position, + .company-info, + .location-details, + .job-requirements, + .hr-details, + .status-time { + font-size: 11px; + } + + .hr-avatar { + width: 20px; + height: 20px; + } + + .stats-card { + padding: 12px; + } + + .stats-card h6 { + font-size: 20px; + } +} + +@media (max-width: 576px) { + .job-records-table .table th, + .job-records-table .table td { + padding: 3px 1px; + font-size: 10px; + } + + .job-title, + .company-name, + .location-city, + .salary-desc { + font-size: 12px; + } + + .job-position, + .company-info, + .location-details, + .job-requirements, + .hr-details, + .status-time { + font-size: 10px; + } + + .hr-avatar { + width: 18px; + height: 18px; + } + + .action-buttons .btn { + font-size: 10px; + padding: 2px 4px; + } + + .stats-card { + padding: 8px; + } + + .stats-card h6 { + font-size: 18px; + } +} + +/* 表格滚动条样式 */ +.table-responsive::-webkit-scrollbar { + height: 8px; +} + +.table-responsive::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 4px; +} + +.table-responsive::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 4px; +} + +.table-responsive::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; +} + +/* 加载状态样式 */ +.loading-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +/* 空状态样式 */ +.empty-state { + text-align: center; + padding: 40px 20px; + color: #718096; +} + +.empty-state i { + font-size: 48px; + margin-bottom: 16px; + color: #a0aec0; +} + +.empty-state h5 { + margin-bottom: 8px; + color: #4a5568; +} + +.empty-state i { + font-size: 48px; + margin-bottom: 16px; + opacity: 0.5; +} + +.empty-state h5 { + color: #4a5568; + margin-bottom: 8px; +} + +.empty-state p { + font-size: 14px; + margin-bottom: 0; +} + +/* 新增字段样式 */ +.job-status { + margin-top: 4px; +} + +.location-coordinates { + margin-top: 4px; + font-size: 11px; +} + +.industry-code { + margin-top: 4px; + font-size: 11px; +} + +.job-extra-info { + margin-top: 4px; + display: flex; + flex-direction: column; + gap: 2px; +} + +.job-extra-info small { + font-size: 10px; + color: #718096; +} + +.platform-info { + margin-top: 4px; + font-size: 11px; +} + +.remark-info { + margin-top: 4px; + font-size: 11px; +} + +.company-logo-img { + width: 32px; + height: 32px; + object-fit: cover; + border-radius: 4px; + margin-top: 4px; +} + +.location-address { + margin-top: 4px; +} + +.job-type { + margin-top: 4px; +} + +.skill-count { + margin-top: 4px; +} + +.hr-cert { + margin-top: 4px; +} + +.welfare-container { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-bottom: 4px; +} + +.welfare-container .badge { + font-size: 10px; + padding: 2px 6px; +} + +.welfare-count { + margin-top: 4px; +} + +.no-welfare { + color: #a0aec0; + font-style: italic; +} + +.job-flags { + margin-top: 4px; +} + +.job-flags .badge { + font-size: 10px; + padding: 2px 6px; +} + +/* 公司信息模态框样式 */ +.company-logo-large img { + border: 1px solid #e2e8f0; + padding: 8px; +} + +.company-basic-info ul li { + margin-bottom: 4px; + font-size: 14px; +} + +.intro-content { + background-color: #f7fafc; + padding: 12px; + border-radius: 8px; + border: 1px solid #e2e8f0; +} + +.intro-content p { + margin-bottom: 0; + line-height: 1.6; + color: #4a5568; +} + +.welfare-tags { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.welfare-tags .badge { + font-size: 12px; + padding: 4px 8px; +} + +/* 响应式调整 */ +@media (max-width: 992px) { + .company-logo-img { + width: 28px; + height: 28px; + } + + .welfare-container .badge { + font-size: 9px; + padding: 1px 4px; + } + + .job-flags .badge { + font-size: 9px; + padding: 1px 4px; + } +} + +@media (max-width: 768px) { + .company-logo-img { + width: 24px; + height: 24px; + } + + .welfare-container .badge { + font-size: 8px; + padding: 1px 3px; + } + + .job-flags .badge { + font-size: 8px; + padding: 1px 3px; + } +} + +@media (max-width: 576px) { + .company-logo-img { + width: 20px; + height: 20px; + } + + .welfare-container .badge { + font-size: 7px; + padding: 1px 2px; + } + + .job-flags .badge { + font-size: 7px; + padding: 1px 2px; + } +} + +/* 51job详细表格样式 */ +.job-title { + line-height: 1.3; +} + +.job-title a { + color: #0066cc; + font-weight: 600; +} + +.job-title a:hover { + color: #004499; + text-decoration: underline; +} + +.encrypt-ids { + font-size: 0.75rem; + line-height: 1.2; +} + +.status-badges .badge { + font-size: 0.7rem; + padding: 2px 6px; +} + +.company-details { + font-size: 0.85rem; + line-height: 1.3; +} + +.company-details i { + color: #6c757d; +} + +.location-city { + color: #495057; + font-weight: 500; +} + +.coordinates { + font-size: 0.7rem; + color: #6c757d; +} + +.salary-desc { + font-size: 1.1rem; + font-weight: 600; +} + +.work-schedule { + font-size: 0.85rem; + line-height: 1.3; +} + +.requirements { + font-size: 0.9rem; + line-height: 1.4; +} + +.skills-container .badge { + font-size: 0.75rem; + margin-bottom: 2px; +} + +.job-labels .badge { + font-size: 0.7rem; + border: 1px solid #0d6efd; + color: #0d6efd; + background-color: transparent; +} + +.hr-info { + display: flex; + align-items: center; +} + +.hr-avatar { + flex-shrink: 0; +} + +.hr-name { + font-weight: 500; +} + +.hr-details { + font-size: 0.85rem; + line-height: 1.3; +} + +.hr-status i { + font-size: 0.6rem; +} + +.company-tags .badge { + font-size: 0.75rem; + margin-bottom: 2px; +} + +.welfare-list .badge { + font-size: 0.7rem; + border: 1px solid #198754; + color: #198754; + background-color: transparent; +} + +.status-flags .badge { + font-size: 0.7rem; + margin-bottom: 2px; +} + +.platform-info .badge { + font-size: 0.8rem; + font-weight: 500; +} + +.time-info { + font-size: 0.85rem; + line-height: 1.3; +} + +.time-info strong { + color: #495057; + font-size: 0.8rem; +} + +.extended-info { + font-size: 0.85rem; + line-height: 1.3; +} + +.remark { + background-color: #f8f9fa; + padding: 4px 6px; + border-radius: 4px; + border-left: 3px solid #6c757d; +} + +.action-buttons .btn { + width: 32px; + height: 28px; + padding: 4px; + display: flex; + align-items: center; + justify-content: center; +} + +.action-buttons .btn i { + font-size: 0.8rem; +} + +/* 表格响应式优化 */ +@media (max-width: 1400px) { + .job-description, + .job-requirements { + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .extended-info .remark { + max-width: 100px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +@media (max-width: 1200px) { + .encrypt-ids, + .coordinates, + .cert-level, + .tag-count, + .skill-count { + display: none; + } + + .action-buttons .btn:nth-child(n+4) { + display: none; + } +} + +/* AI Prompt Floating Panel */ +.ai-prompt-panel { + position: fixed; + top: 50%; + right: -350px; /* Initially hidden */ + width: 380px; + transform: translateY(-50%); + background-color: #ffffff; + border: 1px solid #e2e8f0; + border-radius: 12px 0 0 12px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); + transition: right 0.4s ease-in-out; + z-index: 1050; +} + +.ai-prompt-panel.show { + right: 0; /* Slides into view */ +} + +.ai-prompt-handle { + position: absolute; + left: -40px; + top: 50%; + transform: translateY(-50%); + width: 40px; + height: 60px; + background-color: #667eea; + border-radius: 8px 0 0 8px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: white; + font-size: 24px; + box-shadow: -5px 5px 15px rgba(0, 0, 0, 0.1); +} + +.ai-prompt-content { + padding: 20px; + max-height: 80vh; + overflow-y: auto; +} + +.ai-prompt-content h5 { + color: #4a5568; + font-weight: 600; + margin-bottom: 16px; +} + +/* AI提示词面板页签样式 */ +#aiPromptTabs .nav-link { + font-size: 12px; + padding: 8px 12px; + border-radius: 6px; + margin-bottom: 8px; + color: #6c757d; + border: 1px solid transparent; + transition: all 0.3s ease; +} + +#aiPromptTabs .nav-link:hover { + color: #495057; + background-color: #f8f9fa; + border-color: #dee2e6; +} + +#aiPromptTabs .nav-link.active { + color: #fff; + background-color: #667eea; + border-color: #667eea; + font-weight: 600; +} + +#aiPromptTabs .nav-link i { + font-size: 14px; +} + +/* 候选人信息表单样式 */ +.profile-form .form-label { + font-size: 13px; + font-weight: 600; + color: #495057; + margin-bottom: 6px; +} + +.profile-form .form-control { + font-size: 13px; + padding: 8px 12px; + border-radius: 6px; + border: 1px solid #ced4da; +} + +.profile-form .form-control:focus { + border-color: #667eea; + box-shadow: 0 0 0 0.15rem rgba(102, 126, 234, 0.25); +} + +.profile-form .form-text { + font-size: 11px; + color: #6c757d; + margin-top: 4px; +} + +.profile-form .text-danger { + color: #dc3545 !important; + font-size: 12px; +} + +/* 打招呼内容表单样式 */ +.greeting-form .form-label { + font-size: 13px; + font-weight: 600; + color: #495057; + margin-bottom: 6px; +} + +.greeting-form .form-control { + font-size: 13px; + padding: 8px 12px; + border-radius: 6px; + border: 1px solid #ced4da; + resize: vertical; +} + +.greeting-form .form-control:focus { + border-color: #667eea; + box-shadow: 0 0 0 0.15rem rgba(102, 126, 234, 0.25); +} + +/* 表单验证样式 */ +.form-control.is-valid { + border-color: #198754; + background-image: none; +} + +.form-control.is-valid:focus { + border-color: #198754; + box-shadow: 0 0 0 0.15rem rgba(25, 135, 84, 0.25); +} + +.form-control.is-invalid { + border-color: #dc3545; + background-image: none; +} + +.form-control.is-invalid:focus { + border-color: #dc3545; + box-shadow: 0 0 0 0.15rem rgba(220, 53, 69, 0.25); +} + +/* 操作按钮样式 */ +.ai-prompt-content .btn { + font-size: 13px; + padding: 8px 16px; + border-radius: 6px; + font-weight: 500; +} + +.ai-prompt-content .btn-primary { + background-color: #667eea; + border-color: #667eea; +} + +.ai-prompt-content .btn-primary:hover { + background-color: #5a6fd8; + border-color: #5a6fd8; +} + +.ai-prompt-content .btn-outline-success { + color: #198754; + border-color: #198754; +} + +.ai-prompt-content .btn-outline-success:hover { + background-color: #198754; + border-color: #198754; + color: #fff; +} + +/* 标签输入组件样式 */ +.tags-input-container { + min-height: 120px; + padding: 8px; + border: 1px solid #cbd5e0; + border-radius: 8px; + background-color: #ffffff; + cursor: text; + transition: var(--transition); + display: flex; + flex-direction: column; + gap: 8px; +} + +.tags-input-container:focus-within { + border-color: #667eea; + box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25); +} + +.tags-wrapper { + display: flex; + flex-wrap: wrap; + gap: 6px; + min-height: 32px; +} + +.tag-item { + display: inline-flex; + align-items: center; + padding: 6px 10px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-radius: 6px; + font-size: 13px; + font-weight: 500; + box-shadow: 0 2px 4px rgba(102, 126, 234, 0.2); + transition: all 0.2s ease; + animation: tagFadeIn 0.3s ease; +} + +.tag-item:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3); +} + +.tag-text { + margin-right: 6px; + user-select: none; +} + +.tag-remove { + display: flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + background-color: rgba(255, 255, 255, 0.2); + border-radius: 50%; + cursor: pointer; + transition: all 0.2s ease; + font-size: 14px; + line-height: 1; +} + +.tag-remove:hover { + background-color: rgba(255, 255, 255, 0.35); + transform: scale(1.1); +} + +.tags-input { + flex: 1; + min-width: 200px; + border: none; + outline: none; + padding: 8px 4px; + font-size: 14px; + background: transparent; +} + +.tags-input::placeholder { + color: #a0aec0; +} + +@keyframes tagFadeIn { + from { + opacity: 0; + transform: scale(0.8); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes shake { + 0%, 100% { + transform: translateX(0); + } + 25% { + transform: translateX(-5px); + } + 75% { + transform: translateX(5px); + } +} + +/* 响应式调整 */ +@media (max-width: 768px) { + .tags-input-container { + min-height: 100px; + padding: 6px; + } + + .tag-item { + padding: 5px 8px; + font-size: 12px; + } + + .tags-input { + font-size: 13px; + padding: 6px 4px; + } +} \ No newline at end of file diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html new file mode 100644 index 00000000..db09a8fc --- /dev/null +++ b/src/main/resources/static/index.html @@ -0,0 +1,3218 @@ + + + + + + Boss直聘配置 - 智能求职助手 + + + + + + + + + + + + + + +
    +
    +
    +
    +

    + 智能求职助手 +

    +

    支持 Boss直聘 / 51job / 智联招聘 / 猎聘 / 神仙外企 / 拉钩

    +
    +
    +
    +
    + +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    +
    + 公共配置 +
    + 此配置将应用于所有招聘平台 +
    +
    +
    + +
    +
    + 黑名单过滤配置 +
    + + +
    + +
    +
    + +
    +
    + + 职位标题、职位描述中包含这些关键字的岗位将被自动过滤 +
    +
    + + +
    + +
    +
    + +
    +
    + + 公司名称中包含这些关键字的岗位将被自动过滤 +
    +
    + + + +
    + + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    + + +
    +
    +
    +
    + 基本配置 +
    +
    +
    +
    + + +
    +
    + 搜索条件 +
    + + +
    + + +
    + + +
    + + + 未选择 + +
    + + +
    + + + 未选择 + +
    + +
    + + +
    +
    + 职位要求 +
    + +
    + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    +
    + + +
    + + +
    + + +
    + +
    +
    + +
    +
    + ~ +
    +
    + +
    +
    +
    +
    + + +
    +
    + 简历配置 +
    + + +
    + + +
    + + + + + +
    + + +
    +
    + +
    +
    + +
    +
    + + +
    +
    +
    +
    + 智能设置 +
    +
    +
    +
    + + +
    +
    + 功能开关 +
    + +
    + + +
    + + +
    +
    +

    + +

    +
    +
    +
    + +
    +
    + +
    +
    + + 输入关键字后按回车键添加,点击标签上的 × 可删除 +
    +
    +
    +
    +
    +
    + +
    + + +
    + + + +
    + + +
    + + +
    + + +
    +
    + + +
    +
    + AI智能配置 +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    +
    + + + +
    +
    +
    + +
    +
    + +
    +
    + + +
    +
    + 任务执行步骤 +
    + + +
    + + +
    + + + + + + + + + + + + +
    +
    +
    +
    + +
    +
    +
    +
    + + +
    +
    +
    +
    求职配置查看
    +
    +
    + +
    +
    +
    + 加载中... +
    +

    加载中...

    +
    + +
    + 暂无配置数据,请先在"配置"页签中保存配置。 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    配置项
    配置ID{{ config.id }}
    创建时间{{ formatDateTime(config.createdAt) }}
    更新时间{{ formatDateTime(config.updatedAt) }}
    搜索关键词{{ safe(config.keywords) }}
    目标行业{{ safe(config.industry) }}
    工作城市{{ mapCityCodesToNames(config.cityCode) || '-' }}
    工作经验{{ safe(config.experience) }}
    求职类型{{ config.jobType }}
    薪资档位{{ config.salary }}
    学历要求{{ safe(config.degree) }}
    公司规模{{ safe(config.scale) }}
    融资阶段{{ safe(config.stage) }}
    期望薪资{{ Array.isArray(config.expectedSalary) ? config.expectedSalary.join(' ~ ') : config.expectedSalary }}
    过滤不活跃HR + 开启 + 关闭 +
    发送图片简历 + 开启 + 关闭 +
    过滤黑名单岗位 + 开启 + 关闭 +
    黑名单关键词{{ safe(config.blacklistKeywords) }}
    接收推荐岗位 + 开启 + 关闭 +
    AI岗位匹配检测 + 开启 + 关闭 +
    AI智能打招呼 + 开启 + 关闭 +
    国企识别检查 + 开启 + 关闭 +
    HR状态过滤列表{{ safe(config.deadStatus) }}
    补充城市代码
    简历图片路径{{ config.resumeImagePath }}
    打招呼内容{{ config.sayHi }}
    备注{{ config.remark }}
    简历内容 +
    {{ config.resumeContent }}
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    岗位明细
    +
    + + + + + +
    +
    +
    + +
    +
    +
    + 加载中... +
    +

    加载中...

    +
    + +
    + +
    +
    +
    +
    {{ totalElements }}
    + 总岗位数 +
    +
    +
    +
    +
    {{ currentPageData.length }}
    + 当前页 +
    +
    +
    +
    +
    {{ totalPages }}
    + 总页数 +
    +
    +
    +
    +
    {{ currentPage + 1 }}
    + 当前页 +
    +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    岗位信息公司信息工作地点薪资待遇职位要求技能标签HR信息公司福利投递状态过滤原因操作
    + +
    暂无数据
    +

    请先执行岗位采集任务

    +
    +
    {{ job.jobTitle }}
    +
    {{ job.jobPositionName }}
    +
    + {{ label }} +
    +
    + {{ job.jobStatusDesc }} +
    +
    +
    {{ job.companyName }}
    +
    +
    {{ job.companyIndustry }}
    +
    {{ job.companyScale }}
    +
    {{ job.companyStage }}
    +
    + +
    +
    {{ job.workCity }}
    +
    +
    {{ job.workArea }}
    +
    {{ job.businessDistrict }}
    +
    +
    + {{ truncateText(job.jobAddress, 30) }} +
    +
    +
    {{ job.salaryDesc }}
    +
    +
    {{ job.jobExperience }}
    +
    {{ job.jobDegree }}
    +
    +
    + {{ getJobTypeText(job.jobType) }} +
    +
    +
    + {{ truncateText(job.jobPostDescription, 80) }} +
    +
    + 经验要求: {{ job.jobExperienceName }} +
    +
    + 学历要求: {{ job.jobDegreeName }} +
    +
    +
    + {{ skill }} + + +{{ parseArray(job.jobShowSkills || job.skills).length - 4 }} + +
    +
    + 共{{ parseArray(job.jobShowSkills || job.skills).length }}项技能 +
    +
    +
    + +
    {{ job.hrName }}
    +
    +
    +
    {{ job.hrTitle }}
    +
    + + + {{ job.hrOnline ? '在线' : '离线' }} + +
    +
    {{ job.hrActiveTimeDesc }}
    +
    + {{ job.bossActiveTimeDesc }} +
    +
    + 认证{{ job.hrCertLevel }}级 +
    +
    +
    +
    + {{ welfare }} + + +{{ parseArray(job.brandLabels).length - 3 }} + +
    +
    + 共{{ parseArray(job.brandLabels).length }}项福利 +
    +
    + 暂无福利信息 +
    +
    +
    + {{ getStatusText(job.status) }} +
    +
    +
    {{ formatDateTime(job.createdAt) }}
    +
    {{ formatDateTime(job.updatedAt) }}
    +
    +
    + 收藏 + 优选 +
    +
    +
    + {{ truncateText(job.filterReason, 60) }} +
    +
    + - +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + 基本配置 +
    +
    +
    +
    + + +
    +
    + 搜索条件 +
    + + +
    + + +
    + + +
    + + + 未选择 + +
    + + +
    + + + 未选择 + +
    +
    + + +
    +
    + 职位要求 +
    + +
    + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    +
    +
    + +
    +
    +
    +
    + + +
    +
    +
    +
    + 智能设置 +
    +
    +
    +
    + + +
    +
    + 功能开关 +
    + +
    + + +
    +
    + + +
    +
    + AI智能配置 +
    + +
    + + +
    + +
    + + +
    +
    +
    + +
    +
    + +
    +
    + + +
    +
    + 任务执行步骤 +
    + + +
    + + +
    + + + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    岗位明细
    +
    + + + + + +
    +
    +
    + +
    +
    +
    + 加载中... +
    +

    加载中...

    +
    + +
    + +
    +
    +
    +
    {{ totalElements }}
    + 总岗位数 +
    +
    +
    +
    +
    {{ records.length }}
    + 当前页 +
    +
    +
    +
    +
    {{ totalPages }}
    + 总页数 +
    +
    +
    +
    +
    {{ currentPage + 1 }}
    + 当前页 +
    +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    岗位信息公司信息工作地点薪资待遇职位要求技能标签HR信息公司福利投递状态过滤原因操作
    + +
    暂无数据
    +

    请先执行岗位采集任务

    +
    +
    {{ job.jobTitle }}
    +
    + {{ label }} +
    +
    + {{ job.platform }} +
    +
    +
    {{ job.companyName }}
    +
    +
    {{ job.companyIndustry }}
    +
    {{ job.companyScale }}
    +
    {{ job.companyStage }}
    +
    + +
    +
    {{ job.workCity }}
    +
    +
    {{ job.workArea }}
    +
    {{ job.businessDistrict }}
    +
    +
    +
    {{ job.salaryDesc }}
    +
    +
    {{ job.jobExperience }}
    +
    {{ job.jobDegree }}
    +
    +
    + {{ getJobTypeText(job.jobType) }} +
    +
    +
    + {{ truncateText(job.jobDescription, 80) }} +
    +
    +
    + {{ skill }} + + +{{ parseArray(job.skills).length - 4 }} + +
    +
    + 共{{ parseArray(job.skills).length }}项技能 +
    +
    +
    + +
    {{ job.hrName }}
    +
    +
    +
    {{ job.hrTitle }}
    +
    + + + {{ job.hrOnline ? '在线' : '离线' }} + +
    +
    + 认证{{ job.hrCertLevel }}级 +
    +
    +
    +
    + {{ welfare }} + + +{{ parseArray(job.welfareList).length - 3 }} + +
    +
    + 共{{ parseArray(job.welfareList).length }}项福利 +
    +
    + 暂无福利信息 +
    +
    +
    + {{ getStatusInfo(job.status).text }} +
    +
    +
    {{ formatDateTime(job.createdAt) }}
    +
    {{ formatDateTime(job.updatedAt) }}
    +
    +
    + 收藏 + 优选 +
    +
    +
    + {{ truncateText(job.filterReason, 60) }} +
    +
    + - +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + 基本配置 +
    +
    +
    +
    + + +
    +
    + 搜索条件 +
    + + +
    + + +
    + + +
    + + + 未选择 + +
    + + +
    + + + 未选择 + +
    +
    + + +
    +
    + 职位要求 +
    + +
    + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + 智能设置 +
    +
    +
    +
    + + +
    +
    + 功能开关 +
    +
    + + +
    +
    + + +
    +
    + AI智能配置 +
    + +
    + + +
    + +
    + + +
    +
    +
    + +
    +
    + +
    +
    + + +
    +
    + 任务执行步骤 +
    + + +
    + + +
    + + + + + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    岗位明细
    +
    + + + + + +
    +
    +
    + +
    +
    +
    + 加载中... +
    +

    加载中...

    +
    + +
    + +
    +
    +
    +
    {{ totalElements }}
    + 总岗位数 +
    +
    +
    +
    +
    {{ records.length }}
    + 当前页 +
    +
    +
    +
    +
    {{ totalPages }}
    + 总页数 +
    +
    +
    +
    +
    {{ currentPage + 1 }}
    + 当前页 +
    +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    岗位信息公司信息工作地点薪资待遇职位要求技能标签HR信息公司福利投递状态过滤原因操作
    + +
    暂无数据
    +

    请先执行岗位采集任务

    +
    +
    {{ job.jobTitle }}
    +
    + {{ label }} +
    +
    + {{ job.platform }} +
    +
    +
    {{ job.companyName }}
    +
    +
    {{ job.companyIndustry }}
    +
    {{ job.companyScale }}
    +
    {{ job.companyStage }}
    +
    + +
    +
    {{ job.workCity }}
    +
    +
    {{ job.workArea }}
    +
    {{ job.businessDistrict }}
    +
    +
    +
    {{ job.salaryDesc }}
    +
    +
    {{ job.jobExperience }}
    +
    {{ job.jobDegree }}
    +
    +
    + {{ getJobTypeText(job.jobType) }} +
    +
    +
    + {{ truncateText(job.jobDescription, 80) }} +
    +
    +
    + {{ skill }} + + +{{ parseArray(job.skills).length - 4 }} + +
    +
    + 共{{ parseArray(job.skills).length }}项技能 +
    +
    +
    + +
    {{ job.hrName }}
    +
    +
    +
    {{ job.hrTitle }}
    +
    + + + {{ job.hrOnline ? '在线' : '离线' }} + +
    +
    + 认证{{ job.hrCertLevel }}级 +
    +
    +
    +
    + {{ welfare }} + + +{{ parseArray(job.welfareList).length - 3 }} + +
    +
    + 共{{ parseArray(job.welfareList).length }}项福利 +
    +
    + 暂无福利信息 +
    +
    +
    + {{ getStatusInfo(job.status).text }} +
    +
    +
    {{ formatDateTime(job.createdAt) }}
    +
    {{ formatDateTime(job.updatedAt) }}
    +
    +
    + 收藏 + 优选 +
    +
    +
    + {{ truncateText(job.filterReason, 60) }} +
    +
    + - +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + 基本配置 +
    +
    +
    +
    + + +
    +
    + 搜索条件 +
    + + +
    + + +
    + + +
    + + + 未选择 + +
    + + +
    + + + 未选择 + +
    +
    + + +
    +
    + 职位要求 +
    + +
    + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    + + +
    +
    +
    + + +
    +
    + 简历配置 +
    + + +
    + + +
    + + +
    + + +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    + 智能设置 +
    +
    +
    +
    + +
    +
    + 功能开关 +
    + +
    + + +
    +
    + + +
    +
    + AI智能配置 +
    + +
    + + +
    + +
    + +
    +
    +
    + +
    +
    + +
    +
    + + +
    +
    + 任务执行步骤 +
    + +
    + + +
    + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    岗位明细
    +
    + + + + + +
    +
    +
    + +
    + +
    +
    + 加载中... +
    +

    加载岗位数据中...

    +
    + + + + + +
    + +
    +
    +
    +
    {{ totalElements }}
    + 总岗位数 +
    +
    +
    +
    +
    {{ records.length }}
    + 当前页 +
    +
    +
    +
    +
    {{ totalPages }}
    + 总页数 +
    +
    +
    +
    +
    {{ currentPage + 1 }}
    + 当前页 +
    +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    岗位信息公司信息工作地点薪资待遇职位要求技能标签HR信息公司福利投递状态过滤原因操作
    + +
    暂无数据
    +

    请先执行岗位采集任务

    +
    +
    {{ job.jobTitle }}
    +
    + {{ label }} +
    +
    + {{ job.platform }} +
    +
    +
    {{ job.companyName }}
    +
    +
    {{ job.companyIndustry }}
    +
    {{ job.companyScale }}
    +
    {{ job.companyStage }}
    +
    + +
    +
    {{ job.workCity }}
    +
    +
    {{ job.workArea }}
    +
    {{ job.businessDistrict }}
    +
    +
    +
    {{ job.salaryDesc }}
    +
    +
    {{ job.jobExperience }}
    +
    {{ job.jobDegree }}
    +
    +
    + {{ getJobTypeText(job.jobType) }} +
    +
    +
    + {{ truncateText(job.jobDescription, 80) }} +
    +
    +
    + {{ skill }} + + +{{ parseArray(job.skills).length - 4 }} + +
    +
    + 共{{ parseArray(job.skills).length }}项技能 +
    +
    +
    + +
    {{ job.hrName }}
    +
    +
    +
    {{ job.hrTitle }}
    +
    + + + {{ job.hrOnline ? '在线' : '离线' }} + +
    +
    + 认证{{ job.hrCertLevel }}级 +
    +
    +
    +
    + {{ welfare }} + + +{{ parseArray(job.welfareList).length - 3 }} + +
    +
    + 共{{ parseArray(job.welfareList).length }}项福利 +
    +
    + 暂无福利信息 +
    +
    +
    + {{ getStatusInfo(job.status).text }} +
    +
    +
    {{ formatDateTime(job.createdAt) }}
    +
    {{ formatDateTime(job.updatedAt) }}
    +
    +
    + 收藏 + 优选 +
    +
    +
    + {{ truncateText(job.filterReason, 60) }} +
    +
    + - +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    +
    +
    + + 求职信息补全(AI使用) +
    + + + + + +
    + +
    +
    + +
    + + +
    + +
    + + +
    + + +
    + + + 多个领域请用英文逗号分隔 +
    + + +
    + + + 多个技术请用英文逗号分隔 +
    + + +
    + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    + + + 多个成就请用分号分隔 +
    + + +
    + + + 多个强项请用英文逗号分隔 +
    + + +
    + + + 多个方面请用英文逗号分隔 +
    + + +
    + + +
    + + +
    + +
    +
    + + +
    +
    + + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    + + +
    + +
    + + + + + + + + + + + + diff --git a/src/main/resources/static/js/app.js b/src/main/resources/static/js/app.js new file mode 100644 index 00000000..ab5544d6 --- /dev/null +++ b/src/main/resources/static/js/app.js @@ -0,0 +1,862 @@ +// Boss直聘配置应用主脚本 +class BossConfigApp { + constructor() { + this.config = {}; + this.isRunning = false; + this.isDataLoading = false; // 添加数据加载状态标识 + this.init(); + } + + init() { + CommonUtils.initializeTooltips(); + this.bindEvents(); + // 先加载字典数据,再加载配置数据,确保下拉框已准备好 + this.loadDataSequentially(); + } + + // 绑定事件 + bindEvents() { + // 数据库备份按钮 + const backupDataBtn = document.getElementById('backupDataBtn'); + if (backupDataBtn) { + backupDataBtn.addEventListener('click', () => { + this.handleBackupData(); + }); + console.log('已绑定数据库备份按钮事件'); + } else { + console.warn('未找到数据库备份按钮元素'); + } + + // 求职配置视图:绑定标签切换加载与刷新 + this.bindBossConfigViewEvents(); + + // 绑定岗位明细按钮事件 + this.bindJobRecordsEvents(); + } + + + // ===================== + // 求职配置只读视图 + // ===================== + bindBossConfigViewEvents() { + const self = this; + document.getElementById('boss-current-tab')?.addEventListener('shown.bs.tab', function () { + console.log('求职配置tab已显示'); + // 切换到"求职配置"页签时,触发 Vue 视图加载 + if (window.bossConfigView && typeof window.bossConfigView.load === 'function') { + console.log('调用Vue应用的load方法'); + window.bossConfigView.load(false); + } else { + console.log('Vue应用未找到,检查初始化'); + // 兼容:若 Vue 尚未初始化,保底旧渲染(基本不会走到) + self.loadBossConfigView(); + } + }); + } + + // ===================== + // 岗位明细按钮事件 + // ===================== + bindJobRecordsEvents() { + // Boss直聘岗位明细按钮 + this.bindPlatformJobRecordsEvents('boss', 'BOSS直聘'); + + // 智联招聘岗位明细按钮 + this.bindPlatformJobRecordsEvents('zhilian', '智联招聘'); + + // 前程无忧岗位明细按钮 + this.bindPlatformJobRecordsEvents('job51', '前程无忧'); + + // 猎聘岗位明细按钮 + this.bindPlatformJobRecordsEvents('liepin', '猎聘'); + } + + bindPlatformJobRecordsEvents(platform, platformName) { + // 平台代码映射到后端使用的枚举名称 + const platformEnumMap = { + 'boss': 'BOSS直聘', + 'zhilian': '智联招聘', + 'job51': '51job', + 'liepin': 'LIEPIN' + }; + + const backendPlatform = platformEnumMap[platform] || platformName; + + // 重置岗位状态按钮 + const resetBtn = document.getElementById(`${platform}RecordResetBtn`); + if (resetBtn) { + resetBtn.addEventListener('click', () => { + this.handleResetFilter(backendPlatform, platformName); + }); + console.log(`已绑定${platformName}重置岗位状态按钮事件`); + } + + // 删除岗位按钮 + const deleteBtn = document.getElementById(`${platform}RecordDeleteBtn`); + if (deleteBtn) { + deleteBtn.addEventListener('click', () => { + this.handleDeleteAllJobs(backendPlatform, platformName); + }); + console.log(`已绑定${platformName}删除岗位按钮事件`); + } + } + + // 处理重置岗位状态 + handleResetFilter(platform, platformName) { + const confirmModal = document.getElementById('confirmModal'); + const confirmModalBody = document.getElementById('confirmModalBody'); + const confirmModalOk = document.getElementById('confirmModalOk'); + + if (confirmModal && confirmModalBody && confirmModalOk) { + confirmModalBody.textContent = `确定要重置${platformName}的所有岗位状态吗?此操作将清除所有岗位的过滤状态。`; + + // 移除之前的事件监听器 + const newOkBtn = confirmModalOk.cloneNode(true); + confirmModalOk.parentNode.replaceChild(newOkBtn, confirmModalOk); + + newOkBtn.addEventListener('click', async () => { + try { + const response = await fetch('https://github.com/api/jobs/reset-filter', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `platform=${encodeURIComponent(platformName)}` + }); + + if (response.ok) { + const result = await response.json(); + CommonUtils.showToast(`重置成功,共重置${result}个岗位状态`, 'success'); + + // 刷新对应的Vue应用数据 + this.refreshPlatformData(platform); + } else { + CommonUtils.showToast('重置失败,请稍后重试', 'danger'); + } + } catch (error) { + console.error('重置岗位状态失败:', error); + CommonUtils.showToast('重置失败,请稍后重试', 'danger'); + } + + // 关闭模态框 + const modal = bootstrap.Modal.getInstance(confirmModal); + if (modal) { + modal.hide(); + } + }); + + // 显示确认模态框 + const modal = new bootstrap.Modal(confirmModal); + modal.show(); + } else { + console.error('确认模态框元素未找到'); + } + } + + // 处理删除所有岗位 + handleDeleteAllJobs(platform, platformName) { + const confirmModal = document.getElementById('confirmModal'); + const confirmModalBody = document.getElementById('confirmModalBody'); + const confirmModalOk = document.getElementById('confirmModalOk'); + + if (confirmModal && confirmModalBody && confirmModalOk) { + confirmModalBody.textContent = `确定要删除${platformName}的所有岗位吗?此操作不可恢复,请谨慎操作!`; + + // 移除之前的事件监听器 + const newOkBtn = confirmModalOk.cloneNode(true); + confirmModalOk.parentNode.replaceChild(newOkBtn, confirmModalOk); + + newOkBtn.addEventListener('click', async () => { + try { + const response = await fetch('https://github.com/api/jobs', { + method: 'DELETE', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `platform=${encodeURIComponent(platformName)}` + }); + + if (response.ok) { + CommonUtils.showToast(`${platformName}所有岗位已删除`, 'success'); + + // 刷新对应的Vue应用数据 + this.refreshPlatformData(platform); + } else { + CommonUtils.showToast('删除失败,请稍后重试', 'danger'); + } + } catch (error) { + console.error('删除岗位失败:', error); + CommonUtils.showToast('删除失败,请稍后重试', 'danger'); + } + + // 关闭模态框 + const modal = bootstrap.Modal.getInstance(confirmModal); + if (modal) { + modal.hide(); + } + }); + + // 显示确认模态框 + const modal = new bootstrap.Modal(confirmModal); + modal.show(); + } else { + console.error('确认模态框元素未找到'); + } + } + + // 刷新平台数据 + refreshPlatformData(platform) { + // 将后端平台名称映射回前端平台代码 + const backendToFrontendMap = { + 'BOSS直聘': 'boss', + '智联招聘': 'zhilian', + '51job': 'job51', + 'LIEPIN': 'liepin' + }; + + const frontendPlatform = backendToFrontendMap[platform] || platform; + + switch (frontendPlatform) { + case 'boss': + if (window.bossRecordsRoot && typeof window.bossRecordsRoot.refreshData === 'function') { + window.bossRecordsRoot.refreshData(); + } + break; + case 'zhilian': + if (window.zhilianRecordsRoot && typeof window.zhilianRecordsRoot.refreshData === 'function') { + window.zhilianRecordsRoot.refreshData(); + } + break; + case 'job51': + if (window.job51RecordsRoot && typeof window.job51RecordsRoot.refreshData === 'function') { + window.job51RecordsRoot.refreshData(); + } + break; + case 'liepin': + if (window.liepinRecordsRoot && typeof window.liepinRecordsRoot.refreshData === 'function') { + window.liepinRecordsRoot.refreshData(); + } + break; + } + } + + loadBossConfigView(force = false) { + // 已废弃:现在使用Vue应用来处理配置展示 + console.log('loadBossConfigView方法已废弃,现在使用Vue应用'); + } + + renderBossConfigView(container, data) { + // 已废弃:现在使用Vue应用来处理配置展示 + console.log('renderBossConfigView方法已废弃,现在使用Vue应用'); + } + + + + // 按顺序加载数据:先字典,后配置 + async loadDataSequentially() { + this.isDataLoading = true; // 设置数据加载状态 + try { + console.log('App.js: 开始按顺序加载数据:等待字典事件 -> 等待配置事件'); + // 优先等待由 boss-config-form.js 分发的字典加载完成事件,最短等待,避免重复请求 + await new Promise((resolve) => { + let resolved = false; + const handler = () => { + if (resolved) return; + resolved = true; + window.removeEventListener('bossDictDataLoaded', handler); + resolve(); + }; + window.addEventListener('bossDictDataLoaded', handler, { once: true }); + // 超时快速继续,避免阻塞 + setTimeout(() => { + if (resolved) return; + window.removeEventListener('bossDictDataLoaded', handler); + resolve(); + }, 1500); + }); + + console.log('App.js: 等待boss-config-form.js加载配置数据'); + await this.waitForBossConfigLoaded(); + console.log('App.js: 配置数据加载完成'); + } catch (error) { + console.error('App.js: 数据加载失败:', error); + } finally { + this.isDataLoading = false; // 数据加载完成,清除加载状态 + } + } + + // 等待boss-config-form.js加载配置数据完成,避免重复请求 + async waitForBossConfigLoaded() { + return new Promise((resolve) => { + let resolved = false; + const handler = (event) => { + if (resolved) return; + resolved = true; + try { + // 从boss-config-form.js获取已加载的配置数据 + const configData = event.detail && event.detail.config ? event.detail.config : {}; + if (configData && Object.keys(configData).length > 0) { + console.log('App.js: 从boss-config-form.js获取配置数据:', configData); + this.config = configData; + this.populateForm(); + } else { + console.log('App.js: boss-config-form.js未提供配置数据,尝试本地缓存'); + this.loadFromLocalStorage(); + } + } catch (error) { + console.warn('App.js: 处理配置数据时出错:', error); + this.loadFromLocalStorage(); + } finally { + window.removeEventListener('bossConfigLoaded', handler); + resolve(); + } + }; + + window.addEventListener('bossConfigLoaded', handler, { once: true }); + + // 超时回退到本地缓存 + setTimeout(() => { + if (resolved) return; + resolved = true; + window.removeEventListener('bossConfigLoaded', handler); + console.log('App.js: 等待配置事件超时,使用本地缓存'); + this.loadFromLocalStorage(); + resolve(); + }, 2000); + }); + } + + // 从本地缓存加载配置 + loadFromLocalStorage() { + try { + const savedConfig = localStorage.getItem('bossConfig'); + if (savedConfig) { + this.config = JSON.parse(savedConfig); + console.log('App.js: 从本地缓存加载配置完成'); + } + } catch (error) { + console.warn('App.js: 本地缓存配置损坏,已清理:', error.message); + localStorage.removeItem('bossConfig'); + } + } + + + // 处理数据库备份 + handleBackupData() { + const backupBtn = document.getElementById('backupDataBtn'); + if (backupBtn) { + backupBtn.disabled = true; + backupBtn.innerHTML = '备份中...'; + } + + fetch('https://github.com/api/backup/export', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + CommonUtils.showToast('数据库备份成功', 'success'); + console.log('备份路径:', data.backupPath); + } else { + CommonUtils.showToast('数据库备份失败: ' + data.message, 'danger'); + } + }) + .catch(error => { + console.error('备份请求失败:', error); + CommonUtils.showToast('数据库备份失败: ' + error.message, 'danger'); + }) + .finally(() => { + if (backupBtn) { + backupBtn.disabled = false; + backupBtn.innerHTML = '数据库备份'; + } + }); + } + + + +} + +class TaskStatusUpdater { + constructor(platforms, interval = 3000) { + this.platforms = platforms; + this.interval = interval; + this.timer = null; + } + + startPolling() { + this.stopPolling(); + this.pollStatus(); // Poll immediately + this.timer = setInterval(() => this.pollStatus(), this.interval); + console.log('Task status polling started.'); + } + + stopPolling() { + if (this.timer) { + clearInterval(this.timer); + this.timer = null; + console.log('Task status polling stopped.'); + } + } + + async pollStatus() { + try { + const response = await fetch('https://github.com/api/tasks/status'); + if (!response.ok) { + console.error('Failed to fetch task statuses:', response.statusText); + return; + } + const statuses = await response.json(); + this.updateUI(statuses); + } catch (error) { + console.error('Error polling task statuses:', error); + } + } + + updateUI(statuses) { + for (const key in statuses) { + const statusInfo = statuses[key]; + const { platform, stage, status, message, count } = statusInfo; + + const platformPrefix = this.platforms[platform]; + if (!platformPrefix) continue; + + let stageId; + if (platform === 'BOSS_ZHIPIN') { + // Boss platform has a simpler ID structure + stageId = `${stage.toLowerCase()}Status`; + } else { + stageId = `${platformPrefix.toLowerCase()}${stage.charAt(0) + stage.slice(1).toLowerCase()}Status`; + } + const statusElement = document.getElementById(stageId); + + if (statusElement) { + let statusText = `${message}`; + if (count > 0) { + statusText += ` (${count})`; + } + statusElement.textContent = statusText; + + statusElement.className = 'badge ms-2'; // Reset classes + switch (status) { + case 'STARTED': + statusElement.classList.add('bg-primary', 'text-white'); + break; + case 'IN_PROGRESS': + statusElement.classList.add('bg-info', 'text-dark'); + break; + case 'COMPLETED': + statusElement.classList.add('bg-success', 'text-white'); + break; + case 'FAILED': + statusElement.classList.add('bg-danger', 'text-white'); + break; + case 'SUCCESS': + statusElement.classList.add('bg-success', 'text-white'); + break; + case 'FAILURE': + statusElement.classList.add('bg-danger', 'text-white'); + break; + default: + statusElement.classList.add('bg-light', 'text-dark'); + } + } + + // 如果是LOGIN阶段,根据状态更新登录按钮和后续步骤 + if (stage === 'LOGIN') { + if (status === 'SUCCESS') { + this.enableNextSteps(platform, platformPrefix); + } else if (status === 'FAILURE') { + this.disableNextSteps(platform, platformPrefix); + } else if (status === 'IN_PROGRESS' || status === 'STARTED') { + this.updateLoginButton(platform, platformPrefix, true, false); // 禁用按钮,表示正在登录 + } + } + } + } + + // 更新登录按钮状态(登录按钮仅用于展示,不响应用户点击) + updateLoginButton(platform, platformPrefix, isLoading, isEnabled) { + // 登录按钮仅用于展示轮询检查的登录信息,无需启用/禁用 + console.log('Login button is display-only, no state change needed'); + } + + // 启用登录成功后的后续步骤 + enableNextSteps(platform, platformPrefix) { + let collectBtnId, filterBtnId, applyBtnId; + let collectStatusId, filterStatusId, applyStatusId; + + if (platform === 'BOSS_ZHIPIN') { + collectBtnId = 'collectBtn'; + filterBtnId = 'filterBtn'; + applyBtnId = 'deliverBtn'; + collectStatusId = 'collectStatus'; + filterStatusId = 'filterStatus'; + applyStatusId = 'deliverStatus'; + } else { + collectBtnId = `${platformPrefix}CollectBtn`; + filterBtnId = `${platformPrefix}FilterBtn`; + applyBtnId = `${platformPrefix}ApplyBtn`; + collectStatusId = `${platformPrefix}CollectStatus`; + filterStatusId = `${platformPrefix}FilterStatus`; + applyStatusId = `${platformPrefix}ApplyStatus`; + } + + // 启用后续步骤按钮 + const collectBtn = document.getElementById(collectBtnId); + const filterBtn = document.getElementById(filterBtnId); + const applyBtn = document.getElementById(applyBtnId); + + if (collectBtn) { + collectBtn.disabled = false; + const collectStatus = document.getElementById(collectStatusId); + if (collectStatus && collectStatus.textContent.includes('等待登录')) { + collectStatus.textContent = '可开始采集'; + } + } + + if (filterBtn) { + filterBtn.disabled = false; + const filterStatus = document.getElementById(filterStatusId); + if (filterStatus && filterStatus.textContent.includes('等待登录')) { + filterStatus.textContent = '可开始过滤'; + } + } + + if (applyBtn) { + applyBtn.disabled = false; + const applyStatus = document.getElementById(applyStatusId); + if (applyStatus && applyStatus.textContent.includes('等待登录')) { + applyStatus.textContent = '可开始投递'; + } + } + + // 同步登录状态到对应平台的配置表单实例 + this.syncLoginStatusToConfigForm(platform, platformPrefix); + } + + // 禁用后续步骤 + disableNextSteps(platform, platformPrefix) { + let collectBtnId, filterBtnId, applyBtnId; + let collectStatusId, filterStatusId, applyStatusId; + + if (platform === 'BOSS_ZHIPIN') { + collectBtnId = 'collectBtn'; + filterBtnId = 'filterBtn'; + applyBtnId = 'deliverBtn'; + collectStatusId = 'collectStatus'; + filterStatusId = 'filterStatus'; + applyStatusId = 'deliverStatus'; + } else { + collectBtnId = `${platformPrefix}CollectBtn`; + filterBtnId = `${platformPrefix}FilterBtn`; + applyBtnId = `${platformPrefix}ApplyBtn`; + collectStatusId = `${platformPrefix}CollectStatus`; + filterStatusId = `${platformPrefix}FilterStatus`; + applyStatusId = `${platformPrefix}ApplyStatus`; + } + + // 禁用后续步骤按钮 + const collectBtn = document.getElementById(collectBtnId); + const filterBtn = document.getElementById(filterBtnId); + const applyBtn = document.getElementById(applyBtnId); + + if (collectBtn) { + collectBtn.disabled = true; + const collectStatus = document.getElementById(collectStatusId); + if (collectStatus) { + collectStatus.textContent = '等待登录'; + } + } + + if (filterBtn) { + filterBtn.disabled = true; + const filterStatus = document.getElementById(filterStatusId); + if (filterStatus) { + filterStatus.textContent = '等待登录'; + } + } + + if (applyBtn) { + applyBtn.disabled = true; + const applyStatus = document.getElementById(applyStatusId); + if (applyStatus) { + applyStatus.textContent = '等待登录'; + } + } + + // 同步登录状态到对应平台的配置表单实例 + this.syncLoginStatusToConfigForm(platform, platformPrefix); + } + + // 同步登录状态到对应平台的配置表单实例 + syncLoginStatusToConfigForm(platform, platformPrefix) { + try { + // 根据平台同步状态到对应的配置表单实例 + if (platform === 'LIEPIN' && window.liepinConfigApp) { + // 触发猎聘配置表单刷新状态 + if (typeof window.liepinConfigApp.fetchAllTaskStatus === 'function') { + window.liepinConfigApp.fetchAllTaskStatus(); + console.log('已同步登录状态到猎聘配置表单'); + } + } else if (platform === 'ZHILIAN_ZHAOPIN' && window.zhilianConfigApp) { + // 触发智联配置表单刷新状态 + if (typeof window.zhilianConfigApp.fetchAllTaskStatus === 'function') { + window.zhilianConfigApp.fetchAllTaskStatus(); + console.log('已同步登录状态到智联配置表单'); + } + } else if (platform === 'JOB_51' && window.job51ConfigApp) { + // 触发51job配置表单刷新状态 + if (typeof window.job51ConfigApp.fetchAllTaskStatus === 'function') { + window.job51ConfigApp.fetchAllTaskStatus(); + console.log('已同步登录状态到51job配置表单'); + } + } + } catch (error) { + console.warn('同步登录状态到配置表单失败:', error); + } + } +} + +// 页面加载完成后初始化应用(原配置表单/记录页逻辑) +document.addEventListener('DOMContentLoaded', () => { + // 先初始化Boss配置表单(负责字典加载与事件分发) + if (window.Views && window.Views.BossConfigForm) { + window.bossConfigFormApp = new window.Views.BossConfigForm(); + } + + // 再初始化主应用(会等待字典事件) + window.bossConfigApp = new BossConfigApp(); + + // 初始化智联配置表单 + if (window.ZhilianConfigForm) { + window.zhilianConfigApp = new ZhilianConfigForm(); + } + + // 初始化51job配置表单 + if (window.Job51ConfigForm) { + window.job51ConfigApp = new Job51ConfigForm(); + } + + // 初始化猎聘配置表单 + if (window.LiepinConfigForm) { + window.liepinConfigApp = new LiepinConfigForm(); + } + + // Boss字典加载逻辑已移至 Views.BossConfigForm 中 + + // ============================= + // 初始化"求职配置"Vue 视图 + // ============================= + try { + const { createApp } = window.Vue || {}; + if (createApp && document.getElementById('bossConfigApp')) { + const app = createApp({ + data() { + return { + config: {}, + loading: false, + error: '', + cityNameByCode: {}, + }; + }, + computed: { + hasData() { + // 检查config是否存在且不为空对象 + if (!this.config || typeof this.config !== 'object') { + console.log('hasData: config不存在或不是对象'); + return false; + } + const keys = Object.keys(this.config); + console.log('hasData: config键数量=', keys.length, '键:', keys); + // 只要有任何有效数据就认为有数据,不需要检查所有字段都有值 + return keys.length > 0; + } + }, + methods: { + async loadDict() { + // 优先复用 boss-config-form.js 分发的字典事件,避免重复请求 + await new Promise((resolve) => { + let resolved = false; + const handler = (e) => { + if (resolved) return; + resolved = true; + try { + const detail = e && e.detail ? e.detail : {}; + const groupMap = detail.groupMap; + // groupMap 可能是 Map,也可能是普通对象(容错处理) + let cityItems = []; + if (groupMap && typeof groupMap.get === 'function') { + cityItems = groupMap.get('cityList') || []; + } else if (groupMap && typeof groupMap === 'object') { + cityItems = groupMap['cityList'] || []; + } + const map = {}; + (Array.isArray(cityItems) ? cityItems : []).forEach(it => { + if (it && it.code) map[String(it.code)] = it.name || String(it.code); + }); + this.cityNameByCode = map; + } catch (_) { + this.cityNameByCode = {}; + } + window.removeEventListener('bossDictDataLoaded', handler); + resolve(); + }; + window.addEventListener('bossDictDataLoaded', handler, { once: true }); + // 超时快速回退到本地fetch,避免阻塞 + setTimeout(async () => { + if (resolved) return; + window.removeEventListener('bossDictDataLoaded', handler); + try { + const res = await fetch('https://github.com/dicts/BOSS_ZHIPIN'); + if (res.ok) { + const data = await res.json(); + const cityGroup = (data && Array.isArray(data.groups)) ? data.groups.find(g => g.key === 'cityList') : null; + const map = {}; + (Array.isArray(cityGroup?.items) ? cityGroup.items : []).forEach(it => { + if (it && it.code) map[String(it.code)] = it.name || String(it.code); + }); + this.cityNameByCode = map; + } else { + this.cityNameByCode = {}; + } + } catch (_) { + this.cityNameByCode = {}; + } finally { + resolved = true; + resolve(); + } + }, 800); + }); + }, + async load(force = false) { + console.log('开始加载配置数据'); + this.loading = true; + this.error = ''; + try { + // 预先加载城市字典(不阻塞后续渲染) + await this.loadDict(); + const res = await fetch('https://github.com/api/config/boss'); + console.log('API响应状态:', res.status); + if (!res.ok) throw new Error('HTTP ' + res.status); + const ct = res.headers.get('content-type') || ''; + console.log('Content-Type:', ct); + const text = await res.text(); + console.log('响应文本长度:', text.length); + // 仅当明确为 JSON 时解析;否则直接报错并给出片段提示,避免 JSON.parse 抛出 SyntaxError + if (!ct.includes('application/json')) { + const snippet = (text || '').slice(0, 120); + // 明确判断 HTML 场景 + if (/^\s* this.cityNameByCode[String(v)] || String(v)).join('、'); + } + const codes = String(value).split(',').map(s => s.trim()).filter(Boolean); + if (codes.length === 0) return ''; + return codes.map(c => this.cityNameByCode[c] || c).join('、'); + }, + safe(v) { + if (Array.isArray(v)) return v.join('、'); + return (v ?? '').toString(); + }, + join(v) { + if (Array.isArray(v)) return v.join('、'); + return v ?? ''; + }, + formatDateTime(input) { + try { + if (!input) return ''; + const d = new Date(input); + if (isNaN(d.getTime())) return ''; + const pad = (n) => (n < 10 ? '0' + n : '' + n); + return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; + } catch (_) { return ''; } + }, + formatKVList(obj) { + if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return ''; + const entries = Object.entries(obj); + if (!entries.length) return ''; + return entries.map(([k, v]) => `${k} - ${v}`).join('
    '); + } + }, + mounted() { + console.log('Vue应用已挂载'); + // 如果一进来就处于该页签,可立即加载 + const pane = document.getElementById('boss-current-pane'); + if (pane && pane.classList.contains('show') && pane.classList.contains('active')) { + console.log('当前页签已激活,立即加载数据'); + this.load(false); + } + } + }); + window.bossConfigView = app.mount('#bossConfigApp'); + } + } catch (_) {} + + // ============================= + // 初始化"岗位明细"Vue 视图 + // ============================= + try { + if (window.Views && window.Views.BossRecordsVue) { + window.bossRecordsVue = new window.Views.BossRecordsVue(); + } + } catch (error) { + console.error('初始化Boss岗位明细Vue应用失败:', error); + } + + try { + if (window.Views && window.Views.ZhilianRecordsVue) { + window.zhilianRecordsVue = new window.Views.ZhilianRecordsVue(); + } + } catch (error) { + console.error('初始化智联岗位明细Vue应用失败:', error); + } + + try { + if (window.Views && window.Views.LiepinRecordsVue) { + window.liepinRecordsVue = new window.Views.LiepinRecordsVue(); + } + } catch (error) { + console.error('初始化猎聘岗位明细Vue应用失败:', error); + } + + const platforms = { + 'BOSS_ZHIPIN': 'boss', + 'ZHILIAN_ZHAOPIN': 'zhilian', + 'JOB_51': 'job51', + 'LIEPIN': 'liepin' + }; + const taskStatusUpdater = new TaskStatusUpdater(platforms); + taskStatusUpdater.startPolling(); +}); + + +// 注意:不要重复创建Vue应用,已在上面的DOMContentLoaded中创建 diff --git a/src/main/resources/static/js/common.js b/src/main/resources/static/js/common.js new file mode 100644 index 00000000..84b5fecc --- /dev/null +++ b/src/main/resources/static/js/common.js @@ -0,0 +1,171 @@ +// 公共工具方法和基础类 +class CommonUtils { + // 工具:简易转义 + static escapeHtml(str) { + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + // 工具:时间格式化(ISO 或时间戳) + static formatDateTime(input) { + try { + if (!input) return ''; + const d = new Date(input); + if (isNaN(d.getTime())) return ''; + const pad = (n) => (n < 10 ? '0' + n : '' + n); + return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; + } catch (_) { return ''; } + } + + // 显示全局Toast + static showToast(message, variant = 'success') { + try { + const toastEl = document.getElementById('globalToast'); + const bodyEl = document.getElementById('globalToastBody'); + if (!toastEl || !bodyEl) return; + bodyEl.textContent = message || '操作成功'; + // 根据 variant 调整背景色 + const variants = ['success', 'danger', 'warning', 'info', 'primary', 'secondary', 'dark']; + variants.forEach(v => toastEl.classList.remove(`text-bg-${v}`)); + toastEl.classList.add(`text-bg-${variant}`); + const toast = bootstrap.Toast.getOrCreateInstance(toastEl, { delay: 2000 }); + toast.show(); + } catch (_) {} + } + + // 显示确认对话框 + static showConfirmModal(title, message, onConfirm, onCancel) { + const modal = new bootstrap.Modal(document.getElementById('confirmModal')); + document.getElementById('confirmModalLabel').textContent = title; + document.getElementById('confirmModalBody').textContent = message; + + // 移除之前的事件监听器 + const okBtn = document.getElementById('confirmModalOk'); + const newOkBtn = okBtn.cloneNode(true); + okBtn.parentNode.replaceChild(newOkBtn, okBtn); + + // 添加新的事件监听器 + newOkBtn.addEventListener('click', () => { + modal.hide(); + if (onConfirm) onConfirm(); + }); + + // 绑定取消事件 + modal._element.addEventListener('hidden.bs.modal', () => { + if (onCancel) onCancel(); + }, { once: true }); + + modal.show(); + } + + // 显示提示对话框 + static showAlertModal(title, message) { + const modal = new bootstrap.Modal(document.getElementById('alertModal')); + document.getElementById('alertModalLabel').textContent = title; + document.getElementById('alertModalBody').textContent = message; + modal.show(); + } + + // 显示输入对话框 + static showInputModal(title, label1, placeholder1, label2, placeholder2, onConfirm) { + const modal = new bootstrap.Modal(document.getElementById('inputModal')); + const field2Container = document.getElementById('inputModalField2Container'); + + // 设置标题 + document.getElementById('inputModalLabel').textContent = title; + + // 设置第一个输入框 + document.getElementById('inputModalLabel1').textContent = label1; + const field1 = document.getElementById('inputModalField1'); + field1.placeholder = placeholder1; + field1.value = ''; + + // 设置第二个输入框(如果提供) + if (label2 && placeholder2) { + field2Container.style.display = 'block'; + document.getElementById('inputModalLabel2').textContent = label2; + const field2 = document.getElementById('inputModalField2'); + field2.placeholder = placeholder2; + field2.value = ''; + } else { + field2Container.style.display = 'none'; + } + + // 移除之前的事件监听器 + const okBtn = document.getElementById('inputModalOk'); + const newOkBtn = okBtn.cloneNode(true); + okBtn.parentNode.replaceChild(newOkBtn, okBtn); + + // 添加新的事件监听器 + newOkBtn.addEventListener('click', () => { + const value1 = field1.value.trim(); + const value2 = field2Container.style.display !== 'none' ? + document.getElementById('inputModalField2').value.trim() : null; + + modal.hide(); + if (onConfirm) onConfirm(value1, value2); + }); + + modal.show(); + } + + // 初始化工具提示 + static initializeTooltips() { + const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); + tooltipTriggerList.map(function (tooltipTriggerEl) { + return new bootstrap.Tooltip(tooltipTriggerEl); + }); + } +} + +// 导出配置功能 +function exportConfig() { + const config = (window.bossConfigApp && window.bossConfigApp.config) || {}; + const dataStr = JSON.stringify(config, null, 2); + const dataBlob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(dataBlob); + + const link = document.createElement('a'); + link.href = url; + link.download = 'boss-config.json'; + link.click(); + + URL.revokeObjectURL(url); +} + +// 导入配置功能 +function importConfig() { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.json'; + + input.onchange = (event) => { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + try { + const config = JSON.parse(e.target.result); + if (window.bossConfigApp) { + window.bossConfigApp.config = config; + if (typeof window.bossConfigApp.populateForm === 'function') { + window.bossConfigApp.populateForm(); + } + CommonUtils.showAlertModal('导入成功', '配置已导入'); + } + } catch (error) { + if (window.bossConfigApp) { + CommonUtils.showAlertModal('导入失败', '配置导入失败: ' + error.message); + } + } + }; + reader.readAsText(file); + } + }; + + input.click(); +} diff --git a/src/main/resources/static/js/components/tags-input.js b/src/main/resources/static/js/components/tags-input.js new file mode 100644 index 00000000..e4b8f599 --- /dev/null +++ b/src/main/resources/static/js/components/tags-input.js @@ -0,0 +1,200 @@ +/** + * 标签输入组件 + * 支持回车添加标签,点击×删除标签 + */ + +class TagsInput { + constructor(inputElement, tagsContainerElement) { + this.input = inputElement; + this.tagsContainer = tagsContainerElement; + this.tags = []; + + this.init(); + } + + /** + * 初始化组件 + */ + init() { + // 绑定事件 + this.input.addEventListener('keydown', (e) => this.handleKeyDown(e)); + + // 点击容器时聚焦到输入框 + const container = this.tagsContainer.parentElement; + container.addEventListener('click', () => { + this.input.focus(); + }); + } + + /** + * 处理键盘事件 + */ + handleKeyDown(e) { + if (e.key === 'Enter') { + e.preventDefault(); + this.addTag(); + } else if (e.key === 'Backspace' && this.input.value === '' && this.tags.length > 0) { + // 当输入框为空且按下退格键时,删除最后一个标签 + this.removeTag(this.tags.length - 1); + } + } + + /** + * 添加标签 + */ + addTag() { + const value = this.input.value.trim(); + + // 验证输入 + if (!value) { + return; + } + + // 检查重复 + if (this.tags.includes(value)) { + this.input.value = ''; + this.showDuplicateAnimation(); + return; + } + + // 添加到数组 + this.tags.push(value); + + // 渲染标签 + this.renderTag(value, this.tags.length - 1); + + // 清空输入框 + this.input.value = ''; + + // 触发变更事件 + this.triggerChange(); + } + + /** + * 渲染单个标签 + */ + renderTag(value, index) { + const tagElement = document.createElement('div'); + tagElement.className = 'tag-item'; + tagElement.dataset.index = index; + + const textSpan = document.createElement('span'); + textSpan.className = 'tag-text'; + textSpan.textContent = value; + + const removeBtn = document.createElement('span'); + removeBtn.className = 'tag-remove'; + removeBtn.innerHTML = '×'; + removeBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this.removeTag(index); + }); + + tagElement.appendChild(textSpan); + tagElement.appendChild(removeBtn); + + this.tagsContainer.appendChild(tagElement); + } + + /** + * 删除标签 + */ + removeTag(index) { + // 从数组中删除 + this.tags.splice(index, 1); + + // 重新渲染所有标签 + this.renderAllTags(); + + // 触发变更事件 + this.triggerChange(); + } + + /** + * 重新渲染所有标签 + */ + renderAllTags() { + this.tagsContainer.innerHTML = ''; + this.tags.forEach((tag, index) => { + this.renderTag(tag, index); + }); + } + + /** + * 显示重复动画 + */ + showDuplicateAnimation() { + const container = this.tagsContainer.parentElement; + container.style.animation = 'shake 0.3s ease'; + setTimeout(() => { + container.style.animation = ''; + }, 300); + } + + /** + * 获取所有标签(以逗号分隔的字符串形式) + */ + getValue() { + return this.tags.join(','); + } + + /** + * 获取所有标签(以数组形式) + */ + getTags() { + return [...this.tags]; + } + + /** + * 设置标签(从逗号或换行分隔的字符串) + */ + setValue(value) { + if (!value) { + this.tags = []; + this.renderAllTags(); + return; + } + + // 支持逗号和换行分隔 + const keywords = value.split(/[,\n]/) + .map(k => k.trim()) + .filter(k => k !== ''); + + this.tags = [...new Set(keywords)]; // 去重 + this.renderAllTags(); + } + + /** + * 设置标签(从数组) + */ + setTags(tags) { + this.tags = [...new Set(tags)]; // 去重 + this.renderAllTags(); + } + + /** + * 清空所有标签 + */ + clear() { + this.tags = []; + this.renderAllTags(); + this.triggerChange(); + } + + /** + * 触发变更事件 + */ + triggerChange() { + const event = new CustomEvent('tagschange', { + detail: { + tags: this.getTags(), + value: this.getValue() + } + }); + this.input.dispatchEvent(event); + } +} + +// 导出到全局 +window.TagsInput = TagsInput; + diff --git a/src/main/resources/static/js/views/ai-prompt.js b/src/main/resources/static/js/views/ai-prompt.js new file mode 100644 index 00000000..d28f7805 --- /dev/null +++ b/src/main/resources/static/js/views/ai-prompt.js @@ -0,0 +1,244 @@ +document.addEventListener('DOMContentLoaded', () => { + const aiPromptPanel = document.getElementById('aiPromptPanel'); + const aiPromptHandle = document.querySelector('.ai-prompt-handle'); + + // Toggle panel visibility + if (aiPromptHandle) { + aiPromptHandle.addEventListener('click', () => { + aiPromptPanel.classList.toggle('show'); + }); + } + + // 初始化候选人信息表单 + initializeProfileForm(); + + // 初始化打招呼内容 + initializeGreetingForm(); + + // 保存候选人信息按钮事件 + const saveAiPromptBtn = document.getElementById('saveAiPromptBtn'); + if (saveAiPromptBtn) { + saveAiPromptBtn.addEventListener('click', saveProfileData); + } +}); + +// 初始化候选人信息表单 +function initializeProfileForm() { + // 加载已保存的候选人信息 + loadProfileData(); + + // 为表单字段添加实时验证 + const requiredFields = ['profileRole', 'profileYears']; + requiredFields.forEach(fieldId => { + const field = document.getElementById(fieldId); + if (field) { + field.addEventListener('blur', validateRequiredField); + } + }); +} + +// 初始化打招呼内容 +function initializeGreetingForm() { + // 加载已保存的打招呼内容 + loadGreetingContent(); +} + +// 验证必填字段 +function validateRequiredField(event) { + const field = event.target; + const value = field.value.trim(); + + if (!value) { + field.classList.add('is-invalid'); + field.classList.remove('is-valid'); + } else { + field.classList.remove('is-invalid'); + field.classList.add('is-valid'); + } +} + +// 收集候选人信息表单数据 +function collectProfileData() { + const profileData = { + role: document.getElementById('profileRole')?.value?.trim() || '', + years: parseInt(document.getElementById('profileYears')?.value) || 0, + domains: parseStringToList(document.getElementById('profileDomains')?.value), + coreStack: parseStringToList(document.getElementById('profileCoreStack')?.value), + scale: { + qps_peak: document.getElementById('profileQpsPeak')?.value?.trim() || '', + sla: document.getElementById('profileSla')?.value?.trim() || '' + }, + achievements: parseStringToList(document.getElementById('profileAchievements')?.value, ';'), + strengths: parseStringToList(document.getElementById('profileStrengths')?.value), + improvements: parseStringToList(document.getElementById('profileImprovements')?.value), + availability: document.getElementById('profileAvailability')?.value?.trim() || '', + links: { + github: document.getElementById('profileGithub')?.value?.trim() || '', + portfolio: document.getElementById('profilePortfolio')?.value?.trim() || '' + } + }; + + return profileData; +} + +// 解析字符串为列表 +function parseStringToList(str, separator = ',') { + if (!str || !str.trim()) { + return []; + } + return str.split(separator) + .map(item => item.trim()) + .filter(item => item.length > 0); +} + +// 验证候选人信息 +function validateProfileData(profileData) { + const errors = []; + + if (!profileData.role) { + errors.push('角色/职位不能为空'); + } + + if (profileData.years <= 0) { + errors.push('工作年限必须大于0'); + } + + return errors; +} + +// 保存候选人信息 +function saveProfileData() { + const profileData = collectProfileData(); + const errors = validateProfileData(profileData); + + if (errors.length > 0) { + showToast('请检查以下错误:\n' + errors.join('\n'), 'error'); + return; + } + + // 显示保存中的状态 + const saveBtn = document.getElementById('saveAiPromptBtn'); + const originalText = saveBtn.innerHTML; + saveBtn.innerHTML = '保存中...'; + saveBtn.disabled = true; + + // 发送到后端保存 + fetch('https://github.com/api/ai/profile', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(profileData), + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(result => { + showToast('候选人信息保存成功!', 'success'); + // 本地存储 + localStorage.setItem('candidateProfile', JSON.stringify(profileData)); + }) + .catch((error) => { + console.error('Error saving profile:', error); + showToast('候选人信息保存失败!', 'error'); + }) + .finally(() => { + // 恢复按钮状态 + saveBtn.innerHTML = originalText; + saveBtn.disabled = false; + }); +} + +// 加载候选人信息 +function loadProfileData() { + // 先从本地存储加载 + const savedProfile = localStorage.getItem('candidateProfile'); + if (savedProfile) { + try { + const profileData = JSON.parse(savedProfile); + populateProfileForm(profileData); + } catch (error) { + console.error('Error parsing saved profile:', error); + } + } + + // 再从后端加载 + fetch('https://github.com/api/ai/profile') + .then(response => response.json()) + .then(profileData => { + if (profileData && Object.keys(profileData).length > 0) { + populateProfileForm(profileData); + } + }) + .catch(error => { + console.error('Error loading profile from server:', error); + }); +} + +// 填充候选人信息表单 +function populateProfileForm(profileData) { + if (!profileData) return; + + // 基本字段 + setFieldValue('profileRole', profileData.role); + setFieldValue('profileYears', profileData.years); + setFieldValue('profileDomains', profileData.domains?.join(', ')); + setFieldValue('profileCoreStack', profileData.coreStack?.join(', ')); + setFieldValue('profileAchievements', profileData.achievements?.join('; ')); + setFieldValue('profileStrengths', profileData.strengths?.join(', ')); + setFieldValue('profileImprovements', profileData.improvements?.join(', ')); + setFieldValue('profileAvailability', profileData.availability); + + // 项目规模 + if (profileData.scale) { + setFieldValue('profileQpsPeak', profileData.scale.qps_peak); + setFieldValue('profileSla', profileData.scale.sla); + } + + // 个人链接 + if (profileData.links) { + setFieldValue('profileGithub', profileData.links.github); + setFieldValue('profilePortfolio', profileData.links.portfolio); + } +} + +// 设置字段值 +function setFieldValue(fieldId, value) { + const field = document.getElementById(fieldId); + if (field && value !== undefined && value !== null) { + field.value = value; + } +} + +// 加载打招呼内容 +function loadGreetingContent() { + // 从本地存储加载 + const savedGreeting = localStorage.getItem('aiGreetingContent'); + if (savedGreeting) { + const greetingField = document.getElementById('aiGreetingContent'); + if (greetingField) { + greetingField.value = savedGreeting; + } + } +} + +// 显示提示消息 +function showToast(message, type = 'success') { + const toast = new bootstrap.Toast(document.getElementById('globalToast')); + const toastBody = document.getElementById('globalToastBody'); + const toastElement = document.getElementById('globalToast'); + + if (type === 'error') { + toastElement.className = 'toast align-items-center text-bg-danger border-0'; + } else if (type === 'warning') { + toastElement.className = 'toast align-items-center text-bg-warning border-0'; + } else { + toastElement.className = 'toast align-items-center text-bg-success border-0'; + } + + toastBody.textContent = message; + toast.show(); +} diff --git a/src/main/resources/static/js/views/boss-config-form.js b/src/main/resources/static/js/views/boss-config-form.js new file mode 100644 index 00000000..e6f7e975 --- /dev/null +++ b/src/main/resources/static/js/views/boss-config-form.js @@ -0,0 +1,1659 @@ +// Boss 配置表单模块(导出类,不自动初始化) +(function () { + if (!window.Views) window.Views = {}; + + class BossConfigApp { + constructor() { + this.config = {}; + this.isRunning = false; + this.dictDataLoaded = false; // 字典数据加载状态标志 + this.taskStates = { + loginTaskId: null, + collectTaskId: null, + filterTaskId: null, + applyTaskId: null + }; + this.statusPollingInterval = null; // 状态轮询定时器 + this.latestTaskStatus = null; // 缓存最新的任务状态查询结果 + this.hrStatusTagsInput = null; // HR状态标签输入组件 + this.init(); + } + + init() { + this.initializeTooltips(); + this.initializeTagsInput(); + this.bindEvents(); + // 先加载字典数据,再加载配置数据,确保下拉框已准备好 + this.loadDataSequentially(); + // 初始化时启动状态轮询,确保能及时获取到登录状态 + this.startStatusPolling(); + } + + initializeTagsInput() { + // 初始化HR状态标签输入组件 + const hrStatusInput = document.getElementById('bossHrStatusKeywords'); + const hrStatusWrapper = document.getElementById('hrStatusTags'); + if (hrStatusInput && hrStatusWrapper) { + this.hrStatusTagsInput = new window.TagsInput(hrStatusInput, hrStatusWrapper); + } + } + + initializeTooltips() { + const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); + tooltipTriggerList.map(function (tooltipTriggerEl) { + return new bootstrap.Tooltip(tooltipTriggerEl); + }); + } + + bindEvents() { + document.getElementById('saveConfigBtn')?.addEventListener('click', () => { + this.handleSaveOnly(); + }); + document.getElementById('backupDataBtn')?.addEventListener('click', () => { + this.handleBackupData(); + }); + + // 任务执行按钮 + document.getElementById('loginBtn')?.addEventListener('click', () => { + this.handleLogin(); + }); + document.getElementById('loginManualBtn')?.addEventListener('click', () => { + this.handleManualLogin(); + }); + document.getElementById('collectBtn')?.addEventListener('click', () => { + this.handleCollect(); + }); + document.getElementById('filterBtn')?.addEventListener('click', () => { + this.handleFilter(); + }); + document.getElementById('deliverBtn')?.addEventListener('click', () => { + this.handleApply(); + }); + document.getElementById('resetTasksBtn')?.addEventListener('click', () => { + this.resetTaskFlow(); + }); + + document.getElementById('sendImgResumeCheckBox')?.addEventListener('change', (event) => { + const resumeField = document.getElementById('resumeImagePathField'); + if (resumeField && !event.target.checked) { + // If unchecked, remove validation state + resumeField.classList.remove('is-invalid', 'is-valid'); + } + }); + + this.bindFormValidation(); + this.bindAdvancedConfig(); + } + + bindFormValidation() { + const requiredFields = [ + 'keywordsField', + 'cityCodeField', + 'sayHiTextArea' + ]; + requiredFields.forEach(fieldId => { + const field = document.getElementById(fieldId); + if (field) { + field.addEventListener('blur', () => { + this.validateField(field); + }); + } + }); + const minSalary = document.getElementById('minSalaryField'); + const maxSalary = document.getElementById('maxSalaryField'); + if (minSalary && maxSalary) { + [minSalary, maxSalary].forEach(field => { + field.addEventListener('input', () => { + this.validateSalaryRange(); + }); + }); + } + } + + validateField(field) { + const value = field.value.trim(); + const isValid = value.length > 0; + this.updateFieldValidation(field, isValid); + return isValid; + } + + validateSalaryRange() { + const minSalary = document.getElementById('minSalaryField'); + const maxSalary = document.getElementById('maxSalaryField'); + const minValue = parseInt(minSalary.value) || 0; + const maxValue = parseInt(maxSalary.value) || 0; + const isValid = minValue > 0 && maxValue > 0 && minValue <= maxValue; + this.updateFieldValidation(minSalary, isValid); + this.updateFieldValidation(maxSalary, isValid); + return isValid; + } + + + updateFieldValidation(field, isValid) { + if (isValid) { + field.classList.remove('is-invalid'); + field.classList.add('is-valid'); + } else { + field.classList.remove('is-valid'); + field.classList.add('is-invalid'); + } + } + + bindAdvancedConfig() { + this.bindCityCodeConfig(); + this.bindHRStatusConfig(); + } + + bindCityCodeConfig() { + // 城市配置相关功能已移除,通过字典接口动态加载 + } + + bindHRStatusConfig() { + // HR状态配置已通过 TagsInput 组件实现 + // 相关初始化在 initializeTagsInput() 方法中完成 + } + + addCityCodeItem(container, city, code) { + const item = document.createElement('div'); + item.className = 'd-flex justify-content-between align-items-center mb-2 p-2 bg-white rounded border'; + item.innerHTML = ` + ${city} - ${code} + + `; + container.appendChild(item); + } + + showAddCityCodeModal() { + const city = prompt('请输入城市名称:'); + const code = prompt('请输入城市代码:'); + if (city && code) { + const container = document.getElementById('customCityCodeContainer'); + this.addCityCodeItem(container, city, code); + } + } + + + bindAutoSave() { + const formElements = document.querySelectorAll('input, select, textarea'); + formElements.forEach(element => { + element.addEventListener('change', () => { + this.saveConfig(); + }); + }); + } + + saveConfig() { + const getMultiSelectValues = (selectId) => { + const el = document.getElementById(selectId); + if (!el) return ''; + return Array.from(el.selectedOptions).map(o => o.value).filter(Boolean).join(','); + }; + + this.config = { + keywords: document.getElementById('keywordsField').value, + industry: getMultiSelectValues('industryField'), + cityCode: getMultiSelectValues('cityCodeField'), + experience: document.getElementById('experienceComboBox').value, + jobType: document.getElementById('jobTypeComboBox').value, + salary: document.getElementById('salaryComboBox').value, + degree: document.getElementById('degreeComboBox').value, + scale: document.getElementById('scaleComboBox').value, + stage: document.getElementById('stageComboBox').value, + minSalary: document.getElementById('minSalaryField').value, + maxSalary: document.getElementById('maxSalaryField').value, + resumeImagePath: document.getElementById('resumeImagePathField').value, + sayHi: document.getElementById('sayHiTextArea').value, + filterDeadHR: document.getElementById('filterDeadHRCheckBox').checked, + sendImgResume: document.getElementById('sendImgResumeCheckBox').checked, + recommendJobs: document.getElementById('recommendJobsCheckBox').checked, + enableAIJobMatchDetection: document.getElementById('enableAIJobMatchDetectionCheckBox').checked, + enableAIGreeting: document.getElementById('enableAIGreetingCheckBox').checked, + checkStateOwned: document.getElementById('checkStateOwnedCheckBox').checked + }; + localStorage.setItem('bossConfig', JSON.stringify(this.config)); + try { + fetch('https://github.com/api/config/boss', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(this.config) + }).then(() => {}).catch(() => {}); + } catch (e) {} + } + + // 按顺序加载数据:先字典,后配置 + async loadDataSequentially() { + try { + console.log('BossConfigForm: 开始按顺序加载数据:字典 -> 配置'); + + // 先加载字典数据 + await this.loadBossDicts(); + console.log('BossConfigForm: 字典数据加载完成,开始加载配置数据'); + + // 等待DOM元素完全渲染 + await this.waitForDOMReady(); + + // 再加载配置数据 + await this.loadSavedConfig(); + console.log('BossConfigForm: 配置数据加载完成'); + + // 在所有数据加载并填充表单后,再绑定自动保存 + this.bindAutoSave(); + } catch (error) { + console.error('BossConfigForm: 数据加载失败:', error); + } + } + + // 等待DOM元素完全准备就绪 + async waitForDOMReady() { + return new Promise((resolve) => { + // 等待一个事件循环,确保所有DOM操作完成 + setTimeout(() => { + console.log('BossConfigForm: DOM元素准备就绪'); + resolve(); + }, 100); + }); + } + + // 加载保存的配置 + async loadSavedConfig() { + try { + console.log('BossConfigForm: 开始加载配置数据...'); + + const res = await fetch('https://github.com/api/config/boss'); + if (!res.ok) throw new Error('HTTP ' + res.status); + const ct = res.headers.get('content-type') || ''; + let data; + if (ct.includes('application/json')) { + data = await res.json(); + } else { + const text = await res.text(); + const snippet = (text || '').slice(0, 80); + throw new Error('返回非JSON:' + snippet); + } + + if (data && typeof data === 'object' && Object.keys(data).length) { + this.config = data; + console.log('BossConfigForm: 从后端加载到配置数据:', this.config); + + // 确保字典数据已加载后再填充表单 + await this.waitForDictDataReady(); + this.populateForm(); + + localStorage.setItem('bossConfig', JSON.stringify(this.config)); + + // 触发配置加载完成事件,通知app.js + this.dispatchConfigLoadedEvent(); + return; + } + + // 如果后端没有数据,尝试本地缓存 + const savedConfig = localStorage.getItem('bossConfig'); + if (savedConfig) { + try { + this.config = JSON.parse(savedConfig); + console.log('BossConfigForm: 从本地缓存加载到配置数据:', this.config); + + // 确保字典数据已加载后再填充表单 + await this.waitForDictDataReady(); + this.populateForm(); + + // 触发配置加载完成事件,通知app.js + this.dispatchConfigLoadedEvent(); + } catch (error) { + console.warn('本地配置损坏,已清理:' + error.message); + localStorage.removeItem('bossConfig'); + } + } + } catch (err) { + console.warn('后端配置读取失败:' + (err?.message || '未知错误')); + // 尝试本地缓存 + const savedConfig = localStorage.getItem('bossConfig'); + if (savedConfig) { + try { + this.config = JSON.parse(savedConfig); + console.log('BossConfigForm: 从本地缓存加载到配置数据(异常情况):', this.config); + + // 确保字典数据已加载后再填充表单 + await this.waitForDictDataReady(); + this.populateForm(); + + // 触发配置加载完成事件,通知app.js + this.dispatchConfigLoadedEvent(); + } catch (error) { + console.warn('本地配置损坏,已清理:' + error.message); + localStorage.removeItem('bossConfig'); + } + } + } + } + + // 触发配置加载完成事件,通知app.js + dispatchConfigLoadedEvent() { + try { + console.log('BossConfigForm: 触发配置加载完成事件'); + window.dispatchEvent(new CustomEvent('bossConfigLoaded', { + detail: { config: this.config } + })); + } catch (error) { + console.error('BossConfigForm: 触发配置事件失败:', error); + } + } + + // 等待字典数据准备就绪 + async waitForDictDataReady() { + return new Promise(async (resolve) => { + // 首先等待字典数据加载事件 + if (!this.dictDataLoaded) { + console.log('BossConfigForm: 等待字典数据加载完成...'); + + // 监听字典数据加载完成事件 + const handleDictLoaded = () => { + console.log('BossConfigForm: 收到字典数据加载完成事件'); + window.removeEventListener('bossDictDataLoaded', handleDictLoaded); + // 继续等待DOM完全渲染 + this.waitForAllDictDataReady().then(resolve); + }; + + window.addEventListener('bossDictDataLoaded', handleDictLoaded); + + // 设置超时,避免无限等待 + setTimeout(() => { + console.warn('BossConfigForm: 等待字典数据超时,强制继续'); + window.removeEventListener('bossDictDataLoaded', handleDictLoaded); + this.waitForAllDictDataReady().then(resolve); + }, 5000); + } else { + // 字典数据已加载,等待DOM完全渲染 + console.log('BossConfigForm: 字典数据已就绪,等待DOM完全渲染'); + await this.waitForAllDictDataReady(); + resolve(); + } + }); + } + + populateForm() { + console.log('BossConfigForm: 开始填充表单,配置数据:', this.config); + + // 先处理普通字段 + Object.keys(this.config).forEach(key => { + const element = document.getElementById(this.getFieldId(key)); + if (element) { + if (element.type === 'checkbox') { + element.checked = this.config[key]; + console.log(`BossConfigForm: 设置复选框 ${key} = ${this.config[key]}`); + } else { + // 处理可能的数组字段转换为逗号分隔字符串 + let value = this.config[key]; + if (Array.isArray(value)) { + value = value.join(','); + console.log(`BossConfigForm: 数组字段 ${key} 转换为字符串:`, this.config[key], '->', value); + } + element.value = value || ''; + console.log(`BossConfigForm: 设置字段 ${key} = ${value}`); + } + } + }); + + // 特殊处理城市选择器 + this.populateCitySelector(); + + // 特殊处理行业选择器 + this.populateIndustrySelector(); + + // 特殊处理期望薪资字段 + this.populateExpectedSalary(); + + // 特殊处理其他下拉框 + this.populateSelectBoxes(); + + // 特殊处理HR状态标签 + this.populateHrStatusTags(); + } + + // 填充HR状态标签 + populateHrStatusTags() { + if (this.hrStatusTagsInput && this.config.deadStatus) { + // 处理数组格式或逗号分隔的字符串 + let statusArray = []; + if (Array.isArray(this.config.deadStatus)) { + statusArray = this.config.deadStatus; + } else if (typeof this.config.deadStatus === 'string') { + statusArray = this.config.deadStatus.split(',').map(s => s.trim()).filter(Boolean); + } + console.log('BossConfigForm: 填充HR状态标签:', statusArray); + this.hrStatusTagsInput.setTags(statusArray); + } + } + + // 填充城市选择器 + populateCitySelector() { + const cityCode = this.config.cityCode; + if (!cityCode) return; + + // 处理数组格式(从后端返回)或字符串格式(从本地缓存) + let cityCodeStr = ''; + if (Array.isArray(cityCode)) { + cityCodeStr = cityCode.join(','); + } else { + cityCodeStr = cityCode; + } + + console.log('BossConfigForm: 填充城市选择器,原始城市代码:', cityCode, '处理后:', cityCodeStr); + + const citySelect = document.getElementById('cityCodeField'); + const cityDropdownBtn = document.getElementById('cityDropdownBtn'); + const citySummary = document.getElementById('citySelectionSummary'); + + if (!citySelect) { + console.warn('BossConfigForm: 未找到城市选择器元素'); + return; + } + + // 解析城市代码(支持逗号分隔的多个城市) + const codes = cityCodeStr.split(',').map(s => s.trim()).filter(Boolean); + console.log('BossConfigForm: 解析的城市代码:', codes); + + // 设置隐藏select的选中状态 + Array.from(citySelect.options).forEach(opt => { + opt.selected = codes.includes(opt.value); + }); + + // 更新下拉框显示状态 + this.updateCityDropdownDisplay(); + + // 更新城市摘要 + if (typeof this.updateCitySummary === 'function') { + this.updateCitySummary(); + } else { + this.updateCitySummaryFallback(); + } + } + + // 更新城市下拉框显示状态 + updateCityDropdownDisplay() { + const citySelect = document.getElementById('cityCodeField'); + const cityListContainer = document.getElementById('cityDropdownList'); + + if (!citySelect || !cityListContainer) return; + + // 更新checkbox状态 + const checkboxes = cityListContainer.querySelectorAll('input[type="checkbox"]'); + checkboxes.forEach(checkbox => { + const option = Array.from(citySelect.options).find(o => o.value === checkbox.value); + if (option) { + checkbox.checked = option.selected; + } + }); + } + + // 更新城市摘要显示 + updateCitySummary() { + const cityDropdownBtn = document.getElementById('cityDropdownBtn'); + const citySummary = document.getElementById('citySelectionSummary'); + const citySelect = document.getElementById('cityCodeField'); + + if (!cityDropdownBtn || !citySummary || !citySelect) return; + + const selectedOptions = Array.from(citySelect.selectedOptions); + const values = selectedOptions.map(o => o.textContent); + + if (values.length === 0) { + cityDropdownBtn.textContent = '选择城市'; + citySummary.textContent = '未选择'; + } else if (values.length <= 2) { + const text = values.join('、'); + cityDropdownBtn.textContent = text; + citySummary.textContent = `已选 ${values.length} 项:${text}`; + } else { + cityDropdownBtn.textContent = `已选 ${values.length} 项`; + citySummary.textContent = `已选 ${values.length} 项`; + } + } + + // 备用的城市摘要更新方法 + updateCitySummaryFallback() { + const cityDropdownBtn = document.getElementById('cityDropdownBtn'); + const citySummary = document.getElementById('citySelectionSummary'); + const citySelect = document.getElementById('cityCodeField'); + + if (!cityDropdownBtn || !citySummary || !citySelect) return; + + const selectedOptions = Array.from(citySelect.selectedOptions); + const values = selectedOptions.map(o => o.textContent); + + if (values.length === 0) { + cityDropdownBtn.textContent = '选择城市'; + citySummary.textContent = '未选择'; + } else if (values.length <= 2) { + const text = values.join('、'); + cityDropdownBtn.textContent = text; + citySummary.textContent = `已选 ${values.length} 项:${text}`; + } else { + cityDropdownBtn.textContent = `已选 ${values.length} 项`; + citySummary.textContent = `已选 ${values.length} 项`; + } + } + + // 填充行业选择器 + populateIndustrySelector() { + const industry = this.config.industry; + if (!industry) return; + + // 处理数组格式(从后端返回)或字符串格式(从本地缓存) + let industryStr = ''; + if (Array.isArray(industry)) { + industryStr = industry.join(','); + } else { + industryStr = industry; + } + + console.log('BossConfigForm: 填充行业选择器,原始行业数据:', industry, '处理后:', industryStr); + + const industrySelect = document.getElementById('industryField'); + const industryDropdownBtn = document.getElementById('industryDropdownBtn'); + const industrySummary = document.getElementById('industrySelectionSummary'); + + if (!industrySelect) { + console.warn('BossConfigForm: 未找到行业选择器元素'); + return; + } + + // 解析行业代码(支持逗号分隔的多个行业) + const codes = industryStr.split(',').map(s => s.trim()).filter(Boolean); + console.log('BossConfigForm: 解析的行业代码:', codes); + + // 设置隐藏select的选中状态 + Array.from(industrySelect.options).forEach(opt => { + opt.selected = codes.includes(opt.value); + }); + + // 更新下拉框显示状态 + this.updateIndustryDropdownDisplay(); + + // 更新行业摘要 + if (typeof this.updateIndustrySummary === 'function') { + this.updateIndustrySummary(); + } + } + + // 更新行业下拉框显示状态 + updateIndustryDropdownDisplay() { + const industrySelect = document.getElementById('industryField'); + const industryListContainer = document.getElementById('industryDropdownList'); + + if (!industrySelect || !industryListContainer) return; + + // 更新checkbox状态 + const checkboxes = industryListContainer.querySelectorAll('input[type="checkbox"]'); + checkboxes.forEach(checkbox => { + const option = Array.from(industrySelect.options).find(o => o.value === checkbox.value); + if (option) { + checkbox.checked = option.selected; + } + }); + } + + // 填充期望薪资字段 + populateExpectedSalary() { + const expectedSalary = this.config.expectedSalary; + if (Array.isArray(expectedSalary) && expectedSalary.length >= 2) { + const minSalaryField = document.getElementById('minSalaryField'); + const maxSalaryField = document.getElementById('maxSalaryField'); + + if (minSalaryField && maxSalaryField) { + minSalaryField.value = expectedSalary[0] || ''; + maxSalaryField.value = expectedSalary[1] || ''; + console.log(`BossConfigForm: 期望薪资回填: ${expectedSalary[0]} ~ ${expectedSalary[1]}`); + } + } + } + + // 填充其他下拉框 + populateSelectBoxes() { + const selectFields = [ + 'experienceComboBox', + 'jobTypeComboBox', + 'salaryComboBox', + 'degreeComboBox', + 'scaleComboBox', + 'stageComboBox' + ]; + + selectFields.forEach(fieldId => { + const element = document.getElementById(fieldId); + const configKey = this.getConfigKeyFromFieldId(fieldId); + + if (element && this.config[configKey]) { + let value = this.config[configKey]; + + // 处理数组格式,取第一个元素(下拉框只能选一个值) + if (Array.isArray(value)) { + value = value.length > 0 ? value[0] : ''; + console.log(`BossConfigForm: 下拉框 ${fieldId} 数组转换:`, this.config[configKey], '->', value); + } + + console.log(`BossConfigForm: 设置下拉框 ${fieldId} = ${value}`); + + // 查找匹配的选项 + const option = Array.from(element.options).find(opt => opt.value === value); + if (option) { + element.value = value; + console.log(`BossConfigForm: 成功设置下拉框 ${fieldId}`); + } else { + console.warn(`BossConfigForm: 下拉框 ${fieldId} 中未找到值 ${value} 对应的选项`); + } + } + }); + } + + // 从字段ID获取配置键名 + getConfigKeyFromFieldId(fieldId) { + const fieldMap = { + 'experienceComboBox': 'experience', + 'jobTypeComboBox': 'jobType', + 'salaryComboBox': 'salary', + 'degreeComboBox': 'degree', + 'scaleComboBox': 'scale', + 'stageComboBox': 'stage' + }; + return fieldMap[fieldId] || fieldId; + } + + // 检查字典数据是否完全加载 + isDictDataReady() { + const requiredSelects = [ + 'experienceComboBox', + 'jobTypeComboBox', + 'salaryComboBox', + 'degreeComboBox', + 'scaleComboBox', + 'stageComboBox', + 'cityCodeField' + ]; + + for (const selectId of requiredSelects) { + const select = document.getElementById(selectId); + if (!select || select.options.length <= 1) { + console.log(`BossConfigForm: 下拉框 ${selectId} 尚未完全加载`); + return false; + } + } + + console.log('BossConfigForm: 所有字典数据已完全加载'); + return true; + } + + // 等待所有字典数据完全加载 + async waitForAllDictDataReady() { + return new Promise((resolve) => { + if (this.isDictDataReady()) { + resolve(); + return; + } + + console.log('BossConfigForm: 等待所有字典数据完全加载...'); + + let attempts = 0; + const maxAttempts = 50; // 最多等待5秒 + + const checkInterval = setInterval(() => { + attempts++; + + if (this.isDictDataReady()) { + clearInterval(checkInterval); + console.log('BossConfigForm: 所有字典数据加载完成'); + resolve(); + } else if (attempts >= maxAttempts) { + clearInterval(checkInterval); + console.warn('BossConfigForm: 等待字典数据超时,强制继续'); + resolve(); + } + }, 100); + }); + } + + getFieldId(key) { + const fieldMap = { + keywords: 'keywordsField', + industry: 'industryField', + cityCode: 'cityCodeField', + experience: 'experienceComboBox', + jobType: 'jobTypeComboBox', + salary: 'salaryComboBox', + degree: 'degreeComboBox', + scale: 'scaleComboBox', + stage: 'stageComboBox', + minSalary: 'minSalaryField', + maxSalary: 'maxSalaryField', + resumeImagePath: 'resumeImagePathField', + sayHi: 'sayHiTextArea', + filterDeadHR: 'filterDeadHRCheckBox', + sendImgResume: 'sendImgResumeCheckBox', + recommendJobs: 'recommendJobsCheckBox', + enableAIJobMatchDetection: 'enableAIJobMatchDetectionCheckBox', + enableAIGreeting: 'enableAIGreetingCheckBox', + checkStateOwned: 'checkStateOwnedCheckBox', + waitTime: 'waitTimeField' + }; + return fieldMap[key] || key; + } + + handleSaveOnly() { + this.saveConfig(); + try { + const toastEl = document.getElementById('globalToast'); + const bodyEl = document.getElementById('globalToastBody'); + if (toastEl && bodyEl) { + bodyEl.textContent = '配置已保存'; + const toast = bootstrap.Toast.getOrCreateInstance(toastEl, { delay: 2000 }); + toast.show(); + } + } catch (_) {} + } + + handleBackupData() { + const backupBtn = document.getElementById('backupDataBtn'); + if (backupBtn) { + backupBtn.disabled = true; + backupBtn.innerHTML = '备份中...'; + } + + fetch('https://github.com/api/backup/export', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + this.showToast('数据库备份成功', 'success'); + console.log('备份路径:', data.backupPath); + } else { + this.showToast('数据库备份失败: ' + data.message, 'error'); + } + }) + .catch(error => { + console.error('备份请求失败:', error); + this.showToast('数据库备份失败: ' + error.message, 'error'); + }) + .finally(() => { + if (backupBtn) { + backupBtn.disabled = false; + backupBtn.innerHTML = '数据库备份'; + } + }); + } + + showToast(message, type = 'success') { + try { + const toastEl = document.getElementById('globalToast'); + const bodyEl = document.getElementById('globalToastBody'); + if (toastEl && bodyEl) { + bodyEl.textContent = message; + toastEl.className = `toast align-items-center text-bg-${type === 'success' ? 'success' : 'danger'} border-0`; + const toast = bootstrap.Toast.getOrCreateInstance(toastEl, { delay: 3000 }); + toast.show(); + } + } catch (error) { + console.error('显示提示失败:', error); + } + } + + handleStartOnly() { + if (this.isRunning) { + alert('任务已在运行中...'); + return; + } + if (!this.validateRequiredFields()) { + alert('请先完善必填项再开始执行'); + return; + } + this.startExecution(); + } + + validateRequiredFields() { + const requiredFields = [ + 'keywordsField', + 'cityCodeField', + 'sayHiTextArea' + ]; + let isValid = true; + requiredFields.forEach(fieldId => { + const field = document.getElementById(fieldId); + if (field && !this.validateField(field)) { + isValid = false; + } + }); + + // Conditionally validate resumeImagePathField + const sendImgResume = document.getElementById('sendImgResumeCheckBox').checked; + const resumeField = document.getElementById('resumeImagePathField'); + if (sendImgResume) { + if (resumeField && !this.validateField(resumeField)) { + isValid = false; + } + } else { + // If not sending image resume, ensure the field is not marked as invalid + if (resumeField) { + resumeField.classList.remove('is-invalid'); + } + } + + return isValid && this.validateSalaryRange() ; + } + + startExecution() { + this.isRunning = true; + const startBtn = document.getElementById('startDeliveryBtn'); + if (startBtn) { + startBtn.disabled = true; + startBtn.innerHTML = '执行中...'; + startBtn.classList.add('loading'); + } + this.simulateExecution(); + } + + simulateExecution() { + const steps = [ + { message: '正在启动浏览器...', delay: 2000 }, + { message: '正在登录Boss直聘...', delay: 3000 }, + { message: '正在设置搜索条件...', delay: 2000 }, + { message: '正在筛选职位...', delay: 4000 }, + { message: '正在投递简历...', delay: 5000 }, + { message: '正在发送打招呼消息...', delay: 3000 }, + { message: '任务执行完成!', delay: 1000 } + ]; + let currentStep = 0; + const executeStep = () => { + if (currentStep < steps.length) { + const step = steps[currentStep]; + console.log(step.message); + currentStep++; + setTimeout(executeStep, step.delay); + } else { + this.finishExecution(); + } + }; + executeStep(); + } + + finishExecution() { + this.isRunning = false; + const startBtn = document.getElementById('startDeliveryBtn'); + if (startBtn) { + startBtn.disabled = false; + startBtn.innerHTML = '开始执行投递'; + startBtn.classList.remove('loading'); + startBtn.classList.add('success-flash'); + setTimeout(() => startBtn.classList.remove('success-flash'), 600); + } + } + + // 处理登录 + async handleLogin() { + if (!this.validateRequiredFields()) { + CommonUtils.showAlertModal('验证失败', '请先完善必填项'); + return; + } + + this.updateButtonState('loginBtn', 'loginStatus', '执行中...', true); + + try { + const config = this.getCurrentConfig(); + const response = await fetch('https://github.com/api/boss/task/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(config) + }); + + const result = await response.json(); + + if (result.success) { + this.taskStates.loginTaskId = result.taskId; + CommonUtils.showToast('Boss登录任务已提交'); + // 启动状态轮询 + this.startStatusPolling(); + } else { + this.updateButtonState('loginBtn', 'loginStatus', '登录失败', false, 'danger'); + CommonUtils.showToast(result.message || '登录失败', 'danger'); + } + } catch (error) { + this.updateButtonState('loginBtn', 'loginStatus', '登录失败', false, 'danger'); + CommonUtils.showToast('登录接口调用失败: ' + error.message, 'danger'); + } + } + + // 手动确认登录 + handleManualLogin() { + this.taskStates.loginTaskId = 'manual_login_' + Date.now(); + this.updateButtonState('loginBtn', 'loginStatus', '登录成功', false, 'success'); + this.enableNextStep('collectBtn', 'collectStatus', '可开始采集'); + this.enableNextStep('filterBtn', 'filterStatus', '可开始过滤'); + this.enableNextStep('deliverBtn', 'deliverStatus', '可开始投递'); + CommonUtils.showToast('已手动标记为登录状态', 'success'); + } + + // 处理采集 + async handleCollect() { + if (!this.isLoggedIn()) { + CommonUtils.showAlertModal('操作提示', '请先完成登录步骤'); + return; + } + + this.updateButtonState('collectBtn', 'collectStatus', '采集中...', true); + + try { + const config = this.getCurrentConfig(); + const response = await fetch('https://github.com/api/boss/task/collect', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(config) + }); + + const result = await response.json(); + + if (result.success) { + this.taskStates.collectTaskId = result.taskId; + CommonUtils.showToast('Boss采集任务已提交'); + // 启动状态轮询(如果未启动) + this.startStatusPolling(); + } else { + this.updateButtonState('collectBtn', 'collectStatus', '采集失败', false, 'danger'); + CommonUtils.showToast(result.message || '采集失败', 'danger'); + } + } catch (error) { + this.updateButtonState('collectBtn', 'collectStatus', '采集失败', false, 'danger'); + CommonUtils.showToast('采集接口调用失败: ' + error.message, 'danger'); + } + } + + // 处理过滤 + async handleFilter() { + if (!this.isLoggedIn()) { + CommonUtils.showAlertModal('操作提示', '请先完成登录步骤'); + return; + } + + this.updateButtonState('filterBtn', 'filterStatus', '过滤中...', true); + + try { + const config = this.getCurrentConfig(); + const request = { + collectTaskId: this.taskStates.collectTaskId, + config: config + }; + + const response = await fetch('https://github.com/api/boss/task/filter', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request) + }); + + const result = await response.json(); + + if (result.success) { + this.taskStates.filterTaskId = result.taskId; + CommonUtils.showToast('Boss过滤任务已提交'); + // 启动状态轮询(如果未启动) + this.startStatusPolling(); + } else { + this.updateButtonState('filterBtn', 'filterStatus', '过滤失败', false, 'danger'); + CommonUtils.showToast(result.message || '过滤失败', 'danger'); + } + } catch (error) { + this.updateButtonState('filterBtn', 'filterStatus', '过滤失败', false, 'danger'); + CommonUtils.showToast('过滤接口调用失败: ' + error.message, 'danger'); + } + } + + // 处理投递 + async handleApply() { + if (!this.isLoggedIn()) { + CommonUtils.showAlertModal('操作提示', '请先完成登录步骤'); + return; + } + + CommonUtils.showConfirmModal( + '投递确认', + '是否执行实际投递?\n点击"确定"将真实投递简历\n点击"取消"将仅模拟投递', + () => this.executeApply(true), + () => this.executeApply(false) + ); + } + + // 执行投递 + async executeApply(enableActualDelivery) { + this.updateButtonState('deliverBtn', 'deliverStatus', '投递中...', true); + + try { + const config = this.getCurrentConfig(); + const request = { + filterTaskId: this.taskStates.filterTaskId, + config: config, + enableActualDelivery: enableActualDelivery + }; + + const response = await fetch('https://github.com/api/boss/task/deliver', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request) + }); + + const result = await response.json(); + + if (result.success) { + this.taskStates.applyTaskId = result.taskId; + const deliveryType = enableActualDelivery ? '实际投递' : '模拟投递'; + CommonUtils.showToast(`Boss${deliveryType}任务已提交`); + // 启动状态轮询(如果未启动) + this.startStatusPolling(); + } else { + this.updateButtonState('deliverBtn', 'deliverStatus', '投递失败', false, 'danger'); + CommonUtils.showToast(result.message || '投递失败', 'danger'); + } + } catch (error) { + this.updateButtonState('deliverBtn', 'deliverStatus', '投递失败', false, 'danger'); + CommonUtils.showToast('投递接口调用失败: ' + error.message, 'danger'); + } + } + + // 获取当前配置 + getCurrentConfig() { + const getMultiSelectValues = (selectId) => { + const el = document.getElementById(selectId); + if (!el) return ''; + return Array.from(el.selectedOptions).map(o => o.value).filter(Boolean).join(','); + }; + + return { + keywords: document.getElementById('keywordsField')?.value || '', + industry: getMultiSelectValues('industryField'), + cityCode: getMultiSelectValues('cityCodeField'), + experience: document.getElementById('experienceComboBox')?.value || '', + jobType: document.getElementById('jobTypeComboBox')?.value || '', + salary: document.getElementById('salaryComboBox')?.value || '', + degree: document.getElementById('degreeComboBox')?.value || '', + scale: document.getElementById('scaleComboBox')?.value || '', + stage: document.getElementById('stageComboBox')?.value || '', + expectedSalary: [ + document.getElementById('minSalaryField')?.value || '0', + document.getElementById('maxSalaryField')?.value || '0' + ], + resumeImagePath: document.getElementById('resumeImagePathField')?.value || '', + sayHi: document.getElementById('sayHiTextArea')?.value || '', + filterDeadHR: document.getElementById('filterDeadHRCheckBox')?.checked || false, + sendImgResume: document.getElementById('sendImgResumeCheckBox')?.checked || false, + recommendJobs: document.getElementById('recommendJobsCheckBox')?.checked || false, + enableBlacklistFilter: document.getElementById('enableBlacklistFilterCheckBox')?.checked || false, + enableAIJobMatchDetection: document.getElementById('enableAIJobMatchDetectionCheckBox')?.checked || false, + enableAIGreeting: document.getElementById('enableAIGreetingCheckBox')?.checked || false, + checkStateOwned: document.getElementById('checkStateOwnedCheckBox')?.checked || false, + deadStatus: this.hrStatusTagsInput ? this.hrStatusTagsInput.getTags() : [] + }; + } + + // 更新按钮状态 + updateButtonState(buttonId, statusId, statusText, isLoading, statusType = 'warning') { + const button = document.getElementById(buttonId); + const status = document.getElementById(statusId); + + if (button) { + button.disabled = isLoading; + } + + if (status) { + status.textContent = statusText; + const statusClasses = { + 'warning': 'badge bg-warning text-dark ms-2', + 'success': 'badge bg-success text-white ms-2', + 'danger': 'badge bg-danger text-white ms-2', + 'info': 'badge bg-info text-white ms-2', + 'default': 'badge bg-light text-dark ms-2' + }; + status.className = statusClasses[statusType] || statusClasses['default']; + } + } + + // 启用下一步按钮 + enableNextStep(buttonId, statusId, statusText) { + const button = document.getElementById(buttonId); + const status = document.getElementById(statusId); + + if (button) { + button.disabled = false; + } + + if (status) { + status.textContent = statusText; + status.className = 'badge bg-info text-dark ms-2'; + } + } + + // 重置任务流程 + resetTaskFlow() { + CommonUtils.showConfirmModal( + '重置确认', + '确定要重置任务流程吗?这将清除所有任务状态。', + () => { + this.taskStates = { + loginTaskId: null, + collectTaskId: null, + filterTaskId: null, + applyTaskId: null + }; + + this.stopStatusPolling(); + + this.updateButtonState('loginBtn', 'loginStatus', '待执行', false, 'default'); + this.updateButtonState('collectBtn', 'collectStatus', '等待登录', true, 'default'); + this.updateButtonState('filterBtn', 'filterStatus', '等待登录', true, 'default'); + this.updateButtonState('deliverBtn', 'deliverStatus', '等待登录', true, 'default'); + + document.getElementById('collectBtn').disabled = true; + document.getElementById('filterBtn').disabled = true; + document.getElementById('deliverBtn').disabled = true; + + CommonUtils.showToast('任务流程已重置', 'info'); + } + ); + } + + // 启动状态轮询 + startStatusPolling() { + if (this.statusPollingInterval) { + return; // 已经在轮询中 + } + + console.log('Boss: 启动任务状态轮询'); + this.statusPollingInterval = setInterval(() => { + this.fetchAllTaskStatus(); + }, 2000); // 每2秒轮询一次 + + // 立即执行一次 + this.fetchAllTaskStatus(); + } + + // 停止状态轮询 + stopStatusPolling() { + if (this.statusPollingInterval) { + console.log('Boss: 停止任务状态轮询'); + clearInterval(this.statusPollingInterval); + this.statusPollingInterval = null; + } + } + + // 检查是否已登录(基于最新的任务状态缓存) + isLoggedIn() { + // 优先检查缓存的任务状态 + if (this.latestTaskStatus) { + const loginStatus = this.latestTaskStatus.login; + // 后端返回的字段是 status,不是 state + const state = loginStatus?.status || loginStatus?.state; + if (loginStatus && state === 'SUCCESS') { + return true; + } + } + + // 兼容:检查UI状态(处理app.js已更新UI但本地状态未同步的情况) + const loginStatusEl = document.getElementById('loginStatus'); + if (loginStatusEl) { + const statusText = loginStatusEl.textContent.trim(); + // 如果状态文本包含"成功"或"完成",也认为已登录 + if (statusText.includes('成功') || statusText.includes('完成') || statusText.includes('登录状态正常')) { + return true; + } + } + + return false; + } + + // 查询所有任务状态 + async fetchAllTaskStatus() { + try { + const response = await fetch('https://github.com/api/tasks/status'); + if (!response.ok) return; + + const result = await response.json(); + if (!result) return; + + // 后端返回的是扁平结构:{ "BOSS_ZHIPIN_LOGIN": {...}, "BOSS_ZHIPIN_COLLECT": {...}, ... } + // 需要转换为前端期望的嵌套结构 + const bossStatus = { + login: result['BOSS_ZHIPIN_LOGIN'], + collect: result['BOSS_ZHIPIN_COLLECT'], + filter: result['BOSS_ZHIPIN_FILTER'], + deliver: result['BOSS_ZHIPIN_DELIVER'] + }; + + console.log('Boss: 任务状态数据(转换后):', bossStatus); + + // 缓存最新的任务状态 + this.latestTaskStatus = bossStatus; + + this.updateTaskStatusUI(bossStatus); + + } catch (error) { + console.warn('Boss: 查询任务状态失败:', error); + } + } + + // 更新任务状态UI + updateTaskStatusUI(statusData) { + // 更新登录任务状态 + if (statusData.login) { + this.updateTaskUI('login', statusData.login); + } + + // 更新采集任务状态 + if (statusData.collect) { + this.updateTaskUI('collect', statusData.collect); + } + + // 更新过滤任务状态 + if (statusData.filter) { + this.updateTaskUI('filter', statusData.filter); + } + + // 更新投递任务状态 + if (statusData.deliver) { + this.updateTaskUI('deliver', statusData.deliver); + } + } + + // 更新单个任务的UI + updateTaskUI(taskType, taskStatus) { + const buttonMap = { + 'login': { btn: 'loginBtn', status: 'loginStatus' }, + 'collect': { btn: 'collectBtn', status: 'collectStatus' }, + 'filter': { btn: 'filterBtn', status: 'filterStatus' }, + 'deliver': { btn: 'deliverBtn', status: 'deliverStatus' } + }; + + const uiElements = buttonMap[taskType]; + if (!uiElements) return; + + // 后端返回的字段是 status,不是 state + // 状态值:STARTED, SUCCESS, FAILURE + const state = taskStatus.status || taskStatus.state; + const message = taskStatus.message || ''; + + console.log(`Boss: 更新${taskType}任务UI,状态=${state},消息=${message}`); + + switch (state) { + case 'STARTED': + case 'RUNNING': + this.updateButtonState(uiElements.btn, uiElements.status, message || '执行中...', true, 'warning'); + break; + case 'SUCCESS': + this.updateButtonState(uiElements.btn, uiElements.status, message || '完成', false, 'success'); + // 启用下一步 + if (taskType === 'login') { + this.enableNextStep('collectBtn', 'collectStatus', '可开始采集'); + this.enableNextStep('filterBtn', 'filterStatus', '可开始过滤'); + this.enableNextStep('deliverBtn', 'deliverStatus', '可开始投递'); + } + // 如果所有任务都完成,停止轮询 + if (taskType === 'deliver') { + this.stopStatusPolling(); + } + break; + case 'FAILED': + case 'FAILURE': + this.updateButtonState(uiElements.btn, uiElements.status, message || '失败', false, 'danger'); + this.stopStatusPolling(); + break; + case 'PENDING': + // 待执行状态,保持默认 + break; + } + } + + // 加载Boss字典数据 + async loadBossDicts() { + // 等待DOM元素准备就绪 + await this.waitForDOMElements(); + + try { + console.log('BossConfigForm: 开始加载Boss字典数据...'); + const res = await fetch('https://github.com/dicts/BOSS_ZHIPIN'); + if (!res.ok) throw new Error('HTTP ' + res.status); + const data = await res.json(); + console.log('BossConfigForm: 接收到字典数据:', data); + + if (!data || !Array.isArray(data.groups)) { + console.warn('BossConfigForm: 字典数据结构不正确:', data); + return; + } + + const groupMap = new Map(); + data.groups.forEach(g => { + console.log(`BossConfigForm: 处理字典组: ${g.key}, 项目数量: ${Array.isArray(g.items) ? g.items.length : 0}`); + groupMap.set(g.key, Array.isArray(g.items) ? g.items : []); + }); + + // 渲染城市选择器 + this.renderCitySelector(groupMap.get('cityList') || []); + + // 渲染行业选择器 + this.renderIndustrySelector(groupMap.get('industryList') || []); + + // 渲染其他下拉框 + this.fillSelect('experienceComboBox', groupMap.get('experienceList')); + this.fillSelect('salaryComboBox', groupMap.get('salaryList')); + this.fillSelect('degreeComboBox', groupMap.get('degreeList')); + this.fillSelect('scaleComboBox', groupMap.get('scaleList')); + this.fillSelect('stageComboBox', groupMap.get('stageList')); + this.fillSelect('jobTypeComboBox', groupMap.get('jobTypeList')); + + console.log('BossConfigForm: 字典数据加载完成'); + + // 标记字典数据已加载完成 + this.dictDataLoaded = true; + + // 触发自定义事件,通知其他组件字典数据已就绪 + window.dispatchEvent(new CustomEvent('bossDictDataLoaded', { + detail: { groupMap: groupMap } + })); + + } catch (e) { + console.warn('BossConfigForm: 加载Boss字典失败:', e?.message || e); + // 如果失败,延迟重试 + setTimeout(() => this.loadBossDicts(), 2000); + } + } + + // 等待DOM元素准备就绪 + async waitForDOMElements() { + const requiredElements = [ + 'cityCodeField', + 'experienceComboBox', + 'salaryComboBox', + 'degreeComboBox', + 'scaleComboBox', + 'stageComboBox', + 'jobTypeComboBox' + ]; + + return new Promise((resolve) => { + let attempts = 0; + const maxAttempts = 50; // 最多等待5秒 + + const checkElements = () => { + attempts++; + const missingElements = requiredElements.filter(id => !document.getElementById(id)); + + if (missingElements.length === 0) { + console.log('BossConfigForm: 所有DOM元素已准备就绪'); + resolve(); + } else if (attempts >= maxAttempts) { + console.warn('BossConfigForm: 等待DOM元素超时,缺失元素:', missingElements); + resolve(); // 即使超时也继续执行 + } else { + console.log(`BossConfigForm: 等待DOM元素准备就绪,缺失: ${missingElements.join(', ')} (${attempts}/${maxAttempts})`); + setTimeout(checkElements, 100); + } + }; + + checkElements(); + }); + } + + // 渲染城市选择器 + renderCitySelector(cityItems) { + console.log('BossConfigForm: 渲染城市选择器,城市数量:', cityItems.length); + + const citySelect = document.getElementById('cityCodeField'); + const citySearch = document.getElementById('citySearchField'); + const cityListContainer = document.getElementById('cityDropdownList'); + const cityDropdownBtn = document.getElementById('cityDropdownBtn'); + const citySummary = document.getElementById('citySelectionSummary'); + + if (!citySelect) { + console.warn('BossConfigForm: 未找到城市选择器元素'); + return; + } + + // 更新城市摘要显示 + const updateCitySummary = () => { + if (!cityDropdownBtn || !citySummary) return; + const values = Array.from(citySelect.selectedOptions).map(o => o.textContent); + if (values.length === 0) { + cityDropdownBtn.textContent = '选择城市'; + citySummary.textContent = '未选择'; + } else if (values.length <= 2) { + const text = values.join('、'); + cityDropdownBtn.textContent = text; + citySummary.textContent = `已选 ${values.length} 项:${text}`; + } else { + cityDropdownBtn.textContent = `已选 ${values.length} 项`; + citySummary.textContent = `已选 ${values.length} 项`; + } + }; + + // 将updateCitySummary方法绑定到实例,供其他方法调用 + this.updateCitySummary = updateCitySummary; + + // 渲染城市选项 + const renderCityOptions = (list) => { + // 保留当前已选 + const selected = new Set(Array.from(citySelect.selectedOptions).map(o => o.value)); + + // 重建隐藏select + citySelect.innerHTML = ''; + list.forEach(it => { + const value = it.code ?? ''; + const label = `${it.name ?? ''}${it.code ? ' (' + it.code + ')' : ''}`; + const opt = document.createElement('option'); + opt.value = value; + opt.textContent = label; + if (selected.has(value)) opt.selected = true; + citySelect.appendChild(opt); + }); + + // 重建dropdown列表 + if (cityListContainer) { + cityListContainer.innerHTML = ''; + list.forEach(it => { + const value = it.code ?? ''; + const label = `${it.name ?? ''}${it.code ? ' (' + it.code + ')' : ''}`; + + const item = document.createElement('div'); + item.className = 'form-check mb-1'; + const id = `city_chk_${value}`.replace(/[^a-zA-Z0-9_\-]/g, '_'); + item.innerHTML = ` + + + `; + const checkbox = item.querySelector('input[type="checkbox"]'); + checkbox.addEventListener('change', () => { + // 同步到隐藏select + const option = Array.from(citySelect.options).find(o => o.value === value); + if (option) option.selected = checkbox.checked; + updateCitySummary(); + }); + cityListContainer.appendChild(item); + }); + } + + updateCitySummary(); + }; + + renderCityOptions(cityItems); + + // 绑定搜索功能 + if (citySearch) { + citySearch.addEventListener('input', () => { + const kw = citySearch.value.trim().toLowerCase(); + if (!kw) { + renderCityOptions(cityItems); + return; + } + const filtered = cityItems.filter(it => + String(it.name || '').toLowerCase().includes(kw) || + String(it.code || '').toLowerCase().includes(kw) + ); + renderCityOptions(filtered); + }); + } + } + + // 渲染行业选择器 + renderIndustrySelector(industryItems) { + console.log('BossConfigForm: 渲染行业选择器,行业数量:', industryItems.length); + + const industrySelect = document.getElementById('industryField'); + const industrySearch = document.getElementById('industrySearchField'); + const industryListContainer = document.getElementById('industryDropdownList'); + const industryDropdownBtn = document.getElementById('industryDropdownBtn'); + const industrySummary = document.getElementById('industrySelectionSummary'); + + if (!industrySelect) { + console.warn('BossConfigForm: 未找到行业选择器元素'); + return; + } + + // 更新行业摘要显示 + const updateIndustrySummary = () => { + if (!industryDropdownBtn || !industrySummary) return; + const values = Array.from(industrySelect.selectedOptions).map(o => o.textContent); + if (values.length === 0) { + industryDropdownBtn.textContent = '选择行业'; + industrySummary.textContent = '未选择'; + } else if (values.length <= 2) { + const text = values.join('、'); + industryDropdownBtn.textContent = text; + industrySummary.textContent = `已选 ${values.length} 项:${text}`; + } else { + industryDropdownBtn.textContent = `已选 ${values.length} 项`; + industrySummary.textContent = `已选 ${values.length} 项`; + } + }; + + // 将updateIndustrySummary方法绑定到实例,供其他方法调用 + this.updateIndustrySummary = updateIndustrySummary; + + // 渲染行业选项 + const renderIndustryOptions = (list) => { + // 保留当前已选 + const selected = new Set(Array.from(industrySelect.selectedOptions).map(o => o.value)); + + // 重建隐藏select + industrySelect.innerHTML = ''; + list.forEach(it => { + const value = it.code ?? it.name ?? ''; + const label = it.name ?? String(it.code ?? ''); + const opt = document.createElement('option'); + opt.value = value; + opt.textContent = label; + if (selected.has(value)) opt.selected = true; + industrySelect.appendChild(opt); + }); + + // 重建dropdown列表 + if (industryListContainer) { + industryListContainer.innerHTML = ''; + list.forEach(it => { + const value = it.code ?? it.name ?? ''; + const label = it.name ?? String(it.code ?? ''); + + const item = document.createElement('div'); + item.className = 'form-check mb-1'; + const id = `industry_chk_${value}`.replace(/[^a-zA-Z0-9_\-]/g, '_'); + item.innerHTML = ` + + + `; + const checkbox = item.querySelector('input[type="checkbox"]'); + checkbox.addEventListener('change', () => { + // 限制最多选择3个 + const selectedCount = Array.from(industrySelect.selectedOptions).length; + if (checkbox.checked && selectedCount >= 3) { + checkbox.checked = false; + CommonUtils.showToast('最多只能选择3个行业', 'warning'); + return; + } + + // 同步到隐藏select + const option = Array.from(industrySelect.options).find(o => o.value === value); + if (option) option.selected = checkbox.checked; + updateIndustrySummary(); + }); + industryListContainer.appendChild(item); + }); + } + + updateIndustrySummary(); + }; + + renderIndustryOptions(industryItems); + + // 绑定搜索功能 + if (industrySearch) { + industrySearch.addEventListener('input', () => { + const kw = industrySearch.value.trim().toLowerCase(); + if (!kw) { + renderIndustryOptions(industryItems); + return; + } + const filtered = industryItems.filter(it => + String(it.name || '').toLowerCase().includes(kw) || + String(it.code || '').toLowerCase().includes(kw) + ); + renderIndustryOptions(filtered); + }); + } + } + + // 填充下拉框 + fillSelect(selectId, items) { + console.log(`BossConfigForm: 填充下拉框 ${selectId},数据项数量:`, Array.isArray(items) ? items.length : 0); + + if (!Array.isArray(items) || items.length === 0) { + console.warn(`BossConfigForm: 下拉框 ${selectId} 的数据无效:`, items); + return; + } + + const sel = document.getElementById(selectId); + if (!sel) { + console.warn(`BossConfigForm: 未找到下拉框元素: ${selectId}`); + // 延迟重试 + setTimeout(() => { + const retrySel = document.getElementById(selectId); + if (retrySel) { + console.log(`BossConfigForm: 重试填充下拉框 ${selectId}`); + this.fillSelect(selectId, items); + } + }, 500); + return; + } + + try { + // 保留第一项"请选择",其余重建 + const first = sel.querySelector('option'); + sel.innerHTML = ''; + if (first && first.value === '') sel.appendChild(first); + + items.forEach(it => { + const opt = document.createElement('option'); + opt.value = it.code ?? it.name ?? ''; + opt.textContent = it.name ?? String(it.code ?? ''); + sel.appendChild(opt); + }); + + console.log(`BossConfigForm: 下拉框 ${selectId} 填充完成,共 ${items.length} 项`); + + // 触发change事件,通知其他组件 + sel.dispatchEvent(new Event('change', { bubbles: true })); + + } catch (error) { + console.error(`BossConfigForm: 填充下拉框 ${selectId} 时出错:`, error); + } + } + } + + // 导出为全局可用类,由 app.js 统一初始化 + window.Views.BossConfigForm = BossConfigApp; +})(); diff --git a/src/main/resources/static/js/views/boss-config-readonly.js b/src/main/resources/static/js/views/boss-config-readonly.js new file mode 100644 index 00000000..07cb6bae --- /dev/null +++ b/src/main/resources/static/js/views/boss-config-readonly.js @@ -0,0 +1,139 @@ +// Boss 求职配置只读视图(导出创建函数,由 app.js 调用) +(function () { + if (!window.Views) window.Views = {}; + + function createBossConfigReadonlyApp() { + const { createApp } = window.Vue || {}; + if (!createApp) { + console.error('Vue.js未加载'); + return null; + } + + const mountElement = document.getElementById('bossConfigApp'); + if (!mountElement) { + console.error('找不到挂载元素 #bossConfigApp'); + return null; + } + + const app = createApp({ + data() { + return { + config: {}, + loading: false, + error: '', + initialized: false + }; + }, + computed: { + hasData() { + return this.config && Object.keys(this.config).length > 0; + } + }, + methods: { + async load(force = false) { + if (this.loading && !force) return; + + this.loading = true; + this.error = ''; + + try { + const res = await fetch('https://github.com/api/config/boss', { + headers: { + 'Accept': 'application/json', + 'Cache-Control': force ? 'no-cache' : 'default' + } + }); + + if (!res.ok) { + throw new Error(`请求失败: HTTP ${res.status}`); + } + + const data = await res.json(); + this.config = data || {}; + + } catch (err) { + console.error('加载配置失败:', err); + this.error = err?.message || '加载失败,请稍后重试'; + this.config = {}; + } finally { + this.loading = false; + } + }, + + // 格式化工具方法 + safe(v) { + if (v == null) return ''; + return Array.isArray(v) ? v.join('、') : String(v); + }, + + join(v) { + if (v == null) return ''; + return Array.isArray(v) ? v.join('、') : String(v); + }, + + formatDateTime(input) { + if (!input) return ''; + try { + const d = new Date(input); + if (isNaN(d.getTime())) return ''; + + const pad = n => String(n).padStart(2, '0'); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; + } catch (err) { + console.warn('日期格式化失败:', err); + return ''; + } + }, + + formatKVList(obj) { + if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return ''; + return Object.entries(obj) + .map(([k, v]) => `${k} - ${v}`) + .join('
    '); + }, + + // Tab 切换处理 + handleTabShow() { + if (!this.initialized) { + this.load(true); + this.initialized = true; + } else { + this.load(false); + } + } + }, + mounted() { + // 确保Bootstrap已加载 + if (typeof bootstrap === 'undefined') { + console.error('Bootstrap未加载'); + return; + } + + // 设置tab事件监听 + const tabElement = document.getElementById('boss-current-tab'); + if (tabElement) { + tabElement.addEventListener('shown.bs.tab', this.handleTabShow); + } + + // 如果当前tab是激活状态,立即加载数据 + const pane = document.getElementById('boss-current-pane'); + if (pane?.classList.contains('active') && pane?.classList.contains('show')) { + this.handleTabShow(); + } + }, + unmounted() { + // 清理事件监听 + const tabElement = document.getElementById('boss-current-tab'); + if (tabElement) { + tabElement.removeEventListener('shown.bs.tab', this.handleTabShow); + } + } + }); + + return app.mount('#bossConfigApp'); + } + + window.Views.createBossConfigReadonlyApp = createBossConfigReadonlyApp; +})(); + + diff --git a/src/main/resources/static/js/views/boss-records-vue.js b/src/main/resources/static/js/views/boss-records-vue.js new file mode 100644 index 00000000..6e4334a0 --- /dev/null +++ b/src/main/resources/static/js/views/boss-records-vue.js @@ -0,0 +1,382 @@ +// Boss岗位明细Vue应用 +(function () { + if (!window.Views) window.Views = {}; + + class BossRecordsVue { + constructor() { + this.app = null; + this.init(); + } + + init() { + // 等待DOM加载完成 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => this.createVueApp()); + } else { + this.createVueApp(); + } + } + + createVueApp() { + const { createApp } = Vue; + + this.app = createApp({ + data() { + return { + loading: false, + error: null, + searchKeyword: '', + jobs: [], + currentPage: 0, + pageSize: 10, + totalElements: 0, + totalPages: 0, + numberOfElements: 0, + first: true, + last: false, + empty: true + } + }, + computed: { + currentPageData() { + return this.jobs || []; + }, + visiblePages() { + const pages = []; + const start = Math.max(0, this.currentPage - 2); + const end = Math.min(this.totalPages - 1, this.currentPage + 2); + + for (let i = start; i <= end; i++) { + pages.push(i); + } + return pages; + } + }, + methods: { + // 加载岗位数据 + async loadJobs(page = 0) { + this.loading = true; + this.error = null; + + try { + const params = new URLSearchParams(); + params.set('platform', 'BOSS直聘'); + if (this.searchKeyword.trim()) { + params.set('keyword', this.searchKeyword.trim()); + } + params.set('page', page.toString()); + params.set('size', this.pageSize.toString()); + + const response = await fetch(`/api/jobs?${params.toString()}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + this.jobs = data.content || []; + this.currentPage = data.number || 0; + this.totalElements = data.totalElements || 0; + this.totalPages = data.totalPages || 0; + this.numberOfElements = data.numberOfElements || 0; + this.first = data.first || false; + this.last = data.last || false; + this.empty = data.empty || true; + + } catch (error) { + console.error('加载岗位数据失败:', error); + this.error = '加载数据失败,请稍后重试'; + this.jobs = []; + } finally { + this.loading = false; + } + }, + + // 搜索岗位 + searchJobs() { + this.loadJobs(0); + }, + + // 刷新数据 + refreshData() { + this.loadJobs(this.currentPage); + }, + + // 分页跳转 + goToPage(page) { + if (page >= 0 && page < this.totalPages) { + this.loadJobs(page); + } + }, + + // 解析数组字符串 + parseArray(str) { + if (!str) return []; + try { + if (typeof str === 'string') { + return JSON.parse(str); + } + return Array.isArray(str) ? str : []; + } catch (e) { + console.warn('解析数组失败:', str, e); + return []; + } + }, + + // 截断文本 + truncateText(text, maxLength = 50) { + if (!text) return ''; + if (text.length <= maxLength) return text; + return text.substring(0, maxLength) + '...'; + }, + + // 格式化日期时间 + formatDateTime(dateStr) { + if (!dateStr) return '-'; + try { + const date = new Date(dateStr); + if (isNaN(date.getTime())) return '-'; + + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + + return `${year}-${month}-${day} ${hours}:${minutes}`; + } catch (e) { + return '-'; + } + }, + + // 获取状态徽章样式 + getStatusBadgeClass(status) { + const statusMap = { + 0: 'bg-secondary', // 待处理 + 1: 'bg-success', // 已处理 + 2: 'bg-warning', // 已忽略 + 3: 'bg-info', // 进行中 + 4: 'bg-danger' // 失败 + }; + return statusMap[status] || 'bg-dark'; + }, + + // 获取状态文本 + getStatusText(status) { + // PENDING(1, "待处理"), + // FILTERED(2, "已过滤"), + // DELIVERED_SUCCESS(3, "投递成功"), + // DELIVERED_FAILED(4, "投递失败"); + const statusMap = { + 0: '待处理', + 1: '待处理', + 2: '已过滤', + 3: '投递成功', + 4: '投递失败' + }; + return statusMap[status] || '未知'; + }, + + // 查看岗位详情 + viewJobDetail(job) { + if (job.jobUrl) { + window.open(job.jobUrl, '_blank'); + } else { + this.showAlert('岗位链接不可用'); + } + }, + + // 复制岗位链接 + async copyJobUrl(job) { + if (job.jobUrl) { + try { + await navigator.clipboard.writeText(job.jobUrl); + this.showAlert('链接已复制到剪贴板'); + } catch (e) { + // 降级方案 + const textArea = document.createElement('textarea'); + textArea.value = job.jobUrl; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand('copy'); + document.body.removeChild(textArea); + this.showAlert('链接已复制到剪贴板'); + } + } else { + this.showAlert('岗位链接不可用'); + } + }, + + // 切换收藏状态 + async toggleFavorite(job) { + try { + const response = await fetch(`/api/jobs/${job.id}/favorite`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + isFavorite: !job.isFavorite + }) + }); + + if (response.ok) { + job.isFavorite = !job.isFavorite; + this.showAlert(job.isFavorite ? '已添加到收藏' : '已取消收藏'); + } else { + this.showAlert('操作失败,请稍后重试'); + } + } catch (error) { + console.error('切换收藏状态失败:', error); + this.showAlert('操作失败,请稍后重试'); + } + }, + + // 获取工作类型文本 + getJobTypeText(jobType) { + const typeMap = { + 0: '全职', + 1: '兼职', + 2: '实习', + 3: '合同工', + 4: '外包' + }; + return typeMap[jobType] || '未知'; + }, + + // 查看公司信息 + viewCompanyInfo(job) { + if (job.brandIntroduce) { + this.showCompanyModal(job); + } else { + this.showAlert('暂无公司详细信息'); + } + }, + + // 显示公司信息模态框 + showCompanyModal(job) { + // 创建模态框HTML + const modalHtml = ` + + `; + + // 移除已存在的模态框 + const existingModal = document.getElementById('companyModal'); + if (existingModal) { + existingModal.remove(); + } + + // 添加新模态框到页面 + document.body.insertAdjacentHTML('beforeend', modalHtml); + + // 显示模态框 + const modal = new bootstrap.Modal(document.getElementById('companyModal')); + modal.show(); + + // 模态框关闭后移除 + document.getElementById('companyModal').addEventListener('hidden.bs.modal', function() { + this.remove(); + }); + }, + + // 显示提示信息 + showAlert(message) { + // 使用Bootstrap的toast组件 + const toastElement = document.getElementById('globalToast'); + const toastBody = document.getElementById('globalToastBody'); + if (toastElement && toastBody) { + toastBody.textContent = message; + const toast = new bootstrap.Toast(toastElement); + toast.show(); + } else { + alert(message); + } + } + }, + mounted() { + // 监听标签页切换事件 + const bossRecordsTab = document.getElementById('boss-records-tab'); + if (bossRecordsTab) { + bossRecordsTab.addEventListener('shown.bs.tab', () => { + this.loadJobs(0); + }); + } + + // 初始加载数据 + this.loadJobs(0); + } + }); + + // 挂载到指定元素 + const mountElement = document.getElementById('bossRecordsApp'); + if (mountElement) { + const rootInstance = this.app.mount(mountElement); + // 暴露根组件实例,便于外部按钮调用其方法 + window.bossRecordsRoot = rootInstance; + } + } + + // 销毁应用 + destroy() { + if (this.app) { + this.app.unmount(); + this.app = null; + } + } + } + + // 导出类 + window.Views.BossRecordsVue = BossRecordsVue; +})(); diff --git a/src/main/resources/static/js/views/common-config.js b/src/main/resources/static/js/views/common-config.js new file mode 100644 index 00000000..7cbeb7b5 --- /dev/null +++ b/src/main/resources/static/js/views/common-config.js @@ -0,0 +1,196 @@ +/** + * 公共配置管理模块 + * 用于管理跨平台的通用配置,如黑名单关键字等 + */ + +(function() { + 'use strict'; + + // API端点 + const API_ENDPOINTS = { + SAVE_CONFIG: '/api/common/config/save', + GET_CONFIG: '/api/common/config/get' + }; + + // DOM元素 + let jobBlacklistInput; + let companyBlacklistInput; + let jobTagsInput; + let companyTagsInput; + let saveBtn; + let resetBtn; + + /** + * 初始化模块 + */ + function init() { + // 获取DOM元素 + jobBlacklistInput = document.getElementById('commonJobBlacklistKeywords'); + companyBlacklistInput = document.getElementById('commonCompanyBlacklistKeywords'); + const jobTagsWrapper = document.getElementById('jobBlacklistTags'); + const companyTagsWrapper = document.getElementById('companyBlacklistTags'); + saveBtn = document.getElementById('saveCommonConfigBtn'); + resetBtn = document.getElementById('resetCommonConfigBtn'); + + // 初始化标签输入组件 + if (jobBlacklistInput && jobTagsWrapper) { + jobTagsInput = new window.TagsInput(jobBlacklistInput, jobTagsWrapper); + } + if (companyBlacklistInput && companyTagsWrapper) { + companyTagsInput = new window.TagsInput(companyBlacklistInput, companyTagsWrapper); + } + + // 绑定事件 + if (saveBtn) { + saveBtn.addEventListener('click', saveCommonConfig); + } + if (resetBtn) { + resetBtn.addEventListener('click', resetCommonConfig); + } + + // 页面加载时获取配置 + loadCommonConfig(); + } + + /** + * 加载公共配置 + */ + async function loadCommonConfig() { + try { + const response = await fetch(API_ENDPOINTS.GET_CONFIG); + + if (!response.ok) { + console.warn('未找到公共配置,将使用默认值'); + return; + } + + const config = await response.json(); + + // 填充表单 + if (config.jobBlacklistKeywords && jobTagsInput) { + jobTagsInput.setValue(config.jobBlacklistKeywords); + } + if (config.companyBlacklistKeywords && companyTagsInput) { + companyTagsInput.setValue(config.companyBlacklistKeywords); + } + + console.log('公共配置加载成功'); + } catch (error) { + console.error('加载公共配置失败:', error); + } + } + + /** + * 保存公共配置 + */ + async function saveCommonConfig() { + try { + // 获取表单数据(使用标签输入组件) + const jobKeywords = jobTagsInput ? jobTagsInput.getValue() : ''; + const companyKeywords = companyTagsInput ? companyTagsInput.getValue() : ''; + + // 构建配置对象 + const config = { + jobBlacklistKeywords: jobKeywords, + companyBlacklistKeywords: companyKeywords + }; + + // 显示加载状态 + saveBtn.disabled = true; + saveBtn.innerHTML = '保存中...'; + + // 发送请求 + const response = await fetch(API_ENDPOINTS.SAVE_CONFIG, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(config) + }); + + if (!response.ok) { + throw new Error('保存失败'); + } + + // 显示成功消息 + if (window.CommonUtils && window.CommonUtils.showToast) { + window.CommonUtils.showToast('公共配置保存成功!', 'success'); + } else { + alert('公共配置保存成功!'); + } + + console.log('公共配置保存成功:', config); + } catch (error) { + console.error('保存公共配置失败:', error); + if (window.CommonUtils && window.CommonUtils.showToast) { + window.CommonUtils.showToast('保存失败:' + error.message, 'danger'); + } else { + alert('保存失败:' + error.message); + } + } finally { + // 恢复按钮状态 + saveBtn.disabled = false; + saveBtn.innerHTML = '保存公共配置'; + } + } + + /** + * 重置公共配置 + */ + function resetCommonConfig() { + if (window.CommonUtils && window.CommonUtils.showConfirm) { + window.CommonUtils.showConfirm( + '确定要重置所有公共配置吗?', + () => { + if (jobTagsInput) jobTagsInput.clear(); + if (companyTagsInput) companyTagsInput.clear(); + if (window.CommonUtils.showToast) { + window.CommonUtils.showToast('配置已重置', 'info'); + } + } + ); + } else { + if (confirm('确定要重置所有公共配置吗?')) { + if (jobTagsInput) jobTagsInput.clear(); + if (companyTagsInput) companyTagsInput.clear(); + alert('配置已重置'); + } + } + } + + /** + * 获取岗位黑名单关键字(供其他模块调用) + */ + function getJobBlacklistKeywords() { + if (!jobTagsInput) { + return ''; + } + return jobTagsInput.getValue(); + } + + /** + * 获取公司黑名单关键字(供其他模块调用) + */ + function getCompanyBlacklistKeywords() { + if (!companyTagsInput) { + return ''; + } + return companyTagsInput.getValue(); + } + + // 导出公共方法 + window.CommonConfig = { + init: init, + getJobBlacklistKeywords: getJobBlacklistKeywords, + getCompanyBlacklistKeywords: getCompanyBlacklistKeywords, + loadCommonConfig: loadCommonConfig + }; + + // 页面加载完成后初始化 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); + diff --git a/src/main/resources/static/js/views/job-records.js b/src/main/resources/static/js/views/job-records.js new file mode 100644 index 00000000..3c8b2663 --- /dev/null +++ b/src/main/resources/static/js/views/job-records.js @@ -0,0 +1,377 @@ +// 岗位明细列表模块(导出类,不自动初始化) +(function () { + if (!window.Views) window.Views = {}; + + class JobRecords { + constructor() { + this.bindEvents(); + } + + bindEvents() { + const self = this; + document.getElementById('boss-records-tab')?.addEventListener('shown.bs.tab', function () { + self.loadJobDetails('Boss直聘', 'boss', 0); + }); + document.getElementById('zhilian-records-tab')?.addEventListener('shown.bs.tab', function () { + self.loadJobDetails('zhilian', 'zhilian', 0); + }); + document.getElementById('job51-records-tab')?.addEventListener('shown.bs.tab', function () { + self.loadJobDetails('51job', 'job51', 0); + }); + + document.getElementById('bossRecordSearchBtn')?.addEventListener('click', () => { + this.loadJobDetails('Boss直聘', 'boss', 0); + }); + document.getElementById('zhilianRecordSearchBtn')?.addEventListener('click', () => { + this.loadJobDetails('zhilian', 'zhilian', 0); + }); + document.getElementById('job51RecordSearchBtn')?.addEventListener('click', () => { + this.loadJobDetails('51job', 'job51', 0); + }); + document.getElementById('job51RecordRefreshBtn')?.addEventListener('click', () => { + this.loadJobDetails('51job', 'job51', 0); + }); + } + + loadJobDetails(platformText, platformKey, pageIndex = 0) { + const keywordInputIdMap = { boss: 'bossRecordKeyword', zhilian: 'zhilianRecordKeyword', job51: 'job51RecordKeyword' }; + const tbodyIdMap = { boss: 'bossRecordTbody', zhilian: 'zhilianRecordTbody', job51: 'job51RecordTbody' }; + const pagerIdMap = { boss: 'bossRecordPagination', zhilian: 'zhilianRecordPagination', job51: 'job51RecordPagination' }; + + const keyword = document.getElementById(keywordInputIdMap[platformKey])?.value?.trim() || ''; + const params = new URLSearchParams(); + if (platformText) params.set('platform', platformText); + if (keyword) params.set('keyword', keyword); + params.set('page', String(pageIndex)); + params.set('size', '10'); + + const tbody = document.getElementById(tbodyIdMap[platformKey]); + const colSpan = platformKey === 'job51' ? '14' : '7'; // 51job使用14列,其他使用7列 + if (tbody) { + tbody.innerHTML = `加载中...`; + } + + fetch(`/api/jobs?${params.toString()}`) + .then(res => res.json()) + .then(page => { + this.renderJobDetailsTable(platformKey, page); + this.renderPagination(platformKey, page); + }) + .catch(() => { + if (tbody) { + const colSpan = platformKey === 'job51' ? '14' : '7'; + tbody.innerHTML = `加载失败`; + } + }); + } + + renderJobDetailsTable(platformKey, page) { + const tbodyIdMap = { boss: 'bossRecordTbody', zhilian: 'zhilianRecordTbody', job51: 'job51RecordTbody' }; + const tbody = document.getElementById(tbodyIdMap[platformKey]); + if (!tbody) { + return; + } + + const content = Array.isArray(page?.content) ? page.content : []; + if (content.length === 0) { + const colSpan = platformKey === 'job51' ? '14' : '7'; + tbody.innerHTML = `暂无数据`; + return; + } + + const statusBadge = (status) => { + const map = { 0: { text: '待处理', cls: 'secondary' }, 1: { text: '已处理', cls: 'success' }, 2: { text: '已忽略', cls: 'warning' } }; + const it = map[status] || { text: '未知', cls: 'dark' }; + return `${it.text}`; + }; + + const rows = content.map((j, index) => { + + const escapeHtml = (str) => String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); + const fmt = (input) => { try { if (!input) return ''; const d = new Date(input); if (isNaN(d.getTime())) return ''; const pad = (n) => (n < 10 ? '0' + n : '' + n); return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; } catch (_) { return ''; } }; + const truncateText = (text, maxLength) => { if (!text || text.length <= maxLength) return text; return text.substring(0, maxLength) + '...'; }; + const parseArray = (str) => { try { return Array.isArray(str) ? str : (str ? JSON.parse(str) : []); } catch { return str ? str.split(',').map(s => s.trim()) : []; } }; + + if (platformKey === 'job51') { + // 51job的详细字段显示 - 基于完整的API对象结构 + const jobTitle = escapeHtml(j.jobTitle || '未知岗位'); + const companyName = escapeHtml(j.companyName || '未知公司'); + const workCity = escapeHtml(j.workCity || '未知城市'); + const workArea = escapeHtml(j.workArea || ''); + const businessDistrict = escapeHtml(j.businessDistrict || ''); + const salaryDesc = escapeHtml(j.salaryDesc || '面议'); + const deliverTime = fmt(j.createdAt || j.updatedAt); + const state = statusBadge(j.status || 0); + const hrName = escapeHtml(j.hrName || '未知HR'); + const hrTitle = escapeHtml(j.hrTitle || ''); + const hrActiveTime = escapeHtml(j.hrActiveTime || j.hrActiveTimeDesc || '未知'); + const jobExperience = escapeHtml(j.jobExperience || '经验不限'); + const jobDegree = escapeHtml(j.jobDegree || '学历不限'); + const jobDescription = escapeHtml(truncateText(j.jobDescription || '暂无描述', 100)); + const skills = parseArray(j.skills || j.jobLabels || '[]'); + const companyStage = escapeHtml(j.companyStage || ''); + const companyScale = escapeHtml(j.companyScale || ''); + const companyIndustry = escapeHtml(j.companyIndustry || ''); + const companyTag = escapeHtml(j.companyTag || ''); + const filterReason = escapeHtml(j.filterReason || ''); + + // 新增字段解析 + const encryptJobId = escapeHtml(j.encryptJobId || ''); + const encryptHrId = escapeHtml(j.encryptHrId || ''); + const encryptCompanyId = escapeHtml(j.encryptCompanyId || ''); + const platform = escapeHtml(j.platform || '51job'); + const industryCode = j.industryCode || null; + const jobType = j.jobType || null; + const isDeleted = j.isDeleted || false; + const longitude = j.longitude || null; + const latitude = j.latitude || null; + const hrCertLevel = escapeHtml(j.hrCertLevel || ''); + const jobRequirements = escapeHtml(truncateText(j.jobRequirements || '', 80)); + const remark = escapeHtml(j.remark || ''); + const securityId = escapeHtml(j.securityId || ''); + + return ` + + + + +
    + ID: ${j.id || '-'} +
    +
    + ${encryptJobId ? `JobID: ${encryptJobId}` : ''} + ${encryptHrId ? `HrID: ${encryptHrId}` : ''} +
    +
    + ${j.isFavorite ? '' : ''} + ${j.isOptimal ? '' : ''} + ${j.isContacted ? '' : ''} + ${isDeleted ? '' : ''} +
    + + + +
    ${jobDescription}
    + ${jobRequirements ? `
    要求: ${jobRequirements}
    ` : ''} +
    + ${jobType !== null ? `类型: ${jobType}` : ''} + ${j.jobPositionName ? `
    职位: ${escapeHtml(j.jobPositionName)}
    ` : ''} +
    + ${j.isProxyJob ? '
    代理职位
    ' : ''} + + + +
    ${companyName}
    +
    + ${companyIndustry ? `
    ${companyIndustry}
    ` : ''} + ${companyScale ? `
    ${companyScale}
    ` : ''} + ${companyStage ? `
    ${companyStage}
    ` : ''} +
    + ${j.companyLogo ? `` : ''} + ${encryptCompanyId ? `
    CompanyID: ${encryptCompanyId}
    ` : ''} + ${industryCode ? `
    行业代码: ${industryCode}
    ` : ''} + + + +
    ${workCity}
    + ${workArea ? `
    ${workArea}
    ` : ''} + ${businessDistrict ? `
    ${businessDistrict}
    ` : ''} + ${longitude && latitude ? `
    坐标: ${longitude.toFixed(4)}, ${latitude.toFixed(4)}
    ` : ''} + ${j.cityCode ? `
    城市代码: ${j.cityCode}
    ` : ''} + + + +
    ${salaryDesc}
    +
    + ${j.leastMonthDesc ? `
    ${escapeHtml(j.leastMonthDesc)}
    ` : ''} + ${j.daysPerWeekDesc ? `
    ${escapeHtml(j.daysPerWeekDesc)}
    ` : ''} +
    + ${j.jobPayTypeDesc ? `
    ${escapeHtml(j.jobPayTypeDesc)}
    ` : ''} + + + +
    +
    经验: ${jobExperience}
    +
    学历: ${jobDegree}
    +
    + ${j.jobExperienceName ? `
    经验类型: ${escapeHtml(j.jobExperienceName)}
    ` : ''} + ${j.jobDegreeName ? `
    学历类型: ${escapeHtml(j.jobDegreeName)}
    ` : ''} + + + +
    + ${skills.slice(0, 3).map(skill => `${escapeHtml(skill)}`).join('')} + ${skills.length > 3 ? `+${skills.length - 3}` : ''} +
    + ${parseArray(j.jobLabels || '').length > 0 ? `
    + ${parseArray(j.jobLabels || '').slice(0, 2).map(label => `${escapeHtml(label)}`).join('')} +
    ` : ''} + ${skills.length > 0 ? `
    共${skills.length}项技能
    ` : ''} + + + +
    + ${j.hrAvatar ? `${hrName}` : ''} + ${hrName} +
    +
    + ${hrTitle ? `
    ${hrTitle}
    ` : ''} +
    + + + ${j.hrOnline ? '在线' : '离线'} + +
    + ${hrActiveTime !== '未知' ? `
    ${hrActiveTime}
    ` : ''} + ${hrCertLevel ? `
    认证: ${hrCertLevel}
    ` : ''} +
    + + + +
    + ${companyTag ? parseArray(companyTag).slice(0, 3).map(tag => `${escapeHtml(tag)}`).join('') : ''} + ${companyTag && parseArray(companyTag).length > 3 ? `+${parseArray(companyTag).length - 3}` : ''} +
    + ${j.welfareList ? `
    + ${parseArray(j.welfareList).slice(0, 2).map(welfare => `${escapeHtml(welfare)}`).join('')} +
    ` : ''} + ${companyTag ? `
    共${parseArray(companyTag).length}个标签
    ` : '暂无标签'} + + + +
    ${state}
    +
    + ${j.isGoldHunter ? '金猎' : ''} + ${j.isShielded ? '屏蔽' : ''} + ${j.isOutland ? '海外' : ''} + ${j.showTopPosition ? '置顶' : ''} +
    + ${j.jobValidStatus ? `
    有效性: ${j.jobValidStatus}
    ` : ''} + ${j.jobInvalidStatus ? `
    失效: ${j.jobInvalidStatus}
    ` : ''} + ${filterReason ? `
    过滤: ${truncateText(filterReason, 20)}
    ` : ''} + + + +
    + ${platform} +
    + ${j.searchId ? `
    SearchID: ${escapeHtml(j.searchId)}
    ` : ''} + ${j.itemId ? `
    ItemID: ${escapeHtml(j.itemId)}
    ` : ''} + ${j.expectId ? `
    ExpectID: ${escapeHtml(j.expectId)}
    ` : ''} + ${securityId ? `
    SecurityID: ${securityId}
    ` : ''} + ${j.atsDirectPost ? '
    ATS直投
    ' : ''} + + + +
    +
    + 创建:
    + ${deliverTime || '-'} +
    + ${j.updatedAt ? `
    + 更新:
    + ${fmt(j.updatedAt)} +
    ` : ''} +
    + + + +
    + ${remark ? `
    备注: ${truncateText(remark, 50)}
    ` : ''} + ${j.jobAddress ? `
    ${escapeHtml(truncateText(j.jobAddress, 40))}
    ` : ''} + ${j.anonymousStatus ? `
    匿名状态: ${j.anonymousStatus}
    ` : ''} + ${j.proxyType ? `
    代理类型: ${j.proxyType}
    ` : ''} + ${j.jobDetailType ? `
    详情类型: ${j.jobDetailType}
    ` : ''} +
    + + + +
    + + + + + +
    + + + `; + } else { + // 其他平台保持原有的简单显示 + const jobTitle = escapeHtml(j.jobTitle || '-'); + const companyName = escapeHtml(j.companyName || '-'); + // 修复字段映射问题 - 尝试多个可能的字段名 + const city = escapeHtml(j.workCity || j.workLocation || j.city || '-'); + const salary = escapeHtml(j.salaryDesc || j.salaryRange || j.salary || '-'); + const deliverTime = fmt(j.createdAt || j.updatedAt); + const state = statusBadge(j.status); + const hrActive = escapeHtml(j.hrActiveTime || j.hrActiveTimeDesc || j.bossActiveTimeDesc || '-'); + return ` + + ${jobTitle} + ${companyName} + ${city} + ${salary} + ${deliverTime || '-'} + ${state} + ${hrActive} + + `; + } + }).join(''); + + tbody.innerHTML = rows; + } + + renderPagination(platformKey, page) { + const pagerIdMap = { boss: 'bossRecordPagination', zhilian: 'zhilianRecordPagination', job51: 'job51RecordPagination' }; + const pager = document.getElementById(pagerIdMap[platformKey]); + if (!pager) return; + + const totalPages = page?.totalPages ?? 0; + const number = page?.number ?? 0; + const disabledPrev = number <= 0 ? ' disabled' : ''; + const disabledNext = number >= totalPages - 1 ? ' disabled' : ''; + + const pageItems = []; + for (let i = 0; i < totalPages; i++) { + const active = i === number ? ' active' : ''; + pageItems.push(`
  • ${i + 1}
  • `); + } + + pager.innerHTML = ` +
  • 上一页
  • + ${pageItems.join('')} +
  • 下一页
  • + `; + + pager.querySelectorAll('a.page-link').forEach(a => { + a.addEventListener('click', (e) => { + e.preventDefault(); + const pageTo = parseInt(a.getAttribute('data-page')); + if (!isNaN(pageTo) && pageTo >= 0 && pageTo < totalPages) { + const platformText = platformKey === 'boss' ? 'Boss直聘' : platformKey === 'zhilian' ? '智联招聘' : '51job'; + this.loadJobDetails(platformText, platformKey, pageTo); + } + }); + }); + } + } + + // 导出由 app.js 统一初始化 + window.Views.JobRecords = JobRecords; +})(); + + diff --git a/src/main/resources/static/js/views/job51-config-form.js b/src/main/resources/static/js/views/job51-config-form.js new file mode 100644 index 00000000..e48d6e58 --- /dev/null +++ b/src/main/resources/static/js/views/job51-config-form.js @@ -0,0 +1,1154 @@ +// 51job配置表单管理类 +class Job51ConfigForm { + constructor() { + this.config = {}; + this.isRunning = false; + this.taskStates = { + loginTaskId: null, + collectTaskId: null, + filterTaskId: null, + applyTaskId: null + }; + this.statusPollingInterval = null; // 状态轮询定时器 + this.latestTaskStatus = null; // 缓存最新的任务状态查询结果 + this.init(); + } + + init() { + this.initializeTooltips(); + this.bindEvents(); + // 先加载字典数据,再加载配置数据,确保下拉框已准备好 + this.loadDataSequentially(); + } + + // 初始化工具提示 + initializeTooltips() { + const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); + tooltipTriggerList.map(function (tooltipTriggerEl) { + return new bootstrap.Tooltip(tooltipTriggerEl); + }); + } + + // 绑定事件 + bindEvents() { + // 保存配置按钮 + const saveConfigBtn = document.getElementById('job51SaveConfigBtn'); + if (saveConfigBtn) { + saveConfigBtn.addEventListener('click', () => { + this.handleSaveConfig(); + }); + } + + // 数据库备份按钮 + const backupDataBtn = document.getElementById('job51BackupDataBtn'); + if (backupDataBtn) { + backupDataBtn.addEventListener('click', () => { + this.handleBackupData(); + }); + } + + // 任务执行按钮 + document.getElementById('job51LoginBtn')?.addEventListener('click', () => { + this.handleLogin(); + }); + + // 手动确认登录按钮 + const job51LoginManualBtn = document.getElementById('job51LoginManualBtn'); + if (job51LoginManualBtn) { + job51LoginManualBtn.addEventListener('click', () => { + console.log('51job手动登录按钮被点击'); + this.handleManualLogin(); + }); + console.log('已绑定51job手动登录按钮事件'); + } else { + console.warn('未找到51job手动登录按钮元素'); + } + + document.getElementById('job51CollectBtn')?.addEventListener('click', () => { + this.handleCollect(); + }); + + document.getElementById('job51FilterBtn')?.addEventListener('click', () => { + this.handleFilter(); + }); + + document.getElementById('job51ApplyBtn')?.addEventListener('click', () => { + this.handleApply(); + }); + + document.getElementById('job51ResetTasksBtn')?.addEventListener('click', () => { + this.resetTaskFlow(); + }); + + // 表单验证 + this.bindFormValidation(); + + // 自动保存 + this.bindAutoSave(); + } + + // 表单验证 + bindFormValidation() { + const requiredFields = [ + 'job51KeywordsField', + 'job51CityCodeField', + 'job51ExperienceComboBox', + 'job51JobTypeComboBox', + 'job51SalaryComboBox', + 'job51DegreeComboBox' + ]; + + requiredFields.forEach(fieldId => { + const field = document.getElementById(fieldId); + if (field) { + field.addEventListener('blur', () => { + if (field.tagName === 'SELECT') { + this.validateSelectField(field); + } else { + this.validateField(field); + } + }); + } + }); + } + + // 验证单个字段 + validateField(field) { + const value = field.value.trim(); + const isValid = value.length > 0; + this.updateFieldValidation(field, isValid); + return isValid; + } + + // 验证选择框字段 + validateSelectField(field) { + const value = field.value; + const isValid = value && value !== ''; + this.updateFieldValidation(field, isValid); + return isValid; + } + + // 更新字段验证状态 + updateFieldValidation(field, isValid) { + if (isValid) { + field.classList.remove('is-invalid'); + field.classList.add('is-valid'); + } else { + field.classList.remove('is-valid'); + field.classList.add('is-invalid'); + } + } + + // 自动保存配置 + bindAutoSave() { + const formElements = document.querySelectorAll('#job51-config-pane input, #job51-config-pane select, #job51-config-pane textarea'); + formElements.forEach(element => { + element.addEventListener('change', () => { + this.saveConfig(); + }); + }); + } + + // 保存配置 + saveConfig() { + const getMultiSelectValues = (selectId) => { + const el = document.getElementById(selectId); + if (!el) return ''; + return Array.from(el.selectedOptions).map(o => o.value).filter(Boolean).join(','); + }; + + this.config = { + // 搜索条件 + keywords: document.getElementById('job51KeywordsField')?.value || '', + industry: getMultiSelectValues('job51IndustryField'), + cityCode: getMultiSelectValues('job51CityCodeField'), + + // 职位要求 + experience: document.getElementById('job51ExperienceComboBox')?.value || '', + jobType: document.getElementById('job51JobTypeComboBox')?.value || '', + salary: document.getElementById('job51SalaryComboBox')?.value || '', + degree: document.getElementById('job51DegreeComboBox')?.value || '', + scale: document.getElementById('job51ScaleComboBox')?.value || '', + companyNature: document.getElementById('job51CompanyNatureComboBox')?.value || '', + + + // 功能开关 + autoApply: document.getElementById('job51AutoApplyCheckBox')?.checked || false, + blacklistFilter: document.getElementById('job51BlacklistFilterCheckBox')?.checked || false, + + // AI配置 + enableAIJobMatch: document.getElementById('job51EnableAIJobMatchCheckBox')?.checked || false + }; + + localStorage.setItem('job51Config', JSON.stringify(this.config)); + + // 同步保存到后端 + try { + fetch('https://github.com/api/config/job51', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(this.config) + }).then(() => {}).catch(() => {}); + } catch (e) {} + } + + // 加载保存的配置(优先后端,其次本地缓存) + async loadSavedConfig() { + try { + console.log('开始加载51job保存的配置...'); + const res = await fetch('https://github.com/api/config/job51'); + if (!res.ok) throw new Error('HTTP ' + res.status); + const ct = res.headers.get('content-type') || ''; + let data; + if (ct.includes('application/json')) { + data = await res.json(); + } else { + const text = await res.text(); + const snippet = (text || '').slice(0, 80); + throw new Error('返回非JSON:' + snippet); + } + + console.log('51job后端配置数据:', data); + if (data && typeof data === 'object' && Object.keys(data).length) { + this.config = data; + this.populateForm(); + localStorage.setItem('job51Config', JSON.stringify(this.config)); + console.log('51job配置已从后端加载并回填表单'); + return; + } + + // 如果后端没有数据,尝试本地缓存 + const savedConfig = localStorage.getItem('job51Config'); + if (savedConfig) { + try { + this.config = JSON.parse(savedConfig); + this.populateForm(); + console.log('51job配置已从本地缓存加载并回填表单'); + } catch (error) { + console.warn('51job本地配置损坏,已清理:' + error.message); + localStorage.removeItem('job51Config'); + } + } + } catch (err) { + console.warn('51job后端配置读取失败:' + (err?.message || '未知错误')); + // 尝试本地缓存 + const savedConfig = localStorage.getItem('job51Config'); + if (savedConfig) { + try { + this.config = JSON.parse(savedConfig); + this.populateForm(); + console.log('51job配置已从本地缓存加载并回填表单'); + } catch (error) { + console.warn('51job本地配置损坏,已清理:' + error.message); + localStorage.removeItem('job51Config'); + } + } + } + } + + // 填充表单 + populateForm() { + Object.keys(this.config).forEach(key => { + const element = document.getElementById(this.getFieldId(key)); + if (element) { + if (element.type === 'checkbox') { + element.checked = this.config[key]; + } else { + // 处理可能的数组字段转换为逗号分隔字符串 + let value = this.config[key]; + if (Array.isArray(value)) { + value = value.join(','); + console.log(`51job: 数组字段 ${key} 转换为字符串:`, this.config[key], '->', value); + } + element.value = value || ''; + } + } + }); + + // 回填城市多选 + try { + // 处理数组格式(从后端返回)或字符串格式(从本地缓存) + let cityCodeStr = ''; + if (Array.isArray(this.config.cityCode)) { + cityCodeStr = this.config.cityCode.join(','); + } else { + cityCodeStr = this.config.cityCode || ''; + } + + const codes = cityCodeStr.split(',').map(s => s.trim()).filter(Boolean); + const citySelect = document.getElementById('job51CityCodeField'); + if (citySelect && codes.length) { + console.log('51job回填城市选择,原始数据:', this.config.cityCode, '处理后:', codes); + Array.from(citySelect.options).forEach(opt => { + opt.selected = codes.includes(opt.value); + }); + + // 同步下拉复选框 + const cityListContainer = document.getElementById('job51CityDropdownList'); + const cityDropdownBtn = document.getElementById('job51CityDropdownBtn'); + const citySummary = document.getElementById('job51CitySelectionSummary'); + const selectedSet = new Set(codes); + if (cityListContainer) { + cityListContainer.querySelectorAll('input[type="checkbox"]').forEach(checkbox => { + checkbox.checked = selectedSet.has(checkbox.value); + }); + } + + // 更新按钮文案和摘要 + if (cityDropdownBtn && citySummary) { + const values = Array.from(citySelect.selectedOptions).map(o => o.textContent || '').filter(Boolean); + if (values.length === 0) { + cityDropdownBtn.textContent = '选择城市'; + citySummary.textContent = '未选择'; + } else if (values.length <= 2) { + const text = values.join('、'); + cityDropdownBtn.textContent = text; + citySummary.textContent = `已选 ${values.length} 项:${text}`; + } else { + cityDropdownBtn.textContent = `已选 ${values.length} 项`; + citySummary.textContent = `已选 ${values.length} 项`; + } + } + } + } catch (_) {} + + // 回填行业多选(支持二级列表) + try { + // 处理数组格式(从后端返回)或字符串格式(从本地缓存) + let industryStr = ''; + if (Array.isArray(this.config.industry)) { + industryStr = this.config.industry.join(','); + } else { + industryStr = this.config.industry || ''; + } + + const codes = industryStr.split(',').map(s => s.trim()).filter(Boolean); + const industrySelect = document.getElementById('job51IndustryField'); + if (industrySelect && codes.length) { + console.log('51job回填行业选择,原始数据:', this.config.industry, '处理后:', codes); + Array.from(industrySelect.options).forEach(opt => { + opt.selected = codes.includes(opt.value); + }); + + // 同步下拉复选框 + const industryParentList = document.getElementById('job51IndustryParentList'); + const industryChildList = document.getElementById('job51IndustryChildList'); + const industryDropdownBtn = document.getElementById('job51IndustryDropdownBtn'); + const industrySummary = document.getElementById('job51IndustrySelectionSummary'); + const selectedSet = new Set(codes); + + if (industryChildList) { + industryChildList.querySelectorAll('input[type="checkbox"]').forEach(checkbox => { + checkbox.checked = selectedSet.has(checkbox.value); + }); + } + + // 更新按钮文案和摘要 + if (this.updateIndustrySummary) { + this.updateIndustrySummary(); + } else if (industryDropdownBtn && industrySummary) { + const values = Array.from(industrySelect.selectedOptions).map(o => o.textContent || '').filter(Boolean); + if (values.length === 0) { + industryDropdownBtn.textContent = '选择行业'; + industrySummary.textContent = '未选择'; + } else if (values.length <= 2) { + const text = values.join('、'); + industryDropdownBtn.textContent = text; + industrySummary.textContent = `已选 ${values.length} 项:${text}`; + } else { + industryDropdownBtn.textContent = `已选 ${values.length} 项`; + industrySummary.textContent = `已选 ${values.length} 项`; + } + } + + // 更新父级列表中的计数(如果有) + if (industryParentList) { + industryParentList.querySelectorAll('div.px-2').forEach(parentDiv => { + // 这里只是触发重新渲染,实际渲染由renderIndustrySelection负责 + }); + } + } + } catch (_) {} + } + + // 获取字段ID + getFieldId(key) { + const fieldMap = { + keywords: 'job51KeywordsField', + industry: 'job51IndustryField', + cityCode: 'job51CityCodeField', + experience: 'job51ExperienceComboBox', + jobType: 'job51JobTypeComboBox', + salary: 'job51SalaryComboBox', + degree: 'job51DegreeComboBox', + scale: 'job51ScaleComboBox', + companyNature: 'job51CompanyNatureComboBox', + autoApply: 'job51AutoApplyCheckBox', + blacklistFilter: 'job51BlacklistFilterCheckBox', + blacklistKeywords: 'job51BlacklistKeywordsTextArea', + enableAIJobMatch: 'job51EnableAIJobMatchCheckBox' + }; + return fieldMap[key] || key; + } + + // 按顺序加载数据:先字典,后配置 + async loadDataSequentially() { + try { + console.log('51job: 开始按顺序加载数据:字典 -> 配置'); + + // 先加载字典数据 + await this.loadJob51Dicts(); + console.log('51job: 字典数据加载完成,开始加载配置数据'); + + // 等待DOM元素完全渲染 + await this.waitForDOMReady(); + + // 再加载配置数据 + await this.loadSavedConfig(); + console.log('51job: 配置数据加载完成'); + } catch (error) { + console.error('51job数据加载失败:', error); + } + } + + // 等待DOM元素完全准备就绪 + async waitForDOMReady() { + return new Promise((resolve) => { + // 等待一个事件循环,确保所有DOM操作完成 + setTimeout(() => { + console.log('51job: DOM元素准备就绪'); + resolve(); + }, 100); + }); + } + + // 加载51job字典数据 + async loadJob51Dicts() { + try { + console.log('开始加载51job字典数据...'); + const res = await fetch('https://github.com/dicts/JOB_51'); + if (!res.ok) throw new Error('HTTP ' + res.status); + const data = await res.json(); + console.log('接收到51job字典数据:', data); + + if (!data || !Array.isArray(data.groups)) { + console.warn('51job字典数据结构不正确:', data); + return; + } + + const groupMap = new Map(); + data.groups.forEach(g => { + console.log(`处理51job字典组: ${g.key}, 项目数量: ${Array.isArray(g.items) ? g.items.length : 0}`); + groupMap.set(g.key, Array.isArray(g.items) ? g.items : []); + }); + + // 渲染城市选择 + const cityItems = groupMap.get('cityList') || []; + console.log('51job城市数据:', cityItems); + this.renderCitySelection(cityItems); + + // 渲染行业选择(二级列表) + const industryItems = groupMap.get('industryList') || []; + console.log('51job行业数据:', industryItems); + this.renderIndustrySelection(industryItems); + + // 渲染其他选择框 + console.log('开始渲染51job其他下拉框...'); + this.fillSelect('job51ExperienceComboBox', groupMap.get('experienceList')); + this.fillSelect('job51JobTypeComboBox', groupMap.get('jobTypeList')); + this.fillSelect('job51SalaryComboBox', groupMap.get('salaryList')); + this.fillSelect('job51DegreeComboBox', groupMap.get('degreeList')); + this.fillSelect('job51ScaleComboBox', groupMap.get('scaleList')); + this.fillSelect('job51CompanyNatureComboBox', groupMap.get('companyNatureList')); + console.log('51job所有下拉框渲染完成'); + + } catch (e) { + console.warn('加载51job字典失败:', e?.message || e); + throw e; // 重新抛出错误,让调用者知道字典加载失败 + } + } + + // 渲染行业选择(二级列表) + renderIndustrySelection(industryItems) { + console.log('51job: 渲染行业选择器(级联选择),行业数量:', industryItems.length); + + const industrySelect = document.getElementById('job51IndustryField'); + const industrySearch = document.getElementById('job51IndustrySearchField'); + const parentListContainer = document.getElementById('job51IndustryParentList'); + const childListContainer = document.getElementById('job51IndustryChildList'); + const industryDropdownBtn = document.getElementById('job51IndustryDropdownBtn'); + const industrySummary = document.getElementById('job51IndustrySelectionSummary'); + + if (!industrySelect || !parentListContainer || !childListContainer) { + console.error('51job: 行业选择器DOM元素未找到'); + return; + } + + // 保存原始数据供搜索使用 + this.allIndustryItems = industryItems; + + // 区分父级和子级 + const parents = industryItems.filter(it => !it.parentCode); + const children = industryItems.filter(it => it.parentCode); + + // 构建映射关系 + const childrenMap = new Map(); + parents.forEach(parent => { + childrenMap.set(parent.code, children.filter(child => child.parentCode === parent.code)); + }); + + console.log('51job: 一级行业数:', parents.length, '二级行业数:', children.length); + + // 当前选中的父级行业 + let currentParentCode = null; + + const updateIndustrySummary = () => { + if (!industrySelect || !industryDropdownBtn || !industrySummary) return; + const values = Array.from(industrySelect.selectedOptions).map(o => o.textContent); + if (values.length === 0) { + industryDropdownBtn.textContent = '选择行业'; + industrySummary.textContent = '未选择'; + } else if (values.length <= 2) { + const text = values.join('、'); + industryDropdownBtn.textContent = text; + industrySummary.textContent = `已选 ${values.length} 项:${text}`; + } else { + industryDropdownBtn.textContent = `已选 ${values.length} 项`; + industrySummary.textContent = `已选 ${values.length} 项`; + } + }; + + // 将updateIndustrySummary方法绑定到实例,供其他方法调用 + this.updateIndustrySummary = updateIndustrySummary; + + // 初始化:将所有子项添加到隐藏的select中,以便后续回填 + console.log('51job: 初始化行业select,添加所有子项:', children.length); + industrySelect.innerHTML = ''; + children.forEach(child => { + const value = child.code ?? child.name ?? ''; + const label = child.name ?? String(child.code ?? ''); + const opt = document.createElement('option'); + opt.value = value; + opt.textContent = label; + industrySelect.appendChild(opt); + }); + + // 渲染左侧一级行业列表 + const renderParentList = (parentList) => { + parentListContainer.innerHTML = ''; + const selected = new Set(Array.from(industrySelect.selectedOptions).map(o => o.value)); + + parentList.forEach(parent => { + const parentCode = parent.code ?? ''; + const parentName = parent.name ?? String(parent.code ?? ''); + const childCount = (childrenMap.get(parentCode) || []).length; + + // 计算该父级下有多少子项被选中 + const childItems = childrenMap.get(parentCode) || []; + const selectedCount = childItems.filter(child => selected.has(child.code ?? child.name ?? '')).length; + + const itemDiv = document.createElement('div'); + itemDiv.className = 'px-2 py-1 mb-1 rounded'; + itemDiv.style.cursor = 'pointer'; + itemDiv.style.transition = 'background-color 0.15s'; + + if (currentParentCode === parentCode) { + itemDiv.classList.add('bg-primary', 'text-white'); + } + + itemDiv.innerHTML = ` +
    + ${parentName} + ${selectedCount}/${childCount} +
    + `; + + // 鼠标悬停效果 + itemDiv.addEventListener('mouseenter', () => { + if (currentParentCode !== parentCode) { + itemDiv.style.backgroundColor = '#f8f9fa'; + } + }); + itemDiv.addEventListener('mouseleave', () => { + if (currentParentCode !== parentCode) { + itemDiv.style.backgroundColor = ''; + } + }); + + // 点击选择父级行业 + itemDiv.addEventListener('click', (e) => { + e.stopPropagation(); // 阻止事件冒泡,防止dropdown关闭 + currentParentCode = parentCode; + renderParentList(parentList); + renderChildList(childrenMap.get(parentCode) || []); + }); + + parentListContainer.appendChild(itemDiv); + }); + }; + + // 渲染右侧二级行业列表 + const renderChildList = (childList) => { + childListContainer.innerHTML = ''; + + if (childList.length === 0) { + childListContainer.innerHTML = '
    该分类下暂无子项
    '; + return; + } + + const selected = new Set(Array.from(industrySelect.selectedOptions).map(o => o.value)); + + // 渲染复选框列表(不再重建select,因为所有选项已在初始化时添加) + childList.forEach(child => { + const value = child.code ?? child.name ?? ''; + const label = child.name ?? String(child.code ?? ''); + const checkDiv = document.createElement('div'); + checkDiv.className = 'form-check mb-1'; + const checkId = `job51_industry_chk_${value}`.replace(/[^a-zA-Z0-9_\-]/g, '_'); + checkDiv.innerHTML = ` + + + `; + + const checkbox = checkDiv.querySelector('input[type="checkbox"]'); + checkbox.addEventListener('change', (e) => { + e.stopPropagation(); // 阻止事件冒泡,防止dropdown关闭 + + // 限制最多选择5个 + const currentSelected = Array.from(industrySelect.selectedOptions); + if (checkbox.checked && currentSelected.length >= 5) { + checkbox.checked = false; + this.showToast('最多只能选择5个行业', 'warning'); + return; + } + + const option = Array.from(industrySelect.options).find(o => o.value === value); + if (option) option.selected = checkbox.checked; + updateIndustrySummary(); + // 更新父级列表中的计数 + renderParentList(parents); + }); + + childListContainer.appendChild(checkDiv); + }); + }; + + // 初始渲染 + renderParentList(parents); + + // 如果有已选项,自动展开对应的父级 + const selectedValues = Array.from(industrySelect.selectedOptions).map(o => o.value); + if (selectedValues.length > 0) { + // 找到第一个已选项的父级 + const firstSelected = children.find(c => selectedValues.includes(c.code ?? c.name ?? '')); + if (firstSelected && firstSelected.parentCode) { + currentParentCode = firstSelected.parentCode; + renderParentList(parents); + renderChildList(childrenMap.get(currentParentCode) || []); + } + } + + updateIndustrySummary(); + + // 搜索功能 + if (industrySearch) { + industrySearch.addEventListener('input', () => { + const kw = (industrySearch.value || '').trim().toLowerCase(); + if (!kw) { + // 无搜索词,显示所有父级 + renderParentList(parents); + if (currentParentCode) { + renderChildList(childrenMap.get(currentParentCode) || []); + } + return; + } + + // 筛选匹配的父级(按名称或编码) + const matchedParents = parents.filter(p => + String(p.name || '').toLowerCase().includes(kw) || + String(p.code || '').toLowerCase().includes(kw) + ); + + // 筛选匹配的子级 + const matchedChildren = children.filter(c => + String(c.name || '').toLowerCase().includes(kw) || + String(c.code || '').toLowerCase().includes(kw) + ); + + // 如果有子级匹配,则显示其父级 + const parentCodesFromChildren = new Set(matchedChildren.map(c => c.parentCode).filter(Boolean)); + const parentsToShow = new Set([ + ...matchedParents.map(p => p.code), + ...Array.from(parentCodesFromChildren) + ]); + + const filteredParents = parents.filter(p => parentsToShow.has(p.code)); + + renderParentList(filteredParents); + + // 如果只有一个父级匹配或当前有选中的父级,自动展开 + if (filteredParents.length === 1) { + currentParentCode = filteredParents[0].code; + renderParentList(filteredParents); + renderChildList((childrenMap.get(currentParentCode) || []).filter(c => + String(c.name || '').toLowerCase().includes(kw) || + String(c.code || '').toLowerCase().includes(kw) + )); + } else if (currentParentCode && parentsToShow.has(currentParentCode)) { + renderChildList((childrenMap.get(currentParentCode) || []).filter(c => + String(c.name || '').toLowerCase().includes(kw) || + String(c.code || '').toLowerCase().includes(kw) + )); + } else { + childListContainer.innerHTML = '
    请选择左侧分类
    '; + } + }); + } + } + + // 渲染城市选择 + renderCitySelection(cityItems) { + const citySelect = document.getElementById('job51CityCodeField'); + const citySearch = document.getElementById('job51CitySearchField'); + const cityListContainer = document.getElementById('job51CityDropdownList'); + const cityDropdownBtn = document.getElementById('job51CityDropdownBtn'); + const citySummary = document.getElementById('job51CitySelectionSummary'); + + const updateCitySummary = () => { + if (!citySelect || !cityDropdownBtn || !citySummary) return; + const values = Array.from(citySelect.selectedOptions).map(o => o.textContent); + if (values.length === 0) { + cityDropdownBtn.textContent = '选择城市'; + citySummary.textContent = '未选择'; + } else if (values.length <= 2) { + const text = values.join('、'); + cityDropdownBtn.textContent = text; + citySummary.textContent = `已选 ${values.length} 项:${text}`; + } else { + cityDropdownBtn.textContent = `已选 ${values.length} 项`; + citySummary.textContent = `已选 ${values.length} 项`; + } + }; + + const renderCityOptions = (list) => { + if (!citySelect) return; + + // 保留当前已选 + const selected = new Set(Array.from(citySelect.selectedOptions).map(o => o.value)); + + // 重建隐藏select + citySelect.innerHTML = ''; + list.forEach(it => { + const value = it.code ?? ''; + const label = `${it.name ?? ''}${it.code ? ' (' + it.code + ')' : ''}`; + const opt = document.createElement('option'); + opt.value = value; + opt.textContent = label; + if (selected.has(value)) opt.selected = true; + citySelect.appendChild(opt); + }); + + // 重建dropdown列表 + if (cityListContainer) { + cityListContainer.innerHTML = ''; + list.forEach(it => { + const value = it.code ?? ''; + const label = `${it.name ?? ''}${it.code ? ' (' + it.code + ')' : ''}`; + + const item = document.createElement('div'); + item.className = 'form-check mb-1'; + const id = `job51_city_chk_${value}`.replace(/[^a-zA-Z0-9_\-]/g, '_'); + item.innerHTML = ` + + + `; + const checkbox = item.querySelector('input[type="checkbox"]'); + checkbox.addEventListener('change', () => { + // 同步到隐藏select + const option = Array.from(citySelect.options).find(o => o.value === value); + if (option) option.selected = checkbox.checked; + updateCitySummary(); + }); + cityListContainer.appendChild(item); + }); + } + + updateCitySummary(); + }; + + renderCityOptions(cityItems); + if (citySearch) { + citySearch.addEventListener('input', () => { + const kw = citySearch.value.trim().toLowerCase(); + if (!kw) { + renderCityOptions(cityItems); + return; + } + const filtered = cityItems.filter(it => + String(it.name || '').toLowerCase().includes(kw) || + String(it.code || '').toLowerCase().includes(kw) + ); + renderCityOptions(filtered); + }); + } + } + + // 通用方法:将字典渲染到 select + fillSelect(selectId, items) { + console.log(`51job: 填充下拉框 ${selectId},数据项数量:`, Array.isArray(items) ? items.length : 0); + + if (!Array.isArray(items) || items.length === 0) { + console.warn(`51job: 下拉框 ${selectId} 的数据无效:`, items); + return; + } + + const sel = document.getElementById(selectId); + if (!sel) { + console.warn(`51job: 未找到下拉框元素: ${selectId}`); + // 延迟重试 + setTimeout(() => { + const retrySel = document.getElementById(selectId); + if (retrySel) { + console.log(`51job: 重试填充下拉框 ${selectId}`); + this.fillSelect(selectId, items); + } + }, 500); + return; + } + + try { + // 保留第一项"请选择",其余重建 + const first = sel.querySelector('option'); + sel.innerHTML = ''; + if (first && first.value === '') sel.appendChild(first); + + items.forEach(it => { + const opt = document.createElement('option'); + opt.value = it.code ?? it.name ?? ''; + opt.textContent = it.name ?? String(it.code ?? ''); + sel.appendChild(opt); + }); + + console.log(`51job: 下拉框 ${selectId} 填充完成,共 ${items.length} 项`); + + } catch (error) { + console.error(`51job: 填充下拉框 ${selectId} 时出错:`, error); + } + } + + // 更新城市选择摘要 + updateCitySummary() { + const citySelect = document.getElementById('job51CityCodeField'); + const cityDropdownBtn = document.getElementById('job51CityDropdownBtn'); + const citySummary = document.getElementById('job51CitySelectionSummary'); + + if (!citySelect || !cityDropdownBtn || !citySummary) return; + + const values = Array.from(citySelect.selectedOptions).map(o => o.textContent); + if (values.length === 0) { + cityDropdownBtn.textContent = '选择城市'; + citySummary.textContent = '未选择'; + } else if (values.length <= 2) { + const text = values.join('、'); + cityDropdownBtn.textContent = text; + citySummary.textContent = `已选 ${values.length} 项:${text}`; + } else { + cityDropdownBtn.textContent = `已选 ${values.length} 项`; + citySummary.textContent = `已选 ${values.length} 项`; + } + } + + // 处理保存配置 + handleSaveConfig() { + this.saveConfig(); + this.showToast('51job配置已保存'); + } + + // 处理数据库备份 + handleBackupData() { + const backupBtn = document.getElementById('job51BackupDataBtn'); + if (backupBtn) { + backupBtn.disabled = true; + backupBtn.innerHTML = '备份中...'; + } + + fetch('https://github.com/api/backup/export', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + this.showToast('数据库备份成功', 'success'); + console.log('备份路径:', data.backupPath); + } else { + this.showToast('数据库备份失败: ' + data.message, 'danger'); + } + }) + .catch(error => { + console.error('备份请求失败:', error); + this.showToast('数据库备份失败: ' + error.message, 'danger'); + }) + .finally(() => { + if (backupBtn) { + backupBtn.disabled = false; + backupBtn.innerHTML = '数据库备份'; + } + }); + } + + // 处理登录 + async handleLogin() { + if (!this.validateRequiredFields()) { + this.showAlertModal('验证失败', '请先完善必填项'); + return; + } + + try { + const config = this.getCurrentConfig(); + const response = await fetch('https://github.com/api/job51/task/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(config) + }); + + const result = await response.json(); + + if (result.success) { + this.taskStates.loginTaskId = result.taskId; + this.showToast('51job登录任务已提交'); + } else { + this.showToast(result.message || '登录失败', 'danger'); + } + } catch (error) { + this.showToast('登录接口调用失败: ' + error.message, 'danger'); + } + } + + // 手动确认登录 - UI状态由app.js统一处理 + handleManualLogin() { + this.taskStates.loginTaskId = 'manual_login_' + Date.now(); + // UI状态更新由app.js的TaskStatusUpdater统一处理 + this.showToast('已手动标记为登录状态', 'success'); + } + + // 处理采集 + async handleCollect() { + + try { + const config = this.getCurrentConfig(); + const response = await fetch('https://github.com/api/job51/task/collect', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(config) + }); + + const result = await response.json(); + + if (result.success) { + this.taskStates.collectTaskId = result.taskId; + this.showToast('51job采集任务已提交'); + } else { + this.showToast(result.message || '采集失败', 'danger'); + } + } catch (error) { + this.showToast('采集接口调用失败: ' + error.message, 'danger'); + } + } + + // 处理过滤 + async handleFilter() { + + try { + const config = this.getCurrentConfig(); + const request = { + collectTaskId: this.taskStates.collectTaskId, + config: config + }; + + const response = await fetch('https://github.com/api/job51/task/filter', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request) + }); + + const result = await response.json(); + + if (result.success) { + this.taskStates.filterTaskId = result.taskId; + this.showToast('51job过滤任务已提交'); + } else { + this.showToast(result.message || '过滤失败', 'danger'); + } + } catch (error) { + this.showToast('过滤接口调用失败: ' + error.message, 'danger'); + } + } + + // 处理投递 + async handleApply() { + + this.showConfirmModal( + '投递确认', + '是否执行实际投递?\n点击"确定"将真实投递简历\n点击"取消"将仅模拟投递', + () => this.executeApply(true), + () => this.executeApply(false) + ); + } + + // 执行投递 + async executeApply(enableActualDelivery) { + + try { + const config = this.getCurrentConfig(); + const request = { + filterTaskId: this.taskStates.filterTaskId, + config: config, + enableActualDelivery: enableActualDelivery + }; + + const response = await fetch('https://github.com/api/job51/task/deliver', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request) + }); + + const result = await response.json(); + + if (result.success) { + this.taskStates.applyTaskId = result.taskId; + const deliveryType = enableActualDelivery ? '实际投递' : '模拟投递'; + this.showToast(`51job${deliveryType}任务已提交`); + } else { + this.showToast(result.message || '投递失败', 'danger'); + } + } catch (error) { + this.showToast('投递接口调用失败: ' + error.message, 'danger'); + } + } + + // 获取当前配置 + getCurrentConfig() { + const getMultiSelectValues = (selectId) => { + const el = document.getElementById(selectId); + if (!el) return ''; + return Array.from(el.selectedOptions).map(o => o.value).filter(Boolean).join(','); + }; + + return { + keywords: document.getElementById('job51KeywordsField')?.value || '', + industry: getMultiSelectValues('job51IndustryField'), + cityCode: getMultiSelectValues('job51CityCodeField'), + experience: document.getElementById('job51ExperienceComboBox')?.value || '', + jobType: document.getElementById('job51JobTypeComboBox')?.value || '', + salary: document.getElementById('job51SalaryComboBox')?.value || '', + degree: document.getElementById('job51DegreeComboBox')?.value || '', + scale: document.getElementById('job51ScaleComboBox')?.value || '', + companyNature: document.getElementById('job51CompanyNatureComboBox')?.value || '', + autoApply: document.getElementById('job51AutoApplyCheckBox')?.checked || false, + blacklistFilter: document.getElementById('job51BlacklistFilterCheckBox')?.checked || false, + blacklistKeywords: document.getElementById('job51BlacklistKeywordsTextArea')?.value || '', + enableAIJobMatch: document.getElementById('job51EnableAIJobMatchCheckBox')?.checked || false + }; + } + + // 验证必填字段 + validateRequiredFields() { + const requiredFields = [ + 'job51KeywordsField', + 'job51CityCodeField', + 'job51ExperienceComboBox', + 'job51JobTypeComboBox', + 'job51SalaryComboBox', + 'job51DegreeComboBox' + ]; + + let isValid = true; + + requiredFields.forEach(fieldId => { + const field = document.getElementById(fieldId); + if (field) { + if (field.tagName === 'SELECT') { + if (!this.validateSelectField(field)) { + isValid = false; + } + } else { + if (!this.validateField(field)) { + isValid = false; + } + } + } + }); + + return isValid; + } + + + // 重置任务流程 + resetTaskFlow() { + this.showConfirmModal( + '重置确认', + '确定要重置任务流程吗?这将清除所有任务状态。', + () => { + // 只重置任务状态数据,UI状态由app.js的TaskStatusUpdater处理 + this.taskStates = { + loginTaskId: null, + collectTaskId: null, + filterTaskId: null, + applyTaskId: null + }; + this.showToast('任务流程已重置', 'info'); + } + ); + } + + // 显示全局Toast + showToast(message, variant = 'success') { + try { + const toastEl = document.getElementById('globalToast'); + const bodyEl = document.getElementById('globalToastBody'); + if (!toastEl || !bodyEl) return; + bodyEl.textContent = message || '操作成功'; + const variants = ['success', 'danger', 'warning', 'info', 'primary', 'secondary', 'dark']; + variants.forEach(v => toastEl.classList.remove(`text-bg-${v}`)); + toastEl.classList.add(`text-bg-${variant}`); + const toast = bootstrap.Toast.getOrCreateInstance(toastEl, { delay: 2000 }); + toast.show(); + } catch (_) {} + } + + // 显示确认对话框 + showConfirmModal(title, message, onConfirm, onCancel) { + const modal = new bootstrap.Modal(document.getElementById('confirmModal')); + document.getElementById('confirmModalLabel').textContent = title; + document.getElementById('confirmModalBody').textContent = message; + + const okBtn = document.getElementById('confirmModalOk'); + const newOkBtn = okBtn.cloneNode(true); + okBtn.parentNode.replaceChild(newOkBtn, okBtn); + + newOkBtn.addEventListener('click', () => { + modal.hide(); + if (onConfirm) onConfirm(); + }); + + modal._element.addEventListener('hidden.bs.modal', () => { + if (onCancel) onCancel(); + }, { once: true }); + + modal.show(); + } + + // 显示提示对话框 + showAlertModal(title, message) { + const modal = new bootstrap.Modal(document.getElementById('alertModal')); + document.getElementById('alertModalLabel').textContent = title; + document.getElementById('alertModalBody').textContent = message; + modal.show(); + } +} + +// 导出到全局 +window.Job51ConfigForm = Job51ConfigForm; diff --git a/src/main/resources/static/js/views/job51-records-vue.js b/src/main/resources/static/js/views/job51-records-vue.js new file mode 100644 index 00000000..0b417913 --- /dev/null +++ b/src/main/resources/static/js/views/job51-records-vue.js @@ -0,0 +1,261 @@ +/** + * 51job岗位明细Vue组件 + */ +const { createApp } = Vue; + +// 创建51job岗位明细Vue应用 +const Job51RecordsApp = createApp({ + data() { + return { + loading: false, + error: null, + records: [], + totalElements: 0, + totalPages: 0, + currentPage: 0, + pageSize: 10, + searchKeyword: '', + + // 状态映射 + statusMap: { + 0: { text: '待投递', class: 'badge bg-secondary' }, + 1: { text: '已投递', class: 'badge bg-primary' }, + 2: { text: '已沟通', class: 'badge bg-info' }, + 3: { text: '面试中', class: 'badge bg-warning' }, + 4: { text: '已录用', class: 'badge bg-success' }, + 5: { text: '已拒绝', class: 'badge bg-danger' }, + 6: { text: '已过期', class: 'badge bg-dark' } + }, + + // 工作类型映射 + jobTypeMap: { + 0: '全职', + 1: '兼职', + 2: '实习', + 3: '合同工' + } + }; + }, + + computed: { + // 可见页码 + visiblePages() { + const start = Math.max(0, this.currentPage - 2); + const end = Math.min(this.totalPages, start + 5); + return Array.from({ length: end - start }, (_, i) => start + i); + } + }, + + mounted() { + this.loadRecords(); + + // 绑定搜索功能 + const searchBtn = document.getElementById('job51RecordSearchBtn'); + const refreshBtn = document.getElementById('job51RecordRefreshBtn'); + const searchInput = document.getElementById('job51RecordKeyword'); + + if (searchBtn) { + searchBtn.addEventListener('click', () => { + this.searchKeyword = searchInput?.value || ''; + this.currentPage = 0; + this.loadRecords(); + }); + } + + if (refreshBtn) { + refreshBtn.addEventListener('click', () => { + this.searchKeyword = ''; + if (searchInput) searchInput.value = ''; + this.currentPage = 0; + this.loadRecords(); + }); + } + + if (searchInput) { + searchInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.searchKeyword = e.target.value; + this.currentPage = 0; + this.loadRecords(); + } + }); + } + }, + + methods: { + // 加载岗位记录 + async loadRecords() { + this.loading = true; + this.error = null; + + try { + const params = new URLSearchParams({ + page: this.currentPage, + size: this.pageSize, + sort: 'createdAt,desc' + }); + + if (this.searchKeyword) { + params.append('keyword', this.searchKeyword); + } + + const response = await fetch(`/api/jobs?platform=51job&${params}`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + this.records = data.content || []; + this.totalElements = data.totalElements || 0; + this.totalPages = data.totalPages || 0; + this.currentPage = data.number || 0; + + } catch (error) { + console.error('加载51job岗位记录失败:', error); + this.error = '加载数据失败,请稍后重试'; + this.records = []; + } finally { + this.loading = false; + } + }, + + // 分页跳转 + goToPage(page) { + if (page >= 0 && page < this.totalPages && page !== this.currentPage) { + this.currentPage = page; + this.loadRecords(); + } + }, + + // 格式化日期时间 + formatDateTime(dateStr) { + if (!dateStr) return '-'; + try { + const date = new Date(dateStr); + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }); + } catch (e) { + return dateStr; + } + }, + + // 解析数组字符串 + parseArray(arrayStr) { + if (!arrayStr) return []; + if (Array.isArray(arrayStr)) return arrayStr; + try { + return JSON.parse(arrayStr); + } catch (e) { + // 如果不是JSON格式,尝试按逗号分割 + return arrayStr.split(',').map(item => item.trim()).filter(item => item); + } + }, + + // 截断文本 + truncateText(text, maxLength = 100) { + if (!text) return ''; + return text.length > maxLength ? text.substring(0, maxLength) + '...' : text; + }, + + // 获取状态显示信息 + getStatusInfo(status) { + return this.statusMap[status] || { text: '未知', class: 'badge bg-secondary' }; + }, + + // 获取工作类型文本 + getJobTypeText(jobType) { + return this.jobTypeMap[jobType] || '未知'; + }, + + // 查看职位详情 + viewJobDetail(job) { + if (job.jobUrl) { + window.open(job.jobUrl, '_blank'); + } else { + this.showAlert('暂无职位链接'); + } + }, + + // 复制职位链接 + async copyJobUrl(job) { + if (!job.jobUrl) { + this.showAlert('暂无职位链接'); + return; + } + + try { + await navigator.clipboard.writeText(job.jobUrl); + this.showToast('链接已复制到剪贴板'); + } catch (error) { + console.error('复制失败:', error); + this.showAlert('复制失败,请手动复制链接'); + } + }, + + // 收藏/取消收藏 + async toggleFavorite(job) { + try { + // 这里可以实现收藏功能的API调用 + // 目前先只在前端更新状态,后续可以添加后端API + job.isFavorite = !job.isFavorite; + this.showToast(job.isFavorite ? '已收藏' : '已取消收藏'); + } catch (error) { + console.error('收藏操作失败:', error); + this.showAlert('操作失败,请稍后重试'); + } + }, + + // 查看公司信息 + viewCompanyInfo(job) { + // 可以在这里实现公司信息弹窗或跳转 + if (job.companyName) { + this.showAlert(`公司名称:${job.companyName}\n行业:${job.companyIndustry || '未知'}\n规模:${job.companyScale || '未知'}\n性质:${job.companyStage || '未知'}`); + } + }, + + // 显示Toast消息 + showToast(message, type = 'success') { + const toastEl = document.getElementById('globalToast'); + const toastBody = document.getElementById('globalToastBody'); + + if (toastEl && toastBody) { + toastBody.textContent = message; + toastEl.className = `toast align-items-center text-bg-${type} border-0`; + + const toast = new bootstrap.Toast(toastEl); + toast.show(); + } + }, + + // 显示Alert对话框 + showAlert(message) { + const modalEl = document.getElementById('alertModal'); + const modalBody = document.getElementById('alertModalBody'); + + if (modalEl && modalBody) { + modalBody.textContent = message; + const modal = new bootstrap.Modal(modalEl); + modal.show(); + } else { + alert(message); + } + } + } +}); + +// 等待DOM加载完成后挂载Vue应用 +document.addEventListener('DOMContentLoaded', function() { + const container = document.getElementById('job51RecordsVueApp'); + if (container) { + const rootInstance = Job51RecordsApp.mount('#job51RecordsVueApp'); + // 暴露根组件实例,便于外部按钮调用其方法 + window.job51RecordsRoot = rootInstance; + } +}); diff --git a/src/main/resources/static/js/views/liepin-config-form.js b/src/main/resources/static/js/views/liepin-config-form.js new file mode 100644 index 00000000..21edae6d --- /dev/null +++ b/src/main/resources/static/js/views/liepin-config-form.js @@ -0,0 +1,770 @@ +// liepin配置表单管理类 +class LiepinConfigForm { + constructor() { + this.config = {}; + this.isRunning = false; + this.taskStates = { + loginTaskId: null, + collectTaskId: null, + filterTaskId: null, + applyTaskId: null + }; + this.statusPollingInterval = null; // 状态轮询定时器 + this.latestTaskStatus = null; // 缓存最新的任务状态查询结果 + this.init(); + } + + init() { + this.initializeTooltips(); + this.bindEvents(); + this.loadDataSequentially(); + } + + initializeTooltips() { + const tooltipTriggerList = [].slice.call(document.querySelectorAll('#liepin-pane [data-bs-toggle="tooltip"]')); + tooltipTriggerList.map(function (tooltipTriggerEl) { + return new bootstrap.Tooltip(tooltipTriggerEl); + }); + } + + bindEvents() { + document.getElementById('liepinSaveConfigBtn')?.addEventListener('click', () => this.handleSaveConfig()); + document.getElementById('liepinBackupDataBtn')?.addEventListener('click', () => this.handleBackupData()); + document.getElementById('liepinLoginBtn')?.addEventListener('click', () => this.handleLogin()); + document.getElementById('liepinLoginManualBtn')?.addEventListener('click', () => this.handleManualLogin()); + document.getElementById('liepinCollectBtn')?.addEventListener('click', () => this.handleCollect()); + document.getElementById('liepinFilterBtn')?.addEventListener('click', () => this.handleFilter()); + document.getElementById('liepinApplyBtn')?.addEventListener('click', () => this.handleApply()); + document.getElementById('liepinResetTasksBtn')?.addEventListener('click', () => this.resetTaskFlow()); + + this.bindAutoSave(); + } + + bindAutoSave() { + const formElements = document.querySelectorAll('#liepin-config-pane input, #liepin-config-pane select, #liepin-config-pane textarea'); + formElements.forEach(element => { + element.addEventListener('change', () => { + this.saveConfig(); + }); + }); + } + + saveConfig() { + const getMultiSelectValues = (selectId) => { + const el = document.getElementById(selectId); + if (!el) return ''; + return Array.from(el.selectedOptions).map(o => o.value).filter(Boolean).join(','); + }; + + this.config = { + keywords: document.getElementById('liepinKeywordsField')?.value || '', + industry: getMultiSelectValues('liepinIndustryField'), + cityCode: getMultiSelectValues('liepinCityCodeField'), + experience: document.getElementById('liepinExperienceComboBox')?.value || '', + jobType: document.getElementById('liepinJobTypeComboBox')?.value || '', + salary: document.getElementById('liepinSalaryComboBox')?.value || '', + degree: document.getElementById('liepinDegreeComboBox')?.value || '', + scale: document.getElementById('liepinScaleComboBox')?.value || '', + companyNature: document.getElementById('liepinCompanyNatureComboBox')?.value || '', + recruiterActivity: document.getElementById('liepinRecruiterActivityComboBox')?.value || '', + blacklistFilter: document.getElementById('liepinBlacklistFilterCheckBox')?.checked || false, + enableAIJobMatch: document.getElementById('liepinEnableAIJobMatchCheckBox')?.checked || false + }; + + localStorage.setItem('liepinConfig', JSON.stringify(this.config)); + + try { + fetch('https://github.com/api/config/liepin', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(this.config) + }).then(() => {}).catch(() => {}); + } catch (e) {} + } + + async loadSavedConfig() { + try { + const res = await fetch('https://github.com/api/config/liepin'); + if (!res.ok) throw new Error('HTTP ' + res.status); + const data = await res.json(); + if (data && typeof data === 'object' && Object.keys(data).length) { + this.config = data; + this.populateForm(); + localStorage.setItem('liepinConfig', JSON.stringify(this.config)); + return; + } + } catch (err) { + console.warn('Liepin backend config read failed: ' + (err?.message || 'Unknown error')); + } + + const savedConfig = localStorage.getItem('liepinConfig'); + if (savedConfig) { + try { + this.config = JSON.parse(savedConfig); + this.populateForm(); + } catch (error) { + console.warn('Liepin local config corrupted, cleared: ' + error.message); + localStorage.removeItem('liepinConfig'); + } + } + } + + populateForm() { + Object.keys(this.config).forEach(key => { + const element = document.getElementById(this.getFieldId(key)); + if (element) { + if (element.type === 'checkbox') { + element.checked = this.config[key]; + } else { + let value = this.config[key]; + if (Array.isArray(value)) { + value = value.join(','); + } + element.value = value || ''; + } + } + }); + + try { + let cityCodeStr = Array.isArray(this.config.cityCode) ? this.config.cityCode.join(',') : (this.config.cityCode || ''); + const codes = cityCodeStr.split(',').map(s => s.trim()).filter(Boolean); + const citySelect = document.getElementById('liepinCityCodeField'); + if (citySelect && codes.length) { + Array.from(citySelect.options).forEach(opt => { + opt.selected = codes.includes(opt.value); + }); + this.updateCitySummary(); + } + } catch (_) {} + + // 回填行业多选(隐藏select)并同步dropdown显示 + try { + let industryStr = ''; + if (Array.isArray(this.config.industry)) industryStr = this.config.industry.join(','); + else industryStr = this.config.industry || ''; + const codes = industryStr.split(',').map(s => s.trim()).filter(Boolean); + const industrySelect = document.getElementById('liepinIndustryField'); + if (industrySelect && codes.length) { + Array.from(industrySelect.options).forEach(opt => { opt.selected = codes.includes(opt.value); }); + const btn = document.getElementById('liepinIndustryDropdownBtn'); + const summary = document.getElementById('liepinIndustrySelectionSummary'); + const set = new Set(codes); + const childListContainer = document.getElementById('liepinIndustryChildList'); + if (childListContainer) childListContainer.querySelectorAll('input[type="checkbox"]').forEach(chk => chk.checked = set.has(chk.value)); + if (btn && summary) { + const values = Array.from(industrySelect.selectedOptions).map(o => o.textContent || '').filter(Boolean); + if (values.length === 0) { btn.textContent = '选择行业'; summary.textContent = '未选择'; } + else if (values.length <= 2) { const text = values.join('、'); btn.textContent = text; summary.textContent = `已选 ${values.length} 项:${text}`; } + else { btn.textContent = `已选 ${values.length} 项`; summary.textContent = `已选 ${values.length} 项`; } + } + } + } catch (_) {} + } + + getFieldId(key) { + const fieldMap = { + keywords: 'liepinKeywordsField', + industry: 'liepinIndustryField', + cityCode: 'liepinCityCodeField', + experience: 'liepinExperienceComboBox', + jobType: 'liepinJobTypeComboBox', + salary: 'liepinSalaryComboBox', + degree: 'liepinDegreeComboBox', + scale: 'liepinScaleComboBox', + companyNature: 'liepinCompanyNatureComboBox', + recruiterActivity: 'liepinRecruiterActivityComboBox', + blacklistFilter: 'liepinBlacklistFilterCheckBox', + blacklistKeywords: 'liepinBlacklistKeywordsTextArea', + enableAIJobMatch: 'liepinEnableAIJobMatchCheckBox' + }; + return fieldMap[key] || `liepin${key.charAt(0).toUpperCase() + key.slice(1)}Field`; + } + + async loadDataSequentially() { + try { + await this.loadLiepinDicts(); + await this.waitForDOMReady(); + await this.loadSavedConfig(); + } catch (error) { + console.error('Liepin data loading failed:', error); + } + } + + async waitForDOMReady() { + return new Promise(resolve => setTimeout(resolve, 100)); + } + + async loadLiepinDicts() { + try { + const res = await fetch('https://github.com/dicts/LIEPIN'); + if (!res.ok) throw new Error('HTTP ' + res.status); + const data = await res.json(); + if (!data || !Array.isArray(data.groups)) { + console.warn('Liepin dict data structure incorrect:', data); + return; + } + + const groupMap = new Map(); + data.groups.forEach(g => groupMap.set(g.key, g.items || [])); + + this.renderCitySelection(groupMap.get('cityList') || []); + this.renderIndustrySelection(groupMap.get('industryList') || []); + this.fillSelect('liepinExperienceComboBox', groupMap.get('experienceList')); + this.fillSelect('liepinJobTypeComboBox', groupMap.get('jobTypeList')); + this.fillSelect('liepinSalaryComboBox', groupMap.get('salaryList')); + this.fillSelect('liepinDegreeComboBox', groupMap.get('degreeList')); + this.fillSelect('liepinScaleComboBox', groupMap.get('scaleList')); + this.fillSelect('liepinCompanyNatureComboBox', groupMap.get('companyNatureList')); + this.fillSelect('liepinRecruiterActivityComboBox', groupMap.get('pubTimes')); + + } catch (e) { + console.warn('Loading Liepin dicts failed:', e?.message || e); + throw e; + } + } + + renderCitySelection(cityItems) { + const citySelect = document.getElementById('liepinCityCodeField'); + const citySearch = document.getElementById('liepinCitySearchField'); + const cityListContainer = document.getElementById('liepinCityDropdownList'); + + const renderCityOptions = (list) => { + if (!citySelect) return; + const selected = new Set(Array.from(citySelect.selectedOptions).map(o => o.value)); + citySelect.innerHTML = ''; + list.forEach(it => { + const value = it.code ?? ''; + const opt = document.createElement('option'); + opt.value = value; + opt.textContent = `${it.name ?? ''}${it.code ? ' (' + it.code + ')' : ''}`; + if (selected.has(value)) opt.selected = true; + citySelect.appendChild(opt); + }); + + if (cityListContainer) { + cityListContainer.innerHTML = ''; + list.forEach(it => { + const value = it.code ?? ''; + const label = `${it.name ?? ''}${it.code ? ' (' + it.code + ')' : ''}`; + const item = document.createElement('div'); + item.className = 'form-check mb-1'; + const id = `liepin_city_chk_${value}`.replace(/[^a-zA-Z0-9_\-]/g, '_'); + item.innerHTML = ``; + item.querySelector('input[type="checkbox"]').addEventListener('change', (e) => { + const option = Array.from(citySelect.options).find(o => o.value === e.target.value); + if (option) option.selected = e.target.checked; + this.updateCitySummary(); + }); + cityListContainer.appendChild(item); + }); + } + this.updateCitySummary(); + }; + + renderCityOptions(cityItems); + citySearch?.addEventListener('input', () => { + const kw = citySearch.value.trim().toLowerCase(); + const filtered = kw ? cityItems.filter(it => String(it.name || '').toLowerCase().includes(kw) || String(it.code || '').toLowerCase().includes(kw)) : cityItems; + renderCityOptions(filtered); + }); + } + + renderIndustrySelection(industryItems) { + console.log('猎聘: 渲染行业选择器(级联选择),行业数量:', industryItems.length); + + const industrySelect = document.getElementById('liepinIndustryField'); + const industrySearch = document.getElementById('liepinIndustrySearchField'); + const parentListContainer = document.getElementById('liepinIndustryParentList'); + const childListContainer = document.getElementById('liepinIndustryChildList'); + const industryDropdownBtn = document.getElementById('liepinIndustryDropdownBtn'); + const industrySummary = document.getElementById('liepinIndustrySelectionSummary'); + + if (!industrySelect || !parentListContainer || !childListContainer) { + console.error('猎聘: 行业选择器DOM元素未找到'); + return; + } + + // 保存原始数据供搜索使用 + this.allIndustryItems = industryItems; + + // 区分父级和子级 + const parents = industryItems.filter(it => !it.parentCode); + const children = industryItems.filter(it => it.parentCode); + + // 构建映射关系 + const childrenMap = new Map(); + parents.forEach(parent => { + childrenMap.set(parent.code, children.filter(child => child.parentCode === parent.code)); + }); + + console.log('猎聘: 一级行业数:', parents.length, '二级行业数:', children.length); + + // 当前选中的父级行业 + let currentParentCode = null; + + const updateIndustrySummary = () => { + if (!industrySelect || !industryDropdownBtn || !industrySummary) return; + const values = Array.from(industrySelect.selectedOptions).map(o => o.textContent); + if (values.length === 0) { + industryDropdownBtn.textContent = '选择行业'; + industrySummary.textContent = '未选择'; + } else if (values.length <= 2) { + const text = values.join('、'); + industryDropdownBtn.textContent = text; + industrySummary.textContent = `已选 ${values.length} 项:${text}`; + } else { + industryDropdownBtn.textContent = `已选 ${values.length} 项`; + industrySummary.textContent = `已选 ${values.length} 项`; + } + }; + + // 将updateIndustrySummary方法绑定到实例,供其他方法调用 + this.updateIndustrySummary = updateIndustrySummary; + + // 初始化:将所有子项添加到隐藏的select中,以便后续回填 + console.log('猎聘: 初始化行业select,添加所有子项:', children.length); + industrySelect.innerHTML = ''; + children.forEach(child => { + const value = child.code ?? child.name ?? ''; + const label = child.name ?? String(child.code ?? ''); + const opt = document.createElement('option'); + opt.value = value; + opt.textContent = label; + industrySelect.appendChild(opt); + }); + + // 渲染左侧一级行业列表 + const renderParentList = (parentList) => { + parentListContainer.innerHTML = ''; + const selected = new Set(Array.from(industrySelect.selectedOptions).map(o => o.value)); + + parentList.forEach(parent => { + const parentCode = parent.code ?? ''; + const parentName = parent.name ?? String(parent.code ?? ''); + const childCount = (childrenMap.get(parentCode) || []).length; + + // 计算该父级下有多少子项被选中 + const childItems = childrenMap.get(parentCode) || []; + const selectedCount = childItems.filter(child => selected.has(child.code ?? child.name ?? '')).length; + + const itemDiv = document.createElement('div'); + itemDiv.className = 'px-2 py-1 mb-1 rounded'; + itemDiv.style.cursor = 'pointer'; + itemDiv.style.transition = 'background-color 0.15s'; + + if (currentParentCode === parentCode) { + itemDiv.classList.add('bg-primary', 'text-white'); + } + + itemDiv.innerHTML = ` +
    + ${parentName} + ${selectedCount}/${childCount} +
    + `; + + // 鼠标悬停效果 + itemDiv.addEventListener('mouseenter', () => { + if (currentParentCode !== parentCode) { + itemDiv.style.backgroundColor = '#f8f9fa'; + } + }); + itemDiv.addEventListener('mouseleave', () => { + if (currentParentCode !== parentCode) { + itemDiv.style.backgroundColor = ''; + } + }); + + // 点击选择父级行业 + itemDiv.addEventListener('click', (e) => { + e.stopPropagation(); // 阻止事件冒泡,防止dropdown关闭 + currentParentCode = parentCode; + renderParentList(parentList); + renderChildList(childrenMap.get(parentCode) || []); + }); + + parentListContainer.appendChild(itemDiv); + }); + }; + + // 渲染右侧二级行业列表 + const renderChildList = (childList) => { + childListContainer.innerHTML = ''; + + if (childList.length === 0) { + childListContainer.innerHTML = '
    该分类下暂无子项
    '; + return; + } + + const selected = new Set(Array.from(industrySelect.selectedOptions).map(o => o.value)); + + // 渲染复选框列表(不再重建select,因为所有选项已在初始化时添加) + childList.forEach(child => { + const value = child.code ?? child.name ?? ''; + const label = child.name ?? String(child.code ?? ''); + const checkDiv = document.createElement('div'); + checkDiv.className = 'form-check mb-1'; + const checkId = `liepin_industry_chk_${value}`.replace(/[^a-zA-Z0-9_\-]/g, '_'); + checkDiv.innerHTML = ` + + + `; + + const checkbox = checkDiv.querySelector('input[type="checkbox"]'); + checkbox.addEventListener('change', (e) => { + e.stopPropagation(); // 阻止事件冒泡,防止dropdown关闭 + + // 获取当前所有已选项 + const currentSelected = Array.from(industrySelect.options).filter(o => o.selected); + const currentSelectedValues = currentSelected.map(o => o.value); + + if (checkbox.checked) { + // 逻辑1:限制最多勾选5个行业 + if (currentSelectedValues.length >= 5) { + checkbox.checked = false; + this.showToast('最多只能选择5个行业', 'warning'); + return; + } + + // 逻辑2:选择了"不限"后,将已勾选的行业撤销勾选 + const isUnlimited = label === '不限' || value === '0' || value === '' || label.includes('不限'); + if (isUnlimited) { + // 取消所有已选项 + Array.from(industrySelect.options).forEach(opt => opt.selected = false); + currentSelectedValues.forEach(val => { + const chk = childListContainer.querySelector(`input[value="${val}"]`); + if (chk && chk !== checkbox) chk.checked = false; + }); + // 只保留"不限"选项 + const option = Array.from(industrySelect.options).find(o => o.value === value); + if (option) option.selected = true; + updateIndustrySummary(); + renderParentList(parents); + return; + } + + // 逻辑3:只能选择同一大类的子行业,勾选了其他大类的子行业,原大类的行业勾选撤销 + if (currentSelectedValues.length > 0) { + // 找到当前选中项的父级 + const currentChildItem = children.find(c => (c.code ?? c.name ?? '') === value); + const newParentCode = currentChildItem?.parentCode; + + // 检查已选项的父级 + const existingParentCodes = new Set(); + currentSelectedValues.forEach(val => { + const childItem = children.find(c => (c.code ?? c.name ?? '') === val); + if (childItem?.parentCode) { + existingParentCodes.add(childItem.parentCode); + } + }); + + // 如果新选择的父级与已有父级不同,取消其他父级的所有选项 + if (newParentCode && existingParentCodes.size > 0 && !existingParentCodes.has(newParentCode)) { + // 取消其他父级下的所有选项 + Array.from(industrySelect.options).forEach(opt => { + const childItem = children.find(c => (c.code ?? c.name ?? '') === opt.value); + if (childItem?.parentCode !== newParentCode) { + opt.selected = false; + const chk = childListContainer.querySelector(`input[value="${opt.value}"]`); + if (chk) chk.checked = false; + } + }); + this.showToast('只能选择同一大类的行业,已自动取消其他大类的选择', 'info'); + } + } + + // 如果当前有"不限"被选中,取消"不限" + const unlimitedOptions = Array.from(industrySelect.options).filter(opt => { + const optLabel = opt.textContent || ''; + return opt.selected && (optLabel === '不限' || optLabel.includes('不限') || opt.value === '0' || opt.value === ''); + }); + unlimitedOptions.forEach(opt => { + opt.selected = false; + const chk = childListContainer.querySelector(`input[value="${opt.value}"]`); + if (chk) chk.checked = false; + }); + } + + const option = Array.from(industrySelect.options).find(o => o.value === value); + if (option) option.selected = checkbox.checked; + updateIndustrySummary(); + // 更新父级列表中的计数 + renderParentList(parents); + }); + + childListContainer.appendChild(checkDiv); + }); + }; + + // 初始渲染 + renderParentList(parents); + + // 如果有已选项,自动展开对应的父级 + const selectedValues = Array.from(industrySelect.selectedOptions).map(o => o.value); + if (selectedValues.length > 0) { + // 找到第一个已选项的父级 + const firstSelected = children.find(c => selectedValues.includes(c.code ?? c.name ?? '')); + if (firstSelected && firstSelected.parentCode) { + currentParentCode = firstSelected.parentCode; + renderParentList(parents); + renderChildList(childrenMap.get(currentParentCode) || []); + } + } + + // 搜索功能 + if (industrySearch) { + industrySearch.addEventListener('input', () => { + const kw = (industrySearch.value || '').trim().toLowerCase(); + if (!kw) { + // 恢复原始显示 + renderParentList(parents); + if (currentParentCode) { + renderChildList(childrenMap.get(currentParentCode) || []); + } else { + childListContainer.innerHTML = '
    请先选择左侧分类
    '; + } + return; + } + + // 搜索匹配的子行业 + const matchedChildren = children.filter(child => { + const childName = String(child.name || '').toLowerCase(); + const childCode = String(child.code || '').toLowerCase(); + return childName.includes(kw) || childCode.includes(kw); + }); + + // 找到这些子行业对应的父级 + const matchedParentCodes = new Set(matchedChildren.map(c => c.parentCode).filter(Boolean)); + const matchedParents = parents.filter(p => matchedParentCodes.has(p.code)); + + if (matchedParents.length === 0) { + parentListContainer.innerHTML = '
    无匹配结果
    '; + childListContainer.innerHTML = '
    无匹配结果
    '; + return; + } + + // 渲染搜索结果 + renderParentList(matchedParents); + + // 自动展开第一个匹配的父级 + if (matchedParents.length > 0) { + currentParentCode = matchedParents[0].code; + renderParentList(matchedParents); + const filteredChildren = matchedChildren.filter(c => c.parentCode === currentParentCode); + renderChildList(filteredChildren); + } + }); + } + } + + fillSelect(selectId, items) { + if (!Array.isArray(items) || items.length === 0) return; + const sel = document.getElementById(selectId); + if (!sel) return; + const first = sel.querySelector('option'); + sel.innerHTML = ''; + if (first && first.value === '') sel.appendChild(first); + items.forEach(it => { + const opt = document.createElement('option'); + opt.value = it.code ?? it.name ?? ''; + opt.textContent = it.name ?? String(it.code ?? ''); + sel.appendChild(opt); + }); + } + + updateCitySummary() { + const citySelect = document.getElementById('liepinCityCodeField'); + const cityDropdownBtn = document.getElementById('liepinCityDropdownBtn'); + const citySummary = document.getElementById('liepinCitySelectionSummary'); + if (!citySelect || !cityDropdownBtn || !citySummary) return; + + const values = Array.from(citySelect.selectedOptions).map(o => o.textContent); + if (values.length === 0) { + cityDropdownBtn.textContent = '选择城市'; + citySummary.textContent = '未选择'; + } else if (values.length <= 2) { + const text = values.join('、'); + cityDropdownBtn.textContent = text; + citySummary.textContent = `已选 ${values.length} 项:${text}`; + } else { + cityDropdownBtn.textContent = `已选 ${values.length} 项`; + citySummary.textContent = `已选 ${values.length} 项`; + } + } + + handleSaveConfig() { + this.saveConfig(); + this.showToast('猎聘配置已保存'); + } + + handleBackupData() { + const backupBtn = document.getElementById('liepinBackupDataBtn'); + if (backupBtn) { + backupBtn.disabled = true; + backupBtn.innerHTML = '备份中...'; + } + fetch('https://github.com/api/backup/export', { method: 'POST' }) + .then(res => res.json()) + .then(data => { + this.showToast(data.success ? '数据库备份成功' : '数据库备份失败: ' + data.message, data.success ? 'success' : 'danger'); + }) + .catch(error => this.showToast('数据库备份失败: ' + error.message, 'danger')) + .finally(() => { + if (backupBtn) { + backupBtn.disabled = false; + backupBtn.innerHTML = '数据库备份'; + } + }); + } + + async handleLogin() { + try { + const response = await fetch('https://github.com/api/liepin/task/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(this.getCurrentConfig()) + }); + const result = await response.json(); + if (result.success) { + this.taskStates.loginTaskId = result.taskId; + this.showToast('猎聘登录任务已提交'); + } else { + this.showToast(result.message || '登录失败', 'danger'); + } + } catch (error) { + this.showToast('登录接口调用失败: ' + error.message, 'danger'); + } + } + + // 手动确认登录 - 由app.js统一处理UI状态 + handleManualLogin() { + // 标记任务ID,供其他逻辑使用 + this.taskStates.loginTaskId = 'manual_login_' + Date.now(); + // UI状态更新由app.js的TaskStatusUpdater统一处理 + this.showToast('猎聘已标记为登录状态'); + } + + async handleCollect() { + try { + const response = await fetch('https://github.com/api/liepin/task/collect', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(this.getCurrentConfig()) + }); + const result = await response.json(); + if (result.success) { + this.taskStates.collectTaskId = result.taskId; + this.showToast('猎聘采集任务已提交'); + } else { + this.showToast(result.message || '采集失败', 'danger'); + } + } catch (error) { + this.showToast('采集接口调用失败: ' + error.message, 'danger'); + } + } + + async handleFilter() { + try { + const request = { collectTaskId: this.taskStates.collectTaskId, config: this.getCurrentConfig() }; + const response = await fetch('https://github.com/api/liepin/task/filter', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request) + }); + const result = await response.json(); + if (result.success) { + this.taskStates.filterTaskId = result.taskId; + this.showToast('猎聘过滤任务已提交'); + } else { + this.showToast(result.message || '过滤失败', 'danger'); + } + } catch (error) { + this.showToast('过滤接口调用失败: ' + error.message, 'danger'); + } + } + + handleApply() { + this.showConfirmModal('投递确认', '是否执行实际投递?', () => this.executeApply(true), () => this.executeApply(false)); + } + + async executeApply(enableActualDelivery) { + try { + const request = { filterTaskId: this.taskStates.filterTaskId, config: this.getCurrentConfig(), enableActualDelivery }; + const response = await fetch('https://github.com/api/liepin/task/deliver', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request) + }); + const result = await response.json(); + if (result.success) { + this.taskStates.applyTaskId = result.taskId; + const deliveryType = enableActualDelivery ? '实际投递' : '模拟投递'; + this.showToast(`猎聘${deliveryType}任务已提交`); + } else { + this.showToast(result.message || '投递失败', 'danger'); + } + } catch (error) { + this.showToast('投递接口调用失败: ' + error.message, 'danger'); + } + } + + getCurrentConfig() { + const getMultiSelectValues = (selectId) => Array.from(document.getElementById(selectId)?.selectedOptions || []).map(o => o.value).filter(Boolean).join(','); + return { + keywords: document.getElementById('liepinKeywordsField')?.value || '', + cityCode: getMultiSelectValues('liepinCityCodeField'), + experience: document.getElementById('liepinExperienceComboBox')?.value || '', + jobType: document.getElementById('liepinJobTypeComboBox')?.value || '', + salary: document.getElementById('liepinSalaryComboBox')?.value || '', + degree: document.getElementById('liepinDegreeComboBox')?.value || '', + scale: document.getElementById('liepinScaleComboBox')?.value || '', + companyNature: document.getElementById('liepinCompanyNatureComboBox')?.value || '', + recruiterActivity: document.getElementById('liepinRecruiterActivityComboBox')?.value || '', + blacklistFilter: document.getElementById('liepinBlacklistFilterCheckBox')?.checked || false, + blacklistKeywords: document.getElementById('liepinBlacklistKeywordsTextArea')?.value || '', + enableAIJobMatch: document.getElementById('liepinEnableAIJobMatchCheckBox')?.checked || false + }; + } + + resetTaskFlow() { + this.showConfirmModal('重置确认', '确定要重置任务流程吗?', () => { + // 只重置任务状态数据,UI状态由app.js的TaskStatusUpdater处理 + this.taskStates = { loginTaskId: null, collectTaskId: null, filterTaskId: null, applyTaskId: null }; + this.showToast('任务流程已重置', 'info'); + }); + } + + showToast(message, variant = 'success') { + const toastEl = document.getElementById('globalToast'); + const bodyEl = document.getElementById('globalToastBody'); + if (!toastEl || !bodyEl) return; + bodyEl.textContent = message; + toastEl.className = `toast align-items-center text-bg-${variant} border-0`; + bootstrap.Toast.getOrCreateInstance(toastEl).show(); + } + + showConfirmModal(title, message, onConfirm, onCancel) { + const modalEl = document.getElementById('confirmModal'); + const modal = bootstrap.Modal.getOrCreateInstance(modalEl); + document.getElementById('confirmModalLabel').textContent = title; + document.getElementById('confirmModalBody').textContent = message; + const okBtn = document.getElementById('confirmModalOk'); + const newOkBtn = okBtn.cloneNode(true); + okBtn.parentNode.replaceChild(newOkBtn, okBtn); + newOkBtn.addEventListener('click', () => { + modal.hide(); + onConfirm?.(); + }, { once: true }); + modalEl.addEventListener('hidden.bs.modal', () => onCancel?.(), { once: true }); + modal.show(); + } + + showAlertModal(title, message) { + const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('alertModal')); + document.getElementById('alertModalLabel').textContent = title; + document.getElementById('alertModalBody').textContent = message; + modal.show(); + } +} + +window.LiepinConfigForm = LiepinConfigForm; diff --git a/src/main/resources/static/js/views/liepin-records-vue.js b/src/main/resources/static/js/views/liepin-records-vue.js new file mode 100644 index 00000000..5c93ea09 --- /dev/null +++ b/src/main/resources/static/js/views/liepin-records-vue.js @@ -0,0 +1,336 @@ +// 猎聘岗位明细Vue应用 +(function () { + if (!window.Views) window.Views = {}; + + class LiepinRecordsVue { + constructor() { + this.app = null; + this.init(); + } + + init() { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => this.createVueApp()); + } else { + this.createVueApp(); + } + } + + createVueApp() { + const { createApp } = Vue; + + this.app = createApp({ + data() { + return { + loading: false, + error: null, + searchKeyword: '', + records: [], + currentPage: 0, + pageSize: 10, + totalElements: 0, + totalPages: 0, + numberOfElements: 0, + first: true, + last: false, + empty: true + } + }, + computed: { + visiblePages() { + const pages = []; + const start = Math.max(0, this.currentPage - 2); + const end = Math.min(this.totalPages - 1, this.currentPage + 2); + + for (let i = start; i <= end; i++) { + pages.push(i); + } + return pages; + } + }, + methods: { + async loadJobs(page = 0) { + this.loading = true; + this.error = null; + + try { + const params = new URLSearchParams(); + params.set('platform', 'LIEPIN'); // 使用与后端保存一致的平台名称 + if (this.searchKeyword.trim()) { + params.set('keyword', this.searchKeyword.trim()); + } + params.set('page', page.toString()); + params.set('size', this.pageSize.toString()); + + const response = await fetch(`/api/jobs?${params.toString()}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + this.records = data.content || []; + this.currentPage = data.number || 0; + this.totalElements = data.totalElements || 0; + this.totalPages = data.totalPages || 0; + this.numberOfElements = data.numberOfElements || 0; + this.first = data.first || false; + this.last = data.last || false; + this.empty = data.empty || true; + + console.log('猎聘岗位数据加载完成:', { + total: this.totalElements, + currentPage: this.currentPage, + records: this.records.length + }); + + } catch (error) { + console.error('加载岗位数据失败:', error); + this.error = '加载数据失败,请稍后重试'; + this.records = []; + } finally { + this.loading = false; + } + }, + + searchJobs() { + this.loadJobs(0); + }, + + refreshData() { + this.loadJobs(this.currentPage); + }, + + goToPage(page) { + if (page >= 0 && page < this.totalPages) { + this.loadJobs(page); + } + }, + + parseArray(str) { + if (!str) return []; + try { + if (typeof str === 'string') { + return JSON.parse(str); + } + return Array.isArray(str) ? str : []; + } catch (e) { + return []; + } + }, + + truncateText(text, maxLength = 50) { + if (!text) return ''; + if (text.length <= maxLength) return text; + return text.substring(0, maxLength) + '...'; + }, + + formatDateTime(dateStr) { + if (!dateStr) return '-'; + try { + const date = new Date(dateStr); + if (isNaN(date.getTime())) return '-'; + + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + + return `${year}-${month}-${day} ${hours}:${minutes}`; + } catch (e) { + return '-'; + } + }, + + getStatusInfo(status) { + const statusMap = { + 0: { text: '待处理', class: 'badge bg-secondary' }, + 1: { text: '待处理', class: 'badge bg-secondary' }, + 2: { text: '已过滤', class: 'badge bg-warning' }, + 3: { text: '投递成功', class: 'badge bg-success' }, + 4: { text: '投递失败', class: 'badge bg-danger' } + }; + return statusMap[status] || { text: '未知', class: 'badge bg-dark' }; + }, + + getJobTypeText(jobType) { + const typeMap = { + 0: '全职', + 1: '兼职', + 2: '实习', + 3: '合同工', + 4: '外包' + }; + return typeMap[jobType] || '未知'; + }, + + viewJobDetail(job) { + if (job.jobUrl) { + window.open(job.jobUrl, '_blank'); + } else { + this.showAlert('岗位链接不可用'); + } + }, + + async copyJobUrl(job) { + if (job.jobUrl) { + try { + await navigator.clipboard.writeText(job.jobUrl); + this.showAlert('链接已复制到剪贴板'); + } catch (e) { + const textArea = document.createElement('textarea'); + textArea.value = job.jobUrl; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand('copy'); + document.body.removeChild(textArea); + this.showAlert('链接已复制到剪贴板'); + } + } else { + this.showAlert('岗位链接不可用'); + } + }, + + async toggleFavorite(job) { + try { + const response = await fetch(`/api/jobs/${job.id}/favorite`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + isFavorite: !job.isFavorite + }) + }); + + if (response.ok) { + job.isFavorite = !job.isFavorite; + this.showAlert(job.isFavorite ? '已添加到收藏' : '已取消收藏'); + } else { + this.showAlert('操作失败,请稍后重试'); + } + } catch (error) { + this.showAlert('操作失败,请稍后重试'); + } + }, + + viewCompanyInfo(job) { + if (job.companyName) { + this.showCompanyModal(job); + } else { + this.showAlert('暂无公司详细信息'); + } + }, + + showCompanyModal(job) { + const modalHtml = ` + + `; + + const existingModal = document.getElementById('companyModalLiepin'); + if (existingModal) { + existingModal.remove(); + } + + document.body.insertAdjacentHTML('beforeend', modalHtml); + + const modal = new bootstrap.Modal(document.getElementById('companyModalLiepin')); + modal.show(); + + document.getElementById('companyModalLiepin').addEventListener('hidden.bs.modal', function() { + this.remove(); + }); + }, + + showAlert(message) { + const toastElement = document.getElementById('globalToast'); + const toastBody = document.getElementById('globalToastBody'); + if (toastElement && toastBody) { + toastBody.textContent = message; + const toast = new bootstrap.Toast(toastElement); + toast.show(); + } else { + alert(message); + } + } + }, + mounted() { + const liepinRecordsTab = document.getElementById('liepin-records-tab'); + if (liepinRecordsTab) { + liepinRecordsTab.addEventListener('shown.bs.tab', () => { + this.loadJobs(0); + }); + } + + const searchBtn = document.getElementById('liepinRecordSearchBtn'); + if (searchBtn) { + searchBtn.addEventListener('click', () => { + const keywordInput = document.getElementById('liepinRecordKeyword'); + if (keywordInput) { + this.searchKeyword = keywordInput.value; + this.searchJobs(); + } + }); + } + + const refreshBtn = document.getElementById('liepinRecordRefreshBtn'); + if (refreshBtn) { + refreshBtn.addEventListener('click', () => { + this.refreshData(); + }); + } + + const keywordInput = document.getElementById('liepinRecordKeyword'); + if (keywordInput) { + keywordInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.searchKeyword = keywordInput.value; + this.searchJobs(); + } + }); + } + + // 初始加载 + const pane = document.getElementById('liepin-records-pane'); + if(pane && pane.classList.contains('active')){ + this.loadJobs(0); + } + } + }); + + const mountElement = document.getElementById('liepinRecordsVueApp'); + if (mountElement) { + const rootInstance = this.app.mount(mountElement); + // 暴露根组件实例,便于外部按钮调用其方法 + window.liepinRecordsRoot = rootInstance; + } + } + + destroy() { + if (this.app) { + this.app.unmount(); + this.app = null; + } + } + } + + window.Views.LiepinRecordsVue = LiepinRecordsVue; +})(); diff --git a/src/main/resources/static/js/views/zhilian-config-form.js b/src/main/resources/static/js/views/zhilian-config-form.js new file mode 100644 index 00000000..e3cb0975 --- /dev/null +++ b/src/main/resources/static/js/views/zhilian-config-form.js @@ -0,0 +1,1060 @@ +// 智联招聘配置表单管理类 +class ZhilianConfigForm { + constructor() { + this.config = {}; + this.taskStates = { + loginTaskId: null, + collectTaskId: null, + filterTaskId: null, + applyTaskId: null + }; + this.statusPollingInterval = null; // 状态轮询定时器 + this.latestTaskStatus = null; // 缓存最新的任务状态查询结果 + this.init(); + } + + init() { + this.initializeTooltips(); + this.bindEvents(); + this.loadDataSequentially(); + // 初始化时启动状态轮询,确保能及时获取到登录状态 + this.startStatusPolling(); + } + + initializeTooltips() { + const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); + tooltipTriggerList.map(function (tooltipTriggerEl) { + return new bootstrap.Tooltip(tooltipTriggerEl); + }); + } + + bindEvents() { + document.getElementById('zhilianSaveConfigBtn')?.addEventListener('click', () => this.handleSaveConfig()); + document.getElementById('zhilianBackupDataBtn')?.addEventListener('click', () => this.handleBackupData()); + + document.getElementById('zhilianLoginBtn')?.addEventListener('click', () => this.handleLogin()); + document.getElementById('zhilianLoginManualBtn')?.addEventListener('click', () => this.handleManualLogin()); + document.getElementById('zhilianCollectBtn')?.addEventListener('click', () => this.handleCollect()); + document.getElementById('zhilianFilterBtn')?.addEventListener('click', () => this.handleFilter()); + document.getElementById('zhilianApplyBtn')?.addEventListener('click', () => this.handleApply()); + document.getElementById('zhilianResetTasksBtn')?.addEventListener('click', () => this.resetTaskFlow()); + + this.bindFormValidation(); + this.bindAutoSave(); + } + + bindFormValidation() { + const requiredFields = [ + 'zhilianKeywordsField', + 'zhilianCityCodeField', + 'zhilianExperienceComboBox', + 'zhilianJobTypeComboBox', + 'zhilianSalaryComboBox', + 'zhilianDegreeComboBox' + ]; + requiredFields.forEach(id => { + const field = document.getElementById(id); + if (!field) return; + field.addEventListener('blur', () => { + if (field.tagName === 'SELECT') this.validateSelectField(field); + else this.validateField(field); + }); + }); + } + + validateField(field) { + const value = (field.value || '').trim(); + const ok = value.length > 0; + this.updateFieldValidation(field, ok); + return ok; + } + + validateSelectField(field) { + const ok = !!field.value; + this.updateFieldValidation(field, ok); + return ok; + } + + updateFieldValidation(field, isValid) { + if (isValid) { + field.classList.remove('is-invalid'); + field.classList.add('is-valid'); + } else { + field.classList.remove('is-valid'); + field.classList.add('is-invalid'); + } + } + + bindAutoSave() { + const formElements = document.querySelectorAll('#zhilian-config-pane input, #zhilian-config-pane select, #zhilian-config-pane textarea'); + formElements.forEach(el => el.addEventListener('change', () => this.saveConfig())); + } + + getMultiValues(selectId) { + const el = document.getElementById(selectId); + if (!el) return ''; + return Array.from(el.selectedOptions).map(o => o.value).filter(Boolean).join(','); + } + + getCurrentConfig() { + return { + // 搜索条件 + keywords: document.getElementById('zhilianKeywordsField')?.value || '', + industry: this.getMultiValues('zhilianIndustryField'), + cityCode: this.getMultiValues('zhilianCityCodeField'), + + // 职位要求 + experience: document.getElementById('zhilianExperienceComboBox')?.value || '', + jobType: document.getElementById('zhilianJobTypeComboBox')?.value || '', + salary: document.getElementById('zhilianSalaryComboBox')?.value || '', + degree: document.getElementById('zhilianDegreeComboBox')?.value || '', + scale: document.getElementById('zhilianScaleComboBox')?.value || '', + companyNature: document.getElementById('zhilianCompanyNatureComboBox')?.value || '', + + // 功能开关 + blacklistFilter: document.getElementById('zhilianBlacklistFilterCheckBox')?.checked || false, + + // AI配置 + enableAIJobMatch: document.getElementById('zhilianEnableAIJobMatchCheckBox')?.checked || false + }; + } + + saveConfig() { + this.config = this.getCurrentConfig(); + localStorage.setItem('zhilianConfig', JSON.stringify(this.config)); + try { + fetch('https://github.com/api/config/zhilian', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(this.config) + }).then(() => {}).catch(() => {}); + } catch (_) {} + } + + async loadSavedConfig() { + try { + const res = await fetch('https://github.com/api/config/zhilian'); + if (!res.ok) throw new Error('HTTP ' + res.status); + const ct = res.headers.get('content-type') || ''; + let data; + if (ct.includes('application/json')) data = await res.json(); + else { + const text = await res.text(); + const snippet = (text || '').slice(0, 80); + throw new Error('返回非JSON:' + snippet); + } + if (data && typeof data === 'object' && Object.keys(data).length) { + this.config = data; + this.populateForm(); + localStorage.setItem('zhilianConfig', JSON.stringify(this.config)); + return; + } + const saved = localStorage.getItem('zhilianConfig'); + if (saved) { + try { + this.config = JSON.parse(saved); + this.populateForm(); + } catch (e) { + localStorage.removeItem('zhilianConfig'); + } + } + } catch (_) { + const saved = localStorage.getItem('zhilianConfig'); + if (saved) { + try { + this.config = JSON.parse(saved); + this.populateForm(); + } catch (e) { + localStorage.removeItem('zhilianConfig'); + } + } + } + } + + getFieldId(key) { + const map = { + keywords: 'zhilianKeywordsField', + industry: 'zhilianIndustryField', + cityCode: 'zhilianCityCodeField', + experience: 'zhilianExperienceComboBox', + jobType: 'zhilianJobTypeComboBox', + salary: 'zhilianSalaryComboBox', + degree: 'zhilianDegreeComboBox', + scale: 'zhilianScaleComboBox', + companyNature: 'zhilianCompanyNatureComboBox', + blacklistFilter: 'zhilianBlacklistFilterCheckBox', + blacklistKeywords: 'zhilianBlacklistKeywordsTextArea', + enableAIJobMatch: 'zhilianEnableAIJobMatchCheckBox' + }; + return map[key] || key; + } + + populateForm() { + Object.keys(this.config).forEach(key => { + const el = document.getElementById(this.getFieldId(key)); + if (!el) return; + if (el.type === 'checkbox') el.checked = !!this.config[key]; + else { + let value = this.config[key]; + if (Array.isArray(value)) value = value.join(','); + el.value = value || ''; + } + }); + + // 回填城市多选(隐藏select)并同步dropdown显示 + try { + let cityStr = ''; + if (Array.isArray(this.config.cityCode)) cityStr = this.config.cityCode.join(','); + else cityStr = this.config.cityCode || ''; + const codes = cityStr.split(',').map(s => s.trim()).filter(Boolean); + const citySelect = document.getElementById('zhilianCityCodeField'); + if (citySelect && codes.length) { + Array.from(citySelect.options).forEach(opt => { opt.selected = codes.includes(opt.value); }); + const list = document.getElementById('zhilianCityDropdownList'); + const btn = document.getElementById('zhilianCityDropdownBtn'); + const summary = document.getElementById('zhilianCitySelectionSummary'); + const set = new Set(codes); + if (list) list.querySelectorAll('input[type="checkbox"]').forEach(chk => chk.checked = set.has(chk.value)); + if (btn && summary) { + const values = Array.from(citySelect.selectedOptions).map(o => o.textContent || '').filter(Boolean); + if (values.length === 0) { btn.textContent = '选择城市'; summary.textContent = '未选择'; } + else if (values.length <= 2) { const text = values.join('、'); btn.textContent = text; summary.textContent = `已选 ${values.length} 项:${text}`; } + else { btn.textContent = `已选 ${values.length} 项`; summary.textContent = `已选 ${values.length} 项`; } + } + } + } catch (_) {} + + // 回填行业多选(隐藏select)并同步dropdown显示 + try { + let industryStr = ''; + if (Array.isArray(this.config.industry)) industryStr = this.config.industry.join(','); + else industryStr = this.config.industry || ''; + const codes = industryStr.split(',').map(s => s.trim()).filter(Boolean); + const industrySelect = document.getElementById('zhilianIndustryField'); + if (industrySelect && codes.length) { + Array.from(industrySelect.options).forEach(opt => { opt.selected = codes.includes(opt.value); }); + const childListContainer = document.getElementById('zhilianIndustryChildList'); + const btn = document.getElementById('zhilianIndustryDropdownBtn'); + const summary = document.getElementById('zhilianIndustrySelectionSummary'); + const set = new Set(codes); + if (childListContainer) childListContainer.querySelectorAll('input[type="checkbox"]').forEach(chk => chk.checked = set.has(chk.value)); + if (btn && summary) { + const values = Array.from(industrySelect.selectedOptions).map(o => o.textContent || '').filter(Boolean); + if (values.length === 0) { btn.textContent = '选择行业'; summary.textContent = '未选择'; } + else if (values.length <= 2) { const text = values.join('、'); btn.textContent = text; summary.textContent = `已选 ${values.length} 项:${text}`; } + else { btn.textContent = `已选 ${values.length} 项`; summary.textContent = `已选 ${values.length} 项`; } + } + } + } catch (_) {} + } + + async loadDataSequentially() { + try { + console.log('智联招聘: 开始按顺序加载数据:字典 -> 配置'); + + // 先加载字典数据 + await this.loadZhilianDicts(); + console.log('智联招聘: 字典数据加载完成,开始加载配置数据'); + + // 等待DOM元素完全渲染 + await this.waitForDOMReady(); + + // 再加载配置数据 + await this.loadSavedConfig(); + console.log('智联招聘: 配置数据加载完成'); + } catch (e) { + console.warn('智联数据加载失败:', e?.message || e); + } + } + + // 等待DOM元素完全准备就绪 + async waitForDOMReady() { + return new Promise((resolve) => { + // 等待一个事件循环,确保所有DOM操作完成 + setTimeout(() => { + console.log('智联招聘: DOM元素准备就绪'); + resolve(); + }, 100); + }); + } + + async loadZhilianDicts() { + const res = await fetch('https://github.com/dicts/ZHILIAN_ZHAOPIN'); + if (!res.ok) throw new Error('HTTP ' + res.status); + const data = await res.json(); + if (!data || !Array.isArray(data.groups)) return; + const groupMap = new Map(); + data.groups.forEach(g => groupMap.set(g.key, Array.isArray(g.items) ? g.items : [])); + + // 渲染城市 + this.renderCitySelection(groupMap.get('cityList') || []); + + // 渲染行业 + this.renderIndustrySelection(groupMap.get('industryList') || []); + + // 渲染其他下拉 + this.fillSelect('zhilianExperienceComboBox', groupMap.get('experienceList')); + this.fillSelect('zhilianJobTypeComboBox', groupMap.get('jobTypeList')); + this.fillSelect('zhilianSalaryComboBox', groupMap.get('salaryList')); + this.fillSelect('zhilianDegreeComboBox', groupMap.get('degreeList')); + this.fillSelect('zhilianScaleComboBox', groupMap.get('scaleList')); + this.fillSelect('zhilianCompanyNatureComboBox', groupMap.get('companyNatureList')); + } + + renderCitySelection(cityItems) { + const citySelect = document.getElementById('zhilianCityCodeField'); + const citySearch = document.getElementById('zhilianCitySearchField'); + const cityListContainer = document.getElementById('zhilianCityDropdownList'); + const cityDropdownBtn = document.getElementById('zhilianCityDropdownBtn'); + const citySummary = document.getElementById('zhilianCitySelectionSummary'); + + const updateCitySummary = () => { + if (!citySelect || !cityDropdownBtn || !citySummary) return; + const values = Array.from(citySelect.selectedOptions).map(o => o.textContent); + if (values.length === 0) { cityDropdownBtn.textContent = '选择城市'; citySummary.textContent = '未选择'; } + else if (values.length <= 2) { const text = values.join('、'); cityDropdownBtn.textContent = text; citySummary.textContent = `已选 ${values.length} 项:${text}`; } + else { cityDropdownBtn.textContent = `已选 ${values.length} 项`; citySummary.textContent = `已选 ${values.length} 项`; } + }; + + const renderCityOptions = (list) => { + if (!citySelect) return; + const selected = new Set(Array.from(citySelect.selectedOptions).map(o => o.value)); + citySelect.innerHTML = ''; + list.forEach(it => { + const value = it.code ?? ''; + const label = `${it.name ?? ''}${it.code ? ' (' + it.code + ')' : ''}`; + const opt = document.createElement('option'); + opt.value = value; + opt.textContent = label; + if (selected.has(value)) opt.selected = true; + citySelect.appendChild(opt); + }); + if (cityListContainer) { + cityListContainer.innerHTML = ''; + list.forEach(it => { + const value = it.code ?? ''; + const label = `${it.name ?? ''}${it.code ? ' (' + it.code + ')' : ''}`; + const item = document.createElement('div'); + item.className = 'form-check mb-1'; + const id = `zhilian_city_chk_${value}`.replace(/[^a-zA-Z0-9_\-]/g, '_'); + item.innerHTML = ` + + + `; + const checkbox = item.querySelector('input[type="checkbox"]'); + checkbox.addEventListener('change', () => { + const option = Array.from(citySelect.options).find(o => o.value === value); + if (option) option.selected = checkbox.checked; + updateCitySummary(); + }); + cityListContainer.appendChild(item); + }); + } + updateCitySummary(); + }; + + renderCityOptions(cityItems); + if (citySearch) { + citySearch.addEventListener('input', () => { + const kw = (citySearch.value || '').trim().toLowerCase(); + if (!kw) { renderCityOptions(cityItems); return; } + const filtered = cityItems.filter(it => String(it.name || '').toLowerCase().includes(kw) || String(it.code || '').toLowerCase().includes(kw)); + renderCityOptions(filtered); + }); + } + } + + renderIndustrySelection(industryItems) { + console.log('智联招聘: 渲染行业选择器(级联选择),行业数量:', industryItems.length); + + const industrySelect = document.getElementById('zhilianIndustryField'); + const industrySearch = document.getElementById('zhilianIndustrySearchField'); + const parentListContainer = document.getElementById('zhilianIndustryParentList'); + const childListContainer = document.getElementById('zhilianIndustryChildList'); + const industryDropdownBtn = document.getElementById('zhilianIndustryDropdownBtn'); + const industrySummary = document.getElementById('zhilianIndustrySelectionSummary'); + + if (!industrySelect || !parentListContainer || !childListContainer) { + console.error('智联招聘: 行业选择器DOM元素未找到'); + return; + } + + // 保存原始数据供搜索使用 + this.allIndustryItems = industryItems; + + // 区分父级和子级 + const parents = industryItems.filter(it => !it.parentCode); + const children = industryItems.filter(it => it.parentCode); + + // 构建映射关系 + const childrenMap = new Map(); + parents.forEach(parent => { + childrenMap.set(parent.code, children.filter(child => child.parentCode === parent.code)); + }); + + console.log('智联招聘: 一级行业数:', parents.length, '二级行业数:', children.length); + + // 当前选中的父级行业 + let currentParentCode = null; + + const updateIndustrySummary = () => { + if (!industrySelect || !industryDropdownBtn || !industrySummary) return; + const values = Array.from(industrySelect.selectedOptions).map(o => o.textContent); + if (values.length === 0) { + industryDropdownBtn.textContent = '选择行业'; + industrySummary.textContent = '未选择'; + } else if (values.length <= 2) { + const text = values.join('、'); + industryDropdownBtn.textContent = text; + industrySummary.textContent = `已选 ${values.length} 项:${text}`; + } else { + industryDropdownBtn.textContent = `已选 ${values.length} 项`; + industrySummary.textContent = `已选 ${values.length} 项`; + } + }; + + // 将updateIndustrySummary方法绑定到实例,供其他方法调用 + this.updateIndustrySummary = updateIndustrySummary; + + // 初始化:将所有子项添加到隐藏的select中,以便后续回填 + console.log('智联招聘: 初始化行业select,添加所有子项:', children.length); + industrySelect.innerHTML = ''; + children.forEach(child => { + const value = child.code ?? child.name ?? ''; + const label = child.name ?? String(child.code ?? ''); + const opt = document.createElement('option'); + opt.value = value; + opt.textContent = label; + industrySelect.appendChild(opt); + }); + + // 渲染左侧一级行业列表 + const renderParentList = (parentList) => { + parentListContainer.innerHTML = ''; + const selected = new Set(Array.from(industrySelect.selectedOptions).map(o => o.value)); + + parentList.forEach(parent => { + const parentCode = parent.code ?? ''; + const parentName = parent.name ?? String(parent.code ?? ''); + const childCount = (childrenMap.get(parentCode) || []).length; + + // 计算该父级下有多少子项被选中 + const childItems = childrenMap.get(parentCode) || []; + const selectedCount = childItems.filter(child => selected.has(child.code ?? child.name ?? '')).length; + + const itemDiv = document.createElement('div'); + itemDiv.className = 'px-2 py-1 mb-1 rounded'; + itemDiv.style.cursor = 'pointer'; + itemDiv.style.transition = 'background-color 0.15s'; + + if (currentParentCode === parentCode) { + itemDiv.classList.add('bg-primary', 'text-white'); + } + + itemDiv.innerHTML = ` +
    + ${parentName} + ${selectedCount}/${childCount} +
    + `; + + // 鼠标悬停效果 + itemDiv.addEventListener('mouseenter', () => { + if (currentParentCode !== parentCode) { + itemDiv.style.backgroundColor = '#f8f9fa'; + } + }); + itemDiv.addEventListener('mouseleave', () => { + if (currentParentCode !== parentCode) { + itemDiv.style.backgroundColor = ''; + } + }); + + // 点击选择父级行业 + itemDiv.addEventListener('click', (e) => { + e.stopPropagation(); // 阻止事件冒泡,防止dropdown关闭 + currentParentCode = parentCode; + renderParentList(parentList); + renderChildList(childrenMap.get(parentCode) || []); + }); + + parentListContainer.appendChild(itemDiv); + }); + }; + + // 渲染右侧二级行业列表 + const renderChildList = (childList) => { + childListContainer.innerHTML = ''; + + if (childList.length === 0) { + childListContainer.innerHTML = '
    该分类下暂无子项
    '; + return; + } + + const selected = new Set(Array.from(industrySelect.selectedOptions).map(o => o.value)); + + // 渲染复选框列表(不再重建select,因为所有选项已在初始化时添加) + childList.forEach(child => { + const value = child.code ?? child.name ?? ''; + const label = child.name ?? String(child.code ?? ''); + const checkDiv = document.createElement('div'); + checkDiv.className = 'form-check mb-1'; + const checkId = `zhilian_industry_chk_${value}`.replace(/[^a-zA-Z0-9_\-]/g, '_'); + checkDiv.innerHTML = ` + + + `; + + const checkbox = checkDiv.querySelector('input[type="checkbox"]'); + checkbox.addEventListener('change', (e) => { + e.stopPropagation(); // 阻止事件冒泡,防止dropdown关闭 + + // 获取当前所有已选项 + const currentSelected = Array.from(industrySelect.options).filter(o => o.selected); + const currentSelectedValues = currentSelected.map(o => o.value); + + if (checkbox.checked) { + // 逻辑1:限制最多勾选5个行业 + if (currentSelectedValues.length >= 5) { + checkbox.checked = false; + this.showToast('最多只能选择5个行业', 'warning'); + return; + } + + // 逻辑2:选择了"不限"后,将已勾选的行业撤销勾选 + const isUnlimited = label === '不限' || value === '0' || value === '' || label.includes('不限'); + if (isUnlimited) { + // 取消所有已选项 + Array.from(industrySelect.options).forEach(opt => opt.selected = false); + currentSelectedValues.forEach(val => { + const chk = childListContainer.querySelector(`input[value="${val}"]`); + if (chk && chk !== checkbox) chk.checked = false; + }); + // 只保留"不限"选项 + const option = Array.from(industrySelect.options).find(o => o.value === value); + if (option) option.selected = true; + updateIndustrySummary(); + renderParentList(parents); + return; + } + + // 逻辑3:只能选择同一大类的子行业,勾选了其他大类的子行业,原大类的行业勾选撤销 + if (currentSelectedValues.length > 0) { + // 找到当前选中项的父级 + const currentChildItem = children.find(c => (c.code ?? c.name ?? '') === value); + const newParentCode = currentChildItem?.parentCode; + + // 检查已选项的父级 + const existingParentCodes = new Set(); + currentSelectedValues.forEach(val => { + const childItem = children.find(c => (c.code ?? c.name ?? '') === val); + if (childItem?.parentCode) { + existingParentCodes.add(childItem.parentCode); + } + }); + + // 如果新选择的父级与已有父级不同,取消其他父级的所有选项 + if (newParentCode && existingParentCodes.size > 0 && !existingParentCodes.has(newParentCode)) { + // 取消其他父级下的所有选项 + Array.from(industrySelect.options).forEach(opt => { + const childItem = children.find(c => (c.code ?? c.name ?? '') === opt.value); + if (childItem?.parentCode !== newParentCode) { + opt.selected = false; + const chk = childListContainer.querySelector(`input[value="${opt.value}"]`); + if (chk) chk.checked = false; + } + }); + this.showToast('只能选择同一大类的行业,已自动取消其他大类的选择', 'info'); + } + } + + // 如果当前有"不限"被选中,取消"不限" + const unlimitedOptions = Array.from(industrySelect.options).filter(opt => { + const optLabel = opt.textContent || ''; + return opt.selected && (optLabel === '不限' || optLabel.includes('不限') || opt.value === '0' || opt.value === ''); + }); + unlimitedOptions.forEach(opt => { + opt.selected = false; + const chk = childListContainer.querySelector(`input[value="${opt.value}"]`); + if (chk) chk.checked = false; + }); + } + + const option = Array.from(industrySelect.options).find(o => o.value === value); + if (option) option.selected = checkbox.checked; + updateIndustrySummary(); + // 更新父级列表中的计数 + renderParentList(parents); + }); + + childListContainer.appendChild(checkDiv); + }); + }; + + // 初始渲染 + renderParentList(parents); + + // 如果有已选项,自动展开对应的父级 + const selectedValues = Array.from(industrySelect.selectedOptions).map(o => o.value); + if (selectedValues.length > 0) { + // 找到第一个已选项的父级 + const firstSelected = children.find(c => selectedValues.includes(c.code ?? c.name ?? '')); + if (firstSelected && firstSelected.parentCode) { + currentParentCode = firstSelected.parentCode; + renderParentList(parents); + renderChildList(childrenMap.get(currentParentCode) || []); + } + } + + updateIndustrySummary(); + + // 搜索功能 + if (industrySearch) { + industrySearch.addEventListener('input', () => { + const kw = (industrySearch.value || '').trim().toLowerCase(); + if (!kw) { + // 无搜索词,显示所有父级 + renderParentList(parents); + if (currentParentCode) { + renderChildList(childrenMap.get(currentParentCode) || []); + } + return; + } + + // 筛选匹配的父级(按名称或编码) + const matchedParents = parents.filter(p => + String(p.name || '').toLowerCase().includes(kw) || + String(p.code || '').toLowerCase().includes(kw) + ); + + // 筛选匹配的子级 + const matchedChildren = children.filter(c => + String(c.name || '').toLowerCase().includes(kw) || + String(c.code || '').toLowerCase().includes(kw) + ); + + // 合并:包含匹配子级的父级也要显示 + const parentCodesWithMatchedChildren = new Set(matchedChildren.map(c => c.parentCode)); + const allMatchedParents = [...new Set([ + ...matchedParents, + ...parents.filter(p => parentCodesWithMatchedChildren.has(p.code)) + ])]; + + renderParentList(allMatchedParents); + + // 如果当前选中的父级不在匹配列表中,清空右侧 + if (currentParentCode && !allMatchedParents.find(p => p.code === currentParentCode)) { + currentParentCode = null; + childListContainer.innerHTML = '
    未找到匹配的行业
    '; + } else if (currentParentCode) { + // 显示当前父级下匹配的子级 + const currentChildren = childrenMap.get(currentParentCode) || []; + const filteredChildren = currentChildren.filter(c => + String(c.name || '').toLowerCase().includes(kw) || + String(c.code || '').toLowerCase().includes(kw) + ); + renderChildList(filteredChildren.length > 0 ? filteredChildren : currentChildren); + } + }); + } + } + + fillSelect(selectId, items) { + console.log(`智联招聘: 填充下拉框 ${selectId},数据项数量:`, Array.isArray(items) ? items.length : 0); + + if (!Array.isArray(items) || items.length === 0) { + console.warn(`智联招聘: 下拉框 ${selectId} 的数据无效:`, items); + return; + } + + const sel = document.getElementById(selectId); + if (!sel) { + console.warn(`智联招聘: 未找到下拉框元素: ${selectId}`); + // 延迟重试 + setTimeout(() => { + const retrySel = document.getElementById(selectId); + if (retrySel) { + console.log(`智联招聘: 重试填充下拉框 ${selectId}`); + this.fillSelect(selectId, items); + } + }, 500); + return; + } + + try { + // 保留第一项"请选择",其余重建 + const first = sel.querySelector('option'); + sel.innerHTML = ''; + if (first && first.value === '') sel.appendChild(first); + + items.forEach(it => { + const opt = document.createElement('option'); + opt.value = it.code ?? it.name ?? ''; + opt.textContent = it.name ?? String(it.code ?? ''); + sel.appendChild(opt); + }); + + console.log(`智联招聘: 下拉框 ${selectId} 填充完成,共 ${items.length} 项`); + + } catch (error) { + console.error(`智联招聘: 填充下拉框 ${selectId} 时出错:`, error); + } + } + + handleSaveConfig() { + this.saveConfig(); + this.showToast('智联配置已保存'); + } + + handleBackupData() { + const btn = document.getElementById('zhilianBackupDataBtn'); + if (btn) { btn.disabled = true; btn.innerHTML = '备份中...'; } + fetch('https://github.com/api/backup/export', { method: 'POST', headers: { 'Content-Type': 'application/json' } }) + .then(r => r.json()) + .then(d => { if (d.success) this.showToast('数据库备份成功', 'success'); else this.showToast('数据库备份失败: ' + d.message, 'danger'); }) + .catch(e => { this.showToast('数据库备份失败: ' + e.message, 'danger'); }) + .finally(() => { if (btn) { btn.disabled = false; btn.innerHTML = '数据库备份'; } }); + } + + async handleLogin() { + if (!this.validateRequiredFields()) { this.showAlertModal('验证失败', '请先完善必填项'); return; } + this.updateButtonState('zhilianLoginBtn', 'zhilianLoginStatus', '执行中...', true); + try { + const response = await fetch('https://github.com/api/zhilian/task/login', { + method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(this.getCurrentConfig()) + }); + const result = await response.json(); + if (result.success) { + this.taskStates.loginTaskId = result.taskId; + this.showToast('智联登录任务已提交'); + // 启动状态轮询 + this.startStatusPolling(); + } else { + this.updateButtonState('zhilianLoginBtn', 'zhilianLoginStatus', '登录失败', false); + this.showToast(result.message || '登录失败', 'danger'); + } + } catch (e) { + this.updateButtonState('zhilianLoginBtn', 'zhilianLoginStatus', '登录失败', false); + this.showToast('登录接口调用失败: ' + e.message, 'danger'); + } + } + + handleManualLogin() { + this.taskStates.loginTaskId = 'manual_login_' + Date.now(); + this.updateButtonState('zhilianLoginBtn', 'zhilianLoginStatus', '登录成功', false); + this.enableNextStep('zhilianCollectBtn', 'zhilianCollectStatus', '可开始采集'); + this.enableNextStep('zhilianFilterBtn', 'zhilianFilterStatus', '可开始过滤'); + this.enableNextStep('zhilianApplyBtn', 'zhilianApplyStatus', '可开始投递'); + this.showToast('已手动标记为登录状态', 'success'); + } + + async handleCollect() { + if (!this.isLoggedIn()) { this.showAlertModal('操作提示', '请先完成登录步骤'); return; } + this.updateButtonState('zhilianCollectBtn', 'zhilianCollectStatus', '采集中...', true); + try { + const response = await fetch('https://github.com/api/zhilian/task/collect', { + method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(this.getCurrentConfig()) + }); + const result = await response.json(); + if (result.success) { + this.taskStates.collectTaskId = result.taskId; + this.showToast('智联采集任务已提交'); + // 启动状态轮询(如果未启动) + this.startStatusPolling(); + } else { + this.updateButtonState('zhilianCollectBtn', 'zhilianCollectStatus', '采集失败', false); + this.showToast(result.message || '采集失败', 'danger'); + } + } catch (e) { + this.updateButtonState('zhilianCollectBtn', 'zhilianCollectStatus', '采集失败', false); + this.showToast('采集接口调用失败: ' + e.message, 'danger'); + } + } + + async handleFilter() { + if (!this.isLoggedIn()) { this.showAlertModal('操作提示', '请先完成登录步骤'); return; } + this.updateButtonState('zhilianFilterBtn', 'zhilianFilterStatus', '过滤中...', true); + try { + const request = { collectTaskId: this.taskStates.collectTaskId, config: this.getCurrentConfig() }; + const response = await fetch('https://github.com/api/zhilian/task/filter', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request) }); + const result = await response.json(); + if (result.success) { + this.taskStates.filterTaskId = result.taskId; + this.showToast('智联过滤任务已提交'); + // 启动状态轮询(如果未启动) + this.startStatusPolling(); + } else { + this.updateButtonState('zhilianFilterBtn', 'zhilianFilterStatus', '过滤失败', false); + this.showToast(result.message || '过滤失败', 'danger'); + } + } catch (e) { + this.updateButtonState('zhilianFilterBtn', 'zhilianFilterStatus', '过滤失败', false); + this.showToast('过滤接口调用失败: ' + e.message, 'danger'); + } + } + + async handleApply() { + if (!this.isLoggedIn()) { this.showAlertModal('操作提示', '请先完成登录步骤'); return; } + this.showConfirmModal('投递确认', '是否执行实际投递?\n点击"确定"将真实投递简历\n点击"取消"将仅模拟投递', () => this.executeApply(true), () => this.executeApply(false)); + } + + async executeApply(enableActualDelivery) { + this.updateButtonState('zhilianApplyBtn', 'zhilianApplyStatus', '投递中...', true); + try { + const request = { filterTaskId: this.taskStates.filterTaskId, config: this.getCurrentConfig(), enableActualDelivery }; + const response = await fetch('https://github.com/api/zhilian/task/deliver', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request) }); + const result = await response.json(); + if (result.success) { + this.taskStates.applyTaskId = result.taskId; + const deliveryType = enableActualDelivery ? '实际投递' : '模拟投递'; + this.showToast(`智联${deliveryType}任务已提交`); + // 启动状态轮询(如果未启动) + this.startStatusPolling(); + } else { + this.updateButtonState('zhilianApplyBtn', 'zhilianApplyStatus', '投递失败', false); + this.showToast(result.message || '投递失败', 'danger'); + } + } catch (e) { + this.updateButtonState('zhilianApplyBtn', 'zhilianApplyStatus', '投递失败', false); + this.showToast('投递接口调用失败: ' + e.message, 'danger'); + } + } + + validateRequiredFields() { + const required = [ + 'zhilianKeywordsField', + 'zhilianCityCodeField', + 'zhilianExperienceComboBox', + 'zhilianJobTypeComboBox', + 'zhilianSalaryComboBox', + 'zhilianDegreeComboBox' + ]; + let ok = true; + required.forEach(id => { + const field = document.getElementById(id); + if (!field) return; + if (field.tagName === 'SELECT') { if (!this.validateSelectField(field)) ok = false; } + else { if (!this.validateField(field)) ok = false; } + }); + return ok; + } + + updateButtonState(buttonId, statusId, statusText, isLoading) { + const button = document.getElementById(buttonId); + const status = document.getElementById(statusId); + if (button) button.disabled = isLoading; + if (status) { + status.textContent = statusText; + status.className = isLoading ? 'badge bg-warning text-dark ms-2' : 'badge bg-success text-white ms-2'; + } + } + + enableNextStep(buttonId, statusId, statusText) { + const button = document.getElementById(buttonId); + const status = document.getElementById(statusId); + if (button) button.disabled = false; + if (status) { status.textContent = statusText; status.className = 'badge bg-info text-white ms-2'; } + } + + resetTaskFlow() { + this.showConfirmModal('重置确认', '确定要重置任务流程吗?这将清除所有任务状态。', () => { + this.taskStates = { loginTaskId: null, collectTaskId: null, filterTaskId: null, applyTaskId: null }; + this.stopStatusPolling(); + this.updateButtonState('zhilianLoginBtn', 'zhilianLoginStatus', '待执行', false); + this.updateButtonState('zhilianCollectBtn', 'zhilianCollectStatus', '等待登录', true); + this.updateButtonState('zhilianFilterBtn', 'zhilianFilterStatus', '等待登录', true); + this.updateButtonState('zhilianApplyBtn', 'zhilianApplyStatus', '等待登录', true); + const cb = id => { const el = document.getElementById(id); if (el) el.disabled = true; }; + cb('zhilianCollectBtn'); cb('zhilianFilterBtn'); cb('zhilianApplyBtn'); + this.showToast('任务流程已重置', 'info'); + }); + } + + // 启动状态轮询 + startStatusPolling() { + if (this.statusPollingInterval) { + return; // 已经在轮询中 + } + + console.log('智联招聘: 启动任务状态轮询'); + this.statusPollingInterval = setInterval(() => { + this.fetchAllTaskStatus(); + }, 2000); // 每2秒轮询一次 + + // 立即执行一次 + this.fetchAllTaskStatus(); + } + + // 停止状态轮询 + stopStatusPolling() { + if (this.statusPollingInterval) { + console.log('智联招聘: 停止任务状态轮询'); + clearInterval(this.statusPollingInterval); + this.statusPollingInterval = null; + } + } + + // 检查是否已登录(基于最新的任务状态缓存) + isLoggedIn() { + // 优先检查缓存的任务状态 + if (this.latestTaskStatus) { + const loginStatus = this.latestTaskStatus.login; + // 后端返回的字段是 status,不是 state + const state = loginStatus?.status || loginStatus?.state; + if (loginStatus && state === 'SUCCESS') { + return true; + } + } + + // 兼容:检查UI状态(处理app.js已更新UI但本地状态未同步的情况) + const loginStatusEl = document.getElementById('zhilianLoginStatus'); + if (loginStatusEl) { + const statusText = loginStatusEl.textContent.trim(); + // 如果状态文本包含"成功"或"完成",也认为已登录 + if (statusText.includes('成功') || statusText.includes('完成') || statusText.includes('登录状态正常')) { + return true; + } + } + + return false; + } + + // 查询所有任务状态 + async fetchAllTaskStatus() { + try { + const response = await fetch('https://github.com/api/tasks/status'); + if (!response.ok) return; + + const result = await response.json(); + if (!result) return; + + // 后端返回的是扁平结构:{ "ZHILIAN_ZHAOPIN_LOGIN": {...}, "ZHILIAN_ZHAOPIN_COLLECT": {...}, ... } + // 需要转换为前端期望的嵌套结构 + const zhilianStatus = { + login: result['ZHILIAN_ZHAOPIN_LOGIN'], + collect: result['ZHILIAN_ZHAOPIN_COLLECT'], + filter: result['ZHILIAN_ZHAOPIN_FILTER'], + deliver: result['ZHILIAN_ZHAOPIN_DELIVER'] + }; + + console.log('智联招聘: 任务状态数据(转换后):', zhilianStatus); + + // 缓存最新的任务状态 + this.latestTaskStatus = zhilianStatus; + + this.updateTaskStatusUI(zhilianStatus); + + } catch (error) { + console.warn('智联招聘: 查询任务状态失败:', error); + } + } + + // 更新任务状态UI + updateTaskStatusUI(statusData) { + // 更新登录任务状态 + if (statusData.login) { + this.updateTaskUI('login', statusData.login); + } + + // 更新采集任务状态 + if (statusData.collect) { + this.updateTaskUI('collect', statusData.collect); + } + + // 更新过滤任务状态 + if (statusData.filter) { + this.updateTaskUI('filter', statusData.filter); + } + + // 更新投递任务状态 + if (statusData.deliver) { + this.updateTaskUI('deliver', statusData.deliver); + } + } + + // 更新单个任务的UI + updateTaskUI(taskType, taskStatus) { + const buttonMap = { + 'login': { btn: 'zhilianLoginBtn', status: 'zhilianLoginStatus' }, + 'collect': { btn: 'zhilianCollectBtn', status: 'zhilianCollectStatus' }, + 'filter': { btn: 'zhilianFilterBtn', status: 'zhilianFilterStatus' }, + 'deliver': { btn: 'zhilianApplyBtn', status: 'zhilianApplyStatus' } + }; + + const uiElements = buttonMap[taskType]; + if (!uiElements) return; + + // 后端返回的字段是 status,不是 state + // 状态值:STARTED, SUCCESS, FAILURE + const state = taskStatus.status || taskStatus.state; + const message = taskStatus.message || ''; + + console.log(`智联招聘: 更新${taskType}任务UI,状态=${state},消息=${message}`); + + switch (state) { + case 'STARTED': + case 'RUNNING': + this.updateButtonState(uiElements.btn, uiElements.status, message || '执行中...', true); + break; + case 'SUCCESS': + this.updateButtonState(uiElements.btn, uiElements.status, message || '完成', false); + // 启用下一步 + if (taskType === 'login') { + this.enableNextStep('zhilianCollectBtn', 'zhilianCollectStatus', '可开始采集'); + this.enableNextStep('zhilianFilterBtn', 'zhilianFilterStatus', '可开始过滤'); + this.enableNextStep('zhilianApplyBtn', 'zhilianApplyStatus', '可开始投递'); + } + // 如果所有任务都完成,停止轮询 + if (taskType === 'deliver') { + this.stopStatusPolling(); + } + break; + case 'FAILED': + case 'FAILURE': + this.updateButtonState(uiElements.btn, uiElements.status, message || '失败', false); + this.stopStatusPolling(); + break; + case 'PENDING': + // 待执行状态,保持默认 + break; + } + } + + showToast(message, variant = 'success') { + try { + const toastEl = document.getElementById('globalToast'); + const bodyEl = document.getElementById('globalToastBody'); + if (!toastEl || !bodyEl) return; + bodyEl.textContent = message || '操作成功'; + const variants = ['success', 'danger', 'warning', 'info', 'primary', 'secondary', 'dark']; + variants.forEach(v => toastEl.classList.remove(`text-bg-${v}`)); + toastEl.classList.add(`text-bg-${variant}`); + const toast = bootstrap.Toast.getOrCreateInstance(toastEl, { delay: 2000 }); + toast.show(); + } catch (_) {} + } + + showConfirmModal(title, message, onConfirm, onCancel) { + const modal = new bootstrap.Modal(document.getElementById('confirmModal')); + document.getElementById('confirmModalLabel').textContent = title; + document.getElementById('confirmModalBody').textContent = message; + const okBtn = document.getElementById('confirmModalOk'); + const newOkBtn = okBtn.cloneNode(true); + okBtn.parentNode.replaceChild(newOkBtn, okBtn); + newOkBtn.addEventListener('click', () => { modal.hide(); if (onConfirm) onConfirm(); }); + modal._element.addEventListener('hidden.bs.modal', () => { if (onCancel) onCancel(); }, { once: true }); + modal.show(); + } + + showAlertModal(title, message) { + const modal = new bootstrap.Modal(document.getElementById('alertModal')); + document.getElementById('alertModalLabel').textContent = title; + document.getElementById('alertModalBody').textContent = message; + modal.show(); + } +} + +// 导出到全局 +window.ZhilianConfigForm = ZhilianConfigForm; + + diff --git a/src/main/resources/static/js/views/zhilian-records-vue.js b/src/main/resources/static/js/views/zhilian-records-vue.js new file mode 100644 index 00000000..656f3f49 --- /dev/null +++ b/src/main/resources/static/js/views/zhilian-records-vue.js @@ -0,0 +1,394 @@ +// 智联招聘岗位明细Vue应用 +(function () { + if (!window.Views) window.Views = {}; + + class ZhilianRecordsVue { + constructor() { + this.app = null; + this.init(); + } + + init() { + // 等待DOM加载完成 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => this.createVueApp()); + } else { + this.createVueApp(); + } + } + + createVueApp() { + const { createApp } = Vue; + + this.app = createApp({ + data() { + return { + loading: false, + error: null, + searchKeyword: '', + records: [], + currentPage: 0, + pageSize: 10, + totalElements: 0, + totalPages: 0, + numberOfElements: 0, + first: true, + last: false, + empty: true + } + }, + computed: { + visiblePages() { + const pages = []; + const start = Math.max(0, this.currentPage - 2); + const end = Math.min(this.totalPages - 1, this.currentPage + 2); + + for (let i = start; i <= end; i++) { + pages.push(i); + } + return pages; + } + }, + methods: { + // 加载岗位数据 + async loadJobs(page = 0) { + this.loading = true; + this.error = null; + + try { + const params = new URLSearchParams(); + params.set('platform', 'zhilian'); + if (this.searchKeyword.trim()) { + params.set('keyword', this.searchKeyword.trim()); + } + params.set('page', page.toString()); + params.set('size', this.pageSize.toString()); + + const response = await fetch(`/api/jobs?${params.toString()}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + this.records = data.content || []; + this.currentPage = data.number || 0; + this.totalElements = data.totalElements || 0; + this.totalPages = data.totalPages || 0; + this.numberOfElements = data.numberOfElements || 0; + this.first = data.first || false; + this.last = data.last || false; + this.empty = data.empty || true; + + } catch (error) { + console.error('加载岗位数据失败:', error); + this.error = '加载数据失败,请稍后重试'; + this.records = []; + } finally { + this.loading = false; + } + }, + + // 搜索岗位 + searchJobs() { + this.loadJobs(0); + }, + + // 刷新数据 + refreshData() { + this.loadJobs(this.currentPage); + }, + + // 分页跳转 + goToPage(page) { + if (page >= 0 && page < this.totalPages) { + this.loadJobs(page); + } + }, + + // 解析数组字符串 + parseArray(str) { + if (!str) return []; + try { + if (typeof str === 'string') { + return JSON.parse(str); + } + return Array.isArray(str) ? str : []; + } catch (e) { + console.warn('解析数组失败:', str, e); + return []; + } + }, + + // 截断文本 + truncateText(text, maxLength = 50) { + if (!text) return ''; + if (text.length <= maxLength) return text; + return text.substring(0, maxLength) + '...'; + }, + + // 格式化日期时间 + formatDateTime(dateStr) { + if (!dateStr) return '-'; + try { + const date = new Date(dateStr); + if (isNaN(date.getTime())) return '-'; + + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + + return `${year}-${month}-${day} ${hours}:${minutes}`; + } catch (e) { + return '-'; + } + }, + + // 获取状态信息 + getStatusInfo(status) { + const statusMap = { + 0: { text: '待处理', class: 'badge bg-secondary' }, + 1: { text: '待处理', class: 'badge bg-secondary' }, + 2: { text: '已过滤', class: 'badge bg-warning' }, + 3: { text: '投递成功', class: 'badge bg-success' }, + 4: { text: '投递失败', class: 'badge bg-danger' } + }; + return statusMap[status] || { text: '未知', class: 'badge bg-dark' }; + }, + + // 获取工作类型文本 + getJobTypeText(jobType) { + const typeMap = { + 0: '全职', + 1: '兼职', + 2: '实习', + 3: '合同工', + 4: '外包' + }; + return typeMap[jobType] || '未知'; + }, + + // 查看岗位详情 + viewJobDetail(job) { + if (job.jobUrl) { + window.open(job.jobUrl, '_blank'); + } else { + this.showAlert('岗位链接不可用'); + } + }, + + // 复制岗位链接 + async copyJobUrl(job) { + if (job.jobUrl) { + try { + await navigator.clipboard.writeText(job.jobUrl); + this.showAlert('链接已复制到剪贴板'); + } catch (e) { + // 降级方案 + const textArea = document.createElement('textarea'); + textArea.value = job.jobUrl; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand('copy'); + document.body.removeChild(textArea); + this.showAlert('链接已复制到剪贴板'); + } + } else { + this.showAlert('岗位链接不可用'); + } + }, + + // 切换收藏状态 + async toggleFavorite(job) { + try { + const response = await fetch(`/api/jobs/${job.id}/favorite`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + isFavorite: !job.isFavorite + }) + }); + + if (response.ok) { + job.isFavorite = !job.isFavorite; + this.showAlert(job.isFavorite ? '已添加到收藏' : '已取消收藏'); + } else { + this.showAlert('操作失败,请稍后重试'); + } + } catch (error) { + console.error('切换收藏状态失败:', error); + this.showAlert('操作失败,请稍后重试'); + } + }, + + // 查看公司信息 + viewCompanyInfo(job) { + if (job.companyName) { + this.showCompanyModal(job); + } else { + this.showAlert('暂无公司详细信息'); + } + }, + + // 显示公司信息模态框 + showCompanyModal(job) { + // 创建模态框HTML + const modalHtml = ` + + `; + + // 移除已存在的模态框 + const existingModal = document.getElementById('companyModal'); + if (existingModal) { + existingModal.remove(); + } + + // 添加新模态框到页面 + document.body.insertAdjacentHTML('beforeend', modalHtml); + + // 显示模态框 + const modal = new bootstrap.Modal(document.getElementById('companyModal')); + modal.show(); + + // 模态框关闭后移除 + document.getElementById('companyModal').addEventListener('hidden.bs.modal', function() { + this.remove(); + }); + }, + + // 显示提示信息 + showAlert(message) { + // 使用Bootstrap的toast组件 + const toastElement = document.getElementById('globalToast'); + const toastBody = document.getElementById('globalToastBody'); + if (toastElement && toastBody) { + toastBody.textContent = message; + const toast = new bootstrap.Toast(toastElement); + toast.show(); + } else { + alert(message); + } + } + }, + mounted() { + // 监听标签页切换事件 + const zhilianRecordsTab = document.getElementById('zhilian-records-tab'); + if (zhilianRecordsTab) { + zhilianRecordsTab.addEventListener('shown.bs.tab', () => { + this.loadJobs(0); + }); + } + + // 绑定搜索按钮事件 + const searchBtn = document.getElementById('zhilianRecordSearchBtn'); + if (searchBtn) { + searchBtn.addEventListener('click', () => { + const keywordInput = document.getElementById('zhilianRecordKeyword'); + if (keywordInput) { + this.searchKeyword = keywordInput.value; + this.searchJobs(); + } + }); + } + + // 绑定刷新按钮事件 + const refreshBtn = document.getElementById('zhilianRecordRefreshBtn'); + if (refreshBtn) { + refreshBtn.addEventListener('click', () => { + this.refreshData(); + }); + } + + // 绑定回车键搜索 + const keywordInput = document.getElementById('zhilianRecordKeyword'); + if (keywordInput) { + keywordInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.searchKeyword = keywordInput.value; + this.searchJobs(); + } + }); + } + + // 初始加载数据 + this.loadJobs(0); + } + }); + + // 挂载到指定元素 + const mountElement = document.getElementById('zhilianRecordsVueApp'); + if (mountElement) { + const rootInstance = this.app.mount(mountElement); + // 暴露根组件实例,便于外部按钮调用其方法 + window.zhilianRecordsRoot = rootInstance; + } + } + + // 销毁应用 + destroy() { + if (this.app) { + this.app.unmount(); + this.app = null; + } + } + } + + // 导出类 + window.Views.ZhilianRecordsVue = ZhilianRecordsVue; +})(); diff --git a/src/main/resources/xpathHelper.crx b/src/main/resources/xpathHelper.crx deleted file mode 100644 index d24c7a2c..00000000 Binary files a/src/main/resources/xpathHelper.crx and /dev/null differ