Commit e8edde8b by zwb

添加保存分析数据,修改三级分类查询从数据库获取数据

parent 89a21338
Showing with 464 additions and 123 deletions
......@@ -82,5 +82,8 @@ public interface Constants {
/** 全字段时间格式化字符串模式:yyyy-MM-dd HH:mm:ss */
public static final String DATE_FULL_FORMAT = "yyyy-MM-dd HH:mm:ss";
/**分析职业期望数据*/
String REDIS_USER_ANALYSIS_KEY = "user:analysis:%s";
}
package com.bkty.system.controller;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.bkty.system.domain.dto.AnalysisCareerDto;
import com.bkty.system.init.*;
import com.bkty.system.service.jobRecommend.AiAnalysisService;
import com.bkty.system.service.jobRecommend.JobRecommendService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
......@@ -9,6 +13,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.JxgException;
import org.dromara.common.core.utils.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -35,6 +40,9 @@ public class JobRecommendController {
@Autowired
private JobRecommendService jobRecommendService;
@Autowired
private AiAnalysisService aiAnalysisService;
@Operation(summary = "岗位三级分类查询")
@GetMapping("/positions")
public R<List<Level1Group>> getAllPositions(@RequestParam(value = "level1", required = false) String level1) {
......@@ -46,4 +54,27 @@ public class JobRecommendController {
List<Level1Group> result = jobRecommendService.getLevel1Groups(level1);
return new R<>(result);
}
/**
* 保存分析数据
* @param dto
* @return
*/
@PostMapping("/save-analysis-data")
private R<String> saveAnalysisData(@RequestBody AnalysisCareerDto dto){
if (CollectionUtil.isEmpty(dto.getCareer())) {
throw new JxgException("期望职业不能为空");
}
if (CollectionUtil.isEmpty(dto.getTargetCity())) {
throw new JxgException("期望城市不能为空");
}
if (StringUtils.isBlank(dto.getPay())) {
throw new JxgException("期望薪资不能为空");
}
if (ObjectUtil.isEmpty(dto.getResumeId())) {
throw new JxgException("简历Id不能为空");
}
String id = aiAnalysisService.saveAnalysisData(dto);
return new R<>(id);
}
}
package com.bkty.system.domain.dto;
import lombok.Data;
import java.util.List;
/**
* @author zwb
* @description 期望职业dto
* @data 2024/12/13
**/
@Data
public class AnalysisCareerDto {
private String userId;
private String analysisId;
private String resumeId;
/**
* 期望职业
*/
private List<String> career;
/**
* 单位性质AnalysisCareerEnum.CompanyNature
*/
private List<String> companyNature;
/**
* 目标城市
*/
private List<String> targetCity;
/**
* 公司规模 AnalysisCareerEnum.CompanySize
*/
private List<Integer> companySize;
/**
* 实习时长 AnalysisCareerEnum.InternTime
*/
private Integer internTime;
/**
* 每周出勤 不限就给0
*/
private Integer workTime;
/**
* 求职性质 实习 全职 全选 AnalysisCareerEnum.JobType
*/
private String jobType;
private Integer workYear;
/**
* 期望薪资
*/
private String pay;
/**MBTI性格测试*/
private String mbti;
private Integer authRecommend;
/**推荐状态0.为推荐 1.推荐中 2.推荐完成*/
private Integer recommendStatus;
}
package com.bkty.system.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.dromara.common.mybatis.core.domain.BaseEntity;
// 智能推荐信息
@Data
@TableName("ai_recommend_base")
public class AiRecommendBase extends BaseEntity {
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
private Long userId;
/**期望职业*/
private String career;
/**单位性质*/
private String companyNature;
/**目标城市*/
private String targetCity;
/**期望薪资*/
private String pay;
/**MBTI测试结果*/
private String mbti;
private String companySize;
private Integer internTime;
private Integer workTime;
private Long resumeId;
/**多个类型使用逗号分隔*/
private String jobType;
/**自动推荐状态 0.已经自动推荐*/
private Integer authRecommend;
/**推荐状态 0.未推荐 1.推荐中 2.推荐完成*/
private Integer recommendStatus;
}
\ No newline at end of file
package com.bkty.system.domain.entity;
import lombok.Data;
/**
* @author Zhang Wenbiao
* @description 岗位信息
* @datetime 2025/12/3 18:22
*/
@Data
public class PositionData {
/**
* 主键id
*/
private Long id;
/**
* 行业
*/
private String industryType;
/**
* 行业图片
*/
private String industryImg;
/**
* 岗位类型
*/
private String positionType;
/**
* 岗位名称
*/
private String jobTitle;
/**
* 岗位描述
*/
private String description;
/**
* 岗位图片
*/
private String jobImg;
}
package com.bkty.system.init;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Service
public class ExcelPositionReader {
/**
* 读取xlsx文件并解析为新的分层Map结构
* @param filePath resources下的文件路径
* @return 分层结构 List<Level1Group>
*/
public List<Level1Group> readPositionData(String filePath) throws Exception {
// 用于保持顺序的列表
List<Level1Group> level1Groups = new ArrayList<>();
Map<String, Level1Group> level1Map = new LinkedHashMap<>();
// 获取resources目录下的文件
ClassPathResource resource = new ClassPathResource(filePath);
InputStream inputStream = resource.getInputStream();
try (Workbook workbook = new XSSFWorkbook(inputStream)) {
Sheet sheet = workbook.getSheetAt(0); // 获取第一个sheet
// 跳过第一行(标题行),从第二行开始读取数据
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
// 读取六列数据
String level1Name = getCellValueAsString(row.getCell(0)); // 第一列:一级分类名称
// String level1Icon = getCellValueAsString(row.getCell(1)); // 第二列:一级分类图标url
String level2Name = getCellValueAsString(row.getCell(2)); // 第三列:二级分类名称
String level3Name = getCellValueAsString(row.getCell(3)); // 第四列:三级分类名称
// String level3Desc = getCellValueAsString(row.getCell(4)); // 第五列:三级分类描述
// String level3Icon = getCellValueAsString(row.getCell(5)); // 第六列:三级分类图标url
// 跳过空行或数据不完整的行
if (level1Name == null || level1Name.trim().isEmpty() ||
level2Name == null || level2Name.trim().isEmpty() ||
level3Name == null || level3Name.trim().isEmpty()) {
continue;
}
// 处理一级分类
Level1Group level1Group;
if (level1Map.containsKey(level1Name)) {
level1Group = level1Map.get(level1Name);
} else {
level1Group = new Level1Group(level1Name);
level1Map.put(level1Name, level1Group);
level1Groups.add(level1Group);
}
// 处理二级分类
Level2Group level2Group = null;
for (Level2Group group : level1Group.getLevel2Groups()) {
if (group.getName().equals(level2Name)) {
level2Group = group;
break;
}
}
if (level2Group == null) {
level2Group = new Level2Group(level2Name);
level1Group.getLevel2Groups().add(level2Group);
}
// 添加三级分类
Level3Group level3Group = new Level3Group(
level3Name
);
level2Group.getLevel3Groups().add(level3Group);
}
}
return level1Groups;
}
/**
* 获取单元格值为字符串
*/
private String getCellValueAsString(Cell cell) {
if (cell == null) {
return null;
}
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue().toString();
} else {
return String.valueOf((long) cell.getNumericCellValue());
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
return cell.getCellFormula();
default:
return null;
}
}
}
......@@ -6,15 +6,15 @@ import java.util.List;
// 一级分类类
public class Level1Group {
private String name;
// private String iconUrl;
private List<Level2Group> level2Groups;
// private String iconUrl;
public Level1Group(String name) {
this.name = name;
// this.iconUrl = iconUrl;
this.level2Groups = new ArrayList<>();
}
// getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
......
......@@ -4,11 +4,13 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class PositionCacheInitializer implements CommandLineRunner {
@Autowired
private ExcelPositionReader excelPositionReader;
private PositionDataService positionDataService;
@Autowired
private CategoryCacheManager categoryCacheManager;
......@@ -16,12 +18,10 @@ public class PositionCacheInitializer implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
try {
// 读取resources/template路径下的xlsx文件
// 假设文件名为 positions.xlsx,请根据实际情况调整
var positionDataMap = excelPositionReader.readPositionData("template/positions.xlsx");
List<Level1Group> positions = positionDataService.getPositions();
// 加载到全局缓存
categoryCacheManager.loadPositionCache(positionDataMap);
categoryCacheManager.loadPositionCache(positions);
} catch (Exception e) {
System.err.println("加载岗位数据缓存失败: " + e.getMessage());
e.printStackTrace();
......
package com.bkty.system.init;
import com.bkty.system.domain.entity.PositionData;
import com.bkty.system.mapper.PositionDataMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author Zhang Wenbiao
* @description PositionDataMapper
* @datetime 2025/12/3 18:12
*/
@Service
public class PositionDataService {
@Autowired
private PositionDataMapper positionDataMapper;
/**
* 查询数据并封装为三级结构
*/
public List<Level1Group> getPositions() {
// 从数据库查询数据
List<PositionData> positionList = positionDataMapper.selectPositionNameList();
// 使用Map来组织三级结构
Map<String, Level1Group> level1Map = new LinkedHashMap<>();
for (PositionData position : positionList) {
String level1Name = position.getIndustryType();
String level2Name = position.getPositionType();
String level3Name = position.getJobTitle();
// 如果任何一级名称为空,跳过该记录
if (level1Name == null || level2Name == null || level3Name == null) {
continue;
}
// 构建第一级(行业类型)
Level1Group level1 = level1Map.computeIfAbsent(level1Name,
k -> new Level1Group(k));
// 构建第二级(岗位类型)
Level2Group level2 = findOrCreateLevel2(level1.getLevel2Groups(), level2Name);
// 构建第三级(岗位名称)
addLevel3IfNotExists(level2.getLevel3Groups(), level3Name);
}
return new ArrayList<>(level1Map.values());
}
/**
* 在第二级列表中查找或创建新的第二级
*/
private Level2Group findOrCreateLevel2(List<Level2Group> level2Groups, String level2Name) {
// 查找是否已存在
for (Level2Group level2 : level2Groups) {
if (level2.getName().equals(level2Name)) {
return level2;
}
}
// 不存在则创建并添加到列表
Level2Group newLevel2 = new Level2Group(level2Name);
level2Groups.add(newLevel2);
return newLevel2;
}
/**
* 添加第三级(如果不存在)
*/
private void addLevel3IfNotExists(List<Level3Group> level3Groups, String level3Name) {
// 检查是否已存在
for (Level3Group level3 : level3Groups) {
if (level3.getName().equals(level3Name)) {
return; // 已存在,直接返回
}
}
// 不存在则添加
level3Groups.add(new Level3Group(level3Name));
}
}
package com.bkty.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bkty.system.domain.entity.AiRecommendBase;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AiRecommendBaseMapper extends BaseMapper<AiRecommendBase> {
}
package com.bkty.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bkty.system.domain.entity.AiRecommendBase;
import com.bkty.system.domain.entity.PositionData;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface PositionDataMapper extends BaseMapper<PositionData> {
@Select("""
SELECT industrytype, positiontype, jobtitle
FROM positions_data
WHERE industrytype IS NOT NULL
AND positiontype IS NOT NULL
AND jobtitle IS NOT NULL
ORDER BY industrytype, positiontype, jobtitle
""")
List<PositionData> selectPositionNameList();
}
package com.bkty.system.service.jobRecommend;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bkty.system.domain.dto.AnalysisCareerDto;
import com.bkty.system.domain.entity.AiRecommendBase;
public interface AiAnalysisService extends IService<AiRecommendBase> {
/**
* 保存期望职业
* @param dto
*/
String saveAnalysisData(AnalysisCareerDto dto);
}
package com.bkty.system.service.jobRecommend.impl;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.nacos.common.utils.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bkty.system.domain.dto.AnalysisCareerDto;
import com.bkty.system.domain.entity.AiRecommendBase;
import com.bkty.system.domain.entity.FunctionResumeBase;
import com.bkty.system.mapper.AiRecommendBaseMapper;
import com.bkty.system.mapper.FunctionResumeBaseMapper;
import com.bkty.system.service.coze.CozeApiService;
import com.bkty.system.service.jobRecommend.AiAnalysisService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.SecurityUtils;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Objects;
/**
* @author Zhang Wenbiao
* @description ai分析ServiceImpl
* @datetime 2025/12/3 15:14
*/
@Slf4j
@AllArgsConstructor
@Service
public class AiAnalysisServiceImpl extends ServiceImpl<AiRecommendBaseMapper, AiRecommendBase> implements AiAnalysisService {
private final RedisTemplate<String, String> redisTemplate;
private final FunctionResumeBaseMapper resumeBaseMapper;
private final CozeApiService cozeApiService;
// @Autowired
// @Qualifier("nonLoadBalancedRestTemplate")
// private RestTemplate restTemplate;
@Override
public String saveAnalysisData(AnalysisCareerDto dto) {
AiRecommendBase aiRecommendBase = new AiRecommendBase();
// if (StringUtils.isNotBlank(dto.getAnalysisId())){
// //String json = redisTemplate.opsForValue().get(CacheConstants.REDIS_USER_ANALYSIS_KEY.formatted(user.getId()));
// aiRecommendBase = this.baseMapper.selectById(dto.getAnalysisId());
// }
/* if (CollectionUtil.isNotEmpty(dto.getCareer())){
aiRecommendBase.setCareer(JSON.toJSONString(dto.getCareer()));
}
if (CollectionUtil.isNotEmpty(dto.getTargetCity())){
aiRecommendBase.setTargetCity(JSON.toJSONString(dto.getTargetCity()));
}
if (StringUtils.isNotBlank(dto.getPay())){
aiRecommendBase.setPay(dto.getPay());
}
if (CollectionUtil.isNotEmpty(dto.getCompanyNature())){
aiRecommendBase.setCompanyNature(JSON.toJSONString(dto.getCompanyNature()));
}
if (StringUtils.isNotBlank(dto.getMbti())){
aiRecommendBase.setMbti(dto.getMbti());
}
if (null != dto.getWorkTime()){
aiRecommendBase.setWorkTime(dto.getWorkTime());
}
if (null != dto.getInternTime()){
aiRecommendBase.setInternTime(dto.getInternTime());
}
if (CollectionUtil.isNotEmpty(dto.getCompanySize())){
aiRecommendBase.setCompanySize(JSON.toJSONString(dto.getCompanySize()));
}
if (null != dto.getJobType()){
aiRecommendBase.setJobType(dto.getJobType());
}
if (null != dto.getAuthRecommend()){
aiRecommendBase.setAuthRecommend(dto.getAuthRecommend());
}
if (null != dto.getRecommendStatus()){
aiRecommendBase.setRecommendStatus(dto.getRecommendStatus());
}*/
aiRecommendBase.setResumeId(Long.valueOf(dto.getResumeId())); //简历id
aiRecommendBase.setPay(dto.getPay()); //期望薪资
aiRecommendBase.setCareer(JSON.toJSONString(dto.getCareer())); //目标岗位
aiRecommendBase.setTargetCity(JSON.toJSONString(dto.getTargetCity())); //目标城市
aiRecommendBase.setAuthRecommend(0); //自动推荐
aiRecommendBase.setRecommendStatus(0); //未推荐
aiRecommendBase.setUserId(Objects.requireNonNull(LoginHelper.getLoginUser()).getUserId());
this.baseMapper.insert(aiRecommendBase);
redisTemplate.opsForValue().set(Constants.REDIS_USER_ANALYSIS_KEY.formatted(dto.getResumeId()), JSON.toJSONString(aiRecommendBase));
return String.valueOf(aiRecommendBase.getId());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment