fix(aihr): scope sop data to tenant

This commit is contained in:
2026-07-14 06:40:54 +08:00
parent 20ed2e9431
commit 5c893e832e
2 changed files with 56 additions and 50 deletions
@@ -37,6 +37,7 @@ import org.dromara.aihr.domain.AihrSopDto.UploadResponse;
import org.dromara.aihr.domain.AihrSopDto.VectorIndexStatusResponse; import org.dromara.aihr.domain.AihrSopDto.VectorIndexStatusResponse;
import org.dromara.aihr.domain.AihrSopDto.VectorizeResponse; import org.dromara.aihr.domain.AihrSopDto.VectorizeResponse;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.satoken.utils.LoginHelper;
import org.apache.tika.metadata.Metadata; import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.parser.AutoDetectParser; import org.apache.tika.parser.AutoDetectParser;
@@ -205,7 +206,7 @@ public class AihrSopSeedService {
firstNonBlank(request == null ? null : request.note(), ""), firstNonBlank(request == null ? null : request.note(), ""),
Timestamp.valueOf(java.time.LocalDateTime.now()), Timestamp.valueOf(java.time.LocalDateTime.now()),
Timestamp.valueOf(java.time.LocalDateTime.now()), Timestamp.valueOf(java.time.LocalDateTime.now()),
TENANT_ID, tenantId(),
id id
); );
return updated == 0 ? null : sopReview(id); return updated == 0 ? null : sopReview(id);
@@ -244,7 +245,7 @@ public class AihrSopSeedService {
review_status = VALUES(review_status), review_status = VALUES(review_status),
update_time = VALUES(update_time) update_time = VALUES(update_time)
""", """,
TENANT_ID, tenantId(),
truncate(queryText, 1000), truncate(queryText, 1000),
queryNorm, queryNorm,
fragmentId, fragmentId,
@@ -285,7 +286,7 @@ public class AihrSopSeedService {
rs.getString("review_status"), rs.getString("review_status"),
formatTimestamp(rs.getTimestamp("create_time")), formatTimestamp(rs.getTimestamp("create_time")),
formatTimestamp(rs.getTimestamp("update_time")) formatTimestamp(rs.getTimestamp("update_time"))
), TENANT_ID, reviewStatus, reviewStatus, limitValue); ), tenantId(), reviewStatus, reviewStatus, limitValue);
} }
public AnswerFeedbackReviewResponse reviewAnswerFeedback(Long id) { public AnswerFeedbackReviewResponse reviewAnswerFeedback(Long id) {
@@ -299,7 +300,7 @@ public class AihrSopSeedService {
WHERE tenant_id = ? AND id = ? WHERE tenant_id = ? AND id = ?
""", """,
Timestamp.valueOf(java.time.LocalDateTime.now()), Timestamp.valueOf(java.time.LocalDateTime.now()),
TENANT_ID, tenantId(),
id id
); );
return updated == 0 ? null : new AnswerFeedbackReviewResponse(id, "已复核"); return updated == 0 ? null : new AnswerFeedbackReviewResponse(id, "已复核");
@@ -397,7 +398,7 @@ public class AihrSopSeedService {
rs.getLong("knowledge_id"), rs.getLong("knowledge_id"),
rs.getString("category"), rs.getString("category"),
rs.getString("doc_id") rs.getString("doc_id")
), TENANT_ID); ), tenantId());
int documents = 0; int documents = 0;
int fragments = 0; int fragments = 0;
@@ -408,7 +409,7 @@ public class AihrSopSeedService {
from aihr_knowledge_fragment from aihr_knowledge_fragment
where tenant_id = ? and knowledge_id = ? and doc_id = ? where tenant_id = ? and knowledge_id = ? and doc_id = ?
order by idx asc order by idx asc
""", String.class, TENANT_ID, target.knowledgeId(), target.docId()); """, String.class, tenantId(), target.knowledgeId(), target.docId());
EmbeddingBatch batch = embedFragments(target.knowledgeId(), target.category(), target.docId(), rows); EmbeddingBatch batch = embedFragments(target.knowledgeId(), target.category(), target.docId(), rows);
if (batch.count() > 0) { if (batch.count() > 0) {
documents++; documents++;
@@ -452,7 +453,7 @@ public class AihrSopSeedService {
update aihr_knowledge_fragment update aihr_knowledge_fragment
set embedding_json = null, embedding_model = null, embedding_time = null, update_time = now() set embedding_json = null, embedding_model = null, embedding_time = null, update_time = now()
where tenant_id = ? where tenant_id = ?
""", TENANT_ID); """, tenantId());
deleteQdrantCollection(); deleteQdrantCollection();
return vectorizeMissing(); return vectorizeMissing();
} }
@@ -492,7 +493,7 @@ public class AihrSopSeedService {
rs.getTimestamp("create_time"), rs.getTimestamp("create_time"),
rs.getTimestamp("update_time"), rs.getTimestamp("update_time"),
rs.getString("remark") rs.getString("remark")
), TENANT_ID); ), tenantId());
} catch (DataAccessException e) { } catch (DataAccessException e) {
return List.of(); return List.of();
} }
@@ -506,7 +507,7 @@ public class AihrSopSeedService {
update aihr_knowledge_import_task update aihr_knowledge_import_task
set status = 4, current_file = '', finished_time = now(), update_time = now(), remark = '用户已取消' set status = 4, current_file = '', finished_time = now(), update_time = now(), remark = '用户已取消'
where tenant_id = ? and id = ? and status = 1 where tenant_id = ? and id = ? and status = 1
""", TENANT_ID, taskId); """, tenantId(), taskId);
return localImportTask(taskId).orElseThrow(() -> new IllegalArgumentException("导入任务不存在")); return localImportTask(taskId).orElseThrow(() -> new IllegalArgumentException("导入任务不存在"));
} }
@@ -605,7 +606,7 @@ public class AihrSopSeedService {
(tenant_id, directory, category, status, total_files, imported_files, failed_files, current_file, started_time, create_time, update_time, remark) (tenant_id, directory, category, status, total_files, imported_files, failed_files, current_file, started_time, create_time, update_time, remark)
values (?, ?, ?, 1, ?, 0, 0, '', now(), now(), now(), '任务已启动') values (?, ?, ?, 1, ?, 0, 0, '', now(), now(), now(), '任务已启动')
""", Statement.RETURN_GENERATED_KEYS); """, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, TENANT_ID); ps.setString(1, tenantId());
ps.setString(2, plan.directory()); ps.setString(2, plan.directory());
ps.setString(3, plan.category()); ps.setString(3, plan.category());
ps.setInt(4, plan.files().size()); ps.setInt(4, plan.files().size());
@@ -633,7 +634,7 @@ public class AihrSopSeedService {
rs.getTimestamp("create_time"), rs.getTimestamp("create_time"),
rs.getTimestamp("update_time"), rs.getTimestamp("update_time"),
rs.getString("remark") rs.getString("remark")
), TENANT_ID, taskId); ), tenantId(), taskId);
return rows.stream().findFirst(); return rows.stream().findFirst();
} }
@@ -642,7 +643,7 @@ public class AihrSopSeedService {
update aihr_knowledge_import_task update aihr_knowledge_import_task
set status = ?, imported_files = ?, failed_files = ?, current_file = ?, update_time = now(), remark = ? set status = ?, imported_files = ?, failed_files = ?, current_file = ?, update_time = now(), remark = ?
where tenant_id = ? and id = ? and status <> 4 where tenant_id = ? and id = ? and status <> 4
""", status, imported, failed, currentFile, limitRemark(remark), TENANT_ID, taskId); """, status, imported, failed, currentFile, limitRemark(remark), tenantId(), taskId);
} }
private void finishLocalImportTask(long taskId, int status, int imported, int failed, String remark) { private void finishLocalImportTask(long taskId, int status, int imported, int failed, String remark) {
@@ -650,7 +651,7 @@ public class AihrSopSeedService {
update aihr_knowledge_import_task update aihr_knowledge_import_task
set status = ?, imported_files = ?, failed_files = ?, current_file = '', finished_time = now(), update_time = now(), remark = ? set status = ?, imported_files = ?, failed_files = ?, current_file = '', finished_time = now(), update_time = now(), remark = ?
where tenant_id = ? and id = ? and status <> 4 where tenant_id = ? and id = ? and status <> 4
""", status, imported, failed, limitRemark(remark), TENANT_ID, taskId); """, status, imported, failed, limitRemark(remark), tenantId(), taskId);
} }
private boolean isLocalImportTaskCanceled(long taskId) { private boolean isLocalImportTaskCanceled(long taskId) {
@@ -659,7 +660,7 @@ public class AihrSopSeedService {
select count(1) select count(1)
from aihr_knowledge_import_task from aihr_knowledge_import_task
where tenant_id = ? and id = ? and status = 4 where tenant_id = ? and id = ? and status = 4
""", Integer.class, TENANT_ID, taskId); """, Integer.class, tenantId(), taskId);
return count != null && count > 0; return count != null && count > 0;
} catch (DataAccessException e) { } catch (DataAccessException e) {
return false; return false;
@@ -740,13 +741,13 @@ public class AihrSopSeedService {
} }
deleteQdrantDoc(config.knowledgeId(), docId); deleteQdrantDoc(config.knowledgeId(), docId);
jdbcTemplate.update("delete from aihr_knowledge_fragment where tenant_id = ? and knowledge_id = ? and doc_id = ?", TENANT_ID, config.knowledgeId(), docId); jdbcTemplate.update("delete from aihr_knowledge_fragment where tenant_id = ? and knowledge_id = ? and doc_id = ?", tenantId(), config.knowledgeId(), docId);
for (int i = 0; i < fragments.size(); i++) { for (int i = 0; i < fragments.size(); i++) {
jdbcTemplate.update(""" jdbcTemplate.update("""
insert into aihr_knowledge_fragment insert into aihr_knowledge_fragment
(tenant_id, knowledge_id, idx, doc_id, content, create_time, update_time, remark) (tenant_id, knowledge_id, idx, doc_id, content, create_time, update_time, remark)
values (?, ?, ?, ?, ?, now(), now(), ?) values (?, ?, ?, ?, ?, now(), now(), ?)
""", TENANT_ID, config.knowledgeId(), i + 1, docId, fragments.get(i), "upload:" + fileName); """, tenantId(), config.knowledgeId(), i + 1, docId, fragments.get(i), "upload:" + fileName);
} }
embedFragments(config.knowledgeId(), knowledgeName, docId, fragments); embedFragments(config.knowledgeId(), knowledgeName, docId, fragments);
updateOssInsight(ossId, insight, fingerprint); updateOssInsight(ossId, insight, fingerprint);
@@ -883,7 +884,7 @@ public class AihrSopSeedService {
where tenant_id = ? where tenant_id = ?
order by update_time desc, id desc order by update_time desc, id desc
limit 50 limit 50
""", (rs, rowNum) -> new CategoryOption(rs.getString("name"), rs.getString("description")), TENANT_ID); """, (rs, rowNum) -> new CategoryOption(rs.getString("name"), rs.getString("description")), tenantId());
} catch (DataAccessException e) { } catch (DataAccessException e) {
return List.of(); return List.of();
} }
@@ -908,7 +909,7 @@ public class AihrSopSeedService {
rs.getString("model_name"), rs.getString("model_name"),
rs.getString("resolved_api_host"), rs.getString("resolved_api_host"),
rs.getString("resolved_api_key") rs.getString("resolved_api_key")
), TENANT_ID); ), tenantId());
return rows.stream() return rows.stream()
.filter(runtime -> !isBlank(runtime.modelName()) && !isBlank(runtime.baseUrl())) .filter(runtime -> !isBlank(runtime.modelName()) && !isBlank(runtime.baseUrl()))
.findFirst(); .findFirst();
@@ -938,7 +939,7 @@ public class AihrSopSeedService {
rs.getString("model_name"), rs.getString("model_name"),
rs.getString("resolved_api_host"), rs.getString("resolved_api_host"),
rs.getString("resolved_api_key") rs.getString("resolved_api_key")
), TENANT_ID); ), tenantId());
return rows.stream() return rows.stream()
.filter(runtime -> !isBlank(runtime.modelName()) && !isBlank(runtime.baseUrl())) .filter(runtime -> !isBlank(runtime.modelName()) && !isBlank(runtime.baseUrl()))
.findFirst(); .findFirst();
@@ -1015,7 +1016,7 @@ public class AihrSopSeedService {
where a.tenant_id = ? and a.id = ? where a.tenant_id = ? and a.id = ?
limit 1 limit 1
""", (rs, rowNum) -> insightFromExt(rs.getString("ext1"), firstNonBlank(rs.getString("category"), fallbackCategory)), """, (rs, rowNum) -> insightFromExt(rs.getString("ext1"), firstNonBlank(rs.getString("category"), fallbackCategory)),
TENANT_ID, hit.attachId())); tenantId(), hit.attachId()));
} catch (DataAccessException e) { } catch (DataAccessException e) {
return Optional.empty(); return Optional.empty();
} }
@@ -1061,7 +1062,7 @@ public class AihrSopSeedService {
rs.getString("doc_id"), rs.getString("doc_id"),
rs.getLong("oss_id"), rs.getLong("oss_id"),
rs.getString("match_type") rs.getString("match_type")
), fingerprint.fileSha256(), fingerprint.textSha256(), TENANT_ID, fingerprint.fileSha256(), fingerprint.textSha256(), fileName, fingerprint.sizeBytes(), fingerprint.fileSha256(), fingerprint.textSha256()); ), fingerprint.fileSha256(), fingerprint.textSha256(), tenantId(), fingerprint.fileSha256(), fingerprint.textSha256(), fileName, fingerprint.sizeBytes(), fingerprint.fileSha256(), fingerprint.textSha256());
} catch (DataAccessException e) { } catch (DataAccessException e) {
return List.of(); return List.of();
} }
@@ -1071,13 +1072,13 @@ public class AihrSopSeedService {
String docId = isBlank(hit.docId()) ? UUID.randomUUID().toString().replace("-", "") : hit.docId(); String docId = isBlank(hit.docId()) ? UUID.randomUUID().toString().replace("-", "") : hit.docId();
if (hit.knowledgeId() != targetKnowledgeId && !isBlank(hit.docId())) { if (hit.knowledgeId() != targetKnowledgeId && !isBlank(hit.docId())) {
deleteQdrantDoc(hit.knowledgeId(), hit.docId()); deleteQdrantDoc(hit.knowledgeId(), hit.docId());
jdbcTemplate.update("delete from aihr_knowledge_fragment where tenant_id = ? and knowledge_id = ? and doc_id = ?", TENANT_ID, hit.knowledgeId(), hit.docId()); jdbcTemplate.update("delete from aihr_knowledge_fragment where tenant_id = ? and knowledge_id = ? and doc_id = ?", tenantId(), hit.knowledgeId(), hit.docId());
} }
jdbcTemplate.update(""" jdbcTemplate.update("""
update aihr_knowledge_attach update aihr_knowledge_attach
set knowledge_id = ?, oss_id = ?, doc_id = ?, type = ?, status = 1, update_time = now(), remark = 'duplicate:reusing' set knowledge_id = ?, oss_id = ?, doc_id = ?, type = ?, status = 1, update_time = now(), remark = 'duplicate:reusing'
where tenant_id = ? and id = ? where tenant_id = ? and id = ?
""", targetKnowledgeId, hit.ossId(), docId, fileType(fileName), TENANT_ID, hit.attachId()); """, targetKnowledgeId, hit.ossId(), docId, fileType(fileName), tenantId(), hit.attachId());
return docId; return docId;
} }
@@ -1088,9 +1089,9 @@ public class AihrSopSeedService {
} }
if (!isBlank(hit.docId())) { if (!isBlank(hit.docId())) {
deleteQdrantDoc(hit.knowledgeId(), hit.docId()); deleteQdrantDoc(hit.knowledgeId(), hit.docId());
jdbcTemplate.update("delete from aihr_knowledge_fragment where tenant_id = ? and knowledge_id = ? and doc_id = ?", TENANT_ID, hit.knowledgeId(), hit.docId()); jdbcTemplate.update("delete from aihr_knowledge_fragment where tenant_id = ? and knowledge_id = ? and doc_id = ?", tenantId(), hit.knowledgeId(), hit.docId());
} }
jdbcTemplate.update("delete from aihr_knowledge_attach where tenant_id = ? and id = ?", TENANT_ID, hit.attachId()); jdbcTemplate.update("delete from aihr_knowledge_attach where tenant_id = ? and id = ?", tenantId(), hit.attachId());
} }
} }
@@ -1203,7 +1204,7 @@ public class AihrSopSeedService {
insight.tags(), insight.tags(),
insight.reason() insight.reason()
); );
}, TENANT_ID); }, tenantId());
} catch (DataAccessException e) { } catch (DataAccessException e) {
return List.of(); return List.of();
} }
@@ -1578,7 +1579,7 @@ public class AihrSopSeedService {
update aihr_knowledge_fragment update aihr_knowledge_fragment
set embedding_json = ?, embedding_model = ?, embedding_time = now(), update_time = now() set embedding_json = ?, embedding_model = ?, embedding_time = now(), update_time = now()
where tenant_id = ? and knowledge_id = ? and doc_id = ? and idx = ? where tenant_id = ? and knowledge_id = ? and doc_id = ? and idx = ?
""", embeddings.get(i), runtime.modelName(), TENANT_ID, knowledgeId, docId, i + 1); """, embeddings.get(i), runtime.modelName(), tenantId(), knowledgeId, docId, i + 1);
} }
try { try {
upsertQdrant(knowledgeId, category, docId, fragments, embeddings, runtime.modelName()); upsertQdrant(knowledgeId, category, docId, fragments, embeddings, runtime.modelName());
@@ -1612,7 +1613,7 @@ public class AihrSopSeedService {
rs.getString("resolved_api_host"), rs.getString("resolved_api_host"),
rs.getString("resolved_api_key"), rs.getString("resolved_api_key"),
rs.getInt("model_dimension") rs.getInt("model_dimension")
), TENANT_ID).stream() ), tenantId()).stream()
.filter(runtime -> configured(runtime.baseUrl(), runtime.modelName(), runtime.apiKey())) .filter(runtime -> configured(runtime.baseUrl(), runtime.modelName(), runtime.apiKey()))
.toList()); .toList());
} catch (DataAccessException ignored) { } catch (DataAccessException ignored) {
@@ -1722,7 +1723,7 @@ public class AihrSopSeedService {
rs.getString("model_name"), rs.getString("model_name"),
rs.getString("resolved_api_host"), rs.getString("resolved_api_host"),
rs.getString("resolved_api_key") rs.getString("resolved_api_key")
), TENANT_ID); ), tenantId());
return rows.stream() return rows.stream()
.filter(runtime -> !isBlank(runtime.modelName()) && !isBlank(runtime.baseUrl()) && !isBlank(runtime.apiKey())) .filter(runtime -> !isBlank(runtime.modelName()) && !isBlank(runtime.baseUrl()) && !isBlank(runtime.apiKey()))
.findFirst(); .findFirst();
@@ -1814,7 +1815,7 @@ public class AihrSopSeedService {
(tenant_id, query_text, category, position, source, status, create_time) (tenant_id, query_text, category, position, source, status, create_time)
VALUES (?, ?, ?, ?, ?, '待补充', ?) VALUES (?, ?, ?, ?, ?, '待补充', ?)
""", """,
TENANT_ID, tenantId(),
queryText.trim(), queryText.trim(),
firstNonBlank(category, "sop"), firstNonBlank(category, "sop"),
firstNonBlank(position, "生活顾问"), firstNonBlank(position, "生活顾问"),
@@ -1858,7 +1859,7 @@ public class AihrSopSeedService {
(tenant_id, query_text, category, answer_text, reference_text, snippet_count, source, prompt_version, status, create_time, update_time) (tenant_id, query_text, category, answer_text, reference_text, snippet_count, source, prompt_version, status, create_time, update_time)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, '待评审', ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, '待评审', ?, ?)
""", Statement.RETURN_GENERATED_KEYS); """, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, TENANT_ID); ps.setString(1, tenantId());
ps.setString(2, truncate(response.queryText(), 1000)); ps.setString(2, truncate(response.queryText(), 1000));
ps.setString(3, firstNonBlank(response.category(), "sop")); ps.setString(3, firstNonBlank(response.category(), "sop"));
ps.setString(4, truncate(response.answer(), 2000)); ps.setString(4, truncate(response.answer(), 2000));
@@ -1893,7 +1894,7 @@ public class AihrSopSeedService {
reviewedTime == null ? "" : reviewedTime.toLocalDateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")), reviewedTime == null ? "" : reviewedTime.toLocalDateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
firstNonBlank(rs.getString("prompt_version"), "unknown") firstNonBlank(rs.getString("prompt_version"), "unknown")
); );
}, TENANT_ID, id); }, tenantId(), id);
return rows.isEmpty() ? null : rows.get(0); return rows.isEmpty() ? null : rows.get(0);
} }
@@ -1903,7 +1904,7 @@ public class AihrSopSeedService {
SELECT COUNT(*) AS total, COALESCE(SUM(CASE WHEN usable = 1 THEN 1 ELSE 0 END), 0) AS usable SELECT COUNT(*) AS total, COALESCE(SUM(CASE WHEN usable = 1 THEN 1 ELSE 0 END), 0) AS usable
FROM aihr_sop_answer_review FROM aihr_sop_answer_review
WHERE tenant_id = ? AND status = '已评审' WHERE tenant_id = ? AND status = '已评审'
""", (rs, rowNum) -> new SopReviewStats(rs.getInt("total"), rs.getInt("usable")), TENANT_ID); """, (rs, rowNum) -> new SopReviewStats(rs.getInt("total"), rs.getInt("usable")), tenantId());
} }
public void prepareSopReviewStats() { public void prepareSopReviewStats() {
@@ -1918,7 +1919,7 @@ public class AihrSopSeedService {
WHERE tenant_id = ? AND status = '已评审' WHERE tenant_id = ? AND status = '已评审'
AND reviewed_time >= ? AND reviewed_time < ? AND reviewed_time >= ? AND reviewed_time < ?
""", (rs, rowNum) -> new SopReviewStats(rs.getInt("total"), rs.getInt("usable")), """, (rs, rowNum) -> new SopReviewStats(rs.getInt("total"), rs.getInt("usable")),
TENANT_ID, Timestamp.valueOf(startTime), Timestamp.valueOf(endTime)); tenantId(), Timestamp.valueOf(startTime), Timestamp.valueOf(endTime));
} }
private void ensureSopReviewTable() { private void ensureSopReviewTable() {
@@ -2105,7 +2106,7 @@ public class AihrSopSeedService {
WHERE tenant_id = ? WHERE tenant_id = ?
AND query_norm = ? AND query_norm = ?
AND verdict = 'down' AND verdict = 'down'
""", Long.class, TENANT_ID, queryNorm)); """, Long.class, tenantId(), queryNorm));
} catch (DataAccessException ignored) { } catch (DataAccessException ignored) {
return Set.of(); return Set.of();
} }
@@ -2160,7 +2161,7 @@ public class AihrSopSeedService {
rs.getString("content"), rs.getString("content"),
rs.getInt("idx"), rs.getInt("idx"),
rs.getDouble("score") rs.getDouble("score")
), queryText, TENANT_ID, category, category, queryText, limit); ), queryText, tenantId(), category, category, queryText, limit);
} }
private List<KnowledgeHit> likeHits(String category, String queryText, int limit) { private List<KnowledgeHit> likeHits(String category, String queryText, int limit) {
@@ -2194,7 +2195,7 @@ public class AihrSopSeedService {
""".formatted(String.join(" + ", scoreParts), String.join(" or ", whereParts)); """.formatted(String.join(" + ", scoreParts), String.join(" or ", whereParts));
List<Object> args = new ArrayList<>(); List<Object> args = new ArrayList<>();
terms.stream().map(AihrSopSeedService::likePattern).forEach(args::add); terms.stream().map(AihrSopSeedService::likePattern).forEach(args::add);
args.add(TENANT_ID); args.add(tenantId());
args.add(category); args.add(category);
args.add(category); args.add(category);
terms.stream().map(AihrSopSeedService::likePattern).forEach(args::add); terms.stream().map(AihrSopSeedService::likePattern).forEach(args::add);
@@ -2236,7 +2237,7 @@ public class AihrSopSeedService {
rs.getString("content"), rs.getString("content"),
rs.getInt("idx"), rs.getInt("idx"),
match.score() match.score()
), TENANT_ID, match.docId(), match.idx()); ), tenantId(), match.docId(), match.idx());
return rows.stream().findFirst(); return rows.stream().findFirst();
} }
@@ -2453,7 +2454,7 @@ public class AihrSopSeedService {
firstNonBlank(rs.getString("system_prompt"), SOP_ANSWER_SYSTEM_PROMPT), firstNonBlank(rs.getString("system_prompt"), SOP_ANSWER_SYSTEM_PROMPT),
firstNonBlank(rs.getString("template"), SOP_ANSWER_USER_TEMPLATE), firstNonBlank(rs.getString("template"), SOP_ANSWER_USER_TEMPLATE),
"sop_answer:" + firstNonBlank(rs.getString("version"), "v1") "sop_answer:" + firstNonBlank(rs.getString("version"), "v1")
), TENANT_ID); ), tenantId());
if (!rows.isEmpty()) { if (!rows.isEmpty()) {
return rows.get(0); return rows.get(0);
} }
@@ -2682,7 +2683,7 @@ public class AihrSopSeedService {
rs.getLong("id"), rs.getLong("id"),
Math.max(200, rs.getInt("block_size")), Math.max(200, rs.getInt("block_size")),
Math.max(0, Math.min(rs.getInt("overlap_char"), 200)) Math.max(0, Math.min(rs.getInt("overlap_char"), 200))
), TENANT_ID, category); ), tenantId(), category);
if (!rows.isEmpty()) { if (!rows.isEmpty()) {
return rows.get(0); return rows.get(0);
} }
@@ -2695,7 +2696,7 @@ public class AihrSopSeedService {
text_block_size, enable_hybrid, hybrid_alpha, system_prompt, create_time, update_time, remark) text_block_size, enable_hybrid, hybrid_alpha, system_prompt, create_time, update_time, remark)
values (?, ?, ?, ?, 120, 5, 0.5, 800, 1, 1.0, ?, now(), now(), 'upload') values (?, ?, ?, ?, 120, 5, 0.5, 800, 1, 1.0, ?, now(), now(), 'upload')
""", Statement.RETURN_GENERATED_KEYS); """, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, TENANT_ID); ps.setString(1, tenantId());
ps.setString(2, category); ps.setString(2, category);
ps.setString(3, category + " 上传知识库"); ps.setString(3, category + " 上传知识库");
ps.setString(4, "\n\n"); ps.setString(4, "\n\n");
@@ -2712,20 +2713,20 @@ public class AihrSopSeedService {
from aihr_knowledge_attach from aihr_knowledge_attach
where tenant_id = ? and knowledge_id = ? and name = ? where tenant_id = ? and knowledge_id = ? and name = ?
limit 1 limit 1
""", String.class, TENANT_ID, knowledgeId, fileName); """, String.class, tenantId(), knowledgeId, fileName);
String docId = rows.isEmpty() || rows.get(0) == null ? UUID.randomUUID().toString().replace("-", "") : rows.get(0); String docId = rows.isEmpty() || rows.get(0) == null ? UUID.randomUUID().toString().replace("-", "") : rows.get(0);
if (rows.isEmpty()) { if (rows.isEmpty()) {
jdbcTemplate.update(""" jdbcTemplate.update("""
insert into aihr_knowledge_attach insert into aihr_knowledge_attach
(tenant_id, knowledge_id, oss_id, doc_id, name, type, status, create_time, update_time, remark) (tenant_id, knowledge_id, oss_id, doc_id, name, type, status, create_time, update_time, remark)
values (?, ?, ?, ?, ?, ?, 1, now(), now(), 'oss-upload:parsing') values (?, ?, ?, ?, ?, ?, 1, now(), now(), 'oss-upload:parsing')
""", TENANT_ID, knowledgeId, ossId, docId, fileName, fileType(fileName)); """, tenantId(), knowledgeId, ossId, docId, fileName, fileType(fileName));
} else { } else {
jdbcTemplate.update(""" jdbcTemplate.update("""
update aihr_knowledge_attach update aihr_knowledge_attach
set oss_id = ?, doc_id = ?, type = ?, status = 1, update_time = now(), remark = 'oss-upload:parsing' set oss_id = ?, doc_id = ?, type = ?, status = 1, update_time = now(), remark = 'oss-upload:parsing'
where tenant_id = ? and knowledge_id = ? and name = ? where tenant_id = ? and knowledge_id = ? and name = ?
""", ossId, docId, fileType(fileName), TENANT_ID, knowledgeId, fileName); """, ossId, docId, fileType(fileName), tenantId(), knowledgeId, fileName);
} }
return docId; return docId;
} }
@@ -2735,7 +2736,7 @@ public class AihrSopSeedService {
update aihr_knowledge_attach update aihr_knowledge_attach
set status = ?, update_time = now(), remark = ? set status = ?, update_time = now(), remark = ?
where tenant_id = ? and knowledge_id = ? and doc_id = ? where tenant_id = ? and knowledge_id = ? and doc_id = ?
""", status, limitRemark(remark), TENANT_ID, knowledgeId, docId); """, status, limitRemark(remark), tenantId(), knowledgeId, docId);
} }
private void upsertQdrant(long knowledgeId, String category, String docId, List<String> fragments, List<String> embeddings, String modelName) throws Exception { private void upsertQdrant(long knowledgeId, String category, String docId, List<String> fragments, List<String> embeddings, String modelName) throws Exception {
@@ -2756,7 +2757,7 @@ public class AihrSopSeedService {
continue; continue;
} }
ObjectNode payload = objectMapper.createObjectNode(); ObjectNode payload = objectMapper.createObjectNode();
payload.put("tenant_id", TENANT_ID); payload.put("tenant_id", tenantId());
payload.put("knowledge_id", knowledgeId); payload.put("knowledge_id", knowledgeId);
payload.put("category", category); payload.put("category", category);
payload.put("doc_id", docId); payload.put("doc_id", docId);
@@ -2849,7 +2850,7 @@ public class AihrSopSeedService {
model, model,
dimension dimension
); );
}, TENANT_ID); }, tenantId());
} catch (DataAccessException e) { } catch (DataAccessException e) {
return new VectorDbStats(0, 0, "", null); return new VectorDbStats(0, 0, "", null);
} }
@@ -2922,7 +2923,7 @@ public class AihrSopSeedService {
private ObjectNode qdrantFilter(Long knowledgeId, String docId, String category) { private ObjectNode qdrantFilter(Long knowledgeId, String docId, String category) {
ObjectNode filter = objectMapper.createObjectNode(); ObjectNode filter = objectMapper.createObjectNode();
ArrayNode must = filter.putArray("must"); ArrayNode must = filter.putArray("must");
must.add(qdrantMatch("tenant_id", TENANT_ID)); must.add(qdrantMatch("tenant_id", tenantId()));
if (knowledgeId != null) { if (knowledgeId != null) {
must.add(qdrantMatch("knowledge_id", knowledgeId)); must.add(qdrantMatch("knowledge_id", knowledgeId));
} }
@@ -3243,6 +3244,10 @@ public class AihrSopSeedService {
return value.substring(0, maxLength); return value.substring(0, maxLength);
} }
private String tenantId() {
return firstNonBlank(LoginHelper.getTenantId(), TENANT_ID);
}
private static String normalizeBaseUrl(String baseUrl) { private static String normalizeBaseUrl(String baseUrl) {
String normalized = baseUrl == null ? "" : baseUrl.trim(); String normalized = baseUrl == null ? "" : baseUrl.trim();
while (normalized.endsWith("/")) { while (normalized.endsWith("/")) {
@@ -3263,8 +3268,8 @@ public class AihrSopSeedService {
return firstNonBlank(System.getProperty("aihr.qdrant.apiKey"), System.getenv("AIHR_QDRANT_API_KEY"), ""); return firstNonBlank(System.getProperty("aihr.qdrant.apiKey"), System.getenv("AIHR_QDRANT_API_KEY"), "");
} }
private static String qdrantPointId(long knowledgeId, String docId, int idx) { private String qdrantPointId(long knowledgeId, String docId, int idx) {
return UUID.nameUUIDFromBytes((TENANT_ID + ":" + knowledgeId + ":" + docId + ":" + idx).getBytes(StandardCharsets.UTF_8)).toString(); return UUID.nameUUIDFromBytes((tenantId() + ":" + knowledgeId + ":" + docId + ":" + idx).getBytes(StandardCharsets.UTF_8)).toString();
} }
private static boolean ok(int statusCode) { private static boolean ok(int statusCode) {
+1
View File
@@ -94,3 +94,4 @@
- 2026-07-14 门禁合同复核:`scripts/tests/demo-check-pilot-gates.test.sh` 通过,确认严格预检覆盖批次日期、唯一在职身份、1–2 个项目、每人至少 10 次训练、80% 完训、20 条校准、AI/人工分档一致率、SOP 可用率、满意度和音频/RAG schema;`scripts/tests/pilot-export-contract.test.mjs` 5/5 通过,AIHR 案例/对练定向测试 31/31 通过,`mobile-uni` 类型检查与 49 个单测通过。当前没有新增可安全实现的门禁缺口,剩余阻塞仍是正式组织映射、正式试点数据、视频样片和方言样本等外部输入。 - 2026-07-14 门禁合同复核:`scripts/tests/demo-check-pilot-gates.test.sh` 通过,确认严格预检覆盖批次日期、唯一在职身份、1–2 个项目、每人至少 10 次训练、80% 完训、20 条校准、AI/人工分档一致率、SOP 可用率、满意度和音频/RAG schema;`scripts/tests/pilot-export-contract.test.mjs` 5/5 通过,AIHR 案例/对练定向测试 31/31 通过,`mobile-uni` 类型检查与 49 个单测通过。当前没有新增可安全实现的门禁缺口,剩余阻塞仍是正式组织映射、正式试点数据、视频样片和方言样本等外部输入。
- 2026-07-14 租户边界复核:AI 面试结果、候选人入职主体关联、候选人补充资料、模型配置和案例记录的查询、写入、组织身份解析均改为使用当前登录租户;无登录上下文时仅保留本地开发默认租户 `000000` 回退。该修复已覆盖案例服务的列表、详情、项目范围、贡献者校验、保存和持久化回读路径,仍需在正式多租户环境做隔离回归。 - 2026-07-14 租户边界复核:AI 面试结果、候选人入职主体关联、候选人补充资料、模型配置和案例记录的查询、写入、组织身份解析均改为使用当前登录租户;无登录上下文时仅保留本地开发默认租户 `000000` 回退。该修复已覆盖案例服务的列表、详情、项目范围、贡献者校验、保存和持久化回读路径,仍需在正式多租户环境做隔离回归。
- 2026-07-14 仪表盘租户边界复核:管理端首页的面试、训练、知识库、案例、候选资料、组织和模型统计改为按当前登录租户查询;组织同步写入和组织快照查询也改为当前登录租户;无登录上下文时保留本地开发默认租户回退。尚未把批量知识处理队列和完整 SOP 服务的全部租户调用统一替换,后续继续按数据敏感度分批审查。 - 2026-07-14 仪表盘租户边界复核:管理端首页的面试、训练、知识库、案例、候选资料、组织和模型统计改为按当前登录租户查询;组织同步写入和组织快照查询也改为当前登录租户;无登录上下文时保留本地开发默认租户回退。尚未把批量知识处理队列和完整 SOP 服务的全部租户调用统一替换,后续继续按数据敏感度分批审查。
- 2026-07-14 SOP/RAG 租户边界复核:知识库搜索、知识缺口、SOP 问答评审、答案反馈、异步导入任务、文档/片段/向量写入及 Qdrant tenant filter 改为当前登录租户;无登录上下文时回退本地开发租户 `000000`。定向 AIHR 测试 48/48 通过;正式多租户检索隔离仍需在测试环境做跨租户回归,批量上传队列仍待单独审查。