This commit is contained in:
2026-04-14 16:27:47 +08:00
commit 4b38a4c952
134 changed files with 7478 additions and 0 deletions

11
backend/.env.example Normal file
View File

@@ -0,0 +1,11 @@
K12STUDY_DB_HOST=localhost
K12STUDY_DB_PORT=5432
K12STUDY_DB_NAME=k12study
K12STUDY_DB_USER=k12study
K12STUDY_DB_PASSWORD=k12study
K12STUDY_REDIS_HOST=localhost
K12STUDY_REDIS_PORT=6379
K12STUDY_REDIS_PASSWORD=
AI_CLIENT_BASE_URL=http://localhost:9000

23
backend/ai-client/pom.xml Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>
<artifactId>ai-client</artifactId>
<dependencies>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>api-ai</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,25 @@
package com.k12study.aiclient.client;
import com.k12study.api.ai.dto.AiHealthDto;
import org.springframework.web.client.RestClient;
public class HttpPythonAiClient implements PythonAiClient {
private final RestClient restClient;
public HttpPythonAiClient(RestClient restClient) {
this.restClient = restClient;
}
@Override
public AiHealthDto health() {
try {
AiHealthDto response = restClient.get()
.uri("/health")
.retrieve()
.body(AiHealthDto.class);
return response == null ? new AiHealthDto("python-ai", "UNKNOWN", "0.1.0") : response;
} catch (Exception exception) {
return new AiHealthDto("python-ai", "UNREACHABLE", "0.1.0");
}
}
}

View File

@@ -0,0 +1,7 @@
package com.k12study.aiclient.client;
import com.k12study.api.ai.dto.AiHealthDto;
public interface PythonAiClient {
AiHealthDto health();
}

View File

@@ -0,0 +1,23 @@
package com.k12study.aiclient.config;
import com.k12study.aiclient.client.HttpPythonAiClient;
import com.k12study.aiclient.client.PythonAiClient;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
@Configuration
@EnableConfigurationProperties(AiClientProperties.class)
public class AiClientAutoConfiguration {
@Bean
public RestClient aiRestClient(AiClientProperties properties) {
return RestClient.builder().baseUrl(properties.getBaseUrl()).build();
}
@Bean
public PythonAiClient pythonAiClient(RestClient aiRestClient) {
return new HttpPythonAiClient(aiRestClient);
}
}

View File

