Compare commits

...

73 Commits

Author SHA1 Message Date
sh
6f08d63278 1.添加岗位取消接口
2.添加岗位取消列表
3.修改对应的统计
4.app_user添加orgType机构类型字段
2026-01-26 17:38:07 +08:00
sh
db11d5cb2a 岗位添加人员类型(残疾人或者退伍军人) 2026-01-24 16:22:59 +08:00
sh
735df67c77 添加模型查询岗位接口 2026-01-24 13:28:38 +08:00
sh
0194405957 处理语音转文字返回状态 2026-01-23 19:08:45 +08:00
sh
2820710fa6 添加/app/chat/guest 接口去除思维链 2026-01-23 13:22:14 +08:00
sh
c932369f69 修改es添加regionCode注释 2026-01-22 16:18:29 +08:00
sh
01ad333483 job表添加region_code字段 2026-01-22 16:07:59 +08:00
sh
fe32de4791 修改es查询问题 2026-01-22 15:55:10 +08:00
sh
88043f44e6 添加统计(汇总和各县统计) 2026-01-22 11:43:45 +08:00
sh
10d8980095 添加查询所有市的接口-添加返回字母 2026-01-21 19:36:11 +08:00
sh
e4bb4e55e3 添加查询所有市的接口 2026-01-21 17:43:35 +08:00
sh
5e3ec6ddae 添加岗位模板下载,岗位模板上传功能 2026-01-21 15:35:15 +08:00
sh
90c553a31d 提交es漏扫通过版本 2026-01-16 19:07:27 +08:00
sh
5e9a5d9a30 添加后端部署多节点,nginx节点检查方法/actuator/health 2026-01-16 19:06:11 +08:00
sh
fde43cc549 修改aichat地址 2026-01-16 17:06:54 +08:00
sh
5bface3047 修改内网asr和tts地址 2026-01-16 16:54:47 +08:00
sh
4051c67af5 添加一体机身份证登录(身份证和手机号) 2026-01-14 16:58:09 +08:00
sh
eaca90a521 修改es过滤只要有效的岗位都填充,企业先不管,因为有写岗位是微信抓取的,或者导入的 2026-01-14 13:27:19 +08:00
sh
afa6616a6f 修改企业登录报错问题 2026-01-12 12:32:39 +08:00
sh
8483250a65 修改一体机密码登录——先指定求职者,如果求职者查不到,就放开角色权限 2026-01-12 11:45:12 +08:00
sh
ddb2acf37a 修改一体机密码登录 2026-01-11 18:59:58 +08:00
sh
ed11350ed4 添加给浪潮提供的根据用户userid查询简历 2026-01-11 18:41:15 +08:00
sh
90681d1198 修改企业审核权限 2026-01-10 20:40:50 +08:00
sh
6e09130823 修改一体机登录密码验证 2026-01-10 18:15:22 +08:00
sh
4c6a8f30b0 ai对话添加websocket 2026-01-09 23:12:02 +08:00
sh
f28ef213b7 添加asr和tts 2026-01-08 23:06:33 +08:00
sh
1ec2d722eb 修改金蝶集群启动es 2026-01-08 12:43:04 +08:00
sh
c76ab3e4c9 修改报错问题 2026-01-05 17:20:15 +08:00
sh
a31fc4cc72 集成ai部分 2026-01-05 15:39:01 +08:00
sh
deb775ff5c 修改token密钥的复杂程度 2025-12-29 17:55:13 +08:00
sh
c5dcd0b3ce 先修改一版——接口返回身份证进行加密 2025-12-29 16:57:35 +08:00
sh
4ad2a85850 修改登录-添加先排除网格员 2025-12-24 12:51:56 +08:00
sh
0199c91dbd 添加最后登录时间 2025-12-23 18:25:50 +08:00
sh
4ff76a3100 修改小程序登录逻辑 2025-12-23 17:58:44 +08:00
sh
497e4f5001 修改地址 2025-12-19 18:12:27 +08:00
sh
1a45c37c44 修改地址 2025-12-19 17:58:31 +08:00
sh
82df2b5da9 添加企业端查询移动端用户-简历信息 2025-12-19 15:39:24 +08:00
sh
40d214f80b 修改推荐岗位——企业时,传递页数查询 2025-12-18 19:42:19 +08:00
sh
be1213da46 修改推荐岗位判断推荐条数 2025-12-17 18:42:44 +08:00
sh
922223c635 app_user添加dw_userid 2025-12-15 16:39:57 +08:00
sh
461e3adb12 修改161:11111改为161:80 2025-12-12 13:08:06 +08:00
sh
cb5835fcaf 修改转发人员配置、新增工作人员接口权限配置 2025-12-11 17:25:17 +08:00
sh
3822488f26 添加微信抓取判重的方法 2025-12-11 15:51:13 +08:00
sh
6385dd163b 获取移动端用户信息提示 2025-12-09 12:29:17 +08:00
sh
22d2e40264 岗位-详情时查询岗位申请人信息 2025-12-08 21:20:08 +08:00
sh
aaa7d5ec3f 修改附件上传-必须登录有token才能上传 2025-12-08 20:03:59 +08:00
sh
77397ff40a 修改pc端技能保存 2025-12-08 18:01:42 +08:00
sh
e04ef92a65 修改企业用户注册 2025-12-08 17:06:42 +08:00
sh
9e4191fc90 修改个人信息-保存一体机密码 2025-12-08 16:37:45 +08:00
sh
b61f280567 修改竞争力分析-添加area判断为空的情况 2025-12-08 16:25:12 +08:00
sh
fe9c72ff6c 修改es的查询数据,只获取有效的 2025-12-08 12:08:06 +08:00
sh
8b072cbcbe 修改nls、微信授权地址,通过修改状态来控制测试和正式 2025-12-08 10:49:01 +08:00
sh
b77af1ca0d 添加工作人员管理、转发人员配置-基础类 2025-12-06 18:42:36 +08:00
sh
97deb1aef6 修改swagger报错问题 2025-12-06 18:08:34 +08:00
sh
7d8a15bcdc 修改小程序注册方法-已测试改为调用新方法 2025-12-06 16:25:47 +08:00
sh
c2a8b4013b 修改微信登录方法-已测试改为调用新方法 2025-12-06 15:42:27 +08:00
sh
28e4a9983e 修改微信登录方法-未调用,需要测试 2025-12-06 13:32:36 +08:00
sh
ac12331e8d 修改微信登录方法-未调用,需要测试 2025-12-06 13:31:46 +08:00
sh
0fe02ff1a9 修改问题岗位-保存联系人 2025-12-04 20:59:10 +08:00
sh
6e5455ca25 岗位列表-添加岗位联系人 2025-12-04 19:30:51 +08:00
sh
879bd8c009 修改岗位详情-添加岗位联系人 2025-12-04 17:52:22 +08:00
sh
5c02fb16ca 修改岗位详情-添加岗位联系人 2025-12-04 16:17:02 +08:00
sh
cb5d4d00a7 修改移动端,展示附件信息 2025-12-04 16:00:26 +08:00
sh
c8f9b6547a 修改附件展示 2025-12-04 12:29:50 +08:00
sh
905bfa1996 修改互联网地址和修改附件 2025-12-04 11:03:41 +08:00
sh
4b06d2d0f9 修改正式环境服务器-附件信息(未完成) 2025-12-03 19:42:47 +08:00
sh
50a5c5e128 修改pc端查询推荐岗位逻辑
1.添加过期时间
2.限制条数
2025-12-03 17:55:21 +08:00
sh
6adda93d5d 查询岗位列表-返回附件信息 2025-12-03 16:15:15 +08:00
sh
5f76945039 修改新增岗位问题,修改上传附件问题 2025-12-03 15:35:58 +08:00
sh
d7e92d9b59 修改查看岗位详情-返回附件信息 2025-12-03 14:38:17 +08:00
sh
9080ae2e07 修改查看岗位详情-返回附件信息 2025-12-03 12:46:31 +08:00
sh
11aa1b11b1 修改测试环境地址 2025-12-03 12:03:09 +08:00
sh
9ef94da845 修改返回测试环境附件地址 2025-12-03 12:01:43 +08:00
108 changed files with 5591 additions and 296 deletions

View File

@@ -110,6 +110,11 @@
<artifactId>gson</artifactId>
<version>2.9.1</version>
</dependency>
<!-- 健康检查核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -79,7 +79,8 @@ public class SysLoginController
public AjaxResult appLogin(@RequestBody LoginBody loginBody)
{
AjaxResult ajax = AjaxResult.success();
ajax=loginService.appLogin(loginBody);
//ajax=loginService.appLogin(loginBody);
ajax=loginService.appLoginNew(loginBody);
return ajax;
}
@@ -94,6 +95,9 @@ public class SysLoginController
if(loginBody==null||StringUtils.isBlank(loginBody.getIdCard())){
return AjaxResult.error("请输入有效的身份证号!");
}
if(StringUtils.isBlank(loginBody.getUsername())){
return AjaxResult.error("姓名不能为空!");
}
AjaxResult ajax = AjaxResult.success();
ajax=loginService.idCardLogin(loginBody);
return ajax;

View File

@@ -261,7 +261,7 @@ public class SysUserController extends BaseController
}
@ApiOperation("企业资质审核")
@PreAuthorize("@ss.hasPermi('app:company:approval:list')")
@PreAuthorize("@ss.hasPermi('company:List:review')")
@PostMapping("/approval")
public AjaxResult approval(@RequestBody Company company)
{
@@ -277,7 +277,7 @@ public class SysUserController extends BaseController
sysUser.setPhonenumber(company1.getContactPersonPhone());
sysUser.setNickName(company1.getContactPersonPhone());
}else{
sysUser.setPassword("123456");
sysUser.setPassword("Abcd1234@");
sysUser.setUserName(company1.getName());
sysUser.setNickName(company1.getName());
}

View File

@@ -71,7 +71,7 @@ token:
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
secret: Abc123!@#Def456$%^Ghi789&*()Jkl0+-=MnoPqrStuVwxYz987$%^654@#$321!@#ZyxWvu
# 令牌有效期默认30分钟
expireTime: 30
@@ -140,6 +140,13 @@ mybatis-plus:
file:
upload-dir: /data/file
#nginx节点健康检查
management:
endpoints:
web:
exposure:
include: health
#微信小程序
wx:
appid: wx4aa34488b965a331
@@ -183,4 +190,24 @@ oauth:
tyQueryUnitInfo: http://10.98.80.146/qxgl_backend/security/get_organization_by_organizationid
connect-timeout: 10
read-timeout: 30
write-timeout: 30
write-timeout: 30
#ai
chat:
baseUrl: http://192.168.133.200:8080
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://192.168.133.200:8000/asr/file
tts: http://192.168.133.200:19527/synthesize

View File

@@ -89,12 +89,12 @@
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.14.0</version>
<version>7.17.24</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.14.0</version>
<version>7.17.24</version>
</dependency>
<dependency>
@@ -111,11 +111,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!--localDate-->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,296 @@
package com.ruoyi.cms.config;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.cms.domain.chat.ChatRequest;
import com.ruoyi.common.utils.StringUtils;
import lombok.var;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Component
public class ChatClient {
// 超时设置(单位:秒)
private static final int CONNECT_TIMEOUT = 30;
private static final int WRITE_TIMEOUT = 30;
private static final int READ_TIMEOUT = 300; // 流式响应不设置读取超时
// 单例 OkHttp 客户端(复用连接池)
private static final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
.build();
private static final String CHAT_ENDPOINT = "chat";
private static final String CHAT_HISTORY = "history";
public static final List<String> TEXT_FILE_EXTENSIONS= Arrays.asList(".txt", ".md", ".html", ".doc", ".docx", ".pdf", ".ppt", ".pptx", ".csv", ".xls", ".xlsx");
public static final List<String> IMAGE_FILE_EXTENSIONS=Arrays.asList(".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp");
private ChatConfig chatConfig;
@Autowired
public void setChatConfig(ChatConfig chatConfig) {
this.chatConfig = chatConfig;
}
@Value("${spring.profiles.active}")
private String env;
/**
* 发送流式聊天请求
* @param chatRequest 查询请求体
* @param callback 流式响应回调接口
*/
public void sendStreamingChat(ChatRequest chatRequest, StreamCallback callback) {
String url=chatConfig.getBaseUrl()+chatConfig.getChatUrl();
// 构建请求体
String jsonBody = buildChatRequestBody(chatRequest, CHAT_ENDPOINT);
// 构建请求
Request request = null;
try {
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"),jsonBody);
request = new Request.Builder()
.url(url)
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer " + chatConfig.getApiKey())
.post(body).build();
}catch (Exception e){
e.printStackTrace();
callback.onError(new RuntimeException("构建请求失败: " + e.getMessage(), e));
return;
}
// 发送异步请求
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
callback.onError(new RuntimeException("请求发送失败: " + e.getMessage(), e));
}
@Override
public void onResponse(Call call, Response response) throws IOException {
try {
if (!response.isSuccessful()) {
String errorBody = response.body() != null ? response.body().string() : "无错误信息";
String errorMsg = String.format("API 响应错误: 状态码=%d, 错误信息=%s",
response.code(), errorBody);
System.err.println(errorMsg); // 打印详细错误
callback.onError(new RuntimeException(errorMsg));
return;
}
// 处理流式响应
ResponseBody body = response.body();
if (body == null) {
callback.onError(new RuntimeException("响应体为空"));
return;
}
// 逐行读取 SSE 格式的响应
try (var bufferedSource = body.source()) {
while (!bufferedSource.exhausted()) {
String chunk = bufferedSource.readUtf8Line();
if (chunk != null && !chunk.trim().isEmpty()) {
callback.onData(chunk);
}
}
}
// 通知流结束
callback.onComplete();
} catch (Exception e) {
String errorMsg = "处理响应失败: " + e.getMessage();
System.err.println(errorMsg);
callback.onError(new RuntimeException(errorMsg, e));
} finally {
response.close();
}
}
});
}
/**
* 构建聊天请求的 JSON 体
*/
/**
* 构建聊天请求的 JSON 体
* 根据文档 [cite: 162-172] 修改为 Vision API 兼容格式
*/
private String buildChatRequestBody(ChatRequest chatRequest, String key) {
JSONObject chatObject = new JSONObject();
// 1. 设置会话ID (如果有)
if(StringUtils.isNotEmpty(chatRequest.getSessionId())){
chatObject.put("chatId", chatRequest.getSessionId());
}
if("chat".equals(key)){
// 基础参数设置
chatObject.put("stream", true);
chatObject.put("model", chatConfig.getModel()); // 需确保为 "qd-job-turbo"
chatObject.put("user", chatRequest.getSessionId());
// 2. 获取历史消息列表,如果为空则初始化
JSONArray messages = chatRequest.getMessages();
if (messages == null) {
messages = new JSONArray();
}
// 3. 构建当前用户的多模态消息内容 (Multimodal Content) [cite: 162]
JSONArray contentArray = new JSONArray();
// 3.1 添加文本内容 [cite: 166, 172]
if(StringUtils.isNotEmpty(chatRequest.getData())){
JSONObject textPart = new JSONObject();
textPart.put("type", "text");
textPart.put("text", chatRequest.getData());
contentArray.add(textPart);
}
// 3.2 添加文件内容 (图片、PDF、Excel等) [cite: 174, 180, 232]
// 文档说明image_url 字段兼容 PDF, Excel, PPT 等所有 OCR 支持的文件
if(!CollectionUtils.isEmpty(chatRequest.getFileUrl())){
for(String url : chatRequest.getFileUrl()){
String finalUrl = "";
if(Objects.equals(env, "pro")){
finalUrl = url.replace("https://fw.rc.qingdao.gov.cn/rgpp-api/api/ng", "http://10.213.6.207:19010");
}else {
finalUrl = url;
}
// 处理内网/外网地址映射 (保留你原有的逻辑)
JSONObject filePart = new JSONObject();
filePart.put("type", "image_url"); // 固定为 image_url [cite: 174]
JSONObject imageUrlObj = new JSONObject();
imageUrlObj.put("url", finalUrl); // 文件地址 [cite: 172]
filePart.put("image_url", imageUrlObj);
contentArray.add(filePart);
}
}
// 4. 将当前消息封装为 User Message 对象并加入消息列表 [cite: 151, 163]
// 只有当有内容(文本或文件)时才添加
if (!contentArray.isEmpty()) {
JSONObject currentUserMessage = new JSONObject();
currentUserMessage.put("role", "user");
currentUserMessage.put("content", contentArray); // content 为数组格式
messages.add(currentUserMessage);
}
// 5. 将完整的消息列表放入请求体 [cite: 135]
chatObject.put("messages", messages);
} else {
// 非 chat 场景的逻辑保留
chatObject.put("appId", chatConfig.getAppId());
}
return chatObject.toJSONString();
}
/**
* 简单的 JSON 转义处理(防止特殊字符破坏 JSON 格式)
*/
private String escapeJson(String value) {
if (value == null) return "";
return value
.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\b", "\\b")
.replace("\f", "\\f")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
/**
* 发送聊天请求并返回完整JSON响应
* @param chatRequest 用户输入的查询内容
* @return 完整的JSON响应字符串
* @throws IOException 网络请求异常
*/
public String sendChatGuest(ChatRequest chatRequest) throws IOException {
String url=chatConfig.getBaseUrl()+chatConfig.getGuestUrl();
JSONArray array = chatRequest.getMessages();
if(array==null||array.isEmpty()||array.size()==0){
array = new JSONArray();
}
JSONObject contentObject = new JSONObject();
contentObject.put("content","你是一个岗位招聘专家请根据用户的问题生成用户下一步想要提出的问题。需要以用户的口吻进行生成。结合上下文中用户提出的问题以及助手回复的答案需要猜测用户更进一步的需求例如期望的薪资期望的工作地点掌握的技能。生成的问题举例有没有薪资在9000以上的工作我的学历是本科。我希望找国企。注意不仅限于这些还要根据上下文。其次所有的问题应该限定在喀什。并且只生成3到4个。");
contentObject.put("role","system");
array.add(contentObject);
JSONObject jsonBody = new JSONObject();
jsonBody.put("stream",false);
jsonBody.put("model",chatConfig.getModel());
jsonBody.put("messages",array);
// 构建请求(使用非流式响应模式)
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), String.valueOf(jsonBody));
Request request = new Request.Builder()
.url(url)
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer " + chatConfig.getApiKey())
.post(requestBody).build();
// 发送同步请求
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
String errorBody = response.body() != null ? response.body().string() : "无错误信息";
String errorMsg = String.format("API 响应错误: 状态码=%d, 错误信息=%s",
response.code(), errorBody);
throw new IOException(errorMsg);
}
ResponseBody body = response.body();
if (body == null) {
throw new IOException("响应体为空");
}
JSONObject object = JSONObject.parseObject(body.string());
String choices = object.getString("choices");
if (choices != null && !choices.trim().isEmpty()) {
JSONArray jsonArray = JSONArray.parseArray(choices);
object = JSONObject.parseObject(jsonArray.getString(0));
object = object.getJSONObject("message");
String content = object.getString("content");// 消息内容
return content;
}
return body.string();
}
}
/**
* 流式响应回调接口
*/
public interface StreamCallback {
/**
* 接收分片数据
* @param chunk SSE 格式的分片数据
*/
void onData(String chunk);
/**
* 响应结束
*/
void onComplete();
/**
* 发生错误
* @param e 异常信息
*/
void onError(Throwable e);
}
}

View File

@@ -0,0 +1,115 @@
package com.ruoyi.cms.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "chat")
public class ChatConfig {
private String baseUrl;
private String chatUrl;
private String chatDetailUrl;
private String chatHistoryUrl;
private String updateNameUrl;
private String delChatUrl;
private String delAllChatUrl;
private String guestUrl;
private String praiseUrl;
private String apiKey;
private String appId;
private String model;
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getChatUrl() {
return chatUrl;
}
public void setChatUrl(String chatUrl) {
this.chatUrl = chatUrl;
}
public String getChatDetailUrl() {
return chatDetailUrl;
}
public void setChatDetailUrl(String chatDetailUrl) {
this.chatDetailUrl = chatDetailUrl;
}
public String getChatHistoryUrl() {
return chatHistoryUrl;
}
public void setChatHistoryUrl(String chatHistoryUrl) {
this.chatHistoryUrl = chatHistoryUrl;
}
public String getUpdateNameUrl() {
return updateNameUrl;
}
public void setUpdateNameUrl(String updateNameUrl) {
this.updateNameUrl = updateNameUrl;
}
public String getDelChatUrl() {
return delChatUrl;
}
public void setDelChatUrl(String delChatUrl) {
this.delChatUrl = delChatUrl;
}
public String getDelAllChatUrl() {
return delAllChatUrl;
}
public void setDelAllChatUrl(String delAllChatUrl) {
this.delAllChatUrl = delAllChatUrl;
}
public String getGuestUrl() {
return guestUrl;
}
public void setGuestUrl(String guestUrl) {
this.guestUrl = guestUrl;
}
public String getPraiseUrl() {
return praiseUrl;
}
public void setPraiseUrl(String praiseUrl) {
this.praiseUrl = praiseUrl;
}
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
}

View File

@@ -1,16 +1,20 @@
package com.ruoyi.cms.controller.app;
import com.ruoyi.cms.util.IdGenerator;
import com.ruoyi.cms.util.ProxyServerUtil;
import com.ruoyi.common.core.domain.entity.File;
import com.ruoyi.cms.service.IFileService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.SiteSecurityUtils;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@RestController
@@ -24,7 +28,10 @@ public class AppFileController extends BaseController {
@ApiOperation("上传文件")
@PostMapping("/upload")
public AjaxResult upload(@RequestParam("file") MultipartFile file, @RequestParam(value = "bussinessid",required = false) Long bussinessId) {
return fileService.upload(file,bussinessId);
if(!(SiteSecurityUtils.isLogin() || SecurityUtils.isLogin())){
return AjaxResult.error("未登录,请登录后在上传文件!");
}
return fileService.upload(file,bussinessId);
}
@ApiOperation("获取附件列表")
@@ -44,10 +51,15 @@ public class AppFileController extends BaseController {
@ApiOperation("上传文件")
@PostMapping("/uploadFile")
public AjaxResult uploadFile(@RequestParam("file") MultipartFile file, @RequestParam(value = "bussinessid",required = false) Long bussinessId) {
public AjaxResult uploadFile(@RequestParam("file") MultipartFile file, @RequestParam(value = "bussinessid",required = false) Long bussinessId, HttpServletRequest request) {
String proxyServer = ProxyServerUtil.getProxyServer(request);
System.out.println("获取服务器地址======================"+proxyServer);
if(!(SiteSecurityUtils.isLogin() || SecurityUtils.isLogin())){
return AjaxResult.error("未登录,请登录后在上传文件!");
}
if(bussinessId==null){
bussinessId=idGenerator.generateId();
}
return fileService.uploadFile(file,bussinessId);
return fileService.uploadFile(file,bussinessId,request);
}
}

View File

@@ -2,17 +2,16 @@ package com.ruoyi.cms.controller.app;
import com.ruoyi.cms.domain.ESJobDocument;
import com.ruoyi.cms.domain.Job;
import com.ruoyi.cms.domain.JobApply;
import com.ruoyi.cms.domain.query.ESJobSearch;
import com.ruoyi.cms.service.ICompanyService;
import com.ruoyi.cms.service.IESJobSearchService;
import com.ruoyi.cms.service.IJobCollectionService;
import com.ruoyi.cms.service.IJobService;
import com.ruoyi.cms.service.*;
import com.ruoyi.cms.util.RoleUtils;
import com.ruoyi.cms.util.sensitiveWord.SensitiveWordChecker;
import com.ruoyi.common.annotation.BussinessLog;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.SiteSecurityUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
@@ -21,6 +20,7 @@ import org.dromara.easyes.core.biz.EsPageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
@@ -42,6 +42,8 @@ public class AppJobController extends BaseController
private IESJobSearchService esJobSearchService;
@Autowired
private SensitiveWordChecker sensitiveWordChecker;
@Autowired
private IJobApplyService jobApplyService;
/**
* 查询岗位列表
@@ -143,14 +145,34 @@ public class AppJobController extends BaseController
list.setList(jobList);
return getTableDataInfo(list);
}
/**
* ai对话模型查询岗位数据(es查询)
* @param job
* @return
*/
@ApiOperation("添加模型查询岗位")
@GetMapping("/searchModelJobsList")
public TableDataInfo searchModelJobsList(ESJobSearch job)
{
EsPageInfo<ESJobDocument> list = esJobSearchService.nearJob(job);
List<ESJobDocument> jobList = list.getList();
jobList.forEach(it->it.setAppJobUrl(String.valueOf(it.getJobId())));
return getDataTable(jobList);
}
/**
* 获取岗位详细信息
*/
@ApiOperation("获取岗位详细信息")
@GetMapping(value = "/{jobId}")
public AjaxResult getInfo(@PathVariable("jobId") Long jobId)
public AjaxResult getInfo(@PathVariable("jobId") Long jobId, HttpServletRequest request)
{
Job job = jobService.selectJobByJobIdApp(jobId);
if (jobId == null) {
return AjaxResult.error("jobId不能为空");
}
//Job job = jobService.selectJobByJobIdApp(jobId);
Job job = jobService.selectHttpJobByJobIdApp(jobId,request);
return success(job);
}
@@ -232,4 +254,33 @@ public class AppJobController extends BaseController
}
return success(jobService.selectApplyJobUserList(jobId));
}
@ApiOperation("删除岗位申请")
@DeleteMapping("/applyJobCencal")
public AjaxResult applyJobCencal(@RequestBody JobApply apply){
if(apply==null){
return AjaxResult.error("参数为空!");
}
if (apply.getJobId() == null) {
return AjaxResult.error("岗位id为空!");
}
if(!SiteSecurityUtils.isLogin()){
return AjaxResult.error("用户未登录!");
}
if (apply.getUserId() == null) {
apply.setUserId(SiteSecurityUtils.getUserId());
}
return toAjax(jobApplyService.applyJobCencal(apply));
}
@ApiOperation("获取取消岗位岗位详情")
@GetMapping("/selectCencalList")
public TableDataInfo selectCencalList(){
JobApply queryApply = new JobApply();
if (queryApply.getUserId() == null) {
queryApply.setUserId(SiteSecurityUtils.getUserId());
}
List<Job> list=jobApplyService.selectCencalList(queryApply);
return getDataTable(list);
}
}

