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);
}
// 更新状态和时间