@@ -0,0 +1,16 @@
package com.k12study.aiclient.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "ai.client")
public class AiClientProperties {
private String baseUrl = "http://localhost:9000";
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>api-ai</artifactId>
<dependencies>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,8 @@
package com.k12study.api.ai.dto;
public record AiHealthDto(
String name,
String status,
String version
) {
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>api-auth</artifactId>
<dependencies>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,15 @@
package com.k12study.api.auth.dto;
import java.util.List;
public record CurrentUserResponse(
String userId,
String username,
String displayName,
String provinceCode,
String areaCode,
String tenantId,
String deptId,
List<String> roles
) {
}

View File

@@ -0,0 +1,10 @@
package com.k12study.api.auth.dto;
public record LoginRequest(
String username,
String password,
String provinceCode,
String areaCode,
String tenantId
) {
}

View File

@@ -0,0 +1,9 @@
package com.k12study.api.auth.dto;
public record TokenResponse(
String accessToken,
String refreshToken,
String tokenType,
long expiresIn
) {
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>api-upms</artifactId>
<dependencies>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,12 @@
package com.k12study.api.upms.dto;
import java.util.List;
public record AreaNodeDto(
String areaCode,
String areaName,
String areaLevel,
String provinceCode,
List<AreaNodeDto> children
) {
}

View File

@@ -0,0 +1,13 @@
package com.k12study.api.upms.dto;
import java.util.List;
public record CurrentRouteUserDto(
String userId,
String username,
String displayName,
String tenantId,
String deptId,
List<String> permissionCodes
) {
}

View File

@@ -0,0 +1,13 @@
package com.k12study.api.upms.dto;
import java.util.List;
public record DeptNodeDto(
String deptId,
String deptName,
String deptType,
String tenantId,
String deptPath,
List<DeptNodeDto> children
) {
}

View File

@@ -0,0 +1,6 @@
package com.k12study.api.upms.dto;
public enum LayoutType {
DEFAULT,
SIDEBAR
}

View File

@@ -0,0 +1,11 @@
package com.k12study.api.upms.dto;
import java.util.List;
public record RouteMetaDto(
String title,
String icon,
List<String> permissionCodes,
boolean hidden
) {
}

View File

@@ -0,0 +1,14 @@
package com.k12study.api.upms.dto;
import java.util.List;
public record RouteNodeDto(
String id,
String path,
String name,
String component,
LayoutType layout,
RouteMetaDto meta,
List<RouteNodeDto> children
) {
}

View File

@@ -0,0 +1,14 @@
package com.k12study.api.upms.dto;
import java.util.List;
public record TenantNodeDto(
String tenantId,
String tenantName,
String tenantType,
String provinceCode,
String areaCode,
String tenantPath,
List<TenantNodeDto> children
) {
}

21
backend/apis/pom.xml Normal file
View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>
<artifactId>apis-parent</artifactId>
<packaging>pom</packaging>
<modules>
<module>api-auth</module>
<module>api-upms</module>
<module>api-ai</module>
</modules>
</project>

50
backend/auth/pom.xml Normal file
View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>
<artifactId>auth</artifactId>
<dependencies>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-web</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-security</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-redis</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>api-auth</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,11 @@
package com.k12study.auth;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = {"com.k12study.auth", "com.k12study.common"})
public class AuthApplication {
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}

View File

@@ -0,0 +1,14 @@
package com.k12study.auth.config;
import com.k12study.auth.AuthApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
@Configuration
@ComponentScan(
basePackages = "com.k12study.auth",
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = AuthApplication.class)
)
public class AuthModuleConfiguration {
}

View File

@@ -0,0 +1,40 @@
package com.k12study.auth.controller;
import com.k12study.api.auth.dto.CurrentUserResponse;
import com.k12study.api.auth.dto.LoginRequest;
import com.k12study.api.auth.dto.TokenResponse;
import com.k12study.auth.service.AuthService;
import com.k12study.common.api.response.ApiResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/auth")
public class AuthController {
private final AuthService authService;
public AuthController(AuthService authService) {
this.authService = authService;
}
@PostMapping("/login")
public ApiResponse<TokenResponse> login(@RequestBody LoginRequest request) {
return ApiResponse.success("登录成功", authService.login(request));
}
@PostMapping("/refresh")
public ApiResponse<TokenResponse> refresh(@RequestParam("refreshToken") String refreshToken) {
return ApiResponse.success("刷新成功", authService.refresh(refreshToken));
}
@GetMapping("/current-user")
public ApiResponse<CurrentUserResponse> currentUser(
@RequestHeader(value = "Authorization", required = false) String authorizationHeader) {
return ApiResponse.success(authService.currentUser(authorizationHeader));
}
}

View File

@@ -0,0 +1,66 @@
package com.k12study.auth.service;
import com.k12study.api.auth.dto.CurrentUserResponse;
import com.k12study.api.auth.dto.LoginRequest;
import com.k12study.api.auth.dto.TokenResponse;
import com.k12study.common.security.context.RequestUserContextHolder;
import com.k12study.common.security.jwt.JwtTokenProvider;
import com.k12study.common.security.jwt.JwtUserPrincipal;
import java.util.List;
import org.springframework.stereotype.Service;
@Service
public class AuthService {
private final JwtTokenProvider jwtTokenProvider;
public AuthService(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
public TokenResponse login(LoginRequest request) {
String username = request.username() == null || request.username().isBlank() ? "admin" : request.username();
JwtUserPrincipal principal = new JwtUserPrincipal(
"U10001",
username,
"K12Study 管理员",
request.tenantId() == null || request.tenantId().isBlank() ? "SCH-HQ" : request.tenantId(),
"DEPT-HQ-ADMIN"
);
String accessToken = jwtTokenProvider.createAccessToken(principal);
String refreshToken = jwtTokenProvider.createAccessToken(principal);
return new TokenResponse(accessToken, refreshToken, "Bearer", 12 * 60 * 60);
}
public TokenResponse refresh(String refreshToken) {
JwtUserPrincipal principal = jwtTokenProvider.parse(refreshToken);
String accessToken = jwtTokenProvider.createAccessToken(principal);
return new TokenResponse(accessToken, refreshToken, "Bearer", 12 * 60 * 60);
}
public CurrentUserResponse currentUser(String authorizationHeader) {
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
JwtUserPrincipal principal = jwtTokenProvider.parse(authorizationHeader.substring("Bearer ".length()));
return new CurrentUserResponse(
principal.userId(),
principal.username(),
principal.displayName(),
"330000",
"330100",
principal.tenantId(),
principal.deptId(),
List.of("SUPER_ADMIN", "ORG_ADMIN")
);
}
var context = RequestUserContextHolder.get();
return new CurrentUserResponse(
context == null ? "U10001" : context.userId(),
context == null ? "admin" : context.username(),
context == null ? "K12Study 管理员" : context.displayName(),
"330000",
"330100",
context == null ? "SCH-HQ" : context.tenantId(),
context == null ? "DEPT-HQ-ADMIN" : context.deptId(),
List.of("SUPER_ADMIN", "ORG_ADMIN")
);
}
}

View File

@@ -0,0 +1,28 @@
server:
port: 8081
spring:
application:
name: k12study-auth
data:
redis:
host: ${K12STUDY_REDIS_HOST:localhost}
port: ${K12STUDY_REDIS_PORT:6379}
password: ${K12STUDY_REDIS_PASSWORD:}
management:
health:
redis:
enabled: false
endpoints:
web:
exposure:
include: health,info
auth:
enabled: true
gateway-mode: true
whitelist:
- /auth/login
- /auth/refresh
- /actuator/**

41
backend/boot-dev/pom.xml Normal file
View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>
<artifactId>boot-dev</artifactId>
<dependencies>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>auth</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>upms</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>ai-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,16 @@
package com.k12study.bootdev;
import com.k12study.aiclient.config.AiClientAutoConfiguration;
import com.k12study.auth.config.AuthModuleConfiguration;
import com.k12study.upms.config.UpmsModuleConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
@SpringBootApplication(scanBasePackages = {"com.k12study.bootdev", "com.k12study.common"})
@Import({AuthModuleConfiguration.class, UpmsModuleConfiguration.class, AiClientAutoConfiguration.class})
public class BootDevApplication {
public static void main(String[] args) {
SpringApplication.run(BootDevApplication.class, args);
}
}

View File

@@ -0,0 +1,39 @@
server:
port: 8088
servlet:
context-path: /api
spring:
application:
name: k12study-boot-dev
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
- com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
data:
redis:
host: ${K12STUDY_REDIS_HOST:localhost}
port: ${K12STUDY_REDIS_PORT:6379}
password: ${K12STUDY_REDIS_PASSWORD:}
management:
health:
redis:
enabled: false
endpoints:
web:
exposure:
include: health,info
auth:
enabled: true
gateway-mode: false
whitelist:
- /auth/login
- /auth/refresh
- /actuator/**
ai:
client:
base-url: http://localhost:9000

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>common-api</artifactId>
<dependencies>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,31 @@
package com.k12study.common.api.response;
import com.k12study.common.core.utils.TraceIdHolder;
import lombok.Getter;
@Getter
public class ApiResponse<T> {
private final int code;
private final String message;
private final T data;
private final String traceId;
private ApiResponse(int code, String message, T data, String traceId) {
this.code = code;
this.message = message;
this.data = data;
this.traceId = traceId;
}
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(0, "OK", data, TraceIdHolder.getOrCreate());
}
public static <T> ApiResponse<T> success(String message, T data) {
return new ApiResponse<>(0, message, data, TraceIdHolder.getOrCreate());
}
public static <T> ApiResponse<T> failure(int code, String message) {
return new ApiResponse<>(code, message, null, TraceIdHolder.getOrCreate());
}
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>common-core</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,13 @@
package com.k12study.common.core.constants;
public final class SecurityConstants {
public static final String TRACE_ID = "X-Trace-Id";
public static final String HEADER_USER_ID = "X-User-Id";
public static final String HEADER_USERNAME = "X-Username";
public static final String HEADER_DISPLAY_NAME = "X-Display-Name";
public static final String HEADER_TENANT_ID = "X-Tenant-Id";
public static final String HEADER_DEPT_ID = "X-Dept-Id";
private SecurityConstants() {
}
}

View File

@@ -0,0 +1,11 @@
package com.k12study.common.core.domain;
public record RouteKey(
String provinceCode,
String areaCode,
String tenantId,
String tenantPath,
String deptId,
String deptPath
) {
}

View File

@@ -0,0 +1,27 @@
package com.k12study.common.core.utils;
import java.util.UUID;
public final class TraceIdHolder {
private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<>();
private TraceIdHolder() {
}
public static String getOrCreate() {
String value = TRACE_ID.get();
if (value == null || value.isBlank()) {
value = UUID.randomUUID().toString().replace("-", "");
TRACE_ID.set(value);
}
return value;
}
public static void set(String traceId) {
TRACE_ID.set(traceId);
}
public static void clear() {
TRACE_ID.remove();
}
}

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>common-mybatis</artifactId>
<dependencies>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,7 @@
package com.k12study.common.mybatis.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfiguration {
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>common-redis</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,7 @@
package com.k12study.common.redis.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedisConfiguration {
}

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>common-security</artifactId>
<dependencies>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,82 @@
package com.k12study.common.security.config;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "auth")
public class AuthProperties {
private boolean enabled = true;
private boolean gatewayMode = false;
private String tokenHeader = "Authorization";
private String tokenPrefix = "Bearer ";
private String secret = "k12study-dev-secret-k12study-dev-secret";
private Duration accessTokenTtl = Duration.ofHours(12);
private Duration refreshTokenTtl = Duration.ofDays(7);
private List<String> whitelist = new ArrayList<>(List.of("/actuator/**", "/auth/login", "/auth/refresh"));
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isGatewayMode() {
return gatewayMode;
}
public void setGatewayMode(boolean gatewayMode) {
this.gatewayMode = gatewayMode;
}
public String getTokenHeader() {
return tokenHeader;
}
public void setTokenHeader(String tokenHeader) {
this.tokenHeader = tokenHeader;
}
public String getTokenPrefix() {
return tokenPrefix;
}
public void setTokenPrefix(String tokenPrefix) {
this.tokenPrefix = tokenPrefix;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public Duration getAccessTokenTtl() {
return accessTokenTtl;
}
public void setAccessTokenTtl(Duration accessTokenTtl) {
this.accessTokenTtl = accessTokenTtl;
}
public Duration getRefreshTokenTtl() {
return refreshTokenTtl;
}
public void setRefreshTokenTtl(Duration refreshTokenTtl) {
this.refreshTokenTtl = refreshTokenTtl;
}
public List<String> getWhitelist() {
return whitelist;
}
public void setWhitelist(List<String> whitelist) {
this.whitelist = whitelist;
}
}

View File

@@ -0,0 +1,16 @@
package com.k12study.common.security.config;
import com.k12study.common.security.jwt.JwtTokenProvider;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(AuthProperties.class)
public class SecurityAutoConfiguration {
@Bean
public JwtTokenProvider jwtTokenProvider(AuthProperties authProperties) {
return new JwtTokenProvider(authProperties);
}
}

View File

@@ -0,0 +1,10 @@
package com.k12study.common.security.context;
public record RequestUserContext(
String userId,
String username,
String displayName,
String tenantId,
String deptId
) {
}

View File

@@ -0,0 +1,20 @@
package com.k12study.common.security.context;
public final class RequestUserContextHolder {
private static final ThreadLocal<RequestUserContext> CONTEXT = new ThreadLocal<>();
private RequestUserContextHolder() {
}
public static void set(RequestUserContext context) {
CONTEXT.set(context);
}
public static RequestUserContext get() {
return CONTEXT.get();
}
public static void clear() {
CONTEXT.remove();
}
}

View File

@@ -0,0 +1,49 @@
package com.k12study.common.security.jwt;
import com.k12study.common.security.config.AuthProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Date;
import javax.crypto.SecretKey;
public class JwtTokenProvider {
private final AuthProperties authProperties;
private final SecretKey secretKey;
public JwtTokenProvider(AuthProperties authProperties) {
this.authProperties = authProperties;
this.secretKey = Keys.hmacShaKeyFor(authProperties.getSecret().getBytes(StandardCharsets.UTF_8));
}
public String createAccessToken(JwtUserPrincipal principal) {
Instant now = Instant.now();
return Jwts.builder()
.subject(principal.userId())
.claim("username", principal.username())
.claim("displayName", principal.displayName())
.claim("tenantId", principal.tenantId())
.claim("deptId", principal.deptId())
.issuedAt(Date.from(now))
.expiration(Date.from(now.plus(authProperties.getAccessTokenTtl())))
.signWith(secretKey)
.compact();
}
public JwtUserPrincipal parse(String token) {
Claims claims = Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload();
return new JwtUserPrincipal(
claims.getSubject(),
claims.get("username", String.class),
claims.get("displayName", String.class),
claims.get("tenantId", String.class),
claims.get("deptId", String.class)
);
}
}

View File

@@ -0,0 +1,10 @@
package com.k12study.common.security.jwt;
public record JwtUserPrincipal(
String userId,
String username,
String displayName,
String tenantId,
String deptId
) {
}

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>common-web</artifactId>
<dependencies>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-security</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,40 @@
package com.k12study.common.web.config;
import com.k12study.common.core.constants.SecurityConstants;
import com.k12study.common.core.utils.TraceIdHolder;
import com.k12study.common.security.context.RequestUserContext;
import com.k12study.common.security.context.RequestUserContextHolder;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.OncePerRequestFilter;
@Configuration
public class CommonWebMvcConfiguration extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String traceId = request.getHeader(SecurityConstants.TRACE_ID);
TraceIdHolder.set(traceId == null || traceId.isBlank() ? TraceIdHolder.getOrCreate() : traceId);
RequestUserContextHolder.set(new RequestUserContext(
request.getHeader(SecurityConstants.HEADER_USER_ID),
request.getHeader(SecurityConstants.HEADER_USERNAME),
request.getHeader(SecurityConstants.HEADER_DISPLAY_NAME),
request.getHeader(SecurityConstants.HEADER_TENANT_ID),
request.getHeader(SecurityConstants.HEADER_DEPT_ID)
));
response.setHeader(SecurityConstants.TRACE_ID, TraceIdHolder.getOrCreate());
filterChain.doFilter(request, response);
} finally {
TraceIdHolder.clear();
RequestUserContextHolder.clear();
}
}
}

View File

@@ -0,0 +1,14 @@
package com.k12study.common.web.exception;
public class BizException extends RuntimeException {
private final int code;
public BizException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,19 @@
package com.k12study.common.web.exception;
import com.k12study.common.api.response.ApiResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BizException.class)
public ApiResponse<Void> handleBizException(BizException exception) {
return ApiResponse.failure(exception.getCode(), exception.getMessage());
}
@ExceptionHandler(Exception.class)
public ApiResponse<Void> handleException(Exception exception) {
return ApiResponse.failure(500, exception.getMessage());
}
}

24
backend/common/pom.xml Normal file
View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>
<artifactId>common-parent</artifactId>
<packaging>pom</packaging>
<modules>
<module>common-api</module>
<module>common-core</module>
<module>common-web</module>
<module>common-security</module>
<module>common-mybatis</module>
<module>common-redis</module>
</modules>
</project>

35
backend/gateway/pom.xml Normal file
View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>
<artifactId>gateway</artifactId>
<dependencies>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-security</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,11 @@
package com.k12study.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = {"com.k12study.gateway", "com.k12study.common"})
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

View File

@@ -0,0 +1,75 @@
package com.k12study.gateway.filter;
import com.k12study.common.core.constants.SecurityConstants;
import com.k12study.common.security.config.AuthProperties;
import com.k12study.common.security.jwt.JwtTokenProvider;
import com.k12study.common.security.jwt.JwtUserPrincipal;
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class JwtRelayFilter implements GlobalFilter, Ordered {
private final AuthProperties authProperties;
private final JwtTokenProvider jwtTokenProvider;
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
public JwtRelayFilter(AuthProperties authProperties, JwtTokenProvider jwtTokenProvider) {
this.authProperties = authProperties;
this.jwtTokenProvider = jwtTokenProvider;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
if (!authProperties.isEnabled() || matches(path, authProperties.getWhitelist())) {
return chain.filter(exchange);
}
String authorization = exchange.getRequest().getHeaders().getFirst(authProperties.getTokenHeader());
if (authorization == null || !authorization.startsWith(authProperties.getTokenPrefix())) {
return unauthorized(exchange, "Missing token");
}
try {
String token = authorization.substring(authProperties.getTokenPrefix().length());
JwtUserPrincipal principal = jwtTokenProvider.parse(token);
var mutatedRequest = exchange.getRequest().mutate()
.header(SecurityConstants.HEADER_USER_ID, principal.userId())
.header(SecurityConstants.HEADER_USERNAME, principal.username())
.header(SecurityConstants.HEADER_DISPLAY_NAME, principal.displayName())
.header(SecurityConstants.HEADER_TENANT_ID, principal.tenantId())
.header(SecurityConstants.HEADER_DEPT_ID, principal.deptId())
.build();
return chain.filter(exchange.mutate().request(mutatedRequest).build());
} catch (Exception exception) {
return unauthorized(exchange, "Invalid token");
}
}
@Override
public int getOrder() {
return -100;
}
private boolean matches(String path, List<String> patterns) {
return patterns.stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
}
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
byte[] body = ("{\"code\":401,\"message\":\"" + message + "\",\"data\":null}").getBytes();
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse()
.bufferFactory()
.wrap(body)));
}
}

View File

@@ -0,0 +1,34 @@
server:
port: 8080
spring:
application:
name: k12study-gateway
cloud:
gateway:
routes:
- id: auth
uri: http://localhost:8081
predicates:
- Path=/api/auth/**
filters:
- StripPrefix=1
- id: upms
uri: http://localhost:8082
predicates:
- Path=/api/upms/**
filters:
- StripPrefix=1
management:
endpoints:
web:
exposure:
include: health,info
auth:
enabled: true
whitelist:
- /api/auth/login
- /api/auth/refresh
- /actuator/**

115
backend/pom.xml Normal file
View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>k12study-backend</name>
<modules>
<module>common</module>
<module>apis</module>
<module>ai-client</module>
<module>auth</module>
<module>upms</module>
<module>gateway</module>
<module>boot-dev</module>
</modules>
<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>${java.version}</maven.compiler.release>
<spring.boot.version>3.3.5</spring.boot.version>
<spring.cloud.version>2023.0.3</spring.cloud.version>
<mybatis.plus.version>3.5.7</mybatis.plus.version>
<postgresql.version>42.7.4</postgresql.version>
<jjwt.version>0.12.6</jjwt.version>
<springdoc.version>2.6.0</springdoc.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>${maven.compiler.release}</release>
<encoding>${project.build.sourceEncoding}</encoding>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
</plugin>
</plugins>
</pluginManagement>
</build>
<repositories>
<repository>
<id>aliyun-public</id>
<name>aliyun-public</name>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
</repositories>
</project>

View File

@@ -0,0 +1,14 @@
# Python AI Placeholder
This service is the placeholder for OCR, grading and speech evaluation capabilities.
## Run
```bash
pip install -r requirements.txt
uvicorn app.main:app --reload --port 9000
```
## Endpoints
- `GET /health`

View File

@@ -0,0 +1,13 @@
from fastapi import FastAPI
app = FastAPI(title="K12Study Python AI", version="0.1.0")
@app.get("/health")
def health():
return {
"name": "python-ai",
"status": "UP",
"version": "0.1.0",
}

View File

@@ -0,0 +1,2 @@
fastapi==0.115.12
uvicorn==0.34.0

50
backend/upms/pom.xml Normal file
View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.k12study</groupId>
<artifactId>k12study-backend</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>
<artifactId>upms</artifactId>
<dependencies>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-web</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-security</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>common-mybatis</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.k12study</groupId>
<artifactId>api-upms</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,11 @@
package com.k12study.upms;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = {"com.k12study.upms", "com.k12study.common"})
public class UpmsApplication {
public static void main(String[] args) {
SpringApplication.run(UpmsApplication.class, args);
}
}

View File

@@ -0,0 +1,14 @@
package com.k12study.upms.config;
import com.k12study.upms.UpmsApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
@Configuration
@ComponentScan(
basePackages = "com.k12study.upms",
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = UpmsApplication.class)
)
public class UpmsModuleConfiguration {
}

View File

@@ -0,0 +1,48 @@
package com.k12study.upms.controller;
import com.k12study.api.upms.dto.AreaNodeDto;
import com.k12study.api.upms.dto.CurrentRouteUserDto;
import com.k12study.api.upms.dto.DeptNodeDto;
import com.k12study.api.upms.dto.RouteNodeDto;
import com.k12study.api.upms.dto.TenantNodeDto;
import com.k12study.common.api.response.ApiResponse;
import com.k12study.upms.service.UpmsQueryService;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/upms")
public class UpmsController {
private final UpmsQueryService upmsQueryService;
public UpmsController(UpmsQueryService upmsQueryService) {
this.upmsQueryService = upmsQueryService;
}
@GetMapping("/routes")
public ApiResponse<List<RouteNodeDto>> routes() {
return ApiResponse.success(upmsQueryService.routes());
}
@GetMapping("/areas/tree")
public ApiResponse<List<AreaNodeDto>> areas() {
return ApiResponse.success(upmsQueryService.areas());
}
@GetMapping("/tenants/tree")
public ApiResponse<List<TenantNodeDto>> tenants() {
return ApiResponse.success(upmsQueryService.tenants());
}
@GetMapping("/depts/tree")
public ApiResponse<List<DeptNodeDto>> departments() {
return ApiResponse.success(upmsQueryService.departments());
}
@GetMapping("/current-user")
public ApiResponse<CurrentRouteUserDto> currentUser() {
return ApiResponse.success(upmsQueryService.currentUser());
}
}

View File

@@ -0,0 +1,10 @@
package com.k12study.upms.domain;
public record SysArea(
String areaCode,
String parentCode,
String areaName,
String areaLevel,
String provinceCode
) {
}

View File

@@ -0,0 +1,11 @@
package com.k12study.upms.domain;
public record SysDept(
String deptId,
String parentDeptId,
String tenantId,
String deptName,
String deptType,
String deptPath
) {
}

View File

@@ -0,0 +1,12 @@
package com.k12study.upms.domain;
public record SysTenant(
String tenantId,
String parentTenantId,
String tenantName,
String tenantType,
String provinceCode,
String areaCode,
String tenantPath
) {
}

View File

@@ -0,0 +1,111 @@
package com.k12study.upms.service;
import com.k12study.api.upms.dto.AreaNodeDto;
import com.k12study.api.upms.dto.CurrentRouteUserDto;
import com.k12study.api.upms.dto.DeptNodeDto;
import com.k12study.api.upms.dto.LayoutType;
import com.k12study.api.upms.dto.RouteMetaDto;
import com.k12study.api.upms.dto.RouteNodeDto;
import com.k12study.api.upms.dto.TenantNodeDto;
import com.k12study.common.security.context.RequestUserContextHolder;
import java.util.List;
import org.springframework.stereotype.Service;
@Service
public class UpmsQueryService {
public List<RouteNodeDto> routes() {
return List.of(
new RouteNodeDto(
"dashboard",
"/",
"dashboard",
"dashboard",
LayoutType.SIDEBAR,
new RouteMetaDto("控制台", "layout-dashboard", List.of("dashboard:view"), false),
List.of()
),
new RouteNodeDto(
"tenant-management",
"/tenant",
"tenant-management",
"tenant",
LayoutType.SIDEBAR,
new RouteMetaDto("租户组织", "building-2", List.of("tenant:view"), false),
List.of()
)
);
}
public List<AreaNodeDto> areas() {
return List.of(
new AreaNodeDto(
"330000",
"浙江省",
"province",
"330000",
List.of(
new AreaNodeDto("330100", "杭州市", "city", "330000", List.of())
)
)
);
}
public List<TenantNodeDto> tenants() {
return List.of(
new TenantNodeDto(
"SCH-HQ",
"K12Study 总校",
"head_school",
"330000",
"330100",
"/SCH-HQ/",
List.of(
new TenantNodeDto(
"SCH-ZJ-HZ-01",
"杭州分校",
"city_school",
"330000",
"330100",
"/SCH-HQ/SCH-ZJ-HZ-01/",
List.of()
)
)
)
);
}
public List<DeptNodeDto> departments() {
return List.of(
new DeptNodeDto(
"DEPT-HQ",
"总校教学部",
"grade",
"SCH-HQ",
"/DEPT-HQ/",
List.of(
new DeptNodeDto(
"DEPT-HQ-MATH",
"数学学科组",
"subject",
"SCH-HQ",
"/DEPT-HQ/DEPT-HQ-MATH/",
List.of()
)
)
)
);
}
public CurrentRouteUserDto currentUser() {
var context = RequestUserContextHolder.get();
return new CurrentRouteUserDto(
context == null ? "U10001" : context.userId(),
context == null ? "admin" : context.username(),
context == null ? "K12Study 管理员" : context.displayName(),
context == null ? "SCH-HQ" : context.tenantId(),
context == null ? "DEPT-HQ-ADMIN" : context.deptId(),
List.of("dashboard:view", "tenant:view", "dept:view")
);
}
}

View File

@@ -0,0 +1,23 @@
server:
port: 8082
spring:
application:
name: k12study-upms
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
- com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
management:
endpoints:
web:
exposure:
include: health,info
auth:
enabled: true
gateway-mode: true
whitelist:
- /actuator/**