View File

@@ -1,14 +1,22 @@
package com.ruoyi.cms.controller.app;
import com.alibaba.nls.client.AccessToken;
import com.ruoyi.cms.handler.SpeechRecognizerAI;
import com.ruoyi.common.annotation.BussinessLog;
import com.ruoyi.common.config.AudioTextRequestClient;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.UnsupportedEncodingException;
import static com.ruoyi.common.enums.BusinessType.OTHER;
/**
* app语音Controller
@@ -21,17 +29,49 @@ import org.springframework.web.bind.annotation.RestController;
@Api(tags = "移动端:用户相关")
public class AppSpeechController extends BaseController
{
private String appKey = "4lFYn2yPsQymwGu8";
/*private String appKey = "4lFYn2yPsQymwGu8";
private String id = "LTAI5t9hhSqdDHqwH3RjgyYj";
private String secret = "ni5aW3vxrWouMwcGqJPfh9Uu56PBuv";
private String url = System.getenv().getOrDefault("NLS_GATEWAY_URL", "wss://nls-gateway-cn-shanghai.aliyuncs.com/ws/v1");
@ApiOperation("统计")
private String url = System.getenv().getOrDefault("NLS_GATEWAY_URL", AliyunNlsUtils.getNlsUrl()+"/ws/v1/");*/
@Autowired
AudioTextRequestClient audioTextRequestClient;
@BussinessLog(title = "语音转文字",businessType = OTHER)
@ApiOperation("语音转文字")
@PostMapping(value = "/asr")
public AjaxResult asr(@RequestParam("file") MultipartFile file){
try {
return AjaxResult.success("请求成功",audioTextRequestClient.getTextFromAudioFile(file));
}catch (Exception e){
e.printStackTrace();
return AjaxResult.error(e.getMessage());
}
}
@BussinessLog(title = "文字转语音",businessType = OTHER)
@ApiOperation("文字转语音")
@GetMapping(value = "/tts")
public ResponseEntity<byte[]> tts(@RequestParam("text") String text) throws UnsupportedEncodingException {
byte[] wavData = audioTextRequestClient.getAudioInputStreamFromText(text);
HttpHeaders headers = new HttpHeaders();
// WAV音频的标准MIME类型
headers.setContentType(MediaType.parseMediaType("audio/wav"));
// inline让浏览器在线播放而非下载
headers.setContentDispositionFormData("inline", System.currentTimeMillis() + ".wav");
// 设置内容长度
headers.setContentLength(wavData.length);
// 4. 返回音频数据
return new ResponseEntity<>(wavData, headers, HttpStatus.OK);
}
/*@ApiOperation("统计")
@GetMapping("/getToken")
public AjaxResult getToken()
{
SpeechRecognizerAI recognizerDemo = new SpeechRecognizerAI(appKey, id, secret, url);
AccessToken accessToken = recognizerDemo.getAccessToken();
String token = "wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1?appkey="+appKey+"&token="+accessToken.getToken();
String token = AliyunNlsUtils.getNlsUrl()+"/ws/v1/?appkey="+appKey+"&token="+accessToken.getToken();
return AjaxResult.success(token);
}
}*/
}

View File

@@ -76,6 +76,18 @@ public class AppUserController extends BaseController
AppUser appUser = appUserService.selectAppUserByUserId(SiteSecurityUtils.getUserId());
return AjaxResult.success(appUser);
}
@ApiOperation("企业查看简历")
@GetMapping("/userResume/{id}")
public AjaxResult getResume(@PathVariable(name = "id", required = true) Long userId)
{
if(userId==null){
return AjaxResult.error("用户id为空!");
}
AppUser appUser = appUserService.selectAppUserByUserId(userId);
return AjaxResult.success(appUser);
}
@ApiOperation("我的浏览")
@GetMapping("/review")
public TableDataInfo review(MineJobQuery jobQuery)

View File

@@ -0,0 +1,238 @@
package com.ruoyi.cms.controller.app;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.cms.domain.ai.AiChatHistory;
import com.ruoyi.cms.domain.chat.ChatRequest;
import com.ruoyi.cms.service.AiChatHistoryService;
import com.ruoyi.cms.util.ChatTextCleanUtil;
import com.ruoyi.common.annotation.BussinessLog;
import com.ruoyi.cms.config.ChatClient;
import com.ruoyi.cms.config.ChatConfig;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.SiteSecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static com.ruoyi.common.enums.BusinessType.QUERY;
@RestController
@RequestMapping(value = "/app/chat")
@Slf4j
@Api(tags = "移动端:ai对话")
public class ChatController extends BaseController {
@Autowired
ChatClient chatClient;
@Autowired
ChatConfig chatConfig;
@Autowired
AiChatHistoryService aiChatHistoryService;
//private final ExecutorService executor = Executors.newCachedThreadPool();
// 可优化线程池配置,避免无限创建线程
private final ExecutorService executor = new ThreadPoolExecutor(
2, // 核心线程数
10, // 最大线程数
30, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(50), // 任务队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略(避免任务丢失)
);
@BussinessLog(title = "查询用户的聊天历史记录",businessType = QUERY)
@ApiOperation("查询用户的聊天历史记录")
@GetMapping(value = "/getHistory")
public AjaxResult getChatHistoryList(AiChatHistory chatHistory) {
return AjaxResult.success(aiChatHistoryService.getList(chatHistory));
}
@BussinessLog(title = "查询用户的聊天详情",businessType = QUERY)
@ApiOperation("查询用户的聊天详情")
@GetMapping(value = "/detail")
public AjaxResult getChatDetail(ChatRequest request) {
return AjaxResult.success(aiChatHistoryService.getDetailList(request.getSessionId()));
}
// 处理前端聊天请求,返回 SSE 发射器
@ApiOperation("用户AI聊天")
@PostMapping("/chat")
@ResponseBody
@BussinessLog(title = "用户AI聊天",businessType = QUERY)
public SseEmitter chatStream(@RequestBody ChatRequest request) {
// 设置超时时间30分钟
SseEmitter emitter = new SseEmitter(1800000L);
long userId = 0;
if(ObjectUtil.isNotEmpty(request.getUserId())&&request.getUserId()!=0){
userId = request.getUserId();
}else{
try {
userId = SiteSecurityUtils.getLoginUser().getUserId();
}catch (Exception e) {
}
}
JSONObject contentObject = new JSONObject();
contentObject.put("content",request.getData());
contentObject.put("role","user");
JSONArray array = aiChatHistoryService.getChatHistoryData(request.getSessionId());
if(array==null||array.isEmpty()||array.size()==0){
array = new JSONArray();
}
array.add(contentObject);
request.setMessages(array);
List<String> answerList = new ArrayList<>();
long[] timeStart = {0};
// 异步处理请求并推送数据
executor.submit(() -> {
try {
// 2. 调用ChatClient的流式聊天方法正确传递参数
chatClient.sendStreamingChat(
request,
new ChatClient.StreamCallback() { // 使用内部类完整路径
@Override
public void onData(String chunk) {
try {
if(timeStart[0] == 0){
timeStart[0] = System.currentTimeMillis();
}
// 1. 处理SSE协议格式
String processedChunk = chunk.trim();
// 解析返回的chunk数据
String content = parseChatChunk(processedChunk);
if (StringUtils.isNotEmpty(content)) {
answerList.add(content);
}
emitter.send(processedChunk, MediaType.TEXT_EVENT_STREAM);
} catch (IOException e) {
emitter.completeWithError(e);
}
}
@Override
public void onComplete() {
emitter.complete();
}
@Override
public void onError(Throwable e) {
try {
emitter.send(SseEmitter.event()
.data("{\"status\":\"error\",\"message\":\"" + e.getMessage() + "\"}")
.name("error"));
} catch (IOException ex) {
// 记录日志
} finally {
emitter.completeWithError(e);
}
}
}
);
} catch (Exception e) {
emitter.completeWithError(e);
}
});
// 处理连接关闭
long finalUserId = userId;
emitter.onCompletion(() -> {
// 此处仅做资源清理,不关闭线程池
log.info("连接关闭,清理资源");
long timeEnd = System.currentTimeMillis();
double duration = (timeEnd - timeStart[0]) / 1000.0;
AiChatHistory chatHistory = new AiChatHistory();
chatHistory.setChatId(request.getSessionId());
chatHistory.setUserId((finalUserId==0)?null:finalUserId);
chatHistory.setAppId(chatConfig.getAppId());
chatHistory.setDataId(request.getDataId());
chatHistory.setTitle(request.getData());
chatHistory.setAnswerStringList(answerList);
chatHistory.setDurationSeconds(duration);
aiChatHistoryService.saveChatHistory(chatHistory);
});
return emitter;
}
@BussinessLog(title = "提供推测出的问询建议",businessType = QUERY)
@ApiOperation("提供推测出的问询建议")
@PostMapping(value = "/guest")
public AjaxResult getChatGuest(@RequestBody ChatRequest request) throws IOException {
JSONArray array = aiChatHistoryService.getChatHistoryData(request.getSessionId());
request.setMessages(array);
String result = chatClient.sendChatGuest(request);
//去除思维链
result = ChatTextCleanUtil.removeThinkChain(result);
try {
String[] strList = result.split("");
List<String> list = new ArrayList<>();
for(String str:strList){
if(StringUtils.isNotEmpty(str)){
str = str+"";
list.add(str);
}
}
return AjaxResult.success(list);
}catch (Exception e) {
return AjaxResult.error(e.getMessage());
}
}
// 解析返回的流式数据
private String parseChatChunk(String chunk) {
try {
String processed = chunk.trim();
if (processed.startsWith("data:")) {
processed = processed.substring("data:".length()).trim();
}
if (processed.isEmpty() || "[DONE]".equals(processed)) {
return null;
}
JSONObject json = JSONObject.parseObject(processed);
String choices = json.getString("choices");
if (choices != null && !choices.trim().isEmpty()) {
JSONArray jsonArray = JSONArray.parseArray(choices);
json = JSONObject.parseObject(jsonArray.getString(0));
json = json.getJSONObject("delta");
String content = json.getString("content");// 消息内容
return content;
}
return null;
} catch (Exception e) {
return null;
}
}
@PreDestroy
public void destroy() {
executor.shutdown(); // 应用退出前关闭线程池
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
executor.shutdownNow(); // 强制关闭未完成的任务
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
executor.shutdownNow();
log.error(e.getMessage());
}
}
}

View File

@@ -0,0 +1,330 @@
package com.ruoyi.cms.controller.app;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.cms.config.ChatClient;
import com.ruoyi.cms.config.ChatConfig;
import com.ruoyi.cms.domain.ai.AiChatHistory;
import com.ruoyi.cms.domain.chat.ChatRequest;
import com.ruoyi.cms.service.AiChatHistoryService;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* WebSocket AI 聊天处理器
*/
@Slf4j
@Component
@ServerEndpoint("/ws/chat/{userId}")
public class ChatWebSocketHandler {
// 存储所有连接的会话
private static final Map<String, Session> SESSIONS = new ConcurrentHashMap<>();
// 由于 WebSocket 是多例的,需要通过 SpringUtils 获取 Bean
private ChatClient getChatClient() {
return SpringUtils.getBean(ChatClient.class);
}
private ChatConfig getChatConfig() {
return SpringUtils.getBean(ChatConfig.class);
}
private AiChatHistoryService getAiChatHistoryService() {
return SpringUtils.getBean(AiChatHistoryService.class);
}
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
SESSIONS.put(userId + "_" + session.getId(), session);
log.info("WebSocket 连接建立userId: {}, sessionId: {}", userId, session.getId());
sendMessage(session, buildResponse("connected", "连接成功", null));
}
@OnClose
public void onClose(Session session, @PathParam("userId") String userId) {
SESSIONS.remove(userId + "_" + session.getId());
log.info("WebSocket 连接关闭userId: {}, sessionId: {}", userId, session.getId());
}
@OnError
public void onError(Session session, Throwable error, @PathParam("userId") String userId) {
log.error("WebSocket 发生错误userId: {}, error: {}", userId, error.getMessage());
sendMessage(session, buildResponse("error", error.getMessage(), null));
}
/**
* 接收客户端消息
* 消息格式:
* {
* "action": "chat", // chat: 聊天, history: 获取历史, detail: 获取详情, guest: 获取建议
* "data": "用户输入的内容",
* "sessionId": "会话ID",
* "dataId": "数据ID",
* "fileUrl": ["文件URL列表"]
* }
*/
@OnMessage
public void onMessage(String message, Session session, @PathParam("userId") String userId) {
log.info("收到消息userId: {}, message: {}", userId, message);
try {
JSONObject json = JSONObject.parseObject(message);
String action = json.getString("action");
if (StringUtils.isEmpty(action)) {
action = "chat";
}
switch (action) {
case "chat":
handleChat(json, session, userId);
break;
case "history":
handleHistory(json, session);
break;
case "detail":
handleDetail(json, session);
break;
case "guest":
handleGuest(json, session);
break;
default:
sendMessage(session, buildResponse("error", "未知的操作类型: " + action, null));
}
} catch (Exception e) {
log.error("处理消息失败: {}", e.getMessage(), e);
sendMessage(session, buildResponse("error", "处理消息失败: " + e.getMessage(), null));
}
}
/**
* 处理聊天请求
*/
private void handleChat(JSONObject json, Session session, String userId) {
ChatRequest request = buildChatRequest(json, userId);
JSONObject contentObject = new JSONObject();
contentObject.put("content", request.getData());
contentObject.put("role", "user");
JSONArray array = getAiChatHistoryService().getChatHistoryData(request.getSessionId());
if (array == null || array.isEmpty()) {
array = new JSONArray();
}
array.add(contentObject);
request.setMessages(array);
List<String> answerList = new ArrayList<>();
long[] timeStart = {0};
getChatClient().sendStreamingChat(request, new ChatClient.StreamCallback() {
@Override
public void onData(String chunk) {
if (timeStart[0] == 0) {
timeStart[0] = System.currentTimeMillis();
}
String processedChunk = chunk.trim();
String content = parseChatChunk(processedChunk);
if (StringUtils.isNotEmpty(content)) {
answerList.add(content);
}
// 发送流式数据
sendMessage(session, buildResponse("data", null, processedChunk));
}
@Override
public void onComplete() {
// 保存聊天记录
saveChatHistory(request, userId, answerList, timeStart[0]);
sendMessage(session, buildResponse("complete", "对话完成", null));
}
@Override
public void onError(Throwable e) {
log.error("聊天请求失败: {}", e.getMessage());
sendMessage(session, buildResponse("error", e.getMessage(), null));
}
});
}
/**
* 处理获取历史记录请求
*/
private void handleHistory(JSONObject json, Session session) {
AiChatHistory chatHistory = new AiChatHistory();
if (json.containsKey("userId")) {
chatHistory.setUserId(json.getLong("userId"));
}
JSONObject result = getAiChatHistoryService().getList(chatHistory);
sendMessage(session, buildResponse("history", null, result.toJSONString()));
}
/**
* 处理获取聊天详情请求
*/
private void handleDetail(JSONObject json, Session session) {
String sessionId = json.getString("sessionId");
JSONObject result = getAiChatHistoryService().getDetailList(sessionId);
sendMessage(session, buildResponse("detail", null, result.toJSONString()));
}
/**
* 处理获取建议请求
*/
private void handleGuest(JSONObject json, Session session) {
try {
ChatRequest request = new ChatRequest();
request.setSessionId(json.getString("sessionId"));
JSONArray array = getAiChatHistoryService().getChatHistoryData(request.getSessionId());
request.setMessages(array);
String result = getChatClient().sendChatGuest(request);
String[] strList = result.split("");
List<String> list = new ArrayList<>();
for (String str : strList) {
if (StringUtils.isNotEmpty(str)) {
str = str + "";
list.add(str);
}
}
sendMessage(session, buildResponse("guest", null, JSONObject.toJSONString(list)));
} catch (Exception e) {
log.error("获取建议失败: {}", e.getMessage());
sendMessage(session, buildResponse("error", "获取建议失败: " + e.getMessage(), null));
}
}
/**
* 构建 ChatRequest 对象
*/
private ChatRequest buildChatRequest(JSONObject json, String userId) {
ChatRequest request = new ChatRequest();
request.setData(json.getString("data"));
request.setSessionId(json.getString("sessionId"));
request.setDataId(json.getString("dataId"));
if (json.containsKey("fileUrl")) {
request.setFileUrl(json.getJSONArray("fileUrl").toJavaList(String.class));
}
if (json.containsKey("userId") && json.getLong("userId") != 0) {
request.setUserId(json.getLong("userId"));
} else if (StringUtils.isNotEmpty(userId) && !"0".equals(userId)) {
request.setUserId(Long.parseLong(userId));
}
return request;
}
/**
* 保存聊天记录
*/
private void saveChatHistory(ChatRequest request, String userId, List<String> answerList, long timeStart) {
long timeEnd = System.currentTimeMillis();
double duration = (timeEnd - timeStart) / 1000.0;
AiChatHistory chatHistory = new AiChatHistory();
chatHistory.setChatId(request.getSessionId());
Long uid = null;
if (request.getUserId() != 0) {
uid = request.getUserId();
} else if (StringUtils.isNotEmpty(userId) && !"0".equals(userId)) {
uid = Long.parseLong(userId);
}
chatHistory.setUserId(uid);
chatHistory.setAppId(getChatConfig().getAppId());
chatHistory.setDataId(request.getDataId());
chatHistory.setTitle(request.getData());
chatHistory.setAnswerStringList(answerList);
chatHistory.setDurationSeconds(duration);
getAiChatHistoryService().saveChatHistory(chatHistory);
}
/**
* 解析流式数据
*/
private String parseChatChunk(String chunk) {
try {
String processed = chunk.trim();
if (processed.startsWith("data:")) {
processed = processed.substring("data:".length()).trim();
}
if (processed.isEmpty() || "[DONE]".equals(processed)) {
return null;
}
JSONObject json = JSONObject.parseObject(processed);
String choices = json.getString("choices");
if (choices != null && !choices.trim().isEmpty()) {
JSONArray jsonArray = JSONArray.parseArray(choices);
json = JSONObject.parseObject(jsonArray.getString(0));
json = json.getJSONObject("delta");
return json.getString("content");
}
return null;
} catch (Exception e) {
return null;
}
}
/**
* 构建响应消息
*/
private String buildResponse(String type, String message, String data) {
JSONObject response = new JSONObject();
response.put("type", type);
if (message != null) {
response.put("message", message);
}
if (data != null) {
response.put("data", data);
}
return response.toJSONString();
}
/**
* 发送消息
*/
private void sendMessage(Session session, String message) {
if (session != null && session.isOpen()) {
try {
synchronized (session) {
session.getBasicRemote().sendText(message);
}
} catch (IOException e) {
log.error("发送消息失败: {}", e.getMessage());
}
}
}
/**
* 向指定用户发送消息
*/
public static void sendMessageToUser(String userId, String message) {
SESSIONS.forEach((key, session) -> {
if (key.startsWith(userId + "_") && session.isOpen()) {
try {
synchronized (session) {
session.getBasicRemote().sendText(message);
}
} catch (IOException e) {
log.error("发送消息给用户 {} 失败: {}", userId, e.getMessage());
}
}
});
}
}

View File

@@ -10,6 +10,8 @@ import com.ruoyi.cms.domain.vo.AppUserLky;
import com.ruoyi.cms.service.IAppReviewJobService;
import com.ruoyi.cms.util.DateValidateUtil;
import com.ruoyi.cms.util.RoleUtils;
import com.ruoyi.cms.util.StringUtil;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.annotation.BussinessLog;
import com.ruoyi.common.core.domain.entity.AppUserShow;
import com.ruoyi.common.core.domain.model.RegisterBody;
@@ -64,6 +66,10 @@ public class CmsAppUserController extends BaseController
{
startPage();
List<AppUser> list = appUserService.selectAppUserList(appUser);
//身份证脱敏
list.forEach(it->{
it.setIdCard(StringUtil.desensitizeIdCard(it.getIdCard()));
});
return getDataTable(list);
}
@@ -166,6 +172,10 @@ public class CmsAppUserController extends BaseController
{
startPage();
List<AppUser> list = appUserService.selectNoTmAppUserList(appUser);
//身份证脱敏
list.forEach(it->{
it.setIdCard(StringUtil.desensitizeIdCard(it.getIdCard()));
});
return getDataTable(list);
}
@@ -264,4 +274,27 @@ public class CmsAppUserController extends BaseController
}
return toAjax(appReviewJobService.insertAppReviewJob(appReviewJob));
}
/**
* 浪潮需要根据用户id查询简历
* @return
*/
@ApiOperation("查看简历")
@GetMapping("/userInfoResume/{userId}")
@Anonymous
public AjaxResult getUserInfoResume(@PathVariable("userId") Long userId)
{
if(userId==null){
return error("用户id为空!");
}
try {
AppUser result=appUserService.selectAppUserByUserId(userId);
if (result == null) {
return error("暂无该用户的简历信息");
}
return success(result);
} catch (Exception e) {
return error("查询简历失败,请稍后重试");
}
}
}

View File

