feat: govern knowledge assets and source citations

This commit is contained in:
key
2026-08-02 01:43:43 +08:00
parent cafb836cda
commit 699cc08050
144 changed files with 17205 additions and 453 deletions
@@ -285,7 +285,6 @@ public class AuthController {
@NotBlank(message = "{user.phonenumber.not.blank}")
@Pattern(regexp = RegexConstants.MOBILE, message = "{user.mobile.phone.number.not.valid}")
String phonenumber,
@NotBlank(message = "{sms.code.not.blank}")
String smsCode
) {
}
@@ -78,6 +78,9 @@ public class CaptchaController {
@Value("${aihr.sms.prod-fixed-code-enabled:false}")
private boolean smsProdFixedCodeEnabled;
@Value("${aihr.sms.verification-enabled:true}")
private boolean smsVerificationEnabled = true;
private final Environment environment;
/**
@@ -90,6 +93,9 @@ public class CaptchaController {
public R<Void> smsCode(
@NotBlank(message = "{user.phonenumber.not.blank}")
@Pattern(regexp = RegexConstants.MOBILE, message = "{user.mobile.phone.number.not.valid}") String phonenumber) {
if (!smsVerificationEnabled) {
return R.ok();
}
boolean prodProfile = environment.acceptsProfiles(Profiles.of("prod"));
boolean fixedCodeEnabled = shouldUseFixedSmsCode(
smsDevFixedCode,
@@ -32,6 +32,7 @@ import org.dromara.web.service.SysLoginService;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@@ -49,6 +50,9 @@ public class SmsAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
private final SysUserMapper userMapper;
@Value("${aihr.sms.verification-enabled:true}")
private boolean smsVerificationEnabled = true;
@Override
public LoginVo login(String body, SysClientVo client) {
SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
@@ -58,7 +62,10 @@ public class SmsAuthStrategy implements IAuthStrategy {
String smsCode = loginBody.getSmsCode();
boolean appClient = isAppClient(client);
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
loginService.checkLogin(LoginType.SMS, tenantId, phonenumber, () -> !validateSmsCode(tenantId, phonenumber, smsCode));
if (shouldVerifySmsCode(appClient, smsVerificationEnabled)) {
loginService.checkLogin(LoginType.SMS, tenantId, phonenumber,
() -> !validateSmsCode(tenantId, phonenumber, smsCode));
}
SysUserVo user = loadOrRegisterUserByPhonenumber(tenantId, phonenumber, appClient);
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return loginService.buildLoginUser(user);
@@ -123,6 +130,10 @@ public class SmsAuthStrategy implements IAuthStrategy {
return client != null && SmsCodeUtils.MOBILE_CLIENT_ID.equals(client.getClientId());
}
static boolean shouldVerifySmsCode(boolean appClient, boolean verificationEnabled) {
return !appClient || verificationEnabled;
}
SysUserVo loadOrRegisterUserByPhonenumber(String tenantId, String phonenumber, boolean appClient) {
if (appClient && userMapper.exists(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getTenantId, tenantId)
@@ -155,6 +155,10 @@ mail:
--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
# https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用
captcha:
# 本地自动化默认免图形验证码;仅显式设置后开启
enable: ${AIHR_DEV_CAPTCHA_ENABLED:false}
aihr:
practice:
# 仅本地演示允许用训练次数代替 hire_date 推断新员工,生产默认关闭
@@ -164,6 +168,8 @@ aihr:
store-display-fields: true
sms:
login-template-id: ${AIHR_SMS_LOGIN_TEMPLATE_ID:}
# 本地自动化默认直接按手机号登录;显式开启后才校验短信验证码
verification-enabled: ${AIHR_SMS_VERIFICATION_ENABLED:false}
# 演示兜底:非空则不真发短信,验证码固定为该值(仅 dev,prod 配置不含此项)
dev-fixed-code: ${AIHR_SMS_DEV_FIXED_CODE:123456}
sms:
@@ -167,6 +167,8 @@ aihr:
store-display-fields: false
sms:
login-template-id: ${AIHR_SMS_LOGIN_TEMPLATE_ID:}
# 短信是移动端唯一认证因子,生产默认保持校验
verification-enabled: ${AIHR_SMS_VERIFICATION_ENABLED:true}
# 试点期固定验证码:生产默认关闭,需同时显式配置固定码与开关
dev-fixed-code: ${AIHR_SMS_DEV_FIXED_CODE:}
prod-fixed-code-enabled: ${AIHR_SMS_PROD_FIXED_CODE_ENABLED:false}
@@ -17,6 +17,7 @@ import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
@@ -37,6 +38,16 @@ class MobileSmsLoginTenantBoundaryTest {
assertEquals("000000", AuthController.mobileTenantId());
}
@Test
void mobileLoginAllowsAnOmittedCodeWhenDevelopmentVerificationIsDisabled() throws Exception {
AuthController.MobileSmsLoginBody body = new ObjectMapper().readValue("""
{"phonenumber":"13900000000"}
""", AuthController.MobileSmsLoginBody.class);
assertEquals("13900000000", body.phonenumber());
assertNull(body.smsCode());
}
@Test
@SuppressWarnings("unchecked")
void resendingSmsUsesTheSameLockAsVerification() throws Exception {
@@ -52,6 +52,13 @@ class SmsAuthStrategyTenantTest {
assertTrue(SmsAuthStrategy.isAppClient(appClient));
}
@Test
void onlyTheMobileAppClientMaySkipSmsVerification() {
assertFalse(SmsAuthStrategy.shouldVerifySmsCode(true, false));
assertTrue(SmsAuthStrategy.shouldVerifySmsCode(true, true));
assertTrue(SmsAuthStrategy.shouldVerifySmsCode(false, false));
}
@Test
void existingAppUserIsRetainedAndWrongOtpDoesNotConsumeTheCode() throws Exception {
SysLoginService loginService = mock(SysLoginService.class);