diff --git a/docs/app-job-suggest-api.md b/docs/app-job-suggest-api.md new file mode 100644 index 0000000..2e21001 --- /dev/null +++ b/docs/app-job-suggest-api.md @@ -0,0 +1,232 @@ +# 岗位名称联想接口文档 + +## 1. 接口说明 + +根据用户输入的岗位关键词,从有效岗位名称预先生成的 IK 分词缓存中查询联想结果,返回匹配的 IK 分词和包含该分词的完整岗位名称,供首页搜索框快速选择。 + +接口查询 Redis 缓存,不在用户输入时访问岗位数据库。 + +## 2. 接口地址 + +**后端接口:** `GET /app/job/suggest` + +**前端代理地址:** `GET /api/app/job/suggest` + +前端页面通过代理地址调用,代理转发到后端 `/app/job/suggest`。 + +## 3. 请求参数 + +请求方式:`GET` + +请求参数通过 Query String 传递。 + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| keyword | String | 否(建议传入) | - | 用户输入的岗位关键词或 IK 分词前缀。未传、为空或仅包含空格时返回空数组 | +| limit | Integer | 否 | 10 | 返回结果数量,服务端限制在 1~20 之间,超过 20 按 20 返回,小于 1 按 1 返回 | + +## 4. 请求示例 + +```http +GET /app/job/suggest?keyword=后端&limit=10 +``` + +前端代理调用: + +```http +GET /api/app/job/suggest?keyword=后端&limit=10 +``` + +使用 cURL 调用: + +```bash +curl "http://localhost:8080/app/job/suggest?keyword=%E5%90%8E%E7%AB%AF&limit=10" +``` + +如果通过前端代理访问: + +```bash +curl "http://47.111.103.66/shihezi/api/app/job/suggest?keyword=%E5%90%8E%E7%AB%AF&limit=10" +``` + +> 实际部署时请根据前端代理配置确认 `/shihezi` 前缀和后端服务地址。 + +## 5. 响应参数 + +| 参数名 | 类型 | 说明 | +|--------|------|------| +| code | Number | 状态码,200 表示成功 | +| msg | String | 提示信息 | +| data | Array | 联想结果列表,结果内容为 IK 分词或完整岗位名称 | + +## 6. 成功响应示例 + +请求: + +```http +GET /app/job/suggest?keyword=后端&limit=10 +``` + +响应: + +```json +{ + "code": 200, + "msg": "操作成功", + "data": [ + "后端", + "后端工程师", + "高级Java后端开发工程师" + ] +} +``` + +## 7. 空关键词响应 + +当 `keyword` 未传、为空或仅包含空格时,接口直接返回空数组。 + +请求: + +```http +GET /app/job/suggest?keyword= +``` + +响应: + +```json +{ + "code": 200, + "msg": "操作成功", + "data": [] +} +``` + +## 8. 无匹配结果响应 + +当输入关键词没有匹配的 IK 分词时,接口返回空数组。 + +请求: + +```http +GET /app/job/suggest?keyword=不存在的岗位&limit=10 +``` + +响应: + +```json +{ + "code": 200, + "msg": "操作成功", + "data": [] +} +``` + +## 9. 返回规则 + +### 9.1 IK 分词规则 + +定时任务从有效岗位名称中使用 IK `smart` 模式分词。例如: + +```text +岗位名称:高级Java后端开发工程师 +IK 分词:高级、java、后端、开发、工程师 +``` + +缓存中的词项只来自 IK 分词结果,不额外生成单字、字符片段或其他自定义前缀。 + +用户输入会进行统一标准化处理: + +- 全角字符转换为半角字符; +- 英文字母转换为小写; +- 去除空白字符。 + +标准化后,接口对缓存中的 IK 分词进行前缀匹配。例如输入 `工程` 可以匹配以 `工程` 开头的 IK 词项。 + +### 9.2 完整岗位名称规则 + +为了提升联想效果,接口不仅返回匹配到的 IK 分词,还会返回包含该分词的完整岗位名称。 + +例如 Redis 中存在以下映射: + +```text +后端 -> 高级Java后端开发工程师 +工程师 -> 高级Java后端开发工程师、后端工程师 +``` + +输入 `后端` 时,接口会将 `后端` 和相关完整岗位名称合并返回,并自动去重。 + +### 9.3 结果数量 + +- 默认最多返回 10 条; +- `limit` 最大为 20; +- 返回结果包含 IK 分词和完整岗位名称,二者合计不超过 `limit`; +- 结果自动去重; +- 没有结果时返回空数组,不返回 `null`。 + +## 10. 数据缓存和定时重建 + +Redis 使用 Hash 保存岗位名称联想数据: + +```text +Redis Key:app:job:title:suggest +``` + +数据结构: + +| Hash 内容 | 说明 | +|-----------|------| +| field | IK 分词,例如 `后端`、`工程师` | +| value | 包含该分词的完整岗位名称列表 | + +定时任务只处理有效岗位,岗位有效条件如下: + +```sql +del_flag = '0' +AND job_status = '0' +AND job_title IS NOT NULL +AND trim(job_title) <> '' +``` + +Quartz 定时任务调用目标: + +```text +jobCron.rebuildJobTitleSuggest() +``` + +应用启动时会初始化一次缓存,之后由 Quartz 定时任务按配置周期重建。重建时先写入临时缓存,完成后再替换正式缓存,避免接口读取到不完整数据。 + +## 11. 前端接入建议 + +首页搜索框可以按以下方式接入: + +1. 将普通输入框改为支持下拉选项的 `AutoComplete` 或自定义下拉组件。 +2. 用户输入停止约 250ms 后调用接口,避免每次键盘事件都请求后端。 +3. 输入为空时清空下拉选项,不调用接口。 +4. 调用 `/api/app/job/suggest`,将返回的 `data` 数组直接转换为下拉选项。 +5. 用户点击某个联想项时,将选中的完整文本回填到搜索框,并执行岗位搜索。 +6. 用户直接回车或点击搜索按钮时,仍使用搜索框当前文本执行搜索。 +7. 岗位列表页使用与原有搜索接口一致的岗位名称参数,例如 `name`,将完整岗位名称作为搜索条件传递。 +8. 建议使用请求序号、取消请求或其他方式处理异步请求顺序,避免旧请求结果覆盖用户最新输入的结果。 + +前端请求示例: + +```ts +const response = await get('/api/app/job/suggest', { + params: { + keyword: value, + limit: 10, + }, +}); + +const options = (response.data || []).map((item: string) => ({ + value: item, + label: item, +})); +``` + +选择联想结果后,建议以完整岗位名称进行搜索: + +```ts +handleSearch(selectedValue); +// selectedValue 例如:高级Java后端开发工程师 +``` diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/ESJobDocument.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/ESJobDocument.java index 62003a9..f510706 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/ESJobDocument.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/ESJobDocument.java @@ -120,6 +120,9 @@ public class ESJobDocument @IndexField(fieldType = FieldType.TEXT, analyzer = Analyzer.IK_SMART, searchAnalyzer = Analyzer.IK_SMART) private String jobCategory; + @ApiModelProperty("岗位性质,对应业务字典 job_nature") + private String jobNature; + @JsonIgnore @ApiModelProperty("学历要求 对应字典education int类型 es方便查询") private Integer education_int; diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/Job.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/Job.java index 32a3141..ceebe52 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/Job.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/Job.java @@ -148,6 +148,10 @@ public class Job extends BaseEntity @ApiModelProperty("岗位分类") private String jobCategory; + @Excel(name = "岗位性质") + @ApiModelProperty("岗位性质,对应业务字典 job_nature") + private String jobNature; + @TableField(exist = false) @ApiModelProperty("公司性质") private String companyNature; diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/BussinessDictTypeServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/BussinessDictTypeServiceImpl.java index f123d05..b618adb 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/BussinessDictTypeServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/BussinessDictTypeServiceImpl.java @@ -242,6 +242,7 @@ public class BussinessDictTypeServiceImpl implements IBussinessDictTypeService filed.put("data_source","数据来源"); filed.put("job_url","岗位url连接"); filed.put("job_category",jobTitleMapper.selectList(null).stream().map(JobTitle::getJobName).collect(Collectors.toList())); + filed.put("job_nature",this.selectDictDataByType("job_nature").stream().map(BussinessDictData::getDictLabel).collect(Collectors.toList())); return filed; } } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java index 84a1b63..f4f2f38 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobServiceImpl.java @@ -439,26 +439,13 @@ public class JobServiceImpl extends ServiceImpl implements IJobSe { LocalDateTime now = LocalDateTime.now(); String formattedDate = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + Job existingJob = jobMapper.selectById(job.getJobId()); //修改岗位状态 if(job.getIsPublish()!=null) { if(job.getIsPublish()==1){ job.setPostingDate(formattedDate); - //修改es缓存 - iesJobSearchService.updateJob(job.getJobId()); - //添加岗位上新 - Job parmJob=null; - if(job.getCompanyId()==null){ - //传递job消息不完整 - parmJob=jobMapper.getJobInfo(job.getJobId()); - } - List users=companyCollectionMapper.selectAppuserList(parmJob.getCompanyId()); - if(users!=null&&users.size()>0){ - List notices= NoticeUtils.createGwsxNotice(users,parmJob); - noticeMapper.batchInsert(notices); - } }else { job.setPostingDate(null); - iesJobSearchService.deleteJob(job.getJobId()); } } int i=jobMapper.updateById(job); @@ -468,6 +455,25 @@ public class JobServiceImpl extends ServiceImpl implements IJobSe handleJobContact(job); //添加附件 handleJobFile(job); + + // 数据库更新成功后再同步 ES,确保岗位性质等最新字段能立即反映到 PC 岗位卡片。 + if (Integer.valueOf(1).equals(job.getIsPublish()) + || (job.getIsPublish() == null && existingJob != null + && Integer.valueOf(1).equals(existingJob.getIsPublish()))) { + iesJobSearchService.updateJob(job.getJobId()); + + // 添加岗位上新通知,始终使用数据库中的完整岗位信息。 + Job publishedJob = jobMapper.getJobInfo(job.getJobId()); + if (publishedJob != null && publishedJob.getCompanyId() != null) { + List users = companyCollectionMapper.selectAppuserList(publishedJob.getCompanyId()); + if(users != null && !users.isEmpty()){ + List notices = NoticeUtils.createGwsxNotice(users, publishedJob); + noticeMapper.batchInsert(notices); + } + } + } else if (Integer.valueOf(0).equals(job.getIsPublish())) { + iesJobSearchService.deleteJob(job.getJobId()); + } } return i; } diff --git a/ruoyi-bussiness/src/main/resources/mapper/app/JobMapper.xml b/ruoyi-bussiness/src/main/resources/mapper/app/JobMapper.xml index 8f30bb1..bef41b4 100644 --- a/ruoyi-bussiness/src/main/resources/mapper/app/JobMapper.xml +++ b/ruoyi-bussiness/src/main/resources/mapper/app/JobMapper.xml @@ -31,6 +31,7 @@ + @@ -74,6 +75,7 @@ + @@ -112,7 +114,7 @@ - select job_id, job_title, min_salary, max_salary, education, experience, company_name, job_location, posting_date, vacancies, del_flag, create_by, create_time, update_by, update_time, remark, latitude, longitude, "view", company_id , is_hot ,is_urgent,apply_num,is_publish, description,job_location_area_code,data_source,job_url,job_category,is_explain,explain_url,cover,job_type,job_address, review_status,is_key_populations,key_populations from job + select job_id, job_title, min_salary, max_salary, education, experience, company_name, job_location, posting_date, vacancies, del_flag, create_by, create_time, update_by, update_time, remark, latitude, longitude, "view", company_id , is_hot ,is_urgent,apply_num,is_publish, description,job_location_area_code,data_source,job_url,job_category,job_nature,is_explain,explain_url,cover,job_type,job_address, review_status,is_key_populations,key_populations from job INSERT INTO row_work ( @@ -142,7 +144,7 @@ job_title, min_salary, max_salary, education, experience, company_name, job_location, job_location_area_code, posting_date, vacancies, latitude, longitude, "view", company_id, is_hot, is_urgent, apply_num, description, is_publish, data_source, job_url, remark, del_flag, - create_by, create_time, row_id, job_category,job_type,job_address,job_status,is_key_populations, + create_by, create_time, row_id, job_category,job_nature,job_type,job_address,job_status,is_key_populations, key_populations ) VALUES @@ -152,7 +154,7 @@ #{job.vacancies}, #{job.latitude}, #{job.longitude}, #{job.view}, #{job.companyId}, #{job.isHot}, #{job.isUrgent}, #{job.applyNum}, #{job.description}, #{job.isPublish}, #{job.dataSource}, #{job.jobUrl}, #{job.remark}, #{job.delFlag}, #{job.createBy}, #{job.createTime}, - #{job.rowId}, #{job.jobCategory},#{job.jobType},#{job.jobAddress},#{job.jobStatus},#{job.isKeyPopulations},#{job.keyPopulations} + #{job.rowId}, #{job.jobCategory},#{job.jobNature},#{job.jobType},#{job.jobAddress},#{job.jobStatus},#{job.isKeyPopulations},#{job.keyPopulations} ) diff --git a/ruoyi-bussiness/src/main/resources/mapper/rc/PublicJobFairMapper.xml b/ruoyi-bussiness/src/main/resources/mapper/rc/PublicJobFairMapper.xml index 6d7afb8..b230159 100644 --- a/ruoyi-bussiness/src/main/resources/mapper/rc/PublicJobFairMapper.xml +++ b/ruoyi-bussiness/src/main/resources/mapper/rc/PublicJobFairMapper.xml @@ -167,7 +167,7 @@