Merge remote-tracking branch 'origin/main'

This commit is contained in:
戈经莹
2026-07-29 21:53:33 +08:00
9 changed files with 24 additions and 1676 deletions

3
.gitignore vendored
View File

@@ -60,3 +60,6 @@ nbdist/
local.sh local.sh
.claude .claude
/test-reports/ /test-reports/
ruoyi-admin/src/main/resources/application-local.yml
ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/ESJobSearchImpl.java
ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/JobTitleSuggestServiceImpl.java

View File

@@ -1,158 +0,0 @@
# 项目相关配置
ruoyi:
# 名称
name: RuoYi
# 版本
version: 3.8.8
# 版权年份
copyrightYear: 2024
# 文件路径 示例( Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath
profile: /Users/chenyanchang/logs/ruoyi/uploadPath
# 获取ip地址开关
addressEnabled: false
# 验证码类型 math 数字计算 char 字符验证
captchaType: math
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.highgo.jdbc.Driver
druid:
# 主库数据源
# master:
# url: jdbc:highgo://124.243.245.42:5866/shz?useUnicode=true&characterEncoding=utf8&currentSchema=shz&stringtype=unspecified
# #username: syssso
# username: sysdba
# password: ZKR2024@comzkr
# 正式环境
# master:
# url: jdbc:highgo://39.98.44.136:6023/highgo?useUnicode=true&characterEncoding=utf8&currentSchema=shz&stringtype=unspecified
# username: sysdba
# password: Hello@2026
# 测试环境
master:
url: jdbc:highgo://47.111.103.66:5866/highgo?useUnicode=true&characterEncoding=utf8&currentSchema=shz&stringtype=unspecified
username: sysdba
password: Hello@2026
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
# 初始连接数
initialSize: 10
# 最小连接池数量
minIdle: 30
# 最大连接池数量
maxActive: 50
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置连接超时时间
connectTimeout: 30000
# 配置网络超时时间
socketTimeout: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT version()
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: ruoyi
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
redis:
# 本地
#host: 127.0.0.1
#port: 6379
host: 124.243.245.42
port: 5379
# 数据库索引
database: 0
# 密码(本地通常无密码)
password:
# 连接超时时间
timeout: 100s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# easy-es
easy-es:
enable: true
banner: false
address: 124.243.245.42:9200
global-config:
process-index-mode: manual
db-config:
refresh-policy: immediate
username: elastic
password: zkr2024@@.com
#ai
chat:
baseUrl: http://39.98.44.136:8082
chatUrl: /v1/chat/completions
chatDetailUrl: /core/chat/getPaginationRecords
chatHistoryUrl: /core/chat/getHistories
updateNameUrl: /core/chat/updateHistory
stickChatUrl: /core/chat/updateHistory
delChatUrl: /core/chat/delHistory
delAllChatUrl: /core/chat/clearHistories
guestUrl: /v1/chat/completions
praiseUrl: /core/chat/feedback/updateUserFeedback
appId: 67cd49095e947ae0ca7fadd8
apiKey: fastgpt-qMl63276wPZvKAxEkW77bur0sSJpmuC6Ngg9lzyEjufLhsBAurjT55j
model: qd-job-turbo
audioText:
asr: http://127.0.0.1:8000/asr/file
tts: http://127.0.0.1:19527/synthesize
#浪潮单点登录相关
#互联网端:
lc_web_auth:
appId: cloud-out-2fb6330e9c0843e1a1424efda5d604c0
appSecret: x14lueHbtLQL7Pz2G7gE4wcGCV6TDblO5xfeu9V2wGk=
getTokenUrl: http://218.31.252.15:9081/prod-psout-api/auth/token
getUserInfoUrl: http://218.31.252.15:9081/prod-psout-api/system/app/authorize/user/info
editUserInfoUrl: http://218.31.252.15:9081/prod-psout-api/auth/user/edit
#监管端:
lc_cms_auth:
appId: cloud-9793ee8a8c3d47b8871007ffc4128502
appSecret: Yi+NACK70UPg8rFvsnnfBUq1wcLD4nm6ilC4II/4C4k=
getTokenUrl: http://218.31.252.15:9081/prod-api/auth/token
getUserInfoUrl: http://218.31.252.15:9081/prod-api/system/app/authorize/user/info
resrtPwdUrl: http://218.31.252.15:9081/prod-api/ps/user/resetPwd/zkr
changeStatusUrl: http://218.31.252.15:9081/prod-api/ps/user/change/status/zkr

