Files
2026-03-22 15:24:40 +08:00

397 lines
16 KiB
Java
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 自然写教学资源管理与内容分发系统软件 V1.0
* controller/ResourceController.java - 资源CRUD与检索API
*/
package com.writech.resource.controller;
import java.util.*;
import java.util.logging.Logger;
/**
* 资源管理控制器
*
* 提供教学资源(课件、字帖、试卷、模板)的增删改查接口,
* 支持按年级/学科/出版社分类检索(Elasticsearch全文检索),
* CDN签名URL下载,教师自定义上传,版本管理等功能。
*/
public class ResourceController {
private static final Logger logger =
Logger.getLogger(ResourceController.class.getName());
// ============================================================
// 数据模型
// ============================================================
/** 资源类型枚举 */
public enum ResourceType {
COURSEWARE, // 课件(PPT/PDF
COPYBOOK, // 字帖模板
EXAM_PAPER, // 试卷
TEMPLATE, // 通用模板
DOT_CODE, // 点阵码资源
VIDEO, // 教学视频
AUDIO, // 音频资料
IMAGE // 图片素材
}
/** 审核状态枚举 */
public enum AuditStatus {
PENDING, // 待审核
APPROVED, // 已通过
REJECTED, // 已驳回
WITHDRAWN // 已撤回
}
/** 资源元数据模型(对应MySQL resource表) */
public static class ResourceMetadata {
private String id;
private String name;
private String description;
private ResourceType type;
private String subject; // 学科
private String grade; // 年级
private String publisher; // 出版社
private String version; // 版本号
private AuditStatus auditStatus;
private String fileKey; // OSS文件Key
private long fileSize; // 文件大小(字节)
private String mimeType; // MIME类型
private String thumbnailUrl; // 缩略图URL
private String uploaderId; // 上传者ID
private String uploaderName; // 上传者姓名
private String schoolId; // 所属学校
private String tags; // 标签(逗号分隔)
private int downloadCount; // 下载次数
private int useCount; // 使用次数
private Date createdAt;
private Date updatedAt;
// Getter/Setter
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String desc) { this.description = desc; }
public ResourceType getType() { return type; }
public void setType(ResourceType type) { this.type = type; }
public String getSubject() { return subject; }
public void setSubject(String subject) { this.subject = subject; }
public String getGrade() { return grade; }
public void setGrade(String grade) { this.grade = grade; }
public String getPublisher() { return publisher; }
public void setPublisher(String publisher) { this.publisher = publisher; }
public AuditStatus getAuditStatus() { return auditStatus; }
public void setAuditStatus(AuditStatus s) { this.auditStatus = s; }
public String getFileKey() { return fileKey; }
public void setFileKey(String key) { this.fileKey = key; }
public long getFileSize() { return fileSize; }
public void setFileSize(long size) { this.fileSize = size; }
public String getSchoolId() { return schoolId; }
public void setSchoolId(String schoolId) { this.schoolId = schoolId; }
public int getDownloadCount() { return downloadCount; }
public int getUseCount() { return useCount; }
public Date getCreatedAt() { return createdAt; }
public Date getUpdatedAt() { return updatedAt; }
}
/** 分类目录树节点 */
public static class CategoryNode {
private String id;
private String name;
private String parentId;
private int level; // 层级(1=年级, 2=学科, 3=出版社)
private int sortOrder;
private List<CategoryNode> children;
public CategoryNode(String id, String name, String parentId, int level) {
this.id = id;
this.name = name;
this.parentId = parentId;
this.level = level;
this.children = new ArrayList<>();
}
public String getId() { return id; }
public String getName() { return name; }
public List<CategoryNode> getChildren() { return children; }
public void addChild(CategoryNode child) { children.add(child); }
}
/** 资源搜索请求 */
public static class SearchRequest {
private String keyword; // 搜索关键词
private String subject; // 学科过滤
private String grade; // 年级过滤
private String publisher; // 出版社过滤
private ResourceType type; // 资源类型过滤
private String schoolId; // 学校授权范围
private int page;
private int pageSize;
private String sortBy; // 排序字段
private String sortOrder; // ASC/DESC
public String getKeyword() { return keyword; }
public void setKeyword(String kw) { this.keyword = kw; }
public String getSubject() { return subject; }
public void setSubject(String s) { this.subject = s; }
public String getGrade() { return grade; }
public void setGrade(String g) { this.grade = g; }
public String getPublisher() { return publisher; }
public void setPublisher(String p) { this.publisher = p; }
public ResourceType getType() { return type; }
public void setType(ResourceType t) { this.type = t; }
public int getPage() { return page > 0 ? page : 1; }
public int getPageSize() { return pageSize > 0 ? Math.min(pageSize, 100) : 20; }
}
/** 搜索结果 */
public static class SearchResult {
private long total;
private int page;
private List<ResourceMetadata> items;
private Map<String, List<Map<String, Object>>> facets; // 聚合面
public SearchResult(long total, int page, List<ResourceMetadata> items) {
this.total = total;
this.page = page;
this.items = items;
}
public long getTotal() { return total; }
public List<ResourceMetadata> getItems() { return items; }
public void setFacets(Map<String, List<Map<String, Object>>> f) { this.facets = f; }
}
// ============================================================
// API接口实现
// ============================================================
/**
* 资源检索接口 GET /api/v1/resource/search
*
* 使用Elasticsearch进行全文检索,支持:
* - 关键词匹配(资源名称、描述、标签)
* - 多条件组合过滤(年级+学科+出版社+类型)
* - 聚合面统计(各分类维度的资源数量)
* - 分页排序
*
* 权限控制:教师仅可搜索本校已授权资源
*/
public Map<String, Object> searchResources(SearchRequest request) {
logger.info(String.format(
"资源检索: keyword=%s, subject=%s, grade=%s, publisher=%s",
request.getKeyword(), request.getSubject(),
request.getGrade(), request.getPublisher()
));
// 构建Elasticsearch查询
// BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 关键词全文匹配(multi_match查询名称+描述+标签)
// if (request.getKeyword() != null && !request.getKeyword().isEmpty()) {
// boolQuery.must(QueryBuilders.multiMatchQuery(
// request.getKeyword(), "name", "description", "tags"
// ).type(MultiMatchQueryBuilder.Type.BEST_FIELDS));
// }
// 条件过滤
// if (request.getSubject() != null) {
// boolQuery.filter(QueryBuilders.termQuery("subject", request.getSubject()));
// }
// if (request.getGrade() != null) {
// boolQuery.filter(QueryBuilders.termQuery("grade", request.getGrade()));
// }
// if (request.getPublisher() != null) {
// boolQuery.filter(QueryBuilders.termQuery("publisher", request.getPublisher()));
// }
// if (request.getType() != null) {
// boolQuery.filter(QueryBuilders.termQuery("type", request.getType().name()));
// }
// 学校授权过滤(仅返回该校已授权的资源)
// boolQuery.filter(QueryBuilders.termQuery("school_id", request.getSchoolId()));
// 仅返回审核通过的资源
// boolQuery.filter(QueryBuilders.termQuery("audit_status", "APPROVED"));
// 聚合统计(按学科/年级/出版社/类型分组计数)
// AggregationBuilder subjectAgg = AggregationBuilders.terms("by_subject").field("subject");
// AggregationBuilder gradeAgg = AggregationBuilders.terms("by_grade").field("grade");
// 执行搜索
// SearchResponse response = elasticsearchClient.search(searchRequest);
Map<String, Object> result = new HashMap<>();
result.put("code", 0);
result.put("message", "success");
result.put("data", new SearchResult(0, request.getPage(), new ArrayList<>()));
return result;
}
/**
* 资源下载接口 GET /api/v1/resource/download/{id}
*
* 生成CDN签名URL返回给客户端,签名URL有效期30分钟。
* 同时记录下载次数,用于使用统计。
*
* 安全措施:
* - Referer校验:仅允许来自writech.com域名的请求
* - 签名URL:包含过期时间和HMAC签名,防盗链
* - 数字水印:下载时可选添加水印(包含学校/教师标识)
*/
public Map<String, Object> downloadResource(String resourceId, String userId) {
logger.info(String.format("资源下载: id=%s, user=%s", resourceId, userId));
// 查询资源元数据
// ResourceMetadata resource = resourceMapper.selectById(resourceId);
// if (resource == null) {
// return errorResponse(404, "资源不存在");
// }
// 权限校验:检查用户是否有权访问该资源
// if (!permissionService.canAccess(userId, resource.getSchoolId())) {
// return errorResponse(403, "无权访问此资源");
// }
// 生成CDN签名下载URL
// String signedUrl = cdnService.generateSignedUrl(
// resource.getFileKey(),
// 30 * 60 // 有效期30分钟
// );
// 异步更新下载计数
// asyncUpdateDownloadCount(resourceId);
Map<String, Object> result = new HashMap<>();
result.put("code", 0);
result.put("data", Map.of(
"resource_id", resourceId,
"download_url", "",
"expires_in", 1800,
"file_name", "",
"file_size", 0
));
return result;
}
/**
* 教师上传资源接口 POST /api/v1/resource/upload
*
* 教师可上传自定义教学资源,上传后状态为PENDING(待审核),
* 需管理员审核通过后才可被其他教师检索和使用。
*
* 上传流程:
* 1. 前端分片上传到OSS(使用STS临时凭证)
* 2. 上传完成后调用此接口创建资源元数据
* 3. 系统自动生成缩略图
* 4. 同步索引到Elasticsearch
*/
public Map<String, Object> uploadResource(Map<String, Object> uploadRequest) {
String name = (String) uploadRequest.get("name");
String description = (String) uploadRequest.get("description");
String fileKey = (String) uploadRequest.get("file_key");
String subject = (String) uploadRequest.get("subject");
String grade = (String) uploadRequest.get("grade");
String typeStr = (String) uploadRequest.get("type");
logger.info(String.format(
"教师上传资源: name=%s, subject=%s, grade=%s, type=%s",
name, subject, grade, typeStr
));
// 参数校验
if (name == null || name.trim().isEmpty()) {
Map<String, Object> err = new HashMap<>();
err.put("code", 400);
err.put("message", "资源名称不能为空");
return err;
}
// 创建资源元数据记录(状态为PENDING待审核)
ResourceMetadata resource = new ResourceMetadata();
resource.setId(UUID.randomUUID().toString().replace("-", ""));
resource.setName(name);
resource.setDescription(description);
resource.setSubject(subject);
resource.setGrade(grade);
resource.setFileKey(fileKey);
resource.setAuditStatus(AuditStatus.PENDING);
// 插入MySQL
// resourceMapper.insert(resource);
// 异步生成缩略图
// asyncGenerateThumbnail(resource.getId(), fileKey);
// 同步到Elasticsearch索引
// elasticsearchService.indexResource(resource);
Map<String, Object> result = new HashMap<>();
result.put("code", 0);
result.put("message", "上传成功,等待审核");
result.put("data", Map.of("resource_id", resource.getId()));
return result;
}
/**
* 获取资源版本历史 GET /api/v1/resource/versions/{id}
*
* 返回资源的所有历史版本列表,支持查看和回滚。
*/
public Map<String, Object> getResourceVersions(String resourceId) {
logger.info("查询资源版本: id=" + resourceId);
// 查询版本历史
// List<ResourceVersion> versions = versionMapper.selectByResourceId(resourceId);
Map<String, Object> result = new HashMap<>();
result.put("code", 0);
result.put("data", Map.of(
"resource_id", resourceId,
"versions", new ArrayList<>()
));
return result;
}
/**
* 获取分类目录树 GET /api/v1/resource/categories
*
* 返回三级分类目录树:年级 → 学科 → 出版社
*/
public Map<String, Object> getCategoryTree() {
// 从MySQL查询分类数据并构建树形结构
// List<CategoryNode> roots = buildCategoryTree();
Map<String, Object> result = new HashMap<>();
result.put("code", 0);
result.put("data", new ArrayList<CategoryNode>());
return result;
}
/**
* 获取资源使用统计 GET /api/v1/stat/resource/{id}
*
* 从ClickHouse查询资源的使用统计数据(下载量、使用次数、终端分布)
*/
public Map<String, Object> getResourceStats(String resourceId) {
logger.info("资源统计查询: id=" + resourceId);
// 从ClickHouse查询使用统计
// ResourceStats stats = clickhouseClient.queryResourceStats(resourceId);
Map<String, Object> result = new HashMap<>();
result.put("code", 0);
result.put("data", Map.of(
"resource_id", resourceId,
"download_count", 0,
"use_count", 0,
"terminal_distribution", Map.of(
"pad", 0, "pc", 0, "mobile", 0, "board", 0
),
"daily_trend", new ArrayList<>()
));
return result;
}
}