diff --git a/client-api/src/main/java/org/jiayunet/controller/JobController.java b/client-api/src/main/java/org/jiayunet/controller/JobController.java index 01d875a..68f6940 100644 --- a/client-api/src/main/java/org/jiayunet/controller/JobController.java +++ b/client-api/src/main/java/org/jiayunet/controller/JobController.java @@ -2,15 +2,13 @@ package org.jiayunet.controller; import lombok.AllArgsConstructor; import org.jiayunet.pojo.PageResult; +import org.jiayunet.pojo.dto.job.JobDetailDto; import org.jiayunet.pojo.dto.job.JobDto; import org.jiayunet.pojo.param.job.JobQueryParam; import org.jiayunet.service.JobService; import org.jiayunet.tool.UserSecurityTool; import org.springframework.validation.annotation.Validated; -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; +import org.springframework.web.bind.annotation.*; /** * 岗位接口 @@ -33,4 +31,14 @@ public class JobController { Long userId = UserSecurityTool.getUserId(); return jobService.listJobs(param, userId); } + + /** + * 岗位详情 + *

返回岗位完整信息、公司信息、匹配度、收藏状态

+ */ + @GetMapping("/{jobId}") + public JobDetailDto getJobDetail(@PathVariable Long jobId) { + Long userId = UserSecurityTool.getUserId(); + return jobService.getJobDetail(jobId, userId); + } } diff --git a/client-api/src/main/java/org/jiayunet/pojo/dto/job/JobDetailDto.java b/client-api/src/main/java/org/jiayunet/pojo/dto/job/JobDetailDto.java new file mode 100644 index 0000000..999884d --- /dev/null +++ b/client-api/src/main/java/org/jiayunet/pojo/dto/job/JobDetailDto.java @@ -0,0 +1,89 @@ +package org.jiayunet.pojo.dto.job; + +import lombok.Data; + +import java.util.List; + +/** + * 岗位详情出参 + * + * @author zk + */ +@Data +public class JobDetailDto { + // ========== 岗位信息 ========== + /** 岗位ID */ + private Long jobId; + /** 岗位标题 */ + private String jobTitle; + /** 薪资描述 */ + private String salary; + /** 工作类型 0=全职 1=兼职 */ + private Integer employmentType; + /** 学历要求 0=不限 1=大专 2=本科 3=硕士 4=博士 */ + private Integer education; + /** 最低工作年限 */ + private Integer minExperience; + /** 岗位职责 */ + private String description; + /** 任职要求 */ + private String requirement; + /** 加分项 */ + private String bonus; + /** 岗位标签 */ + private List tags; + /** 技能标签 */ + private List skillTags; + /** 来源链接 */ + private String sourceUrl; + /** 岗位类型名称 */ + private String categoryName; + /** 要求的行业经验名称 */ + private String requiredIndustryName; + + // ========== 公司信息 ========== + /** 公司ID */ + private Long companyId; + /** 公司名称 */ + private String companyName; + /** 公司简称 */ + private String companyShortName; + /** 公司Logo */ + private String companyLogoUrl; + /** 公司类型 */ + private String companyType; + /** 公司所属行业名称 */ + private String companyIndustryName; + /** 公司标签 */ + private List companyTags; + /** 公司简介 */ + private String companySummary; + /** 公司描述 */ + private String companyDescription; + /** 成立时间 */ + private String companyFoundedYear; + /** 公司地址 */ + private String companyAddress; + /** 公司规模 */ + private String companyScale; + /** 公司官网 */ + private String companyWebsite; + /** 融资状态 */ + private String companyFinancingStage; + /** 最新估值 */ + private String companyLatestValuation; + /** 公司新闻 */ + private List companyNews; + /** 地区名称 */ + private String regionName; + + // ========== 匹配度信息 ========== + /** 匹配总分 */ + private Integer matchScore; + /** 匹配度详情 */ + private JobMatchScoreDto matchDetail; + + // ========== 用户操作状态 ========== + /** 是否已收藏 */ + private Boolean isFavorite; +} diff --git a/client-api/src/main/java/org/jiayunet/service/JobService.java b/client-api/src/main/java/org/jiayunet/service/JobService.java index afad7ea..14a3dcd 100644 --- a/client-api/src/main/java/org/jiayunet/service/JobService.java +++ b/client-api/src/main/java/org/jiayunet/service/JobService.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.extern.slf4j.Slf4j; import org.jiayunet.mapper.*; import org.jiayunet.pojo.PageResult; +import org.jiayunet.pojo.dto.job.JobDetailDto; import org.jiayunet.pojo.dto.job.JobDto; import org.jiayunet.pojo.dto.job.JobMatchScoreDto; import org.jiayunet.pojo.param.job.JobQueryParam; @@ -19,9 +20,9 @@ import java.util.stream.Collectors; /** * 岗位服务 - *

主要功能:岗位列表查询、匹配度计算

- *

依赖:JobMatchService(匹配度计算)

- *

使用表:bg_job(查询岗位)、bg_user_job_dislike(查询不感兴趣记录)、bg_user_job_favorite(查询收藏状态)、bg_china_regions_code(扩展地区子级)、bg_job_category(扩展岗位类型子级)、bg_industry(扩展行业子级)

+ *

主要功能:岗位列表查询、岗位详情查询、匹配度计算

+ *

依赖服务:JobMatchService(匹配度计算)

+ *

使用的表:bg_job、bg_company、bg_user_job_dislike、bg_user_job_favorite、bg_china_regions_code、bg_job_category、bg_industry

* * @author zk */ @@ -48,11 +49,21 @@ public class JobService { private IndustryMapper industryMapper; @Autowired - private org.jiayunet.service.JobMatchService jobMatchService; + private CompanyMapper companyMapper; + + @Autowired + private JobMatchService jobMatchService; /** * 岗位列表查询 - *

1. 扩展筛选条件子级 2. 查询不感兴趣记录 3. 扩展不感兴趣的地区和行业子级 4. 执行分页查询 5. 查询收藏状态 6. 批量计算匹配度 7. 组装返回数据

+ *

方法逻辑流程:

+ *

1. 扩展筛选条件的子级(地区/岗位类型/行业)

+ *

2. 查询用户不感兴趣记录

+ *

3. 提取排除列表(岗位/公司/地区/行业)

+ *

4. 执行分页查询

+ *

5. 查询收藏状态

+ *

6. 批量计算匹配度

+ *

7. 组装返回数据

*/ public PageResult listJobs(JobQueryParam param, Long userId) { // 1. 扩展筛选条件的子级 @@ -150,4 +161,104 @@ public class JobService { List favorites = userJobFavoriteMapper.selectList(new LambdaQueryWrapper().eq(UserJobFavorite::getUserId, userId).in(UserJobFavorite::getJobId, jobIds)); return favorites.stream().collect(Collectors.toMap(UserJobFavorite::getJobId, f -> true)); } + + + /** + * 查询岗位详情 + *

方法逻辑流程:

+ *

1. 查询岗位信息(校验状态)

+ *

2. 查询公司信息

+ *

3. 查询收藏状态

+ *

4. 计算匹配度

+ *

5. 查询关联名称(岗位类型/行业/地区)

+ *

6. 组装返回数据

+ */ + public JobDetailDto getJobDetail(Long jobId, Long userId) { + // 1. 查询岗位 + Job job = jobMapper.selectOne(new LambdaQueryWrapper().eq(Job::getId, jobId).eq(Job::getStatus, 0)); + if (job == null) throw new RuntimeException("岗位不存在或已下架"); + + // 2. 查询公司 + Company company = companyMapper.selectById(job.getCompanyId()); + if (company == null) throw new RuntimeException("公司信息不存在"); + + // 3. 查询收藏状态 + Long count = userJobFavoriteMapper.selectCount(new LambdaQueryWrapper().eq(UserJobFavorite::getUserId, userId).eq(UserJobFavorite::getJobId, jobId)); + + // 4. 计算匹配度 + List jobList = new ArrayList<>(); + JobListItemVo item = new JobListItemVo(); + item.setId(job.getId()); + item.setRequiredIndustryId(job.getRequiredIndustryId()); + item.setMinExperience(job.getMinExperience()); + jobList.add(item); + Map> matchScoreMap = jobMatchService.batchCalculateMatchScore(jobList, userId); + Map scoreMap = matchScoreMap.get(jobId); + + // 5. 查询关联名称 + String categoryName = null; + if (job.getCategoryId() != null) { + JobCategory category = categoryMapper.selectById(job.getCategoryId()); + if (category != null) categoryName = category.getName(); + } + String requiredIndustryName = null; + if (job.getRequiredIndustryId() != null) { + Industry industry = industryMapper.selectById(job.getRequiredIndustryId()); + if (industry != null) requiredIndustryName = industry.getName(); + } + String companyIndustryName = null; + if (company.getIndustryId() != null) { + Industry industry = industryMapper.selectById(company.getIndustryId()); + if (industry != null) companyIndustryName = industry.getName(); + } + String regionName = null; + if (company.getRegionCode() != null) { + ChinaRegionsCode region = regionMapper.selectOne(new LambdaQueryWrapper().eq(ChinaRegionsCode::getCode, company.getRegionCode())); + if (region != null) regionName = region.getName(); + } + + // 6. 组装返回 + JobDetailDto dto = new JobDetailDto(); + dto.setJobId(job.getId()); + dto.setJobTitle(job.getTitle()); + dto.setSalary(job.getSalary()); + dto.setEmploymentType(job.getEmploymentType()); + dto.setEducation(job.getEducation()); + dto.setMinExperience(job.getMinExperience()); + dto.setDescription(job.getDescription()); + dto.setRequirement(job.getRequirement()); + dto.setBonus(job.getBonus()); + dto.setTags(job.getTags()); + dto.setSkillTags(job.getSkillTags()); + dto.setSourceUrl(job.getSourceUrl()); + dto.setCategoryName(categoryName); + dto.setRequiredIndustryName(requiredIndustryName); + dto.setCompanyId(company.getId()); + dto.setCompanyName(company.getName()); + dto.setCompanyShortName(company.getShortName()); + dto.setCompanyLogoUrl(company.getLogoUrl()); + dto.setCompanyType(company.getCompanyType()); + dto.setCompanyIndustryName(companyIndustryName); + dto.setCompanyTags(company.getTags()); + dto.setCompanySummary(company.getSummary()); + dto.setCompanyDescription(company.getDescription()); + dto.setCompanyFoundedYear(company.getFoundedYear()); + dto.setCompanyAddress(company.getAddress()); + dto.setCompanyScale(company.getScale()); + dto.setCompanyWebsite(company.getWebsite()); + dto.setCompanyFinancingStage(company.getFinancingStage()); + dto.setCompanyLatestValuation(company.getLatestValuation()); + dto.setCompanyNews(company.getNews()); + dto.setRegionName(regionName); + dto.setIsFavorite(count > 0); + if (scoreMap != null) { + dto.setMatchScore(scoreMap.get("totalScore")); + dto.setMatchDetail(new JobMatchScoreDto(scoreMap.get("industryScore"), scoreMap.get("skillScore"), scoreMap.get("experienceScore"))); + } else { + dto.setMatchScore(0); + dto.setMatchDetail(new JobMatchScoreDto(0, 0, 0)); + } + return dto; + } + } diff --git a/manager/src/main/java/org/jiayunet/pojo/po/Company.java b/manager/src/main/java/org/jiayunet/pojo/po/Company.java index dabd2a4..b826f23 100644 --- a/manager/src/main/java/org/jiayunet/pojo/po/Company.java +++ b/manager/src/main/java/org/jiayunet/pojo/po/Company.java @@ -1,11 +1,14 @@ package org.jiayunet.pojo.po; import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import lombok.Data; import java.time.Instant; +import java.util.List; /** * 公司表(bg_company) @@ -13,7 +16,7 @@ import java.time.Instant; * @author zk */ @Data -@TableName(value = "bg_company") +@TableName(value = "bg_company", autoResultMap = true) public class Company { @TableId(type = IdType.ASSIGN_ID) @@ -37,8 +40,9 @@ public class Company { /** 行业ID */ private Long industryId; - /** 公司标签(JSON数组) */ - private String tags; + /** 公司标签 */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List tags; /** 公司简要 */ private String summary; @@ -64,8 +68,9 @@ public class Company { /** 最新估值 */ private String latestValuation; - /** 新闻动态(JSON数组) */ - private String news; + /** 新闻动态 */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List news; /** 状态 0=待完善 1=已完善 2=禁用 3=补充中 4=补充失败 */ private Integer status; diff --git a/manager/src/main/java/org/jiayunet/service/CompanyCleanTransactionService.java b/manager/src/main/java/org/jiayunet/service/CompanyCleanTransactionService.java index e9f887e..31ab1ab 100644 --- a/manager/src/main/java/org/jiayunet/service/CompanyCleanTransactionService.java +++ b/manager/src/main/java/org/jiayunet/service/CompanyCleanTransactionService.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.Instant; +import java.util.ArrayList; import java.util.List; /** @@ -62,7 +63,9 @@ public class CompanyCleanTransactionService { // tags:JSON数组 JsonNode tagsNode = root.path("tags"); if (tagsNode.isArray() && !tagsNode.isEmpty()) { - company.setTags(tagsNode.toString()); + List tags = new ArrayList<>(); + tagsNode.forEach(node -> tags.add(node.asText())); + company.setTags(tags); } // summary @@ -116,7 +119,9 @@ public class CompanyCleanTransactionService { // news:JSON数组 JsonNode newsNode = root.path("news"); if (newsNode.isArray() && !newsNode.isEmpty()) { - company.setNews(newsNode.toString()); + List news = new ArrayList<>(); + newsNode.forEach(node -> news.add(node.asText())); + company.setNews(news); } // 更新状态和时间