View File

@@ -240,6 +240,7 @@ public class AppJobController extends BaseController
String errorMsg = "描述中包含敏感词:" + String.join("", sensitiveWords); String errorMsg = "描述中包含敏感词:" + String.join("", sensitiveWords);
return AjaxResult.error(errorMsg, sensitiveWords); return AjaxResult.error(errorMsg, sensitiveWords);
} }
job.setDataSource("1");//系统自录
jobService.publishJob(job); jobService.publishJob(job);
return success(); return success();
} }

View File

@@ -147,6 +147,7 @@ public class CmsJobController extends BaseController
return AjaxResult.error(errorMsg, sensitiveWords); return AjaxResult.error(errorMsg, sensitiveWords);
} }
job.setJobStatus("0"); job.setJobStatus("0");
job.setDataSource("1");//系统自录
// 无敏感词,执行插入 // 无敏感词,执行插入
return toAjax(jobService.insertJob(job)); return toAjax(jobService.insertJob(job));
} }

View File

@@ -141,7 +141,7 @@ public class Job extends BaseEntity
@ApiModelProperty("是否发布 0未发布 1发布") @ApiModelProperty("是否发布 0未发布 1发布")
private Integer isPublish; private Integer isPublish;
@ApiModelProperty("数据来源 5本地石河子模板导入") @ApiModelProperty("数据来源 5本地石河子模板导入;本地录入1;互联网岗位上传2;")
private String dataSource; private String dataSource;
@ApiModelProperty("岗位链接") @ApiModelProperty("岗位链接")

View File

@@ -1011,6 +1011,7 @@ public class JobServiceImpl extends ServiceImpl<JobMapper,Job> implements IJobSe
job.setCreateBy(string); job.setCreateBy(string);
job.setCreateTime(formattedDate); job.setCreateTime(formattedDate);
job.setRowId(Long.valueOf(rowWork.getId())); job.setRowId(Long.valueOf(rowWork.getId()));
job.setDataSource("2");//互联网岗位上传
jobBatch.add(job); jobBatch.add(job);

View File

