refactor: 代码合规性审查修复 - 三层架构、API路径、文档规范
## P0 三层架构违规修复 (4项) - 创建 SchoolStatsService/TeacherStatsService,移除Controller直接调用Mapper - 修复 AdminCourseController 使用 Service 层方法 - 修复 TeacherCourseController 使用 ClassService 获取班级 - 新增 ClassService.getActiveClassesByTenantId() - 新增 CourseService.createSystemCourse() ## P1 API 路径统一 (8项) 后端路径统一为 /api/v1/admin/*: - AdminCourseController: /api/admin/courses → /api/v1/admin/courses - AdminTenantController: /api/admin/tenants → /api/v1/admin/tenants 前端配置调整: - vite.config.ts: 移除代理重写规则 - src/api/index.ts: baseURL /api/v1 → /api - 更新 admin.ts, lesson.ts, package.ts, theme.ts 使用 /v1/admin/* 路径 ## P2 文档规范更新 (5项) - 更新 CLAUDE.md 前端 API 调用文档 - 新增三种调用方式说明(http/适配层/Orval客户端) - 新增 API 路径规范表格 - 更新前端目录结构说明 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
56508eb066
commit
066b1f2257
@ -135,8 +135,8 @@ reading-platform-frontend/
|
||||
│ ├── App.vue # 根组件
|
||||
│ ├── api/ # API 接口
|
||||
│ │ ├── generated/ # Orval 自动生成(禁止手改)
|
||||
│ │ ├── client.ts # 统一入口
|
||||
│ │ └── *.ts # 业务适配层
|
||||
│ │ ├── index.ts # 统一入口,导出 http 方法
|
||||
│ │ └── *.ts # 业务适配层(admin.ts, school.ts, teacher.ts 等)
|
||||
│ ├── assets/ # 静态资源
|
||||
│ ├── components/ # 公共组件
|
||||
│ ├── composables/ # 组合式函数
|
||||
@ -174,9 +174,16 @@ reading-platform-frontend/
|
||||
|
||||
### Controller 层规范
|
||||
|
||||
**API 路径约定**:
|
||||
- 超管端:`/api/v1/admin/*`
|
||||
- 学校端:`/api/school/*`
|
||||
- 教师端:`/api/teacher/*`
|
||||
- 家长端:`/api/parent/*`
|
||||
- 认证:`/api/auth/*`
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/xxx")
|
||||
@RequestMapping("/api/v1/admin/xxx") // 超管端使用 /api/v1/admin/
|
||||
@Tag(name = "XXX管理", description = "XXX相关接口")
|
||||
@RequiredArgsConstructor
|
||||
public class XxxController {
|
||||
@ -228,23 +235,64 @@ public interface XxxMapper extends BaseMapper<Xxx> {
|
||||
|
||||
## 前端开发规范
|
||||
|
||||
### API 开发规范(Orval)
|
||||
### API 开发规范
|
||||
|
||||
1. **生成代码只读** - 不得在 `src/api/generated/` 内做任何手工修改
|
||||
2. **以生成类型为准** - 参数/返回类型优先使用生成的类型
|
||||
3. **统一调用入口** - 通过 `src/api/client.ts` 访问
|
||||
3. **统一调用入口** - 通过 `src/api/index.ts` 导出的 `http` 方法
|
||||
|
||||
### 推荐调用方式
|
||||
|
||||
**方式一:使用 http 方法(推荐)**
|
||||
|
||||
```typescript
|
||||
import { readingApi } from '@/api/client';
|
||||
import { http } from '@/api';
|
||||
|
||||
async function loadTenant(id: number) {
|
||||
const res = await readingApi.getTenant({ id });
|
||||
return res.data;
|
||||
return http.get<TenantDetail>(`/v1/admin/tenants/${id}`);
|
||||
}
|
||||
|
||||
async function createTenant(data: CreateTenantDto) {
|
||||
return http.post<Tenant>('/v1/admin/tenants', data);
|
||||
}
|
||||
```
|
||||
|
||||
**方式二:使用业务适配层(推荐)**
|
||||
|
||||
```typescript
|
||||
import { getTenant, createTenant } from '@/api/admin';
|
||||
|
||||
async function loadTenant(id: number) {
|
||||
return getTenant(id);
|
||||
}
|
||||
|
||||
async function createNewTenant(data: CreateTenantDto) {
|
||||
return createTenant(data);
|
||||
}
|
||||
```
|
||||
|
||||
**方式三:使用 Orval 生成的客户端(可选)**
|
||||
|
||||
```typescript
|
||||
import { getReadingPlatformAPI } from '@/api/generated';
|
||||
|
||||
const api = getReadingPlatformAPI();
|
||||
|
||||
async function loadTenant(id: number) {
|
||||
return api.getTenant({ id });
|
||||
}
|
||||
```
|
||||
|
||||
### API 路径规范
|
||||
|
||||
| 端 | 后端路径 | 前端路径 |
|
||||
|----|----------|----------|
|
||||
| 超管 | `/api/v1/admin/*` | `/v1/admin/*` |
|
||||
| 学校 | `/api/school/*` | `/school/*` |
|
||||
| 教师 | `/api/teacher/*` | `/teacher/*` |
|
||||
| 家长 | `/api/parent/*` | `/parent/*` |
|
||||
| 认证 | `/api/auth/*` | `/auth/*` |
|
||||
|
||||
### Vue SFC 约定
|
||||
|
||||
- 优先使用 `<script lang="ts" setup>`
|
||||
|
||||
@ -167,49 +167,49 @@ export interface AdminSettings {
|
||||
|
||||
export const getTenants = (params: TenantQueryParams) =>
|
||||
http.get<{ items: Tenant[]; total: number; page: number; pageSize: number; totalPages: number }>(
|
||||
'/admin/tenants',
|
||||
'/v1/admin/tenants',
|
||||
{ params }
|
||||
);
|
||||
|
||||
export const getTenant = (id: number) =>
|
||||
http.get<TenantDetail>(`/admin/tenants/${id}`);
|
||||
http.get<TenantDetail>(`/v1/admin/tenants/${id}`);
|
||||
|
||||
export const createTenant = (data: CreateTenantDto) =>
|
||||
http.post<Tenant & { tempPassword: string }>('/admin/tenants', data);
|
||||
http.post<Tenant & { tempPassword: string }>('/v1/admin/tenants', data);
|
||||
|
||||
export const updateTenant = (id: number, data: UpdateTenantDto) =>
|
||||
http.put<Tenant>(`/admin/tenants/${id}`, data);
|
||||
http.put<Tenant>(`/v1/admin/tenants/${id}`, data);
|
||||
|
||||
export const updateTenantQuota = (id: number, data: UpdateTenantQuotaDto) =>
|
||||
http.put<Tenant>(`/admin/tenants/${id}/quota`, data);
|
||||
http.put<Tenant>(`/v1/admin/tenants/${id}/quota`, data);
|
||||
|
||||
export const updateTenantStatus = (id: number, status: string) =>
|
||||
http.put<{ id: number; name: string; status: string }>(`/admin/tenants/${id}/status`, { status });
|
||||
http.put<{ id: number; name: string; status: string }>(`/v1/admin/tenants/${id}/status`, { status });
|
||||
|
||||
export const resetTenantPassword = (id: number) =>
|
||||
http.post<{ tempPassword: string }>(`/admin/tenants/${id}/reset-password`);
|
||||
http.post<{ tempPassword: string }>(`/v1/admin/tenants/${id}/reset-password`);
|
||||
|
||||
export const deleteTenant = (id: number) =>
|
||||
http.delete<{ success: boolean }>(`/admin/tenants/${id}`);
|
||||
http.delete<{ success: boolean }>(`/v1/admin/tenants/${id}`);
|
||||
|
||||
// ==================== 统计数据 ====================
|
||||
|
||||
export const getAdminStats = () =>
|
||||
http.get<AdminStats>('/admin/stats');
|
||||
http.get<AdminStats>('/v1/admin/stats');
|
||||
|
||||
export const getTrendData = () =>
|
||||
http.get<TrendData[]>('/admin/stats/trend');
|
||||
http.get<TrendData[]>('/v1/admin/stats/trend');
|
||||
|
||||
export const getActiveTenants = (limit?: number) =>
|
||||
http.get<ActiveTenant[]>('/admin/stats/tenants/active', { params: { limit } });
|
||||
http.get<ActiveTenant[]>('/v1/admin/stats/tenants/active', { params: { limit } });
|
||||
|
||||
export const getPopularCourses = (limit?: number) =>
|
||||
http.get<PopularCourse[]>('/admin/stats/courses/popular', { params: { limit } });
|
||||
http.get<PopularCourse[]>('/v1/admin/stats/courses/popular', { params: { limit } });
|
||||
|
||||
// ==================== 系统设置 ====================
|
||||
|
||||
export const getAdminSettings = () =>
|
||||
http.get<AdminSettings>('/admin/settings');
|
||||
http.get<AdminSettings>('/v1/admin/settings');
|
||||
|
||||
export const updateAdminSettings = (data: Record<string, any>) =>
|
||||
http.put<AdminSettings>('/admin/settings', data);
|
||||
http.put<AdminSettings>('/v1/admin/settings', data);
|
||||
|
||||
@ -3,7 +3,7 @@ import { message } from 'ant-design-vue';
|
||||
|
||||
// 创建axios实例
|
||||
const request: AxiosInstance = axios.create({
|
||||
baseURL: '/api/v1', // 使用 /api/v1,代理会保留完整路径
|
||||
baseURL: '/api', // 使用 /api 作为统一前缀,超管端路径包含 /v1
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@ -86,64 +86,64 @@ export interface CreateStepData {
|
||||
|
||||
// 获取课程列表
|
||||
export function getLessonList(courseId: number) {
|
||||
return http.get(`/admin/courses/${courseId}/lessons`);
|
||||
return http.get(`/v1/admin/courses/${courseId}/lessons`);
|
||||
}
|
||||
|
||||
// 获取课程详情
|
||||
export function getLessonDetail(courseId: number, lessonId: number) {
|
||||
return http.get(`/admin/courses/${courseId}/lessons/${lessonId}`);
|
||||
return http.get(`/v1/admin/courses/${courseId}/lessons/${lessonId}`);
|
||||
}
|
||||
|
||||
// 按类型获取课程
|
||||
export function getLessonByType(courseId: number, lessonType: string) {
|
||||
return http.get(`/admin/courses/${courseId}/lessons/type/${lessonType}`);
|
||||
return http.get(`/v1/admin/courses/${courseId}/lessons/type/${lessonType}`);
|
||||
}
|
||||
|
||||
// 创建课程
|
||||
export function createLesson(courseId: number, data: CreateLessonData) {
|
||||
return http.post(`/admin/courses/${courseId}/lessons`, data);
|
||||
return http.post(`/v1/admin/courses/${courseId}/lessons`, data);
|
||||
}
|
||||
|
||||
// 更新课程
|
||||
export function updateLesson(lessonId: number, data: Partial<CreateLessonData>) {
|
||||
return http.put(`/admin/courses/0/lessons/${lessonId}`, data);
|
||||
return http.put(`/v1/admin/courses/0/lessons/${lessonId}`, data);
|
||||
}
|
||||
|
||||
// 删除课程
|
||||
export function deleteLesson(courseId: number, lessonId: number) {
|
||||
return http.delete(`/admin/courses/${courseId}/lessons/${lessonId}`);
|
||||
return http.delete(`/v1/admin/courses/${courseId}/lessons/${lessonId}`);
|
||||
}
|
||||
|
||||
// 重新排序课程
|
||||
export function reorderLessons(courseId: number, lessonIds: number[]) {
|
||||
return http.put(`/admin/courses/${courseId}/lessons/reorder`, { lessonIds });
|
||||
return http.put(`/v1/admin/courses/${courseId}/lessons/reorder`, { lessonIds });
|
||||
}
|
||||
|
||||
// ==================== 教学环节 API ====================
|
||||
|
||||
// 获取环节列表
|
||||
export function getStepList(courseId: number, lessonId: number) {
|
||||
return http.get(`/admin/courses/${courseId}/lessons/${lessonId}/steps`);
|
||||
return http.get(`/v1/admin/courses/${courseId}/lessons/${lessonId}/steps`);
|
||||
}
|
||||
|
||||
// 创建环节
|
||||
export function createStep(courseId: number, lessonId: number, data: CreateStepData) {
|
||||
return http.post(`/admin/courses/${courseId}/lessons/${lessonId}/steps`, data);
|
||||
return http.post(`/v1/admin/courses/${courseId}/lessons/${lessonId}/steps`, data);
|
||||
}
|
||||
|
||||
// 更新环节
|
||||
export function updateStep(stepId: number, data: Partial<CreateStepData>) {
|
||||
return http.put(`/admin/courses/0/lessons/steps/${stepId}`, data);
|
||||
return http.put(`/v1/admin/courses/0/lessons/steps/${stepId}`, data);
|
||||
}
|
||||
|
||||
// 删除环节
|
||||
export function deleteStep(courseId: number, _lessonId: number, stepId: number) {
|
||||
return http.delete(`/admin/courses/${courseId}/lessons/steps/${stepId}`);
|
||||
return http.delete(`/v1/admin/courses/${courseId}/lessons/steps/${stepId}`);
|
||||
}
|
||||
|
||||
// 重新排序环节
|
||||
export function reorderSteps(courseId: number, lessonId: number, stepIds: number[]) {
|
||||
return http.put(`/admin/courses/${courseId}/lessons/${lessonId}/steps/reorder`, { stepIds });
|
||||
return http.put(`/v1/admin/courses/${courseId}/lessons/${lessonId}/steps/reorder`, { stepIds });
|
||||
}
|
||||
|
||||
// ==================== 教师端 API ====================
|
||||
|
||||
@ -49,27 +49,27 @@ export interface CreatePackageData {
|
||||
|
||||
// 获取套餐列表
|
||||
export function getPackageList(params?: PackageListParams) {
|
||||
return http.get('/admin/packages', { params });
|
||||
return http.get('/v1/admin/packages', { params });
|
||||
}
|
||||
|
||||
// 获取套餐详情
|
||||
export function getPackageDetail(id: number) {
|
||||
return http.get(`/admin/packages/${id}`);
|
||||
return http.get(`/v1/admin/packages/${id}`);
|
||||
}
|
||||
|
||||
// 创建套餐
|
||||
export function createPackage(data: CreatePackageData) {
|
||||
return http.post('/admin/packages', data);
|
||||
return http.post('/v1/admin/packages', data);
|
||||
}
|
||||
|
||||
// 更新套餐
|
||||
export function updatePackage(id: number, data: Partial<CreatePackageData>) {
|
||||
return http.put(`/admin/packages/${id}`, data);
|
||||
return http.put(`/v1/admin/packages/${id}`, data);
|
||||
}
|
||||
|
||||
// 删除套餐
|
||||
export function deletePackage(id: number) {
|
||||
return http.delete(`/admin/packages/${id}`);
|
||||
return http.delete(`/v1/admin/packages/${id}`);
|
||||
}
|
||||
|
||||
// 设置套餐课程
|
||||
@ -77,7 +77,7 @@ export function setPackageCourses(
|
||||
packageId: number,
|
||||
courses: { courseId: number; gradeLevel: string; sortOrder?: number }[],
|
||||
) {
|
||||
return http.put(`/admin/packages/${packageId}/courses`, { courses });
|
||||
return http.put(`/v1/admin/packages/${packageId}/courses`, { courses });
|
||||
}
|
||||
|
||||
// 添加课程到套餐
|
||||
@ -85,32 +85,32 @@ export function addCourseToPackage(
|
||||
packageId: number,
|
||||
data: { courseId: number; gradeLevel: string; sortOrder?: number },
|
||||
) {
|
||||
return http.post(`/admin/packages/${packageId}/courses`, data);
|
||||
return http.post(`/v1/admin/packages/${packageId}/courses`, data);
|
||||
}
|
||||
|
||||
// 从套餐移除课程
|
||||
export function removeCourseFromPackage(packageId: number, courseId: number) {
|
||||
return http.delete(`/admin/packages/${packageId}/courses/${courseId}`);
|
||||
return http.delete(`/v1/admin/packages/${packageId}/courses/${courseId}`);
|
||||
}
|
||||
|
||||
// 提交审核
|
||||
export function submitPackage(id: number) {
|
||||
return http.post(`/admin/packages/${id}/submit`);
|
||||
return http.post(`/v1/admin/packages/${id}/submit`);
|
||||
}
|
||||
|
||||
// 审核套餐
|
||||
export function reviewPackage(id: number, data: { approved: boolean; comment?: string }) {
|
||||
return http.post(`/admin/packages/${id}/review`, data);
|
||||
return http.post(`/v1/admin/packages/${id}/review`, data);
|
||||
}
|
||||
|
||||
// 发布套餐
|
||||
export function publishPackage(id: number) {
|
||||
return http.post(`/admin/packages/${id}/publish`);
|
||||
return http.post(`/v1/admin/packages/${id}/publish`);
|
||||
}
|
||||
|
||||
// 下架套餐
|
||||
export function offlinePackage(id: number) {
|
||||
return http.post(`/admin/packages/${id}/offline`);
|
||||
return http.post(`/v1/admin/packages/${id}/offline`);
|
||||
}
|
||||
|
||||
// ==================== 学校端套餐 ====================
|
||||
|
||||
@ -29,30 +29,30 @@ export interface UpdateThemeData {
|
||||
|
||||
// 获取主题列表
|
||||
export function getThemeList() {
|
||||
return http.get('/admin/themes');
|
||||
return http.get('/v1/admin/themes');
|
||||
}
|
||||
|
||||
// 获取主题详情
|
||||
export function getThemeDetail(id: number) {
|
||||
return http.get(`/admin/themes/${id}`);
|
||||
return http.get(`/v1/admin/themes/${id}`);
|
||||
}
|
||||
|
||||
// 创建主题
|
||||
export function createTheme(data: CreateThemeData) {
|
||||
return http.post('/admin/themes', data);
|
||||
return http.post('/v1/admin/themes', data);
|
||||
}
|
||||
|
||||
// 更新主题
|
||||
export function updateTheme(id: number, data: UpdateThemeData) {
|
||||
return http.put(`/admin/themes/${id}`, data);
|
||||
return http.put(`/v1/admin/themes/${id}`, data);
|
||||
}
|
||||
|
||||
// 删除主题
|
||||
export function deleteTheme(id: number) {
|
||||
return http.delete(`/admin/themes/${id}`);
|
||||
return http.delete(`/v1/admin/themes/${id}`);
|
||||
}
|
||||
|
||||
// 重新排序主题
|
||||
export function reorderThemes(ids: number[]) {
|
||||
return http.put('/admin/themes/reorder', { ids });
|
||||
return http.put('/v1/admin/themes/reorder', { ids });
|
||||
}
|
||||
|
||||
@ -56,7 +56,6 @@ export default defineConfig({
|
||||
'/api': {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api\/v1/, '/api'),
|
||||
},
|
||||
'/uploads': {
|
||||
target: 'http://localhost:8080',
|
||||
|
||||
@ -8,7 +8,6 @@ import com.reading.platform.common.response.Result;
|
||||
import com.reading.platform.dto.request.CourseCreateRequest;
|
||||
import com.reading.platform.dto.request.CourseUpdateRequest;
|
||||
import com.reading.platform.entity.Course;
|
||||
import com.reading.platform.mapper.CourseMapper;
|
||||
import com.reading.platform.service.CourseService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@ -18,21 +17,17 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "Admin - Course", description = "System Course Management APIs for Admin")
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/courses")
|
||||
@RequestMapping("/api/v1/admin/courses")
|
||||
@RequiredArgsConstructor
|
||||
@RequireRole(UserRole.ADMIN)
|
||||
public class AdminCourseController {
|
||||
|
||||
private final CourseService courseService;
|
||||
private final CourseMapper courseMapper;
|
||||
|
||||
@Operation(summary = "Create system course")
|
||||
@PostMapping
|
||||
public Result<Course> createCourse(@Valid @RequestBody CourseCreateRequest request) {
|
||||
// System courses have null tenantId
|
||||
Course course = courseService.createCourse(null, request);
|
||||
course.setIsSystem(1);
|
||||
courseMapper.updateById(course); // Save isSystem to database
|
||||
Course course = courseService.createSystemCourse(request);
|
||||
return Result.success(course);
|
||||
}
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ import java.util.List;
|
||||
|
||||
@Tag(name = "Admin - Tenant", description = "Tenant Management APIs for Admin")
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/tenants")
|
||||
@RequestMapping("/api/v1/admin/tenants")
|
||||
@RequiredArgsConstructor
|
||||
@RequireRole(UserRole.ADMIN)
|
||||
public class AdminTenantController {
|
||||
|
||||
@ -1,22 +1,15 @@
|
||||
package com.reading.platform.controller.school;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.reading.platform.common.response.Result;
|
||||
import com.reading.platform.common.security.SecurityUtils;
|
||||
import com.reading.platform.entity.Clazz;
|
||||
import com.reading.platform.entity.Student;
|
||||
import com.reading.platform.entity.Teacher;
|
||||
import com.reading.platform.mapper.ClazzMapper;
|
||||
import com.reading.platform.mapper.StudentMapper;
|
||||
import com.reading.platform.mapper.TeacherMapper;
|
||||
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.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 统计数据控制器(学校端)
|
||||
@ -27,81 +20,50 @@ import java.util.*;
|
||||
@Tag(name = "学校端 - 统计数据")
|
||||
public class SchoolStatsController {
|
||||
|
||||
private final TeacherMapper teacherMapper;
|
||||
private final StudentMapper studentMapper;
|
||||
private final ClazzMapper clazzMapper;
|
||||
private final SchoolStatsService schoolStatsService;
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "获取学校统计数据")
|
||||
public Result<Map<String, Object>> getSchoolStats() {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("teacherCount", teacherMapper.selectCount(
|
||||
new LambdaQueryWrapper<Teacher>().eq(Teacher::getTenantId, tenantId)
|
||||
));
|
||||
stats.put("studentCount", studentMapper.selectCount(
|
||||
new LambdaQueryWrapper<Student>().eq(Student::getTenantId, tenantId)
|
||||
));
|
||||
stats.put("classCount", clazzMapper.selectCount(
|
||||
new LambdaQueryWrapper<Clazz>().eq(Clazz::getTenantId, tenantId)
|
||||
));
|
||||
stats.put("lessonCount", 0); // TODO: implement lesson count
|
||||
return Result.success(stats);
|
||||
return Result.success(schoolStatsService.getSchoolStats(tenantId));
|
||||
}
|
||||
|
||||
@GetMapping("/teachers")
|
||||
@Operation(summary = "获取活跃教师排行")
|
||||
public Result<List<Map<String, Object>>> getActiveTeachers(
|
||||
@RequestParam(defaultValue = "5") int limit) {
|
||||
List<Map<String, Object>> teachers = new ArrayList<>();
|
||||
// For now, return empty list
|
||||
return Result.success(teachers);
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
return Result.success(schoolStatsService.getActiveTeachers(tenantId, limit));
|
||||
}
|
||||
|
||||
@GetMapping("/courses")
|
||||
@Operation(summary = "获取课程使用统计")
|
||||
public Result<List<Map<String, Object>>> getCourseUsageStats() {
|
||||
List<Map<String, Object>> courses = new ArrayList<>();
|
||||
// For now, return empty list
|
||||
return Result.success(courses);
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
return Result.success(schoolStatsService.getCourseUsageStats(tenantId));
|
||||
}
|
||||
|
||||
@GetMapping("/activities")
|
||||
@Operation(summary = "获取近期活动")
|
||||
public Result<List<Map<String, Object>>> getRecentActivities(
|
||||
@RequestParam(defaultValue = "10") int limit) {
|
||||
List<Map<String, Object>> activities = new ArrayList<>();
|
||||
// For now, return empty list
|
||||
return Result.success(activities);
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
return Result.success(schoolStatsService.getRecentActivities(tenantId, limit));
|
||||
}
|
||||
|
||||
@GetMapping("/lesson-trend")
|
||||
@Operation(summary = "获取授课趋势")
|
||||
public Result<List<Map<String, Object>>> getLessonTrend(
|
||||
@RequestParam(defaultValue = "6") int months) {
|
||||
List<Map<String, Object>> trend = new ArrayList<>();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
|
||||
|
||||
for (int i = months - 1; i >= 0; i--) {
|
||||
LocalDate date = LocalDate.now().minusMonths(i);
|
||||
String month = date.format(formatter);
|
||||
|
||||
Map<String, Object> item = new HashMap<>();
|
||||
item.put("month", month);
|
||||
item.put("lessonCount", 0);
|
||||
item.put("studentCount", 0);
|
||||
trend.add(item);
|
||||
}
|
||||
|
||||
return Result.success(trend);
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
return Result.success(schoolStatsService.getLessonTrend(tenantId, months));
|
||||
}
|
||||
|
||||
@GetMapping("/course-distribution")
|
||||
@Operation(summary = "获取课程分布")
|
||||
public Result<List<Map<String, Object>>> getCourseDistribution() {
|
||||
List<Map<String, Object>> distribution = new ArrayList<>();
|
||||
// For now, return empty list
|
||||
return Result.success(distribution);
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
return Result.success(schoolStatsService.getCourseDistribution(tenantId));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
package com.reading.platform.controller.teacher;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
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.Clazz;
|
||||
import com.reading.platform.entity.Course;
|
||||
import com.reading.platform.mapper.ClazzMapper;
|
||||
import com.reading.platform.service.ClassService;
|
||||
import com.reading.platform.service.CourseService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@ -23,17 +22,13 @@ import java.util.List;
|
||||
public class TeacherCourseController {
|
||||
|
||||
private final CourseService courseService;
|
||||
private final ClazzMapper clazzMapper;
|
||||
private final ClassService classService;
|
||||
|
||||
@Operation(summary = "Get teacher's classes")
|
||||
@GetMapping("/classes")
|
||||
public Result<List<Clazz>> getClasses() {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
List<Clazz> classes = clazzMapper.selectList(
|
||||
new LambdaQueryWrapper<Clazz>()
|
||||
.eq(Clazz::getTenantId, tenantId)
|
||||
.eq(Clazz::getStatus, "active")
|
||||
);
|
||||
List<Clazz> classes = classService.getActiveClassesByTenantId(tenantId);
|
||||
return Result.success(classes);
|
||||
}
|
||||
|
||||
|
||||
@ -1,27 +1,17 @@
|
||||
package com.reading.platform.controller.teacher;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.reading.platform.common.response.Result;
|
||||
import com.reading.platform.common.security.SecurityUtils;
|
||||
import com.reading.platform.entity.Clazz;
|
||||
import com.reading.platform.entity.Course;
|
||||
import com.reading.platform.entity.Lesson;
|
||||
import com.reading.platform.entity.Student;
|
||||
import com.reading.platform.mapper.ClazzMapper;
|
||||
import com.reading.platform.mapper.CourseMapper;
|
||||
import com.reading.platform.mapper.LessonMapper;
|
||||
import com.reading.platform.mapper.StudentMapper;
|
||||
import com.reading.platform.service.TeacherStatsService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 统计数据控制器(教师端)
|
||||
@ -32,153 +22,35 @@ import java.util.*;
|
||||
@Tag(name = "教师端 - 统计数据")
|
||||
public class TeacherStatsController {
|
||||
|
||||
private final ClazzMapper clazzMapper;
|
||||
private final StudentMapper studentMapper;
|
||||
private final CourseMapper courseMapper;
|
||||
private final LessonMapper lessonMapper;
|
||||
private final TeacherStatsService teacherStatsService;
|
||||
|
||||
@GetMapping("/dashboard")
|
||||
@Operation(summary = "获取教师端首页统计数据")
|
||||
public Result<Map<String, Object>> getDashboard() {
|
||||
Long teacherId = SecurityUtils.getCurrentUserId();
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
|
||||
Map<String, Object> dashboard = new HashMap<>();
|
||||
|
||||
// 基础统计
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("classCount", clazzMapper.selectCount(
|
||||
new LambdaQueryWrapper<Clazz>().eq(Clazz::getTenantId, tenantId)
|
||||
));
|
||||
stats.put("studentCount", studentMapper.selectCount(
|
||||
new LambdaQueryWrapper<Student>().eq(Student::getTenantId, tenantId)
|
||||
));
|
||||
stats.put("courseCount", courseMapper.selectCount(
|
||||
new LambdaQueryWrapper<Course>().eq(Course::getTenantId, tenantId)
|
||||
));
|
||||
|
||||
// Lesson count (handle missing table gracefully)
|
||||
long lessonCount = 0;
|
||||
try {
|
||||
lessonCount = lessonMapper.selectCount(
|
||||
new LambdaQueryWrapper<Lesson>().eq(Lesson::getTeacherId, teacherId)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to query lessons table: {}", e.getMessage());
|
||||
}
|
||||
stats.put("lessonCount", lessonCount);
|
||||
dashboard.put("stats", stats);
|
||||
|
||||
// 今日课程
|
||||
LocalDate today = LocalDate.now();
|
||||
List<Lesson> todayLessons = new ArrayList<>();
|
||||
try {
|
||||
todayLessons = lessonMapper.selectList(
|
||||
new LambdaQueryWrapper<Lesson>()
|
||||
.eq(Lesson::getTeacherId, teacherId)
|
||||
.eq(Lesson::getLessonDate, today)
|
||||
.orderByAsc(Lesson::getStartTime)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to query today lessons: {}", e.getMessage());
|
||||
}
|
||||
dashboard.put("todayLessons", todayLessons);
|
||||
|
||||
// 推荐课程(热门课程)
|
||||
List<Course> recommendedCourses = courseMapper.selectList(
|
||||
new LambdaQueryWrapper<Course>()
|
||||
.eq(Course::getTenantId, tenantId)
|
||||
.eq(Course::getStatus, "published")
|
||||
.orderByDesc(Course::getUsageCount)
|
||||
.last("LIMIT 5")
|
||||
);
|
||||
dashboard.put("recommendedCourses", recommendedCourses);
|
||||
|
||||
// 本周统计
|
||||
Map<String, Object> weeklyStats = new HashMap<>();
|
||||
LocalDate weekAgo = LocalDate.now().minusDays(7);
|
||||
long weeklyLessonCount = 0;
|
||||
try {
|
||||
weeklyLessonCount = lessonMapper.selectCount(
|
||||
new LambdaQueryWrapper<Lesson>()
|
||||
.eq(Lesson::getTeacherId, teacherId)
|
||||
.ge(Lesson::getLessonDate, weekAgo)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to query weekly lessons: {}", e.getMessage());
|
||||
}
|
||||
weeklyStats.put("lessonCount", weeklyLessonCount);
|
||||
weeklyStats.put("studentParticipation", 85); // TODO: calculate actual participation
|
||||
weeklyStats.put("avgRating", 4.5); // TODO: calculate actual rating
|
||||
weeklyStats.put("totalDuration", 300); // TODO: calculate actual duration
|
||||
dashboard.put("weeklyStats", weeklyStats);
|
||||
|
||||
// 近期活动
|
||||
List<Map<String, Object>> recentActivities = new ArrayList<>();
|
||||
// For now, return empty list
|
||||
dashboard.put("recentActivities", recentActivities);
|
||||
|
||||
return Result.success(dashboard);
|
||||
return Result.success(teacherStatsService.getDashboard(teacherId, tenantId));
|
||||
}
|
||||
|
||||
@GetMapping("/today-lessons")
|
||||
@Operation(summary = "获取今日课程")
|
||||
public Result<List<Lesson>> getTodayLessons() {
|
||||
Long teacherId = SecurityUtils.getCurrentUserId();
|
||||
LocalDate today = LocalDate.now();
|
||||
|
||||
List<Lesson> lessons = new ArrayList<>();
|
||||
try {
|
||||
lessons = lessonMapper.selectList(
|
||||
new LambdaQueryWrapper<Lesson>()
|
||||
.eq(Lesson::getTeacherId, teacherId)
|
||||
.eq(Lesson::getLessonDate, today)
|
||||
.orderByAsc(Lesson::getStartTime)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to query today lessons: {}", e.getMessage());
|
||||
}
|
||||
return Result.success(lessons);
|
||||
return Result.success(teacherStatsService.getTodayLessons(teacherId));
|
||||
}
|
||||
|
||||
@GetMapping("/recommended-courses")
|
||||
@Operation(summary = "获取推荐课程")
|
||||
public Result<List<Course>> getRecommendedCourses() {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
|
||||
List<Course> courses = courseMapper.selectList(
|
||||
new LambdaQueryWrapper<Course>()
|
||||
.eq(Course::getTenantId, tenantId)
|
||||
.eq(Course::getStatus, "published")
|
||||
.orderByDesc(Course::getUsageCount)
|
||||
.last("LIMIT 10")
|
||||
);
|
||||
return Result.success(courses);
|
||||
return Result.success(teacherStatsService.getRecommendedCourses(tenantId));
|
||||
}
|
||||
|
||||
@GetMapping("/weekly-stats")
|
||||
@Operation(summary = "获取本周统计")
|
||||
public Result<Map<String, Object>> getWeeklyStats() {
|
||||
Long teacherId = SecurityUtils.getCurrentUserId();
|
||||
LocalDate weekAgo = LocalDate.now().minusDays(7);
|
||||
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
long lessonCount = 0;
|
||||
try {
|
||||
lessonCount = lessonMapper.selectCount(
|
||||
new LambdaQueryWrapper<Lesson>()
|
||||
.eq(Lesson::getTeacherId, teacherId)
|
||||
.ge(Lesson::getLessonDate, weekAgo)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to query weekly lesson count: {}", e.getMessage());
|
||||
}
|
||||
stats.put("lessonCount", lessonCount);
|
||||
stats.put("studentParticipation", 85);
|
||||
stats.put("avgRating", 4.5);
|
||||
stats.put("totalDuration", 300);
|
||||
|
||||
return Result.success(stats);
|
||||
return Result.success(teacherStatsService.getWeeklyStats(teacherId));
|
||||
}
|
||||
|
||||
@GetMapping("/lesson-trend")
|
||||
@ -186,57 +58,13 @@ public class TeacherStatsController {
|
||||
public Result<List<Map<String, Object>>> getLessonTrend(
|
||||
@RequestParam(defaultValue = "6") int months) {
|
||||
Long teacherId = SecurityUtils.getCurrentUserId();
|
||||
List<Map<String, Object>> trend = new ArrayList<>();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
|
||||
|
||||
for (int i = months - 1; i >= 0; i--) {
|
||||
LocalDate date = LocalDate.now().minusMonths(i);
|
||||
String month = date.format(formatter);
|
||||
LocalDate monthStart = date.withDayOfMonth(1);
|
||||
LocalDate monthEnd = date.plusMonths(1).withDayOfMonth(1);
|
||||
|
||||
long lessonCount = 0;
|
||||
try {
|
||||
lessonCount = lessonMapper.selectCount(
|
||||
new LambdaQueryWrapper<Lesson>()
|
||||
.eq(Lesson::getTeacherId, teacherId)
|
||||
.ge(Lesson::getLessonDate, monthStart)
|
||||
.lt(Lesson::getLessonDate, monthEnd)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to query lesson trend for {}: {}", month, e.getMessage());
|
||||
}
|
||||
|
||||
Map<String, Object> item = new HashMap<>();
|
||||
item.put("month", month);
|
||||
item.put("lessonCount", lessonCount);
|
||||
item.put("avgRating", 4.5); // TODO: calculate actual rating
|
||||
trend.add(item);
|
||||
}
|
||||
|
||||
return Result.success(trend);
|
||||
return Result.success(teacherStatsService.getLessonTrend(teacherId, months));
|
||||
}
|
||||
|
||||
@GetMapping("/course-usage")
|
||||
@Operation(summary = "获取课程使用统计")
|
||||
public Result<List<Map<String, Object>>> getCourseUsage() {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
List<Map<String, Object>> usage = new ArrayList<>();
|
||||
|
||||
List<Course> courses = courseMapper.selectList(
|
||||
new LambdaQueryWrapper<Course>()
|
||||
.eq(Course::getTenantId, tenantId)
|
||||
.orderByDesc(Course::getUsageCount)
|
||||
.last("LIMIT 10")
|
||||
);
|
||||
|
||||
for (Course course : courses) {
|
||||
Map<String, Object> item = new HashMap<>();
|
||||
item.put("name", course.getName());
|
||||
item.put("value", course.getUsageCount() != null ? course.getUsageCount() : 0);
|
||||
usage.add(item);
|
||||
}
|
||||
|
||||
return Result.success(usage);
|
||||
return Result.success(teacherStatsService.getCourseUsage(tenantId));
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,4 +28,6 @@ public interface ClassService {
|
||||
|
||||
List<Long> getTeacherIdsByClassId(Long classId);
|
||||
|
||||
List<Clazz> getActiveClassesByTenantId(Long tenantId);
|
||||
|
||||
}
|
||||
|
||||
@ -14,6 +14,8 @@ public interface CourseService {
|
||||
|
||||
Course createCourse(Long tenantId, CourseCreateRequest request);
|
||||
|
||||
Course createSystemCourse(CourseCreateRequest request);
|
||||
|
||||
Course updateCourse(Long id, CourseUpdateRequest request);
|
||||
|
||||
Course getCourseById(Long id);
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
package com.reading.platform.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* School Statistics Service Interface
|
||||
*/
|
||||
public interface SchoolStatsService {
|
||||
|
||||
/**
|
||||
* Get school statistics (teacher count, student count, class count)
|
||||
*/
|
||||
Map<String, Object> getSchoolStats(Long tenantId);
|
||||
|
||||
/**
|
||||
* Get active teachers ranking
|
||||
*/
|
||||
List<Map<String, Object>> getActiveTeachers(Long tenantId, int limit);
|
||||
|
||||
/**
|
||||
* Get course usage statistics
|
||||
*/
|
||||
List<Map<String, Object>> getCourseUsageStats(Long tenantId);
|
||||
|
||||
/**
|
||||
* Get recent activities
|
||||
*/
|
||||
List<Map<String, Object>> getRecentActivities(Long tenantId, int limit);
|
||||
|
||||
/**
|
||||
* Get lesson trend by month
|
||||
*/
|
||||
List<Map<String, Object>> getLessonTrend(Long tenantId, int months);
|
||||
|
||||
/**
|
||||
* Get course distribution
|
||||
*/
|
||||
List<Map<String, Object>> getCourseDistribution(Long tenantId);
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package com.reading.platform.service;
|
||||
|
||||
import com.reading.platform.entity.Course;
|
||||
import com.reading.platform.entity.Lesson;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Teacher Statistics Service Interface
|
||||
*/
|
||||
public interface TeacherStatsService {
|
||||
|
||||
/**
|
||||
* Get teacher dashboard statistics
|
||||
*/
|
||||
Map<String, Object> getDashboard(Long teacherId, Long tenantId);
|
||||
|
||||
/**
|
||||
* Get today's lessons
|
||||
*/
|
||||
List<Lesson> getTodayLessons(Long teacherId);
|
||||
|
||||
/**
|
||||
* Get recommended courses
|
||||
*/
|
||||
List<Course> getRecommendedCourses(Long tenantId);
|
||||
|
||||
/**
|
||||
* Get weekly statistics
|
||||
*/
|
||||
Map<String, Object> getWeeklyStats(Long teacherId);
|
||||
|
||||
/**
|
||||
* Get lesson trend by month
|
||||
*/
|
||||
List<Map<String, Object>> getLessonTrend(Long teacherId, int months);
|
||||
|
||||
/**
|
||||
* Get course usage statistics
|
||||
*/
|
||||
List<Map<String, Object>> getCourseUsage(Long tenantId);
|
||||
}
|
||||
@ -172,4 +172,13 @@ public class ClassServiceImpl implements ClassService {
|
||||
return teacherIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Clazz> getActiveClassesByTenantId(Long tenantId) {
|
||||
return clazzMapper.selectList(
|
||||
new LambdaQueryWrapper<Clazz>()
|
||||
.eq(Clazz::getTenantId, tenantId)
|
||||
.eq(Clazz::getStatus, "active")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -84,6 +84,64 @@ public class CourseServiceImpl implements CourseService {
|
||||
return course;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Course createSystemCourse(CourseCreateRequest request) {
|
||||
Course course = new Course();
|
||||
course.setTenantId(null); // System courses have null tenantId
|
||||
course.setName(request.getName());
|
||||
course.setCode(request.getCode());
|
||||
course.setDescription(request.getDescription());
|
||||
course.setCoverUrl(request.getCoverUrl());
|
||||
course.setCoverImagePath(request.getCoverImagePath());
|
||||
course.setCategory(request.getCategory());
|
||||
course.setAgeRange(request.getAgeRange());
|
||||
course.setDifficultyLevel(request.getDifficultyLevel());
|
||||
course.setDurationMinutes(request.getDurationMinutes());
|
||||
course.setObjectives(request.getObjectives());
|
||||
course.setStatus("draft");
|
||||
course.setIsSystem(1);
|
||||
|
||||
// Course Package Fields
|
||||
course.setCoreContent(request.getCoreContent());
|
||||
course.setIntroSummary(request.getIntroSummary());
|
||||
course.setIntroHighlights(request.getIntroHighlights());
|
||||
course.setIntroGoals(request.getIntroGoals());
|
||||
course.setIntroSchedule(request.getIntroSchedule());
|
||||
course.setIntroKeyPoints(request.getIntroKeyPoints());
|
||||
course.setIntroMethods(request.getIntroMethods());
|
||||
course.setIntroEvaluation(request.getIntroEvaluation());
|
||||
course.setIntroNotes(request.getIntroNotes());
|
||||
course.setScheduleRefData(request.getScheduleRefData());
|
||||
course.setEnvironmentConstruction(request.getEnvironmentConstruction());
|
||||
course.setThemeId(request.getThemeId());
|
||||
course.setPictureBookName(request.getPictureBookName());
|
||||
course.setEbookPaths(request.getEbookPaths());
|
||||
course.setAudioPaths(request.getAudioPaths());
|
||||
course.setVideoPaths(request.getVideoPaths());
|
||||
course.setOtherResources(request.getOtherResources());
|
||||
course.setPptPath(request.getPptPath());
|
||||
course.setPptName(request.getPptName());
|
||||
course.setPosterPaths(request.getPosterPaths());
|
||||
course.setTools(request.getTools());
|
||||
course.setStudentMaterials(request.getStudentMaterials());
|
||||
course.setLessonPlanData(request.getLessonPlanData());
|
||||
course.setActivitiesData(request.getActivitiesData());
|
||||
course.setAssessmentData(request.getAssessmentData());
|
||||
course.setGradeTags(request.getGradeTags());
|
||||
course.setDomainTags(request.getDomainTags());
|
||||
course.setHasCollectiveLesson(request.getHasCollectiveLesson() != null && request.getHasCollectiveLesson() ? 1 : 0);
|
||||
|
||||
course.setVersion("1.0");
|
||||
course.setIsLatest(1);
|
||||
course.setUsageCount(0);
|
||||
course.setTeacherCount(0);
|
||||
|
||||
courseMapper.insert(course);
|
||||
log.info("System course created: id={}, name={}", course.getId(), course.getName());
|
||||
return course;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Course updateCourse(Long id, CourseUpdateRequest request) {
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
package com.reading.platform.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.reading.platform.entity.Clazz;
|
||||
import com.reading.platform.entity.Student;
|
||||
import com.reading.platform.entity.Teacher;
|
||||
import com.reading.platform.mapper.ClazzMapper;
|
||||
import com.reading.platform.mapper.StudentMapper;
|
||||
import com.reading.platform.mapper.TeacherMapper;
|
||||
import com.reading.platform.service.SchoolStatsService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* School Statistics Service Implementation
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SchoolStatsServiceImpl implements SchoolStatsService {
|
||||
|
||||
private final TeacherMapper teacherMapper;
|
||||
private final StudentMapper studentMapper;
|
||||
private final ClazzMapper clazzMapper;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getSchoolStats(Long tenantId) {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("teacherCount", teacherMapper.selectCount(
|
||||
new LambdaQueryWrapper<Teacher>().eq(Teacher::getTenantId, tenantId)
|
||||
));
|
||||
stats.put("studentCount", studentMapper.selectCount(
|
||||
new LambdaQueryWrapper<Student>().eq(Student::getTenantId, tenantId)
|
||||
));
|
||||
stats.put("classCount", clazzMapper.selectCount(
|
||||
new LambdaQueryWrapper<Clazz>().eq(Clazz::getTenantId, tenantId)
|
||||
));
|
||||
stats.put("lessonCount", 0); // TODO: implement lesson count
|
||||
return stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getActiveTeachers(Long tenantId, int limit) {
|
||||
List<Map<String, Object>> teachers = new ArrayList<>();
|
||||
// For now, return empty list
|
||||
return teachers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getCourseUsageStats(Long tenantId) {
|
||||
List<Map<String, Object>> courses = new ArrayList<>();
|
||||
// For now, return empty list
|
||||
return courses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getRecentActivities(Long tenantId, int limit) {
|
||||
List<Map<String, Object>> activities = new ArrayList<>();
|
||||
// For now, return empty list
|
||||
return activities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getLessonTrend(Long tenantId, int months) {
|
||||
List<Map<String, Object>> trend = new ArrayList<>();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
|
||||
|
||||
for (int i = months - 1; i >= 0; i--) {
|
||||
LocalDate date = LocalDate.now().minusMonths(i);
|
||||
String month = date.format(formatter);
|
||||
|
||||
Map<String, Object> item = new HashMap<>();
|
||||
item.put("month", month);
|
||||
item.put("lessonCount", 0);
|
||||
item.put("studentCount", 0);
|
||||
trend.add(item);
|
||||
}
|
||||
|
||||
return trend;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getCourseDistribution(Long tenantId) {
|
||||
List<Map<String, Object>> distribution = new ArrayList<>();
|
||||
// For now, return empty list
|
||||
return distribution;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,215 @@
|
||||
package com.reading.platform.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.reading.platform.entity.Clazz;
|
||||
import com.reading.platform.entity.Course;
|
||||
import com.reading.platform.entity.Lesson;
|
||||
import com.reading.platform.entity.Student;
|
||||
import com.reading.platform.mapper.ClazzMapper;
|
||||
import com.reading.platform.mapper.CourseMapper;
|
||||
import com.reading.platform.mapper.LessonMapper;
|
||||
import com.reading.platform.mapper.StudentMapper;
|
||||
import com.reading.platform.service.TeacherStatsService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Teacher Statistics Service Implementation
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TeacherStatsServiceImpl implements TeacherStatsService {
|
||||
|
||||
private final ClazzMapper clazzMapper;
|
||||
private final StudentMapper studentMapper;
|
||||
private final CourseMapper courseMapper;
|
||||
private final LessonMapper lessonMapper;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getDashboard(Long teacherId, Long tenantId) {
|
||||
Map<String, Object> dashboard = new HashMap<>();
|
||||
|
||||
// 基础统计
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("classCount", clazzMapper.selectCount(
|
||||
new LambdaQueryWrapper<Clazz>().eq(Clazz::getTenantId, tenantId)
|
||||
));
|
||||
stats.put("studentCount", studentMapper.selectCount(
|
||||
new LambdaQueryWrapper<Student>().eq(Student::getTenantId, tenantId)
|
||||
));
|
||||
stats.put("courseCount", courseMapper.selectCount(
|
||||
new LambdaQueryWrapper<Course>().eq(Course::getTenantId, tenantId)
|
||||
));
|
||||
|
||||
// Lesson count (handle missing table gracefully)
|
||||
long lessonCount = 0;
|
||||
try {
|
||||
lessonCount = lessonMapper.selectCount(
|
||||
new LambdaQueryWrapper<Lesson>().eq(Lesson::getTeacherId, teacherId)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to query lessons table: {}", e.getMessage());
|
||||
}
|
||||
stats.put("lessonCount", lessonCount);
|
||||
dashboard.put("stats", stats);
|
||||
|
||||
// 今日课程
|
||||
LocalDate today = LocalDate.now();
|
||||
List<Lesson> todayLessons = new ArrayList<>();
|
||||
try {
|
||||
todayLessons = lessonMapper.selectList(
|
||||
new LambdaQueryWrapper<Lesson>()
|
||||
.eq(Lesson::getTeacherId, teacherId)
|
||||
.eq(Lesson::getLessonDate, today)
|
||||
.orderByAsc(Lesson::getStartTime)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to query today lessons: {}", e.getMessage());
|
||||
}
|
||||
dashboard.put("todayLessons", todayLessons);
|
||||
|
||||
// 推荐课程(热门课程)
|
||||
List<Course> recommendedCourses = courseMapper.selectList(
|
||||
new LambdaQueryWrapper<Course>()
|
||||
.eq(Course::getTenantId, tenantId)
|
||||
.eq(Course::getStatus, "published")
|
||||
.orderByDesc(Course::getUsageCount)
|
||||
.last("LIMIT 5")
|
||||
);
|
||||
dashboard.put("recommendedCourses", recommendedCourses);
|
||||
|
||||
// 本周统计
|
||||
Map<String, Object> weeklyStats = new HashMap<>();
|
||||
LocalDate weekAgo = LocalDate.now().minusDays(7);
|
||||
long weeklyLessonCount = 0;
|
||||
try {
|
||||
weeklyLessonCount = lessonMapper.selectCount(
|
||||
new LambdaQueryWrapper<Lesson>()
|
||||
.eq(Lesson::getTeacherId, teacherId)
|
||||
.ge(Lesson::getLessonDate, weekAgo)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to query weekly lessons: {}", e.getMessage());
|
||||
}
|
||||
weeklyStats.put("lessonCount", weeklyLessonCount);
|
||||
weeklyStats.put("studentParticipation", 85); // TODO: calculate actual participation
|
||||
weeklyStats.put("avgRating", 4.5); // TODO: calculate actual rating
|
||||
weeklyStats.put("totalDuration", 300); // TODO: calculate actual duration
|
||||
dashboard.put("weeklyStats", weeklyStats);
|
||||
|
||||
// 近期活动
|
||||
List<Map<String, Object>> recentActivities = new ArrayList<>();
|
||||
dashboard.put("recentActivities", recentActivities);
|
||||
|
||||
return dashboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Lesson> getTodayLessons(Long teacherId) {
|
||||
LocalDate today = LocalDate.now();
|
||||
List<Lesson> lessons = new ArrayList<>();
|
||||
try {
|
||||
lessons = lessonMapper.selectList(
|
||||
new LambdaQueryWrapper<Lesson>()
|
||||
.eq(Lesson::getTeacherId, teacherId)
|
||||
.eq(Lesson::getLessonDate, today)
|
||||
.orderByAsc(Lesson::getStartTime)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to query today lessons: {}", e.getMessage());
|
||||
}
|
||||
return lessons;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Course> getRecommendedCourses(Long tenantId) {
|
||||
return courseMapper.selectList(
|
||||
new LambdaQueryWrapper<Course>()
|
||||
.eq(Course::getTenantId, tenantId)
|
||||
.eq(Course::getStatus, "published")
|
||||
.orderByDesc(Course::getUsageCount)
|
||||
.last("LIMIT 10")
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getWeeklyStats(Long teacherId) {
|
||||
LocalDate weekAgo = LocalDate.now().minusDays(7);
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
long lessonCount = 0;
|
||||
try {
|
||||
lessonCount = lessonMapper.selectCount(
|
||||
new LambdaQueryWrapper<Lesson>()
|
||||
.eq(Lesson::getTeacherId, teacherId)
|
||||
.ge(Lesson::getLessonDate, weekAgo)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to query weekly lesson count: {}", e.getMessage());
|
||||
}
|
||||
stats.put("lessonCount", lessonCount);
|
||||
stats.put("studentParticipation", 85);
|
||||
stats.put("avgRating", 4.5);
|
||||
stats.put("totalDuration", 300);
|
||||
return stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getLessonTrend(Long teacherId, int months) {
|
||||
List<Map<String, Object>> trend = new ArrayList<>();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
|
||||
|
||||
for (int i = months - 1; i >= 0; i--) {
|
||||
LocalDate date = LocalDate.now().minusMonths(i);
|
||||
String month = date.format(formatter);
|
||||
LocalDate monthStart = date.withDayOfMonth(1);
|
||||
LocalDate monthEnd = date.plusMonths(1).withDayOfMonth(1);
|
||||
|
||||
long lessonCount = 0;
|
||||
try {
|
||||
lessonCount = lessonMapper.selectCount(
|
||||
new LambdaQueryWrapper<Lesson>()
|
||||
.eq(Lesson::getTeacherId, teacherId)
|
||||
.ge(Lesson::getLessonDate, monthStart)
|
||||
.lt(Lesson::getLessonDate, monthEnd)
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to query lesson trend for {}: {}", month, e.getMessage());
|
||||
}
|
||||
|
||||
Map<String, Object> item = new HashMap<>();
|
||||
item.put("month", month);
|
||||
item.put("lessonCount", lessonCount);
|
||||
item.put("avgRating", 4.5); // TODO: calculate actual rating
|
||||
trend.add(item);
|
||||
}
|
||||
|
||||
return trend;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getCourseUsage(Long tenantId) {
|
||||
List<Map<String, Object>> usage = new ArrayList<>();
|
||||
|
||||
List<Course> courses = courseMapper.selectList(
|
||||
new LambdaQueryWrapper<Course>()
|
||||
.eq(Course::getTenantId, tenantId)
|
||||
.orderByDesc(Course::getUsageCount)
|
||||
.last("LIMIT 10")
|
||||
);
|
||||
|
||||
for (Course course : courses) {
|
||||
Map<String, Object> item = new HashMap<>();
|
||||
item.put("name", course.getName());
|
||||
item.put("value", course.getUsageCount() != null ? course.getUsageCount() : 0);
|
||||
usage.add(item);
|
||||
}
|
||||
|
||||
return usage;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user