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