@@ -5,7 +5,9 @@ import com.ruoyi.cms.domain.*;
import com.ruoyi.cms.domain.query.ESJobSearch;
import com.ruoyi.cms.domain.vo.CandidateVO;
import com.ruoyi.cms.domain.vo.CompanyVo;
import com.ruoyi.cms.domain.vo.JobExcelVo;
import com.ruoyi.cms.service.*;
import com.ruoyi.cms.util.EasyExcelUtils;
import com.ruoyi.cms.util.RoleUtils;
import com.ruoyi.cms.util.StringUtil;
import com.ruoyi.cms.util.sensitiveWord.SensitiveWordChecker;
@@ -24,11 +26,15 @@ import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -66,14 +72,15 @@ public class CmsJobController extends BaseController
@ApiOperation("查询岗位列表")
// @PreAuthorize("@ss.hasPermi('cms:job:list')")
@GetMapping("/list")
public TableDataInfo list(Job job)
public TableDataInfo list(Job job,HttpServletRequest request)
{
if (RoleUtils.isCompanyAdmin()) {
Company company = companyService.queryCodeCompany(RoleUtils.getCurrentUseridCard());
job.setCompanyId(Objects.nonNull(company) ? company.getCompanyId() : null);
}
startPage();
List<Job> list = jobService.selectJobList(job);
//List<Job> list = jobService.selectJobList(job);
List<Job> list = jobService.selectHttpJobList(job,request);
return getDataTable(list);
}
@@ -83,9 +90,13 @@ public class CmsJobController extends BaseController
@ApiOperation("获取岗位详细信息")
// @PreAuthorize("@ss.hasPermi('bussiness:job:query')")
@GetMapping(value = "/{jobId}")
public AjaxResult getInfo(@PathVariable("jobId") Long jobId)
public AjaxResult getInfo(@PathVariable("jobId") Long jobId, HttpServletRequest request)
{
return success(jobService.selectJobByJobId(jobId));
if (jobId == null) {
return AjaxResult.error("jobId不能为空");
}
//return success(jobService.selectJobByJobId(jobId));
return success(jobService.selectHttpJobByJobId(jobId,request));
}
/**
@@ -169,7 +180,11 @@ public class CmsJobController extends BaseController
esJobSearch.setCode(RoleUtils.getCurrentUseridCard());
esJobSearch.setUserType(StringUtil.IS_COMPANY_USER);
}
esJobSearch.setPageSize(20);
//判断pageSize
Integer pageSize=esJobSearch.getPageSize();
if (pageSize == null || pageSize <= 0) {
esJobSearch.setPageSize(20);
}
List<ESJobDocument> jobList = jobService.sysRecommend(esJobSearch);
List<Job> jobs=new ArrayList<>();
jobList.stream().forEach(it->{
@@ -331,4 +346,99 @@ public class CmsJobController extends BaseController
});
return success(jobList);
}
@PostMapping("/wechat")
@ApiOperation("微信抓取功能调用的新增")
public AjaxResult wechatInsert(@RequestBody Job job) {
// 不发布
job.setIsPublish(0);
if (job.getJobContactList() == null) {
job.setJobContactList(new ArrayList<>());
}
Integer total=jobService.getTotals(job);
int totalNum = total != null ? total : 0;
System.out.println("岗位条数======================"+totalNum);
if (totalNum == 0) {
return toAjax(jobService.insertJob(job));
}
return AjaxResult.success("此岗位已存在!");
}
/**
* 通用上传请求(单个)
*/
@PostMapping("/uploadFile")
@ApiOperation("岗位批量上传")
public AjaxResult uploadFile(@RequestParam("file") MultipartFile file) throws Exception {
if (file.isEmpty()) {
return AjaxResult.error("上传文件不能为空");
}
String fileName = file.getOriginalFilename();
if (fileName == null || !fileName.endsWith(".xlsx") && !fileName.endsWith(".xls")) {
return AjaxResult.error("请上传Excel格式的文件");
}
try (InputStream inputStream = file.getInputStream()){
List<JobExcelVo> allExcelVoList = new ArrayList<>();
EasyExcelUtils.readExcelByBatch(inputStream, JobExcelVo.class, 100, list -> {
allExcelVoList.addAll(list);
});
if (CollectionUtils.isEmpty(allExcelVoList)) {
throw new Exception("Excel文件中无有效数据");
}
jobService.uploadFileJob(allExcelVoList);
return AjaxResult.success("已上传!");
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error(e.getMessage());
}
}
/**
* 模板下载
* @param request
* @param response
* @throws Exception
*/
@PostMapping("/downloadModel")
public void downloadModel(HttpServletRequest request, HttpServletResponse response)throws Exception{
String name = "模板.xlsx";
String pathFile="/data/downloadmodel/"+name;
File url = new File(pathFile);
String resMsg = "";
try {
request.setCharacterEncoding("utf-8");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
try {
name = new String(name.getBytes("gb2312"), "ISO8859-1");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
response.reset();
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename="+ name);
response.setHeader("Pragma", "public");
response.setHeader("Cache-Control", "max-age=0");
InputStream in = null;
try {
in = new FileInputStream(url);
} catch (FileNotFoundException e1) {
resMsg = "文件未找到";
e1.printStackTrace();
response.getWriter().write(resMsg + ":" + name);
}
OutputStream ou = response.getOutputStream();
byte[] buffer = new byte[1024];
int i = -1;
while ((i = in.read(buffer)) != -1) {
ou.write(buffer, 0, i);
}
ou.flush();
ou.close();
in.close();
}
}

View File

@@ -187,7 +187,7 @@ public class CmsNoticeController extends BaseController
@GetMapping("/noticTotal")
public AjaxResult getNoticTotal(Notice notice){
if(!SecurityUtils.isLogin()){
return AjaxResult.error("未登录!,返回为空");
return AjaxResult.success("未登录!,返回为空");
}
if(notice.getUserId()==null){
String idCard= RoleUtils.getCurrentUseridCard();

View File

@@ -65,12 +65,14 @@ public class CmsSkillController extends BaseController {
@Log(title = "技能", businessType = BusinessType.INSERT)
@PostMapping("/add")
public AjaxResult save(@RequestBody AppSkill appSkill){
if(!SecurityUtils.isLogin()){
if(SecurityUtils.isLogin()){
AppUser appUser=appUserService.selectAppuserByIdcard(RoleUtils.getCurrentUseridCard());
if(appUser==null){
return AjaxResult.error("未传递userId!");
}
appSkill.setUserId(appUser.getUserId());
}else {
return AjaxResult.error("未登录!");
}
return toAjax(appSkillService.insertAppskill(appSkill));
}

View File

@@ -0,0 +1,54 @@
package com.ruoyi.cms.controller.cms;
import com.ruoyi.cms.domain.CommunityUser;
import com.ruoyi.cms.service.ICommunityUserService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/cms/communityUser")
@Api(tags = "后台:工作人员管理")
public class CommunityUserController extends BaseController {
@Autowired
private ICommunityUserService communityUserService;
@ApiOperation("查询工作人员列表")
@PreAuthorize("@ss.hasPermi('application:mgmt:list')")
@GetMapping("/list")
public TableDataInfo list(CommunityUser communityUser) {
startPage();
List<CommunityUser> list = communityUserService.selectCommunityUserList(communityUser);
return getDataTable(list);
}
@ApiOperation("新增工作人员")
@PreAuthorize("@ss.hasPermi('application:mgmt:add')")
@PostMapping
public AjaxResult add(@RequestBody CommunityUser communityUser) {
return toAjax(communityUserService.save(communityUser));
}
@ApiOperation("修改工作人员")
@PreAuthorize("@ss.hasPermi('application:mgmt:edit')")
@PutMapping
public AjaxResult update(@RequestBody CommunityUser communityUser) {
return toAjax(communityUserService.updateById(communityUser));
}
@ApiOperation("删除工作人员")
@PreAuthorize("@ss.hasPermi('application:mgmt:del')")
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable("ids") Long[] ids) {
return toAjax(communityUserService.delCommunityUser(ids));
}
}

View File

@@ -1,17 +1,17 @@
package com.ruoyi.cms.controller.cms;
import com.ruoyi.cms.domain.BussinessDictType;
import com.ruoyi.cms.domain.query.Staticsquery;
import com.ruoyi.cms.service.StaticsqueryService;
import com.ruoyi.cms.util.DateValidateUtil;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
@@ -100,4 +100,24 @@ public class StaticsController extends BaseController {
Map<String,Object> result = service.educationSalary(staticsquery);
return success(result);
}
/**
* 企业岗位统计
* @param staticsquery
* @return
*/
@GetMapping("/qygwtjCount")
public AjaxResult qygwtjCount(@RequestBody Staticsquery staticsquery)
{
if(staticsquery==null){
return error("参数为空!");
}
//判断时间
String timeError = DateValidateUtil.validateStartAndEndTime(staticsquery.getStartTime(),staticsquery.getEndTime());
if (StringUtils.isNotBlank(timeError)) {
return error(timeError);
}
Map<String,Object> result = service.qygwtjCount(staticsquery);
return success(result);
}
}

View File

@@ -31,4 +31,14 @@ public class SysAreaController {
public AjaxResult jobCategory(SysArea sysArea){
return AjaxResult.success(sysAreaService.getList(sysArea));
}
/**
* 查询所有市
* @param sysArea
* @return
*/
@GetMapping("/listCity")
public AjaxResult listCity(SysArea sysArea){
return AjaxResult.success(sysAreaService.getCityList(sysArea));
}
}

View File

@@ -0,0 +1,68 @@
package com.ruoyi.cms.controller.cms;
import cn.hutool.core.collection.CollUtil;
import com.ruoyi.cms.domain.WechatGroup;
import com.ruoyi.cms.domain.vo.WechatGroupVo;
import com.ruoyi.cms.service.IWechatGroupService;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/cms/wechatGroup")
@Api(tags = "后台:转发对象管理")
public class WechatGroupController extends BaseController {
@Autowired
private IWechatGroupService wechatGroupService;
@ApiOperation("查询转发对象列表")
@PreAuthorize("@ss.hasPermi('application:group:list')")
@GetMapping("/list")
public TableDataInfo list(WechatGroup wechatGroup) {
startPage();
List<WechatGroupVo> list = wechatGroupService.selectWechatGroupList(wechatGroup);
return getDataTable(list);
}
@ApiOperation("新增转发对象")
@PreAuthorize("@ss.hasPermi('application:group:add')")
@PostMapping
public AjaxResult add(@RequestBody WechatGroup wechatGroup) {
return toAjax(wechatGroupService.save(wechatGroup));
}
@ApiOperation("修改转发对象")
@PreAuthorize("@ss.hasPermi('application:group:update')")
@PutMapping
public AjaxResult update(@RequestBody WechatGroup wechatGroup) {
return toAjax(wechatGroupService.updateById(wechatGroup));
}
@ApiOperation("删除转发对象")
@PreAuthorize("@ss.hasPermi('application:group:del')")
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable("ids") Long[] ids) {
return toAjax(wechatGroupService.removeBatchByIds(CollUtil.newArrayList(ids)));
}
@GetMapping("/enableList")
@Anonymous
public List<WechatGroupVo> enableList() {
WechatGroup wechatGroup = new WechatGroup();
wechatGroup.setIsPush(1);
return wechatGroupService.selectWechatGroupList(wechatGroup)
.stream().peek(e->e.setPhoneNumber(""))
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,33 @@
package com.ruoyi.cms.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 工作人员配置
*/
@TableName("community_user")
@EqualsAndHashCode(callSuper = true)
@Data
public class CommunityUser extends BaseEntity {
/**
* id
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 微信名
*/
private String wechatName;
/**
* 手机号
*/
private String phoneNumber;
}

View File

@@ -54,7 +54,7 @@ public class ESJobDocument
@ApiModelProperty("用人单位名称")
private String companyName;
@ApiModelProperty("工作地点")
@ApiModelProperty("岗位区划")
private String jobLocation;
@ApiModelProperty("工作地点区县字典代码")
@@ -150,6 +150,15 @@ public class ESJobDocument
@ApiModelProperty("信用代码")
private String code;
@ApiModelProperty("工作地点")
private String jobAddress;
@ApiModelProperty("所属行政区划")
private String regionCode;
@ApiModelProperty("人员类型 1残疾人,2退伍军人")
private String staffType;
@ApiModelProperty("公司信息")
@IndexField(fieldType = FieldType.TEXT)
private String companyVoJson;

View File

@@ -9,6 +9,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.ruoyi.cms.domain.vo.CompanyVo;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.domain.entity.AppUser;
import com.ruoyi.common.core.domain.entity.Company;
import com.ruoyi.common.core.domain.entity.File;
import io.swagger.annotations.ApiModel;
@@ -59,8 +60,8 @@ public class Job extends BaseEntity
@ApiModelProperty("用人单位名称")
private String companyName;
@Excel(name = "工作地点")
@ApiModelProperty("工作地点")
@Excel(name = "岗位区划")
@ApiModelProperty("岗位区划")
private String jobLocation;
@ApiModelProperty("工作地点区县字典代码")
@@ -168,6 +169,15 @@ public class Job extends BaseEntity
@ApiModelProperty("类型 0常规岗位 1就业见习岗位 2实习实训岗位 3社区实践岗位 4零工 对应字段字典position_type")
private String type;
@ApiModelProperty("工作地点")
private String jobAddress;
@ApiModelProperty("所属行政区划")
private String regionCode;
@ApiModelProperty("人员类型 1残疾人,2退伍军人")
private String staffType;
@TableField(exist = false)
@ApiModelProperty("岗位联系人列表")
private List<JobContact> jobContactList;
@@ -184,4 +194,8 @@ public class Job extends BaseEntity
@JsonFormat(pattern = "yyyy-MM-dd")
@ApiModelProperty("时间(足迹、投简历、收藏)")
private String shareTime;
@TableField(exist = false)
@ApiModelProperty("申请人列表")
private List<AppUser> applyUsers;
}

View File

@@ -79,5 +79,5 @@ public class JobFair extends BaseEntity
@TableField(exist = false)
@ApiModelProperty("是否收藏")
public Integer isCollection;
private Integer isCollection;
}

View File

@@ -0,0 +1,25 @@
package com.ruoyi.cms.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class StaticsJob {
@ApiModelProperty("总数")
private String zs;
@ApiModelProperty("高效毕业生岗位")
private String gxbysgw;
@ApiModelProperty("实时在招岗位数")
private String sszzgw;
@ApiModelProperty("简历数量")
private String jlsl;
@ApiModelProperty("名称")
private String label;
@ApiModelProperty("归集岗位合计")
private String gjgwhj;
@ApiModelProperty("注册企业数")
private String zcqys;
@ApiModelProperty("求职者实名数")
private String qzzsms;
}

View File

@@ -0,0 +1,39 @@
package com.ruoyi.cms.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 转发对象配置
*/
@TableName("wechat_group")
@EqualsAndHashCode(callSuper = true)
@Data
public class WechatGroup extends BaseEntity {
/**
* id
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 群聊名称
*/
private String name;
/**
* 工作人员 ID
*/
private Long communityId;
/**
* 是否启用 0=禁用 1=启用
*/
private Integer isPush;
}

View File

@@ -0,0 +1,37 @@
package com.ruoyi.cms.domain.ai;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("ai聊天详情")
@TableName("AI_CHAT_DETAIL")
public class AiChatDetail {
@TableField(exist = false)
private static final long serialVersionUID = 1L;
@TableId(value = "id",type = IdType.AUTO)
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("会话id")
private String chatId;
@ApiModelProperty("数据id")
private String dataId;
@ApiModelProperty("会话类型Human用户AI大模型")
private String obj;
@ApiModelProperty("会话内容")
private String content;
@ApiModelProperty("会话时间")
private Date time;
@ApiModelProperty("耗时")
private Double durationSeconds;
}

View File

@@ -0,0 +1,53 @@
package com.ruoyi.cms.domain.ai;
import com.alibaba.fastjson2.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
@ApiModel("ai聊天历史记录")
@TableName("AI_CHAT_HISTORY")
public class AiChatHistory{
@TableField(exist = false)
private static final long serialVersionUID = 1L;
@TableId(value = "id",type = IdType.AUTO)
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("用户id")
private Long userId;
@JSONField(name = "chatId")
@ApiModelProperty("会话id")
private String chatId;
@ApiModelProperty("应用id")
private String appId;
@JSONField(name = "title")
@ApiModelProperty("第一次的问题")
private String title;
@JSONField(name = "updateTime")
@ApiModelProperty("会话时间")
private Date updateTime;
@ApiModelProperty("是否删除")
private String delFlag;
@TableField(exist = false)
private List<String> answerStringList;
@TableField(exist = false)
private double durationSeconds;
@TableField(exist = false)
private String customTitle;
@TableField(exist = false)
private String dataId;
@TableField(exist = false)
private boolean top=false;
}

View File

@@ -0,0 +1,17 @@
package com.ruoyi.cms.domain.chat;
import com.alibaba.fastjson2.JSONArray;
import lombok.Data;
import java.util.List;
@Data
public class ChatRequest {
private String data;
private String dataId;
private String sessionId;
private long userId;
private List<String> fileUrl;
private JSONArray messages;
}

View File

@@ -0,0 +1,97 @@
package com.ruoyi.cms.domain.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 岗位对象 job
*/
@Data
public class JobExcelVo
{
@ExcelProperty(value = "职位名称", index = 0)
@Excel(name = "职位名称")
@ApiModelProperty("职位名称")
private String jobTitle;
@ExcelProperty(value = "职位名称", index = 1)
@Excel(name = "最小薪资", readConverterExp = "元=")
@ApiModelProperty("最小薪资(元)")
private Long minSalary;
@ExcelProperty(value = "职位名称", index = 2)
@Excel(name = "最大薪资", readConverterExp = "元=")
@ApiModelProperty("最大薪资(元)")
private Long maxSalary;
@ExcelProperty(value = "职位名称", index = 3)
@Excel(name = "学历要求 对应字典education")
@ApiModelProperty("学历要求 对应字典education")
private String education;
@ExcelProperty(value = "职位名称", index = 4)
@Excel(name = "工作经验要求 对应字典experience")
@ApiModelProperty("工作经验要求 对应字典experience")
private String experience;
@ExcelProperty(value = "职位名称", index = 5)
@Excel(name = "用人单位名称")
@ApiModelProperty("用人单位名称")
private String companyName;
@ExcelProperty(value = "职位名称", index = 6)
@Excel(name = "招聘人数")
@ApiModelProperty("招聘人数")
private Long vacancies;
@ExcelProperty(value = "职位名称", index = 7)
@ApiModelProperty("岗位描述")
private String description;
@ExcelProperty(value = "职位名称", index = 8)
@ApiModelProperty("岗位分类")
private String jobCategory;
@ExcelProperty(value = "职位名称", index = 9)
@ApiModelProperty("岗位类型 0疆内 1疆外")
private String jobType;
@ExcelProperty(value = "职位名称", index = 10)
@ApiModelProperty("类型 0常规岗位 1就业见习岗位 2实习实训岗位 3社区实践岗位 4零工 对应字段字典position_type")
private String type;
@ExcelProperty(value = "职位名称", index = 11)
@ApiModelProperty("工作地点")
private String jobAddress;
@ExcelProperty(value = "职位名称", index = 12)
@Excel(name = "岗位区划")
@ApiModelProperty("岗位区划")
private String jobLocation;
@ExcelProperty(value = "联系人", index = 13)
@ApiModelProperty("联系人")
private String contactPerson;
@ExcelProperty(value = "联系人电话", index = 14)
@ApiModelProperty("联系人电话")
private String contactPersonPhone;
@ApiModelProperty("是否发布 0未发布 1发布")
private Integer isPublish;
@ApiModelProperty("工作地点区县字典代码")
private Integer jobLocationAreaCode;
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "发布时间", width = 30, dateFormat = "yyyy-MM-dd")
@ApiModelProperty("发布时间")
private String postingDate;
@ApiModelProperty("数据来源")
private String dataSource;
}

View File

@@ -0,0 +1,10 @@
package com.ruoyi.cms.domain.vo;
import lombok.Data;
@Data
public class WechatAuthVO {
private String openid;
private String unionid;
private String sessionKey;
}

View File

@@ -0,0 +1,20 @@
package com.ruoyi.cms.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
@Data
public class WechatGroupVo {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private Long id;
private String name;
private Integer isPush;
private String wechatNumber;
private String phoneNumber;
private String wechatName;
private Long communityId;
}

View File

@@ -1,6 +1,6 @@
package com.ruoyi.cms.handler;
import com.alibaba.nls.client.protocol.asr.SpeechRecognizer;
import com.ruoyi.cms.util.AliyunNlsUtils;
import org.springframework.stereotype.Component;
import javax.websocket.*;
@@ -20,7 +20,7 @@ public class SpeechRecognitionWebSocketHandler {
String appKey = "4lFYn2yPsQymwGu8";
String id = "LTAI5t9hhSqdDHqwH3RjgyYj";
String secret = "ni5aW3vxrWouMwcGqJPfh9Uu56PBuv";
String url = System.getenv().getOrDefault("NLS_GATEWAY_URL", "wss://nls-gateway-cn-shanghai.aliyuncs.com/ws/v1");
String url = System.getenv().getOrDefault("NLS_GATEWAY_URL", AliyunNlsUtils.getNlsUrl()+"/ws/v1/");
recognizerDemo = new SpeechRecognizerAI(appKey, id, secret, url);
}

View File

@@ -7,6 +7,7 @@ import com.alibaba.nls.client.protocol.SampleRateEnum;
import com.alibaba.nls.client.protocol.asr.SpeechRecognizer;
import com.alibaba.nls.client.protocol.asr.SpeechRecognizerListener;
import com.alibaba.nls.client.protocol.asr.SpeechRecognizerResponse;
import com.ruoyi.cms.util.AliyunNlsUtils;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,7 +27,12 @@ public class SpeechRecognizerAI {
// 获取 AccessToken
accessToken = new AccessToken(id, secret);
try {
accessToken.apply(); // 申请 Token
if(AliyunNlsUtils.USE_TEST_ENV){
accessToken.apply(); // 申请 Token
}else{
AliyunNlsTokenUtil.generateToken(id, secret, this.accessToken);
}
//accessToken.apply(); // 申请 Token
logger.info("Token: {}, Expire Time: {}", accessToken.getToken(), accessToken.getExpireTime());
// 初始化 NlsClient
@@ -35,7 +41,7 @@ public class SpeechRecognizerAI {
} else {
this.client = new NlsClient(url, accessToken.getToken()); // 使用自定义服务地址
}
} catch (IOException e) {
} catch (Exception e) {
logger.error("Failed to initialize NlsClient: {}", e.getMessage());
}
}

View File

@@ -7,6 +7,7 @@ import com.alibaba.nls.client.protocol.SampleRateEnum;
import com.alibaba.nls.client.protocol.tts.SpeechSynthesizer;
import com.alibaba.nls.client.protocol.tts.SpeechSynthesizerListener;
import com.alibaba.nls.client.protocol.tts.SpeechSynthesizerResponse;
import com.ruoyi.cms.util.AliyunNlsUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@@ -20,18 +21,23 @@ import java.nio.ByteBuffer;
@ServerEndpoint("/speech-synthesis")
public class SpeechSynthesisWebSocketHandler {
private static final Logger logger = LoggerFactory.getLogger(SpeechSynthesisWebSocketHandler.class);
private NlsClient client;
private String appKey = "mtA2pwmvCeefHT3Y";
private String accessKeyId = "LTAI5tRBahK93vPNF1JDVEPA";
private String accessKeySecret = "x95OWb4cV6ccQVtbEJ2Gxm2Uwl2thJ";
private String url = "wss://nls-gateway-cn-shanghai.aliyuncs.com/ws/v1";
private String url = AliyunNlsUtils.getNlsUrl()+"/ws/v1/";
public SpeechSynthesisWebSocketHandler() {
// Initialize NLS client with token
AccessToken accessToken = new AccessToken(accessKeyId, accessKeySecret);
try {
accessToken.apply();
if(AliyunNlsUtils.USE_TEST_ENV){
accessToken.apply();
}else{
AliyunNlsTokenUtil.generateToken(accessKeyId, accessKeySecret, accessToken);
}
//accessToken.apply();
String token = accessToken.getToken();
if(url.isEmpty()) {
this.client = new NlsClient(token);

View File

@@ -0,0 +1,13 @@
package com.ruoyi.cms.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.cms.domain.ai.AiChatDetail;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface AiChatDetailMapper extends BaseMapper<AiChatDetail> {
List<AiChatDetail> getList(AiChatDetail aiChatDetail);
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.cms.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.cms.domain.ai.AiChatHistory;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface AiChatHistoryMapper extends BaseMapper<AiChatHistory> {
List<AiChatHistory> getList(AiChatHistory aiChatHistory);
}

View File

@@ -8,6 +8,7 @@ import com.ruoyi.common.core.domain.entity.AppUserShow;
import com.ruoyi.common.core.domain.entity.MyChart;
import com.ruoyi.common.core.domain.entity.AppUser;
import com.ruoyi.common.core.domain.entity.SysUser;
import org.apache.ibatis.annotations.Param;
/**
* APP用户Mapper接口
@@ -26,15 +27,15 @@ public interface AppUserMapper extends BaseMapper<AppUser>
List<AppUser> selectByJobId(Long jobId);
AppUser selectByOpenid(String openid);
AppUser selectByOpenid(@Param("openid")String openid, @Param("userType") String userType);
int insertSysUserRole(Map<String,Object> map);
int insertSysUser(SysUser sysUser);
MyChart getMyTj(Long userId);
MyChart getMyTj(@Param("userId") Long userId);
SysUser selectSysUserIdcard(String idCard);
SysUser selectSysUserIdcard(@Param("idCard") String idCard);
List<AppUserShow> selectUserApplyList(AppUser appUser);
}

View File

@@ -0,0 +1,7 @@
package com.ruoyi.cms.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.cms.domain.CommunityUser;
public interface CommunityUserMapper extends BaseMapper<CommunityUser> {
}

View File

@@ -29,4 +29,6 @@ public interface CompanyMapper extends BaseMapper<Company>
List<Company> selectLikeCompanyList(Company company);
Company selectCompanyByJobId(Long jobId);
List<Company> selectByNames(List<String> list);
}

View File

@@ -22,4 +22,8 @@ public interface FileMapper extends BaseMapper<File>
public List<File> selectFileList(File file);
public int updateBussinessids(@Param("longs") List<Long> longs,@Param("newBussinessid") Long bussinessid);
public List<File> selectFileListByBussinessIds(@Param("longs") List<Long> longs);
public int updateIds(@Param("longs") List<Long> longs,@Param("newBussinessid") Long bussinessid);
}

View File

@@ -9,6 +9,7 @@ import com.ruoyi.cms.domain.Job;
import com.ruoyi.cms.domain.JobApply;
import com.ruoyi.cms.domain.vo.CandidateVO;
import com.ruoyi.common.core.domain.entity.AppUser;
import org.apache.ibatis.annotations.Param;
/**
* 岗位申请Mapper接口
@@ -39,4 +40,10 @@ public interface JobApplyMapper extends BaseMapper<JobApply>
List<Job> selectJobApplyListJob(JobApply jobApply);
public int updateJobZphApply(JobApply jobApply);
public List<Job> selectCencalList(JobApply jobApply);
public int applyJobCencal(JobApply jobApply);
int applyCencalCount(@Param("userId") Long userId);
}

View File

@@ -2,6 +2,7 @@ package com.ruoyi.cms.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.cms.domain.JobContact;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -17,4 +18,6 @@ public interface JobContactMapper extends BaseMapper<JobContact> {
List<JobContact> getSelectList(JobContact jobContact);
int batchInsert(List<JobContact> list);
List<JobContact> selectByJobIds(@Param("jobIds") List<Long> longs);
}

View File

@@ -58,4 +58,8 @@ public interface JobMapper extends BaseMapper<Job>
* @return
*/
Job getJobInfo(Long jobId);
Integer getTotals(Job job);
void updateFileBatchInsert(List<Job> list);
}

View File

@@ -3,8 +3,15 @@ package com.ruoyi.cms.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.cms.domain.Statics;
import com.ruoyi.cms.domain.StaticsJob;
import com.ruoyi.cms.domain.query.Staticsquery;
import java.util.List;
public interface StaticsMapper extends BaseMapper<Statics>
{
public StaticsJob qygwtjCount(Staticsquery staticsquery);
public List<StaticsJob> getGroutCityJobs(Staticsquery staticsquery);
}

View File

@@ -14,4 +14,6 @@ import java.util.List;
public interface SysAreaMapper{
List<SysArea> getList(SysArea sysArea);
List<SysArea> getCityList(SysArea sysArea);
}

View File

@@ -0,0 +1,12 @@
package com.ruoyi.cms.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.cms.domain.WechatGroup;
import com.ruoyi.cms.domain.vo.WechatGroupVo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface WechatGroupMapper extends BaseMapper<WechatGroup> {
List<WechatGroupVo> selectWechatGroupList(@Param("p") WechatGroup wechatGroup);
}

View File

@@ -0,0 +1,18 @@
package com.ruoyi.cms.service;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.cms.domain.ai.AiChatHistory;
public interface AiChatHistoryService extends IService<AiChatHistory> {
JSONObject getList(AiChatHistory aiChatHistory);
void saveChatHistory(AiChatHistory aiChatHistory);
JSONObject getDetailList(String chatId);
JSONArray getChatHistoryData(String chatId);
}

View File

@@ -58,10 +58,16 @@ public interface IAppUserService
public AppUser getPhone(String phone);
AppUser selectByOpenid(String openid);
public AppUser getPhoneAndNoRole(String phone);
public AppUser getPhoneAndUserType(String phone,String userType);
AppUser selectByOpenid(String openid,String userType);
public AppUser registerAppUser(RegisterBody registerBody);
public AppUser registerAppUserNew(RegisterBody registerBody);
public AppUser selectAppuserByIdcard(String idCard);
public AppUserLky selectAppUserInfo(AppUser appUser);
@@ -75,4 +81,8 @@ public interface IAppUserService
public MyChart getMyTj(Long userId);
public List<AppUserShow> selectUserApplyList(AppUser appUser);
public AppUser getYtjValidPhone(String phone);
public AppUser getYtjValidIdcard(String phone);
}

View File

@@ -0,0 +1,12 @@
package com.ruoyi.cms.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.cms.domain.CommunityUser;
import java.util.List;
public interface ICommunityUserService extends IService<CommunityUser> {
List<CommunityUser> selectCommunityUserList(CommunityUser communityUser);
int delCommunityUser(Long[] ids);
}

View File

@@ -4,6 +4,7 @@ import com.ruoyi.common.core.domain.entity.File;
import com.ruoyi.common.core.domain.AjaxResult;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
@@ -56,5 +57,5 @@ public interface IFileService
AjaxResult upload(MultipartFile file, Long bussinessid);
AjaxResult uploadFile(MultipartFile file, Long bussinessid);
AjaxResult uploadFile(MultipartFile file, Long bussinessid, HttpServletRequest request);
}

View File

@@ -72,4 +72,8 @@ public interface IJobApplyService
public List<Job> selectJobApplyListJob(JobApply jobApply);
public int updateJobZphApply(JobApply jobApply);
public int applyJobCencal(JobApply jobApply);
public List<Job> selectCencalList(JobApply jobApply);
}

View File

@@ -6,10 +6,12 @@ import com.ruoyi.cms.domain.ESJobDocument;
import com.ruoyi.cms.domain.Job;
import com.ruoyi.cms.domain.query.ESJobSearch;
import com.ruoyi.cms.domain.vo.CandidateVO;
import com.ruoyi.cms.util.AppWechatEntity;
import com.ruoyi.cms.domain.vo.JobExcelVo;
import com.ruoyi.common.core.domain.entity.AppUser;
import org.dromara.easyes.core.biz.EsPageInfo;
import javax.servlet.http.HttpServletRequest;
/**
* 岗位Service接口
*
@@ -79,6 +81,8 @@ public interface IJobService
Job selectJobByJobIdApp(Long jobId);
Job selectHttpJobByJobIdApp(Long jobId,HttpServletRequest request);
void importRow(String path);
List<CandidateVO> candidates(Long jobId);
@@ -96,4 +100,17 @@ public interface IJobService
List<ESJobDocument> sysRecommend(ESJobSearch esJobSearch);
List<Job> selectAllJob();
public Job selectHttpJobByJobId(Long jobId, HttpServletRequest request);
public List<Job> selectHttpJobList(Job job,HttpServletRequest request);
/**
* 获取微信抓取的重复数据条数
* @param job
* @return
*/
public Integer getTotals(Job job);
void uploadFileJob(List<JobExcelVo> list);
}

View File

@@ -0,0 +1,11 @@
package com.ruoyi.cms.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.cms.domain.WechatGroup;
import com.ruoyi.cms.domain.vo.WechatGroupVo;
import java.util.List;
public interface IWechatGroupService extends IService<WechatGroup> {
List<WechatGroupVo> selectWechatGroupList(WechatGroup wechatGroup);
}

View File

@@ -14,5 +14,7 @@ import java.util.List;
public interface JobContactService{
List<JobContact> getSelectList(JobContact jobContact);
int batchInsert(List<JobContact> list);
}

View File

@@ -27,4 +27,6 @@ public interface StaticsqueryService {
void educationSalaryGen();
Map<String, Object> educationSalary(Staticsquery staticsquery);
Map<String,Object> qygwtjCount(Staticsquery staticsquery);
}

View File

@@ -14,5 +14,7 @@ import java.util.List;
public interface SysAreaService{
List<SysArea> getList(SysArea sysArea);
List<SysArea> getCityList(SysArea sysArea);
}

View File

@@ -0,0 +1,191 @@
package com.ruoyi.cms.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.cms.domain.ai.AiChatDetail;
import com.ruoyi.cms.domain.ai.AiChatHistory;
import com.ruoyi.cms.mapper.AiChatDetailMapper;
import com.ruoyi.cms.mapper.AiChatHistoryMapper;
import com.ruoyi.cms.service.AiChatHistoryService;
import com.ruoyi.common.utils.SiteSecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
public class AiChatHistoryServiceImpl extends ServiceImpl<AiChatHistoryMapper, AiChatHistory> implements AiChatHistoryService {
@Autowired
private AiChatHistoryMapper aiChatHistoryMapper;
@Autowired
private AiChatDetailMapper aiChatDetailMapper;
@Override
public JSONObject getList(AiChatHistory aiChatHistory) {
JSONObject object = new JSONObject();
JSONArray jsonArray = new JSONArray();
if(Objects.isNull(aiChatHistory.getUserId())){
try {
aiChatHistory.setUserId(SiteSecurityUtils.getUserId());
}catch (Exception e){
object.put("list", jsonArray);
return object;
}
}
List<AiChatHistory> list = aiChatHistoryMapper.getList(aiChatHistory);
if(list!=null&&!list.isEmpty()){
JSONObject jsonObject1 = new JSONObject();
for(AiChatHistory history:list){
jsonObject1 = JSONObject.parseObject(JSONObject.toJSONString(history));
jsonArray.add(jsonObject1);
}
}
object.put("list", jsonArray);
return object;
}
@Override
public void saveChatHistory(AiChatHistory aiChatHistory) {
try {
AiChatHistory history = new AiChatHistory();
history.setChatId(aiChatHistory.getChatId());
List<AiChatHistory> list = aiChatHistoryMapper.getList(history);
if(list!=null&&!list.isEmpty()){
history = list.get(0);
history.setUpdateTime(new Date());
aiChatHistoryMapper.updateById(history);
}else{
history.setTitle(aiChatHistory.getTitle());
history.setUserId(aiChatHistory.getUserId());
history.setAppId(aiChatHistory.getAppId());
history.setDelFlag("0");
history.setUpdateTime(new Date());
aiChatHistoryMapper.insert(history);
}
AiChatDetail chatDetail = new AiChatDetail();
chatDetail.setChatId(aiChatHistory.getChatId());
chatDetail.setObj("Human");
chatDetail.setTime(new Date());
chatDetail.setDataId(IdUtils.fastSimpleUUID());
chatDetail.setContent(aiChatHistory.getTitle());
aiChatDetailMapper.insert(chatDetail);
List<String> answerList = aiChatHistory.getAnswerStringList();
if(answerList!=null&&!answerList.isEmpty()){
chatDetail = new AiChatDetail();
chatDetail.setChatId(aiChatHistory.getChatId());
chatDetail.setObj("AI");
chatDetail.setTime(new Date());
chatDetail.setDataId(aiChatHistory.getDataId());
chatDetail.setContent(String.join("",answerList));
chatDetail.setDurationSeconds(aiChatHistory.getDurationSeconds());
aiChatDetailMapper.insert(chatDetail);
}
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public JSONObject getDetailList(String chatId) {
JSONObject dataList = new JSONObject();
JSONArray array = new JSONArray();
AiChatDetail detail = new AiChatDetail();
detail.setChatId(chatId);
List<AiChatDetail> list = aiChatDetailMapper.getList(detail);
if(list!=null&&!list.isEmpty()){
JSONObject data = new JSONObject();
JSONArray value = new JSONArray();
JSONObject valueObject = new JSONObject();
String dataId = "";
List<String> contentList = new ArrayList<>();
List<AiChatDetail> details = new ArrayList<>();
for(AiChatDetail detail1:list){
details = list.stream().filter(fi->fi.getDataId().equals(detail1.getDataId())).collect(Collectors.toList());
if(details.size() > 1){
contentList = new ArrayList<>();
if(StringUtils.isNotEmpty(dataId)){
continue;
}
data = new JSONObject();
data.put("dataId", detail1.getDataId());
data.put("obj", detail1.getObj());
data.put("hideInUI",false);
data.put("customFeedbacks",new JSONArray());
data.put("time",detail1.getTime());
data.put("durationSeconds",detail1.getDurationSeconds());
value = new JSONArray();
dataId = detail1.getDataId();
for(AiChatDetail detail2:details){
if(detail2.getContent().contains("```")){
contentList.add(detail2.getContent());
}else{
if(contentList!=null&&!contentList.isEmpty()){
valueObject = new JSONObject();
valueObject.put("type","text");
valueObject.put("text",new JSONObject().fluentPut("content", String.join("",contentList)));
value.add(valueObject);
}
valueObject = new JSONObject();
valueObject.put("type","text");
valueObject.put("text",new JSONObject().fluentPut("content", detail2.getContent()));
value.add(valueObject);
}
}
data.put("value",value);
array.add(data);
}else{
dataId = "";
data = new JSONObject();
data.put("dataId", detail1.getDataId());
data.put("obj", detail1.getObj());
data.put("hideInUI",false);
data.put("customFeedbacks",new JSONArray());
data.put("time",detail1.getTime());
if("AI".equals(detail1.getObj())){
data.put("durationSeconds",detail1.getDurationSeconds());
}
valueObject = new JSONObject();
valueObject.put("type","text");
valueObject.put("text",new JSONObject().fluentPut("content", detail1.getContent()));
value = new JSONArray();
value.add(valueObject);
data.put("value",value);
array.add(data);
}
}
}
dataList.put("list",array);
return dataList;
}
@Override
public JSONArray getChatHistoryData(String chatId) {
JSONObject content = null;
JSONArray data = new JSONArray();
AiChatDetail detail = new AiChatDetail();
detail.setChatId(chatId);
List<AiChatDetail> list = aiChatDetailMapper.getList(detail);
if(list!=null&&!list.isEmpty()){
for(AiChatDetail d:list){
content = new JSONObject();
if("AI".equals(d.getObj())){
content.put("role","assistant");
}else{
content.put("role","user");
}
content.put("content",d.getContent());
data.add(content);
}
}
return data;
}
}

View File

@@ -66,7 +66,10 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
}
//查询企业信息
if("0".equals(appUser.getIsCompanyUser())){
Company company=companyMapper.selectOne(Wrappers.<Company>lambdaQuery().eq(Company::getCode, appUser.getIdCard()).orderByDesc(Company::getUpdateTime).last("LIMIT 1"));
Company company=companyMapper.selectOne(Wrappers.<Company>lambdaQuery()
//.eq(Company::getCode, appUser.getIdCard())
.apply("UPPER(code) = {0}", StringUtil.toUpperCaseIgnoreBlank(appUser.getIdCard()))
.orderByDesc(Company::getUpdateTime).last("LIMIT 1"));
appUser.setCompany(company);
if(company!=null){
CompanyContact contact=new CompanyContact();
@@ -75,15 +78,21 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
company.setCompanyContactList(companyContactList);
}
}else if("1".equals(appUser.getIsCompanyUser())){
//工作经历
UserWorkExperiences workExperiences=new UserWorkExperiences();
workExperiences.setUserId(appUser.getUserId());
List<UserWorkExperiences> experiences =userWorkExperiencesMapper.getWorkExperiencesList(workExperiences);
appUser.setExperiencesList(experiences);
//技能
AppSkill skill=new AppSkill();
skill.setUserId(appUser.getUserId());
List<AppSkill> skillList=appSkillMapper.getList(skill);
appUser.setAppSkillsList(skillList);
//附件信息
File file=new File();
file.setBussinessid(userId);
List<File> files=fileMapper.selectFileList(file);
appUser.setFileList(files);
}
return appUser;
}
@@ -107,8 +116,18 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int insertAppUser(AppUser appUser)
{
if(StringUtils.isNotEmpty(appUser.getYtjPassword())){
try {
appUser.setYtjPassword(SiteSecurityUtils.encryptPassword(appUser.getYtjPassword()));
} catch (Exception e) {
throw new RuntimeException("密码加密出错", e);
}
}else{
appUser.setYtjPassword(null);
}
return appUserMapper.insert(appUser);
}
@@ -119,6 +138,7 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int updateAppUser(AppUser appUser)
{
//工作经历
@@ -157,6 +177,10 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
});
}
}
//添加一体机密码
if(StringUtils.isNotEmpty(appUser.getYtjPassword())){
appUser.setYtjPassword(SiteSecurityUtils.encryptPassword(appUser.getYtjPassword()));
}
return appUserMapper.updateById(appUser);
}
@@ -179,8 +203,20 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
}
@Override
public AppUser selectByOpenid(String openid) {
return appUserMapper.selectByOpenid(openid);
public AppUser getPhoneAndNoRole(String phone) {
return appUserMapper.selectOne(new LambdaQueryWrapper<AppUser>()
.eq(AppUser::getPhone, phone).eq(AppUser::getDelFlag,"0").isNull(AppUser::getIsCompanyUser).orderByDesc(AppUser::getUpdateTime).last("LIMIT 1"));
}
@Override
public AppUser getPhoneAndUserType(String phone,String userType) {
return appUserMapper.selectOne(new LambdaQueryWrapper<AppUser>()
.eq(AppUser::getPhone, phone).eq(AppUser::getIsCompanyUser,userType).eq(AppUser::getDelFlag,"0").orderByDesc(AppUser::getUpdateTime).last("LIMIT 1"));
}
@Override
public AppUser selectByOpenid(String openid,String userType) {
return appUserMapper.selectByOpenid(openid,userType);
}
@Override
@@ -197,7 +233,7 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
}
}
//角色集合
Map mapUserRole=new HashMap<>();
Map<String,Object> mapUserRole=new HashMap<>();
switch (appUser.getIsCompanyUser()){
case StringUtil.IS_COMPANY_USER://企业
if(registerBody.getCompany()!=null){
@@ -236,32 +272,248 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
//保存sys_user
SysUser parmUser=appUserMapper.selectSysUserIdcard(appUser.getIdCard());
if(parmUser==null){
SysUser sysUser=new SysUser();
sysUser.setUserName(StringUtil.USER_KEY+appUser.getIdCard());
sysUser.setNickName(StringUtils.isEmpty(appUser.getName())?appUser.getPhone():appUser.getName());
sysUser.setPassword(SiteSecurityUtils.encryptPassword("123456"));
sysUser.setPhonenumber(appUser.getPhone());
sysUser.setSex(appUser.getSex());
sysUser.setStatus("0");
sysUser.setLoginIp(appUser.getLoginIp());
sysUser.setLoginDate(appUser.getLoginDate());
sysUser.setIdCard(appUser.getIdCard());
appUserMapper.insertSysUser(sysUser);
//保存sys_user_role
mapUserRole.put("userId",sysUser.getUserId());
appUserMapper.insertSysUserRole(mapUserRole);
registerInsertSysUser(appUser,mapUserRole);
}
//一体机密码
if(StringUtils.isNotEmpty(appUser.getYtjPassword())){
appUser.setYtjPassword(SiteSecurityUtils.encryptPassword(appUser.getYtjPassword()));
try {
appUser.setYtjPassword(SiteSecurityUtils.encryptPassword(appUser.getYtjPassword()));
} catch (Exception e) {
throw new RuntimeException("密码加密出错", e);
}
}else{
appUser.setYtjPassword(null);
}
appUserMapper.updateById(appUser);
return appUser;
}
@Override
@Transactional(rollbackFor = Exception.class)
public AppUser registerAppUserNew(RegisterBody registerBody) {
// 1. 参数校验
validateRegisterParam(registerBody);
// 2. 获取基础数据
AppUser appUser = registerBody.getAppUser();
Long loginUserId = SiteSecurityUtils.getUserId();
// 3. 登录态处理:关联已有用户信息
AppUser existingUser = handleLoginUserAssociation(appUser, loginUserId);
// 4. 差异化业务处理(企业用户/求职者)
handleUserTypeSpecificLogic(existingUser, registerBody);
// 5. 系统用户同步(不存在则创建)
syncSysUserIfNecessary(existingUser);
// 6. 一体机密码加密处理
encryptYtjPassword(existingUser);
// 7. 更新App用户信息
appUserMapper.updateById(existingUser);
return existingUser;
}
/**
* 注册参数校验
* @param registerBody 注册请求体
*/
private void validateRegisterParam(RegisterBody registerBody) {
if (registerBody == null) {
throw new IllegalArgumentException("注册请求参数不能为空");
}
}
/**
* 处理登录用户关联逻辑
* @param appUser 注册传入的用户信息
* @param loginUserId 登录用户ID
* @return 关联后的用户信息
*/
private AppUser handleLoginUserAssociation(AppUser appUser, Long loginUserId) {
if (SiteSecurityUtils.isLogin() && loginUserId != null) {
AppUser dbUser = appUserMapper.selectById(loginUserId);
if (dbUser != null) {
if (appUser == null) {
return dbUser;
} else {
appUser.setPhone(dbUser.getPhone());
appUser.setUserId(dbUser.getUserId());
return appUser;
}
}
}
return appUser;
}
/**
* 处理不同用户类型的差异化业务逻辑
* @param appUser 用户信息
* @param registerBody 注册请求体
*/
private void handleUserTypeSpecificLogic(AppUser appUser, RegisterBody registerBody) {
String userType = appUser.getIsCompanyUser();
switch (userType) {
case StringUtil.IS_COMPANY_USER:
handleCompanyUserLogic(appUser, registerBody.getCompany());
break;
default:
handleJobSeekerUserLogic(appUser, registerBody);
break;
}
}
/**
* 处理企业用户注册逻辑
* @param appUser 企业用户信息
* @param company 企业信息
*/
private void handleCompanyUserLogic(AppUser appUser, Company company) {
if (company != null) {
// 保存企业信息(新增场景)
if (company.getCompanyId() == null) {
companyMapper.insert(company);
// 批量保存企业联系人
saveCompanyContacts(company.getCompanyId(), company.getCompanyContactList());
}
// 关联企业信息到用户
appUser.setIdCard(company.getCode());
appUser.setName(company.getName());
}
}
/**
* 批量保存企业联系人
* @param companyId 企业ID
* @param contactList 联系人列表
*/
private void saveCompanyContacts(Long companyId, List<CompanyContact> contactList) {
if (contactList != null && !contactList.isEmpty()) {
contactList.forEach(contact -> contact.setCompanyId(companyId));
companyContactMapper.batchInsert(contactList);
}
}
/**
* 处理求职者用户注册逻辑
* @param appUser 求职者用户信息
* @param registerBody 注册请求体
*/
private void handleJobSeekerUserLogic(AppUser appUser, RegisterBody registerBody) {
Long userId = appUser.getUserId();
// 保存工作经历
saveUserWorkExperiences(userId, registerBody.getExperiencesList());
// 保存技能信息
saveUserAppSkills(userId, registerBody.getAppSkillsList());
}
/**
* 批量保存用户工作经历
* @param userId 用户ID
* @param experiencesList 工作经历列表
*/
private void saveUserWorkExperiences(Long userId, List<UserWorkExperiences> experiencesList) {
if (experiencesList != null && !experiencesList.isEmpty()) {
experiencesList.forEach(experience -> experience.setUserId(userId));
userWorkExperiencesMapper.batchInsert(experiencesList);
}
}
/**
* 批量保存用户技能信息
* @param userId 用户ID
* @param skillsList 技能列表
*/
private void saveUserAppSkills(Long userId, List<AppSkill> skillsList) {
if (skillsList != null && !skillsList.isEmpty()) {
skillsList.forEach(skill -> skill.setUserId(userId));
appSkillMapper.batchInsert(skillsList);
}
}
/**
* 同步系统用户(不存在则创建)
* @param appUser App用户信息
*/
private void syncSysUserIfNecessary(AppUser appUser) {
SysUser sysUser = appUserMapper.selectSysUserIdcard(appUser.getIdCard());
if (sysUser == null) {
// 构建角色映射
Map<String, Object> roleMap = buildUserRoleMap(appUser.getIsCompanyUser());
// 创建系统用户及角色关联
registerInsertSysUser(appUser, roleMap);
}
}
/**
* 构建用户角色映射
* @param userType 用户类型
* @return 角色ID映射
*/
private Map<String, Object> buildUserRoleMap(String userType) {
Map<String, Object> roleMap = new HashMap<>();
if (StringUtil.IS_COMPANY_USER.equals(userType)) {
roleMap.put("roleId", StringUtil.SYS_QY);
} else {
roleMap.put("roleId", StringUtil.SYS_QZZ);
}
return roleMap;
}
/**
* 加密一体机密码
* @param appUser 用户信息
*/
private void encryptYtjPassword(AppUser appUser) {
if (StringUtils.hasText(appUser.getYtjPassword())) {
appUser.setYtjPassword(SiteSecurityUtils.encryptPassword(appUser.getYtjPassword()));
}
}
/**
* 保存系统用户及角色关联
* @param appUser App用户信息
* @param roleMap 角色映射包含roleId
*/
private void registerInsertSysUser(AppUser appUser, Map<String, Object> roleMap) {
// 构建系统用户
SysUser sysUser = buildSysUser(appUser);
// 插入系统用户
appUserMapper.insertSysUser(sysUser);
// 关联用户角色
roleMap.put("userId", sysUser.getUserId());
appUserMapper.insertSysUserRole(roleMap);
}
/**
* 构建系统用户对象
* @param appUser App用户信息
* @return 系统用户对象
*/
private SysUser buildSysUser(AppUser appUser) {
SysUser sysUser = new SysUser();
sysUser.setUserName(StringUtil.USER_KEY + appUser.getIdCard());
sysUser.setNickName(StringUtils.isEmpty(appUser.getName()) ? appUser.getPhone() : appUser.getName());
sysUser.setPassword(SiteSecurityUtils.encryptPassword("123456"));
sysUser.setPhonenumber(appUser.getPhone());
sysUser.setSex(appUser.getSex());
sysUser.setStatus("0");
sysUser.setLoginIp(appUser.getLoginIp());
sysUser.setLoginDate(appUser.getLoginDate());
sysUser.setIdCard(appUser.getIdCard());
return sysUser;
}
@Override
public AppUser selectAppuserByIdcard(String idCard) {
return appUserMapper.selectOne(Wrappers.<AppUser>lambdaQuery().eq(AppUser::getIdCard, idCard).eq(AppUser::getDelFlag,"0").orderByDesc(AppUser::getUpdateTime).last("LIMIT 1"));
return appUserMapper.selectOne(Wrappers.<AppUser>lambdaQuery()
//.eq(AppUser::getIdCard, idCard)
.apply("UPPER(id_card) = {0}", StringUtil.toUpperCaseIgnoreBlank(idCard))
.eq(AppUser::getDelFlag,"0")
.orderByDesc(AppUser::getUpdateTime).last("LIMIT 1"));
}
@Override
@@ -393,4 +645,68 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
}
return list;
}
@Override
public AppUser getYtjValidPhone(String phone) {
return queryPhoneUser(phone);
}
@Override
public AppUser getYtjValidIdcard(String phone) {
return queryIdcardUser(phone);
}
/**
* 查询用户
* @param phone
* @return
*/
private AppUser queryIdcardUser(String phone) {
AppUser user = appUserMapper.selectOne(buildBaseIdcardQuery(phone)
.isNotNull(AppUser::getIsCompanyUser)
.eq(AppUser::getIsCompanyUser, "1"));
if (user == null) {
user = appUserMapper.selectOne(buildBaseIdcardQuery(phone)
.isNotNull(AppUser::getIsCompanyUser));
}
return user;
}
/**
* 查询用户
* @param phone
* @return
*/
public AppUser queryPhoneUser(String phone) {
AppUser user = appUserMapper.selectOne(buildBaseQuery(phone)
.isNotNull(AppUser::getIsCompanyUser)
.eq(AppUser::getIsCompanyUser, "1"));
if (user == null) {
user = appUserMapper.selectOne(buildBaseQuery(phone)
.isNotNull(AppUser::getIsCompanyUser));
}
return user;
}
/**
* 基础查询条件
* @param phone
* @return
*/
private LambdaQueryWrapper<AppUser> buildBaseQuery(String phone) {
return new LambdaQueryWrapper<AppUser>()
.eq(AppUser::getPhone, phone)
.eq(AppUser::getDelFlag, "0")
.orderByDesc(AppUser::getUpdateTime)
.last("LIMIT 1");
}
private LambdaQueryWrapper<AppUser> buildBaseIdcardQuery(String idCard) {
return new LambdaQueryWrapper<AppUser>()
.apply("UPPER(id_card) = {0}", idCard)
.eq(AppUser::getDelFlag,"0")
.orderByDesc(AppUser::getUpdateTime).last("LIMIT 1");
}
}

