/* * 自然写教学资源管理与内容分发系统软件 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 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 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 items; private Map>> facets; // 聚合面 public SearchResult(long total, int page, List items) { this.total = total; this.page = page; this.items = items; } public long getTotal() { return total; } public List getItems() { return items; } public void setFacets(Map>> f) { this.facets = f; } } // ============================================================ // API接口实现 // ============================================================ /** * 资源检索接口 GET /api/v1/resource/search * * 使用Elasticsearch进行全文检索,支持: * - 关键词匹配(资源名称、描述、标签) * - 多条件组合过滤(年级+学科+出版社+类型) * - 聚合面统计(各分类维度的资源数量) * - 分页排序 * * 权限控制:教师仅可搜索本校已授权资源 */ public Map 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 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 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 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 uploadResource(Map 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 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 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 getResourceVersions(String resourceId) { logger.info("查询资源版本: id=" + resourceId); // 查询版本历史 // List versions = versionMapper.selectByResourceId(resourceId); Map 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 getCategoryTree() { // 从MySQL查询分类数据并构建树形结构 // List roots = buildCategoryTree(); Map result = new HashMap<>(); result.put("code", 0); result.put("data", new ArrayList()); return result; } /** * 获取资源使用统计 GET /api/v1/stat/resource/{id} * * 从ClickHouse查询资源的使用统计数据(下载量、使用次数、终端分布) */ public Map getResourceStats(String resourceId) { logger.info("资源统计查询: id=" + resourceId); // 从ClickHouse查询使用统计 // ResourceStats stats = clickhouseClient.queryResourceStats(resourceId); Map 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; } }