@@ -1,187 +0,0 @@
package com.ruoyi.cms.service.impl;
import com.ruoyi.cms.mapper.JobMapper;
import com.ruoyi.cms.service.IJobTitleSuggestService;
import com.ruoyi.cms.util.JobTitleSuggestTokenizer;
import com.ruoyi.common.core.redis.RedisCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 岗位名称 IK 分词联想服务实现。
*/
@Service
public class JobTitleSuggestServiceImpl implements IJobTitleSuggestService {
private static final Logger logger = LoggerFactory.getLogger(JobTitleSuggestServiceImpl.class);
/**
* Redis Hashfield 是 IK 分词value 是包含该分词的完整岗位名称列表。
*/
private static final String CACHE_KEY = "app:job:title:suggest";
private static final String BUILDING_CACHE_KEY = CACHE_KEY + ":building";
private static final String LOCK_KEY = CACHE_KEY + ":rebuild:lock";
private static final int DEFAULT_LIMIT = 10;
private static final int MAX_LIMIT = 20;
private static final int LOCK_EXPIRE_MINUTES = 30;
@Autowired
private JobMapper jobMapper;
@Autowired
private RedisCache redisCache;
/**
* 确保服务启动后 Redis 中已有一份缓存;后续由 Quartz 定时任务周期性重建。
*/
@PostConstruct
public void init() {
rebuildCache();
}
@Override
public List<String> suggest(String keyword, Integer limit) {
String normalizedKeyword = JobTitleSuggestTokenizer.normalize(keyword);
if (normalizedKeyword.isEmpty()) {
return Collections.emptyList();
}
int resultLimit = normalizeLimit(limit);
try {
Map<String, Object> tokenTitles = redisCache.getCacheMap(CACHE_KEY);
if (tokenTitles == null || tokenTitles.isEmpty()) {
return Collections.emptyList();
}
// Hash field 只来自 IK 分词;这里仅对已有词项做前缀过滤,不生成新的前缀词。
List<Map.Entry<String, Object>> matchedTokens = tokenTitles.entrySet().stream()
.filter(entry -> entry.getKey() != null)
.filter(entry -> entry.getKey().startsWith(normalizedKeyword))
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toList());
Set<String> result = new LinkedHashSet<>();
for (Map.Entry<String, Object> entry : matchedTokens) {
if (result.size() >= resultLimit) {
break;
}
// 先返回匹配到的 IK 分词本身。
result.add(entry.getKey());
// 再返回包含该 IK 分词的完整岗位名称。
appendTitles(result, entry.getValue(), resultLimit);
}
return new ArrayList<>(result).subList(0, Math.min(result.size(), resultLimit));
} catch (Exception e) {
// 联想属于非核心能力Redis 异常时返回空列表,不影响岗位主搜索。
logger.warn("查询岗位名称 IK 分词缓存失败keyword={}", keyword, e);
return Collections.emptyList();
}
}
@Override
public void rebuildCache() {
String lockValue = UUID.randomUUID().toString();
boolean lockAcquired = false;
try {
lockAcquired = redisCache.setIfAbsent(
LOCK_KEY, lockValue, LOCK_EXPIRE_MINUTES, TimeUnit.MINUTES);
if (!lockAcquired) {
logger.info("其他节点正在重建岗位名称 IK 分词缓存,本节点跳过");
return;
}
List<String> sourceList = jobMapper.selectValidJobTitles();
Map<String, List<String>> cacheData = buildTokenTitles(sourceList);
// 先写临时 Hash再原子替换正式 Hash避免接口读到半成品。
redisCache.deleteObject(BUILDING_CACHE_KEY);
if (cacheData.isEmpty()) {
redisCache.deleteObject(CACHE_KEY);
} else {
redisCache.setCacheMap(BUILDING_CACHE_KEY, cacheData);
redisCache.redisTemplate.rename(BUILDING_CACHE_KEY, CACHE_KEY);
}
logger.info("岗位名称 IK 分词缓存重建完成,有效岗位名称{}个IK 词{}个",
sourceList == null ? 0 : sourceList.size(), cacheData.size());
} catch (Exception e) {
logger.error("重建岗位名称 IK 分词缓存失败", e);
} finally {
if (lockAcquired) {
redisCache.deleteObject(LOCK_KEY);
}
}
}
private Map<String, List<String>> buildTokenTitles(List<String> sourceList) {
Map<String, Set<String>> tokenTitles = new TreeMap<>();
if (sourceList == null) {
return Collections.emptyMap();
}
for (String title : sourceList) {
String displayTitle = normalizeDisplayTitle(title);
if (displayTitle.isEmpty()) {
continue;
}
Set<String> tokens = JobTitleSuggestTokenizer.tokenize(displayTitle);
for (String token : tokens) {
tokenTitles.computeIfAbsent(token, key -> new TreeSet<>()).add(displayTitle);
}
}
Map<String, List<String>> result = new LinkedHashMap<>();
for (Map.Entry<String, Set<String>> entry : tokenTitles.entrySet()) {
result.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
return result;
}
private void appendTitles(Set<String> result, Object cachedTitles, int resultLimit) {
if (!(cachedTitles instanceof Iterable)) {
return;
}
for (Object cachedTitle : (Iterable<?>) cachedTitles) {
if (cachedTitle == null || result.size() >= resultLimit) {
break;
}
String title = String.valueOf(cachedTitle);
if (!title.isEmpty()) {
result.add(title);
}
}
}
private String normalizeDisplayTitle(String title) {
if (title == null) {
return "";
}
return title.trim().replaceAll("\\s+", " ");
}
private int normalizeLimit(Integer limit) {
if (limit == null) {
return DEFAULT_LIMIT;
}
return Math.max(1, Math.min(limit, MAX_LIMIT));
}
}

View File

@@ -44,16 +44,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="missedOutdoorFairCount" column="missed_outdoor_fair_count" /> <result property="missedOutdoorFairCount" column="missed_outdoor_fair_count" />
<collection property="companyContactList" ofType="com.ruoyi.common.core.domain.entity.CompanyContact" <collection property="companyContactList" ofType="com.ruoyi.common.core.domain.entity.CompanyContact"
resultMap="CompanyContactResult"/> select="selectCompanyContactsById"
column="company_id"/>
</resultMap> </resultMap>
<resultMap type="com.ruoyi.common.core.domain.entity.CompanyContact" id="CompanyContactResult"> <resultMap type="com.ruoyi.common.core.domain.entity.CompanyContact" id="CompanyContactResult">
<result property="contactPerson" column="con_contact_person"/> <result property="id" column="id" />
<result property="contactPersonPhone" column="con_contact_person_phone"/> <result property="companyId" column="company_id" />
<result property="companyId" column="con_company_id" /> <result property="contactPerson" column="contact_person"/>
<result property="id" column="con_company_contact_id" /> <result property="contactPersonPhone" column="contact_person_phone"/>
</resultMap> </resultMap>
<select id="selectCompanyContactsById" resultMap="CompanyContactResult">
select id, company_id, contact_person, contact_person_phone
from company_contact
where company_id = #{companyId}
and del_flag = '0'
</select>
<sql id="selectCompanyVo"> <sql id="selectCompanyVo">
select company_id, name, location, industry, scale, del_flag, create_by, create_time, update_by, update_time, remark,code,description,nature,company_nature,total_recruitment,registered_address,contact_person,contact_person_phone,is_abnormal,not_pass_reason,status,case when status='2' then update_time else null end reject_time,is_imp_company,imp_company_type,enterprise_type,legal_person,legal_id_card,legal_phone,is_hrs, select company_id, name, location, industry, scale, del_flag, create_by, create_time, update_by, update_time, remark,code,description,nature,company_nature,total_recruitment,registered_address,contact_person,contact_person_phone,is_abnormal,not_pass_reason,status,case when status='2' then update_time else null end reject_time,is_imp_company,imp_company_type,enterprise_type,legal_person,legal_id_card,legal_phone,is_hrs,
(select count(1) (select count(1)
@@ -155,10 +163,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and fc.del_flag = '0' and fc.del_flag = '0'
and fc.review_status = '1' and fc.review_status = '1'
and f.end_time &lt; CURRENT_TIMESTAMP and f.end_time &lt; CURRENT_TIMESTAMP
and coalesce(a.attendance_status, '0') &lt;&gt; '1') as missed_outdoor_fair_count, and coalesce(a.attendance_status, '0') &lt;&gt; '1') as missed_outdoor_fair_count
cct.contact_person as con_contact_person,cct.contact_person_phone as con_contact_person_phone, from company cp
cct.company_id as con_company_id,cct.id as con_company_contact_id from company cp
left join company_contact cct on cp.company_id=cct.company_id and cct.del_flag='0'
<where> cp.del_flag = '0' <where> cp.del_flag = '0'
<if test="name != null and name != ''"> and cp.name like concat('%', cast(#{name, jdbcType=VARCHAR} as varchar), '%')</if> <if test="name != null and name != ''"> and cp.name like concat('%', cast(#{name, jdbcType=VARCHAR} as varchar), '%')</if>
<if test="location != null and location != ''"> and cp.location = #{location}</if> <if test="location != null and location != ''"> and cp.location = #{location}</if>
@@ -250,9 +256,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and fc.review_status = '1' and fc.review_status = '1'
and f.end_time &lt; CURRENT_TIMESTAMP and f.end_time &lt; CURRENT_TIMESTAMP
and coalesce(a.attendance_status, '0') &lt;&gt; '1') as missed_outdoor_fair_count, and coalesce(a.attendance_status, '0') &lt;&gt; '1') as missed_outdoor_fair_count,
su.lc_userid,su.user_id,cct.contact_person as con_contact_person,cct.contact_person_phone as con_contact_person_phone,cct.company_id as con_company_id from company t su.lc_userid,su.user_id from company t
left join sys_user su on t.code=su.id_card and su.del_flag='0' left join sys_user su on t.code=su.id_card and su.del_flag='0'
left join company_contact cct on t.company_id=cct.company_id and cct.del_flag='0'
<where> t.del_flag = '0' and su.lc_userid is not null <where> t.del_flag = '0' and su.lc_userid is not null
<if test="name != null and name != ''"> and t.name like concat('%', cast(#{name, jdbcType=VARCHAR} as varchar), '%')</if> <if test="name != null and name != ''"> and t.name like concat('%', cast(#{name, jdbcType=VARCHAR} as varchar), '%')</if>
<if test="scale != null and scale != ''"> and t.scale = #{scale}</if> <if test="scale != null and scale != ''"> and t.scale = #{scale}</if>