Commit 14c56833 by zwb

新增岗位智推

parent 01791790
Showing with 1598 additions and 53 deletions
......@@ -138,6 +138,20 @@
<artifactId>fastjson2</artifactId>
<version>2.0.43</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>0.29.1</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>0.29.1</version>
</dependency>
</dependencies>
</project>
package org.dromara.common.core.config;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
/**
* @author jiangxiaoge
* @description
* @data 2025/2/13
**/
public class LlmEmbeddingConfig {
// 使用 volatile 保证在多线程环境下的正确性
private static volatile EmbeddingModel instance;
// 私有构造方法,防止外部实例化
private LlmEmbeddingConfig() {
}
public static EmbeddingModel getEmbeddingModel() {
// 双重检查锁定实现单例
if (instance == null) {
synchronized (LlmEmbeddingConfig.class) {
if (instance == null) {
try {
// 初始化 ChatLanguageModel 实例
instance = OpenAiEmbeddingModel
.builder()
//.baseUrl("https://api.openai.com/v1")
.baseUrl("http://47.253.179.191:6666/v1")
.modelName("text-embedding-3-small")
.apiKey("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")
.build();
} catch (Exception e) {
// 错误处理
e.printStackTrace();
return null;
}
}
}
}
return instance;
}
}
......@@ -85,4 +85,7 @@ public interface CacheConstants {
* 下载使用简历对应模版缓存pdf
*/
String RESUME_TEMPLATE_PDF_ID = "template:resume:pdf:download:%s";
/**分析职业期望数据*/
String REDIS_USER_ANALYSIS_KEY = "user:analysis:%s";
}
......@@ -82,8 +82,5 @@ 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";
}
......@@ -151,6 +151,19 @@
<artifactId>fastjson2</artifactId>
<version>2.0.43</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>0.29.1</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>0.29.1</version>
</dependency>
</dependencies>
<build>
......
package com.bkty.system.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用 StringRedisSerializer 处理 Key 和 Value
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(stringRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
\ No newline at end of file
......@@ -12,7 +12,11 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.JxgException;
import org.dromara.common.core.exception.WarnException;
import org.dromara.common.core.utils.DateTimeWrapper;
import org.dromara.common.core.utils.SecurityUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
......@@ -79,4 +83,20 @@ public class JobRecommendController {
public R<List<Level1City>> getAllCitys() {
return new R<>(categoryCacheManager.getCityCache());
}
@Operation(summary = "岗位智推")
@GetMapping("/position")
public R<Void> recommendPosition(@RequestParam(value = "analysisId") Long analysisId,
@RequestParam("resumeId") Long resumeId) {
if (analysisId == null || resumeId == null){
throw new WarnException("分析数据不能为空");
}
if (resumeId == null){
throw new WarnException("简历id不能为空");
}
Long userId = LoginHelper.getLoginUser().getUserId();
jobRecommendService.recommendPosition(analysisId,userId,resumeId);
return R.ok();
}
}
......@@ -5,12 +5,14 @@ import cn.hutool.core.collection.CollectionUtil;
import com.bkty.system.api.model.LoginUser;
import com.bkty.system.domain.dto.*;
import com.bkty.system.domain.entity.FunctionResumeBaseTag;
import com.bkty.system.domain.entity.FunctionResumeSketch;
import com.bkty.system.domain.vo.ResumeBase;
import com.bkty.system.domain.vo.ResumeByPdfVo;
import com.bkty.system.domain.vo.ResumeModelVo;
import com.bkty.system.domain.vo.ResumeVo;
import com.bkty.system.service.resume.NewEditionResumeService;
import com.bkty.system.service.resume.ResumeCacheService;
import com.bkty.system.service.resume.ResumeMakeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
......@@ -44,6 +46,8 @@ public class NewEditionResumeController {
private final ResumeCacheService resumeCacheService;
private final ResumeMakeService resumeMakeService;
/**
* 新版导入简历
* @param file 文件
......@@ -256,4 +260,15 @@ public class NewEditionResumeController {
this.newEditionResumeService.reversalExperience(dto);
return R.ok();
}
/**
* 查询简历分析详情
* @param resumeId
* @return
*/
@GetMapping("/query-resume-sketch")
public R<FunctionResumeSketch> queryResumeSketch(@RequestParam("resumeId") Long resumeId){
FunctionResumeSketch vo = this.resumeMakeService.queryResumeSketch(resumeId);
return new R<>(vo);
}
}
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;
import java.util.Date;
/**
* @author jiangxiaoge
* @description 岗位推荐记录
* @data 2025/2/18
**/
@Data
@TableName("ai_position_recommend_record")
public class AiPositionRecommendRecord extends BaseEntity {
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
private Long userId;
private Long positionId;
/**
* 是否收藏 0.否 1.是 2.不喜欢
*/
private Integer treasuresType;
/**
* 是否已读 0.否 1.是
*/
private Integer alreadyType;
private Integer deliverType;
private Integer operationType;
private Integer rScore;
private String rDetails;
private Date recommendTime;
private Long resumeId;
private String cityCode;
private String analyzeJson;
private String evaluationJson;
private String jobTitle;
private String jobProfession;
private String standardPosition;
private Integer companySize;
private Integer companyNature;
private String expDemand;
private String eduDemand;
private String publicPlatform;
private Integer maxPay;
private String companyName;
private Integer positionScore;
private String jdMd;
/**
* 首次评分
*/
private Integer firstScore;
//岗位优势分析
private String advantageAnalysis;
//岗位匹配度
private String matchRate;
//投递准备
private String deliverPrepare;
//自我介绍
private String selfIntroduction;
//公司规模
private String companyScale;
//公司行业
private String companyIndustry;
// 分析id
private Long analysisId;
}
\ No newline at end of file
package com.bkty.system.domain.entity;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.util.Date;
import java.util.List;
/**
* @author jiangxiaoge
* @description 岗位画像实体类
* @data 2025/2/7
**/
@Data
//@Document(indexName = "function_position_portrait_test") // 使用前缀
@Document(indexName = "#{@environment.getProperty('elasticsearch.index-name-prefix')}") // 使用前缀
public class FunctionPositionPortraitV2 {
@Id
private Long positionDataId;
//岗位名称
@Field(type = FieldType.Keyword)
private String jobTitle;
//公司行业
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String industry;
//标准职位
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String standardPosition;
//城市信息
@Field(type = FieldType.Keyword)
private List<String> workPlace;
//全职、实习、灵活工作
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String natureOfPost;
//薪资范围
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String salaryRange;
//薪资(查询用)
@Field(type = FieldType.Integer)
private Integer pay;
//公司名称
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String companyName;
//公司性质
@Field(type = FieldType.Integer)
private Integer companyNature;
//公司规模
@Field(type = FieldType.Integer)
private Integer companySize;
//经验要求
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String expDemand;
//学历要求
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String eduDemand;
//发布渠道
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String publicPlatform;
//发布链接
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String platformUrl;
//JD信息
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String jdHtml;
//专业技能
@Field(type = FieldType.Keyword)
private List<String> skill;
//福利
@Field(type = FieldType.Keyword)
private List<String> welfare;
//晋升路径
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String promote;
//软技能
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String mild;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String graduate;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String graduateContent;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String graduatePercentage;
@Field(type = FieldType.Dense_Vector, dims = 1536, similarity = "cosine")
private List<Float> featureVector;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String sketch;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String experienceType;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String publicTime;
@Field(type = FieldType.Date)
private Date queryDate;
private String standardJobDesc;
//是否实习
@Field(type = FieldType.Text)
private String isIntern;
}
\ No newline at end of file
package com.bkty.system.domain.entity;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.util.List;
@Data
public class JobEvaluation {
@JSONField(name = "competitiveness")
private Competitiveness competitiveness;
@JSONField(name = "jobFit")
private JobFit jobFit;
@JSONField(name = "resumeTips")
private ResumeTips resumeTips;
@JSONField(name = "prepGrowth")
private PrepGrowth prepGrowth;
@Data
public static class Competitiveness {
private List<String> advantages;
private List<String> weaknesses;
private List<String> suggestions;
}
@Data
public static class JobFit {
private List<String> requirements;
private MatchDetails matchDetails;
}
@Data
public static class MatchDetails {
private List<MatchedSkill> matched;
private List<MissingSkill> missing;
}
@Data
public static class ResumeTips {
private List<String> improvements;
}
@Data
public static class MatchedSkill {
private String skill;
private String status;
private String analysis;
}
@Data
public static class MissingSkill {
private String skill;
private String importance;
private String suggestion;
}
@Data
public static class PrepGrowth {
private List<String> shortTerm;
private List<String> longTerm;
}
}
\ No newline at end of file
package com.bkty.system.domain.entity;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.util.List;
@Data
public class MatchResult {
@JSONField(name = "comprehensiveMatchScore")
private Object comprehensiveMatchScore; // 处理数字和字符串两种情况
private String comprehensiveMatchRecommendation;
private List<String> jobSeekingAdvice;
private MatchDimensionAnalysis matchDimensionAnalysis;
public int getComprehensiveMatchScoreAsInt() {
if (comprehensiveMatchScore instanceof Number) {
return ((Number) comprehensiveMatchScore).intValue();
} else if (comprehensiveMatchScore instanceof String) {
try {
return Integer.parseInt((String) comprehensiveMatchScore);
} catch (NumberFormatException e) {
return 0; // 处理异常情况
}
}
return 0;
}
@Data
public static class MatchDimensionAnalysis {
private MatchDetail education;
private MatchDetail professionalSkills;
private MatchDetail salaryExpectation;
private MatchDetail workLocation;
private MatchDetail practicalExperience;
private MatchDetail softSkills;
}
@Data
public static class MatchDetail {
private String matchDegree;
private String detailedAnalysis;
private String matchPoint;
private String improvementSuggestion;
}
}
\ No newline at end of file
package com.bkty.system.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author jiangxiaoge
* @description 推送消息状态封装
* @data 2025/6/17
**/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ProcessItem {
private String status;
private String text;
private double percent;
private String time;
}
\ No newline at end of file
package com.bkty.system.domain.vo;
import com.bkty.system.domain.entity.AiPositionRecommendRecord;
import com.bkty.system.domain.entity.FunctionPositionPortraitV2;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* @author jiangxiaoge
* @description
* @data 2025/2/18
**/
@Data
public class AiPositionRecommendRecordVo extends AiPositionRecommendRecord {
private String resumeName;
private FunctionPositionPortraitV2 portraitV2;
private List<String> kitIds;
private Map<String, String> levelMap;
}
package com.bkty.system.domain.vo;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* @author Zhang Wenbiao
* @description 职业需求分析数据vo
* @datetime 2025/12/4 12:08
*/
@Data
public class AiRecommendVo {
private String id;
/**期望职业*/
private List<String> career;
/**单位性质*/
private List<String> companyNature;
/**目标城市*/
private List<String> targetCity;
/**期望薪资*/
private String pay;
private String mbti;
private Long resumeCount;
private List<Integer> companySize;
private Integer workTime;
private Integer internTime;
/**求职性质:1-实习,2-全职*/
private String jobType;
/**自动推荐状态 0.已经自动推荐*/
private Integer authRecommend;
/**推荐状态 0.未推荐 1.推荐中 2.推荐完成*/
private Integer recommendStatus;
/**发送给智能体的岗位列表*/
private List<Map<String, String>> jobList;
}
package com.bkty.system.domain.vo;
import com.bkty.system.domain.entity.JobEvaluation;
import com.bkty.system.domain.entity.MatchResult;
import com.bkty.system.domain.entity.FunctionPositionPortraitV2;
import lombok.Data;
/**
* @author jiangxiaoge
* @description
* @data 2025/2/18
**/
@Data
public class FunctionPositionPortraitVo extends FunctionPositionPortraitV2 {
private String score;
private String details;
private String resumeId;
private String cityCode;
private String whetherItMatches;
private String resumeSketch;
private Float scoreFloat;
private MatchResult matchResult;
private JobEvaluation jobEvaluation;
private String jdMd;
private Float rerankScore;
private String cozeResult;
}
\ No newline at end of file
package com.bkty.system.domain.vo;
import lombok.Data;
import java.util.List;
/**
* @author jiangxiaoge
* @description 岗位推荐列表item
* @data 2025/3/28
**/
@Data
public class RecommendItem {
private Long id;
private String jobTitle;
private String standardPosition;
private List<String> welfare;
private String salaryRange;
private Integer maxPay;
private String rDetails;
private String companyName;
private Integer companyNature;
private String city;
private String createTime;
private String resumeName;
private Integer positionScore;
private String jdMd;
}
\ No newline at end of file
package com.bkty.system.domain.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author jiangxiaoge
* @description 重排序vo
* @data 2025/7/1
**/
@Data
@AllArgsConstructor
public class RerankVo {
private FunctionPositionPortraitVo positionData;
private Float score;
}
\ No newline at end of file
......@@ -4,6 +4,8 @@ import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
......@@ -13,6 +15,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
*/
@EnableDubbo
@EnableScheduling
@EnableCaching
@SpringBootApplication
public class employmentBusinessPCSystemApplication {
public static void main(String[] args) {
......
......@@ -64,7 +64,43 @@ public class CategoryCacheManager {
public Level1Group getPositionDataByName(String level1Name) {
return positionNameMap.get(level1Name);
}
/**
* 根据三级分类名称获取二级分类下所有三级分类名称 (更新后的方法)
*/
public List<String> getLevel2NameByLevel3Name(String level3Name){
for (Level1Group level1Group : positionCache) {
for (Level2Group level2Group : level1Group.getLevel2Groups()) {
for (Level3Group level3Group : level2Group.getLevel3Groups()) {
if (level3Group.getName().equals(level3Name)) {
return level2Group.getLevel3Groups().stream().map(Level3Group::getName).toList();
}
}
}
}
return null;
}
/**
* 根据三级分类名称获取每一级分类的名称Map
*/
public Map<String, String> getLevelNameMapByLevel3Name(String level3Name) {
Map<String, String> levelNameMap = new ConcurrentHashMap<>();
for (Level1Group level1Group : positionCache) {
for (Level2Group level2Group : level1Group.getLevel2Groups()) {
for (Level3Group level3Group : level2Group.getLevel3Groups()) {
if (level3Group.getName().equals(level3Name)) {
levelNameMap.put("level1", level1Group.getName());
levelNameMap.put("level2", level2Group.getName());
levelNameMap.put("level3", level3Group.getName());
return levelNameMap;
}
}
}
}
return null;
}
/**
* 根据一级分类获取数据
*/
......
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;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface AiAnalysisMapper extends BaseMapper<AiRecommendBase> {
void updateAnalysis(@Param("analysisId") Long analysisId,
@Param("rType") Integer rType,
@Param("userId") Long userId,
@Param("authRecommend") Integer authRecommend);
}
......@@ -3,7 +3,14 @@ package com.bkty.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bkty.system.domain.entity.CityData;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface CityDataMapper extends BaseMapper<CityData> {
@Select("""
select city_code from city_data
where city_name = #{city}
""")
String getCityCodeByName(@Param("city") String city);
}
package com.bkty.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bkty.system.domain.entity.AiPositionRecommendRecord;
import com.bkty.system.domain.vo.AiPositionRecommendRecordVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @author jiangxiaoge
* @description 岗位推荐mapper
* @data 2025/2/18
**/
@Mapper
public interface PositionRecommendMapper extends BaseMapper<AiPositionRecommendRecord> {
@Select("""
SELECT position_id from ai_position_recommend_record where is_deleted = 0 and analysis_id = #{analysisId}
""")
List<Long> selectPositionIdList(@Param("analysisId") Long analysisId);
List<AiPositionRecommendRecordVo> selectPositionList(@Param("analysisId") Long analysisId,
@Param("queryType") Integer queryType,
@Param("recommendTime") String recommendTime,
@Param("pId") Long pId,
@Param("start") Long start,
@Param("size") Long size,
@Param("sortField") String sortField,
@Param("sortType") String sortType,
@Param("deliverType") Integer deliverType);
}
......@@ -13,4 +13,12 @@ public interface CozeApiService {
* @return
*/
String getCozeTokenCn(String cozeSource) throws Exception;
/**
* 获取cozeToken
* @return
*/
String getCozeToken() throws Exception;
String getCozeDomainUri();
}
......@@ -14,11 +14,9 @@ import org.apache.commons.collections4.MapUtils;
import org.dromara.common.core.constant.CacheConstants;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.CozeConstsnts;
import org.dromara.common.core.exception.JxgException;
import org.dromara.common.core.exception.WarnException;
import org.dromara.common.core.utils.AESUtil;
import org.dromara.common.core.utils.JWTGenerator;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.TimeTool;
import org.dromara.common.core.utils.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.ParameterizedTypeReference;
......@@ -107,4 +105,91 @@ public class CozeApiServiceImpl implements CozeApiService {
}
throw new WarnException("获取coze令牌失败");
}
@Override
public String getCozeToken() throws Exception {
HttpServletRequest request = WebUtils.getRequest();
String redisKey = CacheConstants.REDIS_CN_COZE_TOKEN_KEY;
String publicKey = CozeConstsnts.COZE_CN_OAUTH_PUBLIC_KEY_STR;
String oauthId = CozeConstsnts.COZE_CN_OAUTH_ID;
String apiDomainName = CozeConstsnts.COZE_CN_API_DOMAIN_NAME;
String privateKey = CozeConstsnts.COZE_CN_OAUTH_PRIVATE_KEY_STR;
if (request != null){
/*log.info("request不为空");
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
log.info("{}:{}", headerName, headerValue);
}*/
String cozeSource = request.getHeader(CozeConstsnts.COZE_HEADER_TOKEN_NAME);
//log.info("cozeSource:{}", cozeSource);
if (StringUtils.isNotBlank(cozeSource) && cozeSource.equals(CozeConstsnts.COZE_SOURCE_NAME)){
//log.info("进入国内访问");
redisKey = CacheConstants.REDIS_COZE_TOKEN_KEY;
publicKey = CozeConstsnts.COZE_OAUTH_PUBLIC_KEY_STR;
oauthId = CozeConstsnts.COZE_OAUTH_ID;
apiDomainName = CozeConstsnts.COZE_API_DOMAIN_NAME;
privateKey = CozeConstsnts.COZE_OAUTH_PRIVATE_KEY_STR;
}
}
try {
if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))){
return redisTemplate.opsForValue().get(redisKey);
}
} catch (Exception e) {
log.warn("Redis操作超时或异常,将直接获取新token: {}", e.getMessage());
// 继续执行后续代码获取新token
}
Map<String, Object> headerMap = Map.of("alg", "RS256",
"typ", "JWT",
"kid", publicKey);
//String headerString = Base64.getEncoder().encodeToString(JSON.toJSONString(headerMap).getBytes());
long iat = TimeTool.nowMilli();
long expiredTime = TimeTool.nowMilli() + TimeUnit.MINUTES.toMillis(10);
Map<String, Object> payloadMap = Map.of("iss", oauthId,
"aud", apiDomainName,
"iat", iat,
"exp", expiredTime,
"jti", UUID.randomUUID());
String payloadString = JSON.toJSONString(payloadMap);
String jwtString = JWTGenerator.generatorJwtString(headerMap, payloadString, privateKey);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set(HttpHeaders.AUTHORIZATION, CozeConstsnts.COZE_TOKEN_BEARER + jwtString);
// 创建请求体(示例 JSON 数据)
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("duration_seconds", 86399);
requestBody.put("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
// 发送 POST 请求并获取响应
ResponseEntity<String> response = restTemplate.exchange(
getCozeDomainUri() + "/api/permission/oauth2/token", HttpMethod.POST, requestEntity, String.class);
com.alibaba.fastjson.JSONObject jsonObject = JSON.parseObject(response.getBody());
String accessToken;
if (jsonObject != null) {
accessToken = jsonObject.getString("access_token");
redisTemplate.opsForValue().set(redisKey, accessToken, 86399 - 60, TimeUnit.SECONDS);
return accessToken;
}
throw new JxgException("获取coze令牌失败");
}
@Override
public String getCozeDomainUri(){
HttpServletRequest request = WebUtils.getRequest();
if (request != null){
String header = request.getHeader(CozeConstsnts.COZE_HEADER_TOKEN_NAME);
if (StringUtils.isNotBlank(header) && CozeConstsnts.COZE_SOURCE_NAME.equals(header)){
return Constants.HTTPS + CozeConstsnts.COZE_API_DOMAIN_NAME;
}
}
return Constants.HTTPS + CozeConstsnts.COZE_CN_API_DOMAIN_NAME;
}
}
......@@ -3,6 +3,7 @@ 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;
import com.bkty.system.domain.vo.AiRecommendVo;
public interface AiAnalysisService extends IService<AiRecommendBase> {
......@@ -11,4 +12,9 @@ public interface AiAnalysisService extends IService<AiRecommendBase> {
* @param dto
*/
String saveAnalysisData(AnalysisCareerDto dto);
/**
* 查询个人分析推荐数据
*/
AiRecommendVo queryAnalysisData(Long analysisId);
}
......@@ -14,4 +14,12 @@ public interface JobRecommendService {
* @return
*/
List<Level1Group> getLevel1Groups(String level1);
/**
* 岗位智推
* @param analysisId
* @param userId
* @param resumeId
*/
void recommendPosition(Long analysisId, Long userId, Long resumeId);
}
package com.bkty.system.service.jobRecommend;
import com.bkty.system.domain.entity.FunctionPositionPortraitV2;
import com.bkty.system.domain.vo.AiPositionRecommendRecordVo;
import java.util.Map;
/**
* @author jiangxiaoge
* @description 岗位推荐查询redisService
* @data 2025/3/28
**/
public interface PositionRecommendRedisService {
/**
* 初始化redis数据
*/
public boolean initRedisData(Long analysisId);
/**
* 保存岗位到redis
* @param portraitV2
* @param position
*/
void savePositionToRedis(FunctionPositionPortraitV2 portraitV2, AiPositionRecommendRecordVo position, Long userId);
}
\ No newline at end of file
package com.bkty.system.service.jobRecommend;
import com.bkty.system.domain.entity.FunctionPositionPortraitV2;
import java.util.Collection;
import java.util.List;
/**
* 岗位推荐service
*/
public interface RecommendPositionService {
/**
* 根据求职意向岗位推荐
* @param analysisId
*/
void recommendPosition(Long analysisId, Long userId, Long resumeId);
List<FunctionPositionPortraitV2> queryPositionList(String cityCode, List<Long> positionIdList);
}
......@@ -6,8 +6,10 @@ 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.dto.ResumeMakeDto;
import com.bkty.system.domain.entity.AiRecommendBase;
import com.bkty.system.domain.entity.FunctionResumeBase;
import org.dromara.common.core.constant.CacheConstants;
import com.bkty.system.domain.vo.AiRecommendVo;
import com.bkty.system.mapper.AiRecommendBaseMapper;
import com.bkty.system.mapper.FunctionResumeBaseMapper;
import com.bkty.system.service.coze.CozeApiService;
......@@ -48,63 +50,54 @@ public class AiAnalysisServiceImpl extends ServiceImpl<AiRecommendBaseMapper, Ai
@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()));
}
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(CacheConstants.REDIS_USER_ANALYSIS_KEY.formatted(aiRecommendBase.getId()), JSON.toJSONString(aiRecommendBase));
return String.valueOf(aiRecommendBase.getId());
}
if (StringUtils.isNotBlank(dto.getPay())){
aiRecommendBase.setPay(dto.getPay());
}
if (CollectionUtil.isNotEmpty(dto.getCompanyNature())){
aiRecommendBase.setCompanyNature(JSON.toJSONString(dto.getCompanyNature()));
@Override
public AiRecommendVo queryAnalysisData(Long analysisId) {
AiRecommendVo vo = new AiRecommendVo();
AiRecommendBase recommendBase = null;
String recommendJson = redisTemplate.opsForValue().get(CacheConstants.REDIS_USER_ANALYSIS_KEY.formatted(analysisId));
if (StringUtils.isBlank(recommendJson)){
recommendBase = this.baseMapper.selectById(analysisId);
}else {
recommendBase = JSON.parseObject(recommendJson, AiRecommendBase.class);
}
if (StringUtils.isNotBlank(dto.getMbti())){
aiRecommendBase.setMbti(dto.getMbti());
if (StringUtils.isNotBlank(recommendBase.getCareer())){
List<String> list = JSON.parseObject(recommendBase.getCareer(), List.class);
vo.setCareer(list);
}
if (null != dto.getWorkTime()){
aiRecommendBase.setWorkTime(dto.getWorkTime());
if (StringUtils.isNotBlank(recommendBase.getTargetCity())){
List<String> list = JSON.parseObject(recommendBase.getTargetCity(), List.class);
vo.setTargetCity(list);
}
if (null != dto.getInternTime()){
aiRecommendBase.setInternTime(dto.getInternTime());
if (StringUtils.isNotBlank(recommendBase.getPay())){
vo.setPay(recommendBase.getPay());
}
if (CollectionUtil.isNotEmpty(dto.getCompanySize())){
aiRecommendBase.setCompanySize(JSON.toJSONString(dto.getCompanySize()));
if (null != recommendBase.getAuthRecommend()){
vo.setAuthRecommend(recommendBase.getAuthRecommend());
}
if (null != dto.getJobType()){
aiRecommendBase.setJobType(dto.getJobType());
if (null != recommendBase.getRecommendStatus()){
vo.setRecommendStatus(recommendBase.getRecommendStatus());
}
if (null != dto.getAuthRecommend()){
aiRecommendBase.setAuthRecommend(dto.getAuthRecommend());
}
vo.setId(String.valueOf(recommendBase.getId()));
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(aiRecommendBase.getId()), JSON.toJSONString(aiRecommendBase));
return String.valueOf(aiRecommendBase.getId());
return vo;
}
}
package com.bkty.system.service.jobRecommend.impl;
import com.bkty.system.domain.entity.AiRecommendBase;
import com.bkty.system.init.CategoryCacheManager;
import com.bkty.system.init.Level1Group;
import com.bkty.system.init.Level2Group;
import com.bkty.system.init.Level3Group;
import com.bkty.system.mapper.AiAnalysisMapper;
import com.bkty.system.service.jobRecommend.AiAnalysisService;
import com.bkty.system.service.jobRecommend.JobRecommendService;
import com.bkty.system.service.jobRecommend.RecommendPositionService;
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.utils.DateTimeWrapper;
import org.dromara.common.core.utils.SecurityUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
......@@ -28,6 +36,11 @@ public class JobRecommendServiceImpl implements JobRecommendService {
@Autowired
private CategoryCacheManager categoryCacheManager;
@Autowired
private AiAnalysisMapper aiAnalysisMapper;
private final RecommendPositionService recommendPositionService;
@Override
public List<Level1Group> getLevel1Groups(String level1) {
......@@ -60,4 +73,40 @@ public class JobRecommendServiceImpl implements JobRecommendService {
}
return result;
}
@Override
public void recommendPosition(Long analysisId, Long userId, Long resumeId) {
log.info("开始推荐岗位");
if (userId == null) {
userId = LoginHelper.getLoginUser().getUserId();
}
final Long thisUserId = userId;
AiRecommendBase aiRecommendBase = aiAnalysisMapper.selectById(analysisId);
if (aiRecommendBase.getRecommendStatus() != null && aiRecommendBase.getRecommendStatus() == 1) {
log.info("用户数据推送中");
return;
}
Integer orlStatus = aiRecommendBase.getRecommendStatus();
log.info("修改岗位意向数据为推送中");
aiAnalysisMapper.updateAnalysis(analysisId, 1, thisUserId, 1);
//开启一个异步线程调用方法
new Thread(new Runnable() {
@Override
public void run() {
//异步调用推荐岗位
log.info("开始推荐岗位");
log.info("推荐岗位参数:analysisId:{},userId:{}", analysisId, thisUserId);
try {
recommendPositionService.recommendPosition(analysisId, thisUserId, resumeId);
} catch (Exception e) {
log.info("恢复岗位意向数据,错误信息:{}", e.toString());
aiAnalysisMapper.updateAnalysis(analysisId, orlStatus, thisUserId, 1);
}
}
}).start();
}
}
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.core.toolkit.Wrappers;
import com.bkty.system.domain.entity.AiPositionRecommendRecord;
import com.bkty.system.domain.entity.FunctionPositionPortraitV2;
import com.bkty.system.domain.vo.AiPositionRecommendRecordVo;
import com.bkty.system.domain.vo.RecommendItem;
import com.bkty.system.mapper.PositionRecommendMapper;
import com.bkty.system.service.jobRecommend.PositionRecommendRedisService;
import com.bkty.system.service.jobRecommend.RecommendPositionService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.utils.DateTimeWrapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author jiangxiaoge
* @description 岗位推荐Redis实现类
* @data 2025/3/28
**/
@Slf4j
@Service
@AllArgsConstructor
public class PositionRecommendRedisServiceImpl implements PositionRecommendRedisService {
private final PositionRecommendMapper positionRecommendMapper;
private final RedisTemplate<String, Object> redisTemplate;
private final RecommendPositionService recommendPositionService;
private static final String ALL_KEY = "recommend:%d:all";
private static final String NOT_READ_KEY = "recommend:%d:not_read";
private static final String FAVORITE_KEY = "recommend:%d:favorite";
private static final String NOT_SUITABLE_KEY = "recommend:%d:not_suitable";
private static final String DELIVERED_KEY = "recommend:%d:delivered";
private static final String RECOMMEND_ITEM_KEY = "recommend:%d:item:%d";
private static final String RECOMMEND_COUNT_KEY = "recommend:count:%d";//推荐次数
@Override
public boolean initRedisData(Long analysisId) {
// 1. 查询所有岗位推荐数据
QueryWrapper<AiPositionRecommendRecord> queryWrapper = Wrappers.query();
queryWrapper.select("id", "resume_id", "operation_type",
"treasures_type", "deliver_type", "city_code", "r_details", "position_score", "jd_md");
queryWrapper.eq("analysis_id", analysisId).eq("is_deleted", false);
List<AiPositionRecommendRecordVo> positions = positionRecommendMapper.selectPositionList(analysisId, null,null, null , null, null, null, null, null);
if (CollectionUtil.isEmpty(positions)){
//无岗位推荐数据
return false;
}
Map<String, List<AiPositionRecommendRecordVo>> queryGroup = positions.stream().collect(Collectors.groupingBy(AiPositionRecommendRecordVo::getCityCode));
List<FunctionPositionPortraitV2> portraitV2s = new ArrayList<>();
for (Map.Entry<String, List<AiPositionRecommendRecordVo>> entry : queryGroup.entrySet()) {
String cityCode = entry.getKey();
portraitV2s.addAll(recommendPositionService.queryPositionList(cityCode, entry.getValue().stream().map(AiPositionRecommendRecordVo::getPositionId).toList()));
}
Map<Long, FunctionPositionPortraitV2> collect = portraitV2s.stream().collect(Collectors.toMap(FunctionPositionPortraitV2::getPositionDataId, p -> p));
// 构造 Redis Key
String allKey = String.format(ALL_KEY, analysisId);
String notReadKey = String.format(NOT_READ_KEY, analysisId);
String favoriteKey = String.format(FAVORITE_KEY, analysisId);
String notSuitableKey = String.format(NOT_SUITABLE_KEY, analysisId);
String deliveredKey = String.format(DELIVERED_KEY, analysisId);
delRedisKey(allKey, notReadKey, favoriteKey, notSuitableKey, deliveredKey);
// 2. 遍历数据,将其存入 Redis
for (AiPositionRecommendRecordVo position : positions) {
savePositionToRedis(collect.get(position.getPositionId()), position, analysisId);
}
return true;
}
private void delRedisKey(String... keys){
redisTemplate.delete(List.of(keys));
}
@Override
public void savePositionToRedis(FunctionPositionPortraitV2 portraitV2, AiPositionRecommendRecordVo position, Long analysisId) {
// 构造 Redis Key
//String allKey = String.format(ALL_KEY, userId);
String notReadKey = String.format(NOT_READ_KEY, analysisId);
String favoriteKey = String.format(FAVORITE_KEY, analysisId);
String notSuitableKey = String.format(NOT_SUITABLE_KEY, analysisId);
String deliveredKey = String.format(DELIVERED_KEY, analysisId);
Long resumeId = position.getResumeId();
Long positionId = position.getId();
RecommendItem item = new RecommendItem();
item.setId(position.getId());
item.setCity(position.getCityCode());
item.setStandardPosition(portraitV2.getStandardPosition());
item.setWelfare(portraitV2.getWelfare());
item.setSalaryRange(portraitV2.getSalaryRange());
item.setCompanyName(portraitV2.getCompanyName());
item.setCompanyNature(portraitV2.getCompanyNature());
item.setCreateTime(DateTimeWrapper.parseFromDate(position.getRecommendTime(), null).toString(Constants.DATE_FULL_FORMAT));
item.setRDetails(position.getRDetails());
item.setPositionScore(position.getPositionScore());
//R<String> stringR = remoteResumeService.queryResumeName(resumeId);
item.setResumeName(position.getResumeName());
item.setJobTitle(position.getJobTitle());
item.setJdMd(position.getJdMd());
try {
item.setMaxPay(Integer.valueOf(portraitV2.getSalaryRange().split("-")[1]));
} catch (Exception e){
item.setMaxPay(0);
}
if (resumeId == null || positionId == null) {
return;
}
// 2.1 存入 "全部" 集合
long createTimeTimestamp = position.getCreateTime().getTime();
//redisTemplate.opsForZSet().add(allKey, position.getId(), createTimeTimestamp);
// 2.2 根据岗位状态存入对应的 Redis 集合
if (position.getPositionScore() == null || position.getPositionScore() == 0) {
redisTemplate.opsForZSet().add(notReadKey, position.getId(), createTimeTimestamp);
}
if (position.getPositionScore() != null && position.getPositionScore() > 4) {
redisTemplate.opsForZSet().add(favoriteKey, position.getId(), createTimeTimestamp);
}
if (position.getPositionScore() != null && position.getPositionScore() < 5) {
redisTemplate.opsForZSet().add(notSuitableKey, position.getId(), createTimeTimestamp);
}
if (position.getDeliverType() != null && position.getDeliverType() == 1) {
redisTemplate.opsForZSet().add(deliveredKey, position.getId(), createTimeTimestamp);
}
// 2.3 将 RecommendItem 对象转换为 JSON 字符串并存储到 Redis 中
try {
String jsonString = JSON.toJSONString(item);
redisTemplate.opsForValue().set(String.format(RECOMMEND_ITEM_KEY, analysisId, positionId), jsonString);
} catch (Exception e) {
e.printStackTrace();
}
}
}
\ No newline at end of file
package com.bkty.system.service.resume;
import com.bkty.system.domain.dto.*;
import com.bkty.system.domain.entity.FunctionResumeSketch;
import com.bkty.system.domain.vo.ResumeByPdfVo;
import com.bkty.system.domain.vo.ResumeModelVo;
import com.bkty.system.domain.vo.ResumeVo;
......@@ -120,4 +121,5 @@ public interface NewEditionResumeService {
*/
void reversalExperience(ResumeExpChangeDto dto) throws Exception;
}
package com.bkty.system.service.resume;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bkty.system.domain.entity.FunctionResumeBase;
import com.bkty.system.domain.entity.FunctionResumeSketch;
/**
* @author jiangxiaoge
* @description 简历制作Service
* @data 2024/12/16
**/
public interface ResumeMakeService extends IService<FunctionResumeBase> {
/**
* 查询简历分析详情
* @param resumeId
* @return
*/
FunctionResumeSketch queryResumeSketch(Long resumeId);
}
package com.bkty.system.service.resume.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bkty.system.domain.entity.FunctionResumeBase;
import com.bkty.system.domain.entity.FunctionResumeSketch;
import com.bkty.system.domain.vo.ResumeVo;
import com.bkty.system.mapper.FunctionResumeBaseMapper;
import com.bkty.system.mapper.FunctionResumeSketchMapper;
import com.bkty.system.service.coze.CozeApiService;
import com.bkty.system.service.resume.NewEditionResumeService;
import com.bkty.system.service.resume.ResumeMakeService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import dev.langchain4j.model.embedding.EmbeddingModel;
import org.dromara.common.core.config.LlmEmbeddingConfig;
import org.dromara.common.core.constant.CozeConstsnts;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* @author Zhang Wenbiao
* @description 简历制作service
* @datetime 2025/12/4 16:25
*/
@Slf4j
@AllArgsConstructor
@Service
public class ResumeMakeServiceImpl extends ServiceImpl<FunctionResumeBaseMapper, FunctionResumeBase> implements ResumeMakeService {
private final NewEditionResumeService newEditionResumeService;
private final CozeApiService cozeApiService;
private final FunctionResumeSketchMapper resumeSketchMapper;
@Override
public FunctionResumeSketch queryResumeSketch(Long resumeId) {
try {
ResumeVo join = newEditionResumeService.queryNewEditionResumeId(String.valueOf(resumeId)).join();
String resumeDescByCoze = getResumeDescByCoze(com.alibaba.fastjson2.JSON.toJSONString(join), "7506433655737630720");
EmbeddingModel embeddingModel = LlmEmbeddingConfig.getEmbeddingModel();
List<Float> dutyVector = embeddingModel.embed(resumeDescByCoze).content().vectorAsList();
QueryWrapper<FunctionResumeSketch> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("resume_id", resumeId).eq("is_deleted", false);
FunctionResumeSketch resumeSketch = new FunctionResumeSketch();
resumeSketch.setResumeId(resumeId);
resumeSketch.setSketch(resumeDescByCoze);
resumeSketch.setVectorQuantity(com.alibaba.fastjson2.JSON.toJSONString(dutyVector));
if (resumeSketchMapper.exists(queryWrapper)) {
UpdateWrapper<FunctionResumeSketch> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("resume_id", resumeId).eq("is_deleted", false)
.set("sketch", resumeDescByCoze).set("vector_quantity", com.alibaba.fastjson2.JSON.toJSONString(dutyVector));
resumeSketchMapper.update(null, updateWrapper);
} else {
resumeSketchMapper.insert(resumeSketch);
}
return resumeSketch;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private String getResumeDescByCoze(String sendData, String botId) throws Exception{
String cozeToken = cozeApiService.getCozeToken();
// 构建请求头
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put(HttpHeaders.AUTHORIZATION, CozeConstsnts.COZE_TOKEN_BEARER + cozeToken);
List<Map<String, Object>> additionalMessages = new ArrayList<>();
Map<String, Object> content = Map.of("content_type", "text", "content", sendData, "role", "user");
additionalMessages.add(content);
Map<String, Object> body = Map.of("bot_id", botId,
"user_id", "9527",
"stream", false,
"additional_messages", additionalMessages);
String uri = cozeApiService.getCozeDomainUri() + "/v3/chat";
// 使用 Hutool 发送 POST 请求
try {
String jsonBody = com.alibaba.fastjson2.JSON.toJSONString(body);
cn.hutool.http.HttpRequest request = cn.hutool.http.HttpRequest.post(uri)
.addHeaders(headers)
.body(jsonBody)
.timeout(30000); // 设置超时时间 30 秒
cn.hutool.http.HttpResponse response = request.execute();
String responseBody = response.body();
Map<String, Object> map = com.alibaba.fastjson2.JSON.parseObject(responseBody, Map.class);
String code = Objects.toString(map.get("code"), "");
if (code.equals("0")) {
Map<String, Object> data = (Map<String, Object>) map.get("data");
String status = Objects.toString(data.get("status"), "");
boolean inProgress = status.equals("in_progress");
while (inProgress) {
String retrieveUri = cozeApiService.getCozeDomainUri() + "/v3/chat/retrieve?chat_id=%s&conversation_id=%s".formatted(data.get("id").toString(), data.get("conversation_id").toString());
// 使用 Hutool 发送 GET 请求
cn.hutool.http.HttpRequest retrieveRequest = cn.hutool.http.HttpRequest.get(retrieveUri)
.addHeaders(headers)
.timeout(30000);
cn.hutool.http.HttpResponse retrieveResponse = retrieveRequest.execute();
String retrieveResponseBody = retrieveResponse.body();
System.out.println(retrieveResponseBody);
Map<String, Object> mapData = com.alibaba.fastjson2.JSON.parseObject(retrieveResponseBody, Map.class);
if (!"0".equals(mapData.get("code").toString())) {
break;
}
Map<String, Object> rData = (Map<String, Object>) mapData.get("data");
String status1 = rData.get("status").toString();
inProgress = status1.equals("in_progress");
if (status1.equals("completed")) {
String messageListUri = cozeApiService.getCozeDomainUri() + "/v3/chat/message/list?chat_id=%s&conversation_id=%s".formatted(data.get("id").toString(), data.get("conversation_id").toString());
// 使用 Hutool 发送 GET 请求
cn.hutool.http.HttpRequest messageListRequest = cn.hutool.http.HttpRequest.get(messageListUri)
.addHeaders(headers)
.timeout(30000);
cn.hutool.http.HttpResponse messageListResponse = messageListRequest.execute();
String messageListResponseBody = messageListResponse.body();
com.alibaba.fastjson2.JSONObject jsonObject = com.alibaba.fastjson2.JSON.parseObject(messageListResponseBody);
if (jsonObject != null) {
if (jsonObject.getInteger("code") == 0) {
com.alibaba.fastjson2.JSONArray jsonArray = jsonObject.getJSONArray("data");
for (int i = 0; i < jsonArray.size(); i++) {
com.alibaba.fastjson2.JSONObject thisJsonObject = jsonArray.getJSONObject(i);
if (thisJsonObject.getString("type").equals("answer")) {
String contentString = thisJsonObject.getString("content");
contentString = contentString.replace("```json","" );
contentString = contentString.replace("```", "");
log.info("执行成功,简历信息:{}\n描述信息:{}", data, contentString);
return contentString;
}
}
}
}
}
Thread.sleep(1000);
}
}
}catch (Exception e) {
log.error("发送HTTP请求时发生异常: ", e);
throw new Exception("请求发送失败: " + e.getMessage(), e);
}
return null;
}
}
package com.bkty.system.utils;
import java.sql.*;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.HashMap;
public class MariaDbAdtUtil {
// 数据库连接配置
//private static final String JDBC_URL = "jdbc:mariadb://127.0.0.1:33066/gov_datacenter";
private static final String JDBC_URL = "jdbc:mariadb://192.168.1.218:3306/gov_datacenter";
private static final String JDBC_USERNAME = "wangwenjiang";
private static final String JDBC_PASSWORD = "Q8$l63I6-iijE3^9";
/**
* 获取 MariaDB 数据库连接
*/
private static Connection getConnection() throws SQLException {
return DriverManager.getConnection(JDBC_URL, JDBC_USERNAME, JDBC_PASSWORD);
}
/**
* 根据ID查询recruit_qqxb表,返回单条记录的Map表示
*
* @param id 要查询的记录ID
* @return 包含记录字段的Map,如果未找到记录则返回null
*/
public static Map<String, Object> queryRecruitQqxbById(long id) {
String query = "SELECT * FROM recruit_qqxb WHERE id = ?";
int maxRetries = 3;
for (int attempt = 0; attempt < maxRetries; attempt++) {
try (Connection connection = getConnection();
PreparedStatement statement = connection.prepareStatement(query)) {
statement.setLong(1, id);
statement.setQueryTimeout(10); // 设置查询超时为10秒
try (ResultSet resultSet = statement.executeQuery()) {
if (resultSet.next()) {
return resultSetToMap(resultSet);
}
return null;
}
} catch (SQLException e) {
if (attempt == maxRetries - 1) {
throw new RuntimeException("查询recruit_qqxb表 id:"+ id +"失败: " + e.getMessage(), e);
}
try {
Thread.sleep(1000); // 等待1秒后重试
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("查询被中断", ie);
}
}
}
return null;
}
/**
* 将ResultSet当前行转换为Map<String, Object>
*
* @param resultSet 查询结果集
* @return 包含当前行数据的Map
* @throws SQLException SQL异常
*/
private static Map<String, Object> resultSetToMap(ResultSet resultSet) throws SQLException {
Map<String, Object> resultMap = new HashMap<>();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnLabel(i);
Object value = resultSet.getObject(i);
resultMap.put(columnName, value);
}
return resultMap;
}
/**
* 查询并返回游标迭代器
*/
public static Iterable<Map.Entry<Integer, String>> queryWithCursor(String query) {
return () -> new Iterator<>() {
private Connection connection;
private PreparedStatement statement;
private ResultSet resultSet;
private boolean isNextAvailable;
{
try {
// 初始化数据库连接和查询
connection = getConnection();
statement = connection.prepareStatement(query, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
statement.setFetchSize(Integer.MIN_VALUE); // 启用游标模式
resultSet = statement.executeQuery();
isNextAvailable = resultSet.next(); // 移动到第一条记录
} catch (SQLException e) {
close(); // 出现异常时清理资源
throw new RuntimeException("查询执行失败: " + e.getMessage(), e);
}
}
@Override
public boolean hasNext() {
return isNextAvailable;
}
@Override
public Map.Entry<Integer, String> next() {
if (!isNextAvailable) {
throw new NoSuchElementException("没有更多数据");
}
try {
// 读取 position_id 和 position_portrayal 字段
int positionId = resultSet.getInt("position_id");
String positionPortrayal = resultSet.getString("position_portrayal");
isNextAvailable = resultSet.next(); // 移动到下一条记录
return new HashMap.SimpleEntry<>(positionId, positionPortrayal);
} catch (SQLException e) {
throw new RuntimeException("获取下一条数据失败: " + e.getMessage(), e);
}
}
/**
* 关闭资源
*/
private void close() {
try {
if (resultSet != null) resultSet.close();
if (statement != null) statement.close();
if (connection != null) connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
protected void finalize() throws Throwable {
close();
super.finalize();
}
};
}
public static void main(String[] args) {
/*String query = "SELECT position_id, position_portrayal FROM function_position_portrayal WHERE is_deleted = false";
for (Map.Entry<Integer, String> entry : queryWithCursor(query)) {
System.out.println("Position ID: " + entry.getKey() + ", Position Portrayal: " + entry.getValue());
// 在这里处理每条数据
}*/
// 新增功能测试
long idToQuery = 38243537L;
Map<String, Object> result = queryRecruitQqxbById(idToQuery);
if (result != null) {
System.out.println("查询结果:");
result.forEach((key, value) -> System.out.println(key + ": " + value));
} else {
System.out.println("未找到ID为 " + idToQuery + " 的记录");
}
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bkty.system.mapper.AiAnalysisMapper">
<update id="updateAnalysis">
update ai_recommend_base set
recommend_status = #{rType}
<if test="authRecommend != null">
, auth_recommend = #{authRecommend}
</if>
where id = #{analysisId}
</update>
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bkty.system.mapper.PositionRecommendMapper">
<select id="selectPositionList" resultType="com.bkty.system.domain.vo.AiPositionRecommendRecordVo">
SELECT
t1.*, t2.resume_name as resumeName
FROM
ai_position_recommend_record t1, function_resume_base t2
WHERE
t1.is_deleted = 0 and
t1.analysis_id = #{analysisId} and
t1.resume_id = t2.id
<if test="queryType == 1 and recommendTime == null">
AND t1.recommend_time = (SELECT MAX(recommend_time) FROM ai_position_recommend_record WHERE analysis_id = #{analysisId} AND is_deleted = 0)
ORDER BY t1.r_score DESC
</if>
<if test="queryType == 1 and recommendTime != null">
AND t1.recommend_time = #{recommendTime}
ORDER BY t1.r_score DESC
</if>
<if test="queryType == 0">
AND t1.operation_type = 1
<if test="deliverType != null">
AND t1.treasures_type != 2
</if>
<choose>
<when test="sortField != null and sortField != ''">
ORDER BY t1.${sortField} ${sortType}
</when>
<otherwise>
ORDER BY t1.update_time DESC
</otherwise>
</choose>
</if>
<if test="queryType == 2">
AND t1.treasures_type = 1
order by t1.create_time desc
</if>
<if test="queryType == 3">
AND t1.treasures_type = 2
order by t1.create_time desc
</if>
<if test="queryType == 4">
AND t1.deliver_type = 1
order by t1.create_time desc
</if>
<if test="pId != null">
AND t1.id = #{pId}
</if>
<if test="start != null">
limit #{start}, #{size}
</if>
</select>
</mapper>
\ No newline at end of file
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