View File

@@ -0,0 +1,46 @@
package com.ruoyi.cms.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.cms.domain.CommunityUser;
import com.ruoyi.cms.domain.WechatGroup;
import com.ruoyi.cms.mapper.CommunityUserMapper;
import com.ruoyi.cms.mapper.WechatGroupMapper;
import com.ruoyi.cms.service.ICommunityUserService;
import com.ruoyi.common.exception.ServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
@Service
public class CommunityUserServiceImpl extends ServiceImpl<CommunityUserMapper, CommunityUser>
implements ICommunityUserService {
@Autowired
private WechatGroupMapper wechatGroupMapper;
@Override
public List<CommunityUser> selectCommunityUserList(CommunityUser communityUser) {
return baseMapper.selectList(Wrappers.lambdaQuery(CommunityUser.class)
.like(StrUtil.isNotBlank(communityUser.getWechatName()), CommunityUser::getWechatName, communityUser.getWechatName())
.like(StrUtil.isNotBlank(communityUser.getPhoneNumber()), CommunityUser::getPhoneNumber, communityUser.getPhoneNumber())
);
}
@Override
public int delCommunityUser(Long[] ids) {
if (ids == null || ids.length == 0) return 0;
Collection<Long> userIds = CollUtil.newArrayList(ids);
Long count = wechatGroupMapper.selectCount(Wrappers.lambdaQuery(WechatGroup.class)
.in(WechatGroup::getCommunityId, userIds));
if (count > 0) {
throw new ServiceException("所选工作人员已配置转发对象!");
}
return baseMapper.deleteBatchIds(userIds);
}
}

View File

@@ -13,6 +13,7 @@ import com.ruoyi.cms.service.IESJobSearchService;
import com.ruoyi.cms.util.ListUtil;
import com.ruoyi.cms.util.StringUtil;
import com.ruoyi.common.core.domain.entity.Company;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.SiteSecurityUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
@@ -28,6 +29,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -48,6 +50,16 @@ public class ESJobSearchImpl implements IESJobSearchService
private AppUserServiceImpl appUserService;
@Autowired
private ICompanyService iCompanyService;
@Autowired
private RedisCache redisCache;
// 锁的key唯一标识ES索引初始化
private static final String ES_INIT_LOCK_KEY = "es:job_document:init:lock";
// 锁过期时间30分钟确保初始化完成
private static final Integer LOCK_EXPIRE_SECONDS = 1800;
// 等待锁时间5分钟避免无限等待
private static final Integer WAIT_LOCK_SECONDS = 300;
@Autowired
private BussinessDictDataServiceImpl bussinessDictDataServicel;
@@ -58,7 +70,52 @@ public class ESJobSearchImpl implements IESJobSearchService
@PostConstruct
public void init()
{
resetTextCache();
boolean isLockAcquired = false;
try {
isLockAcquired = acquireDistributedLock();
if (isLockAcquired) {
resetTextCache();
} else {
logger.info("其他节点正在初始化ES索引直接复用无需重复执行");
}
} catch (InterruptedException e) {
logger.error("ES索引初始化等待锁异常", e);
Thread.currentThread().interrupt();
} finally {
if (isLockAcquired) {
releaseDistributedLock();
}
}
}
/**
* 基于自定义RedisCache实现分布式锁无getRedisTemplate适配版
*/
private boolean acquireDistributedLock() throws InterruptedException {
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < WAIT_LOCK_SECONDS * 1000) {
boolean lockExists = redisCache.hasKey(ES_INIT_LOCK_KEY);
if (!lockExists) {
redisCache.setCacheObject(ES_INIT_LOCK_KEY, "es_init_locked", LOCK_EXPIRE_SECONDS, TimeUnit.SECONDS);
logger.info("成功获取ES初始化分布式锁key{}", ES_INIT_LOCK_KEY);
return true;
}
TimeUnit.MILLISECONDS.sleep(500);
}
logger.warn("等待{}秒未获取到ES初始化锁放弃执行", WAIT_LOCK_SECONDS);
return false;
}
/**
* 释放分布式锁用自定义RedisCache的deleteObject方法
*/
private void releaseDistributedLock() {
try {
redisCache.deleteObject(ES_INIT_LOCK_KEY);
logger.info("已释放ES初始化分布式锁key{}", ES_INIT_LOCK_KEY);
} catch (Exception e) {
logger.error("释放ES初始化锁异常", e);
}
}
@Override
@@ -79,8 +136,24 @@ public class ESJobSearchImpl implements IESJobSearchService
public void resetTextCache() {
logger.info("正在重新刷新es");
// 删除并重新创建索引
esJobDocumentMapper.deleteIndex("job_document");
esJobDocumentMapper.createIndex();
/*esJobDocumentMapper.deleteIndex("job_document");
esJobDocumentMapper.createIndex();*/
if (esJobDocumentMapper.existsIndex("job_document")) {
esJobDocumentMapper.deleteIndex("job_document");
logger.info("已删除原有job_document索引");
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
logger.error("删除索引后休眠异常", e);
}
}
if (!esJobDocumentMapper.existsIndex("job_document")) {
esJobDocumentMapper.createIndex();
logger.info("已创建job_document索引");
} else {
logger.info("索引已被其他节点创建,直接复用");
}
// 分批次处理数据
int batchSize = 1000; // 每批次处理的数据量
@@ -104,7 +177,7 @@ public class ESJobSearchImpl implements IESJobSearchService
BeanUtils.copyBeanProp(esJobDocument, job);
CompanyVo vo=job.getCompanyVo();
esJobDocument.setCompanyVoJson(JSON.toJSONString(vo));
esJobDocument.setAppJobUrl("https://qd.zhaopinzao8dian.com/app#/packageA/pages/post/post?jobId="+ Base64.getEncoder().encodeToString(String.valueOf(job.getJobId()).getBytes()));
esJobDocument.setAppJobUrl("https://www.xjksly.cn/app#/packageA/pages/post/post?jobId="+ Base64.getEncoder().encodeToString(String.valueOf(job.getJobId()).getBytes()));
if(!StringUtil.isEmptyOrNull(job.getScale())){
esJobDocument.setScale(Integer.valueOf(job.getScale()));
}else {
@@ -145,65 +218,36 @@ public class ESJobSearchImpl implements IESJobSearchService
ESJobSearch newSearch = new ESJobSearch();
BeanUtils.copyProperties(esJobSearch,newSearch);
boolean isCompanyUser = StringUtil.IS_COMPANY_USER.equals(esJobSearch.getUserType());
//查询
if(SiteSecurityUtils.isLogin()){
AppUser appUser = appUserService.selectAppUserByUserId(SiteSecurityUtils.getUserId());
if(!ListUtil.isEmptyOrNull(appUser.getJobTitle())){
List<String> jobTitle = appUser.getJobTitle();
newSearch.setJobTitle(String.join(",", jobTitle));
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getJobTitle())){
newSearch.setJobTitle(esJobSearch.getJobTitle());
}
if(!StringUtil.isEmptyOrNull(appUser.getEducation())){
newSearch.setEducation(appUser.getEducation());
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getEducation())){
newSearch.setEducation(esJobSearch.getEducation());
}
if(!StringUtil.isEmptyOrNull(appUser.getArea())){
newSearch.setArea(appUser.getArea());
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getArea())){
newSearch.setArea(esJobSearch.getArea());
}
if(!StringUtil.isEmptyOrNull(appUser.getWorkExperience())){
newSearch.setExperience(appUser.getWorkExperience());
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getExperience())){
newSearch.setExperience(esJobSearch.getExperience());
}
if(!StringUtil.isEmptyOrNull(appUser.getSalaryMax())){
newSearch.setMaxSalary(Long.valueOf(appUser.getSalaryMax()));
}
if(!StringUtil.isEmptyOrNull(appUser.getSalaryMin())){
newSearch.setMinSalary(Long.valueOf(appUser.getSalaryMin()));
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getJobType())){
newSearch.setJobType(esJobSearch.getJobType());
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getType())){
newSearch.setType(esJobSearch.getType());
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getDescription())){
newSearch.setDescription(esJobSearch.getDescription());
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getCompanyNature())){
newSearch.setCompanyNature(esJobSearch.getCompanyNature());
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getCode())){
if (!StringUtil.isEmptyOrNull(esJobSearch.getCode())) {
newSearch.setCode(esJobSearch.getCode());
}
if (!isCompanyUser) {
setJobSeekerParams(appUser, esJobSearch, newSearch);
}
}
LambdaEsQueryWrapper<ESJobDocument> wrapper = getWrapper(newSearch,jobIds);
//todo 暂时
wrapper.limit(esJobSearch.getPageSize());
if (isCompanyUser) {
int current = esJobSearch.getCurrent() == null ? 0 : esJobSearch.getCurrent();
current = Math.max(current, 0); // 非负校验
int pageSize = esJobSearch.getPageSize() == null ? 10 : esJobSearch.getPageSize();
pageSize = Math.min(pageSize, 50); // 限制最大条数
int from = current * pageSize;
wrapper.orderByAsc(ESJobDocument::getId);
wrapper.limit(from, pageSize);
}else{
wrapper.limit(esJobSearch.getPageSize());
}
List<ESJobDocument> esJobDocuments = esJobDocumentMapper.selectList(wrapper);
if (esJobDocuments.size() < esJobSearch.getPageSize()) {
if (!isCompanyUser &&esJobDocuments.size() < esJobSearch.getPageSize()) {
// 定义要逐步放宽的搜索条件字段
List<Runnable> relaxConditions = new ArrayList<>();
relaxConditions.add(() -> newSearch.setArea(null));
@@ -251,6 +295,58 @@ public class ESJobSearchImpl implements IESJobSearchService
return esJobDocuments;
}
/**
* 拼装参数
* @param appUser
* @param esJobSearch
* @param newSearch
*/
private void setJobSeekerParams(AppUser appUser, ESJobSearch esJobSearch, ESJobSearch newSearch) {
if(!ListUtil.isEmptyOrNull(appUser.getJobTitle())){
List<String> jobTitle = appUser.getJobTitle();
newSearch.setJobTitle(String.join(",", jobTitle));
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getJobTitle())){
newSearch.setJobTitle(esJobSearch.getJobTitle());
}
if(!StringUtil.isEmptyOrNull(appUser.getEducation())){
newSearch.setEducation(appUser.getEducation());
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getEducation())){
newSearch.setEducation(esJobSearch.getEducation());
}
if(!StringUtil.isEmptyOrNull(appUser.getArea())){
newSearch.setArea(appUser.getArea());
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getArea())){
newSearch.setArea(esJobSearch.getArea());
}
if(!StringUtil.isEmptyOrNull(appUser.getWorkExperience())){
newSearch.setExperience(appUser.getWorkExperience());
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getExperience())){
newSearch.setExperience(esJobSearch.getExperience());
}
if(!StringUtil.isEmptyOrNull(appUser.getSalaryMax())){
newSearch.setMaxSalary(Long.valueOf(appUser.getSalaryMax()));
}
if(!StringUtil.isEmptyOrNull(appUser.getSalaryMin())){
newSearch.setMinSalary(Long.valueOf(appUser.getSalaryMin()));
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getJobType())){
newSearch.setJobType(esJobSearch.getJobType());
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getType())){
newSearch.setType(esJobSearch.getType());
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getDescription())){
newSearch.setDescription(esJobSearch.getDescription());
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getCompanyNature())){
newSearch.setCompanyNature(esJobSearch.getCompanyNature());
}
}
/**
* 新增全文检索数据
@@ -435,12 +531,21 @@ public class ESJobSearchImpl implements IESJobSearchService
if(!StringUtil.isEmptyOrNull(esJobSearch.getCode())){
wrapper.and(x->x.eq(ESJobDocument::getCode,esJobSearch.getCode()));
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getType())){
wrapper.and(x->x.eq(ESJobDocument::getType,esJobSearch.getType()));
}
if(esJobSearch.getJobId()!=null){
wrapper.and(x->x.eq(ESJobDocument::getJobId,esJobSearch.getJobId()));
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getJobAddress())){
wrapper.and(x->x.like(ESJobDocument::getJobAddress,esJobSearch.getJobAddress()));
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getJobLocation())){
wrapper.and(x->x.like(ESJobDocument::getJobLocation,esJobSearch.getJobLocation()));
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getRegionCode())){
wrapper.and(x->x.like(ESJobDocument::getRegionCode,esJobSearch.getRegionCode()));
}
if(!StringUtil.isEmptyOrNull(esJobSearch.getStaffType())){
wrapper.and(x->x.like(ESJobDocument::getStaffType,esJobSearch.getStaffType()));
}
if(Objects.nonNull(esJobSearch.getOrder())){
if(esJobSearch.getOrder()==1){
wrapper.orderByDesc(ESJobDocument::getIsHot);
@@ -453,9 +558,8 @@ public class ESJobSearchImpl implements IESJobSearchService
}
}
//企业用户排除es去除jobIds
boolean needExclude = true;
needExclude = !StringUtil.IS_COMPANY_USER.equals(esJobSearch.getUserType());
if(needExclude && !ListUtil.isListEmptyOrNull(jobIds)){
boolean isCompanyUser = StringUtil.IS_COMPANY_USER.equals(esJobSearch.getUserType());
if (!isCompanyUser && !ListUtil.isListEmptyOrNull(jobIds)) {
wrapper.not().in(ESJobDocument::getJobId, jobIds);
}
return wrapper;
@@ -519,6 +623,30 @@ public class ESJobSearchImpl implements IESJobSearchService
if(!StringUtil.isEmptyOrNull(jobQuery.getType())){
wrapper.and(a->a.eq(ESJobDocument::getType,jobQuery.getType()));
}
if(!StringUtil.isEmptyOrNull(jobQuery.getJobType())){
wrapper.and(a->a.eq(ESJobDocument::getJobType,jobQuery.getJobType()));
}
if(!StringUtil.isEmptyOrNull(jobQuery.getDescription())){
wrapper.and(x->x.like(ESJobDocument::getDescription,jobQuery.getDescription()));
}
if(!StringUtil.isEmptyOrNull(jobQuery.getCode())){
wrapper.and(x->x.eq(ESJobDocument::getCode,jobQuery.getCode()));
}
if(jobQuery.getJobId()!=null){
wrapper.and(x->x.eq(ESJobDocument::getJobId,jobQuery.getJobId()));
}
if(!StringUtil.isEmptyOrNull(jobQuery.getJobAddress())){
wrapper.and(x->x.like(ESJobDocument::getJobAddress,jobQuery.getJobAddress()));
}
if(!StringUtil.isEmptyOrNull(jobQuery.getJobLocation())){
wrapper.and(x->x.like(ESJobDocument::getJobLocation,jobQuery.getJobLocation()));
}
if(!StringUtil.isEmptyOrNull(jobQuery.getRegionCode())){
wrapper.and(x->x.like(ESJobDocument::getRegionCode,jobQuery.getRegionCode()));
}
if(!StringUtil.isEmptyOrNull(jobQuery.getStaffType())){
wrapper.and(x->x.like(ESJobDocument::getStaffType,jobQuery.getStaffType()));
}
if(Objects.nonNull(jobQuery.getOrder())){
if (jobQuery.getOrder()==2){
wrapper.orderByDesc(ESJobDocument::getPostingDate);
@@ -572,7 +700,7 @@ public class ESJobSearchImpl implements IESJobSearchService
}
BeanUtils.copyBeanProp(esJobDocument, job);
esJobDocument.setAppJobUrl("https://ks.zhaopinzao8dian.com/app#/packageA/pages/post/post?jobId="+ Base64.getEncoder().encodeToString(String.valueOf(job.getJobId()).getBytes()));
esJobDocument.setAppJobUrl("https://www.xjksly.cn/app#/packageA/pages/post/post?jobId="+ Base64.getEncoder().encodeToString(String.valueOf(job.getJobId()).getBytes()));
if(!StringUtil.isEmptyOrNull(job.getScale())){
esJobDocument.setScale(Integer.valueOf(job.getScale()));
}else {

View File

@@ -6,6 +6,8 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Arrays;
import com.ruoyi.cms.util.StringUtil;
import com.ruoyi.common.core.domain.entity.File;
import com.ruoyi.cms.mapper.FileMapper;
import com.ruoyi.cms.service.IFileService;
@@ -17,6 +19,8 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
/**
* 文件Service业务层处理
*
@@ -113,7 +117,7 @@ public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements IF
// 保存文件信息到数据库
saveFileInfo(fileName, bussinessid);
return AjaxResult.success("http://39.98.44.136/file/"+fileName);
return AjaxResult.success(StringUtil.PATH_PROXY_50+fileName);
} catch (IOException e) {
e.printStackTrace();
return AjaxResult.error("文件上传失败");
@@ -127,7 +131,7 @@ public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements IF
* @return
*/
@Override
public AjaxResult uploadFile(MultipartFile file, Long bussinessid) {
public AjaxResult uploadFile(MultipartFile file, Long bussinessid, HttpServletRequest request) {
if (file.isEmpty()) {
return AjaxResult.error("文件为空,请选择文件上传");
}
@@ -147,10 +151,11 @@ public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements IF
Files.copy(file.getInputStream(), filePath);
// 保存文件信息到数据库
saveFileInfo(fileName, bussinessid);
File svFile=saveFileInfo(fileName, bussinessid);
AjaxResult ajaxResult=AjaxResult.success();
ajaxResult.put("filePath","http://39.98.44.136/file/"+fileName);
ajaxResult.put("bussinessid",bussinessid);
ajaxResult.put("filePath", StringUtil.getFilePath(request)+fileName);
ajaxResult.put("bussinessid",String.valueOf(bussinessid));
ajaxResult.put("id",String.valueOf(svFile.getId()));
return ajaxResult;
} catch (IOException e) {
e.printStackTrace();
@@ -158,11 +163,12 @@ public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements IF
}
}
private void saveFileInfo(String fileName, Long bussinessid) {
private File saveFileInfo(String fileName, Long bussinessid) {
// 这里假设你已经有了一个FileService来处理数据库操作
File file = new File();
file.setBussinessid(bussinessid);
file.setFileUrl(fileName);
this.save(file);
return file;
}
}

View File

@@ -15,7 +15,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
@@ -109,12 +108,14 @@ public class JobApplyServiceImpl extends ServiceImpl<JobApplyMapper,JobApply> im
@Override
public HashMap<String, Integer> statistics() {
Integer applyCount = jobApplyMapper.applyJob(SiteSecurityUtils.getUserId()).size();
Integer applyCencalCount=jobApplyMapper.applyCencalCount(SiteSecurityUtils.getUserId());
Integer collectionJobCount = jobCollectionMapper.collectionJob(SiteSecurityUtils.getUserId()).size();
Integer collectionCompanyCount = companyCollectionMapper.collectionJob(SiteSecurityUtils.getUserId()).size();
Integer jobReviewCount = appReviewJobMapper.review(SiteSecurityUtils.getUserId(),new MineJobQuery()).size();
Integer fairCollecitonCount = fairCollectionMapper.selectList(Wrappers.<FairCollection>lambdaQuery().eq(FairCollection::getUserId, SiteSecurityUtils.getUserId())).size();
HashMap<String, Integer> map = new HashMap<>();
map.put("applyCount", applyCount);
map.put("applyCencalCount", applyCencalCount);
map.put("collectionCount", collectionJobCount+collectionCompanyCount);
map.put("jobReviewCount", jobReviewCount);
map.put("fairCollecitonCount", fairCollecitonCount);
@@ -160,4 +161,14 @@ public class JobApplyServiceImpl extends ServiceImpl<JobApplyMapper,JobApply> im
public int updateJobZphApply(JobApply jobApply) {
return jobApplyMapper.updateJobZphApply(jobApply);
}
@Override
public List<Job> selectCencalList(JobApply jobApply) {
return jobApplyMapper.selectCencalList(jobApply);
}
@Override
public int applyJobCencal(JobApply jobApply) {
return jobApplyMapper.applyJobCencal(jobApply);
}
}

View File

@@ -262,8 +262,10 @@ public class JobCollectionServiceImpl extends ServiceImpl<JobCollectionMapper,Jo
}
// 4. 地点匹配
if (user.getArea() != null && user.getArea().contains(jobLocation) || jobLocation.contains(user.getArea())) {
matchScore += 1;
if (user.getArea() != null) {
if(user.getArea().contains(jobLocation) || jobLocation.contains(user.getArea())){
matchScore += 1;
}
}
// 5. 年龄估算(从生日计算)

View File

@@ -18,4 +18,9 @@ public class JobContactServiceImpl extends ServiceImpl<JobContactMapper, JobCont
public List<JobContact> getSelectList(JobContact jobContact){
return jobContactMapper.getSelectList(jobContact);
}
@Override
public int batchInsert(List<JobContact> list) {
return jobContactMapper.batchInsert(list);
}
}

View File

@@ -1,7 +1,5 @@
package com.ruoyi.cms.service.impl;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -10,6 +8,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.cms.domain.*;
import com.ruoyi.cms.domain.vo.JobExcelVo;
import com.ruoyi.cms.util.notice.NoticeUtils;
import com.ruoyi.common.core.domain.entity.File;
import com.ruoyi.cms.domain.query.ESJobSearch;
@@ -25,11 +24,13 @@ import com.ruoyi.common.core.domain.entity.Company;
import com.ruoyi.common.core.domain.entity.JobTitle;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.SiteSecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.dromara.easyes.core.biz.EsPageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,6 +38,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
@@ -46,6 +48,8 @@ import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 岗位Service业务层处理
*
@@ -222,13 +226,38 @@ public class JobServiceImpl extends ServiceImpl<JobMapper,Job> implements IJobSe
if(contacts!=null){
job.setJobContactList(contacts);
}
//查询附件
File file=new File();
file.setBussinessid(jobId);
List<File> filesList=fileMapper.selectFileList(file);
if(filesList!=null){
job.setFilesList(filesList);
return job;
}
@Override
public Job selectHttpJobByJobId(Long jobId, HttpServletRequest request)
{
Job job = jobMapper.selectById(jobId);
//查询公司信息
if(Objects.nonNull(job.getCompanyId())){
Company company = companyMapper.selectById(job.getCompanyId());
job.setCompany(company);
}
//查询联系人
JobContact contact = new JobContact();
contact.setJobId(jobId);
List<JobContact> contacts = jobContactMapper.getSelectList(contact);
job.setJobContactList(contacts == null ? Collections.emptyList() : contacts);
//查询附件
String baseFilePath = StringUtil.getFilePath(request);
//查询附件
File queryFile = new File();
queryFile.setBussinessid(jobId);
List<File> filesList = Optional.ofNullable(fileMapper.selectFileList(queryFile))
.orElseGet(Collections::emptyList);
//添加路径
List<File> processedFiles = filesList.stream()
.filter(Objects::nonNull)
.filter(file -> file.getFileUrl() != null && !file.getFileUrl().trim().isEmpty())
.peek(file -> file.setFileUrl(String.join("", baseFilePath, file.getFileUrl())))
.collect(Collectors.toList());
job.setFilesList(processedFiles);
return job;
}
@@ -246,6 +275,65 @@ public class JobServiceImpl extends ServiceImpl<JobMapper,Job> implements IJobSe
return jobs;
}
@Override
public List<Job> selectHttpJobList(Job job,HttpServletRequest request)
{
List<Job> jobs = jobMapper.selectJobList(job);
if (CollectionUtils.isNotEmpty(jobs)) {
String baseFilePath = StringUtil.getFilePath(request);
baseFilePath = baseFilePath == null ? "" : baseFilePath;
List<Long> jobIds = jobs.stream().filter(Objects::nonNull)
.map(Job::getJobId).filter(Objects::nonNull)
.collect(Collectors.toList());
//查询所有附件
List<File> allFiles = CollectionUtils.isNotEmpty(jobIds)
? Optional.ofNullable(fileMapper.selectFileListByBussinessIds(jobIds))
.orElseGet(Collections::emptyList)
: Collections.emptyList();
//查询所有联系人
List<JobContact> allJobContacts = CollectionUtils.isNotEmpty(jobIds)
? Optional.ofNullable(jobContactMapper.selectByJobIds(jobIds)).orElse(Collections.emptyList())
: Collections.emptyList();
//岗位联系人
Map<Long, List<JobContact>> jobContactGroupMap = allJobContacts.stream()
.filter(Objects::nonNull)
.filter(contact -> Objects.nonNull(contact.getJobId()))
.collect(Collectors.groupingBy(
JobContact::getJobId,
Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)
));
//附件分组
String finalBaseFilePath = baseFilePath;
Map<Long, List<File>> fileGroupMap = allFiles.stream()
.filter(Objects::nonNull)
.filter(file -> Objects.nonNull(file.getBussinessid()))
.filter(file -> StringUtils.isNotBlank(file.getFileUrl()))
.peek(file -> {
String fileUrl = file.getFileUrl().trim();
String fullFileUrl = StringUtils.join(finalBaseFilePath, fileUrl);
file.setFileUrl(fullFileUrl);
})
.collect(Collectors.groupingBy(
File::getBussinessid,
Collectors.collectingAndThen(Collectors.toList(),Collections::unmodifiableList)
));
jobs.forEach(jobItem -> {
if (Objects.nonNull(jobItem)) {
Long jobItemId = jobItem.getJobId();
List<File> jobFiles = fileGroupMap.getOrDefault(jobItemId, Collections.emptyList());
List<JobContact> jobContent = jobContactGroupMap.getOrDefault(jobItemId, Collections.emptyList());
jobItem.setFilesList(jobFiles);
jobItem.setJobContactList(jobContent);
}
});
}
return jobs;
}
@Override
public int view(Long jobId) {
Job job = jobMapper.selectById(jobId);
@@ -263,18 +351,19 @@ public class JobServiceImpl extends ServiceImpl<JobMapper,Job> implements IJobSe
* @param job 岗位
* @return 结果
*/
@Transactional
@Override
public int insertJob(Job job)
{
int insert = jobMapper.insert(job);
//todo 线程池管理
Thread thread = new Thread(() -> {
VectorJob jobVector = jobMapper.selectVectorJob(job.getJobId());
jobVector.setJobUrl("http://39.98.44.136/app#/packageA/pages/post/post?jobId="+job.getJobId());
String jsonBody = JSONUtil.toJsonStr(jobVector);
HttpUtil.post("http://39.98.44.136:6004/insert_vector", jsonBody);
});
thread.start();
// Thread thread = new Thread(() -> {
// VectorJob jobVector = jobMapper.selectVectorJob(job.getJobId());
// jobVector.setJobUrl("http://39.98.44.136/app#/packageA/pages/post/post?jobId="+job.getJobId());
// String jsonBody = JSONUtil.toJsonStr(jobVector);
// HttpUtil.post("http://39.98.44.136:6004/insert_vector", jsonBody);
// });
// thread.start();
if(insert>0){
//添加联系人
List<JobContact> jobContactList = job.getJobContactList() != null ? job.getJobContactList() : Collections.emptyList();
@@ -332,32 +421,75 @@ public class JobServiceImpl extends ServiceImpl<JobMapper,Job> implements IJobSe
int i=jobMapper.updateById(job);
//修改岗位联系人列表
if(i>0){
LambdaUpdateWrapper<JobContact> updateWrapper = Wrappers.<JobContact>lambdaUpdate()
.eq(JobContact::getJobId, job.getJobId())
.set(JobContact::getDelFlag, Constants.Del_FLAG_DELETE);
JobContact emptyEntity = new JobContact();
jobContactMapper.update(emptyEntity, updateWrapper);
List<JobContact> jobContactList = job.getJobContactList() != null ? job.getJobContactList() : Collections.emptyList();
List<JobContact> insertList = jobContactList.stream()
.filter(Objects::nonNull).map(x -> {
JobContact jobContact = new JobContact();
jobContact.setJobId(job.getJobId());
return jobContact;
}).collect(Collectors.toList());
if (!insertList.isEmpty()) {
jobContactMapper.batchInsert(insertList);
}
// 处理联系人
handleJobContact(job);
//添加附件
List<File> filesList = job.getFilesList() != null ? job.getFilesList() : Collections.emptyList();
List<Long> longs = filesList.stream().filter(Objects::nonNull).filter(file -> Objects.isNull(file.getId())).map(File::getBussinessid).collect(Collectors.toList());
if(!longs.isEmpty()){
fileMapper.updateBussinessids(longs,job.getJobId());
}
handleJobFile(job);
}
return i;
}
/**
* 处理联系人
* @param job
*/
private void handleJobContact(Job job) {
List<JobContact> jobContactList = Optional.ofNullable(job.getJobContactList()).orElse(Collections.emptyList());
if (CollectionUtils.isEmpty(jobContactList)) {
return;
}
List<JobContact> insertList = new ArrayList<>();
List<JobContact> updateList = new ArrayList<>();
for (JobContact originContact : jobContactList) {
if (originContact == null) {
continue;
}
JobContact targetContact = new JobContact();
targetContact.setJobId(job.getJobId());
targetContact.setContactPerson(originContact.getContactPerson());
targetContact.setContactPersonPhone(originContact.getContactPersonPhone());
targetContact.setPosition(originContact.getPosition());
if (originContact.getId() == null) {
insertList.add(targetContact);
} else {
targetContact.setId(originContact.getId());
updateList.add(targetContact);
}
}
if (!CollectionUtils.isEmpty(insertList)) {
jobContactMapper.batchInsert(insertList);
}
if (!CollectionUtils.isEmpty(updateList)) {
updateList.forEach(contact -> {
LambdaUpdateWrapper<JobContact> updateWrapper = Wrappers.<JobContact>lambdaUpdate()
.eq(JobContact::getId, contact.getId());
jobContactMapper.update(contact, updateWrapper);
});
}
}
/**
* 处理附件
* @param job
*/
private void handleJobFile(Job job) {
List<File> filesList = Optional.ofNullable(job.getFilesList()).orElse(Collections.emptyList());
List<Long> fileIds = filesList.stream()
.filter(Objects::nonNull)
.map(File::getBussinessid)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (!CollectionUtils.isEmpty(fileIds)) {
fileMapper.updateBussinessids(fileIds, job.getJobId());
}
}
/**
* 批量删除岗位
*
@@ -463,6 +595,63 @@ public class JobServiceImpl extends ServiceImpl<JobMapper,Job> implements IJobSe
}
@Override
public Job selectHttpJobByJobIdApp(Long jobId,HttpServletRequest request) {
Job job = jobMapper.selectById(jobId);
//查询公司信息
if(Objects.nonNull(job.getCompanyId())){
Company company = companyMapper.selectById(job.getCompanyId());
job.setCompany(company);
}
if(SiteSecurityUtils.isLogin()){
//查询申请信息
Long applyCount = jobApplyMapper.selectCount(Wrappers.<JobApply>lambdaQuery().eq(JobApply::getJobId, jobId).eq(JobApply::getUserId, SiteSecurityUtils.getUserId()));
job.setIsApply(applyCount>0?1:0);
//查询收藏信息
Long collectionCount = jobCollectionMapper.selectCount(Wrappers.<JobCollection>lambdaQuery().eq(JobCollection::getJobId, jobId).eq(JobCollection::getUserId, SiteSecurityUtils.getUserId()));
job.setIsCollection(collectionCount>0?1:0);
//todo asyn
//保存浏览记录
List<AppReviewJob> appReviewJobs = appReviewJobMapper.selectList(Wrappers.<AppReviewJob>lambdaQuery().eq(AppReviewJob::getUserId, SiteSecurityUtils.getUserId()).eq(AppReviewJob::getJobId, jobId));
//之前相同岗位的记录删除 保存最新的浏览记录
if(!appReviewJobs.isEmpty()){
appReviewJobMapper.deleteBatchIds(appReviewJobs.stream().map(AppReviewJob::getId).collect(Collectors.toList()));
}
AppReviewJob appReviewJob = new AppReviewJob();
appReviewJob.setUserId(SiteSecurityUtils.getUserId());
LocalDateTime now = LocalDateTime.now();
String formattedDate = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
appReviewJob.setReviewDate(formattedDate);
appReviewJob.setJobId(jobId);
appReviewJobMapper.insert(appReviewJob);
}
this.view(jobId);
//查询联系人
JobContact contact = new JobContact();
contact.setJobId(jobId);
List<JobContact> contacts = jobContactMapper.getSelectList(contact);
job.setJobContactList(contacts == null ? Collections.emptyList() : contacts);
//查询附件
String baseFilePath = StringUtil.getFilePath(request);
File queryFile = new File();
queryFile.setBussinessid(jobId);
List<File> filesList = Optional.ofNullable(fileMapper.selectFileList(queryFile))
.orElseGet(Collections::emptyList);
//添加路径
List<File> processedFiles = filesList.stream()
.filter(Objects::nonNull)
.filter(file -> file.getFileUrl() != null && !file.getFileUrl().trim().isEmpty())
.peek(file -> file.setFileUrl(String.join("", baseFilePath, file.getFileUrl())))
.collect(Collectors.toList());
job.setFilesList(processedFiles);
//查询投递人
List<AppUser> appUsers=jobMapper.selectApplyJobUserList(jobId);
job.setApplyUsers(appUsers == null ? Collections.emptyList() : appUsers);
return job;
}
@Override
public List<CandidateVO> candidates(Long jobId) {
List<CandidateVO> jobList = jobApplyMapper.candidates(jobId);
@@ -882,30 +1071,55 @@ public class JobServiceImpl extends ServiceImpl<JobMapper,Job> implements IJobSe
@Override
public List<ESJobDocument> sysRecommend(ESJobSearch esJobSearch) {
Long userId=SecurityUtils.isLogin()?SecurityUtils.getUserId():null;
List<Long> jobList=new ArrayList<>();
String jobKey="";
AppUser appUser=null;
if(userId!=null){
jobKey=CacheConstants.SYS_JOB_IDS+ userId;
RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
JSONArray cacheObject = redisCache.getCacheObject(jobKey);
jobList = new ArrayList<>();
if(Objects.isNull(cacheObject)){
ArrayList<Long> longs = new ArrayList<>();
jobList =longs;
}else {
jobList = cacheObject.toList(Long.class);
Long userId = SecurityUtils.isLogin() ? SecurityUtils.getUserId() : null;
List<Long> viewedJobIds = new ArrayList<>();
String jobCacheKey = "";
AppUser appUser = null;
if (userId != null) {
jobCacheKey = CacheConstants.SYS_JOB_IDS + userId;
try {
JSONArray cacheObject = redisCache.getCacheObject(jobCacheKey);
if (Objects.isNull(cacheObject)) {
cacheObject = new JSONArray();
}
viewedJobIds = cacheObject.stream()
.map(o -> Long.parseLong(o.toString()))
.distinct().limit(100).collect(Collectors.toList());
String idCard = RoleUtils.getCurrentUseridCard();
if (StringUtils.isNotEmpty(idCard)) {
appUser = appUserService.selectAppuserByIdcard(idCard);
}
} catch (Exception e) {
log.error("获取用户已查看岗位缓存失败", e);
viewedJobIds = new ArrayList<>();
}
appUser=appUserService.selectAppuserByIdcard(RoleUtils.getCurrentUseridCard());
}
//从es中查询
List<ESJobDocument> jobListResult = iesJobSearchService.selectSysTextListExceptJobId(esJobSearch,jobList,appUser);
//存入当前session中查看的岗位 避免重复 todo 定时删除 key上保存用户信息
jobList.addAll(jobListResult.stream().map(ESJobDocument::getJobId).collect(Collectors.toList()));
if(StringUtils.isNotEmpty(jobKey)){
redisCache.setCacheObject(jobKey,jobList);
List<ESJobDocument> jobListResult = new ArrayList<>();
try {
jobListResult = iesJobSearchService.selectSysTextListExceptJobId(esJobSearch, viewedJobIds, appUser);
//降级策略:如果过滤后无数据,忽略已查看记录重新查询
if (CollectionUtils.isEmpty(jobListResult) && !viewedJobIds.isEmpty()) {
log.warn("用户{}已查看岗位过多,忽略过滤条件重新查询");
jobListResult = iesJobSearchService.selectSysTextListExceptJobId(esJobSearch, new ArrayList<>(), appUser);
}
} catch (Exception e) {
log.error("ES推荐岗位查询失败", e);
return new ArrayList<>();
}
if (userId != null && !CollectionUtils.isEmpty(jobListResult)) {
try {
List<Long> newJobIds = jobListResult.stream().map(ESJobDocument::getJobId).distinct().collect(Collectors.toList());
List<Long> updatedJobIds = Stream.concat(viewedJobIds.stream(), newJobIds.stream()).distinct().limit(100).collect(Collectors.toList());
redisCache.setCacheObject(jobCacheKey, updatedJobIds, 24, TimeUnit.HOURS);
} catch (Exception e) {
log.error("更新用户已查看岗位缓存失败", e);
}
}
List<ESJobDocument> esJobDocuments = sysUserCollection(jobListResult);
return esJobDocuments;
}
@@ -918,4 +1132,171 @@ public class JobServiceImpl extends ServiceImpl<JobMapper,Job> implements IJobSe
params.put("offset", offset*batchSize);
return jobMapper.selectAllJob(params);
}
@Override
public Integer getTotals(Job job) {
return jobMapper.getTotals(job);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void uploadFileJob(List<JobExcelVo> list) {
//查询所有企业
Map<String, Long> companyNameToIdMap = companyNameToIdMap(list);
//岗位字典转换
List<Job> jobList = buildJobList(list, companyNameToIdMap);
//岗位去重
List<Job> dedupedJobList = dedupJobList(jobList);
//批量保存岗
jobMapper.updateFileBatchInsert(dedupedJobList);
//构建联系人列表(关联去重后的岗位)
List<JobContact> contactList = buildJobContactList(dedupedJobList, list, companyNameToIdMap);
//联系人去重企业ID+联系人手机号 去重企业ID为0/null时仅按手机号去重
List<JobContact> dedupedContactList = dedupContactList(contactList, dedupedJobList);
//批量保存联系人
if (CollectionUtils.isNotEmpty(dedupedContactList)) {
jobContactMapper.batchInsert(dedupedContactList);
}
}
/**
* 企业名称查询关联(核心业务逻辑)
*/
private Map<String, Long> companyNameToIdMap(List<JobExcelVo> allExcelVoList) {
Set<String> companyNameSet = allExcelVoList.stream()
.map(JobExcelVo::getCompanyName)
.filter(StringUtils::hasText)
.collect(Collectors.toSet());
if (companyNameSet.isEmpty()) {
return Collections.emptyMap();
}
List<Company> companies = companyMapper.selectByNames(new ArrayList<>(companyNameSet));
if (CollectionUtils.isEmpty(companies)) {
return Collections.emptyMap();
}
return companies.stream()
.collect(Collectors.toMap(
Company::getName,
Company::getCompanyId,
(v1, v2) -> v1
));
}
/**
* 字典转换 + Job对象构建业务数据转换
*/
private List<Job> buildJobList(List<JobExcelVo> allExcelVoList, Map<String, Long> companyNameToIdMap) {
return allExcelVoList.stream().map(it -> {
Job job = new Job();
BeanUtils.copyProperties(it, job);
//字典转换
String education = DictUtils.getDictValue("education", it.getEducation());
String experience = DictUtils.getDictValue("experience", it.getExperience());
String jobType = DictUtils.getDictValue("job_type", it.getJobType());
String type = DictUtils.getDictValue("position_type", it.getType());
String areaCode = DictUtils.getDictValue("area", it.getJobLocation());
// 字段赋值
job.setEducation(education);
job.setExperience(experience);
job.setJobType(jobType);
job.setType(type);
job.setJobLocation(StringUtils.isBlank(it.getJobAddress()) ? it.getJobLocation() : it.getJobAddress());
job.setJobLocationAreaCode(StringUtils.isBlank(areaCode) ? 0 : Integer.valueOf(areaCode));
job.setIsPublish(1);
job.setDataSource("1");
job.setPostingDate(DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS));
String companyName = it.getCompanyName();
job.setCompanyName(companyName);
job.setCompanyId(companyNameToIdMap.getOrDefault(companyName, null));
return job;
}).collect(Collectors.toList());
}
/**
* 岗位去重业务规则企业ID+岗位名称+工作地点唯一)
*/
private List<Job> dedupJobList(List<Job> jobList) {
Map<String, Job> dedupMap = new LinkedHashMap<>();
for (Job job : jobList) {
String dedupKey = String.format(
"%s_%s_%s",
Optional.ofNullable(job.getCompanyId()).orElse(null),
job.getJobTitle().trim(),
job.getJobLocation().trim()
);
dedupMap.putIfAbsent(dedupKey, job);
}
return new ArrayList<>(dedupMap.values());
}
/**
* 构建联系人列表(业务关联)
*/
private List<JobContact> buildJobContactList(List<Job> dedupedJobList, List<JobExcelVo> allExcelVoList, Map<String, Long> companyNameToIdMap) {
List<JobContact> contactList = new ArrayList<>();
Map<String, Job> jobMatchMap = dedupedJobList.stream()
.collect(Collectors.toMap(
job -> String.format(
"%s_%s",
job.getJobTitle().trim(),
job.getJobLocation().trim()
),
job -> job
));
for (JobExcelVo vo : allExcelVoList) {
String jobTitle = vo.getJobTitle().trim();
String jobLocation = StringUtils.isBlank(vo.getJobAddress()) ? vo.getJobLocation().trim() : vo.getJobAddress().trim();
String matchKey = String.format("%s_%s", jobTitle, jobLocation);
Job matchedJob = jobMatchMap.get(matchKey);
if (matchedJob == null) {
continue;
}
JobContact contact = new JobContact();
contact.setJobId(matchedJob.getJobId());
contact.setContactPerson(vo.getContactPerson());
contact.setContactPersonPhone(vo.getContactPersonPhone());
contactList.add(contact);
}
return contactList;
}
/**
* 联系人去重兼容企业ID为0的情况
*/
private List<JobContact> dedupContactList(List<JobContact> contactList, List<Job> dedupedJobList) {
if (CollectionUtils.isEmpty(contactList)) {
return new ArrayList<>();
}
Set<Long> validJobIds = dedupedJobList.stream().map(Job::getJobId).filter(Objects::nonNull).collect(Collectors.toSet());
Map<String, JobContact> dedupMap = new LinkedHashMap<>();
for (JobContact contact : contactList) {
if (StringUtils.isBlank(contact.getContactPersonPhone())) {
System.out.printf("跳过联系人:姓名=%s手机号为空%n", contact.getContactPerson());
continue;
}
Long jobId = contact.getJobId();
if (jobId == null || !validJobIds.contains(jobId)) {
System.out.printf("跳过联系人:姓名=%s岗位ID无效或不存在%n", contact.getContactPerson());
continue;
}
String dedupKey = String.format("%s_%s",jobId,contact.getContactPersonPhone().trim());
dedupMap.putIfAbsent(dedupKey, contact);
}
return new ArrayList<>(dedupMap.values());
}
}

View File

@@ -2,14 +2,12 @@ package com.ruoyi.cms.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.cms.domain.CommercialArea;
import com.ruoyi.cms.domain.CompanyCardCollection;
import com.ruoyi.cms.domain.Statics;
import com.ruoyi.cms.domain.StaticsJob;
import com.ruoyi.cms.domain.query.Staticsquery;
import com.ruoyi.cms.mapper.CompanyCardCollectionMapper;
import com.ruoyi.cms.mapper.StaticsMapper;
import com.ruoyi.cms.service.ISubwayLineService;
import com.ruoyi.cms.service.StaticsqueryService;
import com.ruoyi.common.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -551,4 +549,16 @@ public class StaticsqueryServiceImpl extends ServiceImpl<StaticsMapper, Statics>
}
return result;
}
@Override
public Map<String, Object> qygwtjCount(Staticsquery staticsquery) {
String eneTime= DateUtils.addOneDay(staticsquery.getEndTime(),DateUtils.YYYY_MM_DD,DateUtils.YYYY_MM_DD);
staticsquery.setEndTime(eneTime);
HashMap<String, Object> result = new HashMap<>();
StaticsJob staticsJob=staticsMapper.qygwtjCount(staticsquery);
List<StaticsJob> list=staticsMapper.getGroutCityJobs(staticsquery);
result.put("hz",staticsJob);
result.put("group",list);
return result;
}
}

View File

@@ -5,6 +5,7 @@ import com.ruoyi.cms.service.SysAreaService;
import com.ruoyi.common.core.domain.entity.SysArea;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@@ -16,4 +17,9 @@ public class SysAreaServiceImpl implements SysAreaService {
public List<SysArea> getList(SysArea sysArea){
return sysAreaMapper.getList(sysArea);
}
@Override
public List<SysArea> getCityList(SysArea sysArea) {
return sysAreaMapper.getCityList(sysArea);
}
}

View File

@@ -0,0 +1,20 @@
package com.ruoyi.cms.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.cms.domain.WechatGroup;
import com.ruoyi.cms.domain.vo.WechatGroupVo;
import com.ruoyi.cms.mapper.WechatGroupMapper;
import com.ruoyi.cms.service.IWechatGroupService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class WechatGroupServiceImpl extends ServiceImpl<WechatGroupMapper, WechatGroup>
implements IWechatGroupService {
@Override
public List<WechatGroupVo> selectWechatGroupList(WechatGroup wechatGroup) {
return baseMapper.selectWechatGroupList(wechatGroup);
}
}

View File

@@ -0,0 +1,44 @@
package com.ruoyi.cms.util;
/**
* 阿里云配置
*/
public class AliyunNlsUtils {
/**
* 标志
*/
public static final boolean USE_TEST_ENV=true;
/**
* 测试nls
*/
public static final String NLS_TEST_URL="wss://nls-gateway-cn-shanghai.aliyuncs.com";
/**
* 测试微信获取oppenid链接
*/
public static final String WX_TEST_URL="https://api.weixin.qq.com/sns/jscode2session";
/**
* 正式nls
*/
public static final String NLS_FORMAL_URL="http://192.168.2.102:10044";
/**
* 根据环境判断正式还是测试
* @return
*/
public static String getNlsUrl(){
String url = USE_TEST_ENV ? NLS_TEST_URL : NLS_FORMAL_URL;
System.out.println("nls当前环境" + (USE_TEST_ENV ? "测试" : "正式") + "WebSocket地址" + url);
return url;
}
/**
* 微信授权登录
* @return
*/
public static String getWXoppenidUrl(){
String url = USE_TEST_ENV ? WX_TEST_URL : NLS_FORMAL_URL+"/weixin";
System.out.println("微信授权登录当前环境:" + (USE_TEST_ENV ? "测试" : "正式") + "获取oppenid地址" + url);
return url;
}
}

View File

@@ -0,0 +1,100 @@
package com.ruoyi.cms.util;
import org.springframework.util.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 文本内容清理工具类:移除思维链、提取有效业务文本
* 核心能力:
* 1. 优先提取 <file_upload> 标签内的有效文本(自动剔除标签外的思维链)
* 2. 无 file_upload 标签时,移除 标签及内部的思维链
* 3. 统一清理文本格式(多余换行/空格、末尾问号)
*/
public class ChatTextCleanUtil {
// 正则常量:匹配 <file_upload> 标签及内部内容(提取标签内文本)
private static final String PATTERN_FILE_UPLOAD = "\\<file_upload\\>(.*?)\\</file_upload\\>";
// 正则常量:匹配 标签及内部的思维链内容
private static final String PATTERN_THINK_CHAIN = "\\<think\\>.*?\\</think\\>";
// 正则常量:匹配多个换行符
private static final String PATTERN_MULTI_NEWLINE = "\\n+";
// 正则常量:匹配多个空白字符(空格/制表符等)
private static final String PATTERN_MULTI_SPACE = "\\s+";
// 正则常量:匹配末尾的问号
private static final String PATTERN_TRAILING_QUESTION = "\\?$";
/**
* 清理文本中的思维链,提取有效业务内容
*
* @param content 原始文本内容可能包含思维链、file_upload标签等
* @return 清理后的纯业务文本,空值/空文本返回原内容
*/
public static String removeThinkChain(String content) {
// 空值/空文本直接返回,避免后续处理
if (!StringUtils.hasText(content)) {
return content;
}
// 提取有效文本优先处理file_upload标签
String validContent = extractFileUploadContent(content);
// 若未提取到file_upload内容移除think标签的思维链
if (!StringUtils.hasText(validContent)) {
validContent = cleanThinkChainContent(content);
}
// 统一格式化文本(清理多余换行/空格、末尾问号)
return formatText(validContent);
}
/**
* 提取 <file_upload> 标签内的文本内容
*
* @param content 原始文本
* @return 标签内的文本(无标签返回空字符串)
*/
private static String extractFileUploadContent(String content) {
Pattern pattern = Pattern.compile(PATTERN_FILE_UPLOAD, Pattern.DOTALL);
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
// 提取分组1的内容标签内文本并去除首尾空格
return matcher.group(1).trim();
}
return "";
}
/**
* 移除 标签及内部的思维链内容
*
* @param content 原始文本
* @return 移除思维链后的文本,空值返回空字符串
*/
private static String cleanThinkChainContent(String content) {
if (!StringUtils.hasText(content)) {
return "";
}
Pattern pattern = Pattern.compile(PATTERN_THINK_CHAIN, Pattern.DOTALL);
// 替换think标签及内容为空再去除首尾空格
return pattern.matcher(content).replaceAll("").trim();
}
/**
* 格式化文本:清理多余换行、空格,移除末尾问号
*
* @param content 需要格式化的文本
* @return 格式化后的文本,空值返回空字符串
*/
private static String formatText(String content) {
if (!StringUtils.hasText(content)) {
return "";
}
// 多个换行符替换为单个空格
String formatted = content.replaceAll(PATTERN_MULTI_NEWLINE, " ");
//多个空白字符替换为单个空格
formatted = formatted.replaceAll(PATTERN_MULTI_SPACE, " ");
//移除末尾的问号
formatted = formatted.replaceAll(PATTERN_TRAILING_QUESTION, "");
//最终再trim一次确保无首尾空格
return formatted.trim();
}
}

View File

