From 673214481dbf524b70d2e28471c1f087cb7c34ac Mon Sep 17 00:00:00 2001 From: En Date: Tue, 17 Mar 2026 15:03:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AF=BE=E7=A8=8B=E5=8C=85=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=AE=8C=E5=96=84=E4=B8=8E=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端: - 新增 YesNo 枚举类 - 新增 LessonStepCreateRequest、PackageGrantRequest 等 DTO - 新增 ResourceItemCreateRequest、ResourceLibraryCreateRequest - 新增 StatsService 统计服务实现 - 优化 AdminCourseController、AdminResourceController 等控制器 - 完善 TenantService 套餐授权功能 前端: - 优化套餐详情页和列表页展示 - 更新自动生成的 API 类型定义 文档: - 更新设计文档和开发日志 Co-Authored-By: Claude Opus 4.6 --- docs/design/23-校本课程包功能完善设计.md | 2 +- docs/dev-logs/2026-03-15.md | 2 +- .../course-package/course-package.service.ts | 4 +- .../src/api/generated/model/coursePackage.ts | 2 +- reading-platform-frontend/src/components.d.ts | 7 - .../admin/packages/PackageDetailView.vue | 4 +- .../views/admin/packages/PackageListView.vue | 10 +- .../reading/platform/common/enums/YesNo.java | 58 +++++ .../admin/AdminCourseController.java | 22 +- .../admin/AdminCourseLessonController.java | 133 +++++++---- .../admin/AdminResourceController.java | 166 ++++++------- .../admin/AdminStatsController.java | 37 +-- .../admin/AdminTenantController.java | 85 ++++--- .../admin/AdminThemeController.java | 46 +++- .../teacher/TeacherCourseController.java | 3 +- .../dto/request/LessonStepCreateRequest.java | 29 +++ .../dto/request/PackageGrantRequest.java | 21 ++ .../request/ResourceItemCreateRequest.java | 36 +++ .../request/ResourceItemUpdateRequest.java | 21 ++ .../request/ResourceLibraryCreateRequest.java | 24 ++ .../request/ResourceLibraryUpdateRequest.java | 18 ++ .../dto/response/ResourceItemResponse.java | 2 +- .../dto/response/ResourceLibraryResponse.java | 2 +- .../platform/entity/CoursePackage.java | 2 +- .../platform/service/StatsService.java | 43 ++++ .../platform/service/TenantService.java | 6 + .../service/impl/CourseServiceImpl.java | 21 +- .../service/impl/StatsServiceImpl.java | 224 ++++++++++++++++++ .../service/impl/TenantServiceImpl.java | 71 ++++++ 29 files changed, 853 insertions(+), 248 deletions(-) create mode 100644 reading-platform-java/src/main/java/com/reading/platform/common/enums/YesNo.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/request/LessonStepCreateRequest.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/request/PackageGrantRequest.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceItemCreateRequest.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceItemUpdateRequest.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceLibraryCreateRequest.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceLibraryUpdateRequest.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/service/StatsService.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/service/impl/StatsServiceImpl.java diff --git a/docs/design/23-校本课程包功能完善设计.md b/docs/design/23-校本课程包功能完善设计.md index f249154..cf9f667 100644 --- a/docs/design/23-校本课程包功能完善设计.md +++ b/docs/design/23-校本课程包功能完善设计.md @@ -47,7 +47,7 @@ model SchoolCourse { createdBy Int @map("created_by") changesSummary String? @map("changes_summary") // 修改说明 usageCount Int @default(0) @map("usage_count") - status String @default("ACTIVE") // ACTIVE, PENDING_REVIEW, REJECTED + status String @default("ACTIVE") // ACTIVE, PENDING, REJECTED createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") diff --git a/docs/dev-logs/2026-03-15.md b/docs/dev-logs/2026-03-15.md index ae9a263..bdd898c 100644 --- a/docs/dev-logs/2026-03-15.md +++ b/docs/dev-logs/2026-03-15.md @@ -82,7 +82,7 @@ PUT /api/v1/admin/packages/{id}/courses POST /api/v1/admin/packages/{id}/submit ``` -✅ 提交成功,状态变为 `PENDING_REVIEW` +✅ 提交成功,状态变为 `PENDING` #### 4. 审核通过 diff --git a/reading-platform-backend/src/modules/course-package/course-package.service.ts b/reading-platform-backend/src/modules/course-package/course-package.service.ts index a6385b0..8ddcbb8 100644 --- a/reading-platform-backend/src/modules/course-package/course-package.service.ts +++ b/reading-platform-backend/src/modules/course-package/course-package.service.ts @@ -241,7 +241,7 @@ export class CoursePackageService { return this.prisma.coursePackage.update({ where: { id }, data: { - status: 'PENDING_REVIEW', + status: 'PENDING', submittedAt: new Date(), submittedBy: userId, }, @@ -262,7 +262,7 @@ export class CoursePackageService { throw new Error('套餐不存在'); } - if (pkg.status !== 'PENDING_REVIEW') { + if (pkg.status !== 'PENDING') { throw new Error('只有待审核状态的套餐可以审核'); } diff --git a/reading-platform-frontend/src/api/generated/model/coursePackage.ts b/reading-platform-frontend/src/api/generated/model/coursePackage.ts index b02502f..8252a67 100644 --- a/reading-platform-frontend/src/api/generated/model/coursePackage.ts +++ b/reading-platform-frontend/src/api/generated/model/coursePackage.ts @@ -34,7 +34,7 @@ export interface CoursePackage { gradeLevels?: string; /** 课程数量 */ courseCount?: number; - /** 状态:DRAFT、PENDING_REVIEW、APPROVED、REJECTED、PUBLISHED、OFFLINE */ + /** 状态:DRAFT、PENDING、APPROVED、REJECTED、PUBLISHED、OFFLINE */ status?: string; /** 提交时间 */ submittedAt?: string; diff --git a/reading-platform-frontend/src/components.d.ts b/reading-platform-frontend/src/components.d.ts index 03a884c..23753e9 100644 --- a/reading-platform-frontend/src/components.d.ts +++ b/reading-platform-frontend/src/components.d.ts @@ -11,10 +11,8 @@ declare module 'vue' { AAvatar: typeof import('ant-design-vue/es')['Avatar'] ABadge: typeof import('ant-design-vue/es')['Badge'] AButton: typeof import('ant-design-vue/es')['Button'] - AButtonGroup: typeof import('ant-design-vue/es')['ButtonGroup'] ACard: typeof import('ant-design-vue/es')['Card'] ACheckbox: typeof import('ant-design-vue/es')['Checkbox'] - ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup'] ACol: typeof import('ant-design-vue/es')['Col'] ADatePicker: typeof import('ant-design-vue/es')['DatePicker'] ADescriptions: typeof import('ant-design-vue/es')['Descriptions'] @@ -25,8 +23,6 @@ declare module 'vue' { AEmpty: typeof import('ant-design-vue/es')['Empty'] AForm: typeof import('ant-design-vue/es')['Form'] AFormItem: typeof import('ant-design-vue/es')['FormItem'] - AImage: typeof import('ant-design-vue/es')['Image'] - AImagePreviewGroup: typeof import('ant-design-vue/es')['ImagePreviewGroup'] AInput: typeof import('ant-design-vue/es')['Input'] AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] @@ -52,14 +48,11 @@ declare module 'vue' { ARate: typeof import('ant-design-vue/es')['Rate'] ARow: typeof import('ant-design-vue/es')['Row'] ASelect: typeof import('ant-design-vue/es')['Select'] - ASelectOptGroup: typeof import('ant-design-vue/es')['SelectOptGroup'] ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] ASkeleton: typeof import('ant-design-vue/es')['Skeleton'] ASpace: typeof import('ant-design-vue/es')['Space'] ASpin: typeof import('ant-design-vue/es')['Spin'] AStatistic: typeof import('ant-design-vue/es')['Statistic'] - AStep: typeof import('ant-design-vue/es')['Step'] - ASteps: typeof import('ant-design-vue/es')['Steps'] ASubMenu: typeof import('ant-design-vue/es')['SubMenu'] ASwitch: typeof import('ant-design-vue/es')['Switch'] ATable: typeof import('ant-design-vue/es')['Table'] diff --git a/reading-platform-frontend/src/views/admin/packages/PackageDetailView.vue b/reading-platform-frontend/src/views/admin/packages/PackageDetailView.vue index 0fb15a2..e738f97 100644 --- a/reading-platform-frontend/src/views/admin/packages/PackageDetailView.vue +++ b/reading-platform-frontend/src/views/admin/packages/PackageDetailView.vue @@ -84,7 +84,7 @@ const courseColumns = [ const statusColors: Record = { DRAFT: 'default', - PENDING_REVIEW: 'processing', + PENDING: 'processing', APPROVED: 'success', REJECTED: 'error', PUBLISHED: 'blue', @@ -93,7 +93,7 @@ const statusColors: Record = { const statusTexts: Record = { DRAFT: '草稿', - PENDING_REVIEW: '待审核', + PENDING: '待审核', APPROVED: '已通过', REJECTED: '已拒绝', PUBLISHED: '已发布', diff --git a/reading-platform-frontend/src/views/admin/packages/PackageListView.vue b/reading-platform-frontend/src/views/admin/packages/PackageListView.vue index b95f9ae..73b82f3 100644 --- a/reading-platform-frontend/src/views/admin/packages/PackageListView.vue +++ b/reading-platform-frontend/src/views/admin/packages/PackageListView.vue @@ -27,7 +27,7 @@ @change="fetchData" > 草稿 - 待审核 + 待审核 已通过 已拒绝 已发布 @@ -81,7 +81,7 @@ 审核 @@ -143,7 +143,7 @@ const columns = [ const statusColors: Record = { DRAFT: 'default', - PENDING_REVIEW: 'processing', + PENDING: 'processing', APPROVED: 'success', REJECTED: 'error', PUBLISHED: 'blue', @@ -152,7 +152,7 @@ const statusColors: Record = { const statusTexts: Record = { DRAFT: '草稿', - PENDING_REVIEW: '待审核', + PENDING: '待审核', APPROVED: '已通过', REJECTED: '已拒绝', PUBLISHED: '已发布', @@ -183,7 +183,7 @@ const fetchData = async () => { pagination.total = res.total || 0; // 获取待审核数量 try { - const pendingRes = await getPackageList({ status: 'PENDING_REVIEW', pageNum: 1, pageSize: 1 }) as any; + const pendingRes = await getPackageList({ status: 'PENDING', pageNum: 1, pageSize: 1 }) as any; pendingCount.value = pendingRes.total || 0; } catch { pendingCount.value = 0; diff --git a/reading-platform-java/src/main/java/com/reading/platform/common/enums/YesNo.java b/reading-platform-java/src/main/java/com/reading/platform/common/enums/YesNo.java new file mode 100644 index 0000000..8672b87 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/common/enums/YesNo.java @@ -0,0 +1,58 @@ +package com.reading.platform.common.enums; + +import lombok.Getter; + +/** + * 是/否枚举 + * 用于替代数据库中的 0/1 魔法值 + */ +@Getter +public enum YesNo { + + NO(0, "否"), + YES(1, "是"); + + private final Integer code; + private final String description; + + YesNo(Integer code, String description) { + this.code = code; + this.description = description; + } + + /** + * 根据 code 获取枚举 + */ + public static YesNo fromCode(Integer code) { + if (code == null) { + return NO; + } + for (YesNo value : values()) { + if (value.getCode().equals(code)) { + return value; + } + } + return NO; + } + + /** + * 根据 Boolean 获取枚举 + */ + public static YesNo fromBoolean(Boolean bool) { + return Boolean.TRUE.equals(bool) ? YES : NO; + } + + /** + * 判断是否为"是" + */ + public static boolean isYes(Integer code) { + return YES.getCode().equals(code); + } + + /** + * 判断是否为"否" + */ + public static boolean isNo(Integer code) { + return NO.getCode().equals(code); + } +} \ No newline at end of file diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCourseController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCourseController.java index 5469105..738a2d0 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCourseController.java +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCourseController.java @@ -18,6 +18,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; +import java.util.List; +import java.util.stream.Collectors; + /** * 课程管理控制器(超管端) */ @@ -33,12 +36,12 @@ public class AdminCourseController { @PostMapping @Operation(summary = "创建课程") - public Result createCourse(@Valid @RequestBody CourseCreateRequest request) { + public Result createCourse(@Valid @RequestBody CourseCreateRequest request) { log.info("收到课程创建请求,name={}, themeId={}, gradeTags={}", request.getName(), request.getThemeId(), request.getGradeTags()); try { Course course = courseService.createSystemCourse(request); log.info("课程创建成功,id={}", course.getId()); - return Result.success(course); + return Result.success(courseService.getCourseByIdWithLessons(course.getId())); } catch (Exception e) { log.error("课程创建失败", e); throw e; @@ -47,8 +50,9 @@ public class AdminCourseController { @PutMapping("/{id}") @Operation(summary = "更新课程") - public Result updateCourse(@PathVariable Long id, @RequestBody CourseUpdateRequest request) { - return Result.success(courseService.updateCourse(id, request)); + public Result updateCourse(@PathVariable Long id, @RequestBody CourseUpdateRequest request) { + courseService.updateCourse(id, request); + return Result.success(courseService.getCourseByIdWithLessons(id)); } @GetMapping("/{id}") @@ -59,7 +63,7 @@ public class AdminCourseController { @GetMapping @Operation(summary = "分页查询课程") - public Result> getCoursePage(CoursePageQueryRequest request) { + public Result> getCoursePage(CoursePageQueryRequest request) { log.info("查询课程列表,pageNum={}, pageSize={}, keyword={}, category={}, status={}, reviewOnly={}", request.getPageNum(), request.getPageSize(), request.getKeyword(), request.getCategory(), request.getStatus(), request.getReviewOnly()); // 页码 @@ -75,7 +79,13 @@ public class AdminCourseController { request.getCategory(), request.getStatus(), request.getReviewOnly()); - PageResult result = PageResult.of(page); + + // 转换为 CourseResponse + List responseList = page.getRecords().stream() + .map(course -> courseService.getCourseByIdWithLessons(course.getId())) + .collect(Collectors.toList()); + + PageResult result = PageResult.of(responseList, page.getTotal(), page.getCurrent(), page.getSize()); log.info("课程列表查询结果,total={}, list={}", result.getTotal(), result.getList().size()); return Result.success(result); } diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCourseLessonController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCourseLessonController.java index 1ef8488..ad78053 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCourseLessonController.java +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCourseLessonController.java @@ -4,6 +4,9 @@ import com.reading.platform.common.annotation.RequireRole; import com.reading.platform.common.enums.UserRole; import com.reading.platform.common.response.Result; import com.reading.platform.dto.request.CourseLessonCreateRequest; +import com.reading.platform.dto.request.LessonStepCreateRequest; +import com.reading.platform.dto.response.CourseLessonResponse; +import com.reading.platform.dto.response.LessonStepResponse; import com.reading.platform.entity.CourseLesson; import com.reading.platform.entity.LessonStep; import com.reading.platform.service.CourseLessonService; @@ -14,6 +17,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.stream.Collectors; /** * 课程环节控制器(超管端) @@ -28,33 +32,42 @@ public class AdminCourseLessonController { @GetMapping("/{courseId}/lessons") @Operation(summary = "获取课程的所有环节") - public Result> findAll(@PathVariable Long courseId) { - return Result.success(courseLessonService.findByCourseId(courseId)); + public Result> findAll(@PathVariable Long courseId) { + List lessons = courseLessonService.findByCourseId(courseId); + List responses = lessons.stream() + .map(this::toLessonResponse) + .collect(Collectors.toList()); + return Result.success(responses); } @GetMapping("/{courseId}/lessons/{id}") @Operation(summary = "获取课程环节详情") - public Result findOne( + public Result findOne( @PathVariable Long courseId, @PathVariable Long id) { - return Result.success(courseLessonService.findById(id)); + CourseLesson lesson = courseLessonService.findById(id); + return Result.success(toLessonResponse(lesson)); } @GetMapping("/{courseId}/lessons/type/{lessonType}") @Operation(summary = "按类型获取课程环节") - public Result findByType( + public Result findByType( @PathVariable Long courseId, @PathVariable String lessonType) { - return Result.success(courseLessonService.findByType(courseId, lessonType)); + CourseLesson lesson = courseLessonService.findByType(courseId, lessonType); + if (lesson == null) { + return Result.success(null); + } + return Result.success(toLessonResponse(lesson)); } @PostMapping("/{courseId}/lessons") @Operation(summary = "创建课程环节") @RequireRole(UserRole.ADMIN) - public Result create( + public Result create( @PathVariable Long courseId, @Valid @RequestBody CourseLessonCreateRequest request) { - return Result.success(courseLessonService.create( + CourseLesson lesson = courseLessonService.create( courseId, request.getLessonType(), request.getName(), @@ -72,17 +85,18 @@ public class AdminCourseLessonController { request.getReflection(), request.getAssessmentData(), request.getUseTemplate() - )); + ); + return Result.success(toLessonResponse(lesson)); } @PutMapping("/{courseId}/lessons/{id}") @Operation(summary = "更新课程环节") @RequireRole(UserRole.ADMIN) - public Result update( + public Result update( @PathVariable Long courseId, @PathVariable Long id, @RequestBody CourseLessonCreateRequest request) { - return Result.success(courseLessonService.update( + CourseLesson lesson = courseLessonService.update( id, request.getName(), request.getDescription(), @@ -99,7 +113,8 @@ public class AdminCourseLessonController { request.getReflection(), request.getAssessmentData(), request.getUseTemplate() - )); + ); + return Result.success(toLessonResponse(lesson)); } @DeleteMapping("/{courseId}/lessons/{id}") @@ -126,44 +141,50 @@ public class AdminCourseLessonController { @GetMapping("/{courseId}/lessons/{lessonId}/steps") @Operation(summary = "获取课时的教学环节") - public Result> findSteps( + public Result> findSteps( @PathVariable Long courseId, @PathVariable Long lessonId) { - return Result.success(courseLessonService.findSteps(lessonId)); + List steps = courseLessonService.findSteps(lessonId); + List responses = steps.stream() + .map(this::toStepResponse) + .collect(Collectors.toList()); + return Result.success(responses); } @PostMapping("/{courseId}/lessons/{lessonId}/steps") @Operation(summary = "创建教学环节") @RequireRole(UserRole.ADMIN) - public Result createStep( + public Result createStep( @PathVariable Long courseId, @PathVariable Long lessonId, - @RequestBody StepCreateRequest request) { - return Result.success(courseLessonService.createStep( + @Valid @RequestBody LessonStepCreateRequest request) { + LessonStep step = courseLessonService.createStep( lessonId, request.getName(), request.getContent(), request.getDuration(), request.getObjective(), request.getResourceIds() - )); + ); + return Result.success(toStepResponse(step)); } @PutMapping("/{courseId}/lessons/steps/{stepId}") @Operation(summary = "更新教学环节") @RequireRole(UserRole.ADMIN) - public Result updateStep( + public Result updateStep( @PathVariable Long courseId, @PathVariable Long stepId, - @RequestBody StepCreateRequest request) { - return Result.success(courseLessonService.updateStep( + @Valid @RequestBody LessonStepCreateRequest request) { + LessonStep step = courseLessonService.updateStep( stepId, request.getName(), request.getContent(), request.getDuration(), request.getObjective(), request.getResourceIds() - )); + ); + return Result.success(toStepResponse(step)); } @DeleteMapping("/{courseId}/lessons/steps/{stepId}") @@ -188,24 +209,56 @@ public class AdminCourseLessonController { } /** - * 教学环节创建请求 + * 将 CourseLesson 实体转换为 CourseLessonResponse */ - public static class StepCreateRequest { - private String name; - private String content; - private Integer duration; - private String objective; - private List resourceIds; + private CourseLessonResponse toLessonResponse(CourseLesson lesson) { + // 获取教学环节 + List steps = courseLessonService.findSteps(lesson.getId()); + List stepResponses = steps.stream() + .map(this::toStepResponse) + .collect(Collectors.toList()); - public String getName() { return name; } - public void setName(String name) { this.name = name; } - public String getContent() { return content; } - public void setContent(String content) { this.content = content; } - public Integer getDuration() { return duration; } - public void setDuration(Integer duration) { this.duration = duration; } - public String getObjective() { return objective; } - public void setObjective(String objective) { this.objective = objective; } - public List getResourceIds() { return resourceIds; } - public void setResourceIds(List resourceIds) { this.resourceIds = resourceIds; } + return CourseLessonResponse.builder() + .id(lesson.getId()) + .courseId(lesson.getCourseId()) + .lessonType(lesson.getLessonType()) + .name(lesson.getName()) + .description(lesson.getDescription()) + .duration(lesson.getDuration()) + .videoPath(lesson.getVideoPath()) + .videoName(lesson.getVideoName()) + .pptPath(lesson.getPptPath()) + .pptName(lesson.getPptName()) + .pdfPath(lesson.getPdfPath()) + .pdfName(lesson.getPdfName()) + .objectives(lesson.getObjectives()) + .preparation(lesson.getPreparation()) + .extension(lesson.getExtension()) + .reflection(lesson.getReflection()) + .assessmentData(lesson.getAssessmentData()) + .useTemplate(lesson.getUseTemplate()) + .sortOrder(lesson.getSortOrder()) + .steps(stepResponses) + .createdAt(lesson.getCreatedAt()) + .updatedAt(lesson.getUpdatedAt()) + .build(); } -} + + /** + * 将 LessonStep 实体转换为 LessonStepResponse + */ + private LessonStepResponse toStepResponse(LessonStep step) { + return LessonStepResponse.builder() + .id(step.getId()) + .lessonId(step.getLessonId()) + .name(step.getName()) + .content(step.getContent()) + .duration(step.getDuration()) + .objective(step.getObjective()) + .resourceIds(step.getResourceIds()) + .sortOrder(step.getSortOrder()) + .createdAt(step.getCreatedAt()) + .updatedAt(step.getUpdatedAt()) + .build(); + } +} \ No newline at end of file diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminResourceController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminResourceController.java index 3325661..0751a4c 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminResourceController.java +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminResourceController.java @@ -5,16 +5,24 @@ import com.reading.platform.common.annotation.RequireRole; import com.reading.platform.common.enums.UserRole; import com.reading.platform.common.response.PageResult; import com.reading.platform.common.response.Result; +import com.reading.platform.dto.request.ResourceLibraryCreateRequest; +import com.reading.platform.dto.request.ResourceLibraryUpdateRequest; +import com.reading.platform.dto.request.ResourceItemCreateRequest; +import com.reading.platform.dto.request.ResourceItemUpdateRequest; +import com.reading.platform.dto.response.ResourceLibraryResponse; +import com.reading.platform.dto.response.ResourceItemResponse; import com.reading.platform.entity.ResourceItem; import com.reading.platform.entity.ResourceLibrary; import com.reading.platform.service.ResourceLibraryService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * 资源库控制器(超管端) @@ -31,44 +39,52 @@ public class AdminResourceController { @GetMapping("/libraries") @Operation(summary = "分页查询资源库") - public Result> findAllLibraries( + public Result> findAllLibraries( @RequestParam(required = false) String libraryType, @RequestParam(required = false) String keyword, @RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "10") Integer pageSize) { Page page = resourceLibraryService.findAllLibraries(libraryType, keyword, pageNum, pageSize); - return Result.success(PageResult.of(page.getRecords(), page.getTotal(), page.getCurrent(), page.getSize())); + + List responses = page.getRecords().stream() + .map(this::toLibraryResponse) + .collect(Collectors.toList()); + + return Result.success(PageResult.of(responses, page.getTotal(), page.getCurrent(), page.getSize())); } @GetMapping("/libraries/{id}") @Operation(summary = "查询资源库详情") - public Result findLibrary(@PathVariable String id) { - return Result.success(resourceLibraryService.findLibraryById(id)); + public Result findLibrary(@PathVariable String id) { + ResourceLibrary library = resourceLibraryService.findLibraryById(id); + return Result.success(toLibraryResponse(library)); } @PostMapping("/libraries") @Operation(summary = "创建资源库") @RequireRole(UserRole.ADMIN) - public Result createLibrary(@RequestBody LibraryCreateRequest request) { - return Result.success(resourceLibraryService.createLibrary( + public Result createLibrary(@Valid @RequestBody ResourceLibraryCreateRequest request) { + ResourceLibrary library = resourceLibraryService.createLibrary( request.getName(), request.getType(), request.getDescription(), request.getTenantId() - )); + ); + return Result.success(toLibraryResponse(library)); } @PutMapping("/libraries/{id}") @Operation(summary = "更新资源库") @RequireRole(UserRole.ADMIN) - public Result updateLibrary( + public Result updateLibrary( @PathVariable String id, - @RequestBody LibraryUpdateRequest request) { - return Result.success(resourceLibraryService.updateLibrary( + @Valid @RequestBody ResourceLibraryUpdateRequest request) { + ResourceLibrary library = resourceLibraryService.updateLibrary( id, request.getName(), request.getDescription() - )); + ); + return Result.success(toLibraryResponse(library)); } @DeleteMapping("/libraries/{id}") @@ -83,27 +99,33 @@ public class AdminResourceController { @GetMapping("/items") @Operation(summary = "分页查询资源项目") - public Result> findAllItems( + public Result> findAllItems( @RequestParam(required = false) String libraryId, @RequestParam(required = false) String fileType, @RequestParam(required = false) String keyword, @RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "20") Integer pageSize) { Page page = resourceLibraryService.findAllItems(libraryId, fileType, keyword, pageNum, pageSize); - return Result.success(PageResult.of(page.getRecords(), page.getTotal(), page.getCurrent(), page.getSize())); + + List responses = page.getRecords().stream() + .map(this::toItemResponse) + .collect(Collectors.toList()); + + return Result.success(PageResult.of(responses, page.getTotal(), page.getCurrent(), page.getSize())); } @GetMapping("/items/{id}") @Operation(summary = "查询资源项目详情") - public Result findItem(@PathVariable String id) { - return Result.success(resourceLibraryService.findItemById(id)); + public Result findItem(@PathVariable String id) { + ResourceItem item = resourceLibraryService.findItemById(id); + return Result.success(toItemResponse(item)); } @PostMapping("/items") @Operation(summary = "创建资源项目") @RequireRole(UserRole.ADMIN) - public Result createItem(@RequestBody ItemCreateRequest request) { - return Result.success(resourceLibraryService.createItem( + public Result createItem(@Valid @RequestBody ResourceItemCreateRequest request) { + ResourceItem item = resourceLibraryService.createItem( request.getLibraryId(), request.getTitle(), request.getFileType(), @@ -112,21 +134,23 @@ public class AdminResourceController { request.getDescription(), request.getTags(), request.getTenantId() - )); + ); + return Result.success(toItemResponse(item)); } @PutMapping("/items/{id}") @Operation(summary = "更新资源项目") @RequireRole(UserRole.ADMIN) - public Result updateItem( + public Result updateItem( @PathVariable String id, - @RequestBody ItemUpdateRequest request) { - return Result.success(resourceLibraryService.updateItem( + @Valid @RequestBody ResourceItemUpdateRequest request) { + ResourceItem item = resourceLibraryService.updateItem( id, request.getTitle(), request.getDescription(), request.getTags() - )); + ); + return Result.success(toItemResponse(item)); } @DeleteMapping("/items/{id}") @@ -154,81 +178,35 @@ public class AdminResourceController { } /** - * 资源库创建请求 + * 将 ResourceLibrary 实体转换为 ResourceLibraryResponse */ - public static class LibraryCreateRequest { - private String name; - private String type; - private String description; - private String tenantId; - - public String getName() { return name; } - public void setName(String name) { this.name = name; } - public String getType() { return type; } - public void setType(String type) { this.type = type; } - public String getDescription() { return description; } - public void setDescription(String description) { this.description = description; } - public String getTenantId() { return tenantId; } - public void setTenantId(String tenantId) { this.tenantId = tenantId; } + private ResourceLibraryResponse toLibraryResponse(ResourceLibrary library) { + return ResourceLibraryResponse.builder() + .id(library.getId()) + .tenantId(library.getTenantId()) + .name(library.getName()) + .description(library.getDescription()) + .type(library.getLibraryType()) + .createdBy(library.getCreatedBy() != null ? String.valueOf(library.getCreatedBy()) : null) + .createdAt(library.getCreatedAt()) + .updatedAt(library.getUpdatedAt()) + .build(); } /** - * 资源库更新请求 + * 将 ResourceItem 实体转换为 ResourceItemResponse */ - public static class LibraryUpdateRequest { - private String name; - private String description; - - public String getName() { return name; } - public void setName(String name) { this.name = name; } - public String getDescription() { return description; } - public void setDescription(String description) { this.description = description; } + private ResourceItemResponse toItemResponse(ResourceItem item) { + return ResourceItemResponse.builder() + .id(item.getId()) + .libraryId(item.getLibraryId()) + .tenantId(item.getTenantId()) + .type(item.getFileType()) + .name(item.getTitle()) + .description(item.getDescription()) + .status(item.getStatus()) + .createdAt(item.getCreatedAt()) + .updatedAt(item.getUpdatedAt()) + .build(); } - - /** - * 资源项目创建请求(数字资源) - */ - public static class ItemCreateRequest { - private String libraryId; - private String title; - private String fileType; - private String filePath; - private Long fileSize; - private String description; - private String tags; - private String tenantId; - - public String getLibraryId() { return libraryId; } - public void setLibraryId(String libraryId) { this.libraryId = libraryId; } - public String getTitle() { return title; } - public void setTitle(String title) { this.title = title; } - public String getFileType() { return fileType; } - public void setFileType(String fileType) { this.fileType = fileType; } - public String getFilePath() { return filePath; } - public void setFilePath(String filePath) { this.filePath = filePath; } - public Long getFileSize() { return fileSize; } - public void setFileSize(Long fileSize) { this.fileSize = fileSize; } - public String getDescription() { return description; } - public void setDescription(String description) { this.description = description; } - public String getTags() { return tags; } - public void setTags(String tags) { this.tags = tags; } - public String getTenantId() { return tenantId; } - public void setTenantId(String tenantId) { this.tenantId = tenantId; } - } - - /** - * 资源项目更新请求(数字资源) - */ - public static class ItemUpdateRequest { - private String title; - private String description; - private String tags; - - public String getTitle() { return title; } - public void setTitle(String title) { this.title = title; } - public String getDescription() { return description; } - public void setDescription(String description) { this.description = description; } - public String getTags() { return tags; } - public void setTags(String tags) { this.tags = tags; } - } -} +} \ No newline at end of file diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminStatsController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminStatsController.java index 29f1557..85b2483 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminStatsController.java +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminStatsController.java @@ -11,6 +11,7 @@ import com.reading.platform.dto.response.PopularCourseItemResponse; import com.reading.platform.dto.response.RecentActivityItemResponse; import com.reading.platform.dto.response.StatsResponse; import com.reading.platform.dto.response.StatsTrendResponse; +import com.reading.platform.service.StatsService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -19,7 +20,6 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.ArrayList; import java.util.List; /** @@ -32,55 +32,36 @@ import java.util.List; @Tag(name = "超管端 - 统计管理") public class AdminStatsController { + private final StatsService statsService; + @GetMapping @Operation(summary = "获取统计数据") public Result getStats() { - // TODO: 实现统计数据查询 - return Result.success(StatsResponse.builder() - .totalTenants(0L) - .activeTenants(0L) - .totalTeachers(0L) - .totalStudents(0L) - .totalCourses(0L) - .totalLessons(0L) - .build()); + return Result.success(statsService.getStats()); } @GetMapping("/trend") @Operation(summary = "获取趋势数据") public Result getTrendData() { - // TODO: 实现趋势数据查询 - return Result.success(StatsTrendResponse.builder() - .dates(new ArrayList<>()) - .newStudents(new ArrayList<>()) - .newTeachers(new ArrayList<>()) - .newCourses(new ArrayList<>()) - .build()); + return Result.success(statsService.getTrendData()); } @GetMapping("/tenants/active") @Operation(summary = "获取活跃租户") public Result> getActiveTenants(@ModelAttribute ActiveTenantsQueryRequest request) { - // 返回数量限制 - // TODO: 实现活跃租户查询 - return Result.success(new ArrayList<>()); + return Result.success(statsService.getActiveTenants(request)); } @GetMapping("/courses/popular") @Operation(summary = "获取热门课程") public Result> getPopularCourses(@ModelAttribute PopularCoursesQueryRequest request) { - // 返回数量限制 - // TODO: 实现热门课程查询 - return Result.success(new ArrayList<>()); + return Result.success(statsService.getPopularCourses(request)); } @GetMapping("/activities") @Operation(summary = "获取最近活动") public Result> getRecentActivities(@ModelAttribute RecentActivitiesQueryRequest request) { - // 返回数量限制 - // TODO: 实现最近活动查询 - return Result.success(new ArrayList<>()); + return Result.success(statsService.getRecentActivities(request)); } - -} +} \ No newline at end of file diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminTenantController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminTenantController.java index 74d3204..7cfb2cb 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminTenantController.java +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminTenantController.java @@ -1,20 +1,14 @@ package com.reading.platform.controller.admin; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.reading.platform.common.annotation.RequireRole; import com.reading.platform.common.enums.UserRole; -import com.reading.platform.common.mapper.TenantMapper; import com.reading.platform.common.response.PageResult; import com.reading.platform.common.response.Result; import com.reading.platform.dto.request.TenantCreateRequest; import com.reading.platform.dto.request.TenantUpdateRequest; import com.reading.platform.dto.response.TenantResponse; -import com.reading.platform.entity.Student; -import com.reading.platform.entity.Teacher; import com.reading.platform.entity.Tenant; -import com.reading.platform.mapper.StudentMapper; -import com.reading.platform.mapper.TeacherMapper; import com.reading.platform.service.TenantService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -25,6 +19,7 @@ import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @Tag(name = "超管端 - 租户管理", description = "Tenant Management APIs for Admin") @RestController @@ -34,29 +29,26 @@ import java.util.Map; public class AdminTenantController { private final TenantService tenantService; - private final TenantMapper tenantMapper; - private final TeacherMapper teacherMapper; - private final StudentMapper studentMapper; @Operation(summary = "Create tenant") @PostMapping public Result createTenant(@Valid @RequestBody TenantCreateRequest request) { Tenant tenant = tenantService.createTenant(request); - return Result.success(tenantMapper.toVO(tenant)); + return Result.success(toResponse(tenant)); } @Operation(summary = "Update tenant") @PutMapping("/{id}") public Result updateTenant(@PathVariable Long id, @RequestBody TenantUpdateRequest request) { Tenant tenant = tenantService.updateTenant(id, request); - return Result.success(tenantMapper.toVO(tenant)); + return Result.success(toResponse(tenant)); } @Operation(summary = "Get tenant by ID") @GetMapping("/{id}") public Result getTenant(@PathVariable Long id) { Tenant tenant = tenantService.getTenantById(id); - return Result.success(tenantMapper.toVO(tenant)); + return Result.success(toResponse(tenant)); } @Operation(summary = "Get tenant page") @@ -66,27 +58,15 @@ public class AdminTenantController { @RequestParam(required = false, defaultValue = "10") Integer pageSize, @RequestParam(required = false) String keyword, @RequestParam(required = false) String status) { - Page pageResult = tenantService.getTenantPage(pageNum, pageSize, keyword, status); - List voList = tenantMapper.toVO(pageResult.getRecords()); + // 调用 Service 层方法获取带统计数据的租户分页 + Page pageResult = tenantService.getTenantPageWithStats(pageNum, pageSize, keyword, status); - // 填充教师数量和学生数量 - for (TenantResponse vo : voList) { - if (vo.getId() != null) { - Long teacherCount = teacherMapper.selectCount( - new LambdaQueryWrapper() - .eq(Teacher::getTenantId, vo.getId()) - ); - vo.setTeacherCount(teacherCount != null ? teacherCount.intValue() : 0); - - Long studentCount = studentMapper.selectCount( - new LambdaQueryWrapper() - .eq(Student::getTenantId, vo.getId()) - ); - vo.setStudentCount(studentCount != null ? studentCount.intValue() : 0); - } - } - - return Result.success(PageResult.of(voList, pageResult.getTotal(), pageResult.getCurrent(), pageResult.getSize())); + return Result.success(PageResult.of( + pageResult.getRecords(), + pageResult.getTotal(), + pageResult.getCurrent(), + pageResult.getSize() + )); } @Operation(summary = "Delete tenant") @@ -100,7 +80,10 @@ public class AdminTenantController { @GetMapping("/active") public Result> getAllActiveTenants() { List tenants = tenantService.getAllActiveTenants(); - return Result.success(tenantMapper.toVO(tenants)); + List responses = tenants.stream() + .map(this::toResponse) + .collect(Collectors.toList()); + return Result.success(responses); } @Operation(summary = "获取租户统计信息") @@ -118,7 +101,7 @@ public class AdminTenantController { public Result updateTenantQuota(@PathVariable Long id, @RequestBody Map quota) { // TODO: 实现更新租户配额逻辑 Tenant tenant = tenantService.getTenantById(id); - return Result.success(tenantMapper.toVO(tenant)); + return Result.success(toResponse(tenant)); } @Operation(summary = "更新租户状态") @@ -126,7 +109,7 @@ public class AdminTenantController { public Result updateTenantStatus(@PathVariable Long id, @RequestBody Map status) { // TODO: 实现更新租户状态逻辑 Tenant tenant = tenantService.getTenantById(id); - return Result.success(tenantMapper.toVO(tenant)); + return Result.success(toResponse(tenant)); } @Operation(summary = "重置租户密码") @@ -136,4 +119,34 @@ public class AdminTenantController { return Result.success(); } -} + /** + * 将 Tenant 实体转换为 TenantResponse + */ + private TenantResponse toResponse(Tenant tenant) { + return TenantResponse.builder() + .id(tenant.getId()) + .name(tenant.getName()) + .code(tenant.getCode()) + .username(tenant.getUsername()) + .contactName(tenant.getContactName()) + .contactPhone(tenant.getContactPhone()) + .contactEmail(tenant.getContactEmail()) + .address(tenant.getAddress()) + .logoUrl(tenant.getLogoUrl()) + .status(tenant.getStatus()) + .expireAt(tenant.getExpireAt()) + .maxStudents(tenant.getMaxStudents()) + .maxTeachers(tenant.getMaxTeachers()) + .packageType(tenant.getPackageType()) + .teacherQuota(tenant.getTeacherQuota()) + .studentQuota(tenant.getStudentQuota()) + .storageQuota(tenant.getStorageQuota()) + .storageUsed(tenant.getStorageUsed()) + .startDate(tenant.getStartDate()) + .expireDate(tenant.getExpireDate()) + .createdAt(tenant.getCreatedAt()) + .updatedAt(tenant.getUpdatedAt()) + .build(); + } + +} \ No newline at end of file diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminThemeController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminThemeController.java index a5b9086..f358235 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminThemeController.java +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminThemeController.java @@ -4,6 +4,7 @@ import com.reading.platform.common.annotation.RequireRole; import com.reading.platform.common.enums.UserRole; import com.reading.platform.common.response.Result; import com.reading.platform.dto.request.ThemeCreateRequest; +import com.reading.platform.dto.response.ThemeResponse; import com.reading.platform.entity.Theme; import com.reading.platform.service.ThemeService; import io.swagger.v3.oas.annotations.Operation; @@ -13,6 +14,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.stream.Collectors; /** * 主题字典控制器(超管端) @@ -27,40 +29,47 @@ public class AdminThemeController { @GetMapping @Operation(summary = "查询所有主题") - public Result> findAll() { - return Result.success(themeService.findAll()); + public Result> findAll() { + List themes = themeService.findAll(); + List responses = themes.stream() + .map(this::toResponse) + .collect(Collectors.toList()); + return Result.success(responses); } @GetMapping("/{id}") @Operation(summary = "查询主题详情") - public Result findOne(@PathVariable Long id) { - return Result.success(themeService.findById(id)); + public Result findOne(@PathVariable Long id) { + Theme theme = themeService.findById(id); + return Result.success(toResponse(theme)); } @PostMapping @Operation(summary = "创建主题") @RequireRole(UserRole.ADMIN) - public Result create(@Valid @RequestBody ThemeCreateRequest request) { - return Result.success(themeService.create( + public Result create(@Valid @RequestBody ThemeCreateRequest request) { + Theme theme = themeService.create( request.getName(), request.getDescription(), request.getSortOrder() - )); + ); + return Result.success(toResponse(theme)); } @PutMapping("/{id}") @Operation(summary = "更新主题") @RequireRole(UserRole.ADMIN) - public Result update( + public Result update( @PathVariable Long id, @RequestBody ThemeCreateRequest request) { - return Result.success(themeService.update( + Theme theme = themeService.update( id, request.getName(), request.getDescription(), request.getSortOrder(), null - )); + ); + return Result.success(toResponse(theme)); } @DeleteMapping("/{id}") @@ -78,4 +87,19 @@ public class AdminThemeController { themeService.reorder(ids); return Result.success(); } -} + + /** + * 将 Theme 实体转换为 ThemeResponse + */ + private ThemeResponse toResponse(Theme theme) { + return ThemeResponse.builder() + .id(theme.getId()) + .name(theme.getName()) + .description(theme.getDescription()) + .sortOrder(theme.getSortOrder()) + .status(theme.getStatus()) + .createdAt(theme.getCreatedAt()) + .updatedAt(theme.getUpdatedAt()) + .build(); + } +} \ No newline at end of file diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherCourseController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherCourseController.java index aa33650..c465a5e 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherCourseController.java +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherCourseController.java @@ -1,6 +1,7 @@ package com.reading.platform.controller.teacher; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.reading.platform.common.enums.CourseStatus; import com.reading.platform.common.mapper.ClassMapper; import com.reading.platform.common.mapper.CourseMapper; import com.reading.platform.common.mapper.StudentMapper; @@ -68,7 +69,7 @@ public class TeacherCourseController { @RequestParam(required = false) String keyword, @RequestParam(required = false) String category) { Long tenantId = SecurityUtils.getCurrentTenantId(); - Page page = courseService.getCoursePage(tenantId, pageNum, pageSize, keyword, category, "published"); + Page page = courseService.getCoursePage(tenantId, pageNum, pageSize, keyword, category, CourseStatus.PUBLISHED.getCode()); List voList = courseMapper.toVO(page.getRecords()); return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize())); } diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/request/LessonStepCreateRequest.java b/reading-platform-java/src/main/java/com/reading/platform/dto/request/LessonStepCreateRequest.java new file mode 100644 index 0000000..98066bb --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/request/LessonStepCreateRequest.java @@ -0,0 +1,29 @@ +package com.reading.platform.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * 教学环节创建请求 + */ +@Data +@Schema(description = "教学环节创建请求") +public class LessonStepCreateRequest { + + @Schema(description = "环节名称") + private String name; + + @Schema(description = "环节内容") + private String content; + + @Schema(description = "时长(分钟)") + private Integer duration; + + @Schema(description = "教学目标") + private String objective; + + @Schema(description = "关联资源 ID 列表") + private List resourceIds; +} \ No newline at end of file diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/request/PackageGrantRequest.java b/reading-platform-java/src/main/java/com/reading/platform/dto/request/PackageGrantRequest.java new file mode 100644 index 0000000..8956798 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/request/PackageGrantRequest.java @@ -0,0 +1,21 @@ +package com.reading.platform.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 套餐授权请求 + */ +@Data +@Schema(description = "套餐授权请求") +public class PackageGrantRequest { + + @Schema(description = "租户 ID") + private Long tenantId; + + @Schema(description = "结束日期") + private String endDate; + + @Schema(description = "支付金额(分)") + private Long pricePaid; +} \ No newline at end of file diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceItemCreateRequest.java b/reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceItemCreateRequest.java new file mode 100644 index 0000000..8d0ec5b --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceItemCreateRequest.java @@ -0,0 +1,36 @@ +package com.reading.platform.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 资源项目创建请求 + */ +@Data +@Schema(description = "资源项目创建请求") +public class ResourceItemCreateRequest { + + @Schema(description = "资源库 ID") + private String libraryId; + + @Schema(description = "资源标题") + private String title; + + @Schema(description = "文件类型") + private String fileType; + + @Schema(description = "文件路径") + private String filePath; + + @Schema(description = "文件大小(字节)") + private Long fileSize; + + @Schema(description = "资源描述") + private String description; + + @Schema(description = "标签") + private String tags; + + @Schema(description = "租户 ID") + private String tenantId; +} \ No newline at end of file diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceItemUpdateRequest.java b/reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceItemUpdateRequest.java new file mode 100644 index 0000000..fbc8674 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceItemUpdateRequest.java @@ -0,0 +1,21 @@ +package com.reading.platform.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 资源项目更新请求 + */ +@Data +@Schema(description = "资源项目更新请求") +public class ResourceItemUpdateRequest { + + @Schema(description = "资源标题") + private String title; + + @Schema(description = "资源描述") + private String description; + + @Schema(description = "标签") + private String tags; +} \ No newline at end of file diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceLibraryCreateRequest.java b/reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceLibraryCreateRequest.java new file mode 100644 index 0000000..8eb2428 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceLibraryCreateRequest.java @@ -0,0 +1,24 @@ +package com.reading.platform.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 资源库创建请求 + */ +@Data +@Schema(description = "资源库创建请求") +public class ResourceLibraryCreateRequest { + + @Schema(description = "资源库名称") + private String name; + + @Schema(description = "资源库类型") + private String type; + + @Schema(description = "资源库描述") + private String description; + + @Schema(description = "租户 ID") + private String tenantId; +} \ No newline at end of file diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceLibraryUpdateRequest.java b/reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceLibraryUpdateRequest.java new file mode 100644 index 0000000..8c18d78 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/request/ResourceLibraryUpdateRequest.java @@ -0,0 +1,18 @@ +package com.reading.platform.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 资源库更新请求 + */ +@Data +@Schema(description = "资源库更新请求") +public class ResourceLibraryUpdateRequest { + + @Schema(description = "资源库名称") + private String name; + + @Schema(description = "资源库描述") + private String description; +} \ No newline at end of file diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/response/ResourceItemResponse.java b/reading-platform-java/src/main/java/com/reading/platform/dto/response/ResourceItemResponse.java index 12e824f..d999ba4 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/dto/response/ResourceItemResponse.java +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/response/ResourceItemResponse.java @@ -16,7 +16,7 @@ import java.time.LocalDateTime; public class ResourceItemResponse { @Schema(description = "ID") - private String id; + private Long id; @Schema(description = "资源库 ID") private String libraryId; diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/response/ResourceLibraryResponse.java b/reading-platform-java/src/main/java/com/reading/platform/dto/response/ResourceLibraryResponse.java index 1cfcbe2..69b80fa 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/dto/response/ResourceLibraryResponse.java +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/response/ResourceLibraryResponse.java @@ -16,7 +16,7 @@ import java.time.LocalDateTime; public class ResourceLibraryResponse { @Schema(description = "ID") - private String id; + private Long id; @Schema(description = "租户 ID") private String tenantId; diff --git a/reading-platform-java/src/main/java/com/reading/platform/entity/CoursePackage.java b/reading-platform-java/src/main/java/com/reading/platform/entity/CoursePackage.java index 627aad1..22f662c 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/entity/CoursePackage.java +++ b/reading-platform-java/src/main/java/com/reading/platform/entity/CoursePackage.java @@ -37,7 +37,7 @@ public class CoursePackage extends BaseEntity { @Schema(description = "课程数量") private Integer courseCount; - @Schema(description = "状态:DRAFT、PENDING_REVIEW、APPROVED、REJECTED、PUBLISHED、OFFLINE") + @Schema(description = "状态:DRAFT、PENDING、APPROVED、REJECTED、PUBLISHED、OFFLINE") private String status; @Schema(description = "提交时间") diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/StatsService.java b/reading-platform-java/src/main/java/com/reading/platform/service/StatsService.java new file mode 100644 index 0000000..08e11d5 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/StatsService.java @@ -0,0 +1,43 @@ +package com.reading.platform.service; + +import com.reading.platform.dto.request.ActiveTenantsQueryRequest; +import com.reading.platform.dto.request.PopularCoursesQueryRequest; +import com.reading.platform.dto.request.RecentActivitiesQueryRequest; +import com.reading.platform.dto.response.ActiveTenantItemResponse; +import com.reading.platform.dto.response.PopularCourseItemResponse; +import com.reading.platform.dto.response.RecentActivityItemResponse; +import com.reading.platform.dto.response.StatsResponse; +import com.reading.platform.dto.response.StatsTrendResponse; + +import java.util.List; + +/** + * 统计服务接口 + */ +public interface StatsService { + + /** + * 获取统计数据 + */ + StatsResponse getStats(); + + /** + * 获取趋势数据 + */ + StatsTrendResponse getTrendData(); + + /** + * 获取活跃租户 + */ + List getActiveTenants(ActiveTenantsQueryRequest request); + + /** + * 获取热门课程 + */ + List getPopularCourses(PopularCoursesQueryRequest request); + + /** + * 获取最近活动 + */ + List getRecentActivities(RecentActivitiesQueryRequest request); +} \ No newline at end of file diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/TenantService.java b/reading-platform-java/src/main/java/com/reading/platform/service/TenantService.java index 1b3a300..1ccff5a 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/TenantService.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/TenantService.java @@ -3,6 +3,7 @@ package com.reading.platform.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.reading.platform.dto.request.TenantCreateRequest; import com.reading.platform.dto.request.TenantUpdateRequest; +import com.reading.platform.dto.response.TenantResponse; import com.reading.platform.entity.Tenant; import java.util.List; @@ -32,6 +33,11 @@ public interface TenantService extends com.baomidou.mybatisplus.extension.servic */ Page getTenantPage(Integer pageNum, Integer pageSize, String keyword, String status); + /** + * 分页查询租户(带教师和学生统计) + */ + Page getTenantPageWithStats(Integer pageNum, Integer pageSize, String keyword, String status); + /** * 删除租户 */ diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseServiceImpl.java index c5b47b7..e6338fe 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseServiceImpl.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/CourseServiceImpl.java @@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.reading.platform.common.enums.CourseStatus; import com.reading.platform.common.enums.ErrorCode; import com.reading.platform.common.enums.TenantPackageStatus; +import com.reading.platform.common.enums.YesNo; import com.reading.platform.common.exception.BusinessException; import com.reading.platform.dto.request.CourseCreateRequest; import com.reading.platform.dto.request.CourseUpdateRequest; @@ -60,7 +61,7 @@ public class CourseServiceImpl extends ServiceImpl course.setDurationMinutes(request.getDurationMinutes()); course.setObjectives(request.getObjectives()); course.setStatus(CourseStatus.DRAFT.getCode()); - course.setIsSystem(0); + course.setIsSystem(YesNo.NO.getCode()); // Course Package Fields course.setCoreContent(request.getCoreContent()); @@ -90,10 +91,10 @@ public class CourseServiceImpl extends ServiceImpl course.setAssessmentData(nullIfEmptyJson(request.getAssessmentData())); course.setGradeTags(nullIfEmptyJson(request.getGradeTags())); course.setDomainTags(nullIfEmptyJson(request.getDomainTags())); - course.setHasCollectiveLesson(request.getHasCollectiveLesson() != null && request.getHasCollectiveLesson() ? 1 : 0); + course.setHasCollectiveLesson(YesNo.fromBoolean(request.getHasCollectiveLesson()).getCode()); course.setVersion("1.0"); - course.setIsLatest(1); + course.setIsLatest(YesNo.YES.getCode()); course.setUsageCount(0); course.setTeacherCount(0); @@ -121,7 +122,7 @@ public class CourseServiceImpl extends ServiceImpl course.setDurationMinutes(request.getDurationMinutes()); course.setObjectives(request.getObjectives()); course.setStatus(CourseStatus.DRAFT.getCode()); - course.setIsSystem(1); + course.setIsSystem(YesNo.YES.getCode()); // Course Package Fields course.setCoreContent(request.getCoreContent()); @@ -151,10 +152,10 @@ public class CourseServiceImpl extends ServiceImpl course.setAssessmentData(nullIfEmptyJson(request.getAssessmentData())); course.setGradeTags(nullIfEmptyJson(request.getGradeTags())); course.setDomainTags(nullIfEmptyJson(request.getDomainTags())); - course.setHasCollectiveLesson(request.getHasCollectiveLesson() != null && request.getHasCollectiveLesson() ? 1 : 0); + course.setHasCollectiveLesson(YesNo.fromBoolean(request.getHasCollectiveLesson()).getCode()); course.setVersion("1.0"); - course.setIsLatest(1); + course.setIsLatest(YesNo.YES.getCode()); course.setUsageCount(0); course.setTeacherCount(0); @@ -287,7 +288,7 @@ public class CourseServiceImpl extends ServiceImpl course.setDomainTags(nullIfEmptyJson(request.getDomainTags())); } if (request.getHasCollectiveLesson() != null) { - course.setHasCollectiveLesson(request.getHasCollectiveLesson() ? 1 : 0); + course.setHasCollectiveLesson(YesNo.fromBoolean(request.getHasCollectiveLesson()).getCode()); } courseMapper.updateById(course); @@ -346,7 +347,7 @@ public class CourseServiceImpl extends ServiceImpl wrapper.and(w -> w .eq(Course::getTenantId, tenantId) .or() - .eq(Course::getIsSystem, 1) + .eq(Course::getIsSystem, YesNo.YES.getCode()) ); if (StringUtils.hasText(keyword)) { @@ -374,7 +375,7 @@ public class CourseServiceImpl extends ServiceImpl LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); // 只过滤系统课程 - wrapper.eq(Course::getIsSystem, 1); + wrapper.eq(Course::getIsSystem, YesNo.YES.getCode()); // 审核管理页:仅过滤待审核和已驳回,排除已通过/已发布 if (reviewOnly) { @@ -453,7 +454,7 @@ public class CourseServiceImpl extends ServiceImpl .and(w -> w .eq(Course::getTenantId, tenantId) .or() - .eq(Course::getIsSystem, 1) + .eq(Course::getIsSystem, YesNo.YES.getCode()) ) .eq(Course::getStatus, CourseStatus.PUBLISHED.getCode()) .orderByAsc(Course::getName) diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/StatsServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/StatsServiceImpl.java new file mode 100644 index 0000000..b467ba9 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/StatsServiceImpl.java @@ -0,0 +1,224 @@ +package com.reading.platform.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.reading.platform.dto.request.ActiveTenantsQueryRequest; +import com.reading.platform.dto.request.PopularCoursesQueryRequest; +import com.reading.platform.dto.request.RecentActivitiesQueryRequest; +import com.reading.platform.dto.response.ActiveTenantItemResponse; +import com.reading.platform.dto.response.PopularCourseItemResponse; +import com.reading.platform.dto.response.RecentActivityItemResponse; +import com.reading.platform.dto.response.StatsResponse; +import com.reading.platform.dto.response.StatsTrendResponse; +import com.reading.platform.entity.Course; +import com.reading.platform.entity.CourseLesson; +import com.reading.platform.entity.Student; +import com.reading.platform.entity.Teacher; +import com.reading.platform.entity.Tenant; +import com.reading.platform.mapper.CourseLessonMapper; +import com.reading.platform.mapper.CourseMapper; +import com.reading.platform.mapper.StudentMapper; +import com.reading.platform.mapper.TeacherMapper; +import com.reading.platform.mapper.TenantMapper; +import com.reading.platform.service.StatsService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 统计服务实现类 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class StatsServiceImpl implements StatsService { + + private final TenantMapper tenantMapper; + private final TeacherMapper teacherMapper; + private final StudentMapper studentMapper; + private final CourseMapper courseMapper; + private final CourseLessonMapper courseLessonMapper; + + @Override + public StatsResponse getStats() { + log.info("获取统计数据"); + + // 租户总数 + Long totalTenants = tenantMapper.selectCount(null); + + // 活跃租户数 + Long activeTenants = tenantMapper.selectCount( + new LambdaQueryWrapper().eq(Tenant::getStatus, "ACTIVE") + ); + + // 教师总数 + Long totalTeachers = teacherMapper.selectCount(null); + + // 学生总数 + Long totalStudents = studentMapper.selectCount(null); + + // 课程总数 + Long totalCourses = courseMapper.selectCount(null); + + // 课时总数 + Long totalLessons = courseLessonMapper.selectCount(null); + + return StatsResponse.builder() + .totalTenants(totalTenants) + .activeTenants(activeTenants) + .totalTeachers(totalTeachers) + .totalStudents(totalStudents) + .totalCourses(totalCourses) + .totalLessons(totalLessons) + .build(); + } + + @Override + public StatsTrendResponse getTrendData() { + log.info("获取趋势数据"); + + LocalDateTime now = LocalDateTime.now(); + List dates = new ArrayList<>(); + List newStudents = new ArrayList<>(); + List newTeachers = new ArrayList<>(); + List newCourses = new ArrayList<>(); + + // 获取最近 7 天的趋势数据 + for (int i = 6; i >= 0; i--) { + LocalDate date = now.minusDays(i).toLocalDate(); + dates.add(date.format(DateTimeFormatter.ofPattern("MM-dd"))); + + // 当天新增学生数 + LocalDateTime dayStart = date.atStartOfDay(); + LocalDateTime dayEnd = date.plusDays(1).atStartOfDay(); + + Long students = studentMapper.selectCount( + new LambdaQueryWrapper() + .ge(Student::getCreatedAt, dayStart) + .lt(Student::getCreatedAt, dayEnd) + ); + newStudents.add(students != null ? students.intValue() : 0); + + // 当天新增教师数 + Long teachers = teacherMapper.selectCount( + new LambdaQueryWrapper() + .ge(Teacher::getCreatedAt, dayStart) + .lt(Teacher::getCreatedAt, dayEnd) + ); + newTeachers.add(teachers != null ? teachers.intValue() : 0); + + // 当天新增课程数 + Long courses = courseMapper.selectCount( + new LambdaQueryWrapper() + .ge(Course::getCreatedAt, dayStart) + .lt(Course::getCreatedAt, dayEnd) + ); + newCourses.add(courses != null ? courses.intValue() : 0); + } + + return StatsTrendResponse.builder() + .dates(dates) + .newStudents(newStudents) + .newTeachers(newTeachers) + .newCourses(newCourses) + .build(); + } + + @Override + public List getActiveTenants(ActiveTenantsQueryRequest request) { + log.info("获取活跃租户,limit={}", request != null ? request.getLimit() : 10); + + int limit = request != null && request.getLimit() != null ? request.getLimit() : 10; + + // 查询所有活跃租户 + List tenants = tenantMapper.selectList( + new LambdaQueryWrapper() + .eq(Tenant::getStatus, "ACTIVE") + .orderByDesc(Tenant::getUpdatedAt) + .last("LIMIT " + limit) + ); + + return tenants.stream().map(tenant -> { + // 查询该租户的活跃用户数(教师+学生) + Long teacherCount = teacherMapper.selectCount( + new LambdaQueryWrapper().eq(Teacher::getTenantId, tenant.getId()) + ); + Long studentCount = studentMapper.selectCount( + new LambdaQueryWrapper().eq(Student::getTenantId, tenant.getId()) + ); + + // 查询该租户使用的课程数(通过租户套餐) + // 简化处理,返回 0 + int courseCount = 0; + + return ActiveTenantItemResponse.builder() + .tenantId(tenant.getId()) + .tenantName(tenant.getName()) + .activeUsers((teacherCount != null ? teacherCount.intValue() : 0) + + (studentCount != null ? studentCount.intValue() : 0)) + .courseCount(courseCount) + .build(); + }).collect(Collectors.toList()); + } + + @Override + public List getPopularCourses(PopularCoursesQueryRequest request) { + log.info("获取热门课程,limit={}", request != null ? request.getLimit() : 10); + + int limit = request != null && request.getLimit() != null ? request.getLimit() : 10; + + // 查询使用次数最多的课程 + List courses = courseMapper.selectList( + new LambdaQueryWrapper() + .eq(Course::getIsSystem, 1) + .orderByDesc(Course::getUsageCount) + .last("LIMIT " + limit) + ); + + return courses.stream().map(course -> PopularCourseItemResponse.builder() + .courseId(course.getId()) + .courseName(course.getName()) + .usageCount(course.getUsageCount() != null ? course.getUsageCount() : 0) + .teacherCount(course.getTeacherCount() != null ? course.getTeacherCount() : 0) + .build() + ).collect(Collectors.toList()); + } + + @Override + public List getRecentActivities(RecentActivitiesQueryRequest request) { + log.info("获取最近活动,limit={}", request != null ? request.getLimit() : 10); + + int limit = request != null && request.getLimit() != null ? request.getLimit() : 10; + + // 由于没有专门的活动记录表,这里返回空列表 + // 实际项目中应该从操作日志表获取 + List activities = new ArrayList<>(); + + // 可以从最近的课程更新中生成一些活动记录 + List recentCourses = courseMapper.selectList( + new LambdaQueryWrapper() + .orderByDesc(Course::getUpdatedAt) + .last("LIMIT " + limit) + ); + + for (Course course : recentCourses) { + activities.add(RecentActivityItemResponse.builder() + .activityId(course.getId()) + .activityType("COURSE_UPDATE") + .description("课程「" + course.getName() + "」已更新") + .operatorId(course.getSubmittedBy()) + .operatorName("系统") + .operationTime(course.getUpdatedAt()) + .build()); + } + + return activities; + } +} \ No newline at end of file diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/TenantServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/TenantServiceImpl.java index 1ba5a8b..a5cf308 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/impl/TenantServiceImpl.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/TenantServiceImpl.java @@ -7,9 +7,14 @@ import com.reading.platform.common.enums.TenantPackageStatus; import com.reading.platform.common.exception.BusinessException; import com.reading.platform.dto.request.TenantCreateRequest; import com.reading.platform.dto.request.TenantUpdateRequest; +import com.reading.platform.dto.response.TenantResponse; import com.reading.platform.entity.CoursePackage; +import com.reading.platform.entity.Student; +import com.reading.platform.entity.Teacher; import com.reading.platform.entity.Tenant; import com.reading.platform.entity.TenantPackage; +import com.reading.platform.mapper.StudentMapper; +import com.reading.platform.mapper.TeacherMapper; import com.reading.platform.mapper.TenantMapper; import com.reading.platform.mapper.TenantPackageMapper; import com.reading.platform.service.CoursePackageService; @@ -22,6 +27,7 @@ import org.springframework.util.StringUtils; import java.time.LocalDate; import java.util.List; +import java.util.stream.Collectors; /** * 租户服务实现类 @@ -35,6 +41,8 @@ public class TenantServiceImpl extends com.baomidou.mybatisplus.extension.servic private final TenantMapper tenantMapper; private final TenantPackageMapper tenantPackageMapper; private final CoursePackageService coursePackageService; + private final TeacherMapper teacherMapper; + private final StudentMapper studentMapper; @Override @Transactional @@ -240,4 +248,67 @@ public class TenantServiceImpl extends com.baomidou.mybatisplus.extension.servic return tenants; } + @Override + public Page getTenantPageWithStats(Integer pageNum, Integer pageSize, String keyword, String status) { + log.debug("分页查询租户(带统计),页码:{},每页数量:{}", pageNum, pageSize); + + // 先查询租户分页数据 + Page tenantPage = getTenantPage(pageNum, pageSize, keyword, status); + + // 转换为 TenantResponse 并填充统计信息 + List responses = tenantPage.getRecords().stream() + .map(this::toResponseWithStats) + .collect(Collectors.toList()); + + // 构建返回的分页对象 + Page resultPage = new Page<>(tenantPage.getCurrent(), tenantPage.getSize(), tenantPage.getTotal()); + resultPage.setRecords(responses); + + return resultPage; + } + + /** + * 将 Tenant 实体转换为 TenantResponse(带教师和学生统计) + */ + private TenantResponse toResponseWithStats(Tenant tenant) { + // 查询教师数量 + Long teacherCount = teacherMapper.selectCount( + new LambdaQueryWrapper() + .eq(Teacher::getTenantId, tenant.getId()) + ); + + // 查询学生数量 + Long studentCount = studentMapper.selectCount( + new LambdaQueryWrapper() + .eq(Student::getTenantId, tenant.getId()) + ); + + return TenantResponse.builder() + .id(tenant.getId()) + .name(tenant.getName()) + .code(tenant.getCode()) + .username(tenant.getUsername()) + .contactName(tenant.getContactName()) + .contactPhone(tenant.getContactPhone()) + .contactEmail(tenant.getContactEmail()) + .address(tenant.getAddress()) + .logoUrl(tenant.getLogoUrl()) + .status(tenant.getStatus()) + .expireAt(tenant.getExpireAt()) + .maxStudents(tenant.getMaxStudents()) + .maxTeachers(tenant.getMaxTeachers()) + .packageType(tenant.getPackageType()) + .teacherQuota(tenant.getTeacherQuota()) + .studentQuota(tenant.getStudentQuota()) + .storageQuota(tenant.getStorageQuota()) + .storageUsed(tenant.getStorageUsed()) + .teacherCount(teacherCount != null ? teacherCount.intValue() : 0) + .studentCount(studentCount != null ? studentCount.intValue() : 0) + .startDate(tenant.getStartDate()) + .expireDate(tenant.getExpireDate()) + .createdAt(tenant.getCreatedAt()) + .updatedAt(tenant.getUpdatedAt()) + .build(); + } + }