chore: demo fixed-code opt-in flag, sop tweaks and doc sync

短信固定验证码增加 aihr.sms.demo-fixed-code-enabled 显式开关(prod
默认仍禁用,演示环境可显式放行);SOP 服务小调整;mobile base 支持
VITE_BASE;作战清单/TechSpec/API_INTEGRATION/docs README 同步。
This commit is contained in:
2026-07-04 16:05:50 +08:00
parent d622c7fb7e
commit 775873e31b
7 changed files with 55 additions and 12 deletions
@@ -62,11 +62,14 @@ public class CaptchaController {
/**
* 演示/开发兜底:配置后不真发短信,验证码固定为该值,把短信通道从演示关键路径摘掉。
* prod profile 下无条件忽略(代码级闸门,防止配置失误造成登录绕过)。
* prod profile 仅在 aihr.sms.demo-fixed-code-enabled=true 时临时放行。
*/
@Value("${aihr.sms.dev-fixed-code:}")
private String smsDevFixedCode;
@Value("${aihr.sms.demo-fixed-code-enabled:false}")
private boolean smsDemoFixedCodeEnabled;
private final Environment environment;
/**
@@ -80,7 +83,9 @@ public class CaptchaController {
@NotBlank(message = "{user.phonenumber.not.blank}")
@Pattern(regexp = RegexConstants.MOBILE, message = "{user.mobile.phone.number.not.valid}") String phonenumber) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
if (StringUtils.isNotBlank(smsDevFixedCode) && !environment.acceptsProfiles(Profiles.of("prod"))) {
boolean fixedCodeEnabled = StringUtils.isNotBlank(smsDevFixedCode)
&& (!environment.acceptsProfiles(Profiles.of("prod")) || smsDemoFixedCodeEnabled);
if (fixedCodeEnabled) {
log.warn("aihr.sms.dev-fixed-code 已启用,短信验证码走固定码,未真实发送短信");
RedisUtils.setCacheObject(key, smsDevFixedCode, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
return R.ok();
@@ -111,7 +111,7 @@ public class AihrSopSeedService {
SopSeed seed = seeds.getOrDefault(requestedCategory, seeds.get("投诉处理 SOP"));
String category = isBlank(requestedCategory) ? seed.category() : requestedCategory;
String queryText = request == null || request.queryText() == null || request.queryText().isBlank() ? seed.question() : request.queryText();
SearchResponse dbResponse = dbSearch(category, queryText, request == null ? null : request.limit());
SearchResponse dbResponse = dbSearch(dbCategory(category), queryText, request == null ? null : request.limit());
if (dbResponse != null && !dbResponse.snippets().isEmpty()) {
return dbResponse;
}
@@ -1613,7 +1613,7 @@ public class AihrSopSeedService {
join aihr_knowledge_info i on i.id = f.knowledge_id and i.tenant_id = f.tenant_id
left join aihr_knowledge_attach a on a.knowledge_id = f.knowledge_id and a.doc_id = f.doc_id and a.tenant_id = f.tenant_id
where f.tenant_id = ?
and i.name = ?
and (? = '' or i.name = ?)
and match(f.content) against (? in natural language mode)
order by score desc, f.idx asc
limit ?
@@ -1625,7 +1625,7 @@ public class AihrSopSeedService {
rs.getString("content"),
rs.getInt("idx"),
rs.getDouble("score")
), queryText, TENANT_ID, category, queryText, limit);
), queryText, TENANT_ID, category, category, queryText, limit);
}
private List<KnowledgeHit> likeHits(String category, String queryText, int limit) {
@@ -1651,7 +1651,7 @@ public class AihrSopSeedService {
join aihr_knowledge_info i on i.id = f.knowledge_id and i.tenant_id = f.tenant_id
left join aihr_knowledge_attach a on a.knowledge_id = f.knowledge_id and a.doc_id = f.doc_id and a.tenant_id = f.tenant_id
where f.tenant_id = ?
and i.name = ?
and (? = '' or i.name = ?)
and (%s)
order by score desc, f.idx asc
limit ?
@@ -1660,6 +1660,7 @@ public class AihrSopSeedService {
terms.stream().map(AihrSopSeedService::likePattern).forEach(args::add);
args.add(TENANT_ID);
args.add(category);
args.add(category);
terms.stream().map(AihrSopSeedService::likePattern).forEach(args::add);
args.add(limit);
return jdbcTemplate.query(sql, (rs, rowNum) -> new KnowledgeHit(
@@ -1727,6 +1728,7 @@ public class AihrSopSeedService {
private static SearchResponse responseFromHits(String category, String queryText, List<KnowledgeHit> hits) {
KnowledgeHit first = hits.get(0);
String responseCategory = isBlank(category) ? first.category() : category;
List<SnippetResponse> snippets = hits.stream()
.map(hit -> new SnippetResponse("第 " + hit.idx() + " 段:" + hit.title(), hit.content()))
.toList();
@@ -1736,7 +1738,7 @@ public class AihrSopSeedService {
}
return new SearchResponse(
queryText,
category,
responseCategory,
"根据已入库 SOP:" + first.content(),
"引用 " + docs.size() + " 份文档 / " + snippets.size() + " 个片段",
List.copyOf(docs.values()),
@@ -1746,6 +1748,10 @@ public class AihrSopSeedService {
);
}
private static String dbCategory(String category) {
return "sop".equalsIgnoreCase(Optional.ofNullable(category).orElse("").trim()) ? "" : category;
}
private static int limitValue(Integer limit) {
if (limit == null) {
return 3;