Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
张文彪
/
employmentBusinessPc
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
14c56833
authored
Dec 05, 2025
by
zwb
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
新增岗位智推
parent
01791790
Hide whitespace changes
Inline
Side-by-side
Showing
41 changed files
with
3469 additions
and
53 deletions
employmentBusiness-pc-common/employmentBusiness-pc-common-core/pom.xml
employmentBusiness-pc-common/employmentBusiness-pc-common-core/src/main/java/org/dromara/common/core/config/LlmEmbeddingConfig.java
employmentBusiness-pc-common/employmentBusiness-pc-common-core/src/main/java/org/dromara/common/core/config/QwenClientSingleton.java
employmentBusiness-pc-common/employmentBusiness-pc-common-core/src/main/java/org/dromara/common/core/constant/CacheConstants.java
employmentBusiness-pc-common/employmentBusiness-pc-common-core/src/main/java/org/dromara/common/core/constant/Constants.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/pom.xml
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/config/RedisConfig.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/controller/JobRecommendController.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/controller/NewEditionResumeController.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/entity/AiPositionRecommendRecord.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/entity/FunctionPositionPortraitV2.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/entity/JobEvaluation.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/entity/MatchResult.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/entity/ProcessItem.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/vo/AiPositionRecommendRecordVo.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/vo/AiRecommendVo.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/vo/FunctionPositionPortraitVo.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/vo/RecommendItem.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/vo/RerankVo.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/employmentBusinessPCSystemApplication.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/init/CategoryCacheManager.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/llm/JdWriter.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/mapper/AiAnalysisMapper.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/mapper/CityDataMapper.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/mapper/PositionRecommendMapper.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/coze/CozeApiService.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/coze/impl/CozeApiServiceImpl.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/AiAnalysisService.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/JobRecommendService.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/PositionRecommendRedisService.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/RecommendPositionService.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/impl/AiAnalysisServiceImpl.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/impl/JobRecommendServiceImpl.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/impl/PositionRecommendRedisServiceImpl.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/impl/RecommendPositionServiceImpl.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/resume/NewEditionResumeService.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/resume/ResumeMakeService.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/resume/impl/ResumeMakeServiceImpl.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/utils/MariaDbAdtUtil.java
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/resources/mapper/system/AiAnalysisMapper.xml
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/resources/mapper/system/PositionRecommendMapper.xml
employmentBusiness-pc-common/employmentBusiness-pc-common-core/pom.xml
View file @
14c56833
...
@@ -138,6 +138,20 @@
...
@@ -138,6 +138,20 @@
<artifactId>
fastjson2
</artifactId>
<artifactId>
fastjson2
</artifactId>
<version>
2.0.43
</version>
<version>
2.0.43
</version>
</dependency>
</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>
</dependencies>
</project>
</project>
employmentBusiness-pc-common/employmentBusiness-pc-common-core/src/main/java/org/dromara/common/core/config/LlmEmbeddingConfig.java
0 → 100644
View file @
14c56833
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
;
}
}
employmentBusiness-pc-common/employmentBusiness-pc-common-core/src/main/java/org/dromara/common/core/config/QwenClientSingleton.java
0 → 100644
View file @
14c56833
package
org
.
dromara
.
common
.
core
.
config
;
import
com.alibaba.fastjson2.JSON
;
import
com.alibaba.fastjson2.JSONObject
;
import
okhttp3.*
;
import
java.io.IOException
;
import
java.util.List
;
import
java.util.Map
;
public
class
QwenClientSingleton
{
// 单例 OkHttpClient
private
static
final
OkHttpClient
client
;
static
{
Dispatcher
dispatcher
=
new
Dispatcher
();
dispatcher
.
setMaxRequests
(
100
);
// 全局最大并发请求数
dispatcher
.
setMaxRequestsPerHost
(
50
);
// 单host最大并发请求数
client
=
new
OkHttpClient
.
Builder
()
.
dispatcher
(
dispatcher
)
.
connectionPool
(
new
ConnectionPool
(
32
,
30
,
java
.
util
.
concurrent
.
TimeUnit
.
MINUTES
))
// 连接池配置
.
connectTimeout
(
30
,
java
.
util
.
concurrent
.
TimeUnit
.
SECONDS
)
// 连接超时
.
readTimeout
(
60
,
java
.
util
.
concurrent
.
TimeUnit
.
SECONDS
)
// 读取超时
.
writeTimeout
(
60
,
java
.
util
.
concurrent
.
TimeUnit
.
SECONDS
)
// 写入超时
.
retryOnConnectionFailure
(
true
)
// 连接失败时重试
.
build
();
}
// 配置参数
private
final
String
baseUrl
;
private
final
String
apiKey
;
private
final
String
model
;
// 单例
private
static
volatile
QwenClientSingleton
instance
;
private
QwenClientSingleton
(
String
baseUrl
,
String
apiKey
,
String
model
)
{
this
.
baseUrl
=
baseUrl
;
this
.
apiKey
=
apiKey
;
this
.
model
=
model
;
}
public
static
QwenClientSingleton
getInstance
(
String
baseUrl
,
String
apiKey
,
String
model
)
{
if
(
instance
==
null
)
{
synchronized
(
QwenClientSingleton
.
class
)
{
if
(
instance
==
null
)
{
instance
=
new
QwenClientSingleton
(
baseUrl
,
apiKey
,
model
);
}
}
}
return
instance
;
}
// 核心调用方法
public
String
chat
(
String
systemMessage
,
String
userMessage
)
throws
IOException
{
JSONObject
bodyJson
=
new
JSONObject
(
Map
.
of
(
"model"
,
model
,
"messages"
,
List
.
of
(
Map
.
of
(
"role"
,
"system"
,
"content"
,
systemMessage
),
Map
.
of
(
"role"
,
"user"
,
"content"
,
userMessage
)
),
"temperature"
,
0.0
,
"top_p"
,
1.0
,
"chat_template_kwargs"
,
Map
.
of
(
"enable_thinking"
,
false
)
));
Request
request
=
new
Request
.
Builder
()
.
url
(
baseUrl
+
"/v1/chat/completions"
)
.
addHeader
(
"Content-Type"
,
"application/json"
)
.
addHeader
(
"Authorization"
,
"Bearer "
+
apiKey
)
.
post
(
RequestBody
.
create
(
bodyJson
.
toJSONString
(),
MediaType
.
get
(
"application/json; charset=utf-8"
)))
.
build
();
try
(
Response
response
=
client
.
newCall
(
request
).
execute
())
{
if
(!
response
.
isSuccessful
())
{
throw
new
IOException
(
"Unexpected code "
+
response
);
}
JSONObject
respJson
=
JSON
.
parseObject
(
response
.
body
().
string
());
return
respJson
.
getJSONArray
(
"choices"
)
.
getJSONObject
(
0
)
.
getJSONObject
(
"message"
)
.
getString
(
"content"
);
}
}
public
static
void
main
(
String
[]
args
)
throws
IOException
{
QwenClientSingleton
instance
=
QwenClientSingleton
.
getInstance
(
"https://xzt-llm-dev.jinsehuaqin.com"
,
"token-abc123"
,
"/mnt/app/llm/Qwen3/Qwen/Qwen3-8B"
);
String
llmRes
=
instance
.
chat
(
SYSTEM_MESSAGE
,
USER_MESSAGE
);
System
.
out
.
println
(
llmRes
);
}
static
String
USER_MESSAGE
=
"""
[
{
"
id
": 37856490,
"
COMPANY_ID
": 175721181,
"
COMPANY_NAME
": "
北京博远育学文化发展有限公司
",
"
COMPANY_SHORT_NAME
": null,
"
INDUSTRY_CODE
": null,
"
INDUSTRY_NAME
": null,
"
PUBLISH_DATE
": "
2025
-
09
-
16
",
"
START_DATE
": null,
"
END_DATE
": null,
"
COUNTRY
": "
中国
",
"
CITY_CODE
": "
101251100
",
"
CITY_NAME
": "
湖南省
|
张家界市
",
"
POSITION_CODE
": null,
"
POSITION_NAME
": null,
"
POSITION_NAME1
": null,
"
POSITION_NAME2
": null,
"
POSITION_NAME3
": null,
"
JOB_NAME
": "
远程兼职,单单奖,初高中线上解题
",
"
JOB_SALARY
": "
40
-
50
元
/
时
",
"
JOB_EDUCATION
": "
大专
",
"
JOB_EXPERIENCE
": "
经验不限
",
"
JOB_LABEL
": null,
"
JOB_DESCRIPTION
": "
【工作内容】工作周期长期兼职每周工期无要求工作时间不限工作时段不限结算方式月结招聘截止时间
202603071
工作内容在
app
上录制你所学学科的解题视频题目随机目前
k12
全科都在招
2
硬性要求需要有教师资格证语数英物化生科目不必须对应学科有就行
2001
年及以前出生有设备电脑
+
手写板或者平板
+
电容笔
3
基本薪资上传成功一题
6.810
元
+
单单奖综合
1030
/
题按月结算
4
工作时间不限制有时间就可以做没时间就可以不做
5
工作地点不限制但是工作环境需要安静录制不需要露脸【任职要求】
",
"
JOB_NUMBER
": null,
"
COMPANY_ZONE
": "
湖南省
|
张家界市
",
"
COMPANY_DETAIL_INFO
": null,
"
COMPANY_WEBSITE
": null,
"
COMPANY_LOCATION
": "
张家界桑植县桑植县德兴高级中学
1
",
"
COMPANY_LOGO_ADDRESS
": null,
"
DATA_SOURCES
": "
boss
直聘
",
"
URL_LINK
": "
https:
//www.zhipin.com/job_detail/bdeef9f132b6182903Fy39u0EVNR.html",
"CONTACT"
:
null
,
"STAFF_SIZE"
:
null
,
"JOB_NATURE"
:
null
,
"JOB_AGE"
:
null
,
"JOB_SEX"
:
null
,
"JOB_TIME"
:
null
,
"JOB_WELFARE"
:
null
,
"REMARK"
:
null
,
"DESC01"
:
"boss直聘"
,
"DESC02"
:
null
,
"UPDATE_DATE"
:
"2025-09-16"
,
"ORIG_ID"
:
791561870
,
"USE_FLAG"
:
0
,
"PUBLISH_TIME"
:
"2025-09-14 00:00:00"
,
"COLLECTOR_NAME"
:
null
,
"COLLECT_TIME"
:
null
,
"COLLECT_FLAG"
:
0
,
"CLEANER_NAME"
:
null
,
"CLEAN_TIME"
:
null
,
"CLEAN_FLAG"
:
0
,
"UPDATE_REASON"
:
null
,
"TIME_STAMP"
:
"2025-09-18 07:33:21.373032"
,
"UNIQUE_MD5"
:
"5909dca2fb38d68bec8a8a4cb1d17f8f"
,
"VERSION_ID"
:
0
}
]
""";
static String SYSTEM_MESSAGE = """
-
Carefully
consider
the
user
'
s
question
to
ensure
your
answer
is
logical
and
makes
sense
.
-
Make
sure
your
explanation
is
concise
and
easy
to
understand
,
not
verbose
.
-
Strictly
return
the
answer
in
json
format
.
-
Strictly
Ensure
that
the
following
answer
is
in
a
valid
JSON
format
.
-
The
output
should
be
formatted
as
a
JSON
instance
that
conforms
to
the
JSON
schema
below
and
do
not
add
comments
.
Here
is
the
output
schema:
'''
{
"jobName"
:
string
,
// 岗位名称
"standardPosition"
:
string
,
// 岗位标准名称
"standardJobDesc"
:
string
,
// 岗位标准描述
"workPlace"
:
List
[
string
],
// 工作地点。只允许省-市,直辖市-直辖市,全国-全国这三种格式,禁止到市以下级别。
"isIntern"
:
string
,
// 是否是实习岗位。只能是"是"或者"否"。
"salaryConversionProcess"
:
string
,
// 薪资转换为月薪的过程。转换时,如果没有说明工作时间,按照默认每天工作8小时,每月工作24天进行计算。即时薪需要*8*24计算,日薪需要*24进行计算。年薪需要÷12进行计算。
"salaryRange"
:
string
,
// 薪资范围,需将日薪、年薪等转换为月薪,并按从小到大的自然数格式解析,例如“8000-12000”;无法解析则标记为“未知”
"graduate"
:
string
,
// 分析整理岗位是否适合应届生。只能是"合适"或者"不合适"。
"graduateContent"
:
string
,
// 分析整理岗位是否适合应届生的内容描述。例如:如果是合适,则说明为什么合适;如果不合适,则说明为什么不合适
"sketch"
:
string
,
// 岗位语义描述,例如:工作内容、任职资格等。
"experienceType"
:
string
,
// 分析岗位要求的经验最低年限要求的经验年数的最低值<=3年为“否”,>3年为“是”,仅允许“是”或“否”两种取值,不能为空;若无经验要求,则设为“否”。只能输出"是"或者"否"。
"welfare"
:
string
// 岗位福利,例如五险一金、餐补等。
}
'''
#
技能
##
技能
1
:理解岗位内容
当用户告诉你岗位相关内容时,可以使用此技能对岗位数据进行解析理解。
-
用户可能通过对话或者传入
json
文件的方式告诉给你岗位相关的数据信息,你应该都能理解他的意思。为了达到这样一个效果,我会告诉你,他使用的数据字段对应的释义,这样无论他是使用对话还是使用
json
格式数据文件,你都能理解他的意思。
-
具体的字段释义如下:
id:
主键
ID
company_id:
企业
ID
company_name:
企业名称
company_short_name:
企业简称
industry_code:
行业编码
industry_name:
行业名称
publish_date:
发布日期
start_date:
开始时间
end_date:
结束时间
country:
国家
city_code:
城市代码
city_name:
城市名称
position_code:
职位代码
position_name:
职位名称
job_name:
岗位名称
job_salary:
工资范围
job_education:
学历要求
job_experience:
工作经验要求
job_label:
工作标签(技能、岗位特性等)
job_description:
岗位描述
job_number:
工作编号
company_zone:
公司所在区域
company_detail_info:
公司详细信息
company_website:
公司官网网址
company_location:
公司地址
company_logo_address:
公司
logo
地址
data_sources:
数据来源
url_link:
岗位链接地址
remark:
备注
update_date:
更新时间
orig_id:
来源
ID
use_flag:
使用标记(是否有效)
publish_time:
发布时间
collector_name:
采集人姓名
collect_time:
采集时间
collect_flag:
采集标记
cleaner_name:
清理人姓名
clean_time:
清理时间
clean_flag:
清理标记
update_reason:
更新原因
time_stamp:
时间戳
version_id:
版本
ID
portrayal_type:
画像状态(
0
-
未生成,
1
-
已生成)
#
确定岗位名称
jobName
岗位名称,直接读取输入数据中的
jobname
即可
#
确定标准岗位分类
standardPosition
从输入信息中的岗位名称以及岗位工作内容描述综合判断,给出一个一般性描述的具体岗位名称。
#
标准岗位分类描述
standardJobDesc
标准岗位分类描述,请根据给理解到的的岗位名称(
jobName
)和岗位相关信息(例如:
job_label
、
job_description
),生成标准化的岗位语义描述。
岗位语义描述应
**
清晰概括岗位的核心职责和必备技能
**
,并确保表述通用性,适用于我们理解对该岗位的需求。
描述整体情注意不超过
50
字。其中对于技能的要求要精确到具体的技能名称,岗位描述中可能会缺少这部分具体技能名称的描述,你可以根据你自己的能力(泛化的经验)去自动联想一些
**
经验要求和技能要求的内容
**
。
例如:
"jobName"
:
"业务数据分析专家"
"standardJobDesc"
:
"该岗位主要负责业务数据的处理、建模、报表可视化,使用 Python,SQL,BI工具支持业务增长。"
#
判断是否是实习岗位
isIntern
判断岗位是否为实习岗位,如果是则返回“是”,否则返回“否”。
只有当岗位名称中含有“实习”二字,或者岗位描述中提及了类似“实习生”、“实习期”等字眼时才认为是实习岗位并返回“是”。否则认为该岗位不是实习岗位,返回“否”。
#
确定工作地城市信息
workPlace
请结合
cityname
以及你理解的岗位整体数据信息去理解工作城市所在信息。
这里应该是一个数组,因为工作城市可能有多个,数组中的每项内容的格式应为“省
-
市”(直辖市格式为“市
-
市”、全国范围则为“全国
-
全国”)。
**
请注意,这里的数据格式请使用
AA
-
BB
,连接符不要变化,同时,请注意,不需要解析到区县级,只需要省
-
市,直辖市
-
直辖市,全国
-
全国这
3
个中的一个
**
#
薪资转换过程
salaryConversionProcess
薪资转换过程,请将日薪、年薪等转换为月薪的过程记录在此字段中。例如:
"salaryConversionProcess"
:
"原始薪资范围为 300/天 至 400/天,转换为月薪即 (300*30)至(400*30),得到结果为9000到12000。"
"salaryRange"
:
"15000-20000"
,
1
.
当原始薪资是日薪时,按照工作时间进行计算,未明确工作时间时,按照每月
24
天进行计算。
2
.
当原始薪资是年薪时,按照每年
12
个月进行计算。
3
.
当原始薪资是时薪时,按照工作时间进行计算,未明确工作时间时,按照每天工作
8
小时,每月工作
24
天进行计算。
#
确定薪资范围
salaryRange
将日薪、年薪等转换为月薪,并按从小到大的自然数格式解析,例如“
8000
-
12000
”;无法解析则标记为“未知”
**
请注意,不管接收到的数据有没有其他的表达方式如
7
k
,
1
w
这种,都请按照要求转化为具体数值
-
具体数值,连接符也不要进行转化
**
**
同时,薪资范围必须是整千数字,非整千的数字向下取整
**
#
技能
2
:分析整理岗位要求技能
&
经验
分析岗位对应要求的技能和经验。
请你完整的理解岗位整体意思,并按照
**
工作经验要求
**
与
**
工作技能要求
**
这
2
个维度去提取对应的内容。
当你得到的岗位信息特别少时,你可以根据你自己的能力(泛化的经验)去自动联想一些
**
经验要求和技能要求的内容
**
。
**
同时,为了确保不要过度联想导致后期的分析内容失真
**
,请你确保仅在如下几个情况时才发挥你的聪明才智去进行信息联想补充:
-
岗位描述缺乏具体技能要求
-
没有提及经验要求(如“
XX
年经验”)
-
岗位职责含糊,不明确具体做什么
#
分析整理岗位是否适合应届生
graduate
不适合应届毕业生的工作岗位通常具有以下特征或以下特征之一:
1
.
**
经验门槛过高
**
-
明确要求
3
-
5
年工作经验或独立负责核心业务(如高级项目经理)。
2
.
**
高压或高风险岗位
**
-
业绩考核严苛(如销售岗末位淘汰制)、法律
/
财务责任重大(如风控总监)。
3
.
**
缺乏成长性
**
-
重复性劳动(如纯数据录入)、无技能提升空间。
4
.
**
职业路径模糊
**
-
岗位职责混乱(如“全能助理”兼保洁、司机),无明确发展方向。
5
.
**
短期无保障
**
-
岗位描述中出现“日结”“兼职”“短期”等表示该岗位非长期职业。
适合应届毕业生的工作岗位通常具有以下特征:
1
.
**
学习与成长空间
**
-
提供系统培训或
mentorship
(导师制),帮助新人快速适应职场。
-
工作内容包含基础技能锻炼(如沟通、项目管理),并有明确的晋升路径。
2
.
**
行业与岗位特性
**
-
对经验要求较低:如行政助理、市场专员、初级程序员、管培生等。
-
新兴行业(如互联网、新能源、
AI
)更愿意接纳新人,包容试错。
2
.
**
专业对口度
**
-
与大学专业相关,能快速应用理论知识(如医学、工程类、技术类岗位)。
#
给出是否适合应届生的理由
graduateContent
适合或不适合应届生的理由,如果合适,需要说明原因且需要说出明确的晋升途径。
#
语义化描述工作岗位
sketch
你要理解,我们之所以要进行这块的内容,是因为我们最终是需要将这份岗位转义成一段语义内容,能够被向量模型所理解,这样才能让我们后续的工作能继续下去。所以我们
**
绝不仅仅只是将岗位拆分成技能和经验的关键词
**
!!!同时,通过语义的描述可以让模型能够更精准的理解这个岗位的要求,提高后续工作的准确性。
请注意,你需要使用“这个岗位需要
xxxx
技能,需要
xxx
经验”
这种语义描述方式来进行语义转化。
**
需要注意的事情是,我不需要多段落的内容,应该是将其整合在一起。
**
为了确保你能理解我的意思,并输出我想要的内容,你可以参考下我给你的例子:
示例:
背景:
岗位名称:电子工程师
得到的岗位数据理解后内容如下:
1
熟练电路设计,懂得信号处理,
2
精通
c
,
c
++,
具有单片机,
smt32
开发经验,
3
有独立设计和测试项目经验
分析后内容如下:
工作技能要求
电路设计(模拟电路、数字电路)
信号处理(滤波、采样、信号放大)
嵌入式开发(
C
/
C
++
、单片机、
STM32
、
RTOS
)
硬件测试(示波器、逻辑分析仪、
JTAG
调试)
PCB
设计(
Altium
Designer
、
KiCad
、
OrCAD
)
通信协议(
UART
、
SPI
、
I2C
、
CAN
)
硬件调试(
Keil
、
IAR
、
SWD
)
工作经验要求
电路设计经验
2
-
3
年嵌入式开发经验
单片机(
STM32
)开发
独立设计和测试项目
硬件产品研发经验
硬件调试经验
输出:
sketch
:
这个岗位需要熟练的电路设计能力,包括模拟电路和数字电路的设计,能够进行信号处理(滤波、采样、放大)。候选人需要精通
C
和
C
++
语言,具备单片机开发经验,特别是
STM32
平台的开发。需要有嵌入式开发经验,包括
RTOS
和硬件驱动编写。候选人还需要具备硬件测试能力,能够使用示波器、逻辑分析仪进行调试,并有
2
-
3
年的相关工作经验,能够独立设计和测试嵌入式电子项目。
#
分析是否需要
3
年以上工作经验
experienceType
jobExperience
中要求的经验年数的最低值
<=
3
年为“否”,
>
3
年为“是”,仅允许“是”或“否”两种取值,不能为空;若无经验要求,则设为“否”)
#
分析该岗位是否有额外福利
benefits
从
jobDescription
字段中提取除薪资、奖金外其他的福利,例如法定假日外额外假期、下午茶、节日礼品等,总结简化输出为字符串数组。
**
需要注意,这里只允许总结原始数据中的真实数据,请你不要做任何添加和调整,不存在额外福利或不能判断时将此字段空置。
**
##
输出要求
-
请直接分析以下岗位信息并输出标准格式的
JSON
结果,不要包含任何解释、推理过程或思考步骤,直接给出最终结果。
-
请严格遵循你在最后阶段以
json
格式输出对应的内容,且无需添加多余信息,请一定记住,你更多是以
api
形式与后端应用发出的请求进行交互,高质量的稳定输出
json
格式的岗位画像是最合适的。
-
禁止在
JSON
外输出任何解释性文字。
##
输出示例
示例一:
```
json
{
"jobName"
:
"物流员"
,
"standardPosition"
:
"物流客服"
,
"standardJobDesc"
:
"该岗位负责处理物流订单查询、异常跟踪及客户沟通,需具备良好的服务意识和物流系统操作能力。"
,
"workPlace"
:
[
"广东省-广州市"
,
"广东省-佛山市"
,
"全国-全国"
],
"isIntern"
:
"否"
,
"salaryConversionProcess"
:
"年薪10万转换为月薪8333元"
,
"salaryRange"
:
"4000-7000"
,
"requirement"
:
"无要求"
,
"graduate"
:
"不合适"
,
"graduateContent"
:
"岗位缺乏成长性,属于重复性劳动,且职业路径模糊,无明确发展方向。"
,
"sketch"
:
"这里是转化的语义描述"
,
"experienceType"
:
"是"
,
"welfare"
:
[
"下午茶"
,
"节假日礼品市"
,
"五天以上年假"
]
}
```
示例二:
```
json
{
"jobName"
:
"速派出行招聘网约车司机 8000-1.2万元"
,
"standardPosition"
:
"网约车司机"
,
"standardJobDesc"
:
"该岗位负责安全驾驶、接送乘客,需熟悉导航系统,具备良好服务意识和驾驶技能。"
,
"workPlace"
:
[
"广东省-佛山市"
],
"isIntern"
:
"否"
,
"salaryConversionProcess"
:
"日薪800-1200转换为月薪24000元"
,
"salaryRange"
:
"24000-36000"
,
"graduate"
:
"不合适"
,
"graduateContent"
:
"岗位要求高中及以上学历,1-3年驾龄即可,工作时间自由安排,但岗位缺乏成长性,属于重复性劳动,且职业路径模糊,无明确发展方向。"
,
"sketch"
:
"这个岗位需要持有C1驾驶证和网络预约出租汽车驾驶员证,驾龄3年以上,近3个记分周期内未一次性扣满12分。候选人需身体健康,无犯罪、酒驾、吸毒记录。工作内容为网约车驾驶服务,薪酬按每日流水500-800元计算,多劳多得,工资日结。工作时间自由安排,可兼职或全职。"
,
"experienceType"
:
"否"
,
"welfare"
:
[
"工资日结"
,
"工作时间自由安排"
,
"可兼职/全职"
]
}
```
""
"
;
}
employmentBusiness-pc-common/employmentBusiness-pc-common-core/src/main/java/org/dromara/common/core/constant/CacheConstants.java
View file @
14c56833
...
@@ -85,4 +85,7 @@ public interface CacheConstants {
...
@@ -85,4 +85,7 @@ public interface CacheConstants {
* 下载使用简历对应模版缓存pdf
* 下载使用简历对应模版缓存pdf
*/
*/
String
RESUME_TEMPLATE_PDF_ID
=
"template:resume:pdf:download:%s"
;
String
RESUME_TEMPLATE_PDF_ID
=
"template:resume:pdf:download:%s"
;
/**分析职业期望数据*/
String
REDIS_USER_ANALYSIS_KEY
=
"user:analysis:%s"
;
}
}
employmentBusiness-pc-common/employmentBusiness-pc-common-core/src/main/java/org/dromara/common/core/constant/Constants.java
View file @
14c56833
...
@@ -82,8 +82,5 @@ public interface Constants {
...
@@ -82,8 +82,5 @@ public interface Constants {
/** 全字段时间格式化字符串模式:yyyy-MM-dd HH:mm:ss */
/** 全字段时间格式化字符串模式:yyyy-MM-dd HH:mm:ss */
public
static
final
String
DATE_FULL_FORMAT
=
"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"
;
}
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/pom.xml
View file @
14c56833
...
@@ -151,6 +151,19 @@
...
@@ -151,6 +151,19 @@
<artifactId>
fastjson2
</artifactId>
<artifactId>
fastjson2
</artifactId>
<version>
2.0.43
</version>
<version>
2.0.43
</version>
</dependency>
</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>
</dependencies>
<build>
<build>
...
...
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/config/RedisConfig.java
0 → 100644
View file @
14c56833
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
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/controller/JobRecommendController.java
View file @
14c56833
...
@@ -12,7 +12,11 @@ import lombok.RequiredArgsConstructor;
...
@@ -12,7 +12,11 @@ import lombok.RequiredArgsConstructor;
import
lombok.extern.slf4j.Slf4j
;
import
lombok.extern.slf4j.Slf4j
;
import
org.dromara.common.core.domain.R
;
import
org.dromara.common.core.domain.R
;
import
org.dromara.common.core.exception.JxgException
;
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.core.utils.StringUtils
;
import
org.dromara.common.satoken.utils.LoginHelper
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.web.bind.annotation.*
;
import
org.springframework.web.bind.annotation.*
;
...
@@ -79,4 +83,20 @@ public class JobRecommendController {
...
@@ -79,4 +83,20 @@ public class JobRecommendController {
public
R
<
List
<
Level1City
>>
getAllCitys
()
{
public
R
<
List
<
Level1City
>>
getAllCitys
()
{
return
new
R
<>(
categoryCacheManager
.
getCityCache
());
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
();
}
}
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/controller/NewEditionResumeController.java
View file @
14c56833
...
@@ -5,12 +5,14 @@ import cn.hutool.core.collection.CollectionUtil;
...
@@ -5,12 +5,14 @@ import cn.hutool.core.collection.CollectionUtil;
import
com.bkty.system.api.model.LoginUser
;
import
com.bkty.system.api.model.LoginUser
;
import
com.bkty.system.domain.dto.*
;
import
com.bkty.system.domain.dto.*
;
import
com.bkty.system.domain.entity.FunctionResumeBaseTag
;
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.ResumeBase
;
import
com.bkty.system.domain.vo.ResumeByPdfVo
;
import
com.bkty.system.domain.vo.ResumeByPdfVo
;
import
com.bkty.system.domain.vo.ResumeModelVo
;
import
com.bkty.system.domain.vo.ResumeModelVo
;
import
com.bkty.system.domain.vo.ResumeVo
;
import
com.bkty.system.domain.vo.ResumeVo
;
import
com.bkty.system.service.resume.NewEditionResumeService
;
import
com.bkty.system.service.resume.NewEditionResumeService
;
import
com.bkty.system.service.resume.ResumeCacheService
;
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.Operation
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
import
lombok.RequiredArgsConstructor
;
import
lombok.RequiredArgsConstructor
;
...
@@ -44,6 +46,8 @@ public class NewEditionResumeController {
...
@@ -44,6 +46,8 @@ public class NewEditionResumeController {
private
final
ResumeCacheService
resumeCacheService
;
private
final
ResumeCacheService
resumeCacheService
;
private
final
ResumeMakeService
resumeMakeService
;
/**
/**
* 新版导入简历
* 新版导入简历
* @param file 文件
* @param file 文件
...
@@ -256,4 +260,15 @@ public class NewEditionResumeController {
...
@@ -256,4 +260,15 @@ public class NewEditionResumeController {
this
.
newEditionResumeService
.
reversalExperience
(
dto
);
this
.
newEditionResumeService
.
reversalExperience
(
dto
);
return
R
.
ok
();
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
);
}
}
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/entity/AiPositionRecommendRecord.java
0 → 100644
View file @
14c56833
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
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/entity/FunctionPositionPortraitV2.java
0 → 100644
View file @
14c56833
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
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/entity/JobEvaluation.java
0 → 100644
View file @
14c56833
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
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/entity/MatchResult.java
0 → 100644
View file @
14c56833
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
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/entity/ProcessItem.java
0 → 100644
View file @
14c56833
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
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/vo/AiPositionRecommendRecordVo.java
0 → 100644
View file @
14c56833
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
;
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/vo/AiRecommendVo.java
0 → 100644
View file @
14c56833
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
;
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/vo/FunctionPositionPortraitVo.java
0 → 100644
View file @
14c56833
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
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/vo/RecommendItem.java
0 → 100644
View file @
14c56833
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
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/domain/vo/RerankVo.java
0 → 100644
View file @
14c56833
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
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/employmentBusinessPCSystemApplication.java
View file @
14c56833
...
@@ -4,6 +4,8 @@ import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
...
@@ -4,6 +4,8 @@ import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import
org.springframework.boot.SpringApplication
;
import
org.springframework.boot.SpringApplication
;
import
org.springframework.boot.autoconfigure.SpringBootApplication
;
import
org.springframework.boot.autoconfigure.SpringBootApplication
;
import
org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup
;
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
;
import
org.springframework.scheduling.annotation.EnableScheduling
;
/**
/**
...
@@ -13,6 +15,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
...
@@ -13,6 +15,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
*/
*/
@EnableDubbo
@EnableDubbo
@EnableScheduling
@EnableScheduling
@EnableCaching
@SpringBootApplication
@SpringBootApplication
public
class
employmentBusinessPCSystemApplication
{
public
class
employmentBusinessPCSystemApplication
{
public
static
void
main
(
String
[]
args
)
{
public
static
void
main
(
String
[]
args
)
{
...
...
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/init/CategoryCacheManager.java
View file @
14c56833
...
@@ -64,7 +64,43 @@ public class CategoryCacheManager {
...
@@ -64,7 +64,43 @@ public class CategoryCacheManager {
public
Level1Group
getPositionDataByName
(
String
level1Name
)
{
public
Level1Group
getPositionDataByName
(
String
level1Name
)
{
return
positionNameMap
.
get
(
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
;
}
/**
/**
* 根据一级分类获取数据
* 根据一级分类获取数据
*/
*/
...
...
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/llm/JdWriter.java
0 → 100644
View file @
14c56833
package
com
.
bkty
.
system
.
llm
;
import
dev.langchain4j.service.SystemMessage
;
/**
* @author jiangxiaoge
* @description
* @data 2025/6/10
**/
public
interface
JdWriter
{
String
PAY_MESSAGE
=
"""
- Carefully consider the user's question to ensure your answer is logical and makes sense.
- Make sure your explanation is concise and easy to understand, not verbose.
- Strictly return the answer in json format.
- Strictly Ensure that the following answer is in a valid JSON format.
- The output should be formatted as a JSON instance that conforms to the JSON schema below and do not add comments.
Here is the output schema:
'''
{
"
salaryConversionProcess
": string, // 薪资转换为月薪的过程。转换时,如果没有说明工作时间,按照默认每天工作8小时,每月工作24天进行计算。即时薪需要*8*24计算,日薪需要*24进行计算。年薪需要÷12进行计算。
"
salaryRange
": string, // 薪资范围,需将日薪、年薪等转换为月薪,并按从小到大的自然数格式解析,例如“8000-12000”;无法解析则标记为“未知”
}
'''
# 技能
## 技能1:理解岗位内容
当用户告诉你岗位相关内容时,可以使用此技能对岗位数据进行解析理解,并计算该岗位提供的月薪标准。
- 用户可能通过对话或者传入json文件的方式告诉给你岗位相关的数据信息,你应该都能理解他的意思。为了达到这样一个效果,我会告诉你,他使用的数据字段对应的释义,这样无论他是使用对话还是使用json格式数据文件,你都能理解他的意思。
- 具体的字段释义如下:
id: 主键 ID
company_id: 企业 ID
company_name: 企业名称
company_short_name: 企业简称
industry_code: 行业编码
industry_name: 行业名称
publish_date: 发布日期
start_date: 开始时间
end_date: 结束时间
country: 国家
city_code: 城市代码
city_name: 城市名称
position_code: 职位代码
position_name: 职位名称
job_name: 岗位名称
job_salary: 工资范围
job_education: 学历要求
job_experience: 工作经验要求
job_label: 工作标签(技能、岗位特性等)
job_description: 岗位描述
job_number: 工作编号
company_zone: 公司所在区域
company_detail_info: 公司详细信息
company_website: 公司官网网址
company_location: 公司地址
company_logo_address: 公司 logo 地址
data_sources: 数据来源
url_link: 岗位链接地址
remark: 备注
update_date: 更新时间
orig_id: 来源 ID
use_flag: 使用标记(是否有效)
publish_time: 发布时间
collector_name: 采集人姓名
collect_time: 采集时间
collect_flag: 采集标记
cleaner_name: 清理人姓名
clean_time: 清理时间
clean_flag: 清理标记
update_reason: 更新原因
time_stamp: 时间戳
version_id: 版本 ID
portrayal_type: 画像状态(0-未生成,1-已生成)
# 薪资转换过程salaryConversionProcess
薪资转换过程,请将日薪、年薪等转换为月薪的过程记录在此字段中。例如:
"
salaryConversionProcess
": "
原始薪资范围为
300
/
天
至
400
/
天,转换为月薪即
(
300
*
30
)
至
(
400
*
30
)
,得到结果为
9000
到
12000
。
"
"
salaryRange
": "
15000
-
20000
",
1. 当原始薪资是日薪时,按照工作时间进行计算,未明确工作时间时,按照每月24天进行计算。
2. 当原始薪资是年薪时,按照每年12个月进行计算。
3. 当原始薪资是时薪时,按照工作时间进行计算,未明确工作时间时,按照每天工作8小时,每月工作24天进行计算。
# 确定薪资范围salaryRange
将日薪、年薪等转换为月薪,并按从小到大的自然数格式解析,例如“8000-12000”;无法解析则标记为“未知”
**请注意,不管接收到的数据有没有其他的表达方式如7k,1w这种,都请按照要求转化为具体数值-具体数值,连接符也不要进行转化**
**同时,薪资范围必须是整千数字,非整千的数字向下取整**
## 输出要求
- 请直接分析以下岗位信息并输出标准格式的JSON结果,不要包含任何解释、推理过程或思考步骤,直接给出最终结果。
- 请严格遵循你在最后阶段以json格式输出对应的内容,且无需添加多余信息,请一定记住,你更多是以api形式与后端应用发出的请求进行交互,高质量的稳定输出json格式的岗位画像是最合适的。
- 禁止在JSON外输出任何解释性文字。
## 输出示例
示例一:
```json
{
"
salaryConversionProcess
": "
年薪
10
万转换为月薪
8333
元
",
"
salaryRange
": "
4000
-
7000
"
}
```
示例二:
```json
{
"
salaryConversionProcess
": "
日薪
800
-
1200
转换为月薪
24000
元
",
"
salaryRange
": "
24000
-
36000
"
}
```
"""
;
String
SYSTEM_MESSAGE
=
"""
# 角色
文本格式转换专家,擅长将纯文本转化为合适的Markdown格式文本。
# 目标
1. 将输入进来的纯文本转化为合适的Markdown格式文本。
2. 输出转换后的Markdown格式文本。
# 技能
1. 熟悉Markdown格式中的列表标签。
2. 能够准确识别纯文本中的内容结构以进行格式转换。
# 工作流程
1. 接收输入的纯文本。
2. 分析纯文本中的内容结构,判断哪些部分适合使列表标签。
3. 使用加粗标签和列表标签将纯文本转换为Markdown格式文本。
4. 检查转换后的文本,确保没有改动文本内容且未添加其他文字说明。
5. 输出转换后的Markdown格式文本。
# 约束
1. 必须把输入的纯文本转化为Markdown格式文本。
2. 必须仅使用正文格式和列表标签进行转换。
3. 禁止添加任何其他的文字说明。
4. 禁止改动文本内容。
# 输出格式
输出为符合Markdown语法的文本,使用无序列表标签(- )以及有序列表标签(1. ),文字风格保持与输入文本一致,简洁明了。
# 示例
示例1:
输入:
1.负责分析对比市场各车型车机及手机APP车控功能等特点及发展趋势,定期输出竟品分析报告:2.负责跟踪和推进在研车型车机及手机APP车控功能定义的开发落地;3.参与规划和设计在研车型车机及手机APP车控功能的原型图和功能定义:4.参与竞品与本品的动静态体验测评,并编制测评报告,跟踪测评问题的改进,5.开展用户需求研究,协助开展新车型开发的竞争策略的制定和维护。1.硕士研究生及以上学历,车辆工程、机..
输出:
1. 负责分析对比市场各车型车机及手机APP车控功能等特点及发展趋势,定期输出竟品分析报告。
2. 负责跟踪和推进在研车型车机及手机APP车控功能定义的开发落地。
3. 参与规划和设计在研车型车机及手机APP车控功能的原型图和功能定义。
4. 参与竞品与本品的动静态体验测评,并编制测评报告,跟踪测评问题的改进。
5. 开展用户需求研究,协助开展新车型开发的竞争策略的制定和维护。
6. 硕士研究生及以上学历,车辆工程、机..
示例2:
输入:
岗位职责1.负责公司市场运营线上及线上平面设计的需求,制作相关物料的设计及质量把控。2.协助完成AIGC项目产品的平面设计需求。任职要求1.在校生,绘画、设计类专业本科及以上学历。2.视觉表现能力出色,不拘泥于单一的风格表现,具有丰富的想象力与创意。3.有活跃的创意思维,出色的审美和理解能力,重视细节。4.较强的沟通能力和团队精神,思维逻辑清晰,能够从用户角度去思考设计。5.熟练使用Photoshop,Al,Figma,Sketch,Axure 等相关设计软件;有 Cinema 4D 使用经验者更佳,
输出:
岗位职责
1. 负责公司市场运营线上及线上平面设计的需求,制作相关物料的设计及质量把控。
2. 协助完成AIGC项目产品的平面设计需求。
任职要求
1. 在校生,绘画、设计类专业本科及以上学历。
2. 视觉表现能力出色,不拘泥于单一的风格表现,具有丰富的想象力与创意。
3. 有活跃的创意思维,出色的审美和理解能力,重视细节。
4. 较强的沟通能力和团队精神,思维逻辑清晰,能够从用户角度去思考设计。
5. 熟练使用Photoshop,Al,Figma,Sketch,Axure 等相关设计软件;有 Cinema 4D 使用经验者更佳。
"""
;
@SystemMessage
(
SYSTEM_MESSAGE
)
String
writer
(
String
prompt
);
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/mapper/AiAnalysisMapper.java
0 → 100644
View file @
14c56833
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
);
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/mapper/CityDataMapper.java
View file @
14c56833
...
@@ -3,7 +3,14 @@ package com.bkty.system.mapper;
...
@@ -3,7 +3,14 @@ package com.bkty.system.mapper;
import
com.baomidou.mybatisplus.core.mapper.BaseMapper
;
import
com.baomidou.mybatisplus.core.mapper.BaseMapper
;
import
com.bkty.system.domain.entity.CityData
;
import
com.bkty.system.domain.entity.CityData
;
import
org.apache.ibatis.annotations.Mapper
;
import
org.apache.ibatis.annotations.Mapper
;
import
org.apache.ibatis.annotations.Param
;
import
org.apache.ibatis.annotations.Select
;
@Mapper
@Mapper
public
interface
CityDataMapper
extends
BaseMapper
<
CityData
>
{
public
interface
CityDataMapper
extends
BaseMapper
<
CityData
>
{
@Select
(
"""
select city_code from city_data
where city_name = #{city}
"""
)
String
getCityCodeByName
(
@Param
(
"city"
)
String
city
);
}
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/mapper/PositionRecommendMapper.java
0 → 100644
View file @
14c56833
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
);
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/coze/CozeApiService.java
View file @
14c56833
...
@@ -13,4 +13,12 @@ public interface CozeApiService {
...
@@ -13,4 +13,12 @@ public interface CozeApiService {
* @return
* @return
*/
*/
String
getCozeTokenCn
(
String
cozeSource
)
throws
Exception
;
String
getCozeTokenCn
(
String
cozeSource
)
throws
Exception
;
/**
* 获取cozeToken
* @return
*/
String
getCozeToken
()
throws
Exception
;
String
getCozeDomainUri
();
}
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/coze/impl/CozeApiServiceImpl.java
View file @
14c56833
...
@@ -14,11 +14,9 @@ import org.apache.commons.collections4.MapUtils;
...
@@ -14,11 +14,9 @@ import org.apache.commons.collections4.MapUtils;
import
org.dromara.common.core.constant.CacheConstants
;
import
org.dromara.common.core.constant.CacheConstants
;
import
org.dromara.common.core.constant.Constants
;
import
org.dromara.common.core.constant.Constants
;
import
org.dromara.common.core.constant.CozeConstsnts
;
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.exception.WarnException
;
import
org.dromara.common.core.utils.AESUtil
;
import
org.dromara.common.core.utils.*
;
import
org.dromara.common.core.utils.JWTGenerator
;
import
org.dromara.common.core.utils.StringUtils
;
import
org.dromara.common.core.utils.TimeTool
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Qualifier
;
import
org.springframework.beans.factory.annotation.Qualifier
;
import
org.springframework.core.ParameterizedTypeReference
;
import
org.springframework.core.ParameterizedTypeReference
;
...
@@ -107,4 +105,91 @@ public class CozeApiServiceImpl implements CozeApiService {
...
@@ -107,4 +105,91 @@ public class CozeApiServiceImpl implements CozeApiService {
}
}
throw
new
WarnException
(
"获取coze令牌失败"
);
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
;
}
}
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/AiAnalysisService.java
View file @
14c56833
...
@@ -3,6 +3,7 @@ package com.bkty.system.service.jobRecommend;
...
@@ -3,6 +3,7 @@ package com.bkty.system.service.jobRecommend;
import
com.baomidou.mybatisplus.extension.service.IService
;
import
com.baomidou.mybatisplus.extension.service.IService
;
import
com.bkty.system.domain.dto.AnalysisCareerDto
;
import
com.bkty.system.domain.dto.AnalysisCareerDto
;
import
com.bkty.system.domain.entity.AiRecommendBase
;
import
com.bkty.system.domain.entity.AiRecommendBase
;
import
com.bkty.system.domain.vo.AiRecommendVo
;
public
interface
AiAnalysisService
extends
IService
<
AiRecommendBase
>
{
public
interface
AiAnalysisService
extends
IService
<
AiRecommendBase
>
{
...
@@ -11,4 +12,9 @@ public interface AiAnalysisService extends IService<AiRecommendBase> {
...
@@ -11,4 +12,9 @@ public interface AiAnalysisService extends IService<AiRecommendBase> {
* @param dto
* @param dto
*/
*/
String
saveAnalysisData
(
AnalysisCareerDto
dto
);
String
saveAnalysisData
(
AnalysisCareerDto
dto
);
/**
* 查询个人分析推荐数据
*/
AiRecommendVo
queryAnalysisData
(
Long
analysisId
);
}
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/JobRecommendService.java
View file @
14c56833
...
@@ -14,4 +14,12 @@ public interface JobRecommendService {
...
@@ -14,4 +14,12 @@ public interface JobRecommendService {
* @return
* @return
*/
*/
List
<
Level1Group
>
getLevel1Groups
(
String
level1
);
List
<
Level1Group
>
getLevel1Groups
(
String
level1
);
/**
* 岗位智推
* @param analysisId
* @param userId
* @param resumeId
*/
void
recommendPosition
(
Long
analysisId
,
Long
userId
,
Long
resumeId
);
}
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/PositionRecommendRedisService.java
0 → 100644
View file @
14c56833
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
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/RecommendPositionService.java
0 → 100644
View file @
14c56833
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
);
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/impl/AiAnalysisServiceImpl.java
View file @
14c56833
...
@@ -6,8 +6,10 @@ import com.alibaba.nacos.common.utils.StringUtils;
...
@@ -6,8 +6,10 @@ import com.alibaba.nacos.common.utils.StringUtils;
import
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper
;
import
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper
;
import
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
;
import
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
;
import
com.bkty.system.domain.dto.AnalysisCareerDto
;
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.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.AiRecommendBaseMapper
;
import
com.bkty.system.mapper.FunctionResumeBaseMapper
;
import
com.bkty.system.mapper.FunctionResumeBaseMapper
;
import
com.bkty.system.service.coze.CozeApiService
;
import
com.bkty.system.service.coze.CozeApiService
;
...
@@ -48,63 +50,54 @@ public class AiAnalysisServiceImpl extends ServiceImpl<AiRecommendBaseMapper, Ai
...
@@ -48,63 +50,54 @@ public class AiAnalysisServiceImpl extends ServiceImpl<AiRecommendBaseMapper, Ai
@Override
@Override
public
String
saveAnalysisData
(
AnalysisCareerDto
dto
)
{
public
String
saveAnalysisData
(
AnalysisCareerDto
dto
)
{
AiRecommendBase
aiRecommendBase
=
new
AiRecommendBase
();
AiRecommendBase
aiRecommendBase
=
new
AiRecommendBase
();
// if (StringUtils.isNotBlank(dto.getAnalysisId())){
aiRecommendBase
.
setResumeId
(
Long
.
valueOf
(
dto
.
getResumeId
()));
//简历id
// //String json = redisTemplate.opsForValue().get(CacheConstants.REDIS_USER_ANALYSIS_KEY.formatted(user.getId()));
aiRecommendBase
.
setPay
(
dto
.
getPay
());
//期望薪资
// aiRecommendBase = this.baseMapper.selectById(dto.getAnalysisId());
aiRecommendBase
.
setCareer
(
JSON
.
toJSONString
(
dto
.
getCareer
()));
//目标岗位
// }
aiRecommendBase
.
setTargetCity
(
JSON
.
toJSONString
(
dto
.
getTargetCity
()));
//目标城市
aiRecommendBase
.
setAuthRecommend
(
0
);
//自动推荐
/* if (CollectionUtil.isNotEmpty(dto.getCareer())){
aiRecommendBase
.
setRecommendStatus
(
0
);
//未推荐
aiRecommendBase.setCareer(JSON.toJSONString(dto.getCareer()));
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
));
if (CollectionUtil.isNotEmpty(dto.getTargetCity())){
return
String
.
valueOf
(
aiRecommendBase
.
getId
());
aiRecommendBase.setTargetCity(JSON.toJSONString(dto.getTargetCity()));
}
}
if (StringUtils.isNotBlank(dto.getPay())){
@Override
aiRecommendBase.setPay(dto.getPay());
public
AiRecommendVo
queryAnalysisData
(
Long
analysisId
)
{
}
if (CollectionUtil.isNotEmpty(dto.getCompanyNature())){
AiRecommendVo
vo
=
new
AiRecommendVo
();
aiRecommendBase.setCompanyNature(JSON.toJSONString(dto.getCompanyNature()));
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
(
recommendBase
.
getCareer
())){
if (StringUtils.isNotBlank(dto.getMbti())){
List
<
String
>
list
=
JSON
.
parseObject
(
recommendBase
.
getCareer
(),
List
.
class
);
aiRecommendBase.setMbti(dto.getMbti()
);
vo
.
setCareer
(
list
);
}
}
if (null != dto.getWorkTime()){
if
(
StringUtils
.
isNotBlank
(
recommendBase
.
getTargetCity
())){
aiRecommendBase.setWorkTime(dto.getWorkTime());
List
<
String
>
list
=
JSON
.
parseObject
(
recommendBase
.
getTargetCity
(),
List
.
class
);
vo
.
setTargetCity
(
list
);
}
}
if (
null != dto.getInternTime(
)){
if
(
StringUtils
.
isNotBlank
(
recommendBase
.
getPay
()
)){
aiRecommendBase.setInternTime(dto.getInternTime
());
vo
.
setPay
(
recommendBase
.
getPay
());
}
}
if (
CollectionUtil.isNotEmpty(dto.getCompanySize()
)){
if
(
null
!=
recommendBase
.
getAuthRecommend
(
)){
aiRecommendBase.setCompanySize(JSON.toJSONString(dto.getCompanySize()
));
vo
.
setAuthRecommend
(
recommendBase
.
getAuthRecommend
(
));
}
}
if (null !=
dto.getJobType
()){
if
(
null
!=
recommendBase
.
getRecommendStatus
()){
aiRecommendBase.setJobType(dto.getJobType
());
vo
.
setRecommendStatus
(
recommendBase
.
getRecommendStatus
());
}
}
if (null != dto.getAuthRecommend()){
vo
.
setId
(
String
.
valueOf
(
recommendBase
.
getId
()));
aiRecommendBase.setAuthRecommend(dto.getAuthRecommend());
}
if (null != dto.getRecommendStatus()){
return
vo
;
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
());
}
}
}
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/impl/JobRecommendServiceImpl.java
View file @
14c56833
package
com
.
bkty
.
system
.
service
.
jobRecommend
.
impl
;
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.CategoryCacheManager
;
import
com.bkty.system.init.Level1Group
;
import
com.bkty.system.init.Level1Group
;
import
com.bkty.system.init.Level2Group
;
import
com.bkty.system.init.Level2Group
;
import
com.bkty.system.init.Level3Group
;
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.JobRecommendService
;
import
com.bkty.system.service.jobRecommend.RecommendPositionService
;
import
lombok.RequiredArgsConstructor
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.commons.collections4.CollectionUtils
;
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.jetbrains.annotations.NotNull
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
org.springframework.stereotype.Service
;
...
@@ -28,6 +36,11 @@ public class JobRecommendServiceImpl implements JobRecommendService {
...
@@ -28,6 +36,11 @@ public class JobRecommendServiceImpl implements JobRecommendService {
@Autowired
@Autowired
private
CategoryCacheManager
categoryCacheManager
;
private
CategoryCacheManager
categoryCacheManager
;
@Autowired
private
AiAnalysisMapper
aiAnalysisMapper
;
private
final
RecommendPositionService
recommendPositionService
;
@Override
@Override
public
List
<
Level1Group
>
getLevel1Groups
(
String
level1
)
{
public
List
<
Level1Group
>
getLevel1Groups
(
String
level1
)
{
...
@@ -60,4 +73,40 @@ public class JobRecommendServiceImpl implements JobRecommendService {
...
@@ -60,4 +73,40 @@ public class JobRecommendServiceImpl implements JobRecommendService {
}
}
return
result
;
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
();
}
}
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/impl/PositionRecommendRedisServiceImpl.java
0 → 100644
View file @
14c56833
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
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/jobRecommend/impl/RecommendPositionServiceImpl.java
0 → 100644
View file @
14c56833
package
com
.
bkty
.
system
.
service
.
jobRecommend
.
impl
;
import
cn.hutool.core.collection.CollectionUtil
;
import
cn.hutool.http.HttpUtil
;
import
co.elastic.clients.elasticsearch._types.FieldValue
;
import
co.elastic.clients.elasticsearch._types.KnnQuery
;
import
co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery
;
import
co.elastic.clients.elasticsearch._types.query_dsl.Query
;
import
co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders
;
import
co.elastic.clients.json.JsonData
;
import
com.alibaba.fastjson2.JSON
;
import
com.alibaba.fastjson2.JSONObject
;
import
com.alibaba.fastjson2.TypeReference
;
import
com.alibaba.nacos.common.utils.StringUtils
;
import
com.alibaba.nacos.shaded.com.google.common.collect.Lists
;
import
com.bkty.system.domain.entity.*
;
import
com.bkty.system.domain.vo.AiRecommendVo
;
import
com.bkty.system.domain.vo.FunctionPositionPortraitVo
;
import
com.bkty.system.domain.vo.RerankVo
;
import
com.bkty.system.domain.vo.ResumeVo
;
import
com.bkty.system.init.CategoryCacheManager
;
import
com.bkty.system.llm.JdWriter
;
import
com.bkty.system.mapper.AiAnalysisMapper
;
import
com.bkty.system.mapper.CityDataMapper
;
import
com.bkty.system.mapper.PositionRecommendMapper
;
import
com.bkty.system.service.coze.CozeApiService
;
import
com.bkty.system.service.jobRecommend.AiAnalysisService
;
import
com.bkty.system.service.jobRecommend.PositionRecommendRedisService
;
import
com.bkty.system.service.jobRecommend.RecommendPositionService
;
import
com.bkty.system.service.resume.NewEditionResumeService
;
import
com.bkty.system.service.resume.ResumeMakeService
;
import
com.bkty.system.utils.MariaDbAdtUtil
;
import
dev.ai4j.openai4j.OpenAiHttpException
;
import
lombok.AllArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.dromara.common.core.config.QwenClientSingleton
;
import
org.dromara.common.core.constant.Constants
;
import
org.dromara.common.core.constant.CozeConstsnts
;
import
org.dromara.common.core.utils.DateTimeWrapper
;
import
org.springframework.beans.BeanUtils
;
import
org.springframework.context.ApplicationContext
;
import
org.springframework.data.domain.Pageable
;
import
org.springframework.data.elasticsearch.client.elc.NativeQuery
;
import
org.springframework.data.elasticsearch.core.ElasticsearchOperations
;
import
org.springframework.data.elasticsearch.core.SearchHit
;
import
org.springframework.data.elasticsearch.core.SearchHits
;
import
org.springframework.data.elasticsearch.core.mapping.IndexCoordinates
;
import
org.springframework.data.elasticsearch.core.query.SourceFilter
;
import
org.springframework.data.redis.core.RedisTemplate
;
import
org.springframework.http.HttpHeaders
;
import
org.springframework.stereotype.Service
;
import
java.io.IOException
;
import
java.time.LocalDate
;
import
java.time.format.DateTimeFormatter
;
import
java.util.*
;
import
java.util.concurrent.*
;
import
java.util.concurrent.atomic.AtomicInteger
;
import
java.util.concurrent.atomic.AtomicLong
;
import
java.util.stream.IntStream
;
/**
* @author Zhang Wenbiao
* @description 岗位推荐serviceImpl
* @datetime 2025/12/4 11:59
*/
@Slf4j
@AllArgsConstructor
@Service
public
class
RecommendPositionServiceImpl
implements
RecommendPositionService
{
private
final
RedisTemplate
<
String
,
String
>
redisTemplate
;
private
final
AiAnalysisMapper
aiAnalysisMapper
;
private
final
AiAnalysisService
aiAnalysisService
;
private
final
CityDataMapper
cityDataMapper
;
private
final
NewEditionResumeService
newEditionResumeService
;
private
final
ResumeMakeService
resumeMakeService
;
private
final
PositionRecommendMapper
positionRecommendMapper
;
private
final
ElasticsearchOperations
elasticsearchOperations
;
private
final
CategoryCacheManager
categoryCacheManager
;
private
final
CozeApiService
cozeApiService
;
private
final
ApplicationContext
applicationContext
;
@Override
public
void
recommendPosition
(
Long
analysisId
,
Long
userId
,
Long
resumeId
)
{
try
{
delProcess
(
userId
);
// 1. 数据准备阶段
AiRecommendVo
aiRecommendVo
=
prepareRecommendationData
(
analysisId
,
userId
,
resumeId
);
if
(
aiRecommendVo
==
null
)
return
;
// 2. 检查配额限制
// if (!checkQuotaLimit(analysisId, userId)) return;
// 3. 获取简历信息
ResumeVo
resumeVo
=
getResumeInfo
(
analysisId
,
userId
,
resumeId
);
if
(
resumeVo
==
null
)
return
;
// 4. 获取简历画像
List
<
FunctionResumeSketch
>
resumeSketchList
=
getResumeSketch
(
analysisId
,
userId
,
resumeId
);
if
(
resumeSketchList
==
null
)
return
;
// 5. 获取历史推荐岗位ID
List
<
Long
>
positionIdList
=
getHistoryPositionIds
(
analysisId
);
// 6. 构建推荐参数
List
<
RcommendParam
>
rcommendParamList
=
buildRecommendParams
(
aiRecommendVo
,
resumeSketchList
);
List
<
RcommendParam
>
qgRcommendParamList
=
buildNationalRecommendParams
(
aiRecommendVo
,
resumeSketchList
);
// 7. 执行岗位推荐
List
<
FunctionPositionPortraitVo
>
positionPortraitV2List
=
executePositionRecommend
(
rcommendParamList
,
qgRcommendParamList
,
positionIdList
,
userId
);
if
(
CollectionUtil
.
isEmpty
(
positionPortraitV2List
))
{
handleEmptyRecommendation
(
analysisId
,
userId
);
return
;
}
// 8. 处理推荐结果
handleRecommendResults
(
positionPortraitV2List
,
analysisId
,
userId
,
resumeId
,
resumeVo
,
aiRecommendVo
);
}
catch
(
Exception
e
)
{
log
.
error
(
"岗位推荐过程中发生异常"
,
e
);
addProcess
(
userId
,
new
ProcessItem
(
"error"
,
"岗位推荐过程中发生异常"
,
0
,
DateTimeWrapper
.
now
().
toString
()));
aiAnalysisMapper
.
updateAnalysis
(
analysisId
,
0
,
userId
,
0
);
}
}
public
List
<
FunctionPositionPortraitV2
>
queryPositionList
(
String
cityCode
,
List
<
Long
>
positionIdList
)
{
//查询岗位
BoolQuery
.
Builder
bool
=
QueryBuilders
.
bool
().
boost
(
1.0f
);
Query
multiMatch
=
QueryBuilders
.
terms
()
.
field
(
"positionDataId"
)
.
terms
(
termsBuilder
->
termsBuilder
.
value
(
positionIdList
.
stream
()
.
map
(
FieldValue:
:
of
).
toList
()))
.
build
()
.
_toQuery
();
bool
.
must
(
multiMatch
);
Query
bQuery
=
new
Query
(
bool
.
build
());
NativeQuery
build
=
NativeQuery
.
builder
()
.
withQuery
(
bQuery
)
.
withSourceFilter
(
new
SourceFilter
()
{
@Override
public
String
[]
getIncludes
()
{
return
new
String
[]{
"positionDataId"
,
"jobTitle"
,
"industry"
,
"standardPosition"
,
"workPlace"
,
"natureOfPost"
,
"salaryRange"
,
"companyName"
,
"companyNature"
,
"companySize"
,
"expDemand"
,
"eduDemand"
,
"publicPlatform"
,
"platformUrl"
,
"publicTime"
,
"skill"
,
"promote"
,
"mild"
,
"jdHtml"
};
}
@Override
public
String
[]
getExcludes
()
{
return
new
String
[
0
];
}
})
.
withFields
(
"positionDataId"
,
"jobTitle"
,
"industry"
,
"standardPosition"
,
"workPlace"
,
"natureOfPost"
,
"salaryRange"
,
"companyName"
,
"companyNature"
,
"companySize"
,
"expDemand"
,
"eduDemand"
,
"publicPlatform"
,
"platformUrl"
,
"publicTime"
,
"skill"
,
"promote"
,
"mild"
,
"jdHtml"
)
.
withTrackTotalHits
(
true
)
.
withSearchType
(
null
)
.
withPageable
(
Pageable
.
ofSize
(
positionIdList
.
size
()))
.
build
();
SearchHits
<
FunctionPositionPortraitV2
>
search
=
elasticsearchOperations
.
search
(
build
,
FunctionPositionPortraitV2
.
class
,
IndexCoordinates
.
of
(
Constants
.
ES_INDEX_POSITION
+
cityCode
));
List
<
FunctionPositionPortraitV2
>
dataList
=
new
ArrayList
<>();
for
(
SearchHit
<
FunctionPositionPortraitV2
>
hit
:
search
)
{
FunctionPositionPortraitV2
content
=
hit
.
getContent
();
content
.
setFeatureVector
(
null
);
content
.
setSketch
(
null
);
dataList
.
add
(
content
);
}
return
dataList
;
}
/**
* 处理推荐结果
*/
private
void
handleRecommendResults
(
List
<
FunctionPositionPortraitVo
>
positionPortraitV2List
,
Long
analysisId
,
Long
userId
,
Long
resumeId
,
ResumeVo
resumeVo
,
AiRecommendVo
aiRecommendVo
)
throws
InterruptedException
{
positionPortraitV2List
.
sort
((
o1
,
o2
)
->
o2
.
getRerankScore
().
compareTo
(
o1
.
getRerankScore
()));
// 分批处理
List
<
FunctionPositionPortraitVo
>
firstBatch
=
new
ArrayList
<>();
List
<
FunctionPositionPortraitVo
>
secondBatch
=
new
ArrayList
<>();
if
(
positionPortraitV2List
.
size
()
>
50
)
{
firstBatch
=
new
ArrayList
<>(
positionPortraitV2List
.
subList
(
0
,
50
));
secondBatch
=
new
ArrayList
<>(
positionPortraitV2List
.
subList
(
50
,
positionPortraitV2List
.
size
()));
}
else
{
firstBatch
=
new
ArrayList
<>(
positionPortraitV2List
);
}
// 通过coze平台获取分析信息
List
<
FunctionPositionPortraitVo
>
matchResults
=
processPositionAnalysis
(
firstBatch
,
secondBatch
,
userId
,
aiRecommendVo
,
resumeVo
);
if
(
matchResults
.
isEmpty
())
{
handleEmptyAnalysisResults
(
analysisId
,
userId
);
return
;
}
// 处理推荐数据插入
processRecommendationInsert
(
matchResults
,
analysisId
,
userId
,
resumeId
,
resumeVo
,
aiRecommendVo
);
}
/**
* 处理推荐数据插入(使用多线程)
*/
private
void
processRecommendationInsert
(
List
<
FunctionPositionPortraitVo
>
matchResults
,
Long
analysisId
,
Long
userId
,
Long
resumeId
,
ResumeVo
resumeVo
,
AiRecommendVo
aiRecommendVo
)
throws
InterruptedException
{
List
<
FunctionPositionPortraitVo
>
addDataList
=
filterRecommendData
(
matchResults
,
userId
);
if
(
addDataList
.
isEmpty
())
{
handleEmptyRecommendation
(
analysisId
,
userId
);
return
;
}
// 使用多线程处理数据插入
insertRecommendationDataConcurrently
(
addDataList
,
userId
,
resumeVo
,
aiRecommendVo
,
analysisId
);
// 更新分析状态和缓存
aiAnalysisMapper
.
updateAnalysis
(
analysisId
,
2
,
userId
,
0
);
PositionRecommendRedisService
recommendRedisService
=
applicationContext
.
getBean
(
PositionRecommendRedisService
.
class
);
recommendRedisService
.
initRedisData
(
analysisId
);
}
/**
* 使用多线程插入推荐数据
*/
private
void
insertRecommendationDataConcurrently
(
List
<
FunctionPositionPortraitVo
>
addDataList
,
Long
userId
,
ResumeVo
resumeVo
,
AiRecommendVo
aiRecommendVo
,
Long
analysisId
)
throws
InterruptedException
{
ExecutorService
insertExecutor
=
Executors
.
newFixedThreadPool
(
10
);
List
<
CompletableFuture
<
Void
>>
insertFutures
=
new
ArrayList
<>();
Date
date
=
new
Date
();
for
(
FunctionPositionPortraitVo
portraitVo
:
addDataList
)
{
CompletableFuture
<
Void
>
future
=
CompletableFuture
.
runAsync
(()
->
{
try
{
AiPositionRecommendRecord
recommendRecord
=
buildRecommendRecord
(
portraitVo
,
userId
,
date
,
analysisId
);
// generateJobApplicationAdviceInBatches(resumeVo, aiRecommendVo, portraitVo, recommendRecord);
// generateCompanyScaleAndIndustry(recommendRecord);
positionRecommendMapper
.
insert
(
recommendRecord
);
}
catch
(
Exception
e
)
{
log
.
error
(
"插入推荐数据失败,岗位ID: {}"
,
portraitVo
.
getPositionDataId
(),
e
);
}
},
insertExecutor
);
insertFutures
.
add
(
future
);
}
CompletableFuture
.
allOf
(
insertFutures
.
toArray
(
new
CompletableFuture
[
0
])).
join
();
insertExecutor
.
shutdown
();
if
(!
insertExecutor
.
awaitTermination
(
10
,
TimeUnit
.
MINUTES
))
{
insertExecutor
.
shutdownNow
();
}
}
/**
* 构建推荐记录
*/
private
AiPositionRecommendRecord
buildRecommendRecord
(
FunctionPositionPortraitVo
portraitVo
,
Long
userId
,
Date
date
,
Long
analysisId
)
{
AiPositionRecommendRecord
recommendRecord
=
new
AiPositionRecommendRecord
();
recommendRecord
.
setUserId
(
userId
);
recommendRecord
.
setPositionId
(
portraitVo
.
getPositionDataId
());
recommendRecord
.
setTreasuresType
(
0
);
recommendRecord
.
setAlreadyType
(
0
);
recommendRecord
.
setDeliverType
(
0
);
recommendRecord
.
setOperationType
(
0
);
recommendRecord
.
setRScore
(
Integer
.
valueOf
(
portraitVo
.
getScore
()));
recommendRecord
.
setRDetails
(
portraitVo
.
getDetails
());
recommendRecord
.
setRecommendTime
(
date
);
recommendRecord
.
setResumeId
(
Long
.
valueOf
(
portraitVo
.
getResumeId
()));
recommendRecord
.
setCityCode
(
portraitVo
.
getCityCode
());
recommendRecord
.
setAnalyzeJson
(
JSON
.
toJSONString
(
portraitVo
.
getMatchResult
()));
recommendRecord
.
setEvaluationJson
(
JSON
.
toJSONString
(
portraitVo
.
getJobEvaluation
()));
recommendRecord
.
setJobTitle
(
portraitVo
.
getJobTitle
());
recommendRecord
.
setJobProfession
(
portraitVo
.
getIndustry
());
recommendRecord
.
setStandardPosition
(
portraitVo
.
getStandardPosition
());
recommendRecord
.
setCompanySize
(
portraitVo
.
getCompanySize
());
recommendRecord
.
setCompanyNature
(
portraitVo
.
getCompanyNature
());
recommendRecord
.
setExpDemand
(
portraitVo
.
getExpDemand
());
recommendRecord
.
setEduDemand
(
portraitVo
.
getEduDemand
());
recommendRecord
.
setPublicPlatform
(
portraitVo
.
getPublicPlatform
());
recommendRecord
.
setCompanyName
(
portraitVo
.
getCompanyName
());
recommendRecord
.
setJdMd
(
portraitVo
.
getJdMd
());
recommendRecord
.
setMaxPay
(
portraitVo
.
getPay
());
recommendRecord
.
setCreateTime
(
date
);
recommendRecord
.
setUpdateTime
(
date
);
recommendRecord
.
setAnalysisId
(
analysisId
);
return
recommendRecord
;
}
/**
* 过滤推荐数据
*/
private
List
<
FunctionPositionPortraitVo
>
filterRecommendData
(
List
<
FunctionPositionPortraitVo
>
matchResults
,
Long
userId
)
{
List
<
FunctionPositionPortraitVo
>
addDataList
=
matchResults
;
if
(
matchResults
.
size
()
>
10
)
{
addDataList
=
matchResults
.
subList
(
0
,
10
);
}
return
addDataList
;
}
/**
* 处理空分析结果
*/
private
void
handleEmptyAnalysisResults
(
Long
analysisId
,
Long
userId
)
{
addProcess
(
userId
,
new
ProcessItem
(
"failure"
,
NOTIFICATION_TITLE_ERROR
,
100
,
DateTimeWrapper
.
now
().
toString
()));
aiAnalysisMapper
.
updateAnalysis
(
analysisId
,
2
,
userId
,
0
);
}
/**
* 处理岗位分析
*/
private
List
<
FunctionPositionPortraitVo
>
processPositionAnalysis
(
List
<
FunctionPositionPortraitVo
>
firstBatch
,
List
<
FunctionPositionPortraitVo
>
secondBatch
,
Long
userId
,
AiRecommendVo
aiRecommendVo
,
ResumeVo
resumeVo
)
{
ExecutorService
executor
=
new
ThreadPoolExecutor
(
10
,
10
,
0L
,
TimeUnit
.
MILLISECONDS
,
new
LinkedBlockingQueue
<
Runnable
>());
List
<
CompletableFuture
<
Void
>>
futures
=
new
ArrayList
<>();
List
<
FunctionPositionPortraitVo
>
matchResults
=
Collections
.
synchronizedList
(
new
ArrayList
<>());
int
totalJobCount
=
firstBatch
.
size
()
+
secondBatch
.
size
();
AtomicInteger
processedCount
=
new
AtomicInteger
(
0
);
int
baseProgress
=
50
;
double
progressPerJob
=
(
double
)
50
/
totalJobCount
;
AtomicLong
lastPushTime
=
new
AtomicLong
(
0
);
// 处理第一批
for
(
FunctionPositionPortraitVo
resultVo
:
firstBatch
)
{
CompletableFuture
<
Void
>
future
=
CompletableFuture
.
runAsync
(()
->
{
try
{
sendCozeByPositionData
(
userId
,
resultVo
,
aiRecommendVo
,
resumeVo
,
processedCount
,
baseProgress
,
progressPerJob
,
lastPushTime
,
matchResults
);
}
catch
(
Exception
e
)
{
log
.
error
(
"处理岗位分析失败"
,
e
);
log
.
error
(
"处理岗位分析失败的id:{}"
,
resultVo
.
getPositionDataId
());
}
},
executor
);
futures
.
add
(
future
);
}
CompletableFuture
.
allOf
(
futures
.
toArray
(
new
CompletableFuture
[
0
])).
join
();
executor
.
shutdown
();
try
{
if
(!
executor
.
awaitTermination
(
50
,
TimeUnit
.
MINUTES
))
{
executor
.
shutdownNow
();
}
}
catch
(
InterruptedException
e
)
{
executor
.
shutdownNow
();
Thread
.
currentThread
().
interrupt
();
}
matchResults
.
sort
((
o1
,
o2
)
->
o2
.
getScore
().
compareTo
(
o1
.
getScore
()));
// 如果结果不足10个,处理第二批
if
(
matchResults
.
size
()
<
10
&&
CollectionUtil
.
isNotEmpty
(
secondBatch
))
{
processSecondBatch
(
secondBatch
,
matchResults
,
userId
,
aiRecommendVo
,
resumeVo
,
processedCount
,
baseProgress
,
progressPerJob
,
lastPushTime
);
}
return
matchResults
;
}
/**
* 处理第二批岗位分析
*/
private
void
processSecondBatch
(
List
<
FunctionPositionPortraitVo
>
secondBatch
,
List
<
FunctionPositionPortraitVo
>
matchResults
,
Long
userId
,
AiRecommendVo
aiRecommendVo
,
ResumeVo
resumeVo
,
AtomicInteger
processedCount
,
int
baseProgress
,
double
progressPerJob
,
AtomicLong
lastPushTime
)
{
ExecutorService
executor2
=
new
ThreadPoolExecutor
(
10
,
10
,
0L
,
TimeUnit
.
MILLISECONDS
,
new
LinkedBlockingQueue
<
Runnable
>());
List
<
CompletableFuture
<
Void
>>
futuresSecond
=
new
ArrayList
<>();
for
(
FunctionPositionPortraitVo
batch
:
secondBatch
)
{
CompletableFuture
<
Void
>
future
=
CompletableFuture
.
runAsync
(()
->
{
try
{
if
(
matchResults
.
size
()
<
10
)
{
sendCozeByPositionData
(
userId
,
batch
,
aiRecommendVo
,
resumeVo
,
processedCount
,
baseProgress
,
progressPerJob
,
lastPushTime
,
matchResults
);
}
}
catch
(
Exception
e
)
{
log
.
error
(
"处理岗位分析失败"
,
e
);
log
.
error
(
"处理岗位分析失败的id:{}"
,
batch
.
getPositionDataId
());
}
},
executor2
);
futuresSecond
.
add
(
future
);
}
if
(!
futuresSecond
.
isEmpty
())
{
try
{
CompletableFuture
.
allOf
(
futuresSecond
.
toArray
(
new
CompletableFuture
[
0
])).
get
(
5
,
TimeUnit
.
MINUTES
);
}
catch
(
Exception
e
)
{
log
.
warn
(
"处理secondBatch时超时或发生异常"
,
e
);
}
}
executor2
.
shutdown
();
try
{
if
(!
executor2
.
awaitTermination
(
50
,
TimeUnit
.
MINUTES
))
{
executor2
.
shutdownNow
();
}
}
catch
(
InterruptedException
e
)
{
executor2
.
shutdownNow
();
Thread
.
currentThread
().
interrupt
();
}
}
private
void
sendCozeByPositionData
(
Long
userId
,
FunctionPositionPortraitVo
resultVo
,
AiRecommendVo
aiRecommendVo
,
ResumeVo
resumeVo
,
AtomicInteger
processedCount
,
int
baseProgress
,
double
progressPerJob
,
AtomicLong
lastPushTime
,
List
<
FunctionPositionPortraitVo
>
matchResults
)
throws
Exception
{
if
(
resultVo
.
getPay
()
!=
null
&&
resultVo
.
getPay
()
==
0
){
//薪资未知,数据二次确认
Map
<
String
,
Object
>
stringObjectMap
=
MariaDbAdtUtil
.
queryRecruitQqxbById
(
resultVo
.
getPositionDataId
());
try
{
//res = writer.writer(promptTemplate.template());
QwenClientSingleton
instance
=
QwenClientSingleton
.
getInstance
(
"https://xzt-llm-dev.jinsehuaqin.com"
,
"token-abc123"
,
"/mnt/app/llm/Qwen3/Qwen/Qwen3-8B"
);
String
chat
=
instance
.
chat
(
JdWriter
.
PAY_MESSAGE
,
JSON
.
toJSONString
(
stringObjectMap
));
JSONObject
jsonObject
=
JSON
.
parseObject
(
chat
);
String
salaryRange
=
jsonObject
.
getString
(
"salaryRange"
);
if
(
salaryRange
.
contains
(
"-"
)
&&
!
"未知"
.
equals
(
salaryRange
)
&&
!
salaryRange
.
contains
(
"面议"
))
{
resultVo
.
setPay
(
Integer
.
valueOf
(
salaryRange
.
split
(
"-"
)[
0
]));
resultVo
.
setStandardPosition
(
salaryRange
);
//修改es数据
updatePositionSalaryInfoWithClient
(
resultVo
.
getPositionDataId
(),
resultVo
.
getCityCode
(),
salaryRange
,
resultVo
.
getPay
());
}
}
catch
(
OpenAiHttpException
|
IOException
e
)
{
throw
new
RuntimeException
(
e
);
}
catch
(
Exception
e
)
{
log
.
error
(
"薪资确认错误"
,
e
);
}
}
JSONObject
workflowObj
=
sendCozeWorkflow
(
resultVo
,
aiRecommendVo
,
resumeVo
);
System
.
out
.
println
(
"工作流返回信息:"
+
workflowObj
);
if
(
workflowObj
==
null
)
{
log
.
error
(
"工作流返回错误"
);
}
JobEvaluation
strategy
=
workflowObj
.
getObject
(
"Strategy"
,
JobEvaluation
.
class
);
MatchResult
matching
=
workflowObj
.
getObject
(
"matching"
,
MatchResult
.
class
);
String
jdMd
=
setJdMd
(
resultVo
.
getJdHtml
());
resultVo
.
setJdMd
(
jdMd
);
resultVo
.
setScore
(
Objects
.
toString
(
matching
.
getComprehensiveMatchScore
(),
"0"
));
resultVo
.
setDetails
(
matching
.
getComprehensiveMatchRecommendation
());
resultVo
.
setMatchResult
(
matching
);
resultVo
.
setJobEvaluation
(
strategy
);
// 更新进度
int
currentProcessed
=
processedCount
.
incrementAndGet
();
int
progress
=
baseProgress
+
(
int
)
Math
.
min
(
50
,
currentProcessed
*
progressPerJob
);
//String progressTip = String.format("已完成岗位匹配分析:%d%%", progress);
// 新增:Redis 推送逻辑
long
currentTime
=
System
.
currentTimeMillis
();
if
(
currentTime
-
lastPushTime
.
get
()
>
3000
)
{
// 大于3秒才发送
String
companyName
=
resultVo
.
getCompanyName
();
// 获取公司名称
String
jobTitle
=
resultVo
.
getJobTitle
();
// 获取岗位名称
String
tip
=
String
.
format
(
"小职正通过智能模型评估您与 %s · %s 的匹配度…"
,
companyName
,
jobTitle
);
addProcess
(
userId
,
new
ProcessItem
(
"delta"
,
tip
,
progress
,
DateTimeWrapper
.
now
().
toString
()));
// 调用你原来的 Redis 推送方法
lastPushTime
.
set
(
currentTime
);
// 更新上次发送时间
}
if
(
Integer
.
parseInt
(
resultVo
.
getScore
())
>
59
)
{
matchResults
.
add
(
resultVo
);
}
}
public
String
setJdMd
(
String
jdHtml
)
{
//ChatLanguageModel chatModel = LlmModelConfig.getChatLanguageModel();
//JdWriter writer = AiServices.create(JdWriter.class, chatModel);
//PromptTemplate promptTemplate = new PromptTemplate(jdHtml);
String
res
=
null
;
try
{
//res = writer.writer(promptTemplate.template());
QwenClientSingleton
instance
=
QwenClientSingleton
.
getInstance
(
"https://xzt-llm-dev.jinsehuaqin.com"
,
"token-abc123"
,
"/mnt/app/llm/Qwen3/Qwen/Qwen3-8B"
);
res
=
instance
.
chat
(
JdWriter
.
SYSTEM_MESSAGE
,
jdHtml
);
}
catch
(
OpenAiHttpException
e
)
{
throw
new
RuntimeException
(
e
);
}
catch
(
IOException
e
)
{
throw
new
RuntimeException
(
e
);
}
return
res
;
}
private
JSONObject
sendCozeWorkflow
(
FunctionPositionPortraitVo
resultVo
,
AiRecommendVo
aiRecommendVo
,
ResumeVo
resumeVo
)
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
,
String
>>
jobList
=
new
ArrayList
<>();
for
(
String
s
:
aiRecommendVo
.
getCareer
())
{
Map
<
String
,
String
>
levelNameMapByLevel3Name
=
categoryCacheManager
.
getLevelNameMapByLevel3Name
(
s
);
jobList
.
add
(
levelNameMapByLevel3Name
);
}
aiRecommendVo
.
setJobList
(
jobList
);
Map
<
String
,
Object
>
paramMap
=
Map
.
of
(
"input_job_details"
,
JSON
.
toJSONString
(
resultVo
),
"input_user_resume"
,
JSON
.
toJSONString
(
resumeVo
),
"input_job_willing"
,
JSON
.
toJSONString
(
aiRecommendVo
));
/*Map<String, Object> body = Map.of("workflow_id", "7513774076784312335",
"is_async", false,
"parameters", paramMap);*/
Map
<
String
,
Object
>
body
=
Map
.
of
(
"workflow_id"
,
"7553630384651632680"
,
"is_async"
,
false
,
"parameters"
,
paramMap
);
String
uri
=
"https://api.coze.cn/v1/workflow/run"
;
// 使用 Hutool 的 HttpUtil 发送 POST 请求
try
{
// 将Map转换为JSON字符串
String
jsonBody
=
JSON
.
toJSONString
(
body
);
cn
.
hutool
.
http
.
HttpRequest
request
=
cn
.
hutool
.
http
.
HttpRequest
.
post
(
uri
)
.
header
(
"Content-Type"
,
"application/json"
)
.
header
(
HttpHeaders
.
AUTHORIZATION
,
CozeConstsnts
.
COZE_TOKEN_BEARER
+
cozeToken
)
.
body
(
jsonBody
)
.
timeout
(
30000
);
// 设置超时时间 30 秒
cn
.
hutool
.
http
.
HttpResponse
response
=
request
.
execute
();
String
responseBody
=
response
.
body
();
JSONObject
jsonObject
=
JSON
.
parseObject
(
responseBody
);
if
(
jsonObject
!=
null
&&
jsonObject
.
getInteger
(
"code"
)
==
0
)
{
return
jsonObject
.
getJSONObject
(
"data"
);
}
else
{
log
.
error
(
"调用岗位智推分析工作流失败:\n"
+
responseBody
);
}
}
catch
(
Exception
e
)
{
log
.
error
(
"发送HTTP请求时发生异常: "
,
e
);
throw
new
Exception
(
"请求发送失败: "
+
e
.
getMessage
(),
e
);
}
return
null
;
}
/**
* 使用Elasticsearch Client更新岗位薪资信息(支持动态索引)
*
* @param positionDataId 岗位数据ID
* @param cityCode 城市代码,用于构建索引名称
* @param salaryRange 薪资范围
* @param pay 薪资数值
*/
public
void
updatePositionSalaryInfoWithClient
(
Long
positionDataId
,
String
cityCode
,
String
salaryRange
,
Integer
pay
)
{
try
{
// 构建索引坐标
IndexCoordinates
indexCoordinates
=
IndexCoordinates
.
of
(
Constants
.
ES_INDEX_POSITION
+
cityCode
);
// 构建更新脚本
org
.
springframework
.
data
.
elasticsearch
.
core
.
document
.
Document
document
=
org
.
springframework
.
data
.
elasticsearch
.
core
.
document
.
Document
.
create
();
document
.
put
(
"salaryRange"
,
salaryRange
);
document
.
put
(
"pay"
,
pay
);
// 构建更新请求
org
.
springframework
.
data
.
elasticsearch
.
core
.
query
.
UpdateQuery
updateQuery
=
org
.
springframework
.
data
.
elasticsearch
.
core
.
query
.
UpdateQuery
.
builder
(
String
.
valueOf
(
positionDataId
))
.
withDocument
(
document
)
.
withDocAsUpsert
(
true
)
.
build
();
// 执行更新操作
elasticsearchOperations
.
update
(
updateQuery
,
indexCoordinates
);
log
.
info
(
"成功更新岗位薪资信息, positionDataId: {}, cityCode: {}"
,
positionDataId
,
cityCode
);
}
catch
(
Exception
e
)
{
log
.
error
(
"更新岗位薪资信息失败, positionDataId: {}, cityCode: {}"
,
positionDataId
,
cityCode
,
e
);
throw
new
RuntimeException
(
"更新ES文档失败: "
+
e
.
getMessage
(),
e
);
}
}
/**
* 处理空推荐结果
*/
private
void
handleEmptyRecommendation
(
Long
analysisId
,
Long
userId
)
{
addProcess
(
userId
,
new
ProcessItem
(
"failure"
,
NOTIFICATION_TITLE_ERROR
,
100
,
DateTimeWrapper
.
now
().
toString
()));
aiAnalysisMapper
.
updateAnalysis
(
analysisId
,
2
,
userId
,
0
);
String
nowString
=
DateTimeWrapper
.
now
().
toString
();
}
private
final
static
String
NOTIFICATION_TITLE_ERROR
=
"岗位智推未成功"
;
private
static
final
List
<
String
>
PLATFORM_TIPS
=
Arrays
.
asList
(
"小职正在通过智能算法,从猎聘网为您搜寻最合适的岗位中..."
,
"小职正在通过智能算法,从boss直聘为您搜寻最合适的岗位中..."
,
"小职正在通过智能算法,从智联招聘为您搜寻最合适的岗位中..."
,
"小职正在通过智能算法,从前程无忧为您搜寻最合适的岗位中..."
,
"小职正在通过智能算法,从实习僧网为您搜寻最合适的岗位中..."
,
"小职正在通过智能算法,从应届生求职为您搜寻最合适的岗位中..."
,
"小职正在通过智能算法,从牛客网为您搜寻最合适的岗位中..."
);
/**
* 执行岗位推荐
*/
private
List
<
FunctionPositionPortraitVo
>
executePositionRecommend
(
List
<
RcommendParam
>
rcommendParamList
,
List
<
RcommendParam
>
qgRcommendParamList
,
List
<
Long
>
positionIdList
,
Long
userId
)
{
// 显示平台提示信息
for
(
String
platformTip
:
PLATFORM_TIPS
)
{
addProcess
(
userId
,
new
ProcessItem
(
"delta"
,
platformTip
,
50
,
DateTimeWrapper
.
now
().
toString
()));
try
{
Thread
.
sleep
(
1000
);
}
catch
(
InterruptedException
e
)
{
throw
new
RuntimeException
(
e
);
}
}
List
<
FunctionPositionPortraitVo
>
positionPortraitV2List
=
new
ArrayList
<>();
try
{
positionPortraitV2List
=
recommend
(
rcommendParamList
,
positionIdList
);
// 处理全国岗位查询时需要剔除的岗位
if
(
positionIdList
==
null
&&
CollectionUtil
.
isNotEmpty
(
positionPortraitV2List
))
{
positionIdList
=
positionPortraitV2List
.
stream
().
map
(
FunctionPositionPortraitV2:
:
getPositionDataId
).
toList
();
}
else
if
(
CollectionUtil
.
isNotEmpty
(
positionIdList
))
{
positionIdList
.
addAll
(
positionPortraitV2List
.
stream
().
map
(
FunctionPositionPortraitV2:
:
getPositionDataId
).
toList
());
}
positionPortraitV2List
.
addAll
(
recommend
(
qgRcommendParamList
,
positionIdList
));
}
catch
(
Exception
e
)
{
log
.
error
(
"岗位推荐过程中发生异常"
,
e
);
}
return
positionPortraitV2List
;
}
/**
* 岗位推荐
*
* @return
*/
private
List
<
FunctionPositionPortraitVo
>
recommend
(
List
<
RcommendParam
>
param
,
List
<
Long
>
positionIdList
)
{
List
<
FunctionPositionPortraitVo
>
result
=
new
CopyOnWriteArrayList
<>();
Set
<
Long
>
pIdSet
=
ConcurrentHashMap
.
newKeySet
();
// 创建线程池
ExecutorService
executor
=
new
ThreadPoolExecutor
(
16
,
// 核心线程数
32
,
// 最大线程数
0L
,
// 线程空闲时间
TimeUnit
.
MILLISECONDS
,
// 时间单位
new
LinkedBlockingQueue
<
Runnable
>()
// 任务队列
);
List
<
CompletableFuture
<
Void
>>
futures
=
new
ArrayList
<>();
for
(
RcommendParam
rcommendParam
:
param
)
{
CompletableFuture
<
Void
>
future
=
CompletableFuture
.
runAsync
(()
->
{
List
<
FunctionPositionPortraitVo
>
vos
=
queryRecommendPositionByCityAndJobTitle
(
rcommendParam
.
cityCode
,
rcommendParam
.
jobTitle
,
rcommendParam
.
pay
,
rcommendParam
.
resumeId
,
positionIdList
,
rcommendParam
.
floatList
,
rcommendParam
.
sketch
,
rcommendParam
.
isIntern
);
//使用重排序模型排序
List
<
RerankVo
>
rerank
=
rerank
(
vos
,
rcommendParam
.
sketch
);
for
(
RerankVo
vo
:
rerank
)
{
synchronized
(
pIdSet
)
{
if
(!
pIdSet
.
contains
(
vo
.
getPositionData
().
getPositionDataId
()))
{
vo
.
getPositionData
().
setRerankScore
(
vo
.
getScore
());
result
.
add
(
vo
.
getPositionData
());
pIdSet
.
add
(
vo
.
getPositionData
().
getPositionDataId
());
}
}
}
},
executor
);
futures
.
add
(
future
);
}
CompletableFuture
.
allOf
(
futures
.
toArray
(
new
CompletableFuture
[
0
])).
join
();
executor
.
shutdown
();
try
{
// 等待所有任务完成,最多等待5分钟
if
(!
executor
.
awaitTermination
(
5
,
TimeUnit
.
MINUTES
))
{
executor
.
shutdownNow
();
}
}
catch
(
InterruptedException
e
)
{
executor
.
shutdownNow
();
Thread
.
currentThread
().
interrupt
();
}
//单线程调用
/*List<FunctionPositionPortraitVo> result = new ArrayList<>();
Set<Long> pIdSet = new HashSet<>();
for (RcommendParam rcommendParam : param) {
List<FunctionPositionPortraitVo> vos = queryRecommendPositionByCityAndJobTitle(rcommendParam.cityCode, rcommendParam.jobTitle, rcommendParam.pay, rcommendParam.resumeId, positionIdList);
for (FunctionPositionPortraitVo vo : vos) {
if (!pIdSet.contains(vo.getPositionDataId())){
result.add(vo);
pIdSet.add(vo.getPositionDataId());
}
}
if (matchingSize(result) > 19){
List<FunctionPositionPortraitVo> list = result.stream().filter((functionPositionPortraitVo -> "匹配".equals(functionPositionPortraitVo.getWhetherItMatches()))).toList();
return list.subList(0, 20);
}
}
Map<Boolean, List<FunctionPositionPortraitVo>> collect = result.stream().collect(Collectors.groupingBy(vo->vo.getWhetherItMatches().equals("匹配")));
List<FunctionPositionPortraitVo> matchingList = new ArrayList<>();
if (collect.containsKey(true)){
matchingList.addAll(collect.get(true));
}
int size = 20 - matchingList.size();
for (int i = 0; i < size; i++) {
matchingList.add(collect.get(false).get(i));
}*/
return
result
;
}
private
List
<
RerankVo
>
rerank
(
List
<
FunctionPositionPortraitVo
>
positionPortraitV2s
,
String
resumeSketch
)
{
List
<
RerankVo
>
rerankVos
=
new
ArrayList
<>();
for
(
List
<
FunctionPositionPortraitVo
>
portraitV2s
:
Lists
.
partition
(
positionPortraitV2s
,
5
))
{
Map
<
String
,
Object
>
bodyMap
=
constructionBody
(
portraitV2s
,
resumeSketch
);
// 使用 Hutool 发送 POST 请求
String
url
=
"https://xzt-rma-dev.jinsehuaqin.com/score"
;
String
jsonBody
=
JSON
.
toJSONString
(
bodyMap
);
try
{
String
response
=
HttpUtil
.
post
(
url
,
jsonBody
);
log
.
info
(
"重排序接口访问结果:{}"
,
response
);
// 解析响应为 Map
Map
<
String
,
List
<
Float
>>
result
=
JSON
.
parseObject
(
response
,
new
TypeReference
<
Map
<
String
,
List
<
Float
>>>()
{
});
List
<
Float
>
scores
=
result
.
get
(
"scores"
);
for
(
int
i
=
0
;
i
<
scores
.
size
();
i
++)
{
Float
score
=
scores
.
get
(
i
);
rerankVos
.
add
(
new
RerankVo
(
portraitV2s
.
get
(
i
),
score
));
}
}
catch
(
Exception
e
)
{
log
.
error
(
"调用重排序接口失败:{}"
,
e
.
getMessage
(),
e
);
}
}
rerankVos
.
sort
((
o1
,
o2
)
->
o2
.
getScore
().
compareTo
(
o1
.
getScore
()));
return
rerankVos
;
}
private
final
static
String
RERANK_INSTRUCTION
=
"""
检索最符合求职者专业以及技能的岗位。
判断条件:求职者信息中不具备岗位要求的技能则不合适,求职者信息中不具备岗位要求的经验则不合适。
求职者信息:%s
"""
;
private
Map
<
String
,
Object
>
constructionBody
(
List
<
FunctionPositionPortraitVo
>
portraitV2s
,
String
resumeSketch
)
{
Map
<
String
,
Object
>
body
=
new
HashMap
<>();
body
.
put
(
"instruction"
,
RERANK_INSTRUCTION
+
resumeSketch
);
List
<
Map
<
String
,
Object
>>
pairs
=
new
ArrayList
<>(
portraitV2s
.
size
());
for
(
FunctionPositionPortraitV2
portraitV2
:
portraitV2s
)
{
Map
<
String
,
Object
>
pair
=
new
HashMap
<>();
Map
<
String
,
Object
>
query
=
new
HashMap
<>();
query
.
put
(
"jobTitle"
,
portraitV2
.
getJobTitle
());
query
.
put
(
"standardPosition"
,
portraitV2
.
getStandardPosition
());
query
.
put
(
"sketch"
,
portraitV2
.
getSketch
());
pair
.
put
(
"query"
,
JSON
.
toJSONString
(
query
));
Map
<
String
,
Object
>
document
=
new
HashMap
<>();
document
.
put
(
"jdHtml"
,
portraitV2
.
getJdHtml
());
pair
.
put
(
"document"
,
JSON
.
toJSONString
(
document
));
pairs
.
add
(
pair
);
}
body
.
put
(
"pairs"
,
pairs
);
return
body
;
}
/**
*
* @param cityCode 城市代码
* @param jobTitle 岗位名称
* @param pay 薪资
* @param resumeId 简历id
* @param positionIdList 岗位id
* @param titleVector 岗位向量
* @param resumeSketch 简历简述
* @param isIntern 是否实习
* @return
*/
private
List
<
FunctionPositionPortraitVo
>
queryRecommendPositionByCityAndJobTitle
(
String
cityCode
,
String
jobTitle
,
String
pay
,
Long
resumeId
,
List
<
Long
>
positionIdList
,
List
<
Float
>
titleVector
,
String
resumeSketch
,
String
isIntern
)
{
List
<
FunctionPositionPortraitVo
>
resultList
=
new
ArrayList
<>();
//JobTitleVector titleVector = positionSynService.queryJobTitleVector(jobTitle);
//生成查询词向量
//ChatLanguageModel chatModel = LlmModelConfig.getChatLanguageModel();
if
(
titleVector
!=
null
)
{
BoolQuery
.
Builder
bool
=
QueryBuilders
.
bool
().
boost
(
1.0f
);
Query
multiMatch
=
QueryBuilders
.
matchPhrase
()
.
field
(
"standardPosition"
)
.
query
(
jobTitle
)
.
build
()
.
_toQuery
();
bool
.
must
(
multiMatch
);
if
(
CollectionUtil
.
isNotEmpty
(
positionIdList
))
{
Query
notIdQuery
=
QueryBuilders
.
terms
()
.
field
(
"positionDataId"
)
.
terms
(
termsBuilder
->
termsBuilder
.
value
(
positionIdList
.
stream
()
.
map
(
FieldValue:
:
of
)
.
toList
()))
.
build
()
.
_toQuery
();
bool
.
mustNot
(
notIdQuery
);
}
if
(
StringUtils
.
isNotBlank
(
isIntern
))
{
Query
isInternMatch
=
QueryBuilders
.
matchPhrase
()
.
field
(
"isIntern"
)
.
query
(
isIntern
)
.
build
()
.
_toQuery
();
bool
.
must
(
isInternMatch
);
}
if
(
cityCode
.
equals
(
"0000"
))
{
//查询全国岗位
Query
match
=
QueryBuilders
.
match
().
field
(
"workPlace"
).
query
(
"全国"
).
build
().
_toQuery
();
bool
.
must
(
match
);
}
//需要修改画像信息金额字段要分开
BoolQuery
.
Builder
salaryBool
=
QueryBuilders
.
bool
().
boost
(
1.0f
);
int
payInt
=
Integer
.
parseInt
(
pay
);
Query
query1
=
QueryBuilders
.
range
()
.
field
(
"pay"
)
.
gte
(
JsonData
.
of
(
payInt
-
1500
)).
lte
(
JsonData
.
of
(
payInt
+
5000
))
.
build
().
_toQuery
();
// 薪资为"面议"的条件
Query
negotiationQuery
=
QueryBuilders
.
term
()
.
field
(
"salaryRange"
)
.
value
(
"面议"
)
.
build
().
_toQuery
();
salaryBool
.
should
(
query1
);
salaryBool
.
should
(
negotiationQuery
);
salaryBool
.
minimumShouldMatch
(
"1"
);
bool
.
filter
(
salaryBool
.
build
().
_toQuery
());
//bool.filter(query1);
DateTimeFormatter
formatter
=
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd"
);
// 获取近20天的日期列表(从新到旧)
List
<
String
>
last20Days
=
IntStream
.
range
(
0
,
30
)
.
mapToObj
(
i
->
LocalDate
.
now
().
minusDays
(
i
))
.
map
(
date
->
date
.
format
(
formatter
))
.
toList
();
Query
publicTimeQuery
=
QueryBuilders
.
terms
()
.
field
(
"publicTime"
)
.
terms
(
termsBuilder
->
termsBuilder
.
value
(
last20Days
.
stream
()
.
map
(
FieldValue:
:
of
)
.
toList
()))
.
build
()
.
_toQuery
();
bool
.
must
(
publicTimeQuery
);
KnnQuery
query
=
KnnQuery
.
of
(
k
->
k
.
field
(
"featureVector"
)
.
boost
(
0.5f
)
.
k
(
100
)
.
numCandidates
(
6000
)
.
queryVector
(
titleVector
).
filter
(
bool
.
build
().
_toQuery
()));
NativeQuery
build
=
NativeQuery
.
builder
()
//.withQuery(bool.build()._toQuery())
//.withQuery(functionScoreQuery)
.
withKnnQuery
(
query
)
.
withSourceFilter
(
new
SourceFilter
()
{
@Override
public
String
[]
getIncludes
()
{
return
new
String
[]{
"positionDataId"
,
"jobTitle"
,
"industry"
,
"standardPosition"
,
"workPlace"
,
"natureOfPost"
,
"salaryRange"
,
"companyName"
,
"companyNature"
,
"companySize"
,
"expDemand"
,
"eduDemand"
,
"publicPlatform"
,
"platformUrl"
,
"duty"
,
"skill"
,
"promote"
,
"mild"
,
"jdHtml"
,
"sketch"
,
"pay"
};
}
@Override
public
String
[]
getExcludes
()
{
return
new
String
[
0
];
}
})
.
withTrackTotalHits
(
true
)
.
withSearchType
(
null
)
.
withPageable
(
Pageable
.
ofSize
(
100
))
.
build
();
SearchHits
<
FunctionPositionPortraitV2
>
search
=
elasticsearchOperations
.
search
(
build
,
FunctionPositionPortraitV2
.
class
,
IndexCoordinates
.
of
(
Constants
.
ES_INDEX_POSITION
+
cityCode
));
//获取查询的数据
for
(
SearchHit
<
FunctionPositionPortraitV2
>
hit
:
search
)
{
FunctionPositionPortraitVo
vo
=
new
FunctionPositionPortraitVo
();
BeanUtils
.
copyProperties
(
hit
.
getContent
(),
vo
);
vo
.
setFeatureVector
(
null
);
//vo.setSketch(null);
vo
.
setResumeId
(
String
.
valueOf
(
resumeId
));
vo
.
setResumeSketch
(
resumeSketch
);
vo
.
setScoreFloat
(
hit
.
getScore
());
vo
.
setCityCode
(
cityCode
);
resultList
.
add
(
vo
);
}
}
log
.
info
(
"推荐岗位数据完成:{}条"
,
resultList
.
size
());
if
(
resultList
.
size
()
<
100
)
{
List
<
FunctionPositionPortraitVo
>
functionPositionPortraitVos
=
queryRecommendPositionByLevel2
(
cityCode
,
jobTitle
,
pay
,
resumeId
,
positionIdList
,
titleVector
,
resumeSketch
,
isIntern
);
//将resultList使用functionPositionPortraitVos补齐到100个
resultList
.
addAll
(
functionPositionPortraitVos
);
}
return
resultList
.
size
()
>
100
?
resultList
.
subList
(
0
,
100
)
:
resultList
;
}
/**
* 普通查询岗位数量不足时使用关联岗位查询
*
* @param cityCode
* @param jobTitle
* @param pay
* @param resumeId
* @param positionIdList
* @param titleVector
* @param resumeSketch
* @return
*/
private
List
<
FunctionPositionPortraitVo
>
queryRecommendPositionByLevel2
(
String
cityCode
,
String
jobTitle
,
String
pay
,
Long
resumeId
,
List
<
Long
>
positionIdList
,
List
<
Float
>
titleVector
,
String
resumeSketch
,
String
isIntern
)
{
List
<
FunctionPositionPortraitVo
>
resultList
=
new
ArrayList
<>();
BoolQuery
.
Builder
bool
=
QueryBuilders
.
bool
().
boost
(
1.0f
);
List
<
String
>
byLevel3Name
=
categoryCacheManager
.
getLevel2NameByLevel3Name
(
jobTitle
);
BoolQuery
.
Builder
byLevel3NameBool
=
QueryBuilders
.
bool
().
boost
(
1.0f
);
;
for
(
String
level3Name
:
byLevel3Name
)
{
if
(!
level3Name
.
equals
(
jobTitle
))
{
Query
match
=
QueryBuilders
.
matchPhrase
()
.
field
(
"standardPosition"
)
.
query
(
level3Name
)
.
build
()
.
_toQuery
();
byLevel3NameBool
.
should
(
match
);
// should = OR
}
}
byLevel3NameBool
.
minimumShouldMatch
(
"1"
);
bool
.
must
(
byLevel3NameBool
.
build
().
_toQuery
());
Query
multiMatch
=
QueryBuilders
.
terms
()
.
field
(
"standardPosition"
)
.
terms
(
termsBuilder
->
termsBuilder
.
value
(
byLevel3Name
.
stream
()
.
map
(
FieldValue:
:
of
)
.
toList
()))
.
build
()
.
_toQuery
();
bool
.
must
(
multiMatch
);
if
(
CollectionUtil
.
isNotEmpty
(
positionIdList
))
{
Query
notIdQuery
=
QueryBuilders
.
terms
()
.
field
(
"positionDataId"
)
.
terms
(
termsBuilder
->
termsBuilder
.
value
(
positionIdList
.
stream
()
.
map
(
FieldValue:
:
of
)
.
toList
()))
.
build
()
.
_toQuery
();
bool
.
mustNot
(
notIdQuery
);
}
if
(
StringUtils
.
isNotBlank
(
isIntern
)){
Query
isInternMatch
=
QueryBuilders
.
matchPhrase
()
.
field
(
"isIntern"
)
.
query
(
isIntern
)
.
build
()
.
_toQuery
();
bool
.
must
(
isInternMatch
);
}
if
(
cityCode
.
equals
(
"0000"
))
{
//查询全国岗位
Query
match
=
QueryBuilders
.
match
().
field
(
"workPlace"
).
query
(
"全国"
).
build
().
_toQuery
();
bool
.
must
(
match
);
}
//需要修改画像信息金额字段要分开
BoolQuery
.
Builder
salaryBool
=
QueryBuilders
.
bool
().
boost
(
1.0f
);
int
payInt
=
Integer
.
parseInt
(
pay
);
Query
query1
=
QueryBuilders
.
range
()
.
field
(
"pay"
)
.
gte
(
JsonData
.
of
(
payInt
-
1500
)).
lte
(
JsonData
.
of
(
payInt
+
5000
))
.
build
().
_toQuery
();
// 薪资为"面议"的条件
Query
negotiationQuery
=
QueryBuilders
.
term
()
.
field
(
"salaryRange"
)
.
value
(
"面议"
)
.
build
().
_toQuery
();
salaryBool
.
should
(
query1
);
salaryBool
.
should
(
negotiationQuery
);
salaryBool
.
minimumShouldMatch
(
"1"
);
bool
.
filter
(
salaryBool
.
build
().
_toQuery
());
DateTimeFormatter
formatter
=
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd"
);
// 获取近20天的日期列表(从新到旧)
List
<
String
>
last20Days
=
IntStream
.
range
(
0
,
30
)
.
mapToObj
(
i
->
LocalDate
.
now
().
minusDays
(
i
))
.
map
(
date
->
date
.
format
(
formatter
))
.
toList
();
Query
publicTimeQuery
=
QueryBuilders
.
terms
()
.
field
(
"publicTime"
)
.
terms
(
termsBuilder
->
termsBuilder
.
value
(
last20Days
.
stream
()
.
map
(
FieldValue:
:
of
)
.
toList
()))
.
build
()
.
_toQuery
();
bool
.
must
(
publicTimeQuery
);
KnnQuery
query
=
KnnQuery
.
of
(
k
->
k
.
field
(
"featureVector"
)
.
boost
(
0.5f
)
.
k
(
100
)
.
numCandidates
(
6000
)
.
queryVector
(
titleVector
).
filter
(
bool
.
build
().
_toQuery
()));
NativeQuery
build
=
NativeQuery
.
builder
()
//.withQuery(bool.build()._toQuery())
//.withQuery(functionScoreQuery)
.
withKnnQuery
(
query
)
.
withSourceFilter
(
new
SourceFilter
()
{
@Override
public
String
[]
getIncludes
()
{
return
new
String
[]{
"positionDataId"
,
"jobTitle"
,
"industry"
,
"standardPosition"
,
"workPlace"
,
"natureOfPost"
,
"salaryRange"
,
"companyName"
,
"companyNature"
,
"companySize"
,
"expDemand"
,
"eduDemand"
,
"publicPlatform"
,
"platformUrl"
,
"duty"
,
"skill"
,
"promote"
,
"mild"
,
"jdHtml"
,
"sketch"
,
"pay"
};
}
@Override
public
String
[]
getExcludes
()
{
return
new
String
[
0
];
}
})
.
withTrackTotalHits
(
true
)
.
withSearchType
(
null
)
.
withPageable
(
Pageable
.
ofSize
(
100
))
.
build
();
SearchHits
<
FunctionPositionPortraitV2
>
search
=
elasticsearchOperations
.
search
(
build
,
FunctionPositionPortraitV2
.
class
,
IndexCoordinates
.
of
(
Constants
.
ES_INDEX_POSITION
+
cityCode
));
//获取查询的数据
for
(
SearchHit
<
FunctionPositionPortraitV2
>
hit
:
search
)
{
FunctionPositionPortraitVo
vo
=
new
FunctionPositionPortraitVo
();
BeanUtils
.
copyProperties
(
hit
.
getContent
(),
vo
);
vo
.
setFeatureVector
(
null
);
//vo.setSketch(null);
vo
.
setResumeId
(
String
.
valueOf
(
resumeId
));
vo
.
setResumeSketch
(
resumeSketch
);
vo
.
setScoreFloat
(
hit
.
getScore
());
vo
.
setCityCode
(
cityCode
);
resultList
.
add
(
vo
);
}
return
resultList
;
}
/**
* 构建全国推荐参数
*/
private
List
<
RcommendParam
>
buildNationalRecommendParams
(
AiRecommendVo
aiRecommendVo
,
List
<
FunctionResumeSketch
>
resumeSketchList
)
{
String
jobTypeString
=
convertJobType
(
aiRecommendVo
.
getJobType
());
String
finalJobTypeString
=
jobTypeString
;
List
<
RcommendParam
>
qgRcommendParamList
=
new
ArrayList
<>();
for
(
String
jobTitle
:
aiRecommendVo
.
getCareer
())
{
for
(
FunctionResumeSketch
resumeSketch
:
resumeSketchList
)
{
RcommendParam
rcommendParam
=
new
RcommendParam
();
rcommendParam
.
cityCode
=
"0000"
;
rcommendParam
.
jobTitle
=
jobTitle
;
rcommendParam
.
pay
=
aiRecommendVo
.
getPay
();
rcommendParam
.
isIntern
=
finalJobTypeString
;
rcommendParam
.
resumeId
=
resumeSketch
.
getResumeId
();
rcommendParam
.
floatList
=
JSON
.
parseObject
(
resumeSketch
.
getVectorQuantity
(),
new
TypeReference
<
List
<
Float
>>()
{});
rcommendParam
.
sketch
=
resumeSketch
.
getSketch
();
qgRcommendParamList
.
add
(
rcommendParam
);
}
}
return
qgRcommendParamList
;
}
/**
* 构建推荐参数
*/
private
List
<
RcommendParam
>
buildRecommendParams
(
AiRecommendVo
aiRecommendVo
,
List
<
FunctionResumeSketch
>
resumeSketchList
)
{
Map
<
String
,
String
>
cityMap
=
extractProfileData
(
aiRecommendVo
);
String
jobTypeString
=
convertJobType
(
aiRecommendVo
.
getJobType
());
List
<
RcommendParam
>
rcommendParamList
=
new
ArrayList
<>();
String
finalJobTypeString
=
jobTypeString
;
cityMap
.
forEach
((
k
,
v
)
->
{
for
(
String
jobTitle
:
aiRecommendVo
.
getCareer
())
{
for
(
FunctionResumeSketch
resumeSketch
:
resumeSketchList
)
{
RcommendParam
rcommendParam
=
new
RcommendParam
();
rcommendParam
.
cityCode
=
v
;
rcommendParam
.
jobTitle
=
jobTitle
;
rcommendParam
.
pay
=
aiRecommendVo
.
getPay
();
rcommendParam
.
isIntern
=
finalJobTypeString
;
rcommendParam
.
resumeId
=
resumeSketch
.
getResumeId
();
rcommendParam
.
floatList
=
JSON
.
parseObject
(
resumeSketch
.
getVectorQuantity
(),
new
TypeReference
<
List
<
Float
>>()
{});
rcommendParam
.
sketch
=
resumeSketch
.
getSketch
();
rcommendParamList
.
add
(
rcommendParam
);
}
}
});
return
rcommendParamList
;
}
/**
* 转换工作类型
*/
private
String
convertJobType
(
String
jobType
)
{
if
(
StringUtils
.
isNotBlank
(
jobType
))
{
if
(
jobType
.
equals
(
"1"
))
{
return
"是"
;
}
if
(
jobType
.
equals
(
"2"
))
{
return
"否"
;
}
}
return
null
;
}
/**
* 获取历史推荐岗位ID
*/
private
List
<
Long
>
getHistoryPositionIds
(
Long
analysisId
)
{
List
<
Long
>
positionIdList
=
this
.
positionRecommendMapper
.
selectPositionIdList
(
analysisId
);
if
(
CollectionUtil
.
isEmpty
(
positionIdList
))
{
positionIdList
=
null
;
}
return
positionIdList
;
}
/**
* 获取简历画像
*/
private
List
<
FunctionResumeSketch
>
getResumeSketch
(
Long
analysisId
,
Long
userId
,
Long
resumeId
)
{
addProcess
(
userId
,
new
ProcessItem
(
"delta"
,
"小职正在通过智能分析,为您构建简历画像中..."
,
12
,
DateTimeWrapper
.
now
().
toString
()));
FunctionResumeSketch
resumeSketchR
=
null
;
try
{
resumeSketchR
=
resumeMakeService
.
queryResumeSketch
(
resumeId
);
}
catch
(
Exception
e
)
{
addProcess
(
userId
,
new
ProcessItem
(
"error"
,
"获取简历失败,结束推荐岗位"
,
0
,
DateTimeWrapper
.
now
().
toString
()));
log
.
error
(
"获取简历向量失败"
);
aiAnalysisMapper
.
updateAnalysis
(
analysisId
,
0
,
userId
,
0
);
return
null
;
}
List
<
FunctionResumeSketch
>
resumeSketchList
=
new
ArrayList
<>();
resumeSketchList
.
add
(
resumeSketchR
);
return
resumeSketchList
;
}
/**
* 获取简历信息
*/
private
ResumeVo
getResumeInfo
(
Long
analysisId
,
Long
userId
,
Long
resumeId
)
{
ResumeVo
resumeVo
=
null
;
try
{
resumeVo
=
newEditionResumeService
.
queryNewEditionResumeId
(
String
.
valueOf
(
resumeId
)).
join
();
}
catch
(
Exception
e
)
{
addProcess
(
userId
,
new
ProcessItem
(
"error"
,
"获取简历失败,结束推荐岗位"
,
0
,
DateTimeWrapper
.
now
().
toString
()));
log
.
error
(
"获取简历数据失败"
);
aiAnalysisMapper
.
updateAnalysis
(
analysisId
,
0
,
userId
,
0
);
return
null
;
}
addProcess
(
userId
,
new
ProcessItem
(
"delta"
,
"小职正在智能分析您的简历内容中..."
,
3
,
DateTimeWrapper
.
now
().
toString
()));
return
resumeVo
;
}
/**
* 准备推荐数据
*/
private
AiRecommendVo
prepareRecommendationData
(
Long
analysisId
,
Long
userId
,
Long
resumeId
)
{
// 获取求职意向
AiRecommendVo
aiRecommendVo
=
aiAnalysisService
.
queryAnalysisData
(
analysisId
);
// 获取城市信息
Map
<
String
,
String
>
cityMap
=
extractProfileData
(
aiRecommendVo
);
if
(
CollectionUtil
.
isEmpty
(
cityMap
))
{
addProcess
(
userId
,
new
ProcessItem
(
"error"
,
"获取城市失败,结束推荐岗位"
,
0
,
DateTimeWrapper
.
now
().
toString
()));
log
.
error
(
"获取城市失败,结束推荐岗位"
);
aiAnalysisMapper
.
updateAnalysis
(
analysisId
,
0
,
userId
,
0
);
return
null
;
}
return
aiRecommendVo
;
}
/**
* 添加推荐过程
*
* @param userId
* @param item
*/
private
void
addProcess
(
Long
userId
,
ProcessItem
item
)
{
redisTemplate
.
opsForList
().
rightPush
(
RECOMMEND_POSITION_PROCESS
.
formatted
(
userId
),
JSON
.
toJSONString
(
item
));
//设置10天过期
redisTemplate
.
expire
(
RECOMMEND_POSITION_PROCESS
.
formatted
(
userId
),
10
,
TimeUnit
.
DAYS
);
}
/**
* 提取求职意向的城市信息
*
* @param aiRecommendVo
* @return
*/
private
Map
<
String
,
String
>
extractProfileData
(
AiRecommendVo
aiRecommendVo
)
{
Map
<
String
,
String
>
cityMap
=
new
HashMap
<>();
for
(
String
city
:
aiRecommendVo
.
getTargetCity
())
{
String
cityCode
=
cityDataMapper
.
getCityCodeByName
(
city
);
if
(
cityCode
==
null
)
{
log
.
error
(
"{}获取城市编码失败"
,
city
);
continue
;
}
cityMap
.
put
(
city
,
cityCode
);
}
return
cityMap
;
}
/**
* 岗位智能推过程key
*/
private
final
static
String
RECOMMEND_POSITION_PROCESS
=
"recommend:position:process:%s"
;
/**
* 删除推荐过程
*
* @param userId
*/
private
void
delProcess
(
Long
userId
)
{
redisTemplate
.
delete
(
RECOMMEND_POSITION_PROCESS
.
formatted
(
userId
));
}
class
RcommendParam
{
//String cityCode, String jobTitle, String pay, String resumeId
String
cityCode
;
String
jobTitle
;
String
pay
;
String
isIntern
;
Long
resumeId
;
Integer
internTime
;
private
Integer
jobType
;
private
List
<
Integer
>
companySize
;
private
Integer
workTime
;
private
List
<
Float
>
floatList
;
private
String
sketch
;
}
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/resume/NewEditionResumeService.java
View file @
14c56833
package
com
.
bkty
.
system
.
service
.
resume
;
package
com
.
bkty
.
system
.
service
.
resume
;
import
com.bkty.system.domain.dto.*
;
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.ResumeByPdfVo
;
import
com.bkty.system.domain.vo.ResumeModelVo
;
import
com.bkty.system.domain.vo.ResumeModelVo
;
import
com.bkty.system.domain.vo.ResumeVo
;
import
com.bkty.system.domain.vo.ResumeVo
;
...
@@ -120,4 +121,5 @@ public interface NewEditionResumeService {
...
@@ -120,4 +121,5 @@ public interface NewEditionResumeService {
*/
*/
void
reversalExperience
(
ResumeExpChangeDto
dto
)
throws
Exception
;
void
reversalExperience
(
ResumeExpChangeDto
dto
)
throws
Exception
;
}
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/resume/ResumeMakeService.java
0 → 100644
View file @
14c56833
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
);
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/service/resume/impl/ResumeMakeServiceImpl.java
0 → 100644
View file @
14c56833
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
;
}
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/java/com/bkty/system/utils/MariaDbAdtUtil.java
0 → 100644
View file @
14c56833
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
+
" 的记录"
);
}
}
}
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/resources/mapper/system/AiAnalysisMapper.xml
0 → 100644
View file @
14c56833
<?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
employmentBusiness-pc-modules/employmentBusiness-pc-system/src/main/resources/mapper/system/PositionRecommendMapper.xml
0 → 100644
View file @
14c56833
<?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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment