diff --git a/reading-platform-java/Dockerfile b/reading-platform-java/Dockerfile new file mode 100644 index 0000000..103005d --- /dev/null +++ b/reading-platform-java/Dockerfile @@ -0,0 +1,13 @@ +FROM maven:3.9-eclipse-temurin-21 AS build +WORKDIR /app +COPY pom.xml . +RUN mvn dependency:go-offline -q +COPY src ./src +RUN mvn package -DskipTests -q + +FROM eclipse-temurin:21-jre-alpine +WORKDIR /app +COPY --from=build /app/target/*.jar app.jar +RUN mkdir -p /app/uploads +EXPOSE 8080 +ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=prod", "app.jar"] diff --git a/reading-platform-java/pom.xml b/reading-platform-java/pom.xml index ad46c3a..e6a7117 100644 --- a/reading-platform-java/pom.xml +++ b/reading-platform-java/pom.xml @@ -24,6 +24,8 @@ 0.12.5 4.4.0 5.8.26 + 5.2.5 + 10.8.1 @@ -32,6 +34,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-aop + org.springframework.boot spring-boot-starter-validation @@ -95,6 +101,25 @@ true + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + + org.flywaydb + flyway-core + ${flyway.version} + + + org.flywaydb + flyway-mysql + ${flyway.version} + + org.springframework.boot diff --git a/reading-platform-java/src/main/java/com/reading/platform/common/config/SecurityConfig.java b/reading-platform-java/src/main/java/com/reading/platform/common/config/SecurityConfig.java index cabafca..e244ec7 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/common/config/SecurityConfig.java +++ b/reading-platform-java/src/main/java/com/reading/platform/common/config/SecurityConfig.java @@ -40,11 +40,11 @@ public class SecurityConfig { .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth // Public endpoints - .requestMatchers("/api/auth/**").permitAll() + .requestMatchers("/api/v1/auth/**").permitAll() // Swagger/Knife4j endpoints .requestMatchers("/doc.html", "/swagger-ui/**", "/v3/api-docs/**", "/webjars/**").permitAll() - // Static resources - .requestMatchers("/static/**", "/favicon.ico").permitAll() + // Static resources - uploads + .requestMatchers("/uploads/**", "/static/**", "/favicon.ico").permitAll() // OPTIONS requests .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // All other requests require authentication diff --git a/reading-platform-java/src/main/java/com/reading/platform/common/config/WebMvcConfig.java b/reading-platform-java/src/main/java/com/reading/platform/common/config/WebMvcConfig.java new file mode 100644 index 0000000..8659597 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/common/config/WebMvcConfig.java @@ -0,0 +1,19 @@ +package com.reading.platform.common.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + @Value("${file.upload.path:/app/uploads/}") + private String uploadPath; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/uploads/**") + .addResourceLocations("file:" + uploadPath); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/common/response/Result.java b/reading-platform-java/src/main/java/com/reading/platform/common/response/Result.java index d6f8110..07b7459 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/common/response/Result.java +++ b/reading-platform-java/src/main/java/com/reading/platform/common/response/Result.java @@ -1,5 +1,6 @@ package com.reading.platform.common.response; +import com.reading.platform.common.enums.ErrorCode; import lombok.Data; import java.io.Serializable; @@ -47,4 +48,8 @@ public class Result implements Serializable { return error(500, message); } + public static Result error(ErrorCode errorCode) { + return error(errorCode.getCode(), errorCode.getMessage()); + } + } diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/FileUploadController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/FileUploadController.java new file mode 100644 index 0000000..e57617c --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/FileUploadController.java @@ -0,0 +1,38 @@ +package com.reading.platform.controller; + +import com.reading.platform.common.response.Result; +import com.reading.platform.service.FileUploadService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.HashMap; +import java.util.Map; + +@Tag(name = "File Upload", description = "File Upload APIs") +@RestController +@RequestMapping("/api/v1/files") +@RequiredArgsConstructor +public class FileUploadController { + + private final FileUploadService fileUploadService; + + @Operation(summary = "Upload file") + @PostMapping("/upload") + public Result> uploadFile(@RequestParam("file") MultipartFile file) { + String url = fileUploadService.uploadFile(file); + Map result = new HashMap<>(); + result.put("url", url); + result.put("filename", file.getOriginalFilename()); + return Result.success(result); + } + + @Operation(summary = "Delete file") + @DeleteMapping("/delete") + public Result deleteFile(@RequestParam("filePath") String filePath) { + fileUploadService.deleteFile(filePath); + return Result.success(); + } +} 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 new file mode 100644 index 0000000..94bb64c --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCourseLessonController.java @@ -0,0 +1,55 @@ +package com.reading.platform.controller.admin; + +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.entity.CourseLesson; +import com.reading.platform.service.CourseLessonService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "Admin - Course Lessons", description = "Course Lesson Management for Admin") +@RestController +@RequestMapping("/api/v1/admin/courses/{courseId}/lessons") +@RequiredArgsConstructor +@RequireRole(UserRole.ADMIN) +public class AdminCourseLessonController { + + private final CourseLessonService courseLessonService; + + @Operation(summary = "Get lessons for a course") + @GetMapping + public Result> getLessons(@PathVariable Long courseId) { + return Result.success(courseLessonService.getLessonsByCourse(courseId)); + } + + @Operation(summary = "Get lesson by ID") + @GetMapping("/{id}") + public Result getLesson(@PathVariable Long courseId, @PathVariable Long id) { + return Result.success(courseLessonService.getLessonById(id)); + } + + @Operation(summary = "Create course lesson") + @PostMapping + public Result createLesson(@PathVariable Long courseId, @RequestBody CourseLesson lesson) { + lesson.setCourseId(courseId); + return Result.success(courseLessonService.createLesson(lesson)); + } + + @Operation(summary = "Update course lesson") + @PutMapping("/{id}") + public Result updateLesson(@PathVariable Long courseId, @PathVariable Long id, @RequestBody CourseLesson lesson) { + return Result.success(courseLessonService.updateLesson(id, lesson)); + } + + @Operation(summary = "Delete course lesson") + @DeleteMapping("/{id}") + public Result deleteLesson(@PathVariable Long courseId, @PathVariable Long id) { + courseLessonService.deleteLesson(id); + return Result.success(); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCoursePackageController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCoursePackageController.java new file mode 100644 index 0000000..fb330c7 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminCoursePackageController.java @@ -0,0 +1,59 @@ +package com.reading.platform.controller.admin; + +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.response.PageResult; +import com.reading.platform.common.response.Result; +import com.reading.platform.entity.CoursePackage; +import com.reading.platform.service.CoursePackageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "Admin - Course Packages", description = "Course Package Management for Admin") +@RestController +@RequestMapping("/api/v1/admin/course-packages") +@RequiredArgsConstructor +@RequireRole(UserRole.ADMIN) +public class AdminCoursePackageController { + + private final CoursePackageService coursePackageService; + + @Operation(summary = "Get course packages") + @GetMapping + public Result> getPackages( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "20") int pageSize, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) String status) { + Page page = coursePackageService.getPackages(pageNum, pageSize, keyword, status); + return Result.success(PageResult.of(page)); + } + + @Operation(summary = "Get course package by ID") + @GetMapping("/{id}") + public Result getPackage(@PathVariable Long id) { + return Result.success(coursePackageService.getPackageById(id)); + } + + @Operation(summary = "Create course package") + @PostMapping + public Result createPackage(@RequestBody CoursePackage pkg) { + return Result.success(coursePackageService.createPackage(pkg)); + } + + @Operation(summary = "Update course package") + @PutMapping("/{id}") + public Result updatePackage(@PathVariable Long id, @RequestBody CoursePackage pkg) { + return Result.success(coursePackageService.updatePackage(id, pkg)); + } + + @Operation(summary = "Delete course package") + @DeleteMapping("/{id}") + public Result deletePackage(@PathVariable Long id) { + coursePackageService.deletePackage(id); + return Result.success(); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminOperationLogController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminOperationLogController.java new file mode 100644 index 0000000..0131daf --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminOperationLogController.java @@ -0,0 +1,34 @@ +package com.reading.platform.controller.admin; + +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.response.PageResult; +import com.reading.platform.common.response.Result; +import com.reading.platform.entity.OperationLog; +import com.reading.platform.service.OperationLogService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "Admin - Operation Logs", description = "Operation Log Management for Admin") +@RestController +@RequestMapping("/api/v1/admin/operation-logs") +@RequiredArgsConstructor +@RequireRole(UserRole.ADMIN) +public class AdminOperationLogController { + + private final OperationLogService operationLogService; + + @Operation(summary = "Get operation logs") + @GetMapping + public Result> getLogs( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "20") int pageSize, + @RequestParam(required = false) Long tenantId, + @RequestParam(required = false) String module) { + Page page = operationLogService.getLogs(pageNum, pageSize, tenantId, module); + return Result.success(PageResult.of(page)); + } +} 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 new file mode 100644 index 0000000..529ca53 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminResourceController.java @@ -0,0 +1,81 @@ +package com.reading.platform.controller.admin; + +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.response.PageResult; +import com.reading.platform.common.response.Result; +import com.reading.platform.entity.ResourceItem; +import com.reading.platform.entity.ResourceLibrary; +import com.reading.platform.service.ResourceService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "Admin - Resources", description = "Resource Library Management for Admin") +@RestController +@RequestMapping("/api/v1/admin/resources") +@RequiredArgsConstructor +@RequireRole(UserRole.ADMIN) +public class AdminResourceController { + + private final ResourceService resourceService; + + @Operation(summary = "Get all resource libraries") + @GetMapping("/libraries") + public Result> getLibraries(@RequestParam(required = false) Long tenantId) { + return Result.success(resourceService.getLibraries(tenantId)); + } + + @Operation(summary = "Create resource library") + @PostMapping("/libraries") + public Result createLibrary(@RequestBody ResourceLibrary library) { + return Result.success(resourceService.createLibrary(library)); + } + + @Operation(summary = "Update resource library") + @PutMapping("/libraries/{id}") + public Result updateLibrary(@PathVariable Long id, @RequestBody ResourceLibrary library) { + return Result.success(resourceService.updateLibrary(id, library)); + } + + @Operation(summary = "Delete resource library") + @DeleteMapping("/libraries/{id}") + public Result deleteLibrary(@PathVariable Long id) { + resourceService.deleteLibrary(id); + return Result.success(); + } + + @Operation(summary = "Get resource items") + @GetMapping("/items") + public Result> getItems( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "20") int pageSize, + @RequestParam(required = false) Long libraryId, + @RequestParam(required = false) String keyword) { + Page page = resourceService.getItems(pageNum, pageSize, libraryId, keyword); + return Result.success(PageResult.of(page)); + } + + @Operation(summary = "Create resource item") + @PostMapping("/items") + public Result createItem(@RequestBody ResourceItem item) { + return Result.success(resourceService.createItem(item)); + } + + @Operation(summary = "Update resource item") + @PutMapping("/items/{id}") + public Result updateItem(@PathVariable Long id, @RequestBody ResourceItem item) { + return Result.success(resourceService.updateItem(id, item)); + } + + @Operation(summary = "Delete resource item") + @DeleteMapping("/items/{id}") + public Result deleteItem(@PathVariable Long id) { + resourceService.deleteItem(id); + return Result.success(); + } +} 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 new file mode 100644 index 0000000..e9e8014 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminStatsController.java @@ -0,0 +1,56 @@ +package com.reading.platform.controller.admin; + +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.service.AdminStatsService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@Tag(name = "Admin - Stats", description = "Admin Statistics Dashboard") +@RestController +@RequestMapping("/api/v1/admin/stats") +@RequiredArgsConstructor +@RequireRole(UserRole.ADMIN) +public class AdminStatsController { + + private final AdminStatsService adminStatsService; + + @Operation(summary = "Get overall statistics") + @GetMapping + public Result> getStats() { + return Result.success(adminStatsService.getStats()); + } + + @Operation(summary = "Get trend data (last 6 months)") + @GetMapping("/trend") + public Result>> getTrendData() { + return Result.success(adminStatsService.getTrendData()); + } + + @Operation(summary = "Get active tenants") + @GetMapping("/tenants/active") + public Result>> getActiveTenants( + @RequestParam(defaultValue = "10") int limit) { + return Result.success(adminStatsService.getActiveTenants(limit)); + } + + @Operation(summary = "Get popular courses") + @GetMapping("/courses/popular") + public Result>> getPopularCourses( + @RequestParam(defaultValue = "10") int limit) { + return Result.success(adminStatsService.getPopularCourses(limit)); + } + + @Operation(summary = "Get recent activities") + @GetMapping("/activities") + public Result>> getActivities( + @RequestParam(defaultValue = "10") int limit) { + return Result.success(adminStatsService.getRecentActivities(limit)); + } +} 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 new file mode 100644 index 0000000..cd446c3 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/admin/AdminThemeController.java @@ -0,0 +1,54 @@ +package com.reading.platform.controller.admin; + +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.entity.Theme; +import com.reading.platform.service.ThemeService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "Admin - Themes", description = "Theme Management for Admin") +@RestController +@RequestMapping("/api/v1/admin/themes") +@RequiredArgsConstructor +@RequireRole(UserRole.ADMIN) +public class AdminThemeController { + + private final ThemeService themeService; + + @Operation(summary = "Get all themes") + @GetMapping + public Result> getThemes(@RequestParam(required = false) Boolean enabledOnly) { + return Result.success(themeService.getAllThemes(enabledOnly)); + } + + @Operation(summary = "Get theme by ID") + @GetMapping("/{id}") + public Result getTheme(@PathVariable Long id) { + return Result.success(themeService.getThemeById(id)); + } + + @Operation(summary = "Create theme") + @PostMapping + public Result createTheme(@RequestBody Theme theme) { + return Result.success(themeService.createTheme(theme)); + } + + @Operation(summary = "Update theme") + @PutMapping("/{id}") + public Result updateTheme(@PathVariable Long id, @RequestBody Theme theme) { + return Result.success(themeService.updateTheme(id, theme)); + } + + @Operation(summary = "Delete theme") + @DeleteMapping("/{id}") + public Result deleteTheme(@PathVariable Long id) { + themeService.deleteTheme(id); + return Result.success(); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/parent/ParentChildController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/parent/ParentChildController.java index 8c8af67..f2ee0f8 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/controller/parent/ParentChildController.java +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/parent/ParentChildController.java @@ -13,7 +13,7 @@ import java.util.List; @Tag(name = "Parent - Child", description = "Child Information APIs for Parent") @RestController -@RequestMapping("/api/parent/children") +@RequestMapping("/api/v1/parent/children") @RequiredArgsConstructor public class ParentChildController { diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolCourseController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolCourseController.java new file mode 100644 index 0000000..18cec05 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolCourseController.java @@ -0,0 +1,62 @@ +package com.reading.platform.controller.school; + +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.response.PageResult; +import com.reading.platform.common.response.Result; +import com.reading.platform.common.security.SecurityUtils; +import com.reading.platform.entity.SchoolCourse; +import com.reading.platform.service.SchoolCourseService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "School - School Courses", description = "School Custom Course Management") +@RestController +@RequestMapping("/api/v1/school/school-courses") +@RequiredArgsConstructor +@RequireRole(UserRole.SCHOOL) +public class SchoolCourseController { + + private final SchoolCourseService schoolCourseService; + + @Operation(summary = "Get school courses") + @GetMapping + public Result> getCourses( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "20") int pageSize, + @RequestParam(required = false) String keyword) { + Long tenantId = SecurityUtils.getCurrentTenantId(); + Page page = schoolCourseService.getCourses(pageNum, pageSize, tenantId, keyword); + return Result.success(PageResult.of(page)); + } + + @Operation(summary = "Get school course by ID") + @GetMapping("/{id}") + public Result getCourse(@PathVariable Long id) { + return Result.success(schoolCourseService.getCourseById(id)); + } + + @Operation(summary = "Create school course") + @PostMapping + public Result createCourse(@RequestBody SchoolCourse course) { + Long tenantId = SecurityUtils.getCurrentTenantId(); + Long userId = SecurityUtils.getCurrentUserId(); + return Result.success(schoolCourseService.createCourse(tenantId, userId, course)); + } + + @Operation(summary = "Update school course") + @PutMapping("/{id}") + public Result updateCourse(@PathVariable Long id, @RequestBody SchoolCourse course) { + return Result.success(schoolCourseService.updateCourse(id, course)); + } + + @Operation(summary = "Delete school course") + @DeleteMapping("/{id}") + public Result deleteCourse(@PathVariable Long id) { + schoolCourseService.deleteCourse(id); + return Result.success(); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolCoursePackageController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolCoursePackageController.java new file mode 100644 index 0000000..0ebf67f --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolCoursePackageController.java @@ -0,0 +1,39 @@ +package com.reading.platform.controller.school; + +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.response.PageResult; +import com.reading.platform.common.response.Result; +import com.reading.platform.entity.CoursePackage; +import com.reading.platform.service.CoursePackageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "School - Course Packages", description = "Course Packages for School") +@RestController +@RequestMapping("/api/v1/school/course-packages") +@RequiredArgsConstructor +@RequireRole(UserRole.SCHOOL) +public class SchoolCoursePackageController { + + private final CoursePackageService coursePackageService; + + @Operation(summary = "Get available course packages") + @GetMapping + public Result> getPackages( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "20") int pageSize, + @RequestParam(required = false) String keyword) { + Page page = coursePackageService.getPackages(pageNum, pageSize, keyword, "published"); + return Result.success(PageResult.of(page)); + } + + @Operation(summary = "Get course package by ID") + @GetMapping("/{id}") + public Result getPackage(@PathVariable Long id) { + return Result.success(coursePackageService.getPackageById(id)); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolExportController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolExportController.java new file mode 100644 index 0000000..5b46f01 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolExportController.java @@ -0,0 +1,65 @@ +package com.reading.platform.controller.school; + +import com.reading.platform.common.annotation.RequireRole; +import com.reading.platform.common.enums.UserRole; +import com.reading.platform.common.security.SecurityUtils; +import com.reading.platform.service.ExportService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.time.LocalDate; + +@Tag(name = "School - Export", description = "School Data Export") +@RestController +@RequestMapping("/api/v1/school/export") +@RequiredArgsConstructor +@RequireRole(UserRole.SCHOOL) +public class SchoolExportController { + + private final ExportService exportService; + + @Operation(summary = "Export teachers to Excel") + @GetMapping("/teachers") + public ResponseEntity exportTeachers() throws IOException { + Long tenantId = SecurityUtils.getCurrentTenantId(); + byte[] data = exportService.exportTeachers(tenantId); + return buildResponse(data, "teachers_" + LocalDate.now() + ".xlsx"); + } + + @Operation(summary = "Export students to Excel") + @GetMapping("/students") + public ResponseEntity exportStudents() throws IOException { + Long tenantId = SecurityUtils.getCurrentTenantId(); + byte[] data = exportService.exportStudents(tenantId); + return buildResponse(data, "students_" + LocalDate.now() + ".xlsx"); + } + + @Operation(summary = "Export lessons to Excel") + @GetMapping("/lessons") + public ResponseEntity exportLessons() throws IOException { + Long tenantId = SecurityUtils.getCurrentTenantId(); + byte[] data = exportService.exportLessons(tenantId); + return buildResponse(data, "lessons_" + LocalDate.now() + ".xlsx"); + } + + @Operation(summary = "Export growth records to Excel") + @GetMapping("/growth-records") + public ResponseEntity exportGrowthRecords() throws IOException { + Long tenantId = SecurityUtils.getCurrentTenantId(); + byte[] data = exportService.exportGrowthRecords(tenantId); + return buildResponse(data, "growth_records_" + LocalDate.now() + ".xlsx"); + } + + private ResponseEntity buildResponse(byte[] data, String filename) { + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"") + .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) + .body(data); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolOperationLogController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolOperationLogController.java new file mode 100644 index 0000000..23a1573 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolOperationLogController.java @@ -0,0 +1,35 @@ +package com.reading.platform.controller.school; + +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.response.PageResult; +import com.reading.platform.common.response.Result; +import com.reading.platform.common.security.SecurityUtils; +import com.reading.platform.entity.OperationLog; +import com.reading.platform.service.OperationLogService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "School - Operation Logs", description = "Operation Log for School") +@RestController +@RequestMapping("/api/v1/school/operation-logs") +@RequiredArgsConstructor +@RequireRole(UserRole.SCHOOL) +public class SchoolOperationLogController { + + private final OperationLogService operationLogService; + + @Operation(summary = "Get school operation logs") + @GetMapping + public Result> getLogs( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "20") int pageSize, + @RequestParam(required = false) String module) { + Long tenantId = SecurityUtils.getCurrentTenantId(); + Page page = operationLogService.getLogs(pageNum, pageSize, tenantId, module); + return Result.success(PageResult.of(page)); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolScheduleController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolScheduleController.java new file mode 100644 index 0000000..892affd --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolScheduleController.java @@ -0,0 +1,86 @@ +package com.reading.platform.controller.school; + +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.response.PageResult; +import com.reading.platform.common.response.Result; +import com.reading.platform.common.security.SecurityUtils; +import com.reading.platform.entity.SchedulePlan; +import com.reading.platform.entity.ScheduleTemplate; +import com.reading.platform.service.ScheduleService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "School - Schedule", description = "School Schedule Management") +@RestController +@RequestMapping("/api/v1/school/schedules") +@RequiredArgsConstructor +@RequireRole(UserRole.SCHOOL) +public class SchoolScheduleController { + + private final ScheduleService scheduleService; + + @Operation(summary = "Get schedule plans") + @GetMapping + public Result> getSchedulePlans( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "20") int pageSize, + @RequestParam(required = false) Long classId) { + Long tenantId = SecurityUtils.getCurrentTenantId(); + Page page = scheduleService.getSchedulePlans(pageNum, pageSize, tenantId, classId); + return Result.success(PageResult.of(page)); + } + + @Operation(summary = "Get schedule plan by ID") + @GetMapping("/{id}") + public Result getSchedulePlan(@PathVariable Long id) { + return Result.success(scheduleService.getSchedulePlanById(id)); + } + + @Operation(summary = "Create schedule plan") + @PostMapping + public Result createSchedulePlan(@RequestBody SchedulePlan plan) { + Long tenantId = SecurityUtils.getCurrentTenantId(); + return Result.success(scheduleService.createSchedulePlan(tenantId, plan)); + } + + @Operation(summary = "Update schedule plan") + @PutMapping("/{id}") + public Result updateSchedulePlan(@PathVariable Long id, @RequestBody SchedulePlan plan) { + return Result.success(scheduleService.updateSchedulePlan(id, plan)); + } + + @Operation(summary = "Delete schedule plan") + @DeleteMapping("/{id}") + public Result deleteSchedulePlan(@PathVariable Long id) { + scheduleService.deleteSchedulePlan(id); + return Result.success(); + } + + @Operation(summary = "Get schedule templates") + @GetMapping("/templates") + public Result> getScheduleTemplates( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "20") int pageSize) { + Long tenantId = SecurityUtils.getCurrentTenantId(); + Page page = scheduleService.getScheduleTemplates(pageNum, pageSize, tenantId); + return Result.success(PageResult.of(page)); + } + + @Operation(summary = "Create schedule template") + @PostMapping("/templates") + public Result createScheduleTemplate(@RequestBody ScheduleTemplate template) { + Long tenantId = SecurityUtils.getCurrentTenantId(); + return Result.success(scheduleService.createScheduleTemplate(tenantId, template)); + } + + @Operation(summary = "Delete schedule template") + @DeleteMapping("/templates/{id}") + public Result deleteScheduleTemplate(@PathVariable Long id) { + scheduleService.deleteScheduleTemplate(id); + return Result.success(); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolSettingsController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolSettingsController.java new file mode 100644 index 0000000..3cce3cc --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolSettingsController.java @@ -0,0 +1,38 @@ +package com.reading.platform.controller.school; + +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.common.security.SecurityUtils; +import com.reading.platform.service.SystemSettingService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@Tag(name = "School - Settings", description = "School Settings Management") +@RestController +@RequestMapping("/api/v1/school/settings") +@RequiredArgsConstructor +@RequireRole(UserRole.SCHOOL) +public class SchoolSettingsController { + + private final SystemSettingService systemSettingService; + + @Operation(summary = "Get school settings") + @GetMapping + public Result> getSettings() { + Long tenantId = SecurityUtils.getCurrentTenantId(); + return Result.success(systemSettingService.getSettings(tenantId)); + } + + @Operation(summary = "Update school settings") + @PutMapping + public Result updateSettings(@RequestBody Map settings) { + Long tenantId = SecurityUtils.getCurrentTenantId(); + systemSettingService.updateSettings(tenantId, settings); + return Result.success(); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolStatsController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolStatsController.java new file mode 100644 index 0000000..aa1d414 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolStatsController.java @@ -0,0 +1,30 @@ +package com.reading.platform.controller.school; + +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.common.security.SecurityUtils; +import com.reading.platform.service.SchoolStatsService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@Tag(name = "School - Stats", description = "School Statistics Dashboard") +@RestController +@RequestMapping("/api/v1/school/stats") +@RequiredArgsConstructor +@RequireRole(UserRole.SCHOOL) +public class SchoolStatsController { + + private final SchoolStatsService schoolStatsService; + + @Operation(summary = "Get school statistics") + @GetMapping + public Result> getStats() { + Long tenantId = SecurityUtils.getCurrentTenantId(); + return Result.success(schoolStatsService.getStats(tenantId)); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherCourseLessonController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherCourseLessonController.java new file mode 100644 index 0000000..9ac6a60 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherCourseLessonController.java @@ -0,0 +1,35 @@ +package com.reading.platform.controller.teacher; + +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.entity.CourseLesson; +import com.reading.platform.service.CourseLessonService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "Teacher - Course Lessons", description = "Course Lessons for Teacher") +@RestController +@RequestMapping("/api/v1/teacher/courses/{courseId}/lessons") +@RequiredArgsConstructor +@RequireRole(UserRole.TEACHER) +public class TeacherCourseLessonController { + + private final CourseLessonService courseLessonService; + + @Operation(summary = "Get lessons for a course") + @GetMapping + public Result> getLessons(@PathVariable Long courseId) { + return Result.success(courseLessonService.getLessonsByCourse(courseId)); + } + + @Operation(summary = "Get lesson by ID") + @GetMapping("/{id}") + public Result getLesson(@PathVariable Long courseId, @PathVariable Long id) { + return Result.success(courseLessonService.getLessonById(id)); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherDashboardController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherDashboardController.java new file mode 100644 index 0000000..cdad03e --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherDashboardController.java @@ -0,0 +1,48 @@ +package com.reading.platform.controller.teacher; + +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.common.security.SecurityUtils; +import com.reading.platform.service.TeacherDashboardService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@Tag(name = "Teacher - Dashboard", description = "Teacher Dashboard") +@RestController +@RequestMapping("/api/v1/teacher/dashboard") +@RequiredArgsConstructor +@RequireRole(UserRole.TEACHER) +public class TeacherDashboardController { + + private final TeacherDashboardService teacherDashboardService; + + @Operation(summary = "Get teacher dashboard overview") + @GetMapping + public Result> getDashboard() { + Long teacherId = SecurityUtils.getCurrentUserId(); + Long tenantId = SecurityUtils.getCurrentTenantId(); + return Result.success(teacherDashboardService.getDashboard(teacherId, tenantId)); + } + + @Operation(summary = "Get today's lessons") + @GetMapping("/today") + public Result>> getTodayLessons() { + Long teacherId = SecurityUtils.getCurrentUserId(); + Long tenantId = SecurityUtils.getCurrentTenantId(); + return Result.success(teacherDashboardService.getTodayLessons(teacherId, tenantId)); + } + + @Operation(summary = "Get weekly lessons") + @GetMapping("/weekly") + public Result>> getWeeklyLessons() { + Long teacherId = SecurityUtils.getCurrentUserId(); + Long tenantId = SecurityUtils.getCurrentTenantId(); + return Result.success(teacherDashboardService.getWeeklyLessons(teacherId, tenantId)); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherScheduleController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherScheduleController.java new file mode 100644 index 0000000..95abcd3 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherScheduleController.java @@ -0,0 +1,40 @@ +package com.reading.platform.controller.teacher; + +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.response.PageResult; +import com.reading.platform.common.response.Result; +import com.reading.platform.common.security.SecurityUtils; +import com.reading.platform.entity.SchedulePlan; +import com.reading.platform.service.ScheduleService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "Teacher - Schedule", description = "Teacher Schedule View") +@RestController +@RequestMapping("/api/v1/teacher/schedules") +@RequiredArgsConstructor +@RequireRole(UserRole.TEACHER) +public class TeacherScheduleController { + + private final ScheduleService scheduleService; + + @Operation(summary = "Get teacher schedule plans") + @GetMapping + public Result> getSchedulePlans( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "20") int pageSize) { + Long tenantId = SecurityUtils.getCurrentTenantId(); + Page page = scheduleService.getSchedulePlans(pageNum, pageSize, tenantId, null); + return Result.success(PageResult.of(page)); + } + + @Operation(summary = "Get schedule plan by ID") + @GetMapping("/{id}") + public Result getSchedulePlan(@PathVariable Long id) { + return Result.success(scheduleService.getSchedulePlanById(id)); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherSchoolCourseController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherSchoolCourseController.java new file mode 100644 index 0000000..cc17950 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/teacher/TeacherSchoolCourseController.java @@ -0,0 +1,41 @@ +package com.reading.platform.controller.teacher; + +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.response.PageResult; +import com.reading.platform.common.response.Result; +import com.reading.platform.common.security.SecurityUtils; +import com.reading.platform.entity.SchoolCourse; +import com.reading.platform.service.SchoolCourseService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "Teacher - School Courses", description = "School Courses for Teacher") +@RestController +@RequestMapping("/api/v1/teacher/school-courses") +@RequiredArgsConstructor +@RequireRole(UserRole.TEACHER) +public class TeacherSchoolCourseController { + + private final SchoolCourseService schoolCourseService; + + @Operation(summary = "Get school courses") + @GetMapping + public Result> getCourses( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "20") int pageSize, + @RequestParam(required = false) String keyword) { + Long tenantId = SecurityUtils.getCurrentTenantId(); + Page page = schoolCourseService.getCourses(pageNum, pageSize, tenantId, keyword); + return Result.success(PageResult.of(page)); + } + + @Operation(summary = "Get school course by ID") + @GetMapping("/{id}") + public Result getCourse(@PathVariable Long id) { + return Result.success(schoolCourseService.getCourseById(id)); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/entity/CourseLesson.java b/reading-platform-java/src/main/java/com/reading/platform/entity/CourseLesson.java new file mode 100644 index 0000000..347121f --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/entity/CourseLesson.java @@ -0,0 +1,42 @@ +package com.reading.platform.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * Course Lesson Entity (chapters/sections within a course) + */ +@Data +@TableName("course_lessons") +public class CourseLesson { + + @TableId(type = IdType.AUTO) + private Long id; + + private Long courseId; + + private String title; + + private String description; + + private String content; + + private Integer sortOrder; + + private Integer durationMinutes; + + private String videoUrl; + + private String status; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableLogic + private Integer deleted; +} 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 new file mode 100644 index 0000000..fad4ca3 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/entity/CoursePackage.java @@ -0,0 +1,41 @@ +package com.reading.platform.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * Course Package Entity + */ +@Data +@TableName("course_packages") +public class CoursePackage { + + @TableId(type = IdType.AUTO) + private Long id; + + private String name; + + private String description; + + private String coverUrl; + + private Integer courseCount; + + private BigDecimal price; + + private String status; + + private Integer isSystem; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableLogic + private Integer deleted; +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/entity/SchoolCourse.java b/reading-platform-java/src/main/java/com/reading/platform/entity/SchoolCourse.java new file mode 100644 index 0000000..2a01a26 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/entity/SchoolCourse.java @@ -0,0 +1,42 @@ +package com.reading.platform.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * School Course Entity (tenant-customized courses) + */ +@Data +@TableName("school_courses") +public class SchoolCourse { + + @TableId(type = IdType.AUTO) + private Long id; + + private Long tenantId; + + private String name; + + private String description; + + private String coverUrl; + + private String category; + + private String ageRange; + + private String status; + + private Long createdBy; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableLogic + private Integer deleted; +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/entity/Theme.java b/reading-platform-java/src/main/java/com/reading/platform/entity/Theme.java new file mode 100644 index 0000000..fa2b779 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/entity/Theme.java @@ -0,0 +1,38 @@ +package com.reading.platform.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * Theme Entity + */ +@Data +@TableName("themes") +public class Theme { + + @TableId(type = IdType.AUTO) + private Long id; + + private String name; + + private String displayName; + + private String color; + + private String icon; + + private Integer sortOrder; + + private Integer isEnabled; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableLogic + private Integer deleted; +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/mapper/CourseLessonMapper.java b/reading-platform-java/src/main/java/com/reading/platform/mapper/CourseLessonMapper.java new file mode 100644 index 0000000..e64be73 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/mapper/CourseLessonMapper.java @@ -0,0 +1,9 @@ +package com.reading.platform.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.reading.platform.entity.CourseLesson; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CourseLessonMapper extends BaseMapper { +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/mapper/CoursePackageMapper.java b/reading-platform-java/src/main/java/com/reading/platform/mapper/CoursePackageMapper.java new file mode 100644 index 0000000..e484a3f --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/mapper/CoursePackageMapper.java @@ -0,0 +1,9 @@ +package com.reading.platform.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.reading.platform.entity.CoursePackage; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CoursePackageMapper extends BaseMapper { +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/mapper/SchoolCourseMapper.java b/reading-platform-java/src/main/java/com/reading/platform/mapper/SchoolCourseMapper.java new file mode 100644 index 0000000..5a2d9b9 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/mapper/SchoolCourseMapper.java @@ -0,0 +1,9 @@ +package com.reading.platform.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.reading.platform.entity.SchoolCourse; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SchoolCourseMapper extends BaseMapper { +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/mapper/ThemeMapper.java b/reading-platform-java/src/main/java/com/reading/platform/mapper/ThemeMapper.java new file mode 100644 index 0000000..1f12c0b --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/mapper/ThemeMapper.java @@ -0,0 +1,9 @@ +package com.reading.platform.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.reading.platform.entity.Theme; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ThemeMapper extends BaseMapper { +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/CourseLessonService.java b/reading-platform-java/src/main/java/com/reading/platform/service/CourseLessonService.java new file mode 100644 index 0000000..4504c46 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/CourseLessonService.java @@ -0,0 +1,49 @@ +package com.reading.platform.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.reading.platform.common.exception.BusinessException; +import com.reading.platform.common.enums.ErrorCode; +import com.reading.platform.entity.CourseLesson; +import com.reading.platform.mapper.CourseLessonMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class CourseLessonService { + + private final CourseLessonMapper courseLessonMapper; + + public List getLessonsByCourse(Long courseId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CourseLesson::getCourseId, courseId) + .orderByAsc(CourseLesson::getSortOrder); + return courseLessonMapper.selectList(wrapper); + } + + public CourseLesson getLessonById(Long id) { + CourseLesson lesson = courseLessonMapper.selectById(id); + if (lesson == null) { + throw new BusinessException(ErrorCode.DATA_NOT_FOUND, "Course lesson not found"); + } + return lesson; + } + + public CourseLesson createLesson(CourseLesson lesson) { + courseLessonMapper.insert(lesson); + return lesson; + } + + public CourseLesson updateLesson(Long id, CourseLesson lesson) { + getLessonById(id); + lesson.setId(id); + courseLessonMapper.updateById(lesson); + return courseLessonMapper.selectById(id); + } + + public void deleteLesson(Long id) { + courseLessonMapper.deleteById(id); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/CoursePackageService.java b/reading-platform-java/src/main/java/com/reading/platform/service/CoursePackageService.java new file mode 100644 index 0000000..163980a --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/CoursePackageService.java @@ -0,0 +1,54 @@ +package com.reading.platform.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.reading.platform.common.exception.BusinessException; +import com.reading.platform.common.enums.ErrorCode; +import com.reading.platform.entity.CoursePackage; +import com.reading.platform.mapper.CoursePackageMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CoursePackageService { + + private final CoursePackageMapper coursePackageMapper; + + public Page getPackages(int pageNum, int pageSize, String keyword, String status) { + Page page = new Page<>(pageNum, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (keyword != null && !keyword.isEmpty()) { + wrapper.like(CoursePackage::getName, keyword); + } + if (status != null && !status.isEmpty()) { + wrapper.eq(CoursePackage::getStatus, status); + } + wrapper.orderByDesc(CoursePackage::getCreatedAt); + return coursePackageMapper.selectPage(page, wrapper); + } + + public CoursePackage getPackageById(Long id) { + CoursePackage pkg = coursePackageMapper.selectById(id); + if (pkg == null) { + throw new BusinessException(ErrorCode.DATA_NOT_FOUND, "Course package not found"); + } + return pkg; + } + + public CoursePackage createPackage(CoursePackage pkg) { + coursePackageMapper.insert(pkg); + return pkg; + } + + public CoursePackage updatePackage(Long id, CoursePackage pkg) { + getPackageById(id); + pkg.setId(id); + coursePackageMapper.updateById(pkg); + return coursePackageMapper.selectById(id); + } + + public void deletePackage(Long id) { + coursePackageMapper.deleteById(id); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/ExportService.java b/reading-platform-java/src/main/java/com/reading/platform/service/ExportService.java new file mode 100644 index 0000000..6ea6372 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/ExportService.java @@ -0,0 +1,141 @@ +package com.reading.platform.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.reading.platform.entity.*; +import com.reading.platform.mapper.*; +import lombok.RequiredArgsConstructor; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ExportService { + + private final TeacherMapper teacherMapper; + private final StudentMapper studentMapper; + private final LessonMapper lessonMapper; + private final GrowthRecordMapper growthRecordMapper; + + public byte[] exportTeachers(Long tenantId) throws IOException { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Teacher::getTenantId, tenantId); + List teachers = teacherMapper.selectList(wrapper); + + try (Workbook workbook = new XSSFWorkbook()) { + Sheet sheet = workbook.createSheet("Teachers"); + String[] headers = {"ID", "Name", "Username", "Phone", "Email", "Gender", "Status"}; + createHeaderRow(sheet, headers); + + int rowNum = 1; + for (Teacher t : teachers) { + Row row = sheet.createRow(rowNum++); + row.createCell(0).setCellValue(t.getId()); + row.createCell(1).setCellValue(t.getName() != null ? t.getName() : ""); + row.createCell(2).setCellValue(t.getUsername() != null ? t.getUsername() : ""); + row.createCell(3).setCellValue(t.getPhone() != null ? t.getPhone() : ""); + row.createCell(4).setCellValue(t.getEmail() != null ? t.getEmail() : ""); + row.createCell(5).setCellValue(t.getGender() != null ? t.getGender() : ""); + row.createCell(6).setCellValue(t.getStatus() != null ? t.getStatus() : ""); + } + return toBytes(workbook); + } + } + + public byte[] exportStudents(Long tenantId) throws IOException { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Student::getTenantId, tenantId); + List students = studentMapper.selectList(wrapper); + + try (Workbook workbook = new XSSFWorkbook()) { + Sheet sheet = workbook.createSheet("Students"); + String[] headers = {"ID", "Name", "Gender", "Birth Date", "Grade", "Student No", "Status"}; + createHeaderRow(sheet, headers); + + int rowNum = 1; + for (Student s : students) { + Row row = sheet.createRow(rowNum++); + row.createCell(0).setCellValue(s.getId()); + row.createCell(1).setCellValue(s.getName() != null ? s.getName() : ""); + row.createCell(2).setCellValue(s.getGender() != null ? s.getGender() : ""); + row.createCell(3).setCellValue(s.getBirthDate() != null ? s.getBirthDate().toString() : ""); + row.createCell(4).setCellValue(s.getGrade() != null ? s.getGrade() : ""); + row.createCell(5).setCellValue(s.getStudentNo() != null ? s.getStudentNo() : ""); + row.createCell(6).setCellValue(s.getStatus() != null ? s.getStatus() : ""); + } + return toBytes(workbook); + } + } + + public byte[] exportLessons(Long tenantId) throws IOException { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Lesson::getTenantId, tenantId); + List lessons = lessonMapper.selectList(wrapper); + + try (Workbook workbook = new XSSFWorkbook()) { + Sheet sheet = workbook.createSheet("Lessons"); + String[] headers = {"ID", "Title", "Lesson Date", "Start Time", "End Time", "Location", "Status"}; + createHeaderRow(sheet, headers); + + int rowNum = 1; + for (Lesson l : lessons) { + Row row = sheet.createRow(rowNum++); + row.createCell(0).setCellValue(l.getId()); + row.createCell(1).setCellValue(l.getTitle() != null ? l.getTitle() : ""); + row.createCell(2).setCellValue(l.getLessonDate() != null ? l.getLessonDate().toString() : ""); + row.createCell(3).setCellValue(l.getStartTime() != null ? l.getStartTime().toString() : ""); + row.createCell(4).setCellValue(l.getEndTime() != null ? l.getEndTime().toString() : ""); + row.createCell(5).setCellValue(l.getLocation() != null ? l.getLocation() : ""); + row.createCell(6).setCellValue(l.getStatus() != null ? l.getStatus() : ""); + } + return toBytes(workbook); + } + } + + public byte[] exportGrowthRecords(Long tenantId) throws IOException { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(GrowthRecord::getTenantId, tenantId); + List records = growthRecordMapper.selectList(wrapper); + + try (Workbook workbook = new XSSFWorkbook()) { + Sheet sheet = workbook.createSheet("Growth Records"); + String[] headers = {"ID", "Student ID", "Type", "Title", "Content", "Record Date"}; + createHeaderRow(sheet, headers); + + int rowNum = 1; + for (GrowthRecord g : records) { + Row row = sheet.createRow(rowNum++); + row.createCell(0).setCellValue(g.getId()); + row.createCell(1).setCellValue(g.getStudentId()); + row.createCell(2).setCellValue(g.getType() != null ? g.getType() : ""); + row.createCell(3).setCellValue(g.getTitle() != null ? g.getTitle() : ""); + row.createCell(4).setCellValue(g.getContent() != null ? g.getContent() : ""); + row.createCell(5).setCellValue(g.getRecordDate() != null ? g.getRecordDate().toString() : ""); + } + return toBytes(workbook); + } + } + + private void createHeaderRow(Sheet sheet, String[] headers) { + Row headerRow = sheet.createRow(0); + CellStyle style = sheet.getWorkbook().createCellStyle(); + Font font = sheet.getWorkbook().createFont(); + font.setBold(true); + style.setFont(font); + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(style); + } + } + + private byte[] toBytes(Workbook workbook) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + workbook.write(out); + return out.toByteArray(); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/FileUploadService.java b/reading-platform-java/src/main/java/com/reading/platform/service/FileUploadService.java new file mode 100644 index 0000000..fc1e294 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/FileUploadService.java @@ -0,0 +1,71 @@ +package com.reading.platform.service; + +import com.reading.platform.common.exception.BusinessException; +import com.reading.platform.common.enums.ErrorCode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +@Slf4j +@Service +public class FileUploadService { + + @Value("${file.upload.path:/app/uploads/}") + private String uploadPath; + + @Value("${file.upload.base-url:/uploads/}") + private String baseUrl; + + public String uploadFile(MultipartFile file) { + if (file == null || file.isEmpty()) { + throw new BusinessException(ErrorCode.INVALID_PARAMETER, "File cannot be empty"); + } + + String originalFilename = file.getOriginalFilename(); + String extension = ""; + if (originalFilename != null && originalFilename.contains(".")) { + extension = originalFilename.substring(originalFilename.lastIndexOf(".")); + } + + String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); + String newFilename = UUID.randomUUID().toString().replace("-", "") + extension; + String relativePath = datePath + "/" + newFilename; + String fullPath = uploadPath + relativePath; + + try { + Path targetPath = Paths.get(fullPath); + Files.createDirectories(targetPath.getParent()); + file.transferTo(targetPath.toFile()); + log.info("File uploaded: {}", fullPath); + return baseUrl + relativePath; + } catch (IOException e) { + log.error("File upload failed", e); + throw new BusinessException(ErrorCode.INTERNAL_ERROR, "File upload failed: " + e.getMessage()); + } + } + + public void deleteFile(String filePath) { + if (filePath == null || filePath.isEmpty()) { + return; + } + String relativePath = filePath.startsWith(baseUrl) ? filePath.substring(baseUrl.length()) : filePath; + String fullPath = uploadPath + relativePath; + File file = new File(fullPath); + if (file.exists()) { + boolean deleted = file.delete(); + if (!deleted) { + log.warn("Failed to delete file: {}", fullPath); + } + } + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/OperationLogService.java b/reading-platform-java/src/main/java/com/reading/platform/service/OperationLogService.java new file mode 100644 index 0000000..dfbc45e --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/OperationLogService.java @@ -0,0 +1,48 @@ +package com.reading.platform.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.reading.platform.common.security.SecurityUtils; +import com.reading.platform.entity.OperationLog; +import com.reading.platform.mapper.OperationLogMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class OperationLogService { + + private final OperationLogMapper operationLogMapper; + + public Page getLogs(int pageNum, int pageSize, Long tenantId, String module) { + Page page = new Page<>(pageNum, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (tenantId != null) { + wrapper.eq(OperationLog::getTenantId, tenantId); + } + if (module != null && !module.isEmpty()) { + wrapper.eq(OperationLog::getModule, module); + } + wrapper.orderByDesc(OperationLog::getCreatedAt); + return operationLogMapper.selectPage(page, wrapper); + } + + public void log(String action, String module, String targetType, Long targetId, String details) { + try { + OperationLog log = new OperationLog(); + log.setAction(action); + log.setModule(module); + log.setTargetType(targetType); + log.setTargetId(targetId); + log.setDetails(details); + try { + log.setUserId(SecurityUtils.getCurrentUserId()); + log.setUserRole(SecurityUtils.getCurrentRole()); + log.setTenantId(SecurityUtils.getCurrentTenantId()); + } catch (Exception ignored) {} + operationLogMapper.insert(log); + } catch (Exception e) { + // Log silently - don't fail main operations + } + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/ResourceService.java b/reading-platform-java/src/main/java/com/reading/platform/service/ResourceService.java new file mode 100644 index 0000000..feeef2c --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/ResourceService.java @@ -0,0 +1,92 @@ +package com.reading.platform.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.reading.platform.common.exception.BusinessException; +import com.reading.platform.common.enums.ErrorCode; +import com.reading.platform.entity.ResourceItem; +import com.reading.platform.entity.ResourceLibrary; +import com.reading.platform.mapper.ResourceItemMapper; +import com.reading.platform.mapper.ResourceLibraryMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ResourceService { + + private final ResourceLibraryMapper resourceLibraryMapper; + private final ResourceItemMapper resourceItemMapper; + + public List getLibraries(Long tenantId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (tenantId != null) { + wrapper.eq(ResourceLibrary::getTenantId, tenantId); + } + wrapper.orderByDesc(ResourceLibrary::getCreatedAt); + return resourceLibraryMapper.selectList(wrapper); + } + + public ResourceLibrary getLibraryById(Long id) { + ResourceLibrary lib = resourceLibraryMapper.selectById(id); + if (lib == null) { + throw new BusinessException(ErrorCode.DATA_NOT_FOUND, "Resource library not found"); + } + return lib; + } + + public ResourceLibrary createLibrary(ResourceLibrary library) { + resourceLibraryMapper.insert(library); + return library; + } + + public ResourceLibrary updateLibrary(Long id, ResourceLibrary library) { + getLibraryById(id); + library.setId(id); + resourceLibraryMapper.updateById(library); + return resourceLibraryMapper.selectById(id); + } + + public void deleteLibrary(Long id) { + resourceLibraryMapper.deleteById(id); + } + + public Page getItems(int pageNum, int pageSize, Long libraryId, String keyword) { + Page page = new Page<>(pageNum, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (libraryId != null) { + wrapper.eq(ResourceItem::getLibraryId, libraryId); + } + if (keyword != null && !keyword.isEmpty()) { + wrapper.like(ResourceItem::getName, keyword); + } + wrapper.orderByDesc(ResourceItem::getCreatedAt); + return resourceItemMapper.selectPage(page, wrapper); + } + + public ResourceItem getItemById(Long id) { + ResourceItem item = resourceItemMapper.selectById(id); + if (item == null) { + throw new BusinessException(ErrorCode.DATA_NOT_FOUND, "Resource item not found"); + } + return item; + } + + public ResourceItem createItem(ResourceItem item) { + resourceItemMapper.insert(item); + return item; + } + + public ResourceItem updateItem(Long id, ResourceItem item) { + getItemById(id); + item.setId(id); + resourceItemMapper.updateById(item); + return resourceItemMapper.selectById(id); + } + + public void deleteItem(Long id) { + resourceItemMapper.deleteById(id); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/ScheduleService.java b/reading-platform-java/src/main/java/com/reading/platform/service/ScheduleService.java new file mode 100644 index 0000000..813dd76 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/ScheduleService.java @@ -0,0 +1,75 @@ +package com.reading.platform.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.reading.platform.common.exception.BusinessException; +import com.reading.platform.common.enums.ErrorCode; +import com.reading.platform.entity.SchedulePlan; +import com.reading.platform.entity.ScheduleTemplate; +import com.reading.platform.mapper.SchedulePlanMapper; +import com.reading.platform.mapper.ScheduleTemplateMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ScheduleService { + + private final SchedulePlanMapper schedulePlanMapper; + private final ScheduleTemplateMapper scheduleTemplateMapper; + + public Page getSchedulePlans(int pageNum, int pageSize, Long tenantId, Long classId) { + Page page = new Page<>(pageNum, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SchedulePlan::getTenantId, tenantId); + if (classId != null) { + wrapper.eq(SchedulePlan::getClassId, classId); + } + wrapper.orderByDesc(SchedulePlan::getCreatedAt); + return schedulePlanMapper.selectPage(page, wrapper); + } + + public SchedulePlan getSchedulePlanById(Long id) { + SchedulePlan plan = schedulePlanMapper.selectById(id); + if (plan == null) { + throw new BusinessException(ErrorCode.DATA_NOT_FOUND, "Schedule plan not found"); + } + return plan; + } + + public SchedulePlan createSchedulePlan(Long tenantId, SchedulePlan plan) { + plan.setTenantId(tenantId); + schedulePlanMapper.insert(plan); + return plan; + } + + public SchedulePlan updateSchedulePlan(Long id, SchedulePlan plan) { + SchedulePlan existing = getSchedulePlanById(id); + plan.setId(id); + plan.setTenantId(existing.getTenantId()); + schedulePlanMapper.updateById(plan); + return schedulePlanMapper.selectById(id); + } + + public void deleteSchedulePlan(Long id) { + schedulePlanMapper.deleteById(id); + } + + public Page getScheduleTemplates(int pageNum, int pageSize, Long tenantId) { + Page page = new Page<>(pageNum, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ScheduleTemplate::getTenantId, tenantId) + .orderByDesc(ScheduleTemplate::getCreatedAt); + return scheduleTemplateMapper.selectPage(page, wrapper); + } + + public ScheduleTemplate createScheduleTemplate(Long tenantId, ScheduleTemplate template) { + template.setTenantId(tenantId); + scheduleTemplateMapper.insert(template); + return template; + } + + public void deleteScheduleTemplate(Long id) { + scheduleTemplateMapper.deleteById(id); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/SchoolCourseService.java b/reading-platform-java/src/main/java/com/reading/platform/service/SchoolCourseService.java new file mode 100644 index 0000000..c925f06 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/SchoolCourseService.java @@ -0,0 +1,55 @@ +package com.reading.platform.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.reading.platform.common.exception.BusinessException; +import com.reading.platform.common.enums.ErrorCode; +import com.reading.platform.entity.SchoolCourse; +import com.reading.platform.mapper.SchoolCourseMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class SchoolCourseService { + + private final SchoolCourseMapper schoolCourseMapper; + + public Page getCourses(int pageNum, int pageSize, Long tenantId, String keyword) { + Page page = new Page<>(pageNum, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SchoolCourse::getTenantId, tenantId); + if (keyword != null && !keyword.isEmpty()) { + wrapper.like(SchoolCourse::getName, keyword); + } + wrapper.orderByDesc(SchoolCourse::getCreatedAt); + return schoolCourseMapper.selectPage(page, wrapper); + } + + public SchoolCourse getCourseById(Long id) { + SchoolCourse course = schoolCourseMapper.selectById(id); + if (course == null) { + throw new BusinessException(ErrorCode.DATA_NOT_FOUND, "School course not found"); + } + return course; + } + + public SchoolCourse createCourse(Long tenantId, Long userId, SchoolCourse course) { + course.setTenantId(tenantId); + course.setCreatedBy(userId); + schoolCourseMapper.insert(course); + return course; + } + + public SchoolCourse updateCourse(Long id, SchoolCourse course) { + SchoolCourse existing = getCourseById(id); + course.setId(id); + course.setTenantId(existing.getTenantId()); + schoolCourseMapper.updateById(course); + return schoolCourseMapper.selectById(id); + } + + public void deleteCourse(Long id) { + schoolCourseMapper.deleteById(id); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/SchoolStatsService.java b/reading-platform-java/src/main/java/com/reading/platform/service/SchoolStatsService.java new file mode 100644 index 0000000..f02a20e --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/SchoolStatsService.java @@ -0,0 +1,33 @@ +package com.reading.platform.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.reading.platform.entity.*; +import com.reading.platform.mapper.*; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class SchoolStatsService { + + private final TeacherMapper teacherMapper; + private final StudentMapper studentMapper; + private final ClazzMapper clazzMapper; + private final LessonMapper lessonMapper; + + public Map getStats(Long tenantId) { + Map stats = new HashMap<>(); + stats.put("teacherCount", teacherMapper.selectCount( + new LambdaQueryWrapper().eq(Teacher::getTenantId, tenantId))); + stats.put("studentCount", studentMapper.selectCount( + new LambdaQueryWrapper().eq(Student::getTenantId, tenantId))); + stats.put("classCount", clazzMapper.selectCount( + new LambdaQueryWrapper().eq(Clazz::getTenantId, tenantId))); + stats.put("lessonCount", lessonMapper.selectCount( + new LambdaQueryWrapper().eq(Lesson::getTenantId, tenantId))); + return stats; + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/SystemSettingService.java b/reading-platform-java/src/main/java/com/reading/platform/service/SystemSettingService.java new file mode 100644 index 0000000..cb410b3 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/SystemSettingService.java @@ -0,0 +1,61 @@ +package com.reading.platform.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.reading.platform.entity.SystemSetting; +import com.reading.platform.mapper.SystemSettingMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class SystemSettingService { + + private final SystemSettingMapper systemSettingMapper; + + public Map getSettings(Long tenantId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemSetting::getTenantId, tenantId); + List settings = systemSettingMapper.selectList(wrapper); + Map result = new HashMap<>(); + for (SystemSetting s : settings) { + result.put(s.getSettingKey(), s.getSettingValue()); + } + return result; + } + + public void updateSettings(Long tenantId, Map settings) { + for (Map.Entry entry : settings.entrySet()) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemSetting::getTenantId, tenantId) + .eq(SystemSetting::getSettingKey, entry.getKey()); + SystemSetting existing = systemSettingMapper.selectOne(wrapper); + if (existing != null) { + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(SystemSetting::getTenantId, tenantId) + .eq(SystemSetting::getSettingKey, entry.getKey()); + SystemSetting update = new SystemSetting(); + update.setSettingValue(entry.getValue()); + systemSettingMapper.update(update, updateWrapper); + } else { + SystemSetting newSetting = new SystemSetting(); + newSetting.setTenantId(tenantId); + newSetting.setSettingKey(entry.getKey()); + newSetting.setSettingValue(entry.getValue()); + systemSettingMapper.insert(newSetting); + } + } + } + + public String getSetting(Long tenantId, String key) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SystemSetting::getTenantId, tenantId) + .eq(SystemSetting::getSettingKey, key); + SystemSetting setting = systemSettingMapper.selectOne(wrapper); + return setting != null ? setting.getSettingValue() : null; + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/TeacherDashboardService.java b/reading-platform-java/src/main/java/com/reading/platform/service/TeacherDashboardService.java new file mode 100644 index 0000000..f020839 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/TeacherDashboardService.java @@ -0,0 +1,86 @@ +package com.reading.platform.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.reading.platform.entity.*; +import com.reading.platform.mapper.*; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class TeacherDashboardService { + + private final LessonMapper lessonMapper; + private final TaskMapper taskMapper; + private final GrowthRecordMapper growthRecordMapper; + private final NotificationMapper notificationMapper; + + public Map getDashboard(Long teacherId, Long tenantId) { + Map dashboard = new HashMap<>(); + dashboard.put("lessonCount", lessonMapper.selectCount( + new LambdaQueryWrapper() + .eq(Lesson::getTeacherId, teacherId) + .eq(Lesson::getTenantId, tenantId))); + dashboard.put("taskCount", taskMapper.selectCount( + new LambdaQueryWrapper() + .eq(Task::getCreatorId, teacherId) + .eq(Task::getTenantId, tenantId))); + dashboard.put("growthRecordCount", growthRecordMapper.selectCount( + new LambdaQueryWrapper() + .eq(GrowthRecord::getRecordedBy, teacherId) + .eq(GrowthRecord::getTenantId, tenantId))); + dashboard.put("unreadNotifications", notificationMapper.selectCount( + new LambdaQueryWrapper() + .eq(Notification::getTenantId, tenantId) + .eq(Notification::getIsRead, 0))); + return dashboard; + } + + public List> getTodayLessons(Long teacherId, Long tenantId) { + LocalDate today = LocalDate.now(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Lesson::getTeacherId, teacherId) + .eq(Lesson::getTenantId, tenantId) + .eq(Lesson::getLessonDate, today) + .orderByAsc(Lesson::getStartTime); + List lessons = lessonMapper.selectList(wrapper); + return lessons.stream().map(l -> { + Map map = new HashMap<>(); + map.put("id", l.getId()); + map.put("title", l.getTitle()); + map.put("startTime", l.getStartTime()); + map.put("endTime", l.getEndTime()); + map.put("location", l.getLocation()); + map.put("status", l.getStatus()); + return map; + }).collect(Collectors.toList()); + } + + public List> getWeeklyLessons(Long teacherId, Long tenantId) { + LocalDate today = LocalDate.now(); + LocalDate weekStart = today.minusDays(today.getDayOfWeek().getValue() - 1); + LocalDate weekEnd = weekStart.plusDays(6); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Lesson::getTeacherId, teacherId) + .eq(Lesson::getTenantId, tenantId) + .between(Lesson::getLessonDate, weekStart, weekEnd) + .orderByAsc(Lesson::getLessonDate, Lesson::getStartTime); + List lessons = lessonMapper.selectList(wrapper); + return lessons.stream().map(l -> { + Map map = new HashMap<>(); + map.put("id", l.getId()); + map.put("title", l.getTitle()); + map.put("lessonDate", l.getLessonDate()); + map.put("startTime", l.getStartTime()); + map.put("endTime", l.getEndTime()); + map.put("status", l.getStatus()); + return map; + }).collect(Collectors.toList()); + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/ThemeService.java b/reading-platform-java/src/main/java/com/reading/platform/service/ThemeService.java new file mode 100644 index 0000000..16bd2a0 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/service/ThemeService.java @@ -0,0 +1,51 @@ +package com.reading.platform.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.reading.platform.common.exception.BusinessException; +import com.reading.platform.common.enums.ErrorCode; +import com.reading.platform.entity.Theme; +import com.reading.platform.mapper.ThemeMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ThemeService { + + private final ThemeMapper themeMapper; + + public List getAllThemes(Boolean enabledOnly) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (Boolean.TRUE.equals(enabledOnly)) { + wrapper.eq(Theme::getIsEnabled, 1); + } + wrapper.orderByAsc(Theme::getSortOrder); + return themeMapper.selectList(wrapper); + } + + public Theme getThemeById(Long id) { + Theme theme = themeMapper.selectById(id); + if (theme == null) { + throw new BusinessException(ErrorCode.DATA_NOT_FOUND, "Theme not found"); + } + return theme; + } + + public Theme createTheme(Theme theme) { + themeMapper.insert(theme); + return theme; + } + + public Theme updateTheme(Long id, Theme theme) { + getThemeById(id); + theme.setId(id); + themeMapper.updateById(theme); + return themeMapper.selectById(id); + } + + public void deleteTheme(Long id) { + themeMapper.deleteById(id); + } +} diff --git a/reading-platform-java/src/main/resources/application-dev.yml b/reading-platform-java/src/main/resources/application-dev.yml index 7d46f84..8373b00 100644 --- a/reading-platform-java/src/main/resources/application-dev.yml +++ b/reading-platform-java/src/main/resources/application-dev.yml @@ -13,6 +13,16 @@ spring: serialization: write-dates-as-timestamps: false + flyway: + enabled: true + locations: classpath:db/migration + baseline-on-migrate: true + +file: + upload: + path: ./uploads/ + base-url: /uploads/ + logging: level: com.reading.platform: debug diff --git a/reading-platform-java/src/main/resources/application-prod.yml b/reading-platform-java/src/main/resources/application-prod.yml new file mode 100644 index 0000000..e7812c8 --- /dev/null +++ b/reading-platform-java/src/main/resources/application-prod.yml @@ -0,0 +1,39 @@ +server: + port: 8080 + +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://mysql:3306/reading_platform?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + username: root + password: reading_platform_pwd + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: Asia/Shanghai + serialization: + write-dates-as-timestamps: false + servlet: + multipart: + max-file-size: 300MB + max-request-size: 1500MB + web: + resources: + static-locations: file:/app/uploads/,classpath:/static/ + + flyway: + enabled: true + locations: classpath:db/migration + baseline-on-migrate: true + +jwt: + secret: ${JWT_SECRET:reading-platform-jwt-secret-key-must-be-at-least-256-bits-long} + expiration: 604800000 + +file: + upload: + path: /app/uploads/ + base-url: /uploads/ + +logging: + level: + com.reading.platform: info diff --git a/reading-platform-java/src/main/resources/application.yml b/reading-platform-java/src/main/resources/application.yml index ac76f37..5c50480 100644 --- a/reading-platform-java/src/main/resources/application.yml +++ b/reading-platform-java/src/main/resources/application.yml @@ -6,6 +6,10 @@ spring: name: reading-platform profiles: active: dev + servlet: + multipart: + max-file-size: 300MB + max-request-size: 1500MB # MyBatis-Plus Configuration mybatis-plus: diff --git a/reading-platform-java/src/main/resources/db/migration/V1__init_schema.sql b/reading-platform-java/src/main/resources/db/migration/V1__init_schema.sql index 601d92b..2e53ee9 100644 --- a/reading-platform-java/src/main/resources/db/migration/V1__init_schema.sql +++ b/reading-platform-java/src/main/resources/db/migration/V1__init_schema.sql @@ -519,4 +519,4 @@ CREATE INDEX idx_notifications_tenant ON notifications(tenant_id); -- Insert default admin user (password: admin123) INSERT INTO admin_users (username, password, name, status) VALUES -('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 'Super Admin', 'active'); +('admin', '$2b$10$VSes.57X35WZov4LdyJ.eu1pll7qnt7tGuq/u3iXOrYawHTebe.Eu', 'Super Admin', 'active'); diff --git a/reading-platform-java/src/main/resources/db/migration/V2__add_course_package_fields.sql b/reading-platform-java/src/main/resources/db/migration/V2__add_course_package_fields.sql index 107353d..6b9537a 100644 --- a/reading-platform-java/src/main/resources/db/migration/V2__add_course_package_fields.sql +++ b/reading-platform-java/src/main/resources/db/migration/V2__add_course_package_fields.sql @@ -4,75 +4,75 @@ -- Add new fields to courses table for course package refactoring -- Core content -ALTER TABLE courses ADD COLUMN IF NOT EXISTS core_content TEXT COMMENT 'Core content summary'; +ALTER TABLE courses ADD COLUMN core_content TEXT COMMENT 'Core content summary'; -- Course introduction fields (8 fields) -ALTER TABLE courses ADD COLUMN IF NOT EXISTS intro_summary TEXT COMMENT 'Course summary'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS intro_highlights TEXT COMMENT 'Course highlights'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS intro_goals TEXT COMMENT 'Course goals'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS intro_schedule TEXT COMMENT 'Content schedule'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS intro_key_points TEXT COMMENT 'Key points and difficulties'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS intro_methods TEXT COMMENT 'Teaching methods'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS intro_evaluation TEXT COMMENT 'Evaluation methods'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS intro_notes TEXT COMMENT 'Notes and precautions'; +ALTER TABLE courses ADD COLUMN intro_summary TEXT COMMENT 'Course summary'; +ALTER TABLE courses ADD COLUMN intro_highlights TEXT COMMENT 'Course highlights'; +ALTER TABLE courses ADD COLUMN intro_goals TEXT COMMENT 'Course goals'; +ALTER TABLE courses ADD COLUMN intro_schedule TEXT COMMENT 'Content schedule'; +ALTER TABLE courses ADD COLUMN intro_key_points TEXT COMMENT 'Key points and difficulties'; +ALTER TABLE courses ADD COLUMN intro_methods TEXT COMMENT 'Teaching methods'; +ALTER TABLE courses ADD COLUMN intro_evaluation TEXT COMMENT 'Evaluation methods'; +ALTER TABLE courses ADD COLUMN intro_notes TEXT COMMENT 'Notes and precautions'; -- Schedule reference data -ALTER TABLE courses ADD COLUMN IF NOT EXISTS schedule_ref_data TEXT COMMENT 'Schedule reference data (JSON)'; +ALTER TABLE courses ADD COLUMN schedule_ref_data TEXT COMMENT 'Schedule reference data (JSON)'; -- Environment construction (Step 7) -ALTER TABLE courses ADD COLUMN IF NOT EXISTS environment_construction TEXT COMMENT 'Environment construction content'; +ALTER TABLE courses ADD COLUMN environment_construction TEXT COMMENT 'Environment construction content'; -- Theme and picture book relation -ALTER TABLE courses ADD COLUMN IF NOT EXISTS theme_id BIGINT COMMENT 'Theme ID'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS picture_book_name VARCHAR(200) COMMENT 'Picture book name'; +ALTER TABLE courses ADD COLUMN theme_id BIGINT COMMENT 'Theme ID'; +ALTER TABLE courses ADD COLUMN picture_book_name VARCHAR(200) COMMENT 'Picture book name'; -- Cover image path -ALTER TABLE courses ADD COLUMN IF NOT EXISTS cover_image_path VARCHAR(500) COMMENT 'Cover image file path'; +ALTER TABLE courses ADD COLUMN cover_image_path VARCHAR(500) COMMENT 'Cover image file path'; -- Digital resources (JSON arrays) -ALTER TABLE courses ADD COLUMN IF NOT EXISTS ebook_paths TEXT COMMENT 'Ebook paths (JSON array)'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS audio_paths TEXT COMMENT 'Audio paths (JSON array)'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS video_paths TEXT COMMENT 'Video paths (JSON array)'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS other_resources TEXT COMMENT 'Other resources (JSON array)'; +ALTER TABLE courses ADD COLUMN ebook_paths TEXT COMMENT 'Ebook paths (JSON array)'; +ALTER TABLE courses ADD COLUMN audio_paths TEXT COMMENT 'Audio paths (JSON array)'; +ALTER TABLE courses ADD COLUMN video_paths TEXT COMMENT 'Video paths (JSON array)'; +ALTER TABLE courses ADD COLUMN other_resources TEXT COMMENT 'Other resources (JSON array)'; -- Teaching materials -ALTER TABLE courses ADD COLUMN IF NOT EXISTS ppt_path VARCHAR(500) COMMENT 'PPT file path'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS ppt_name VARCHAR(200) COMMENT 'PPT file name'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS poster_paths TEXT COMMENT 'Poster paths (JSON array)'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS tools TEXT COMMENT 'Teaching tools (JSON array)'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS student_materials TEXT COMMENT 'Student materials'; +ALTER TABLE courses ADD COLUMN ppt_path VARCHAR(500) COMMENT 'PPT file path'; +ALTER TABLE courses ADD COLUMN ppt_name VARCHAR(200) COMMENT 'PPT file name'; +ALTER TABLE courses ADD COLUMN poster_paths TEXT COMMENT 'Poster paths (JSON array)'; +ALTER TABLE courses ADD COLUMN tools TEXT COMMENT 'Teaching tools (JSON array)'; +ALTER TABLE courses ADD COLUMN student_materials TEXT COMMENT 'Student materials'; -- Lesson plan and activities -ALTER TABLE courses ADD COLUMN IF NOT EXISTS lesson_plan_data TEXT COMMENT 'Lesson plan data (JSON)'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS activities_data TEXT COMMENT 'Activities data (JSON)'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS assessment_data TEXT COMMENT 'Assessment data (JSON)'; +ALTER TABLE courses ADD COLUMN lesson_plan_data TEXT COMMENT 'Lesson plan data (JSON)'; +ALTER TABLE courses ADD COLUMN activities_data TEXT COMMENT 'Activities data (JSON)'; +ALTER TABLE courses ADD COLUMN assessment_data TEXT COMMENT 'Assessment data (JSON)'; -- Grade and domain tags -ALTER TABLE courses ADD COLUMN IF NOT EXISTS grade_tags TEXT COMMENT 'Grade tags (JSON array)'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS domain_tags TEXT COMMENT 'Domain tags (JSON array)'; +ALTER TABLE courses ADD COLUMN grade_tags TEXT COMMENT 'Grade tags (JSON array)'; +ALTER TABLE courses ADD COLUMN domain_tags TEXT COMMENT 'Domain tags (JSON array)'; -- Collective lesson flag -ALTER TABLE courses ADD COLUMN IF NOT EXISTS has_collective_lesson TINYINT DEFAULT 0 COMMENT 'Has collective lesson'; +ALTER TABLE courses ADD COLUMN has_collective_lesson TINYINT DEFAULT 0 COMMENT 'Has collective lesson'; -- Version and review fields -ALTER TABLE courses ADD COLUMN IF NOT EXISTS version VARCHAR(20) DEFAULT '1.0' COMMENT 'Version number'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS parent_id BIGINT COMMENT 'Parent course ID for versions'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS is_latest TINYINT DEFAULT 1 COMMENT 'Is latest version'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS submitted_at DATETIME COMMENT 'Submitted for review at'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS submitted_by BIGINT COMMENT 'Submitted by user ID'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS reviewed_at DATETIME COMMENT 'Reviewed at'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS reviewed_by BIGINT COMMENT 'Reviewed by user ID'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS review_comment TEXT COMMENT 'Review comment'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS review_checklist TEXT COMMENT 'Review checklist (JSON)'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS published_at DATETIME COMMENT 'Published at'; +ALTER TABLE courses ADD COLUMN version VARCHAR(20) DEFAULT '1.0' COMMENT 'Version number'; +ALTER TABLE courses ADD COLUMN parent_id BIGINT COMMENT 'Parent course ID for versions'; +ALTER TABLE courses ADD COLUMN is_latest TINYINT DEFAULT 1 COMMENT 'Is latest version'; +ALTER TABLE courses ADD COLUMN submitted_at DATETIME COMMENT 'Submitted for review at'; +ALTER TABLE courses ADD COLUMN submitted_by BIGINT COMMENT 'Submitted by user ID'; +ALTER TABLE courses ADD COLUMN reviewed_at DATETIME COMMENT 'Reviewed at'; +ALTER TABLE courses ADD COLUMN reviewed_by BIGINT COMMENT 'Reviewed by user ID'; +ALTER TABLE courses ADD COLUMN review_comment TEXT COMMENT 'Review comment'; +ALTER TABLE courses ADD COLUMN review_checklist TEXT COMMENT 'Review checklist (JSON)'; +ALTER TABLE courses ADD COLUMN published_at DATETIME COMMENT 'Published at'; -- Usage statistics -ALTER TABLE courses ADD COLUMN IF NOT EXISTS usage_count INT DEFAULT 0 COMMENT 'Usage count'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS teacher_count INT DEFAULT 0 COMMENT 'Teacher count'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS avg_rating DECIMAL(3,2) DEFAULT 0 COMMENT 'Average rating'; -ALTER TABLE courses ADD COLUMN IF NOT EXISTS created_by BIGINT COMMENT 'Created by user ID'; +ALTER TABLE courses ADD COLUMN usage_count INT DEFAULT 0 COMMENT 'Usage count'; +ALTER TABLE courses ADD COLUMN teacher_count INT DEFAULT 0 COMMENT 'Teacher count'; +ALTER TABLE courses ADD COLUMN avg_rating DECIMAL(3,2) DEFAULT 0 COMMENT 'Average rating'; +ALTER TABLE courses ADD COLUMN created_by BIGINT COMMENT 'Created by user ID'; -- Create indexes for better query performance -CREATE INDEX IF NOT EXISTS idx_courses_theme ON courses(theme_id); -CREATE INDEX IF NOT EXISTS idx_courses_status ON courses(status); -CREATE INDEX IF NOT EXISTS idx_courses_parent ON courses(parent_id); +CREATE INDEX idx_courses_theme ON courses(theme_id); +CREATE INDEX idx_courses_status ON courses(status); +CREATE INDEX idx_courses_parent ON courses(parent_id); diff --git a/reading-platform-java/src/main/resources/db/migration/V3__add_themes.sql b/reading-platform-java/src/main/resources/db/migration/V3__add_themes.sql new file mode 100644 index 0000000..eec1a23 --- /dev/null +++ b/reading-platform-java/src/main/resources/db/migration/V3__add_themes.sql @@ -0,0 +1,21 @@ +-- Add themes table +CREATE TABLE IF NOT EXISTS themes ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(50) NOT NULL UNIQUE COMMENT 'Theme technical name', + display_name VARCHAR(100) NOT NULL COMMENT 'Display name', + color VARCHAR(20) COMMENT 'Theme color (hex)', + icon VARCHAR(100) COMMENT 'Theme icon', + sort_order INT DEFAULT 0 COMMENT 'Sort order', + is_enabled TINYINT DEFAULT 1 COMMENT 'Is enabled', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted TINYINT DEFAULT 0 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Themes'; + +-- Insert default themes +INSERT INTO themes (name, display_name, color, sort_order, is_enabled) VALUES +('nature', '自然探索', '#4CAF50', 1, 1), +('ocean', '海洋世界', '#2196F3', 2, 1), +('space', '太空冒险', '#9C27B0', 3, 1), +('animals', '动物王国', '#FF9800', 4, 1), +('fairytale', '童话故事', '#E91E63', 5, 1); diff --git a/reading-platform-java/src/main/resources/db/migration/V4__add_course_packages.sql b/reading-platform-java/src/main/resources/db/migration/V4__add_course_packages.sql new file mode 100644 index 0000000..a73d133 --- /dev/null +++ b/reading-platform-java/src/main/resources/db/migration/V4__add_course_packages.sql @@ -0,0 +1,14 @@ +-- Add course packages table +CREATE TABLE IF NOT EXISTS course_packages ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(200) NOT NULL COMMENT 'Package name', + description TEXT COMMENT 'Package description', + cover_url VARCHAR(500) COMMENT 'Cover image URL', + course_count INT DEFAULT 0 COMMENT 'Number of courses', + price DECIMAL(10, 2) DEFAULT 0 COMMENT 'Price', + status VARCHAR(20) DEFAULT 'draft' COMMENT 'Status: draft, published, archived', + is_system TINYINT DEFAULT 1 COMMENT 'Is system package', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted TINYINT DEFAULT 0 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Course Packages'; diff --git a/reading-platform-java/src/main/resources/db/migration/V5__add_school_courses.sql b/reading-platform-java/src/main/resources/db/migration/V5__add_school_courses.sql new file mode 100644 index 0000000..e4514a8 --- /dev/null +++ b/reading-platform-java/src/main/resources/db/migration/V5__add_school_courses.sql @@ -0,0 +1,17 @@ +-- Add school courses table (tenant-customized courses) +CREATE TABLE IF NOT EXISTS school_courses ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + tenant_id BIGINT NOT NULL COMMENT 'Tenant ID', + name VARCHAR(200) NOT NULL COMMENT 'Course name', + description TEXT COMMENT 'Course description', + cover_url VARCHAR(500) COMMENT 'Cover URL', + category VARCHAR(50) COMMENT 'Course category', + age_range VARCHAR(50) COMMENT 'Age range', + status VARCHAR(20) DEFAULT 'draft' COMMENT 'Status: draft, published, archived', + created_by BIGINT COMMENT 'Created by user ID', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted TINYINT DEFAULT 0, + INDEX idx_tenant (tenant_id), + INDEX idx_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='School Custom Courses'; diff --git a/reading-platform-java/src/main/resources/db/migration/V6__add_course_lessons.sql b/reading-platform-java/src/main/resources/db/migration/V6__add_course_lessons.sql new file mode 100644 index 0000000..c479b1c --- /dev/null +++ b/reading-platform-java/src/main/resources/db/migration/V6__add_course_lessons.sql @@ -0,0 +1,17 @@ +-- Add course lessons table (chapters/sections within a course) +CREATE TABLE IF NOT EXISTS course_lessons ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + course_id BIGINT NOT NULL COMMENT 'Course ID', + title VARCHAR(200) NOT NULL COMMENT 'Lesson title', + description TEXT COMMENT 'Lesson description', + content TEXT COMMENT 'Lesson content', + sort_order INT DEFAULT 0 COMMENT 'Sort order', + duration_minutes INT COMMENT 'Duration in minutes', + video_url VARCHAR(1000) COMMENT 'Video URL', + status VARCHAR(20) DEFAULT 'draft' COMMENT 'Status: draft, published', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted TINYINT DEFAULT 0, + INDEX idx_course (course_id), + INDEX idx_sort (sort_order) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Course Lessons (chapters/sections)';