@@ -1,5 +1,7 @@
package com.ruoyi.cms.util;
import com.ruoyi.common.utils.StringUtils;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
@@ -12,6 +14,8 @@ public class DateValidateUtil {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final String ERROR_MSG = "生日格式错误!不为空时必须填写 yyyy-MM-dd 完整格式(如 1991-09-01";
private static final String DEFAULT_DATE_FORMAT_TIP = "请使用 yyyy-MM-dd 格式";
// 改为校验 String 类型
public static String validateBirthDate(String birthDateStr) {
if (birthDateStr == null || birthDateStr.trim().isEmpty()) {
@@ -26,4 +30,73 @@ public class DateValidateUtil {
return ERROR_MSG; // 格式错误返回提示
}
}
/**
* 校验开始时间和结束时间的合法性
* @param startTime 开始时间字符串
* @param endTime 结束时间字符串
* @return 校验不通过返回错误信息校验通过返回null
*/
public static String validateStartAndEndTime(String startTime, String endTime) {
StringBuilder errorMsg = new StringBuilder();
if (StringUtils.isBlank(startTime)) {
errorMsg.append("开始时间不能为空!");
} else {
if (!isValidDate(startTime)) {
errorMsg.append(StringUtils.isNotBlank(errorMsg.toString()) ? " " : "")
.append("开始时间格式错误,").append(DEFAULT_DATE_FORMAT_TIP);
}
}
if (StringUtils.isBlank(endTime)) {
errorMsg.append(StringUtils.isNotBlank(errorMsg.toString()) ? " " : "")
.append("结束时间不能为空!");
} else {
if (!isValidDate(endTime)) {
errorMsg.append(StringUtils.isNotBlank(errorMsg.toString()) ? " " : "")
.append("结束时间格式错误,").append(DEFAULT_DATE_FORMAT_TIP);
} else if (StringUtils.isNotBlank(startTime) && isValidDate(startTime)) {
if (!isEndTimeAfterStartTime(startTime, endTime)) {
errorMsg.append(StringUtils.isNotBlank(errorMsg.toString()) ? " " : "")
.append("结束时间不能早于开始时间!");
}
}
}
return errorMsg.length() > 0 ? errorMsg.toString() : null;
}
/**
* 校验单个日期字符串是否符合 yyyy-MM-dd 格式
* @param dateStr 日期字符串
* @return 合法返回true否则false
*/
public static boolean isValidDate(String dateStr) {
if (StringUtils.isBlank(dateStr)) {
return false;
}
try {
LocalDate.parse(dateStr); // 默认解析 yyyy-MM-dd 格式
return true;
} catch (DateTimeParseException e) {
return false;
}
}
/**
* 校验结束时间是否晚于开始时间(前提:两个时间格式都合法)
* @param startTime 开始时间
* @param endTime 结束时间
* @return 结束时间晚于开始时间返回true否则false
*/
public static boolean isEndTimeAfterStartTime(String startTime, String endTime) {
try {
LocalDate startDate = LocalDate.parse(startTime);
LocalDate endDate = LocalDate.parse(endTime);
return !endDate.isBefore(startDate); // 结束时间 >= 开始时间 返回true
} catch (DateTimeParseException e) {
return false;
}
}
}

View File

@@ -0,0 +1,17 @@
package com.ruoyi.cms.util;
import javax.servlet.http.HttpServletRequest;
public class ProxyServerUtil {
private static final String PROXY_HEADER = "X-Proxy-Server";
/**
* 从请求头中获取代理服务器标识B或C
* @param request HttpServletRequest
* @return 代理标识(如"proxy-b"、"proxy-c"无则返回null
*/
public static String getProxyServer(HttpServletRequest request) {
// 从请求头中获取自定义标识
return request.getHeader(PROXY_HEADER);
}
}

View File

@@ -1,5 +1,8 @@
package com.ruoyi.cms.util;
import com.ruoyi.common.utils.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -11,11 +14,24 @@ public class StringUtil {
/*1101(求职者)、1102(招聘者)、1103(网格员)、1104(内部工作者)*/
public static final Long COMPANY_ADMIN_ROLE_KEY = 1102L;
/************************移动端角色开始***************************/
/**
* 企业用户
* 移动端-企业用户
*/
public static final String IS_COMPANY_USER = "0";
/**
* 移动端-求职者
*/
public static final String IS_JOB_REQUEST_USER = "1";
/**
* 移动端-网格员
*/
public static final String IS_GRID_USER = "2";
/**
* 移动端-内部工作者
*/
public static final String IS_INTERNAL_USER = "3";
/************************移动端角色结束***************************/
/**
* pc端-求职者
*/
@@ -33,7 +49,7 @@ public class StringUtil {
/**
* 岗位互联网
*/
public static final String BASE_WW_GW="http://http://222.80.110.161:11111/kashi/job-portal/detail/:";
public static final String BASE_WW_GW="https://www.xjksly.cn/kashi/job-portal/detail/";
/**
*录用
@@ -48,6 +64,26 @@ public class StringUtil {
*录用-招聘会
*/
public static final String HIRE_SOURCE_ZPH="1";
/**
* 标记
*/
public static final boolean PATH_TEST_ENV = true;
/**
* 测试环境附件地址
*/
public static final String PATH_DEV = "http://ks.zhaopinzao8dian.com/file/";
/**
* 正式环境环境地址
*/
public static final String PATH_PROXY_37 = "http://10.98.80.37/file/";
/**
* 互联网
*/
public static final String PATH_PROXY_50="https://www.xjksly.cn/file/";
/**
* 经办端
*/
public static final String PATH_PROXY_146="http://10.98.80.146/file/";
/**
* 身份证规则
@@ -104,15 +140,93 @@ public class StringUtil {
.collect(Collectors.toList()); // 收集为List
}
/**
* 获取附件地址
* @return
*/
public static String getFilePath(HttpServletRequest request){
String proxyServer = getProxyServer(request);
if ("proxy-50".equals(proxyServer)) {
return PATH_PROXY_50;
} else if ("proxy-146".equals(proxyServer)) {
return PATH_PROXY_146;
}
return !PATH_TEST_ENV ? PATH_PROXY_37 : PATH_DEV;
}
/**
* 原方法-不传request
* @return
*/
public static String getFilePath(){
return !PATH_TEST_ENV ? PATH_PROXY_37 : PATH_DEV;
}
/**
* 获取nginx地址
* @param request
* @return
*/
private static String getProxyServer(HttpServletRequest request) {
if (request == null) {
return null;
}
return request.getHeader("X-Proxy-Server");
}
/**
* 手机号脱敏
* @param phone
* @return
*/
public static String desensitizePhone(String phone) {
if (StringUtils.isEmpty(phone) || phone.length() != 11) {
return phone;
}
return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
/**
* 脱敏逻辑前4位 + ***+ 后4位
* @param idCard
* @return
*/
public static String desensitizeIdCard(String idCard) {
if (idCard == null || idCard.length() != 18) {
return idCard; // 非标准身份证号不脱敏(或按规则处理)
if (StringUtils.isEmpty(idCard)) {
return null;
}
return idCard.substring(0, 4) + "***" + idCard.substring(14);
// 处理18位身份证支持末尾X/x
if (idCard.matches("\\d{17}[\\dXx]")) {
return idCard.replaceAll("(\\d{6})\\d{8}([\\dXx]{4})", "$1********$2");
}
// 处理15位身份证
else if (idCard.matches("\\d{15}")) {
return idCard.replaceAll("(\\d{6})\\d{6}(\\d{3})", "$1******$2");
}
// 非标准格式如16位、含特殊字符返回部分脱敏前4位+****+后2位避免明文暴露
else {
int len = idCard.length();
if (len >= 6) {
return idCard.substring(0, 4) + "****" + idCard.substring(len - 2);
}
// 长度过短(<6位直接返回***,避免明文
return "***";
}
}
/**
* 转大写
* @param str
* @return
*/
public static String toUpperCaseIgnoreBlank(String str) {
if (str == null) {
return null;
}
String trimmedStr = str.trim();
if (trimmedStr.isEmpty()) {
return str;
}
return str.toUpperCase();
}
}

View File

@@ -249,7 +249,7 @@ public class WechatUtil {
try {
System.out.println("appid==============="+appid);
System.out.println("secret================"+secret);
String response = getAccessData("https://api.weixin.qq.com/sns/jscode2session?appid="+appid+"&secret="+secret+"&js_code="+code+"&grant_type=authorization_code");
String response = getAccessData(AliyunNlsUtils.getWXoppenidUrl()+"?appid="+appid+"&secret="+secret+"&js_code="+code+"&grant_type=authorization_code");
JSONObject result = JSONObject.parseObject(response);
// 微信返回错误码处理
if (result.containsKey("errcode") && result.getInteger("errcode") != 0) {

View File

@@ -0,0 +1,11 @@
<?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.ruoyi.cms.mapper.AiChatDetailMapper">
<select id="getList" parameterType="com.ruoyi.cms.domain.ai.AiChatDetail" resultType="com.ruoyi.cms.domain.ai.AiChatDetail">
select * from ai_chat_detail where chat_id=#{chatId} order by time
</select>
</mapper>

View File

@@ -0,0 +1,26 @@
<?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.ruoyi.cms.mapper.AiChatHistoryMapper">
<select id="getList" parameterType="com.ruoyi.cms.domain.ai.AiChatHistory" resultType="com.ruoyi.cms.domain.ai.AiChatHistory">
select * from ai_chat_history where del_flag='0'
<if test="userId != null and userId != ''">
and user_id = #{userId}
</if>
<if test="chatId != null and chatId != ''">
and chat_id = #{chatId}
</if>
<if test="appId != null and appId != ''">
and app_id = #{appId}
</if>
<if test="title != null and title != ''">
and title like CONCAT('%',#{title},'%')
</if>
<if test="updateTime != null">
and to_char(update_time,'yyyy-mm-dd') = to_char(#{updateTime},'yyyy-mm-dd')
</if>
</select>
</mapper>

View File

@@ -31,10 +31,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="idCard" column="id_card" />
<result property="workExperience" column="work_experience" />
<result property="isCompanyUser" column="is_company_user" />
<result property="dwUserid" column="dw_userid" />
</resultMap>
<sql id="selectAppUserVo">
select user_id, name, age, sex, birth_date, education, political_affiliation, phone, avatar, salary_min, salary_max, area, status, del_flag, login_ip, login_date, create_by, create_time, update_by, update_time, remark,job_title_id,is_recommend,id_card,work_experience,is_company_user from app_user
select user_id, name, age, sex, birth_date, education, political_affiliation, phone, avatar, salary_min, salary_max, area, status, del_flag, login_ip, login_date, create_by, create_time, update_by, update_time, remark,job_title_id,is_recommend,id_card,work_experience,is_company_user,dw_userid from app_user
</sql>
<select id="selectAppUserList" parameterType="AppUser" resultMap="AppUserResult">
@@ -67,7 +68,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<select id="selectByOpenid" resultType="com.ruoyi.common.core.domain.entity.AppUser">
<include refid="selectAppUserVo"/> WHERE DEL_FLAG = '0' and openid=#{openid} LIMIT 1
<include refid="selectAppUserVo"/> WHERE DEL_FLAG = '0' and openid=#{openid} and is_company_user=#{userType} LIMIT 1
</select>
<insert id="insertSysUserRole" parameterType="java.util.Map">
@@ -109,10 +110,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</insert>
<select id="getMyTj" resultType="com.ruoyi.common.core.domain.entity.MyChart">
SELECT t1.yzj,t2.ysc,t3.ytd,0 AS yyy FROM
SELECT t1.yzj,t2.ysc,t3.ytd,0 AS yyy,t4.yqx FROM
(SELECT COUNT(user_id) AS yzj FROM app_review_job WHERE user_id = #{userId} AND del_flag = '0') t1
CROSS JOIN (SELECT COUNT(user_id) AS ysc FROM job_collection WHERE user_id = #{userId} AND del_flag = '0') t2
CROSS JOIN (SELECT COUNT(user_id) AS ytd FROM job_apply WHERE user_id = #{userId} AND del_flag = '0') t3
CROSS JOIN (SELECT COUNT(user_id) AS yqx FROM job_apply WHERE user_id = #{userId} AND del_flag = '2') t4
</select>
<select id="selectSysUserIdcard" resultType="com.ruoyi.common.core.domain.entity.SysUser">

View File

@@ -106,7 +106,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<select id="selectCompanyByJobId" resultType="com.ruoyi.common.core.domain.entity.Company">
select a.* from COMPANY a inner join job b on a.company_id=b.company_id and b.job_id=#{jobId} limit 1
select a.* from COMPANY a inner join job b on a.company_id=b.company_id and a.del_flag = '0' and b.del_flag = '0' and b.job_id=#{jobId} limit 1
</select>
<select id="selectByNames" resultType="com.ruoyi.common.core.domain.entity.Company" parameterType="java.util.List">
SELECT company_id, name FROM company
WHERE del_flag = '0' and name IN
<foreach collection="list" item="name" open="(" separator="," close=")">
#{name}
</foreach>
</select>
</mapper>

View File

@@ -39,7 +39,42 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where
<if test="longs != null and longs.size() > 0">
bussinessid in (
<foreach collection="longs" item="oldId" open="(" close=")" separator=",">
<foreach collection="longs" item="oldId" separator=",">
#{oldId}
</foreach>
)
</if>
<if test="longs == null or longs.size() == 0">
1 = 2
</if>
</update>
<select id="selectFileListByBussinessIds" resultMap="FileResult">
<include refid="selectFileVo"/>
<where> del_flag = '0'
<if test="longs != null and longs.size() > 0">
and bussinessid in (
<foreach collection="longs" item="oldId" separator=",">
#{oldId}
</foreach>
)
</if>
</where>
</select>
<update id="updateIds">
update file
<if test="newBussinessid != null">
set bussinessid = #{newBussinessid}
</if>
<if test="newBussinessid == null">
set bussinessid = bussinessid
where 1 = 2
</if>
where
<if test="longs != null and longs.size() > 0">
id in (
<foreach collection="longs" item="oldId" separator=",">
#{oldId}
</foreach>
)

View File

@@ -130,4 +130,20 @@
update job_apply set update_time=sysdate(),update_by=#{updateBy},hire=#{hire},hire_source=#{hireSource} where user_id=#{userId} AND job_id=#{jobId}
</update>
<update id="applyJobCencal" parameterType="JobApply">
update job_apply set del_flag='2',update_time=sysdate(),update_by=#{updateBy} where user_id=#{userId} and job_id=#{jobId}
</update>
<select id="selectCencalList" parameterType="JobApply" resultType="com.ruoyi.cms.domain.Job">
select b.*,a.create_time as shareTime from job_apply a inner join job b on a.job_id=b.job_id and b.del_flag='0'
<where> a.del_flag = '2'
<if test="jobId != null "> and a.job_id = #{jobId}</if>
<if test="userId != null "> and a.user_id = #{userId}</if>
</where>
</select>
<select id="applyCencalCount" resultType="java.lang.Integer">
select count(user_id) from job_apply where del_flag = '2' and user_id=#{userId}
</select>
</mapper>

View File

@@ -55,4 +55,22 @@
</foreach>
</insert>
<select id="selectByJobIds" parameterType="java.util.List" resultMap="jobContactResult">
<include refid="JobContactVo"/>
<where> del_flag = '0'
<if test="jobIds != null and jobIds.size() > 0">
AND job_id IN (
<foreach collection="jobIds" item="jobId" separator=",">
<if test="jobId != null">
#{jobId}
</if>
</foreach>
)
</if>
<if test="jobIds == null or jobIds.size() == 0">
AND 1 = 0
</if>
</where>
</select>
</mapper>

View File

@@ -34,6 +34,9 @@
<result property="explainUrl" column="explain_url" />
<result property="cover" column="cover" />
<result property="jobType" column="job_type" />
<result property="jobAddress" column="job_address" />
<result property="regionCode" column="region_code" />
<result property="staffType" column="staff_type" />
</resultMap>
@@ -77,6 +80,9 @@
<result property="scale" column="scale" />
<result property="companyNature" column="company_nature" />
<result property="code" column="code" />
<result property="jobAddress" column="job_address" />
<result property="regionCode" column="region_code" />
<result property="staffType" column="staff_type" />
<association property="companyVo" resultMap="CompanyResult"/>
</resultMap>
@@ -95,7 +101,7 @@
</resultMap>
<sql id="selectJobVo">
select job_id, job_title, min_salary, max_salary, education, experience, company_name, job_location, posting_date, vacancies, del_flag, create_by, create_time, update_by, update_time, remark, latitude, longitude, "view", company_id , is_hot ,apply_num,is_publish, description,job_location_area_code,data_source,job_url,job_category,is_explain,explain_url,cover,job_type from job
select job_id, job_title, min_salary, max_salary, education, experience, company_name, job_location, posting_date, vacancies, del_flag, create_by, create_time, update_by, update_time, remark, latitude, longitude, "view", company_id , is_hot ,apply_num,is_publish, description,job_location_area_code,data_source,job_url,job_category,is_explain,explain_url,cover,job_type,job_address,region_code,staff_type from job
</sql>
<insert id="insertBatchRowWork">
INSERT INTO row_work (
@@ -125,7 +131,7 @@
job_title, min_salary, max_salary, education, experience, company_name, job_location,
job_location_area_code, posting_date, vacancies, latitude, longitude, "view", company_id,
is_hot, apply_num, description, is_publish, data_source, job_url, remark, del_flag,
create_by, create_time, row_id, job_category,jobType
create_by, create_time, row_id, job_category,job_type,type,job_address,region_code,staff_type
) VALUES
<foreach collection="list" item="job" separator=",">
(
@@ -134,7 +140,8 @@
#{job.vacancies}, #{job.latitude}, #{job.longitude}, #{job.view}, #{job.companyId},
#{job.isHot}, #{job.applyNum}, #{job.description}, #{job.isPublish}, #{job.dataSource},
#{job.jobUrl}, #{job.remark}, #{job.delFlag}, #{job.createBy}, #{job.createTime},
#{job.rowId}, #{job.jobCategory},#{job.jobType}
#{job.rowId}, #{job.jobCategory},#{job.jobType},#{job.type},#{job.jobAddress},
#{job.regionCode},#{job.staffType}
)
</foreach>
</insert>
@@ -275,7 +282,8 @@
<select id="selectAllJob" resultMap="JobEsResult">
SELECT j.*,c.industry,c.scale,c.nature as company_nature,c.code,c.description as company_description,c.name,c.company_id,t.contact_person,t.contact_person_phone FROM job as j
left join company as c on c.company_id = j.company_id and j.del_flag='0' and c.del_flag='0'
left join company_contact as t on t.company_id=j.company_id and t.del_flag='0' limit #{offset},#{batchSize}
LEFT JOIN (SELECT t1.*, ROW_NUMBER() OVER (PARTITION BY t1.company_id ORDER BY t1.id) AS rn FROM company_contact AS t1 WHERE t1.del_flag = '0' ) AS t
ON t.company_id = j.company_id AND t.rn = 1 WHERE j.del_flag = '0' limit #{offset},#{batchSize}
</select>
<select id="selectAllInsertRowWork" resultType="com.ruoyi.cms.domain.RowWork">
select Id, TaskId, TaskName, Std_class, SF, ZCMC, Aca112, Acb22a, Aac011, Acb240,
@@ -292,7 +300,7 @@
</select>
<select id="selectVectorJob" resultType="com.ruoyi.cms.domain.VectorJob">
SELECT
ANY_VALUE(job_id) as job_id,
j.job_id,
j.job_title,
ed.dict_label as education,
ex.dict_label as experience,
@@ -302,15 +310,15 @@
ar.dict_label as area,
ab.dict_label as nature,
ac.dict_label as scale,
concat(j.min_salary,"元-",j.max_salary,"元") as salary
concat(j.min_salary,'元-',j.max_salary,'元') as salary
FROM
job as j
inner join company as c on c.company_id = j.company_id
inner join qd.bussiness_dict_data as ed on ed.dict_type = 'education' and ed.dict_value = j.education
inner join qd.bussiness_dict_data as ex on ex.dict_type = 'experience' and ex.dict_value = j.experience
left join qd.bussiness_dict_data as ar on ar.dict_type = 'area' and ar.dict_value = j.job_location_area_code
left join qd.bussiness_dict_data as ab on ab.dict_type = 'company_nature' and ab.dict_value = c.nature
left join qd.bussiness_dict_data as ac on ac.dict_type = 'scale' and ac.dict_value = c.scale
inner join bussiness_dict_data as ed on ed.dict_type = 'education' and ed.dict_value = j.education
inner join bussiness_dict_data as ex on ex.dict_type = 'experience' and ex.dict_value = j.experience
left join bussiness_dict_data as ar on ar.dict_type = 'area' and ar.dict_value = j.job_location_area_code
left join bussiness_dict_data as ab on ab.dict_type = 'company_nature' and ab.dict_value = c.nature
left join bussiness_dict_data as ac on ac.dict_type = 'scale' and ac.dict_value = c.scale
where job_id =#{jobId}
</select>
@@ -328,4 +336,42 @@
left join company as c on c.company_id = j.company_id and j.del_flag='0' and j.job_id=#{jobId}
and c.del_flag='0' limit 1
</select>
<select id="getTotals" parameterType="Job" resultType="java.lang.Integer">
select count(job_id) as total_count from job
<where> del_flag = '0'
<if test="companyName != null and companyName != ''"> and company_name =#{companyName}</if>
<if test="dataSource != null and dataSource!='' "> and data_source = #{dataSource}</if>
<if test="description != null and description!='' "> and description = #{description}</if>
<if test="education != null and education != ''"> and education = #{education}</if>
<if test="experience != null and experience != ''"> and experience = #{experience}</if>
<if test="isPublish != null "> and is_publish = #{isPublish}</if>
<if test="jobLocation != null and jobLocation != ''"> and job_location = #{jobLocation}</if>
<if test="jobTitle != null and jobTitle != ''"> and job_title = #{jobTitle}</if>
<if test="minSalary != null "> and min_salary = #{minSalary}</if>
<if test="maxSalary != null "> and max_salary = #{maxSalary}</if>
<if test="vacancies != null "> and vacancies = #{vacancies}</if>
</where>
</select>
<insert id="updateFileBatchInsert" useGeneratedKeys="true" keyProperty="jobId" parameterType="java.util.List">
INSERT INTO job (
job_title, min_salary, max_salary, education, experience, company_name, job_location,
job_location_area_code, posting_date, vacancies, latitude, longitude, "view", company_id,
is_hot, apply_num, description, is_publish, data_source, job_url, remark, del_flag,
create_by, create_time, row_id, job_category,job_type,type,job_address,region_code,staff_type
) VALUES
<foreach collection="list" item="job" separator=",">
(
#{job.jobTitle}, #{job.minSalary}, #{job.maxSalary}, #{job.education}, #{job.experience},
#{job.companyName}, #{job.jobLocation}, #{job.jobLocationAreaCode}, #{job.postingDate},
#{job.vacancies}, #{job.latitude}, #{job.longitude}, #{job.view}, #{job.companyId},
#{job.isHot}, #{job.applyNum}, #{job.description}, #{job.isPublish}, #{job.dataSource},
#{job.jobUrl}, #{job.remark}, #{job.delFlag}, #{job.createBy}, #{job.createTime},
#{job.rowId}, #{job.jobCategory},#{job.jobType},#{job.type},#{job.jobAddress},
#{job.regionCode},#{job.staffType}
)
</foreach>
</insert>
</mapper>

View File

@@ -0,0 +1,60 @@
<?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.ruoyi.cms.mapper.StaticsMapper">
<select id="qygwtjCount" parameterType="com.ruoyi.cms.domain.query.Staticsquery" resultType="com.ruoyi.cms.domain.StaticsJob">
WITH time_params AS (
SELECT
CAST(#{startTime} AS timestamp) AS start_time,
CAST(#{endTime} AS timestamp) AS end_time
),
job_stats AS (
SELECT COUNT(*) AS 归集岗位合计 FROM job, time_params tp
WHERE posting_date &gt;= tp.start_time AND posting_date &lt; tp.end_time
),
company_stats AS (
SELECT COUNT(company_id) AS 注册企业数 FROM company, time_params tp
WHERE del_flag='0' AND create_time &gt;= tp.start_time AND create_time &lt; tp.end_time
),
user_stats AS (
SELECT COUNT(*) AS 求职者实名数 FROM app_user, time_params tp
WHERE del_flag='0' AND is_company_user='1' AND id_card IS NOT NULL
AND create_time &gt;= tp.start_time AND create_time &lt; tp.end_time
),
apply_stats AS (
SELECT COUNT(id) AS 简历投递数量 FROM job_apply, time_params tp
WHERE del_flag='0' AND create_time &gt;= tp.start_time AND create_time &lt; tp.end_time
)
SELECT
js.归集岗位合计 gjgwhj, cs.注册企业数 zcqys,
us.求职者实名数 qzzsms, as2.简历投递数量 jlsl
FROM job_stats js, company_stats cs, user_stats us, apply_stats as2;
</select>
<select id="getGroutCityJobs" parameterType="com.ruoyi.cms.domain.query.Staticsquery" resultType="com.ruoyi.cms.domain.StaticsJob">
WITH job_stats AS (
SELECT job_location_area_code, COUNT(job_id) AS zs,
SUM(CASE WHEN education >= 3 THEN 1 ELSE 0 END) AS gxbysgw,
SUM(CASE WHEN del_flag = '0' THEN 1 ELSE 0 END) AS sszzgw
FROM job WHERE job_location_area_code IS NOT NULL
<if test="startTime!=null and startTime!='' ">
<![CDATA[ AND posting_date >= CAST(#{startTime} AS timestamp) ]]>
</if>
<if test="endTime!=null and endTime!='' ">
<![CDATA[ AND posting_date <= CAST(#{endTime} AS timestamp) ]]>
</if>
GROUP BY job_location_area_code
),apply_stats AS (
SELECT j.job_location_area_code, COUNT(ja.id) AS jlsl
FROM job j INNER JOIN job_apply ja ON j.job_id = ja.job_id
WHERE j.job_location_area_code IS NOT NULL
GROUP BY j.job_location_area_code )
SELECT js.zs, js.gxbysgw, js.sszzgw, COALESCE(as2.jlsl, 0) AS jlsl,
bdd.dict_label as label FROM job_stats js
LEFT JOIN bussiness_dict_data bdd ON js.job_location_area_code = bdd.dict_value AND bdd.dict_type = 'area'
LEFT JOIN apply_stats as2 ON js.job_location_area_code = as2.job_location_area_code
ORDER BY bdd.dict_sort
</select>
</mapper>

View File

@@ -24,4 +24,13 @@
order by code
</select>
<select id="getCityList" resultType="com.ruoyi.common.core.domain.entity.SysArea" parameterType="com.ruoyi.common.core.domain.entity.SysArea">
WITH top_area AS (
SELECT code, name, (name LIKE '%市%') AS is_municipality FROM sys_area WHERE del_flag='0' and parent_code IS NULL
)
SELECT sa.*,get_name_first_pinyin(sa.name) AS zm FROM sys_area sa INNER JOIN top_area ta ON sa.code = ta.code WHERE ta.is_municipality = TRUE
UNION ALL
SELECT sa.*,get_name_first_pinyin(sa.name) AS zm FROM sys_area sa INNER JOIN top_area ta ON sa.parent_code = ta.code WHERE ta.is_municipality = FALSE
</select>
</mapper>

View File

@@ -0,0 +1,18 @@
<?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.ruoyi.cms.mapper.WechatGroupMapper">
<select id="selectWechatGroupList" resultType="com.ruoyi.cms.domain.vo.WechatGroupVo">
select g.create_time, g.id, g.name, g.is_push, u.wechat_name, u.phone_number, u.id communityId
from wechat_group g join community_user u on g.community_id = u.id
where g.del_flag = 0 and u.del_flag = 0
<if test="p.name != null and p.name != ''">
and g.name like '%' || #{p.name} || '%'
</if>
<if test="p.isPush != null">
and g.is_push = #{p.isPush}
</if>
</select>
</mapper>

View File

@@ -120,6 +120,22 @@
<artifactId>UserAgentUtils</artifactId>
</dependency>
<!--奇安信密码机-->
<!--<dependency>
<groupId>org.quickssl</groupId>
<artifactId>quickapi-client-java</artifactId>
<version>1.5.11-SNAPSHOT</version>
<classifier>shaded</classifier>
<scope>compile</scope>
</dependency>-->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.59</version>
<scope>compile</scope>
</dependency>
<!-- servlet包 -->
<dependency>
<groupId>javax.servlet</groupId>
@@ -142,6 +158,12 @@
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.22</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,67 @@
package com.ruoyi.common.config;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@Component
public class AudioTextRequestClient {
@Value("${audioText.asr}")
private String asr;
// 1. 读取 TTS 服务地址
@Value("${audioText.tts}")
private String tts;
private final RestTemplate restTemplate = new RestTemplate();
public String getTextFromAudioFile(MultipartFile file) throws IOException {
HttpRequest request = HttpRequest.post(asr);
request.header(HttpHeaders.CONTENT_TYPE,MediaType.MULTIPART_FORM_DATA_VALUE);
request.form("file",file.getBytes(),file.getOriginalFilename());
HttpResponse response = request.execute();
if(response.isOk()){
String body = response.body();
JSONObject jsonObject = JSONObject.parseObject(body);
if("0".equals(jsonObject.getString("code")) || "200".equals(jsonObject.getString("code"))){
return jsonObject.getString("text");
}else{
throw new RuntimeException("语音转文字接口调用失败: " + jsonObject.getString("error"));
}
}else{
throw new RuntimeException("语音转文字接口调用失败: " + response.body());
}
}
public byte[] getAudioInputStreamFromText(String audioText) throws UnsupportedEncodingException {
JSONObject object = new JSONObject();
object.put("text", audioText);
object.put("speaker_id", 0);
object.put("length_scale", 1.0);
object.put("noise_scale", 0.667);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(object.toJSONString(), headers);
ResponseEntity<byte[]> response = restTemplate.postForEntity(tts, entity, byte[].class);
if (!response.getStatusCode().is2xxSuccessful()){
String body = new String(response.getBody(),"UTF-8");
JSONObject jsonObject = JSONObject.parseObject(body);
throw new RuntimeException("文字转语音接口调用失败: " + jsonObject.getString("error"));
}else{
return response.getBody();
}
}
}

View File

@@ -0,0 +1,36 @@
package com.ruoyi.common.constant;
import java.util.Arrays;
import java.util.List;
/**
* 加解密配置常量
*/
public class EncryptConstants {
/**
* 是否开启加解密功能
*/
public static final boolean ENCRYPT_ENABLED = true;
/**
* 需要加解密的URL路径模式
*/
public static final List<String> URL_PATTERNS = Arrays.asList(
/*"/app/login",
"/app/user/resume",
"/app/user/experience/edit",
"/app/user/experience/delete",
"/app/user/experience/getSingle/*",
"/app/user/experience/list",
"/login",
"/system/user/resetPwd",
"/system/user/list",
"/system/user",
"/cms/appUser/list",
"/cms/appUser/getResumeList",
"/cms/appUser/getResumeDetail/*",
"/app/alipay/scanLogin",
"/app/user/cert"*/
);
}

View File

@@ -0,0 +1,6 @@
package com.ruoyi.common.constant;
public class SM4Constants {
public static final String SM4_KET = "86C63180C1306ABC4D8F989E0A0BC9F3";
}

View File

@@ -1,6 +1,5 @@
package com.ruoyi.common.core.domain.entity;
import java.time.LocalDate;
import java.util.Date;
import java.util.List;
@@ -111,7 +110,7 @@ public class AppUser extends BaseEntity
/**
* 是否企业用户 0是1否改为0企业1求职者2网格员
*/
@ApiModelProperty("app角色0企业1求职者2网格员 3内部政府人员")
@ApiModelProperty("app角色0企业1求职者2网格员 3内部政府人员 4其他浪潮用")
private String isCompanyUser;
@TableField(exist = false)
@@ -135,6 +134,9 @@ public class AppUser extends BaseEntity
@ApiModelProperty("工作经验")
private String workExperience;
@ApiModelProperty("机构类型 0用人单位 1培训机构 2评价机构 3人力资源机构")
private String orgType;
@TableField(exist = false)
@ApiModelProperty("公司信息")
private Company company;
@@ -162,4 +164,7 @@ public class AppUser extends BaseEntity
@ApiModelProperty("户籍地址")
private String domicileAddress;
@ApiModelProperty("地纬userid")
private String dwUserid;
}

View File

@@ -4,8 +4,9 @@ import lombok.Data;
@Data
public class MyChart {
private String ytd;
private String ysc;
private String yzj;
private String yyy;
private String ytd;//投递
private String ysc;//收藏
private String yzj;//足迹
private String yyy;//已预约
private String yqx;//已取消
}

View File

@@ -43,4 +43,7 @@ public class SysArea extends BaseEntity {
@ApiModelProperty("父级地区编码顶级为NULL")
private String parentCode;
@TableField(exist = false)
@ApiModelProperty("字母")
private String zm;
}

View File

@@ -44,6 +44,11 @@ public class LoginBody
*/
private String idCard;
/**
* 企业类型
*/
public String orgType;
public String getUsername()
{
return username;
@@ -115,4 +120,12 @@ public class LoginBody
public void setIdCard(String idCard) {
this.idCard = idCard;
}
public String getOrgType() {
return orgType;
}
public void setOrgType(String orgType) {
this.orgType = orgType;
}
}

View File

@@ -0,0 +1,251 @@
package com.ruoyi.common.core.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/**
* JDK 1.8 兼容版分布式锁工具类(基于 Redis
*/
@Component
public class DistributedLockUtil {
@Autowired
private RedisCache redisCache;
// 锁默认配置
private static final long DEFAULT_EXPIRE_SECONDS = 30;
private static final long DEFAULT_ACQUIRE_TIMEOUT_SECONDS = 5;
private static final long RENEW_INTERVAL_SECONDS = DEFAULT_EXPIRE_SECONDS / 3;
// 续期线程池
private final ScheduledExecutorService renewExecutor = Executors.newScheduledThreadPool(
Runtime.getRuntime().availableProcessors(),
new ThreadFactory() {
private final AtomicBoolean init = new AtomicBoolean(false);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "distributed-lock-renewer");
thread.setDaemon(true);
if (init.compareAndSet(false, true)) {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
renewExecutor.shutdown();
}
}));
}
return thread;
}
}
);
// 释放锁 Lua 脚本(原子操作)
private static final String RELEASE_LOCK_LUA_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
private final DefaultRedisScript<Long> releaseScript = new DefaultRedisScript<>();
// 初始化 Lua 脚本
{
releaseScript.setScriptText(RELEASE_LOCK_LUA_SCRIPT);
releaseScript.setResultType(Long.class);
}
/**
* 获取锁(带自动续期)
*/
public String acquireLockWithRenewal(String lockKey) {
return acquireLockWithRenewal(lockKey, DEFAULT_EXPIRE_SECONDS, DEFAULT_ACQUIRE_TIMEOUT_SECONDS);
}
/**
* 自定义参数获取锁
*/
public String acquireLockWithRenewal(String lockKey, long expireSeconds, long acquireTimeoutSeconds) {
String identifier = UUID.randomUUID().toString();
long endTime = System.currentTimeMillis() + acquireTimeoutSeconds * 1000;
while (System.currentTimeMillis() < endTime) {
boolean locked = tryLockOnce(lockKey, identifier, expireSeconds);
if (locked) {
startRenewal(lockKey, identifier, expireSeconds);
return identifier;
}
// 指数退避重试
try {
long sleepMs = calculateBackoffSleep(System.currentTimeMillis() - (endTime - acquireTimeoutSeconds * 1000));
Thread.sleep(sleepMs);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
return null;
}
/**
* 原子释放锁
*/
public boolean releaseLockSafely(String lockKey, String identifier) {
if (identifier == null) {
return false;
}
Long result = (Long) redisCache.redisTemplate.execute(
releaseScript,
Collections.singletonList(lockKey),
identifier
);
return result != null && result > 0;
}
/**
* 尝试获取一次锁
*/
private boolean tryLockOnce(String lockKey, String identifier, long expireSeconds) {
try {
if (!redisCache.hasKey(lockKey)) {
redisCache.setCacheObject(lockKey, identifier, (int) expireSeconds, TimeUnit.SECONDS);
String storedId = redisCache.getCacheObject(lockKey);
return identifier.equals(storedId);
}
return false;
} catch (Exception e) {
return false;
}
}
/**
* 启动锁自动续期
*/
private void startRenewal(final String lockKey, final String identifier, final long expireSeconds) {
final AtomicReference<ScheduledFuture<?>> futureRef = new AtomicReference<>();
Runnable renewalTask = new Runnable() {
@Override
public void run() {
try {
String storedId = redisCache.getCacheObject(lockKey);
if (identifier.equals(storedId)) {
redisCache.expire(lockKey, expireSeconds);
} else {
cancelFuture();
}
} catch (Exception e) {
cancelFuture();
}
}
private void cancelFuture() {
ScheduledFuture<?> future = futureRef.get();
if (future != null && !future.isCancelled()) {
future.cancel(false);
}
}
};
ScheduledFuture<?> future = renewExecutor.scheduleAtFixedRate(
renewalTask,
RENEW_INTERVAL_SECONDS,
RENEW_INTERVAL_SECONDS,
TimeUnit.SECONDS
);
futureRef.set(future);
}
/**
* 指数退避算法(计算重试间隔)
*/
private long calculateBackoffSleep(long elapsedMs) {
int retryCount = (int) (elapsedMs / 100);
long sleepMs = 100L * (1 << Math.min(retryCount, 10));
return Math.min(sleepMs, 1000);
}
/**
* 自动释放锁工具(支持 try-with-resources
*/
public static class AutoReleaseLock implements AutoCloseable {
private final DistributedLockUtil lockUtil;
private final String lockKey;
private final String identifier;
public AutoReleaseLock(DistributedLockUtil lockUtil, String lockKey, String identifier) {
this.lockUtil = lockUtil;
this.lockKey = lockKey;
this.identifier = identifier;
}
public boolean isLocked() {
return identifier != null;
}
@Override
public void close() {
if (isLocked()) {
lockUtil.releaseLockSafely(lockKey, identifier);
}
}
}
/**
* 简化锁使用
*/
public AutoReleaseLock tryLock(String lockKey) {
String identifier = acquireLockWithRenewal(lockKey);
return new AutoReleaseLock(this, lockKey, identifier);
}
/**
* 锁+超时时间
* @param lockKey
* @param timeout
* @param unit
* @return
*/
public AutoReleaseLock tryLock(String lockKey, long timeout, TimeUnit unit) {
// 1. 校验参数合法性(防御性编程)
if (lockKey == null || lockKey.trim().isEmpty()) {
throw new IllegalArgumentException("锁key不能为空");
}
if (timeout < 0) {
throw new IllegalArgumentException("超时时间不能为负数");
}
if (unit == null) {
throw new IllegalArgumentException("时间单位不能为空");
}
// 2. 带超时获取锁(核心逻辑,需实现 acquireLockWithTimeout 方法)
String identifier = acquireLockWithTimeout(lockKey, timeout, unit);
// 3. 返回自动释放锁(复用原有 AutoReleaseLock无需修改
return new AutoReleaseLock(this, lockKey, identifier);
}
/**
* 带超时时间获取锁(支持自定义超时单位)
* @param lockKey 锁键
* @param timeout 获取锁的超时时间
* @param unit 时间单位
* @return 锁标识获取失败返回null
*/
public String acquireLockWithTimeout(String lockKey, long timeout, TimeUnit unit) {
// 转换超时时间为秒(向上取整避免精度丢失)
long acquireTimeoutSeconds = (long) Math.ceil((double) unit.toMillis(timeout) / 1000);
// 使用默认的锁过期时间30秒也可根据需要改为让调用方传入
return acquireLockWithRenewal(lockKey, DEFAULT_EXPIRE_SECONDS, acquireTimeoutSeconds);
}
}

View File

@@ -56,4 +56,8 @@ public enum BusinessType
* 清空数据
*/
CLEAN,
/**
* 查询
*/
QUERY
}

View File

@@ -0,0 +1,123 @@
package com.ruoyi.common.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.constant.EncryptConstants;
import com.ruoyi.common.constant.SM4Constants;
import com.ruoyi.common.utils.EncryptHttpServletResponseWrapper;
import com.ruoyi.common.utils.SM4Utils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class EncryptResponseFilter implements Filter {
private static final ObjectMapper objectMapper = new ObjectMapper();
private final PathMatcher pathMatcher = new AntPathMatcher();
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 检查是否开启加解密功能
if (!EncryptConstants.ENCRYPT_ENABLED) {
chain.doFilter(request, response);
return;
}
// 检查是否是SSE流式响应 - 不处理
if (isSseResponse(httpRequest, httpResponse)) {
chain.doFilter(request, response);
return;
}
// 检查URL是否需要加密响应
if (!needProcess(httpRequest)) {
chain.doFilter(request, response);
return;
}
// 包装响应
EncryptHttpServletResponseWrapper responseWrapper = new EncryptHttpServletResponseWrapper(httpResponse);
chain.doFilter(request, responseWrapper);
// 获取原始响应内容
byte[] content = responseWrapper.getContent();
String originalResponse = new String(content, StandardCharsets.UTF_8);
// 加密响应内容
if (StringUtils.isNotBlank(originalResponse) && !originalResponse.trim().isEmpty()) {
String encryptedResponse = SM4Utils.encryptEcb(SM4Constants.SM4_KET, originalResponse);
// 构建加密响应
Map<String, Object> result = new HashMap<>();
result.put("encrypted", true);
result.put("encryptedData", encryptedResponse);
result.put("timestamp", System.currentTimeMillis());
String finalResponse = objectMapper.writeValueAsString(result);
// 设置响应
byte[] encryptedBytes = finalResponse.getBytes(StandardCharsets.UTF_8);
httpResponse.setContentLength(encryptedBytes.length);
httpResponse.setContentType("application/json;charset=UTF-8");
httpResponse.getOutputStream().write(encryptedBytes);
} else {
// 空响应直接返回空
httpResponse.getOutputStream().write(new byte[0]);
}
}
/**
* 判断请求是否需要处理
*/
private boolean needProcess(HttpServletRequest request) {
String requestURI = request.getRequestURI();
// 检查URL是否匹配需要加解密的模式
for (String pattern : EncryptConstants.URL_PATTERNS) {
if (pathMatcher.match(pattern, requestURI)) {
return true;
}
}
return false;
}
/**
* 判断是否是SSE流式响应
*/
private boolean isSseResponse(HttpServletRequest request, HttpServletResponse response) {
String path = request.getRequestURI();
String method = request.getMethod();
// 检查是否是聊天接口
if ("/app/chat/chat".equals(path) && "POST".equalsIgnoreCase(method)) {
return true;
}
// 检查Accept头是否包含text/event-stream
String acceptHeader = request.getHeader("Accept");
if (acceptHeader != null && acceptHeader.contains("text/event-stream")) {
return true;
}
// 检查响应头是否已经设置为text/event-stream
String contentType = response.getContentType();
if (contentType != null && contentType.contains("text/event-stream")) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,268 @@
package com.ruoyi.common.filter;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.constant.EncryptConstants;
import com.ruoyi.common.constant.SM4Constants;
import com.ruoyi.common.utils.EncryptHttpServletRequestWrapper;
import com.ruoyi.common.utils.EncryptHttpServletResponseWrapper;
import com.ruoyi.common.utils.SM4Utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.client.RestTemplate;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 请求包装过滤器 - 处理请求解密和包装
*/
@Component
@Slf4j
public class RequestWrapperFilter implements Filter {
private static final ObjectMapper objectMapper = new ObjectMapper();
private final PathMatcher pathMatcher = new AntPathMatcher();
private final RestTemplate restTemplate = new RestTemplate();
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 检查是否开启加解密功能
if (!EncryptConstants.ENCRYPT_ENABLED) {
chain.doFilter(request, response);
return;
}
// 检查是否是SSE流式响应 - 不处理
if (isSseResponse(httpRequest, httpResponse)) {
chain.doFilter(request, response);
return;
}
// 检查URL是否需要解密
if (needProcess(httpRequest)) {
// 处理请求解密
String method = httpRequest.getMethod();
if("POST".equalsIgnoreCase(method)) {
HttpServletRequest processedRequest = processBodyRequest(httpRequest);
chain.doFilter(processedRequest, response);
}else{
EncryptHttpServletResponseWrapper responseWrapper = new EncryptHttpServletResponseWrapper(httpResponse);
String forwardUrl = buildGetRequestURI(httpRequest);
if (StringUtils.isNotBlank(forwardUrl)) {
log.info("GET请求解密后转发URL{}", forwardUrl);
// 服务器内部转发
request.setAttribute("ENCRYPT_PROCESSED", Boolean.TRUE);
RequestDispatcher dispatcher = request.getRequestDispatcher(forwardUrl);
dispatcher.forward(request, responseWrapper);
// 2. 监听/处理转发后的响应(核心逻辑:从包装器中获取响应数据)
byte[] content = responseWrapper.getContent();
String originalResponse = new String(content, StandardCharsets.UTF_8);
if (StringUtils.isNotBlank(originalResponse) && !originalResponse.trim().isEmpty()) {
String encryptedResponse = SM4Utils.encryptEcb(SM4Constants.SM4_KET, originalResponse);
// 构建加密响应
Map<String, Object> result = new HashMap<>();
result.put("encrypted", true);
result.put("encryptedData", encryptedResponse);
result.put("timestamp", System.currentTimeMillis());
String finalResponse = objectMapper.writeValueAsString(result);
// 设置响应
byte[] encryptedBytes = finalResponse.getBytes(StandardCharsets.UTF_8);
httpResponse.setContentLength(encryptedBytes.length);
httpResponse.setContentType("application/json;charset=UTF-8");
httpResponse.getOutputStream().write(encryptedBytes);
} else {
// 空响应直接返回空
httpResponse.getOutputStream().write(new byte[0]);
}
return;
} else {
chain.doFilter(request, response);
}
}
} else {
// 不需要解密,直接放行
chain.doFilter(request, response);
}
}
/**
* 判断请求是否需要处理
*/
private boolean needProcess(HttpServletRequest request) {
if (Boolean.TRUE.equals(request.getAttribute("ENCRYPT_PROCESSED"))) {
return false;
}
String requestURI = request.getRequestURI();
// 检查URL是否匹配需要加解密的模式
for (String pattern : EncryptConstants.URL_PATTERNS) {
if (pathMatcher.match(pattern, requestURI)) {
return true;
}
}
return false;
}
/**
* 判断是否是SSE流式响应
*/
private boolean isSseResponse(HttpServletRequest request, HttpServletResponse response) {
String path = request.getRequestURI();
String method = request.getMethod();
// 检查是否是聊天接口
if ("/app/chat/chat".equals(path) && "POST".equalsIgnoreCase(method)) {
return true;
}
// 检查Accept头是否包含text/event-stream
String acceptHeader = request.getHeader("Accept");
if (acceptHeader != null && acceptHeader.contains("text/event-stream")) {
return true;
}
// 检查响应头是否已经设置为text/event-stream
String contentType = response.getContentType();
if (contentType != null && contentType.contains("text/event-stream")) {
return true;
}
return false;
}
private HttpServletRequest processBodyRequest(HttpServletRequest request) throws IOException {
String body = getRequestBody(request);
log.info("过滤器 - 原始请求体: " + body);
if (StringUtils.isNotBlank(body)) {
try {
// 解析请求体提取encryptedData字段
JsonNode jsonNode = objectMapper.readTree(body);
JsonNode encryptedDataNode = jsonNode.get("encryptedData");
if (encryptedDataNode != null && encryptedDataNode.isTextual()) {
String encryptedData = encryptedDataNode.asText();
// 解密 encryptedData
String decryptedData = SM4Utils.decryptEcb(SM4Constants.SM4_KET, encryptedData);
// 使用解密后的数据创建包装请求
return new EncryptHttpServletRequestWrapper(request, decryptedData);
} else {
log.info("请求体中未找到encryptedData字段直接返回原始请求");
return request;
}
} catch (Exception e) {
log.error("POST请求体解密失败: " + e.getMessage());
// 解密失败时返回原始请求,让业务层处理
return request;
}
}
return request;
}
private String getRequestBody(HttpServletRequest request) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
}
} finally {
if (bufferedReader != null) {
bufferedReader.close();
}
}
return stringBuilder.toString();
}
private String buildGetRequestURI(HttpServletRequest request) {
String baseUrl = request.getRequestURI(); // 完整基础URL
String encryptedData = request.getParameter("encryptedData");
if (StringUtils.isBlank(encryptedData)) {
log.info("GET请求未携带encryptedData参数返回原始URL");
return null; // 无加密参数,不处理
}
try {
// 解密参数增加异常捕获避免解密失败导致URL拼接异常
String decryptedData = SM4Utils.decryptEcb(SM4Constants.SM4_KET, encryptedData);
if (StringUtils.isBlank(decryptedData)) {
log.error("GET请求encryptedData解密后为空");
return null;
}
JSONObject object = JSONObject.parseObject(decryptedData);
if (object.isEmpty()) {
log.info("解密后的参数为空返回原始URL");
return null;
}
// 拼接参数用StringBuilder避免字符串频繁拼接
StringBuilder params = new StringBuilder();
for (Map.Entry<String, Object> entry : object.entrySet()) { // 用entrySet更高效
String key = entry.getKey();
Object value = entry.getValue();
// 跳过空值和空key
if (StringUtils.isBlank(key) || Objects.isNull(value)) {
continue;
}
// 处理参数值:数组/嵌套JSON转为JSON字符串普通类型直接转字符串
String paramValue = String.valueOf(value);
// URL编码处理中文、特殊字符如空格、&、=等)
String encodedValue = URLEncoder.encode(paramValue, "UTF-8");
if (params.length() > 0) {
params.append("&");
}
params.append(key).append("=").append(encodedValue);
}
// 拼接完整URL仅当有参数时才加?
if (params.length() > 0) {
return baseUrl + "?" + params.toString();
} else {
log.info("解密后的参数均为空返回原始URL");
return null;
}
} catch (Exception e) {
log.error("GET请求参数解密/拼接失败", e);
return null; // 异常时返回null走原始请求
}
}
}

View File

@@ -8,6 +8,7 @@ import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import org.apache.commons.lang3.time.DateFormatUtils;
@@ -219,4 +220,30 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
return null;
}
}
/**
* 添加1天
* @param dateStr
* @param inputFormat
* @param outputFormat
* @return
*/
public static String addOneDay(String dateStr, String inputFormat, String outputFormat) {
if (dateStr == null || dateStr.trim().isEmpty()
|| inputFormat == null || inputFormat.trim().isEmpty()
|| outputFormat == null || outputFormat.trim().isEmpty()) {
System.err.println("参数错误dateStr/inputFormat/outputFormat 不能为空");
return null;
}
try {
DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern(inputFormat.trim());
DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern(outputFormat.trim());
LocalDate date = LocalDate.parse(dateStr.trim(), inputFormatter);
LocalDate nextDay = date.plusDays(1);
return nextDay.format(outputFormatter);
} catch (Exception e) {
System.err.println("日期解析失败:输入字符串[" + dateStr + "] 与输入格式[" + inputFormat + "]不匹配");
return null;
}
}
}

Some files were not shown because too many files have changed in this diff Show More