更新
This commit is contained in:
@@ -3,16 +3,34 @@ 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.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.UpmsApiPaths;
|
||||
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.PathVariable;
|
||||
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.RestController;
|
||||
|
||||
/**
|
||||
* @description UPMS HTTP 入口;路由/组织/班级/文件/站内信等聚合查询,全部经 UpmsQueryService 执行租户隔离后返回
|
||||
* @filename UpmsController.java
|
||||
* @author wangys
|
||||
* @copyright xyzh
|
||||
* @since 2026-04-17
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(UpmsApiPaths.BASE)
|
||||
public class UpmsController {
|
||||
@@ -23,27 +41,79 @@ public class UpmsController {
|
||||
}
|
||||
|
||||
@GetMapping("/routes")
|
||||
public ApiResponse<List<RouteNodeDto>> routes() {
|
||||
return ApiResponse.success(upmsQueryService.routes());
|
||||
public ApiResponse<List<RouteNodeDto>> routes(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorizationHeader) {
|
||||
return ApiResponse.success(upmsQueryService.routes(authorizationHeader));
|
||||
}
|
||||
|
||||
@GetMapping("/areas")
|
||||
public ApiResponse<List<AreaNodeDto>> areas() {
|
||||
return ApiResponse.success(upmsQueryService.areas());
|
||||
public ApiResponse<List<AreaNodeDto>> areas(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorizationHeader) {
|
||||
return ApiResponse.success(upmsQueryService.areas(authorizationHeader));
|
||||
}
|
||||
|
||||
@GetMapping("/tenants")
|
||||
public ApiResponse<List<TenantNodeDto>> tenants() {
|
||||
return ApiResponse.success(upmsQueryService.tenants());
|
||||
public ApiResponse<List<TenantNodeDto>> tenants(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorizationHeader) {
|
||||
return ApiResponse.success(upmsQueryService.tenants(authorizationHeader));
|
||||
}
|
||||
|
||||
@GetMapping("/departments")
|
||||
public ApiResponse<List<DeptNodeDto>> departments() {
|
||||
return ApiResponse.success(upmsQueryService.departments());
|
||||
public ApiResponse<List<DeptNodeDto>> departments(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorizationHeader) {
|
||||
return ApiResponse.success(upmsQueryService.departments(authorizationHeader));
|
||||
}
|
||||
|
||||
@GetMapping("/users/current")
|
||||
public ApiResponse<CurrentRouteUserDto> currentUser() {
|
||||
return ApiResponse.success(upmsQueryService.currentUser());
|
||||
public ApiResponse<CurrentRouteUserDto> currentUser(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorizationHeader) {
|
||||
return ApiResponse.success(upmsQueryService.currentUser(authorizationHeader));
|
||||
}
|
||||
|
||||
@GetMapping("/classes")
|
||||
public ApiResponse<List<SchoolClassDto>> classes(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorizationHeader) {
|
||||
return ApiResponse.success(upmsQueryService.classes(authorizationHeader));
|
||||
}
|
||||
|
||||
@GetMapping("/classes/{classId}/members")
|
||||
public ApiResponse<List<SchoolClassMemberDto>> classMembers(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorizationHeader,
|
||||
@PathVariable String classId) {
|
||||
return ApiResponse.success(upmsQueryService.classMembers(authorizationHeader, classId));
|
||||
}
|
||||
|
||||
@GetMapping("/classes/{classId}/courses")
|
||||
public ApiResponse<List<SchoolClassCourseDto>> classCourses(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorizationHeader,
|
||||
@PathVariable String classId) {
|
||||
return ApiResponse.success(upmsQueryService.classCourses(authorizationHeader, classId));
|
||||
}
|
||||
|
||||
@PostMapping("/files/upload")
|
||||
public ApiResponse<FileMetadataDto> uploadFile(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorizationHeader,
|
||||
@RequestBody FileUploadRequestDto request) {
|
||||
return ApiResponse.success("上传登记成功", upmsQueryService.uploadFile(authorizationHeader, request));
|
||||
}
|
||||
|
||||
@GetMapping("/files/{fileId}")
|
||||
public ApiResponse<FileMetadataDto> fileById(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorizationHeader,
|
||||
@PathVariable String fileId) {
|
||||
return ApiResponse.success(upmsQueryService.fileById(authorizationHeader, fileId));
|
||||
}
|
||||
|
||||
@GetMapping("/messages/inbox")
|
||||
public ApiResponse<List<InboxMessageDto>> inboxMessages(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorizationHeader) {
|
||||
return ApiResponse.success(upmsQueryService.inboxMessages(authorizationHeader));
|
||||
}
|
||||
|
||||
@PostMapping("/messages/{messageId}/read")
|
||||
public ApiResponse<MessageReadResultDto> readMessage(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorizationHeader,
|
||||
@PathVariable String messageId) {
|
||||
return ApiResponse.success("已标记已读", upmsQueryService.readMessage(authorizationHeader, messageId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,38 @@ 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.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 java.util.List;
|
||||
public interface UpmsQueryService {
|
||||
List<RouteNodeDto> routes();
|
||||
List<RouteNodeDto> routes(String authorizationHeader);
|
||||
|
||||
List<AreaNodeDto> areas();
|
||||
List<AreaNodeDto> areas(String authorizationHeader);
|
||||
|
||||
List<TenantNodeDto> tenants();
|
||||
List<TenantNodeDto> tenants(String authorizationHeader);
|
||||
|
||||
List<DeptNodeDto> departments();
|
||||
List<DeptNodeDto> departments(String authorizationHeader);
|
||||
|
||||
CurrentRouteUserDto currentUser();
|
||||
CurrentRouteUserDto currentUser(String authorizationHeader);
|
||||
|
||||
List<SchoolClassDto> classes(String authorizationHeader);
|
||||
|
||||
List<SchoolClassMemberDto> classMembers(String authorizationHeader, String classId);
|
||||
|
||||
List<SchoolClassCourseDto> classCourses(String authorizationHeader, String classId);
|
||||
|
||||
FileMetadataDto uploadFile(String authorizationHeader, FileUploadRequestDto request);
|
||||
|
||||
FileMetadataDto fileById(String authorizationHeader, String fileId);
|
||||
|
||||
List<InboxMessageDto> inboxMessages(String authorizationHeader);
|
||||
|
||||
MessageReadResultDto readMessage(String authorizationHeader, String messageId);
|
||||
}
|
||||
|
||||
@@ -3,130 +3,762 @@ package com.k12study.upms.service.impl;
|
||||
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.LayoutType;
|
||||
import com.k12study.api.upms.dto.MessageReadResultDto;
|
||||
import com.k12study.api.upms.dto.RouteMetaDto;
|
||||
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.common.security.context.RequestUserContextHolder;
|
||||
import com.k12study.common.security.jwt.JwtTokenProvider;
|
||||
import com.k12study.common.security.jwt.JwtUserPrincipal;
|
||||
import com.k12study.common.web.exception.BizException;
|
||||
import com.k12study.upms.service.UpmsQueryService;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@Service
|
||||
public class UpmsQueryServiceImpl implements UpmsQueryService {
|
||||
private final NamedParameterJdbcTemplate jdbcTemplate;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
@Override
|
||||
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 UpmsQueryServiceImpl(NamedParameterJdbcTemplate jdbcTemplate, JwtTokenProvider jwtTokenProvider) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.jwtTokenProvider = jwtTokenProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AreaNodeDto> areas() {
|
||||
return List.of(
|
||||
new AreaNodeDto(
|
||||
"330000",
|
||||
"100000",
|
||||
"浙江省",
|
||||
"PROVINCE",
|
||||
List.of(
|
||||
new AreaNodeDto(
|
||||
"330100",
|
||||
"330000",
|
||||
"杭州市",
|
||||
"CITY",
|
||||
List.of()
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
public List<RouteNodeDto> routes(String authorizationHeader) {
|
||||
AuthContext context = requireAuth(authorizationHeader);
|
||||
String sql = """
|
||||
SELECT DISTINCT
|
||||
m.route_id, m.parent_route_id, m.route_path, m.route_name,
|
||||
m.component_key, m.layout_type, m.title, m.icon, m.permission_code, m.hidden
|
||||
FROM upms.tb_sys_menu m
|
||||
JOIN upms.tb_sys_role_menu rm ON rm.route_id = m.route_id
|
||||
JOIN upms.tb_sys_user_role ur ON ur.role_id = rm.role_id
|
||||
WHERE ur.user_id = :userId
|
||||
AND m.tenant_id = :tenantId
|
||||
ORDER BY m.created_at, m.route_id
|
||||
""";
|
||||
List<RouteRow> rows = jdbcTemplate.query(
|
||||
sql,
|
||||
Map.of("userId", context.userId(), "tenantId", context.tenantId()),
|
||||
(rs, rowNum) -> new RouteRow(
|
||||
rs.getString("route_id"),
|
||||
rs.getString("parent_route_id"),
|
||||
rs.getString("route_path"),
|
||||
rs.getString("route_name"),
|
||||
rs.getString("component_key"),
|
||||
rs.getString("layout_type"),
|
||||
rs.getString("title"),
|
||||
rs.getString("icon"),
|
||||
rs.getString("permission_code"),
|
||||
rs.getBoolean("hidden")
|
||||
));
|
||||
return buildRouteTree(rows);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TenantNodeDto> tenants() {
|
||||
return List.of(
|
||||
new TenantNodeDto(
|
||||
"SCH-HQ",
|
||||
null,
|
||||
"K12Study 总校",
|
||||
"HEAD_SCHOOL",
|
||||
"330100",
|
||||
"/SCH-HQ/",
|
||||
List.of(
|
||||
new TenantNodeDto(
|
||||
"SCH-ZJ-HZ-01",
|
||||
"SCH-HQ",
|
||||
"杭州分校",
|
||||
"CITY_SCHOOL",
|
||||
"330100",
|
||||
"/SCH-HQ/SCH-ZJ-HZ-01/",
|
||||
List.of()
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
public List<AreaNodeDto> areas(String authorizationHeader) {
|
||||
requireAuth(authorizationHeader);
|
||||
String sql = """
|
||||
SELECT id, pid, adcode, name, area_type
|
||||
FROM upms.tb_sys_area
|
||||
WHERE del_flag = '0'
|
||||
ORDER BY area_sort NULLS LAST, id
|
||||
""";
|
||||
List<AreaRow> rows = jdbcTemplate.query(
|
||||
sql,
|
||||
Map.of(),
|
||||
(rs, rowNum) -> new AreaRow(
|
||||
rs.getLong("id"),
|
||||
rs.getLong("pid"),
|
||||
String.valueOf(rs.getLong("adcode")),
|
||||
rs.getString("name"),
|
||||
rs.getString("area_type")
|
||||
));
|
||||
return buildAreaTree(rows);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeptNodeDto> departments() {
|
||||
return List.of(
|
||||
new DeptNodeDto(
|
||||
"DEPT-HQ",
|
||||
null,
|
||||
"总校教学部",
|
||||
"GRADE",
|
||||
"SCH-HQ",
|
||||
"330100",
|
||||
"/SCH-HQ/",
|
||||
"/DEPT-HQ/",
|
||||
List.of(
|
||||
new DeptNodeDto(
|
||||
"DEPT-HQ-MATH",
|
||||
"DEPT-HQ",
|
||||
"数学学科组",
|
||||
"SUBJECT",
|
||||
"SCH-HQ",
|
||||
"330100",
|
||||
"/SCH-HQ/",
|
||||
"/DEPT-HQ/DEPT-HQ-MATH/",
|
||||
List.of()
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
public List<TenantNodeDto> tenants(String authorizationHeader) {
|
||||
AuthContext context = requireAuth(authorizationHeader);
|
||||
List<String> roleCodes = findRoleCodes(context.userId());
|
||||
boolean superAdmin = roleCodes.stream().anyMatch("SUPER_ADMIN"::equalsIgnoreCase);
|
||||
|
||||
StringBuilder sql = new StringBuilder("""
|
||||
SELECT tenant_id, parent_tenant_id, tenant_name, tenant_type, adcode, tenant_path
|
||||
FROM upms.tb_sys_tenant
|
||||
WHERE status = 'ACTIVE'
|
||||
""");
|
||||
MapSqlParameterSource params = new MapSqlParameterSource();
|
||||
if (!superAdmin) {
|
||||
if (StringUtils.hasText(context.tenantPath())) {
|
||||
sql.append(" AND tenant_path LIKE :tenantPathPrefix ");
|
||||
params.addValue("tenantPathPrefix", context.tenantPath() + "%");
|
||||
} else {
|
||||
sql.append(" AND tenant_id = :tenantId ");
|
||||
params.addValue("tenantId", context.tenantId());
|
||||
}
|
||||
}
|
||||
sql.append(" ORDER BY tenant_path ");
|
||||
|
||||
List<TenantRow> rows = jdbcTemplate.query(
|
||||
sql.toString(),
|
||||
params,
|
||||
(rs, rowNum) -> new TenantRow(
|
||||
rs.getString("tenant_id"),
|
||||
rs.getString("parent_tenant_id"),
|
||||
rs.getString("tenant_name"),
|
||||
rs.getString("tenant_type"),
|
||||
rs.getString("adcode"),
|
||||
rs.getString("tenant_path")
|
||||
));
|
||||
return buildTenantTree(rows);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CurrentRouteUserDto currentUser() {
|
||||
var context = RequestUserContextHolder.get();
|
||||
public List<DeptNodeDto> departments(String authorizationHeader) {
|
||||
AuthContext context = requireAuth(authorizationHeader);
|
||||
List<String> roleCodes = findRoleCodes(context.userId());
|
||||
boolean superAdmin = roleCodes.stream().anyMatch("SUPER_ADMIN"::equalsIgnoreCase);
|
||||
|
||||
StringBuilder sql = new StringBuilder("""
|
||||
SELECT dept_id, parent_dept_id, dept_name, dept_type, tenant_id, adcode, tenant_path, dept_path
|
||||
FROM upms.tb_sys_dept
|
||||
WHERE tenant_id = :tenantId
|
||||
""");
|
||||
MapSqlParameterSource params = new MapSqlParameterSource()
|
||||
.addValue("tenantId", context.tenantId());
|
||||
if (!superAdmin && StringUtils.hasText(context.deptPath())) {
|
||||
sql.append(" AND dept_path LIKE :deptPathPrefix ");
|
||||
params.addValue("deptPathPrefix", context.deptPath() + "%");
|
||||
}
|
||||
sql.append(" ORDER BY dept_path ");
|
||||
|
||||
List<DeptRow> rows = jdbcTemplate.query(
|
||||
sql.toString(),
|
||||
params,
|
||||
(rs, rowNum) -> new DeptRow(
|
||||
rs.getString("dept_id"),
|
||||
rs.getString("parent_dept_id"),
|
||||
rs.getString("dept_name"),
|
||||
rs.getString("dept_type"),
|
||||
rs.getString("tenant_id"),
|
||||
rs.getString("adcode"),
|
||||
rs.getString("tenant_path"),
|
||||
rs.getString("dept_path")
|
||||
));
|
||||
return buildDeptTree(rows);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CurrentRouteUserDto currentUser(String authorizationHeader) {
|
||||
AuthContext context = requireAuth(authorizationHeader);
|
||||
UserRow user = findUserRow(context.userId(), context.tenantId())
|
||||
.orElseThrow(() -> new BizException(404, "当前用户不存在"));
|
||||
List<String> permissionCodes = findPermissionCodes(context.userId(), context.tenantId());
|
||||
return new CurrentRouteUserDto(
|
||||
context == null ? "U10001" : context.userId(),
|
||||
context == null ? "admin" : context.username(),
|
||||
context == null ? "K12Study 管理员" : context.displayName(),
|
||||
"330100",
|
||||
context == null ? "SCH-HQ" : context.tenantId(),
|
||||
"/SCH-HQ/",
|
||||
context == null ? "DEPT-HQ-ADMIN" : context.deptId(),
|
||||
"/DEPT-HQ/DEPT-HQ-ADMIN/",
|
||||
List.of("dashboard:view", "tenant:view", "dept:view")
|
||||
user.userId(),
|
||||
user.username(),
|
||||
user.displayName(),
|
||||
user.adcode(),
|
||||
user.tenantId(),
|
||||
user.tenantPath(),
|
||||
user.deptId(),
|
||||
user.deptPath(),
|
||||
permissionCodes
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SchoolClassDto> classes(String authorizationHeader) {
|
||||
AuthContext context = requireAuth(authorizationHeader);
|
||||
String sql = """
|
||||
SELECT class_id, class_code, class_name, grade_code, status, tenant_id, dept_id
|
||||
FROM upms.tb_school_class
|
||||
WHERE tenant_id = :tenantId
|
||||
ORDER BY created_at DESC
|
||||
""";
|
||||
return jdbcTemplate.query(
|
||||
sql,
|
||||
Map.of("tenantId", context.tenantId()),
|
||||
(rs, rowNum) -> new SchoolClassDto(
|
||||
rs.getString("class_id"),
|
||||
rs.getString("class_code"),
|
||||
rs.getString("class_name"),
|
||||
rs.getString("grade_code"),
|
||||
rs.getString("status"),
|
||||
rs.getString("tenant_id"),
|
||||
rs.getString("dept_id")
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SchoolClassMemberDto> classMembers(String authorizationHeader, String classId) {
|
||||
AuthContext context = requireAuth(authorizationHeader);
|
||||
ensureClassInTenant(classId, context.tenantId());
|
||||
String sql = """
|
||||
SELECT m.class_id, m.user_id, u.username, u.display_name, m.member_role, m.member_status, m.joined_at, m.left_at
|
||||
FROM upms.tb_school_class_member m
|
||||
JOIN upms.tb_sys_user u ON u.user_id = m.user_id
|
||||
WHERE m.class_id = :classId
|
||||
AND m.tenant_id = :tenantId
|
||||
ORDER BY m.joined_at
|
||||
""";
|
||||
return jdbcTemplate.query(
|
||||
sql,
|
||||
Map.of("classId", classId, "tenantId", context.tenantId()),
|
||||
(rs, rowNum) -> new SchoolClassMemberDto(
|
||||
rs.getString("class_id"),
|
||||
rs.getString("user_id"),
|
||||
rs.getString("username"),
|
||||
rs.getString("display_name"),
|
||||
rs.getString("member_role"),
|
||||
rs.getString("member_status"),
|
||||
toInstant(rs.getTimestamp("joined_at")),
|
||||
toInstant(rs.getTimestamp("left_at"))
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SchoolClassCourseDto> classCourses(String authorizationHeader, String classId) {
|
||||
AuthContext context = requireAuth(authorizationHeader);
|
||||
ensureClassInTenant(classId, context.tenantId());
|
||||
String sql = """
|
||||
SELECT class_id, course_id, relation_status
|
||||
FROM upms.tb_school_class_course_rel
|
||||
WHERE class_id = :classId
|
||||
AND tenant_id = :tenantId
|
||||
ORDER BY created_at
|
||||
""";
|
||||
return jdbcTemplate.query(
|
||||
sql,
|
||||
Map.of("classId", classId, "tenantId", context.tenantId()),
|
||||
(rs, rowNum) -> new SchoolClassCourseDto(
|
||||
rs.getString("class_id"),
|
||||
rs.getString("course_id"),
|
||||
rs.getString("relation_status")
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileMetadataDto uploadFile(String authorizationHeader, FileUploadRequestDto request) {
|
||||
AuthContext context = requireAuth(authorizationHeader);
|
||||
if (request == null || !StringUtils.hasText(request.objectKey())) {
|
||||
throw new BizException(400, "objectKey 不能为空");
|
||||
}
|
||||
String mediaType = normalizeMediaType(request.mediaType());
|
||||
String fileId = "FILE-" + UUID.randomUUID().toString().replace("-", "").substring(0, 16).toUpperCase();
|
||||
String sql = """
|
||||
INSERT INTO upms.tb_sys_file (
|
||||
file_id, media_type, object_key, file_name, mime_type, file_size, file_hash, duration_ms,
|
||||
uploaded_by, adcode, tenant_id, tenant_path, created_at
|
||||
) VALUES (
|
||||
:fileId, :mediaType, :objectKey, :fileName, :mimeType, :fileSize, :fileHash, :durationMs,
|
||||
:uploadedBy, :adcode, :tenantId, :tenantPath, CURRENT_TIMESTAMP
|
||||
)
|
||||
""";
|
||||
MapSqlParameterSource params = new MapSqlParameterSource()
|
||||
.addValue("fileId", fileId)
|
||||
.addValue("mediaType", mediaType)
|
||||
.addValue("objectKey", request.objectKey())
|
||||
.addValue("fileName", request.fileName())
|
||||
.addValue("mimeType", request.mimeType())
|
||||
.addValue("fileSize", request.fileSize())
|
||||
.addValue("fileHash", request.fileHash())
|
||||
.addValue("durationMs", request.durationMs())
|
||||
.addValue("uploadedBy", context.userId())
|
||||
.addValue("adcode", context.adcode())
|
||||
.addValue("tenantId", context.tenantId())
|
||||
.addValue("tenantPath", context.tenantPath());
|
||||
jdbcTemplate.update(sql, params);
|
||||
return fileById(authorizationHeader, fileId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileMetadataDto fileById(String authorizationHeader, String fileId) {
|
||||
AuthContext context = requireAuth(authorizationHeader);
|
||||
String sql = """
|
||||
SELECT file_id, media_type, object_key, file_name, mime_type, file_size, file_hash, duration_ms,
|
||||
uploaded_by, tenant_id, tenant_path, created_at
|
||||
FROM upms.tb_sys_file
|
||||
WHERE file_id = :fileId
|
||||
AND tenant_id = :tenantId
|
||||
LIMIT 1
|
||||
""";
|
||||
return jdbcTemplate.query(sql, Map.of("fileId", fileId, "tenantId", context.tenantId()), FILE_ROW_MAPPER)
|
||||
.stream()
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new BizException(404, "文件不存在"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InboxMessageDto> inboxMessages(String authorizationHeader) {
|
||||
AuthContext context = requireAuth(authorizationHeader);
|
||||
String sql = """
|
||||
SELECT m.message_id, m.message_type, m.biz_type, m.title, m.content, m.web_jump_url,
|
||||
r.read_status, r.read_at, m.send_at
|
||||
FROM upms.tb_sys_message m
|
||||
JOIN upms.tb_sys_message_recipient r ON r.message_id = m.message_id
|
||||
WHERE r.recipient_user_id = :userId
|
||||
AND r.tenant_id = :tenantId
|
||||
AND m.message_status = 'ACTIVE'
|
||||
ORDER BY m.send_at DESC
|
||||
""";
|
||||
return jdbcTemplate.query(
|
||||
sql,
|
||||
Map.of("userId", context.userId(), "tenantId", context.tenantId()),
|
||||
(rs, rowNum) -> new InboxMessageDto(
|
||||
rs.getString("message_id"),
|
||||
rs.getString("message_type"),
|
||||
rs.getString("biz_type"),
|
||||
rs.getString("title"),
|
||||
rs.getString("content"),
|
||||
rs.getString("web_jump_url"),
|
||||
rs.getString("read_status"),
|
||||
toInstant(rs.getTimestamp("read_at")),
|
||||
toInstant(rs.getTimestamp("send_at"))
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageReadResultDto readMessage(String authorizationHeader, String messageId) {
|
||||
AuthContext context = requireAuth(authorizationHeader);
|
||||
String readSource = "MINI".equalsIgnoreCase(context.clientType()) ? "MINI_PROGRAM" : "WEB";
|
||||
String updateSql = """
|
||||
UPDATE upms.tb_sys_message_recipient
|
||||
SET read_status = 'READ',
|
||||
read_at = COALESCE(read_at, CURRENT_TIMESTAMP),
|
||||
read_source = COALESCE(read_source, :readSource),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE message_id = :messageId
|
||||
AND recipient_user_id = :userId
|
||||
AND tenant_id = :tenantId
|
||||
""";
|
||||
int updated = jdbcTemplate.update(
|
||||
updateSql,
|
||||
new MapSqlParameterSource()
|
||||
.addValue("messageId", messageId)
|
||||
.addValue("userId", context.userId())
|
||||
.addValue("tenantId", context.tenantId())
|
||||
.addValue("readSource", readSource));
|
||||
if (updated == 0) {
|
||||
throw new BizException(404, "消息不存在");
|
||||
}
|
||||
|
||||
String querySql = """
|
||||
SELECT message_id, read_status, read_at
|
||||
FROM upms.tb_sys_message_recipient
|
||||
WHERE message_id = :messageId
|
||||
AND recipient_user_id = :userId
|
||||
AND tenant_id = :tenantId
|
||||
LIMIT 1
|
||||
""";
|
||||
return jdbcTemplate.query(
|
||||
querySql,
|
||||
Map.of("messageId", messageId, "userId", context.userId(), "tenantId", context.tenantId()),
|
||||
(rs, rowNum) -> new MessageReadResultDto(
|
||||
rs.getString("message_id"),
|
||||
rs.getString("read_status"),
|
||||
toInstant(rs.getTimestamp("read_at"))
|
||||
))
|
||||
.stream()
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new BizException(404, "消息不存在"));
|
||||
}
|
||||
|
||||
private AuthContext requireAuth(String authorizationHeader) {
|
||||
var context = RequestUserContextHolder.get();
|
||||
if (context != null && StringUtils.hasText(context.userId()) && StringUtils.hasText(context.tenantId())) {
|
||||
return new AuthContext(
|
||||
context.userId(),
|
||||
context.username(),
|
||||
context.displayName(),
|
||||
context.adcode(),
|
||||
context.tenantId(),
|
||||
context.tenantPath(),
|
||||
context.deptId(),
|
||||
context.deptPath(),
|
||||
context.roleCodes(),
|
||||
context.clientType()
|
||||
);
|
||||
}
|
||||
if (StringUtils.hasText(authorizationHeader) && authorizationHeader.startsWith("Bearer ")) {
|
||||
JwtUserPrincipal principal = jwtTokenProvider.parse(authorizationHeader.substring("Bearer ".length()));
|
||||
return new AuthContext(
|
||||
principal.userId(),
|
||||
principal.username(),
|
||||
principal.displayName(),
|
||||
principal.adcode(),
|
||||
principal.tenantId(),
|
||||
principal.tenantPath(),
|
||||
principal.deptId(),
|
||||
principal.deptPath(),
|
||||
principal.roleCodes(),
|
||||
principal.clientType()
|
||||
);
|
||||
}
|
||||
throw new BizException(401, "未登录或登录已失效");
|
||||
}
|
||||
|
||||
private Optional<UserRow> findUserRow(String userId, String tenantId) {
|
||||
String sql = """
|
||||
SELECT user_id, username, display_name, adcode, tenant_id, tenant_path, dept_id, dept_path
|
||||
FROM upms.tb_sys_user
|
||||
WHERE user_id = :userId
|
||||
AND tenant_id = :tenantId
|
||||
LIMIT 1
|
||||
""";
|
||||
return jdbcTemplate.query(sql, Map.of("userId", userId, "tenantId", tenantId), USER_ROW_MAPPER).stream().findFirst();
|
||||
}
|
||||
|
||||
private List<String> findRoleCodes(String userId) {
|
||||
String sql = """
|
||||
SELECT r.role_code
|
||||
FROM upms.tb_sys_user_role ur
|
||||
JOIN upms.tb_sys_role r ON r.role_id = ur.role_id
|
||||
WHERE ur.user_id = :userId
|
||||
ORDER BY r.role_code
|
||||
""";
|
||||
return jdbcTemplate.queryForList(sql, Map.of("userId", userId), String.class);
|
||||
}
|
||||
|
||||
private List<String> findPermissionCodes(String userId, String tenantId) {
|
||||
String sql = """
|
||||
SELECT DISTINCT m.permission_code
|
||||
FROM upms.tb_sys_menu m
|
||||
JOIN upms.tb_sys_role_menu rm ON rm.route_id = m.route_id
|
||||
JOIN upms.tb_sys_user_role ur ON ur.role_id = rm.role_id
|
||||
WHERE ur.user_id = :userId
|
||||
AND m.tenant_id = :tenantId
|
||||
AND m.permission_code IS NOT NULL
|
||||
AND m.permission_code <> ''
|
||||
""";
|
||||
return jdbcTemplate.queryForList(sql, Map.of("userId", userId, "tenantId", tenantId), String.class)
|
||||
.stream()
|
||||
.flatMap(codes -> Arrays.stream(codes.split(",")))
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::hasText)
|
||||
.distinct()
|
||||
.sorted()
|
||||
.toList();
|
||||
}
|
||||
|
||||
private void ensureClassInTenant(String classId, String tenantId) {
|
||||
String sql = """
|
||||
SELECT COUNT(1)
|
||||
FROM upms.tb_school_class
|
||||
WHERE class_id = :classId
|
||||
AND tenant_id = :tenantId
|
||||
""";
|
||||
Integer count = jdbcTemplate.queryForObject(sql, Map.of("classId", classId, "tenantId", tenantId), Integer.class);
|
||||
if (count == null || count == 0) {
|
||||
throw new BizException(404, "班级不存在");
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeMediaType(String mediaType) {
|
||||
if (!StringUtils.hasText(mediaType)) {
|
||||
return "OTHER";
|
||||
}
|
||||
String normalized = mediaType.trim().toUpperCase();
|
||||
return switch (normalized) {
|
||||
case "IMAGE", "AUDIO", "VIDEO", "DOCUMENT", "OTHER" -> normalized;
|
||||
default -> throw new BizException(400, "mediaType 非法");
|
||||
};
|
||||
}
|
||||
|
||||
private List<RouteNodeDto> buildRouteTree(List<RouteRow> rows) {
|
||||
if (rows.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
Map<String, RouteRow> rowMap = new LinkedHashMap<>();
|
||||
Map<String, List<String>> childrenMap = new LinkedHashMap<>();
|
||||
for (RouteRow row : rows) {
|
||||
rowMap.put(row.routeId(), row);
|
||||
if (StringUtils.hasText(row.parentRouteId())) {
|
||||
childrenMap.computeIfAbsent(row.parentRouteId(), key -> new ArrayList<>()).add(row.routeId());
|
||||
}
|
||||
}
|
||||
List<String> rootIds = rows.stream()
|
||||
.filter(row -> !StringUtils.hasText(row.parentRouteId()) || !rowMap.containsKey(row.parentRouteId()))
|
||||
.map(RouteRow::routeId)
|
||||
.toList();
|
||||
return rootIds.stream()
|
||||
.map(routeId -> buildRouteNode(routeId, rowMap, childrenMap))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private RouteNodeDto buildRouteNode(
|
||||
String routeId,
|
||||
Map<String, RouteRow> rowMap,
|
||||
Map<String, List<String>> childrenMap) {
|
||||
RouteRow row = rowMap.get(routeId);
|
||||
List<RouteNodeDto> children = childrenMap.getOrDefault(routeId, List.of()).stream()
|
||||
.map(childId -> buildRouteNode(childId, rowMap, childrenMap))
|
||||
.toList();
|
||||
LayoutType layoutType;
|
||||
try {
|
||||
layoutType = LayoutType.valueOf(row.layoutType());
|
||||
} catch (Exception exception) {
|
||||
layoutType = LayoutType.SIDEBAR;
|
||||
}
|
||||
List<String> permissionCodes = splitPermissionCodes(row.permissionCode());
|
||||
return new RouteNodeDto(
|
||||
row.routeId(),
|
||||
row.routePath(),
|
||||
row.routeName(),
|
||||
row.componentKey(),
|
||||
layoutType,
|
||||
new RouteMetaDto(row.title(), row.icon(), permissionCodes, row.hidden()),
|
||||
children
|
||||
);
|
||||
}
|
||||
|
||||
private List<AreaNodeDto> buildAreaTree(List<AreaRow> rows) {
|
||||
Map<Long, AreaRow> rowMap = rows.stream()
|
||||
.collect(Collectors.toMap(AreaRow::id, row -> row, (a, b) -> a, LinkedHashMap::new));
|
||||
Map<Long, List<Long>> childMap = new LinkedHashMap<>();
|
||||
for (AreaRow row : rows) {
|
||||
childMap.computeIfAbsent(row.pid(), key -> new ArrayList<>()).add(row.id());
|
||||
}
|
||||
return rows.stream()
|
||||
.filter(row -> row.pid() == 0L || !rowMap.containsKey(row.pid()))
|
||||
.map(row -> buildAreaNode(row.id(), rowMap, childMap))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private AreaNodeDto buildAreaNode(Long id, Map<Long, AreaRow> rowMap, Map<Long, List<Long>> childMap) {
|
||||
AreaRow row = rowMap.get(id);
|
||||
List<AreaNodeDto> children = childMap.getOrDefault(id, List.of()).stream()
|
||||
.map(childId -> buildAreaNode(childId, rowMap, childMap))
|
||||
.toList();
|
||||
return new AreaNodeDto(
|
||||
row.areaCode(),
|
||||
String.valueOf(row.pid()),
|
||||
row.areaName(),
|
||||
mapAreaLevel(row.areaType()),
|
||||
children
|
||||
);
|
||||
}
|
||||
|
||||
private List<TenantNodeDto> buildTenantTree(List<TenantRow> rows) {
|
||||
Map<String, TenantRow> rowMap = rows.stream()
|
||||
.collect(Collectors.toMap(TenantRow::tenantId, row -> row, (a, b) -> a, LinkedHashMap::new));
|
||||
Map<String, List<String>> childMap = new LinkedHashMap<>();
|
||||
for (TenantRow row : rows) {
|
||||
if (StringUtils.hasText(row.parentTenantId())) {
|
||||
childMap.computeIfAbsent(row.parentTenantId(), key -> new ArrayList<>()).add(row.tenantId());
|
||||
}
|
||||
}
|
||||
return rows.stream()
|
||||
.filter(row -> !StringUtils.hasText(row.parentTenantId()) || !rowMap.containsKey(row.parentTenantId()))
|
||||
.sorted(Comparator.comparing(TenantRow::tenantPath))
|
||||
.map(row -> buildTenantNode(row.tenantId(), rowMap, childMap))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private TenantNodeDto buildTenantNode(
|
||||
String tenantId,
|
||||
Map<String, TenantRow> rowMap,
|
||||
Map<String, List<String>> childMap) {
|
||||
TenantRow row = rowMap.get(tenantId);
|
||||
List<TenantNodeDto> children = childMap.getOrDefault(tenantId, List.of()).stream()
|
||||
.map(childId -> buildTenantNode(childId, rowMap, childMap))
|
||||
.toList();
|
||||
return new TenantNodeDto(
|
||||
row.tenantId(),
|
||||
row.parentTenantId(),
|
||||
row.tenantName(),
|
||||
row.tenantType(),
|
||||
row.adcode(),
|
||||
row.tenantPath(),
|
||||
children
|
||||
);
|
||||
}
|
||||
|
||||
private List<DeptNodeDto> buildDeptTree(List<DeptRow> rows) {
|
||||
Map<String, DeptRow> rowMap = rows.stream()
|
||||
.collect(Collectors.toMap(DeptRow::deptId, row -> row, (a, b) -> a, LinkedHashMap::new));
|
||||
Map<String, List<String>> childMap = new LinkedHashMap<>();
|
||||
for (DeptRow row : rows) {
|
||||
if (StringUtils.hasText(row.parentDeptId())) {
|
||||
childMap.computeIfAbsent(row.parentDeptId(), key -> new ArrayList<>()).add(row.deptId());
|
||||
}
|
||||
}
|
||||
return rows.stream()
|
||||
.filter(row -> !StringUtils.hasText(row.parentDeptId()) || !rowMap.containsKey(row.parentDeptId()))
|
||||
.sorted(Comparator.comparing(DeptRow::deptPath))
|
||||
.map(row -> buildDeptNode(row.deptId(), rowMap, childMap))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private DeptNodeDto buildDeptNode(
|
||||
String deptId,
|
||||
Map<String, DeptRow> rowMap,
|
||||
Map<String, List<String>> childMap) {
|
||||
DeptRow row = rowMap.get(deptId);
|
||||
List<DeptNodeDto> children = childMap.getOrDefault(deptId, List.of()).stream()
|
||||
.map(childId -> buildDeptNode(childId, rowMap, childMap))
|
||||
.toList();
|
||||
return new DeptNodeDto(
|
||||
row.deptId(),
|
||||
row.parentDeptId(),
|
||||
row.deptName(),
|
||||
row.deptType(),
|
||||
row.tenantId(),
|
||||
row.adcode(),
|
||||
row.tenantPath(),
|
||||
row.deptPath(),
|
||||
children
|
||||
);
|
||||
}
|
||||
|
||||
private List<String> splitPermissionCodes(String permissionCode) {
|
||||
if (!StringUtils.hasText(permissionCode)) {
|
||||
return List.of();
|
||||
}
|
||||
return Arrays.stream(permissionCode.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::hasText)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private String mapAreaLevel(String areaType) {
|
||||
if ("0".equals(areaType)) {
|
||||
return "COUNTRY";
|
||||
}
|
||||
if ("1".equals(areaType)) {
|
||||
return "PROVINCE";
|
||||
}
|
||||
if ("2".equals(areaType)) {
|
||||
return "CITY";
|
||||
}
|
||||
if ("3".equals(areaType)) {
|
||||
return "DISTRICT";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
private Instant toInstant(Timestamp timestamp) {
|
||||
return timestamp == null ? null : timestamp.toInstant();
|
||||
}
|
||||
|
||||
private static final RowMapper<UserRow> USER_ROW_MAPPER = (rs, rowNum) -> new UserRow(
|
||||
rs.getString("user_id"),
|
||||
rs.getString("username"),
|
||||
rs.getString("display_name"),
|
||||
rs.getString("adcode"),
|
||||
rs.getString("tenant_id"),
|
||||
rs.getString("tenant_path"),
|
||||
rs.getString("dept_id"),
|
||||
rs.getString("dept_path")
|
||||
);
|
||||
|
||||
private static final RowMapper<FileMetadataDto> FILE_ROW_MAPPER = (rs, rowNum) -> new FileMetadataDto(
|
||||
rs.getString("file_id"),
|
||||
rs.getString("media_type"),
|
||||
rs.getString("object_key"),
|
||||
rs.getString("file_name"),
|
||||
rs.getString("mime_type"),
|
||||
rs.getObject("file_size", Long.class),
|
||||
rs.getString("file_hash"),
|
||||
rs.getObject("duration_ms", Integer.class),
|
||||
rs.getString("uploaded_by"),
|
||||
rs.getString("tenant_id"),
|
||||
rs.getString("tenant_path"),
|
||||
rs.getTimestamp("created_at") == null ? null : rs.getTimestamp("created_at").toInstant()
|
||||
);
|
||||
|
||||
private record AuthContext(
|
||||
String userId,
|
||||
String username,
|
||||
String displayName,
|
||||
String adcode,
|
||||
String tenantId,
|
||||
String tenantPath,
|
||||
String deptId,
|
||||
String deptPath,
|
||||
List<String> roleCodes,
|
||||
String clientType
|
||||
) {
|
||||
}
|
||||
|
||||
private record RouteRow(
|
||||
String routeId,
|
||||
String parentRouteId,
|
||||
String routePath,
|
||||
String routeName,
|
||||
String componentKey,
|
||||
String layoutType,
|
||||
String title,
|
||||
String icon,
|
||||
String permissionCode,
|
||||
boolean hidden
|
||||
) {
|
||||
}
|
||||
|
||||
private record AreaRow(
|
||||
Long id,
|
||||
Long pid,
|
||||
String areaCode,
|
||||
String areaName,
|
||||
String areaType
|
||||
) {
|
||||
}
|
||||
|
||||
private record TenantRow(
|
||||
String tenantId,
|
||||
String parentTenantId,
|
||||
String tenantName,
|
||||
String tenantType,
|
||||
String adcode,
|
||||
String tenantPath
|
||||
) {
|
||||
}
|
||||
|
||||
private record DeptRow(
|
||||
String deptId,
|
||||
String parentDeptId,
|
||||
String deptName,
|
||||
String deptType,
|
||||
String tenantId,
|
||||
String adcode,
|
||||
String tenantPath,
|
||||
String deptPath
|
||||
) {
|
||||
}
|
||||
|
||||
private record UserRow(
|
||||
String userId,
|
||||
String username,
|
||||
String displayName,
|
||||
String adcode,
|
||||
String tenantId,
|
||||
String tenantPath,
|
||||
String deptId,
|
||||
String deptPath
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ server:
|
||||
spring:
|
||||
application:
|
||||
name: k12study-upms
|
||||
autoconfigure:
|
||||
exclude:
|
||||
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
|
||||
- org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
|
||||
- com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
|
||||
datasource:
|
||||
url: jdbc:postgresql://${K12STUDY_DB_HOST:localhost}:${K12STUDY_DB_PORT:5432}/${K12STUDY_DB_NAME:k12study}
|
||||
username: ${K12STUDY_DB_USER:k12study}
|
||||
password: ${K12STUDY_DB_PASSWORD:k12study}
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
|
||||
Reference in New Issue
Block a user