更新
This commit is contained in:
@@ -16,6 +16,21 @@
|
||||
<artifactId>common-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
|
||||
@@ -9,7 +9,10 @@ public record CurrentUserResponse(
|
||||
String provinceCode,
|
||||
String areaCode,
|
||||
String tenantId,
|
||||
String tenantPath,
|
||||
String deptId,
|
||||
List<String> roles
|
||||
String deptPath,
|
||||
List<String> roles,
|
||||
String clientType
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@ package com.k12study.api.auth.dto;
|
||||
public record LoginRequest(
|
||||
String username,
|
||||
String password,
|
||||
String mobile,
|
||||
String smsCode,
|
||||
String provinceCode,
|
||||
String areaCode,
|
||||
String tenantId
|
||||
String tenantId,
|
||||
String clientType
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.k12study.api.auth.remote;
|
||||
|
||||
/**
|
||||
* @description Auth 模块 HTTP 路径常量,供 Controller/Feign/网关白名单共用,避免字面量漂移
|
||||
* @filename AuthApiPaths.java
|
||||
* @author wangys
|
||||
* @copyright xyzh
|
||||
* @since 2026-04-17
|
||||
*/
|
||||
public final class AuthApiPaths {
|
||||
private AuthApiPaths() {
|
||||
}
|
||||
|
||||
public static final String BASE = "/auth";
|
||||
public static final String LOGIN = BASE + "/tokens";
|
||||
public static final String REFRESH = BASE + "/tokens/refresh";
|
||||
public static final String USERS_CURRENT = BASE + "/users/current";
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.k12study.api.auth.remote;
|
||||
|
||||
import com.k12study.api.auth.dto.CurrentUserResponse;
|
||||
import com.k12study.api.auth.dto.LoginRequest;
|
||||
import com.k12study.api.auth.dto.RefreshTokenRequest;
|
||||
import com.k12study.api.auth.dto.TokenResponse;
|
||||
import com.k12study.api.auth.remote.factory.RemoteAuthServiceFallbackFactory;
|
||||
import com.k12study.common.api.response.ApiResponse;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @description Auth 远程服务契约;微服务模式按 Nacos 服务名寻址,单体/本地通过 k12study.remote.auth.url 直连兜底
|
||||
* @filename AuthRemoteApi.java
|
||||
* @author wangys
|
||||
* @copyright xyzh
|
||||
* @since 2026-04-17
|
||||
*/
|
||||
@FeignClient(
|
||||
contextId = "remoteAuthService",
|
||||
value = "${k12study.remote.auth.service-name:k12study-auth}",
|
||||
url = "${k12study.remote.auth.url:}",
|
||||
path = AuthApiPaths.BASE,
|
||||
fallbackFactory = RemoteAuthServiceFallbackFactory.class)
|
||||
public interface AuthRemoteApi {
|
||||
|
||||
/** 账号密码 / 手机号+验证码登录,成功返回 access+refresh 双令牌 */
|
||||
@PostMapping("/tokens")
|
||||
ApiResponse<TokenResponse> login(@RequestBody LoginRequest request);
|
||||
|
||||
/** 一次一换:撤销旧 refresh、签发新 access+refresh;失败返回 401 */
|
||||
@PostMapping("/tokens/refresh")
|
||||
ApiResponse<TokenResponse> refresh(@RequestBody RefreshTokenRequest request);
|
||||
|
||||
/** 解析 Authorization 中的 access token 返回当前用户画像(含 roleCodes/clientType) */
|
||||
@GetMapping("/users/current")
|
||||
ApiResponse<CurrentUserResponse> currentUser(
|
||||
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.k12study.api.auth.remote.factory;
|
||||
|
||||
import com.k12study.api.auth.dto.CurrentUserResponse;
|
||||
import com.k12study.api.auth.dto.LoginRequest;
|
||||
import com.k12study.api.auth.dto.RefreshTokenRequest;
|
||||
import com.k12study.api.auth.dto.TokenResponse;
|
||||
import com.k12study.api.auth.remote.AuthRemoteApi;
|
||||
import com.k12study.common.api.response.ApiResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.cloud.openfeign.FallbackFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @description Auth Feign 熔断降级工厂;下游不可达时返回 503 + message 说明,前端按统一响应体处理
|
||||
* @filename RemoteAuthServiceFallbackFactory.java
|
||||
* @author wangys
|
||||
* @copyright xyzh
|
||||
* @since 2026-04-17
|
||||
*/
|
||||
@Component
|
||||
public class RemoteAuthServiceFallbackFactory implements FallbackFactory<AuthRemoteApi> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RemoteAuthServiceFallbackFactory.class);
|
||||
private static final int FALLBACK_CODE = 503;
|
||||
|
||||
@Override
|
||||
public AuthRemoteApi create(Throwable cause) {
|
||||
String reason = cause == null ? "unknown" : cause.getClass().getSimpleName() + ":" + cause.getMessage();
|
||||
log.warn("[auth-fallback] remote auth service degraded, cause={}", reason);
|
||||
String message = "auth 服务暂不可用,已触发降级:" + reason;
|
||||
return new AuthRemoteApi() {
|
||||
@Override
|
||||
public ApiResponse<TokenResponse> login(LoginRequest request) {
|
||||
return ApiResponse.failure(FALLBACK_CODE, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse<TokenResponse> refresh(RefreshTokenRequest request) {
|
||||
return ApiResponse.failure(FALLBACK_CODE, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse<CurrentUserResponse> currentUser(String authorization) {
|
||||
return ApiResponse.failure(FALLBACK_CODE, message);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,21 @@
|
||||
<artifactId>common-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.k12study.api.upms.dto;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record FileMetadataDto(
|
||||
String fileId,
|
||||
String mediaType,
|
||||
String objectKey,
|
||||
String fileName,
|
||||
String mimeType,
|
||||
Long fileSize,
|
||||
String fileHash,
|
||||
Integer durationMs,
|
||||
String uploadedBy,
|
||||
String tenantId,
|
||||
String tenantPath,
|
||||
Instant createdAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.k12study.api.upms.dto;
|
||||
|
||||
public record FileUploadRequestDto(
|
||||
String mediaType,
|
||||
String objectKey,
|
||||
String fileName,
|
||||
String mimeType,
|
||||
Long fileSize,
|
||||
String fileHash,
|
||||
Integer durationMs
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.k12study.api.upms.dto;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record InboxMessageDto(
|
||||
String messageId,
|
||||
String messageType,
|
||||
String bizType,
|
||||
String title,
|
||||
String content,
|
||||
String webJumpUrl,
|
||||
String readStatus,
|
||||
Instant readAt,
|
||||
Instant sendAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.k12study.api.upms.dto;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record MessageReadResultDto(
|
||||
String messageId,
|
||||
String readStatus,
|
||||
Instant readAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.k12study.api.upms.dto;
|
||||
|
||||
public record SchoolClassCourseDto(
|
||||
String classId,
|
||||
String courseId,
|
||||
String relationStatus
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.k12study.api.upms.dto;
|
||||
|
||||
public record SchoolClassDto(
|
||||
String classId,
|
||||
String classCode,
|
||||
String className,
|
||||
String gradeCode,
|
||||
String status,
|
||||
String tenantId,
|
||||
String deptId
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.k12study.api.upms.dto;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record SchoolClassMemberDto(
|
||||
String classId,
|
||||
String userId,
|
||||
String username,
|
||||
String displayName,
|
||||
String memberRole,
|
||||
String memberStatus,
|
||||
Instant joinedAt,
|
||||
Instant leftAt
|
||||
) {
|
||||
}
|
||||
@@ -10,4 +10,11 @@ public final class UpmsApiPaths {
|
||||
public static final String AREAS = BASE + "/areas";
|
||||
public static final String TENANTS = BASE + "/tenants";
|
||||
public static final String DEPARTMENTS = BASE + "/departments";
|
||||
public static final String CLASSES = BASE + "/classes";
|
||||
public static final String CLASS_MEMBERS = BASE + "/classes/{classId}/members";
|
||||
public static final String CLASS_COURSES = BASE + "/classes/{classId}/courses";
|
||||
public static final String FILE_UPLOAD = BASE + "/files/upload";
|
||||
public static final String FILE_BY_ID = BASE + "/files/{fileId}";
|
||||
public static final String MESSAGE_INBOX = BASE + "/messages/inbox";
|
||||
public static final String MESSAGE_READ = BASE + "/messages/{messageId}/read";
|
||||
}
|
||||
|
||||
@@ -3,19 +3,91 @@ package com.k12study.api.upms.remote;
|
||||
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.FileMetadataDto;
|
||||
import com.k12study.api.upms.dto.FileUploadRequestDto;
|
||||
import com.k12study.api.upms.dto.InboxMessageDto;
|
||||
import com.k12study.api.upms.dto.MessageReadResultDto;
|
||||
import com.k12study.api.upms.dto.RouteNodeDto;
|
||||
import com.k12study.api.upms.dto.SchoolClassCourseDto;
|
||||
import com.k12study.api.upms.dto.SchoolClassDto;
|
||||
import com.k12study.api.upms.dto.SchoolClassMemberDto;
|
||||
import com.k12study.api.upms.dto.TenantNodeDto;
|
||||
import com.k12study.api.upms.remote.factory.RemoteUpmsServiceFallbackFactory;
|
||||
import com.k12study.common.api.response.ApiResponse;
|
||||
import java.util.List;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
|
||||
/**
|
||||
* @description UPMS 远程服务契约;Controller 方法与此接口路径必须一一对应,启动时由 FeignContractValidator 校验
|
||||
* @filename UpmsRemoteApi.java
|
||||
* @author wangys
|
||||
* @copyright xyzh
|
||||
* @since 2026-04-17
|
||||
*/
|
||||
@FeignClient(
|
||||
contextId = "remoteUpmsService",
|
||||
value = "${k12study.remote.upms.service-name:k12study-upms}",
|
||||
url = "${k12study.remote.upms.url:}",
|
||||
path = UpmsApiPaths.BASE,
|
||||
fallbackFactory = RemoteUpmsServiceFallbackFactory.class)
|
||||
public interface UpmsRemoteApi {
|
||||
ApiResponse<List<RouteNodeDto>> routes();
|
||||
|
||||
ApiResponse<CurrentRouteUserDto> currentUser();
|
||||
@GetMapping("/routes")
|
||||
ApiResponse<List<RouteNodeDto>> routes(
|
||||
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization);
|
||||
|
||||
ApiResponse<List<AreaNodeDto>> areas();
|
||||
@GetMapping("/users/current")
|
||||
ApiResponse<CurrentRouteUserDto> currentUser(
|
||||
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization);
|
||||
|
||||
ApiResponse<List<TenantNodeDto>> tenants();
|
||||
@GetMapping("/areas")
|
||||
ApiResponse<List<AreaNodeDto>> areas(
|
||||
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization);
|
||||
|
||||
ApiResponse<List<DeptNodeDto>> departments();
|
||||
@GetMapping("/tenants")
|
||||
ApiResponse<List<TenantNodeDto>> tenants(
|
||||
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization);
|
||||
|
||||
@GetMapping("/departments")
|
||||
ApiResponse<List<DeptNodeDto>> departments(
|
||||
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization);
|
||||
|
||||
@GetMapping("/classes")
|
||||
ApiResponse<List<SchoolClassDto>> classes(
|
||||
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization);
|
||||
|
||||
@GetMapping("/classes/{classId}/members")
|
||||
ApiResponse<List<SchoolClassMemberDto>> classMembers(
|
||||
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization,
|
||||
@PathVariable("classId") String classId);
|
||||
|
||||
@GetMapping("/classes/{classId}/courses")
|
||||
ApiResponse<List<SchoolClassCourseDto>> classCourses(
|
||||
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization,
|
||||
@PathVariable("classId") String classId);
|
||||
|
||||
@PostMapping("/files/upload")
|
||||
ApiResponse<FileMetadataDto> uploadFile(
|
||||
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization,
|
||||
@RequestBody FileUploadRequestDto request);
|
||||
|
||||
@GetMapping("/files/{fileId}")
|
||||
ApiResponse<FileMetadataDto> fileById(
|
||||
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization,
|
||||
@PathVariable("fileId") String fileId);
|
||||
|
||||
@GetMapping("/messages/inbox")
|
||||
ApiResponse<List<InboxMessageDto>> inboxMessages(
|
||||
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization);
|
||||
|
||||
@PostMapping("/messages/{messageId}/read")
|
||||
ApiResponse<MessageReadResultDto> readMessage(
|
||||
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization,
|
||||
@PathVariable("messageId") String messageId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.k12study.api.upms.remote.factory;
|
||||
|
||||
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.FileMetadataDto;
|
||||
import com.k12study.api.upms.dto.FileUploadRequestDto;
|
||||
import com.k12study.api.upms.dto.InboxMessageDto;
|
||||
import com.k12study.api.upms.dto.MessageReadResultDto;
|
||||
import com.k12study.api.upms.dto.RouteNodeDto;
|
||||
import com.k12study.api.upms.dto.SchoolClassCourseDto;
|
||||
import com.k12study.api.upms.dto.SchoolClassDto;
|
||||
import com.k12study.api.upms.dto.SchoolClassMemberDto;
|
||||
import com.k12study.api.upms.dto.TenantNodeDto;
|
||||
import com.k12study.api.upms.remote.UpmsRemoteApi;
|
||||
import com.k12study.common.api.response.ApiResponse;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.cloud.openfeign.FallbackFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @description UPMS Feign 熔断降级工厂;下游不可达时返回 503 + message 说明,避免阻塞调用方,前端按统一响应体处理
|
||||
* @filename RemoteUpmsServiceFallbackFactory.java
|
||||
* @author wangys
|
||||
* @copyright xyzh
|
||||
* @since 2026-04-17
|
||||
*/
|
||||
@Component
|
||||
public class RemoteUpmsServiceFallbackFactory implements FallbackFactory<UpmsRemoteApi> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RemoteUpmsServiceFallbackFactory.class);
|
||||
private static final int FALLBACK_CODE = 503;
|
||||
|
||||
@Override
|
||||
public UpmsRemoteApi create(Throwable cause) {
|
||||
String reason = cause == null ? "unknown" : cause.getClass().getSimpleName() + ":" + cause.getMessage();
|
||||
log.warn("[upms-fallback] remote upms service degraded, cause={}", reason);
|
||||
String message = "upms 服务暂不可用,已触发降级:" + reason;
|
||||
return new UpmsRemoteApi() {
|
||||
@Override
|
||||
public ApiResponse<List<RouteNodeDto>> routes(String authorization) {
|
||||
return ApiResponse.failure(FALLBACK_CODE, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse<CurrentRouteUserDto> currentUser(String authorization) {
|
||||
return ApiResponse.failure(FALLBACK_CODE, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse<List<AreaNodeDto>> areas(String authorization) {
|
||||
return ApiResponse.failure(FALLBACK_CODE, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse<List<TenantNodeDto>> tenants(String authorization) {
|
||||
return ApiResponse.failure(FALLBACK_CODE, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse<List<DeptNodeDto>> departments(String authorization) {
|
||||
return ApiResponse.failure(FALLBACK_CODE, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse<List<SchoolClassDto>> classes(String authorization) {
|
||||
return ApiResponse.failure(FALLBACK_CODE, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse<List<SchoolClassMemberDto>> classMembers(String authorization, String classId) {
|
||||
return ApiResponse.failure(FALLBACK_CODE, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse<List<SchoolClassCourseDto>> classCourses(String authorization, String classId) {
|
||||
return ApiResponse.failure(FALLBACK_CODE, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse<FileMetadataDto> uploadFile(String authorization, FileUploadRequestDto request) {
|
||||
return ApiResponse.failure(FALLBACK_CODE, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse<FileMetadataDto> fileById(String authorization, String fileId) {
|
||||
return ApiResponse.failure(FALLBACK_CODE, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse<List<InboxMessageDto>> inboxMessages(String authorization) {
|
||||
return ApiResponse.failure(FALLBACK_CODE, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiResponse<MessageReadResultDto> readMessage(String authorization, String messageId) {
|
||||
return ApiResponse.failure(FALLBACK_CODE, message);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user