feat: add readonly platform identity repository
parent
10a66eca1f
commit
ac78354498
@ -0,0 +1,11 @@
|
||||
package com.yau.digitalrmb.platformintegration.application;
|
||||
|
||||
import com.yau.digitalrmb.platformintegration.domain.PlatformActor;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface PlatformIdentityRepository {
|
||||
Optional<PlatformActor> findByPlatformUserId(long platformUserId);
|
||||
|
||||
Optional<PlatformActor> findBySchoolAccount(String schoolAccount);
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package com.yau.digitalrmb.platformintegration.domain;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record PlatformActor(
|
||||
long platformUserId,
|
||||
String account,
|
||||
String displayName,
|
||||
PlatformRole role,
|
||||
Instant tokenSigningTime) {
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
package com.yau.digitalrmb.platformintegration.infrastructure;
|
||||
|
||||
import com.yau.digitalrmb.platformintegration.application.PlatformIdentityRepository;
|
||||
import com.yau.digitalrmb.platformintegration.domain.PlatformActor;
|
||||
import com.yau.digitalrmb.platformintegration.domain.PlatformRole;
|
||||
import org.springframework.jdbc.core.simple.JdbcClient;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public class JdbcPlatformIdentityRepository implements PlatformIdentityRepository {
|
||||
private static final String TEACHER_QUERY = """
|
||||
SELECT cu.ID, cu.CODE, cu.NAME, t.add_time AS signing_time
|
||||
FROM core_user cu JOIN teacher t ON t.user_id = cu.ID
|
||||
WHERE cu.JOB_TYPE1 = 'JT_S_02' AND cu.STATE = 'S1' AND cu.DEL_FLAG = 0
|
||||
AND t.teacher_status = 1 AND t.add_time IS NOT NULL AND %s
|
||||
""";
|
||||
private static final String STUDENT_QUERY = """
|
||||
SELECT cu.ID, cu.CODE, cu.NAME, s.add_time AS signing_time
|
||||
FROM core_user cu JOIN student s ON s.user_id = cu.ID
|
||||
WHERE cu.JOB_TYPE1 = 'JT_S_03' AND cu.STATE = 'S1' AND cu.DEL_FLAG = 0
|
||||
AND s.student_status = 1 AND s.add_time IS NOT NULL AND %s
|
||||
""";
|
||||
|
||||
private final JdbcClient jdbcClient;
|
||||
|
||||
public JdbcPlatformIdentityRepository(@Qualifier("platformJdbcClient") JdbcClient jdbcClient) {
|
||||
this.jdbcClient = jdbcClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<PlatformActor> findByPlatformUserId(long platformUserId) {
|
||||
return findBy("cu.ID = :value", platformUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<PlatformActor> findBySchoolAccount(String schoolAccount) {
|
||||
return findBy("cu.CODE = :value", schoolAccount);
|
||||
}
|
||||
|
||||
private Optional<PlatformActor> findBy(String predicate, Object value) {
|
||||
Optional<PlatformActor> teacher = query(TEACHER_QUERY.formatted(predicate), value, PlatformRole.TEACHER);
|
||||
return teacher.isPresent() ? teacher : query(STUDENT_QUERY.formatted(predicate), value, PlatformRole.STUDENT);
|
||||
}
|
||||
|
||||
private Optional<PlatformActor> query(String sql, Object value, PlatformRole role) {
|
||||
return jdbcClient.sql(sql)
|
||||
.param("value", value)
|
||||
.query((resultSet, rowNumber) -> map(resultSet, role))
|
||||
.optional();
|
||||
}
|
||||
|
||||
private PlatformActor map(ResultSet resultSet, PlatformRole role) throws SQLException {
|
||||
return new PlatformActor(
|
||||
resultSet.getLong("ID"),
|
||||
resultSet.getString("CODE"),
|
||||
resultSet.getString("NAME"),
|
||||
role,
|
||||
resultSet.getTimestamp("signing_time").toInstant());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.yau.digitalrmb.platformintegration.infrastructure;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import com.yau.digitalrmb.platformintegration.config.PlatformIntegrationProperties;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.simple.JdbcClient;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class PlatformReadOnlyDataSourceConfig {
|
||||
@Bean(name = "platformReadOnlyDataSource", destroyMethod = "close")
|
||||
public HikariDataSource platformReadOnlyDataSource(PlatformIntegrationProperties properties) {
|
||||
HikariConfig config = new HikariConfig();
|
||||
config.setJdbcUrl(properties.getDatasource().getUrl());
|
||||
config.setUsername(properties.getDatasource().getUsername());
|
||||
config.setPassword(properties.getDatasource().getPassword());
|
||||
config.setReadOnly(true);
|
||||
config.setMaximumPoolSize(5);
|
||||
config.setPoolName("platform-readonly");
|
||||
return new HikariDataSource(config);
|
||||
}
|
||||
|
||||
@Bean(name = "platformJdbcClient")
|
||||
public JdbcClient platformJdbcClient(@Qualifier("platformReadOnlyDataSource") DataSource dataSource) {
|
||||
return JdbcClient.create(dataSource);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
package com.yau.digitalrmb.platformintegration.infrastructure;
|
||||
|
||||
import com.yau.digitalrmb.platformintegration.application.PlatformIdentityRepository;
|
||||
import com.yau.digitalrmb.platformintegration.domain.PlatformActor;
|
||||
import com.yau.digitalrmb.platformintegration.domain.PlatformRole;
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.jdbc.core.simple.JdbcClient;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class JdbcPlatformIdentityRepositoryTest {
|
||||
private DataSource dataSource;
|
||||
private PlatformIdentityRepository repository;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
JdbcDataSource source = new JdbcDataSource();
|
||||
source.setURL("jdbc:h2:mem:platform_identity;MODE=MySQL;DB_CLOSE_DELAY=-1");
|
||||
source.setUser("sa");
|
||||
dataSource = source;
|
||||
execute("DROP ALL OBJECTS");
|
||||
createSchema();
|
||||
repository = new JdbcPlatformIdentityRepository(JdbcClient.create(dataSource));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvesEnabledTeacherFromCoreUserAndTeacherProfile() throws Exception {
|
||||
execute("INSERT INTO core_user(ID, CODE, NAME, STATE, JOB_TYPE1, DEL_FLAG) VALUES (101, 't001', '教师甲', 'S1', 'JT_S_02', 0)");
|
||||
execute("INSERT INTO teacher(teacher_id, user_id, teacher_status, add_time) VALUES (1, 101, 1, '2026-01-01 00:00:00')");
|
||||
|
||||
PlatformActor actor = repository.findByPlatformUserId(101L).orElseThrow();
|
||||
|
||||
assertThat(actor.account()).isEqualTo("t001");
|
||||
assertThat(actor.role()).isEqualTo(PlatformRole.TEACHER);
|
||||
assertThat(actor.tokenSigningTime()).isEqualTo(Instant.parse("2025-12-31T16:00:00Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void excludesDisabledOrUnsupportedUsers() throws Exception {
|
||||
execute("INSERT INTO core_user(ID, CODE, NAME, STATE, JOB_TYPE1, DEL_FLAG) VALUES (201, 's001', '学生甲', 'S1', 'JT_S_03', 0)");
|
||||
execute("INSERT INTO student(student_id, user_id, student_status, add_time) VALUES (1, 201, 2, '2026-01-01 00:00:00')");
|
||||
execute("INSERT INTO core_user(ID, CODE, NAME, STATE, JOB_TYPE1, DEL_FLAG) VALUES (202, 'admin', '管理员', 'S1', 'JT_S_01', 0)");
|
||||
|
||||
assertThat(repository.findByPlatformUserId(201L)).isEmpty();
|
||||
assertThat(repository.findByPlatformUserId(202L)).isEmpty();
|
||||
}
|
||||
|
||||
private void createSchema() throws Exception {
|
||||
execute("CREATE TABLE core_user(ID BIGINT PRIMARY KEY, CODE VARCHAR(64), NAME VARCHAR(64), STATE VARCHAR(16), JOB_TYPE1 VARCHAR(16), DEL_FLAG INT)");
|
||||
execute("CREATE TABLE teacher(teacher_id BIGINT PRIMARY KEY, user_id BIGINT, teacher_status INT, add_time TIMESTAMP)");
|
||||
execute("CREATE TABLE student(student_id BIGINT PRIMARY KEY, user_id BIGINT, student_status INT, add_time TIMESTAMP)");
|
||||
}
|
||||
|
||||
private void execute(String sql) throws Exception {
|
||||
try (var connection = dataSource.getConnection(); var statement = connection.createStatement()) {
|
||||
statement.execute(sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package com.yau.digitalrmb.platformintegration.infrastructure;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import com.yau.digitalrmb.platformintegration.config.PlatformIntegrationProperties;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.simple.JdbcClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PlatformReadOnlyDataSourceConfigTest {
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(PropertiesConfiguration.class, PlatformReadOnlyDataSourceConfig.class))
|
||||
.withPropertyValues(
|
||||
"platform-integration.datasource.url=jdbc:h2:mem:platform_config",
|
||||
"platform-integration.datasource.username=sa",
|
||||
"platform-integration.datasource.password=test-password",
|
||||
"platform-integration.token.max-age=PT2M",
|
||||
"platform-integration.token.teacher-claim-value=teacher",
|
||||
"platform-integration.token.student-claim-value=student",
|
||||
"platform-integration.cas.login-url=https://sso.example.edu/login",
|
||||
"platform-integration.cas.validate-url=https://sso.example.edu/p3/serviceValidate",
|
||||
"platform-integration.cas.callback-url=https://rmb.example.edu/api/v1/auth/cas/callback",
|
||||
"platform-integration.frontend.callback-url=https://rmb.example.edu/sso-callback");
|
||||
|
||||
@Test
|
||||
void createsNamedReadOnlyDatasourceAndJdbcClient() {
|
||||
contextRunner.run(context -> {
|
||||
HikariDataSource dataSource = context.getBean("platformReadOnlyDataSource", HikariDataSource.class);
|
||||
|
||||
assertThat(dataSource.isReadOnly()).isTrue();
|
||||
assertThat(context.getBean("platformJdbcClient", JdbcClient.class)).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(PlatformIntegrationProperties.class)
|
||||
static class PropertiesConfiguration {
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue