@@ -63,37 +63,8 @@ public class BaseApiController {
|
|||||||
* 返回 {id, name, role: "teacher", profilePicture, school: {id, name}, currentRole}
|
* 返回 {id, name, role: "teacher", profilePicture, school: {id, name}, currentRole}
|
||||||
*/
|
*/
|
||||||
@GetMapping("GetUserInfo")
|
@GetMapping("GetUserInfo")
|
||||||
public Map<String, Object> getUserInfo(BoaUser authUserWithDept) {
|
public BoaUser getUserInfo(BoaUser authUserWithDept) {
|
||||||
// 开发联调兜底:无 CAS 会话时从 UserContext(currentrole)取身份;生产 CAS 下 BoaUser 非空
|
return authUserWithDept;
|
||||||
Integer uid = authUserWithDept != null && authUserWithDept.getId() != null
|
|
||||||
? authUserWithDept.getId() : UserContext.getUserId();
|
|
||||||
Integer schId = schIdOf(authUserWithDept);
|
|
||||||
String teacherName = uid != null ? teacherNameOf(uid) : "";
|
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
|
||||||
result.put("id", uid);
|
|
||||||
result.put("name", authUserWithDept != null && authUserWithDept.getName() != null
|
|
||||||
? authUserWithDept.getName() : teacherName);
|
|
||||||
result.put("role", "teacher");
|
|
||||||
result.put("profilePicture", "");
|
|
||||||
Map<String, Object> school = new LinkedHashMap<>();
|
|
||||||
if (authUserWithDept != null && authUserWithDept.getSchool() != null) {
|
|
||||||
school.put("id", authUserWithDept.getSchool().getId());
|
|
||||||
school.put("name", authUserWithDept.getSchool().getName());
|
|
||||||
} else if (schId != null) {
|
|
||||||
school.put("id", schId);
|
|
||||||
school.put("name", "测试学校");
|
|
||||||
}
|
|
||||||
result.put("school", school);
|
|
||||||
UserSysRole role = new UserSysRole();
|
|
||||||
role.setSchId(schId);
|
|
||||||
role.setUserId(uid);
|
|
||||||
if (authUserWithDept != null && authUserWithDept.getRoleIds() != null && !authUserWithDept.getRoleIds().isEmpty()) {
|
|
||||||
role.setRoleId(authUserWithDept.getRoleIds().get(0));
|
|
||||||
}
|
|
||||||
Integer childId = UserContext.getChildId();
|
|
||||||
role.setChildId(childId == null ? 0 : childId);
|
|
||||||
result.put("currentRole", role);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================ GetUserMod ============================
|
// ============================ GetUserMod ============================
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import com.yunhai.profession.service.IAreaService;
|
|||||||
import com.yunhai.profession.service.IProfessionService;
|
import com.yunhai.profession.service.IProfessionService;
|
||||||
import com.yunhai.profession.service.IStatsService;
|
import com.yunhai.profession.service.IStatsService;
|
||||||
import com.yunhai.profession.utils.ProfessionContext;
|
import com.yunhai.profession.utils.ProfessionContext;
|
||||||
import com.yunhai.profession.utils.ProfessionDicts;
|
|
||||||
import com.yunhai.platform.utils.model.BoaUser;
|
import com.yunhai.platform.utils.model.BoaUser;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
@@ -74,10 +73,10 @@ public class ProfessionController {
|
|||||||
@RequestParam(value = "PageIndex", defaultValue = "1") int pageIndex,
|
@RequestParam(value = "PageIndex", defaultValue = "1") int pageIndex,
|
||||||
@RequestParam(value = "PageSize", defaultValue = "10") int pageSize) {
|
@RequestParam(value = "PageSize", defaultValue = "10") int pageSize) {
|
||||||
return professionService.pageRecords(currentSchId(authUserWithDept), mouid, userid, null, subid, grade, searchname,
|
return professionService.pageRecords(currentSchId(authUserWithDept), mouid, userid, null, subid, grade, searchname,
|
||||||
starttime, endtime, pageIndex, pageSize, false);
|
starttime, endtime, pageIndex, pageSize, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 记录详情(阅读数 +1,含字段定义/附件/点赞态) */
|
/** 记录详情(纯读;阅读数在 VisitCreate 记足迹时 +1,含字段定义/附件/点赞态) */
|
||||||
@GetMapping("ProfessionDetail")
|
@GetMapping("ProfessionDetail")
|
||||||
public Map<String, Object> professionDetail(BoaUser authUserWithDept, @RequestParam("SerID") Integer serId) {
|
public Map<String, Object> professionDetail(BoaUser authUserWithDept, @RequestParam("SerID") Integer serId) {
|
||||||
return professionService.recordDetail(currentSchId(authUserWithDept), serId, teacherId(authUserWithDept));
|
return professionService.recordDetail(currentSchId(authUserWithDept), serId, teacherId(authUserWithDept));
|
||||||
@@ -97,7 +96,7 @@ public class ProfessionController {
|
|||||||
@RequestParam(value = "PageIndex", defaultValue = "1") int pageIndex,
|
@RequestParam(value = "PageIndex", defaultValue = "1") int pageIndex,
|
||||||
@RequestParam(value = "PageSize", defaultValue = "10") int pageSize) {
|
@RequestParam(value = "PageSize", defaultValue = "10") int pageSize) {
|
||||||
return professionService.pageRecords(currentSchId(authUserWithDept), mouid, userid, grouid, subject, grade,
|
return professionService.pageRecords(currentSchId(authUserWithDept), mouid, userid, grouid, subject, grade,
|
||||||
searchname, starttime, endtime, pageIndex, pageSize, true);
|
searchname, starttime, endtime, pageIndex, pageSize, true, teacherId(authUserWithDept));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 某教师某栏目文章分页(个人空间) */
|
/** 某教师某栏目文章分页(个人空间) */
|
||||||
@@ -110,21 +109,21 @@ public class ProfessionController {
|
|||||||
@RequestParam(value = "PageIndex", defaultValue = "1") int pageIndex,
|
@RequestParam(value = "PageIndex", defaultValue = "1") int pageIndex,
|
||||||
@RequestParam(value = "PageSize", defaultValue = "10") int pageSize) {
|
@RequestParam(value = "PageSize", defaultValue = "10") int pageSize) {
|
||||||
return professionService.pageRecords(currentSchId(authUserWithDept), mouid, userid, null, null, grade, searchname,
|
return professionService.pageRecords(currentSchId(authUserWithDept), mouid, userid, null, null, grade, searchname,
|
||||||
null, null, pageIndex, pageSize, false);
|
null, null, pageIndex, pageSize, false, teacherId(authUserWithDept));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 首页各栏目最新记录块 */
|
/** 首页各栏目最新记录块 */
|
||||||
@GetMapping("HomeModuleRecords")
|
@GetMapping("HomeModuleRecords")
|
||||||
public List<Map<String, Object>> homeModuleRecords(BoaUser authUserWithDept,
|
public List<Map<String, Object>> homeModuleRecords(BoaUser authUserWithDept,
|
||||||
@RequestParam(value = "size", defaultValue = "6") int size) {
|
@RequestParam(value = "size", defaultValue = "6") int size) {
|
||||||
return professionService.homeModuleRecords(currentSchId(authUserWithDept), size);
|
return professionService.homeModuleRecords(currentSchId(authUserWithDept), size, teacherId(authUserWithDept));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 个人空间按年时间轴 */
|
/** 个人空间按年时间轴 */
|
||||||
@GetMapping("MyTimeline")
|
@GetMapping("MyTimeline")
|
||||||
public List<Map<String, Object>> myTimeline(BoaUser authUserWithDept,
|
public List<Map<String, Object>> myTimeline(BoaUser authUserWithDept,
|
||||||
@RequestParam("userid") Integer userid) {
|
@RequestParam("userid") Integer userid) {
|
||||||
return professionService.myTimeline(currentSchId(authUserWithDept), userid);
|
return professionService.myTimeline(currentSchId(authUserWithDept), userid, teacherId(authUserWithDept));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 评论 ====================
|
// ==================== 评论 ====================
|
||||||
@@ -193,6 +192,17 @@ public class ProfessionController {
|
|||||||
return professionService.myPoints(currentSchId(authUserWithDept), teacherId, teacherName);
|
return professionService.myPoints(currentSchId(authUserWithDept), teacherId, teacherName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 积分来源明细(发布/评论逐条带分值,分页;传 userid = 查看该教师空间,明细按登录人可见性过滤) */
|
||||||
|
@GetMapping("PointsSourceList")
|
||||||
|
public Map<String, Object> pointsSourceList(BoaUser authUserWithDept,
|
||||||
|
@RequestParam(value = "userid", required = false) Integer userid,
|
||||||
|
@RequestParam(value = "PageIndex", defaultValue = "1") int pageIndex,
|
||||||
|
@RequestParam(value = "PageSize", defaultValue = "10") int pageSize) {
|
||||||
|
Integer teacherId = userid != null ? userid : teacherId(authUserWithDept);
|
||||||
|
return professionService.pointsSourceList(currentSchId(authUserWithDept), teacherId,
|
||||||
|
teacherId(authUserWithDept), pageIndex, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
/** 本校发布排行(前 N) */
|
/** 本校发布排行(前 N) */
|
||||||
@GetMapping("ReleaseRanking")
|
@GetMapping("ReleaseRanking")
|
||||||
public List<Map<String, Object>> releaseRanking(BoaUser authUserWithDept,
|
public List<Map<String, Object>> releaseRanking(BoaUser authUserWithDept,
|
||||||
@@ -242,16 +252,16 @@ public class ProfessionController {
|
|||||||
return rows.subList(0, Math.min(Math.max(top, 1), rows.size()));
|
return rows.subList(0, Math.min(Math.max(top, 1), rows.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 年级段字典(对齐老 GetGradeList) */
|
/** 年级段字典(按当前学校过滤:班级管理该校学制的年级;不可用时回退全量) */
|
||||||
@GetMapping("GetGradeList")
|
@GetMapping("GetGradeList")
|
||||||
public List<Map<String, Object>> gradeList() {
|
public List<Map<String, Object>> gradeList(BoaUser authUserWithDept) {
|
||||||
return ProfessionDicts.gradeList();
|
return professionService.schoolGradeList(currentSchId(authUserWithDept));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 学科字典([{subId,subName}],subId 对齐班级管理 SUB01 编码) */
|
/** 学科字典(按当前学校过滤:班级管理该校启用学科,[{subId,subName}];不可用时回退全量) */
|
||||||
@GetMapping("GetSubList")
|
@GetMapping("GetSubList")
|
||||||
public List<Map<String, Object>> subjectList() {
|
public List<Map<String, Object>> subjectList(BoaUser authUserWithDept) {
|
||||||
return ProfessionDicts.subjectList();
|
return professionService.schoolSubjectList(currentSchId(authUserWithDept));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 行政区划子级(省市级联懒加载,parentCode 不传默认取省;对齐自定义字段 province_city) */
|
/** 行政区划子级(省市级联懒加载,parentCode 不传默认取省;对齐自定义字段 province_city) */
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ public interface ProfessionDiscussRepository extends JpaRepository<ProfessionDis
|
|||||||
|
|
||||||
List<ProfessionDiscuss> findByRecordIdOrderByCreateTimeAsc(Integer recordId);
|
List<ProfessionDiscuss> findByRecordIdOrderByCreateTimeAsc(Integer recordId);
|
||||||
|
|
||||||
|
/** 某教师的全部评论(积分来源明细用,新在前) */
|
||||||
|
List<ProfessionDiscuss> findBySchIdAndTeacherIdOrderByCreateTimeDescIdDesc(Integer schId, Integer teacherId);
|
||||||
|
|
||||||
long deleteByRecordId(Integer recordId);
|
long deleteByRecordId(Integer recordId);
|
||||||
|
|
||||||
// ==================== 校级聚合 ====================
|
// ==================== 校级聚合 ====================
|
||||||
@@ -70,8 +73,9 @@ public interface ProfessionDiscussRepository extends JpaRepository<ProfessionDis
|
|||||||
|
|
||||||
// ===== 教师维度聚合(个人空间概览) =====
|
// ===== 教师维度聚合(个人空间概览) =====
|
||||||
|
|
||||||
/** 教师在时间段内的评论数 */
|
/** 教师新获评论数:他人在该教师的记录下的评论(排除本人自评),时间边界传 null 表示不限 */
|
||||||
@Query("select count(d) from ProfessionDiscuss d where d.schId = :schId and d.teacherId = :uid " +
|
@Query("select count(d) from ProfessionDiscuss d where d.schId = :schId and d.teacherId <> :uid " +
|
||||||
|
"and d.recordId in (select r.id from ProfessionRecord r where r.schId = :schId and r.teacherId = :uid) " +
|
||||||
"and (:start is null or d.createTime >= :start) and (:end is null or d.createTime < :end)")
|
"and (:start is null or d.createTime >= :start) and (:end is null or d.createTime < :end)")
|
||||||
long countByTeacherRange(@Param("schId") Integer schId, @Param("uid") Integer uid,
|
long countByTeacherRange(@Param("schId") Integer schId, @Param("uid") Integer uid,
|
||||||
@Param("start") LocalDateTime start, @Param("end") LocalDateTime end);
|
@Param("start") LocalDateTime start, @Param("end") LocalDateTime end);
|
||||||
|
|||||||
@@ -56,6 +56,16 @@ public interface ProfessionRecordRepository extends JpaRepository<ProfessionReco
|
|||||||
@Param("start") LocalDateTime start,
|
@Param("start") LocalDateTime start,
|
||||||
@Param("end") LocalDateTime end);
|
@Param("end") LocalDateTime end);
|
||||||
|
|
||||||
|
/** 按栏目聚合(仅查看者可见:公开记录 + 本人发布的记录):栏目id、记录数 */
|
||||||
|
@Query("select r.moduleId, count(r) from ProfessionRecord r "
|
||||||
|
+ "where r.schId = :schId and (:start is null or r.createTime >= :start) and (:end is null or r.createTime < :end) "
|
||||||
|
+ "and (r.ifOpen = 1 or r.teacherId = :viewerId) "
|
||||||
|
+ "group by r.moduleId")
|
||||||
|
List<Object[]> countGroupByModuleVisible(@Param("schId") Integer schId,
|
||||||
|
@Param("start") LocalDateTime start,
|
||||||
|
@Param("end") LocalDateTime end,
|
||||||
|
@Param("viewerId") Integer viewerId);
|
||||||
|
|
||||||
/** 按学科聚合:学科值、记录数 */
|
/** 按学科聚合:学科值、记录数 */
|
||||||
@Query("select r.subject, r.subjectName, count(r) from ProfessionRecord r "
|
@Query("select r.subject, r.subjectName, count(r) from ProfessionRecord r "
|
||||||
+ "where r.schId = :schId and (:start is null or r.createTime >= :start) and (:end is null or r.createTime < :end) "
|
+ "where r.schId = :schId and (:start is null or r.createTime >= :start) and (:end is null or r.createTime < :end) "
|
||||||
|
|||||||
@@ -21,13 +21,18 @@ public interface ProfessionVisitRepository extends JpaRepository<ProfessionVisit
|
|||||||
|
|
||||||
List<ProfessionVisit> findByRecordId(Integer recordId);
|
List<ProfessionVisit> findByRecordId(Integer recordId);
|
||||||
|
|
||||||
// ===== 聚合计数(个人空间概览) =====
|
// ===== 聚合计数(个人空间概览,按访客+记录去重:刷新/回看不重复计数) =====
|
||||||
|
|
||||||
/** 被访问总次数(作为被访问者) */
|
/** 同一访客是否已看过该记录(看过即不再新增足迹/阅读数) */
|
||||||
long countByAuthorId(Integer authorId);
|
boolean existsByVisitorIdAndRecordId(Integer visitorId, Integer recordId);
|
||||||
|
|
||||||
/** 时间段内被访问次数(start/end 传 null 表示不限) */
|
/** 被访问总数(同一访客看同一篇只算一次;历史刷新产生的重复足迹不计入) */
|
||||||
@Query("select count(v) from ProfessionVisit v where v.authorId = :uid " +
|
@Query("select count(distinct concat(v.visitorId, ':', v.recordId)) from ProfessionVisit v " +
|
||||||
|
"where v.authorId = :uid")
|
||||||
|
long countDistinctVisitsByAuthor(@Param("uid") Integer uid);
|
||||||
|
|
||||||
|
/** 时间段内被访问总数,按访客+记录去重(start/end 传 null 表示不限) */
|
||||||
|
@Query("select count(distinct concat(v.visitorId, ':', v.recordId)) from ProfessionVisit v where v.authorId = :uid " +
|
||||||
"and (:start is null or v.createTime >= :start) and (:end is null or v.createTime < :end)")
|
"and (:start is null or v.createTime >= :start) and (:end is null or v.createTime < :end)")
|
||||||
long countVisitsByAuthor(@Param("uid") Integer uid,
|
long countVisitsByAuthor(@Param("uid") Integer uid,
|
||||||
@Param("start") java.time.LocalDateTime start,
|
@Param("start") java.time.LocalDateTime start,
|
||||||
|
|||||||
@@ -26,20 +26,21 @@ public interface IProfessionService {
|
|||||||
* @param teacherId 按教师过滤(可空)
|
* @param teacherId 按教师过滤(可空)
|
||||||
* @param groupId 按教师分组过滤(可空;组员=组长+组员教师集合)
|
* @param groupId 按教师分组过滤(可空;组员=组长+组员教师集合)
|
||||||
* @param withOtherModules true 时附带同校其他启用栏目(对齐老契约 OtherData)
|
* @param withOtherModules true 时附带同校其他启用栏目(对齐老契约 OtherData)
|
||||||
|
* @param viewerId 当前登录教师 id;非空时过滤掉他人不公开记录(ifOpen=0 仅发布人可见),空则不过滤(后台)
|
||||||
*/
|
*/
|
||||||
Map<String, Object> pageRecords(Integer schId, Integer moduleId, Integer teacherId, Integer groupId,
|
Map<String, Object> pageRecords(Integer schId, Integer moduleId, Integer teacherId, Integer groupId,
|
||||||
String subject, String grade,
|
String subject, String grade,
|
||||||
String keyword, String startTime, String endTime,
|
String keyword, String startTime, String endTime,
|
||||||
int pageIndex, int pageSize, boolean withOtherModules);
|
int pageIndex, int pageSize, boolean withOtherModules, Integer viewerId);
|
||||||
|
|
||||||
/** 记录详情(阅读数 +1;含栏目绑定字段定义、附件、动态字段值原文、点赞数与当前用户点赞态) */
|
/** 记录详情(阅读数 +1;含栏目绑定字段定义、附件、动态字段值原文、点赞数与当前用户点赞态;不公开记录仅发布人可看) */
|
||||||
Map<String, Object> recordDetail(Integer schId, Integer recordId, Integer currentUserId);
|
Map<String, Object> recordDetail(Integer schId, Integer recordId, Integer currentUserId);
|
||||||
|
|
||||||
/** 首页各栏目最新记录块 */
|
/** 首页各栏目最新记录块(过滤他人不公开记录,计数同口径) */
|
||||||
List<Map<String, Object>> homeModuleRecords(Integer schId, int sizePerModule);
|
List<Map<String, Object>> homeModuleRecords(Integer schId, int sizePerModule, Integer viewerId);
|
||||||
|
|
||||||
/** 个人空间按年时间轴 */
|
/** 个人空间按年时间轴(viewerId 非空时过滤掉他人不公开记录) */
|
||||||
List<Map<String, Object>> myTimeline(Integer schId, Integer teacherId);
|
List<Map<String, Object>> myTimeline(Integer schId, Integer teacherId, Integer viewerId);
|
||||||
|
|
||||||
// ==================== 评论 ====================
|
// ==================== 评论 ====================
|
||||||
|
|
||||||
@@ -70,6 +71,9 @@ public interface IProfessionService {
|
|||||||
/** 我的积分(发布 + 评论) */
|
/** 我的积分(发布 + 评论) */
|
||||||
Map<String, Object> myPoints(Integer schId, Integer teacherId, String teacherName);
|
Map<String, Object> myPoints(Integer schId, Integer teacherId, String teacherName);
|
||||||
|
|
||||||
|
/** 积分来源明细分页:发布/评论逐条带分值(合计口径同 myPoints;明细按查看者可见性过滤) */
|
||||||
|
Map<String, Object> pointsSourceList(Integer schId, Integer teacherId, Integer viewerId, Integer pageIndex, Integer pageSize);
|
||||||
|
|
||||||
/** 发布排行(默认前 10) */
|
/** 发布排行(默认前 10) */
|
||||||
List<Map<String, Object>> releaseRanking(Integer schId, Integer topN);
|
List<Map<String, Object>> releaseRanking(Integer schId, Integer topN);
|
||||||
|
|
||||||
@@ -84,4 +88,10 @@ public interface IProfessionService {
|
|||||||
|
|
||||||
/** 个人空间聚合:姓名/积分/累计记录/获赞/被访问/各栏目篇数/本月概览(发布/评论/阅读/访客) */
|
/** 个人空间聚合:姓名/积分/累计记录/获赞/被访问/各栏目篇数/本月概览(发布/评论/阅读/访客) */
|
||||||
Map<String, Object> spaceOverview(Integer schId, Integer userid);
|
Map<String, Object> spaceOverview(Integer schId, Integer userid);
|
||||||
|
|
||||||
|
/** 年级段字典(按校过滤:取班级管理该校学制的年级并映射回本字典;班级管理不可用/无数据时回退全量) */
|
||||||
|
List<Map<String, Object>> schoolGradeList(Integer schId);
|
||||||
|
|
||||||
|
/** 学科字典(按校过滤:取班级管理该校启用学科并按名映射回本字典;班级管理不可用/无数据时回退全量) */
|
||||||
|
List<Map<String, Object>> schoolSubjectList(Integer schId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.yunhai.profession.service.impl;
|
package com.yunhai.profession.service.impl;
|
||||||
|
|
||||||
import com.yunhai.profession.entity.UpFile;
|
import com.yunhai.profession.entity.UpFile;
|
||||||
import com.yunhai.profession.model.file.UpFileVo;
|
|
||||||
import com.yunhai.profession.model.file.UpFileResultVo;
|
import com.yunhai.profession.model.file.UpFileResultVo;
|
||||||
|
import com.yunhai.profession.model.file.UpFileVo;
|
||||||
import com.yunhai.profession.repository.UpFileRepository;
|
import com.yunhai.profession.repository.UpFileRepository;
|
||||||
import com.yunhai.profession.service.IFileService;
|
import com.yunhai.profession.service.IFileService;
|
||||||
import com.yunhai.platform.apistd.BizException;
|
import com.yunhai.platform.apistd.BizException;
|
||||||
@@ -18,30 +18,14 @@ import org.springframework.util.StringUtils;
|
|||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.time.LocalDate;
|
import java.util.*;
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
@@ -50,7 +34,7 @@ import java.util.zip.ZipOutputStream;
|
|||||||
* 文件上传/下载/删除业务实现。
|
* 文件上传/下载/删除业务实现。
|
||||||
* <p>
|
* <p>
|
||||||
* 存储路径规则:{uploadPath}/{appType}/{yyyyMMdd}/{uuid}.{ext}
|
* 存储路径规则:{uploadPath}/{appType}/{yyyyMMdd}/{uuid}.{ext}
|
||||||
* 与 C# UpLoadFile.FileSave 逻辑等价,但去除分片上传(预览走本服务转 PDF,见 preview)。
|
* 与 C# UpLoadFile.FileSave 逻辑等价,但去除分片上传和 Office 转 PDF(后续按需补充)。
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class FileService implements IFileService {
|
public class FileService implements IFileService {
|
||||||
@@ -60,20 +44,9 @@ public class FileService implements IFileService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UpFileRepository upFileRepository;
|
private UpFileRepository upFileRepository;
|
||||||
|
|
||||||
@Value("${file.upload-path}")
|
@Value("${FILE_PATH}")
|
||||||
private String uploadPath;
|
private String uploadPath;
|
||||||
|
|
||||||
private static final String DEFAULT_APP_TYPE = "profession";
|
|
||||||
|
|
||||||
/** Office 类型扩展名:预览前需转 PDF(浏览器无法内联渲染 msword 等原始格式) */
|
|
||||||
private static final Set<String> OFFICE_EXTS = new HashSet<>(Arrays.asList(
|
|
||||||
"doc", "docx", "wps", "xls", "xlsx", "et", "ppt", "pptx", "dps"));
|
|
||||||
|
|
||||||
/** Office→PDF 全局锁:COM 自动化不宜并发,转换一次后复用同名 .pdf 缓存 */
|
|
||||||
private static final Object PDF_CONVERT_LOCK = new Object();
|
|
||||||
|
|
||||||
private static volatile Path wpsScript;
|
|
||||||
|
|
||||||
// ============================ 上传 ============================
|
// ============================ 上传 ============================
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -93,13 +66,10 @@ public class FileService implements IFileService {
|
|||||||
ext = originalName.substring(dotIdx + 1).toLowerCase();
|
ext = originalName.substring(dotIdx + 1).toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构造存储子路径:{appType}/{yyyyMMdd}/{uuid}.{ext}
|
|
||||||
String dateDir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
|
||||||
String savedName = UUID.randomUUID().toString().replace("-", "") + (ext.isEmpty() ? "" : "." + ext);
|
String savedName = UUID.randomUUID().toString().replace("-", "") + (ext.isEmpty() ? "" : "." + ext);
|
||||||
String relativePath = (appType == null ? DEFAULT_APP_TYPE : appType) + "/" + dateDir + "/" + savedName;
|
|
||||||
|
|
||||||
// 物理写入
|
// 物理写入
|
||||||
Path fullPath = Paths.get(uploadPath, relativePath);
|
Path fullPath = Paths.get(uploadPath, savedName);
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(fullPath.getParent());
|
Files.createDirectories(fullPath.getParent());
|
||||||
file.transferTo(fullPath.toFile());
|
file.transferTo(fullPath.toFile());
|
||||||
@@ -110,10 +80,10 @@ public class FileService implements IFileService {
|
|||||||
|
|
||||||
// 入库
|
// 入库
|
||||||
UpFile entity = new UpFile();
|
UpFile entity = new UpFile();
|
||||||
entity.setTopicType(appType == null ? DEFAULT_APP_TYPE : appType);
|
entity.setTopicType(appType);
|
||||||
entity.setTopicId(topicId);
|
entity.setTopicId(topicId);
|
||||||
entity.setFileTrueName(originalName);
|
entity.setFileTrueName(originalName);
|
||||||
entity.setFileName(relativePath);
|
entity.setFileName(savedName);
|
||||||
entity.setFileSize((int) file.getSize());
|
entity.setFileSize((int) file.getSize());
|
||||||
entity.setFileType(ext);
|
entity.setFileType(ext);
|
||||||
upFileRepository.save(entity);
|
upFileRepository.save(entity);
|
||||||
@@ -141,8 +111,7 @@ public class FileService implements IFileService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UpFileVo> listByTopic(String appType, Integer topicId) {
|
public List<UpFileVo> listByTopic(String appType, Integer topicId) {
|
||||||
String type = appType == null ? DEFAULT_APP_TYPE : appType;
|
return upFileRepository.findByTopicTypeAndTopicId(appType, topicId).stream()
|
||||||
return upFileRepository.findByTopicTypeAndTopicId(type, topicId).stream()
|
|
||||||
.map(this::toVo)
|
.map(this::toVo)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
@@ -156,174 +125,9 @@ public class FileService implements IFileService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preview(Integer fileId, HttpServletResponse response) {
|
public void preview(Integer fileId, HttpServletResponse response) {
|
||||||
UpFile file = upFileRepository.findById(fileId)
|
|
||||||
.orElseThrow(() -> new BizException(ErrorCode.FORBIDDEN, "文件不存在: " + fileId));
|
|
||||||
Path fullPath = Paths.get(uploadPath, file.getFileName());
|
|
||||||
if (!Files.exists(fullPath)) {
|
|
||||||
throw new BizException(ErrorCode.FORBIDDEN, "物理文件不存在: " + file.getFileName());
|
|
||||||
}
|
|
||||||
if (OFFICE_EXTS.contains(extOf(file.getFileName()))) {
|
|
||||||
// Office 原始格式浏览器无法内联渲染:转 PDF 后输出(与老系统 Aspose 转 PDF 链路对齐)
|
|
||||||
Path pdfPath = siblingPdf(fullPath);
|
|
||||||
try {
|
|
||||||
ensureOfficePdf(fullPath, pdfPath);
|
|
||||||
streamFile(pdfPath, "application/pdf", pdfDisplayName(file.getFileTrueName()), response);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("[preview] Office 转 PDF 失败,输出提示页: {}", file.getFileName(), e);
|
|
||||||
writeUnsupportedHtml(response);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
writeFile(fileId, response, true);
|
writeFile(fileId, response, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================ Office 转 PDF ============================
|
|
||||||
|
|
||||||
/** 转换并缓存:{uuid}.doc → 同目录 {uuid}.pdf,已存在直接复用 */
|
|
||||||
private void ensureOfficePdf(Path officePath, Path pdfPath) throws Exception {
|
|
||||||
synchronized (PDF_CONVERT_LOCK) {
|
|
||||||
if (Files.exists(pdfPath) && Files.size(pdfPath) > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Files.deleteIfExists(pdfPath);
|
|
||||||
runWpsConvert(officePath, pdfPath);
|
|
||||||
if (!Files.exists(pdfPath) || Files.size(pdfPath) == 0) {
|
|
||||||
throw new IllegalStateException("转换未产出 PDF: " + pdfPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通过 PowerShell 调用 WPS 的 Office COM 接口导出 PDF(本机/服务器需安装 WPS,
|
|
||||||
* ProgID:KWPS=文字 KET=表格 KWPP=演示,API 兼容 MS Office)。
|
|
||||||
* 未安装 WPS 或转换失败时由调用方降级为提示页。
|
|
||||||
*/
|
|
||||||
private static final String WPS_CONVERT_SCRIPT =
|
|
||||||
"param([string]$Src,[string]$Dst)\n"
|
|
||||||
+ "$ErrorActionPreference='Stop'\n"
|
|
||||||
+ "$ext=[IO.Path]::GetExtension($Src).ToLowerInvariant()\n"
|
|
||||||
+ "$app=$null; $doc=$null\n"
|
|
||||||
+ "try {\n"
|
|
||||||
+ " if($ext -eq '.doc' -or $ext -eq '.docx' -or $ext -eq '.wps') {\n"
|
|
||||||
+ " $app=New-Object -ComObject KWPS.Application\n"
|
|
||||||
+ " $doc=$app.Documents.Open($Src,$false,$true)\n"
|
|
||||||
+ " try { $doc.ExportAsFixedFormat($Dst,17) } catch { $doc.SaveAs2($Dst,17) }\n"
|
|
||||||
+ " } elseif($ext -eq '.xls' -or $ext -eq '.xlsx' -or $ext -eq '.et') {\n"
|
|
||||||
+ " $app=New-Object -ComObject KET.Application\n"
|
|
||||||
+ " $doc=$app.Workbooks.Open($Src,0,$true)\n"
|
|
||||||
+ " $doc.ExportAsFixedFormat(0,$Dst)\n"
|
|
||||||
+ " } elseif($ext -eq '.ppt' -or $ext -eq '.pptx' -or $ext -eq '.dps') {\n"
|
|
||||||
+ " $app=New-Object -ComObject KWPP.Application\n"
|
|
||||||
+ " $doc=$app.Presentations.Open($Src,$true,$false,$false)\n"
|
|
||||||
+ " $doc.SaveAs($Dst,32)\n"
|
|
||||||
+ " } else { throw ('unsupported ext: '+$ext) }\n"
|
|
||||||
+ " if(-not (Test-Path -LiteralPath $Dst)) { throw 'pdf not produced' }\n"
|
|
||||||
+ " exit 0\n"
|
|
||||||
+ "} finally {\n"
|
|
||||||
+ " if($doc){ try{ $doc.Close($false) }catch{} }\n"
|
|
||||||
+ " if($app){ try{ $app.Quit() }catch{} }\n"
|
|
||||||
+ "}\n";
|
|
||||||
|
|
||||||
private Path wpsScript() throws Exception {
|
|
||||||
Path script = wpsScript;
|
|
||||||
if (script != null && Files.exists(script)) {
|
|
||||||
return script;
|
|
||||||
}
|
|
||||||
synchronized (PDF_CONVERT_LOCK) {
|
|
||||||
if (wpsScript == null || !Files.exists(wpsScript)) {
|
|
||||||
Path f = Files.createTempFile("wps2pdf", ".ps1");
|
|
||||||
Files.write(f, WPS_CONVERT_SCRIPT.getBytes(StandardCharsets.US_ASCII));
|
|
||||||
f.toFile().deleteOnExit();
|
|
||||||
wpsScript = f;
|
|
||||||
}
|
|
||||||
return wpsScript;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runWpsConvert(Path officePath, Path pdfPath) throws Exception {
|
|
||||||
Path script = wpsScript();
|
|
||||||
ProcessBuilder pb = new ProcessBuilder("powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass",
|
|
||||||
"-File", script.toString(), "-Src", officePath.toString(), "-Dst", pdfPath.toString());
|
|
||||||
pb.redirectErrorStream(true);
|
|
||||||
Process p = pb.start();
|
|
||||||
StringBuilder output = new StringBuilder();
|
|
||||||
Thread drain = new Thread(() -> {
|
|
||||||
try (BufferedReader r = new BufferedReader(
|
|
||||||
new InputStreamReader(p.getInputStream(), Charset.defaultCharset()))) {
|
|
||||||
String line;
|
|
||||||
while ((line = r.readLine()) != null) {
|
|
||||||
output.append(line).append('\n');
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
drain.setDaemon(true);
|
|
||||||
drain.start();
|
|
||||||
if (!p.waitFor(120, TimeUnit.SECONDS)) {
|
|
||||||
p.destroyForcibly();
|
|
||||||
throw new IllegalStateException("WPS 转换超时: " + officePath);
|
|
||||||
}
|
|
||||||
drain.join(3000);
|
|
||||||
if (p.exitValue() != 0) {
|
|
||||||
throw new IllegalStateException("WPS 转换失败 exit=" + p.exitValue() + " output=" + output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String extOf(String fileName) {
|
|
||||||
String n = String.valueOf(fileName).toLowerCase();
|
|
||||||
int dot = n.lastIndexOf('.');
|
|
||||||
return dot >= 0 ? n.substring(dot + 1) : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path siblingPdf(Path officePath) {
|
|
||||||
String name = officePath.getFileName().toString();
|
|
||||||
int dot = name.lastIndexOf('.');
|
|
||||||
String base = dot > 0 ? name.substring(0, dot) : name;
|
|
||||||
return officePath.resolveSibling(base + ".pdf");
|
|
||||||
}
|
|
||||||
|
|
||||||
private String pdfDisplayName(String trueName) {
|
|
||||||
String n = String.valueOf(trueName);
|
|
||||||
int dot = n.lastIndexOf('.');
|
|
||||||
return (dot > 0 ? n.substring(0, dot) : n) + ".pdf";
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 内联输出物理文件(预览用) */
|
|
||||||
private void streamFile(Path path, String contentType, String displayName, HttpServletResponse response) {
|
|
||||||
try (InputStream is = Files.newInputStream(path);
|
|
||||||
OutputStream os = response.getOutputStream()) {
|
|
||||||
String encodedName = URLEncoder.encode(displayName, "UTF-8").replaceAll("\\+", "%20");
|
|
||||||
response.setContentType(contentType);
|
|
||||||
response.setHeader("Content-Disposition", "inline; filename=\"" + encodedName + "\"");
|
|
||||||
response.setContentLengthLong(Files.size(path));
|
|
||||||
byte[] buffer = new byte[8192];
|
|
||||||
int len;
|
|
||||||
while ((len = is.read(buffer)) != -1) {
|
|
||||||
os.write(buffer, 0, len);
|
|
||||||
}
|
|
||||||
os.flush();
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[streamFile] 文件输出失败: {}", path, e);
|
|
||||||
throw new BizException(ErrorCode.ERROR, "文件下载失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 转换不可用时返回给 iframe 的提示页,避免整块空白 */
|
|
||||||
private void writeUnsupportedHtml(HttpServletResponse response) {
|
|
||||||
try {
|
|
||||||
response.setContentType("text/html;charset=UTF-8");
|
|
||||||
response.setCharacterEncoding("UTF-8");
|
|
||||||
response.getWriter().write(
|
|
||||||
"<!doctype html><html><head><meta charset=\"utf-8\">"
|
|
||||||
+ "<style>body{margin:0;height:100vh;display:flex;align-items:center;justify-content:center;"
|
|
||||||
+ "background:#f7f8fa;color:#8fa0b6;font:14px/1.8 'Microsoft YaHei',sans-serif}</style></head>"
|
|
||||||
+ "<body>该文件暂不支持在线预览,请点击右上角\u201c下载\u201d按钮查看</body></html>");
|
|
||||||
response.getWriter().flush();
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("[preview] 提示页输出失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件输出。inline=true 时 Content-Disposition 用 inline 并按扩展名输出真实 Content-Type
|
* 文件输出。inline=true 时 Content-Disposition 用 inline 并按扩展名输出真实 Content-Type
|
||||||
* (浏览器/前端组件可直接渲染),否则 attachment 强制下载。
|
* (浏览器/前端组件可直接渲染),否则 attachment 强制下载。
|
||||||
@@ -398,11 +202,10 @@ public class FileService implements IFileService {
|
|||||||
public void delete(Integer fileId) {
|
public void delete(Integer fileId) {
|
||||||
UpFile file = upFileRepository.findById(fileId)
|
UpFile file = upFileRepository.findById(fileId)
|
||||||
.orElseThrow(() -> new BizException(ErrorCode.FORBIDDEN, "文件不存在: " + fileId));
|
.orElseThrow(() -> new BizException(ErrorCode.FORBIDDEN, "文件不存在: " + fileId));
|
||||||
// 删物理文件(连同同名预览 PDF 缓存)
|
// 删物理文件
|
||||||
Path fullPath = Paths.get(uploadPath, file.getFileName());
|
Path fullPath = Paths.get(uploadPath, file.getFileName());
|
||||||
try {
|
try {
|
||||||
Files.deleteIfExists(fullPath);
|
Files.deleteIfExists(fullPath);
|
||||||
Files.deleteIfExists(siblingPdf(fullPath));
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("[delete] 物理文件删除失败: {}(数据库记录仍会删除)", fullPath, e);
|
log.warn("[delete] 物理文件删除失败: {}(数据库记录仍会删除)", fullPath, e);
|
||||||
}
|
}
|
||||||
@@ -461,8 +264,7 @@ public class FileService implements IFileService {
|
|||||||
@Override
|
@Override
|
||||||
public void downloadZipByTopic(String topicType, Integer topicId, String zipName,
|
public void downloadZipByTopic(String topicType, Integer topicId, String zipName,
|
||||||
HttpServletResponse response) {
|
HttpServletResponse response) {
|
||||||
String type = topicType == null ? DEFAULT_APP_TYPE : topicType;
|
List<UpFile> files = upFileRepository.findByTopicTypeAndTopicId(topicType, topicId);
|
||||||
List<UpFile> files = upFileRepository.findByTopicTypeAndTopicId(type, topicId);
|
|
||||||
downloadZip(files, zipName, response);
|
downloadZip(files, zipName, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,9 +277,8 @@ public class FileService implements IFileService {
|
|||||||
if (files == null || files.length == 0) {
|
if (files == null || files.length == 0) {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
String type = appType != null ? appType : DEFAULT_APP_TYPE;
|
|
||||||
for (MultipartFile file : files) {
|
for (MultipartFile file : files) {
|
||||||
results.add(upload(file, type, null));
|
results.add(upload(file, appType, null));
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,11 @@ public class MouduleService implements IMouduleService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Map<String, Object>> myModuleList(Integer currentSchId, Integer teacherId) {
|
public List<Map<String, Object>> myModuleList(Integer currentSchId, Integer teacherId) {
|
||||||
|
// 行内带上上级栏目 id+名称:栏目条按上级栏目归组做悬停下拉(无上级时 topModuleName 为 null)
|
||||||
|
Map<Integer, String> topNames = new HashMap<>();
|
||||||
|
for (TopModule t : topModuleRepository.findBySchIdOrderBySortOrderAscIdAsc(currentSchId)) {
|
||||||
|
topNames.put(t.getId(), t.getName());
|
||||||
|
}
|
||||||
List<Map<String, Object>> rows = new ArrayList<>();
|
List<Map<String, Object>> rows = new ArrayList<>();
|
||||||
if (teacherId != null) {
|
if (teacherId != null) {
|
||||||
// 指定教师:各启用栏目下该教师的篇数(含 0,对齐老 MouduleMyList)
|
// 指定教师:各启用栏目下该教师的篇数(含 0,对齐老 MouduleMyList)
|
||||||
@@ -126,6 +131,7 @@ public class MouduleService implements IMouduleService {
|
|||||||
row.put("id", m.getId());
|
row.put("id", m.getId());
|
||||||
row.put("name", m.getName());
|
row.put("name", m.getName());
|
||||||
row.put("recordCount", countByModule.getOrDefault(m.getId(), 0L));
|
row.put("recordCount", countByModule.getOrDefault(m.getId(), 0L));
|
||||||
|
putTopModuleInfo(row, m, topNames);
|
||||||
rows.add(row);
|
rows.add(row);
|
||||||
}
|
}
|
||||||
return rows;
|
return rows;
|
||||||
@@ -136,11 +142,20 @@ public class MouduleService implements IMouduleService {
|
|||||||
row.put("id", m.getId());
|
row.put("id", m.getId());
|
||||||
row.put("name", m.getName());
|
row.put("name", m.getName());
|
||||||
row.put("recordCount", countByModule.getOrDefault(m.getId(), 0L));
|
row.put("recordCount", countByModule.getOrDefault(m.getId(), 0L));
|
||||||
|
putTopModuleInfo(row, m, topNames);
|
||||||
rows.add(row);
|
rows.add(row);
|
||||||
}
|
}
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 栏目行补充上级栏目信息(topModuleId=0/null 视为无上级,名称置 null) */
|
||||||
|
private void putTopModuleInfo(Map<String, Object> row, ProfessionModule m, Map<Integer, String> topNames) {
|
||||||
|
Integer topId = m.getTopModuleId();
|
||||||
|
boolean hasTop = topId != null && topId != 0;
|
||||||
|
row.put("topModuleId", hasTop ? topId : null);
|
||||||
|
row.put("topModuleName", hasTop ? topNames.get(topId) : null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Integer createModule(Integer currentSchId, boolean districtAccount, Map<String, Object> req) {
|
public Integer createModule(Integer currentSchId, boolean districtAccount, Map<String, Object> req) {
|
||||||
@@ -236,9 +251,12 @@ public class MouduleService implements IMouduleService {
|
|||||||
result.put("openUserIds", m.getOpenUserIds());
|
result.put("openUserIds", m.getOpenUserIds());
|
||||||
result.put("remark", m.getRemark());
|
result.put("remark", m.getRemark());
|
||||||
result.put("fieldIds", m.getFieldIds());
|
result.put("fieldIds", m.getFieldIds());
|
||||||
|
// 发布表单只渲染已绑定字段(按绑定顺序,对齐老 Getdatelist 的 itemlist)
|
||||||
List<Map<String, Object>> fields = new ArrayList<>();
|
List<Map<String, Object>> fields = new ArrayList<>();
|
||||||
for (ProfessionField f : fieldRepository.findBySchIdOrderByFieldSortAscIdAsc(m.getSchId())) {
|
for (Integer fieldId : boundIds) {
|
||||||
fields.add(fieldRow(f, boundIds.contains(f.getId())));
|
fieldRepository.findById(fieldId)
|
||||||
|
.filter(f -> f.getSchId().equals(m.getSchId()))
|
||||||
|
.ifPresent(f -> fields.add(fieldRow(f, true)));
|
||||||
}
|
}
|
||||||
result.put("fields", fields);
|
result.put("fields", fields);
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import com.yunhai.profession.entity.ProfessionModule;
|
|||||||
import com.yunhai.profession.entity.ProfessionRecord;
|
import com.yunhai.profession.entity.ProfessionRecord;
|
||||||
import com.yunhai.profession.entity.ProfessionVisit;
|
import com.yunhai.profession.entity.ProfessionVisit;
|
||||||
import com.yunhai.profession.entity.UpFile;
|
import com.yunhai.profession.entity.UpFile;
|
||||||
|
import com.yunhai.profession.model.bsch.SchGradeDto;
|
||||||
|
import com.yunhai.profession.model.bsch.SchInfoDto;
|
||||||
|
import com.yunhai.profession.model.bsch.SchSubjectDto;
|
||||||
import com.yunhai.profession.model.bsch.TeacherCustomGroupDto;
|
import com.yunhai.profession.model.bsch.TeacherCustomGroupDto;
|
||||||
import com.yunhai.profession.repository.ProfessionDiscussRepository;
|
import com.yunhai.profession.repository.ProfessionDiscussRepository;
|
||||||
import com.yunhai.profession.repository.ProfessionFieldRepository;
|
import com.yunhai.profession.repository.ProfessionFieldRepository;
|
||||||
@@ -20,6 +23,7 @@ import com.yunhai.profession.service.IPointsService;
|
|||||||
import com.yunhai.profession.service.IProfessionService;
|
import com.yunhai.profession.service.IProfessionService;
|
||||||
import com.yunhai.profession.service.IClassService;
|
import com.yunhai.profession.service.IClassService;
|
||||||
import com.yunhai.profession.utils.ProfessionContext;
|
import com.yunhai.profession.utils.ProfessionContext;
|
||||||
|
import com.yunhai.profession.utils.ProfessionDicts;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -170,10 +174,10 @@ public class ProfessionService implements IProfessionService {
|
|||||||
public Map<String, Object> pageRecords(Integer schId, Integer moduleId, Integer teacherId, Integer groupId,
|
public Map<String, Object> pageRecords(Integer schId, Integer moduleId, Integer teacherId, Integer groupId,
|
||||||
String subject, String grade,
|
String subject, String grade,
|
||||||
String keyword, String startTime, String endTime,
|
String keyword, String startTime, String endTime,
|
||||||
int pageIndex, int pageSize, boolean withOtherModules) {
|
int pageIndex, int pageSize, boolean withOtherModules, Integer viewerId) {
|
||||||
Specification<ProfessionRecord> spec = recordSpec(schId, moduleId, teacherId,
|
Specification<ProfessionRecord> spec = recordSpec(schId, moduleId, teacherId,
|
||||||
groupId != null ? groupTeacherIds(schId, groupId) : null,
|
groupId != null ? groupTeacherIds(schId, groupId) : null,
|
||||||
subject, grade, keyword, startTime, endTime);
|
subject, grade, keyword, startTime, endTime, viewerId);
|
||||||
Page<ProfessionRecord> page = recordRepository.findAll(spec,
|
Page<ProfessionRecord> page = recordRepository.findAll(spec,
|
||||||
PageRequest.of(Math.max(pageIndex - 1, 0), pageSize <= 0 ? 10 : pageSize,
|
PageRequest.of(Math.max(pageIndex - 1, 0), pageSize <= 0 ? 10 : pageSize,
|
||||||
Sort.by(Sort.Direction.DESC, "createTime", "id")));
|
Sort.by(Sort.Direction.DESC, "createTime", "id")));
|
||||||
@@ -210,7 +214,11 @@ public class ProfessionService implements IProfessionService {
|
|||||||
if (!r.getSchId().equals(schId)) {
|
if (!r.getSchId().equals(schId)) {
|
||||||
throw new IllegalArgumentException("无权查看该记录");
|
throw new IllegalArgumentException("无权查看该记录");
|
||||||
}
|
}
|
||||||
recordRepository.increaseReadCount(recordId);
|
// 不公开记录仅发布人本人可看
|
||||||
|
if (Integer.valueOf(0).equals(r.getIfOpen()) && !r.getTeacherId().equals(currentUserId)) {
|
||||||
|
throw new IllegalArgumentException("该记录不公开,仅发布人可见");
|
||||||
|
}
|
||||||
|
// 纯读:阅读数统一在 visitCreate(浏览足迹)时 +1,避免详情+足迹各加一次导致重复计数
|
||||||
Map<String, Object> row = recordRow(r, moduleNameMap(schId).get(r.getModuleId()), null);
|
Map<String, Object> row = recordRow(r, moduleNameMap(schId).get(r.getModuleId()), null);
|
||||||
row.put("remark", r.getRemark());
|
row.put("remark", r.getRemark());
|
||||||
row.put("contents", r.getContents());
|
row.put("contents", r.getContents());
|
||||||
@@ -250,15 +258,15 @@ public class ProfessionService implements IProfessionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Map<String, Object>> homeModuleRecords(Integer schId, int sizePerModule) {
|
public List<Map<String, Object>> homeModuleRecords(Integer schId, int sizePerModule, Integer viewerId) {
|
||||||
Map<Integer, Long> countByModule = new HashMap<>();
|
Map<Integer, Long> countByModule = new HashMap<>();
|
||||||
for (Object[] row : recordRepository.countGroupByModule(schId, null, null)) {
|
for (Object[] row : recordRepository.countGroupByModuleVisible(schId, null, null, viewerId)) {
|
||||||
countByModule.put((Integer) row[0], (Long) row[1]);
|
countByModule.put((Integer) row[0], (Long) row[1]);
|
||||||
}
|
}
|
||||||
List<Map<String, Object>> blocks = new ArrayList<>();
|
List<Map<String, Object>> blocks = new ArrayList<>();
|
||||||
for (ProfessionModule m : moduleRepository.findVisibleEnabled(schId)) {
|
for (ProfessionModule m : moduleRepository.findVisibleEnabled(schId)) {
|
||||||
Page<ProfessionRecord> page = recordRepository.findAll(
|
Page<ProfessionRecord> page = recordRepository.findAll(
|
||||||
recordSpec(schId, m.getId(), null, null, null, null, null, null, null),
|
recordSpec(schId, m.getId(), null, null, null, null, null, null, null, viewerId),
|
||||||
PageRequest.of(0, Math.max(sizePerModule, 1), Sort.by(Sort.Direction.DESC, "createTime", "id")));
|
PageRequest.of(0, Math.max(sizePerModule, 1), Sort.by(Sort.Direction.DESC, "createTime", "id")));
|
||||||
List<Map<String, Object>> items = new ArrayList<>();
|
List<Map<String, Object>> items = new ArrayList<>();
|
||||||
for (ProfessionRecord r : page.getContent()) {
|
for (ProfessionRecord r : page.getContent()) {
|
||||||
@@ -281,8 +289,9 @@ public class ProfessionService implements IProfessionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Map<String, Object>> myTimeline(Integer schId, Integer teacherId) {
|
public List<Map<String, Object>> myTimeline(Integer schId, Integer teacherId, Integer viewerId) {
|
||||||
Specification<ProfessionRecord> spec = recordSpec(schId, null, teacherId, null, null, null, null, null, null);
|
Specification<ProfessionRecord> spec = recordSpec(schId, null, teacherId, null, null, null, null, null, null,
|
||||||
|
viewerId);
|
||||||
List<ProfessionRecord> records = recordRepository.findAll(spec,
|
List<ProfessionRecord> records = recordRepository.findAll(spec,
|
||||||
PageRequest.of(0, 500, Sort.by(Sort.Direction.DESC, "createTime", "id"))).getContent();
|
PageRequest.of(0, 500, Sort.by(Sort.Direction.DESC, "createTime", "id"))).getContent();
|
||||||
Map<Integer, String> moduleName = moduleNameMap(schId);
|
Map<Integer, String> moduleName = moduleNameMap(schId);
|
||||||
@@ -395,6 +404,10 @@ public class ProfessionService implements IProfessionService {
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void visitCreate(Integer schId, Integer visitorId, String visitorName, Integer recordId) {
|
public void visitCreate(Integer schId, Integer visitorId, String visitorName, Integer recordId) {
|
||||||
|
// 同一访客同一篇记录只记一次足迹:刷新/回看不新增足迹、不加阅读数(防刷新刷访问量)
|
||||||
|
if (visitRepository.existsByVisitorIdAndRecordId(visitorId, recordId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ProfessionVisit v = new ProfessionVisit();
|
ProfessionVisit v = new ProfessionVisit();
|
||||||
v.setSchId(schId);
|
v.setSchId(schId);
|
||||||
// 开发兜底名(teacher-xxx)替换为该教师记录冗余的真实姓名
|
// 开发兜底名(teacher-xxx)替换为该教师记录冗余的真实姓名
|
||||||
@@ -508,6 +521,77 @@ public class ProfessionService implements IProfessionService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> pointsSourceList(Integer schId, Integer teacherId, Integer viewerId,
|
||||||
|
Integer pageIndex, Integer pageSize) {
|
||||||
|
// 合计口径与 myPoints 完全一致(含未公开记录),明细行按查看者可见性过滤
|
||||||
|
Map<String, Object> totals = myPoints(schId, teacherId, null);
|
||||||
|
List<Map<String, Object>> merged = new ArrayList<>();
|
||||||
|
// 发布来源:本人记录(可见性与时间轴一致:ifOpen=0 仅发布人本人可见)
|
||||||
|
Map<Integer, String> moduleName = moduleNameMap(schId);
|
||||||
|
List<ProfessionRecord> records = recordRepository.findAll(
|
||||||
|
recordSpec(schId, null, teacherId, null, null, null, null, null, null, viewerId),
|
||||||
|
Sort.by(Sort.Direction.DESC, "createTime", "id"));
|
||||||
|
for (ProfessionRecord r : records) {
|
||||||
|
Map<String, Object> row = new LinkedHashMap<>();
|
||||||
|
row.put("type", "publish");
|
||||||
|
row.put("id", r.getId());
|
||||||
|
row.put("recordId", r.getId());
|
||||||
|
row.put("title", r.getTitle());
|
||||||
|
row.put("moduleName", moduleName.get(r.getModuleId()));
|
||||||
|
row.put("points", r.getSendPoints());
|
||||||
|
row.put("createTime", r.getCreateTime());
|
||||||
|
merged.add(row);
|
||||||
|
}
|
||||||
|
// 评论来源:本人评论 → 关联记录标题;记录对查看者不可见时跳过(避免借标题泄露未公开记录)
|
||||||
|
List<ProfessionDiscuss> discusses =
|
||||||
|
discussRepository.findBySchIdAndTeacherIdOrderByCreateTimeDescIdDesc(schId, teacherId);
|
||||||
|
Map<Integer, ProfessionRecord> ref = new HashMap<>();
|
||||||
|
if (!discusses.isEmpty()) {
|
||||||
|
Set<Integer> recordIds = new HashSet<>();
|
||||||
|
for (ProfessionDiscuss d : discusses) {
|
||||||
|
recordIds.add(d.getRecordId());
|
||||||
|
}
|
||||||
|
for (ProfessionRecord r : recordRepository.findAllById(recordIds)) {
|
||||||
|
ref.put(r.getId(), r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (ProfessionDiscuss d : discusses) {
|
||||||
|
ProfessionRecord r = ref.get(d.getRecordId());
|
||||||
|
if (r == null || (Integer.valueOf(0).equals(r.getIfOpen()) && !r.getTeacherId().equals(viewerId))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Map<String, Object> row = new LinkedHashMap<>();
|
||||||
|
row.put("type", "comment");
|
||||||
|
row.put("id", d.getId());
|
||||||
|
row.put("recordId", r.getId());
|
||||||
|
row.put("title", r.getTitle());
|
||||||
|
row.put("contents", d.getContents());
|
||||||
|
row.put("points", d.getCommPoints());
|
||||||
|
row.put("createTime", d.getCreateTime());
|
||||||
|
merged.add(row);
|
||||||
|
}
|
||||||
|
// 新在前(createTime/id 双键,与记录列表排序同规);内存分页(明细量级小,免两条 offset 查询)
|
||||||
|
merged.sort(Comparator.comparing((Map<String, Object> row) -> (LocalDateTime) row.get("createTime"),
|
||||||
|
Comparator.nullsLast(Comparator.reverseOrder())));
|
||||||
|
int total = merged.size();
|
||||||
|
int size = pageSize == null || pageSize <= 0 ? 10 : Math.min(pageSize, 100);
|
||||||
|
int pages = (total + size - 1) / size;
|
||||||
|
int idx = pageIndex == null || pageIndex < 1 ? 1 : Math.min(pageIndex, Math.max(pages, 1));
|
||||||
|
int from = Math.min((idx - 1) * size, total);
|
||||||
|
int to = Math.min(from + size, total);
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
|
data.put("teacherId", teacherId);
|
||||||
|
data.put("sendPoints", totals.get("sendPoints"));
|
||||||
|
data.put("commPoints", totals.get("commPoints"));
|
||||||
|
data.put("pointsnum", totals.get("pointsnum"));
|
||||||
|
data.put("total", total);
|
||||||
|
data.put("pageIndex", idx);
|
||||||
|
data.put("pageSize", size);
|
||||||
|
data.put("rows", merged.subList(from, to));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Map<String, Object>> releaseRanking(Integer schId, Integer topN) {
|
public List<Map<String, Object>> releaseRanking(Integer schId, Integer topN) {
|
||||||
List<Map<String, Object>> rows = new ArrayList<>();
|
List<Map<String, Object>> rows = new ArrayList<>();
|
||||||
@@ -702,7 +786,7 @@ public class ProfessionService implements IProfessionService {
|
|||||||
private Specification<ProfessionRecord> recordSpec(Integer schId, Integer moduleId, Integer teacherId,
|
private Specification<ProfessionRecord> recordSpec(Integer schId, Integer moduleId, Integer teacherId,
|
||||||
Collection<Integer> teacherIds,
|
Collection<Integer> teacherIds,
|
||||||
String subject, String grade, String keyword,
|
String subject, String grade, String keyword,
|
||||||
String startTime, String endTime) {
|
String startTime, String endTime, Integer viewerId) {
|
||||||
final LocalDateTime start = parseStart(startTime);
|
final LocalDateTime start = parseStart(startTime);
|
||||||
final LocalDateTime end = parseEnd(endTime);
|
final LocalDateTime end = parseEnd(endTime);
|
||||||
return (root, cq, cb) -> {
|
return (root, cq, cb) -> {
|
||||||
@@ -714,6 +798,10 @@ public class ProfessionService implements IProfessionService {
|
|||||||
if (teacherId != null) {
|
if (teacherId != null) {
|
||||||
ps.add(cb.equal(root.get("teacherId"), teacherId));
|
ps.add(cb.equal(root.get("teacherId"), teacherId));
|
||||||
}
|
}
|
||||||
|
if (viewerId != null) {
|
||||||
|
// 不公开记录(ifOpen=0)仅发布人本人可见
|
||||||
|
ps.add(cb.or(cb.equal(root.get("ifOpen"), 1), cb.equal(root.get("teacherId"), viewerId)));
|
||||||
|
}
|
||||||
if (teacherIds != null) {
|
if (teacherIds != null) {
|
||||||
// 组过滤:空集合(组不存在/组服务不可用)时恒假,避免退化为不过滤
|
// 组过滤:空集合(组不存在/组服务不可用)时恒假,避免退化为不过滤
|
||||||
ps.add(teacherIds.isEmpty() ? cb.disjunction() : root.get("teacherId").in(teacherIds));
|
ps.add(teacherIds.isEmpty() ? cb.disjunction() : root.get("teacherId").in(teacherIds));
|
||||||
@@ -896,7 +984,7 @@ public class ProfessionService implements IProfessionService {
|
|||||||
}
|
}
|
||||||
m.put("recordTotal", recordTotal);
|
m.put("recordTotal", recordTotal);
|
||||||
m.put("likeTotal", recordRepository.sumLikesByTeacher(schId, userid));
|
m.put("likeTotal", recordRepository.sumLikesByTeacher(schId, userid));
|
||||||
m.put("visitTotal", visitRepository.countByAuthorId(userid));
|
m.put("visitTotal", visitRepository.countDistinctVisitsByAuthor(userid));
|
||||||
// 各栏目篇数(启用栏目,含 0)
|
// 各栏目篇数(启用栏目,含 0)
|
||||||
Map<Integer, Long> byModule = new HashMap<>();
|
Map<Integer, Long> byModule = new HashMap<>();
|
||||||
for (Object[] row : recordRepository.countGroupByTeacherModule(schId, null, null)) {
|
for (Object[] row : recordRepository.countGroupByTeacherModule(schId, null, null)) {
|
||||||
@@ -924,5 +1012,92 @@ public class ProfessionService implements IProfessionService {
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 按校字典 ====================
|
||||||
|
|
||||||
|
/** 按校字典缓存 TTL:班级管理数据改动低频,60 秒内直接复用 */
|
||||||
|
private static final long DICT_CACHE_MS = 60_000L;
|
||||||
|
|
||||||
|
/** 缓存键(kind:schId)→ 快照 */
|
||||||
|
private final Map<String, CachedDict> dictCache = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Map<String, Object>> schoolGradeList(Integer schId) {
|
||||||
|
return schoolDict("grade", schId, () -> {
|
||||||
|
Set<String> values = new LinkedHashSet<>();
|
||||||
|
for (SchGradeDto g : classService.getSchGradeList(schoolSerId(schId))) {
|
||||||
|
String v = ProfessionDicts.gradeValueByNickname(g.getName());
|
||||||
|
if (v != null) {
|
||||||
|
values.add(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ProfessionDicts.gradeListOf(values);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Map<String, Object>> schoolSubjectList(Integer schId) {
|
||||||
|
return schoolDict("subject", schId, () -> {
|
||||||
|
Set<String> subIds = new LinkedHashSet<>();
|
||||||
|
for (SchSubjectDto s : classService.getSchSubList(schoolSerId(schId))) {
|
||||||
|
String id = ProfessionDicts.subjectIdByName(s.getSubName());
|
||||||
|
if (id != null) {
|
||||||
|
subIds.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ProfessionDicts.subjectListOf(subIds);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按校范围取字典:loader 调班级管理并映射回本字典编码;
|
||||||
|
* schId 为空、班级管理不可用或映射结果为空(如局端账号无校)时回退全量字典(下拉不空)。
|
||||||
|
*/
|
||||||
|
private List<Map<String, Object>> schoolDict(String kind, Integer schId, java.util.function.Supplier<List<Map<String, Object>>> loader) {
|
||||||
|
if (schId == null) {
|
||||||
|
return fullDict(kind);
|
||||||
|
}
|
||||||
|
String key = kind + ":" + schId;
|
||||||
|
synchronized (dictCache) {
|
||||||
|
CachedDict c = dictCache.get(key);
|
||||||
|
if (c != null && System.currentTimeMillis() - c.at < DICT_CACHE_MS) {
|
||||||
|
return c.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<Map<String, Object>> data;
|
||||||
|
try {
|
||||||
|
data = loader.get();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[schoolDict] 班级管理取{}失败, schId={}", kind, schId, e);
|
||||||
|
data = new ArrayList<>();
|
||||||
|
}
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
data = fullDict(kind);
|
||||||
|
}
|
||||||
|
synchronized (dictCache) {
|
||||||
|
dictCache.put(key, new CachedDict(System.currentTimeMillis(), data));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Map<String, Object>> fullDict(String kind) {
|
||||||
|
return "grade".equals(kind) ? ProfessionDicts.gradeList() : ProfessionDicts.subjectList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 学校 GUID(班级管理侧 SchSerId);查不到返回 null(loader 得空列表 → 回退全量) */
|
||||||
|
private String schoolSerId(Integer schId) {
|
||||||
|
SchInfoDto info = classService.getSchFirst(schId);
|
||||||
|
return info == null ? null : info.getSchSerId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class CachedDict {
|
||||||
|
final long at;
|
||||||
|
final List<Map<String, Object>> data;
|
||||||
|
|
||||||
|
CachedDict(long at, List<Map<String, Object>> data) {
|
||||||
|
this.at = at;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 聚合辅助 ====================
|
// ==================== 聚合辅助 ====================
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,76 @@ public class ProfessionDicts {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班级管理年级昵称 → 本字典 value(如「初一」→ 8),无对应项(幼儿园/大学)返回 null。
|
||||||
|
* 班级管理 gradeId 与本字典错位一位(6=预备/7=初一…),故按昵称映射而非按 Id。
|
||||||
|
*/
|
||||||
|
public static String gradeValueByNickname(String nickname) {
|
||||||
|
if (nickname == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String n = nickname.trim();
|
||||||
|
String[][] grades = {
|
||||||
|
{"小一", "一年级", "1"}, {"小二", "二年级", "2"}, {"小三", "三年级", "3"},
|
||||||
|
{"小四", "四年级", "4"}, {"小五", "五年级", "5"}, {"小六", "六年级", "6"},
|
||||||
|
{"预备", "7"}, {"初一", "8"}, {"初二", "9"}, {"初三", "10"},
|
||||||
|
{"高一", "11"}, {"高二", "12"}, {"高三", "13"},
|
||||||
|
};
|
||||||
|
for (String[] g : grades) {
|
||||||
|
if (n.equals(g[0]) || (g.length > 2 && n.equals(g[1]))) {
|
||||||
|
return g[g.length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班级管理学科名 → 本字典 subId(如「道法」→ SUB06 道德与法治),无对应项返回 null。
|
||||||
|
* 班级管理学科编码与本字典自 SUB06 起不一致(SUB06=生物),故按名称映射而非按编码。
|
||||||
|
*/
|
||||||
|
public static String subjectIdByName(String name) {
|
||||||
|
if (name == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String n = name.trim();
|
||||||
|
String[][] subs = {
|
||||||
|
{"语文", "SUB01"}, {"数学", "SUB02"}, {"英语", "SUB03"}, {"物理", "SUB04"},
|
||||||
|
{"化学", "SUB05"}, {"道德与法治", "SUB06"}, {"道法", "SUB06"}, {"政治", "SUB06"},
|
||||||
|
{"思想政治", "SUB06"}, {"历史", "SUB07"}, {"地理", "SUB08"}, {"生物", "SUB09"},
|
||||||
|
{"音乐", "SUB10"}, {"体育与健康", "SUB11"}, {"体育", "SUB11"},
|
||||||
|
{"美术", "SUB12"}, {"信息技术", "SUB13"}, {"信息", "SUB13"},
|
||||||
|
{"劳动", "SUB14"}, {"劳技", "SUB14"}, {"劳动与技术", "SUB14"},
|
||||||
|
};
|
||||||
|
for (String[] s : subs) {
|
||||||
|
if (n.equals(s[0])) {
|
||||||
|
return s[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按字典顺序取 value 子集的年级段(去重;用于按校范围过滤) */
|
||||||
|
public static List<Map<String, Object>> gradeListOf(java.util.Collection<String> values) {
|
||||||
|
List<Map<String, Object>> list = new ArrayList<>();
|
||||||
|
for (Map<String, Object> it : gradeList()) {
|
||||||
|
if (values != null && values.contains(it.get("value"))) {
|
||||||
|
list.add(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按字典顺序取 subId 子集的学科(去重;用于按校范围过滤) */
|
||||||
|
public static List<Map<String, Object>> subjectListOf(java.util.Collection<String> subIds) {
|
||||||
|
List<Map<String, Object>> list = new ArrayList<>();
|
||||||
|
for (Map<String, Object> it : subjectList()) {
|
||||||
|
if (subIds != null && subIds.contains(it.get("subId"))) {
|
||||||
|
list.add(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
private static Map<String, Object> item(String value, String label) {
|
private static Map<String, Object> item(String value, String label) {
|
||||||
Map<String, Object> m = new LinkedHashMap<>();
|
Map<String, Object> m = new LinkedHashMap<>();
|
||||||
m.put("value", value);
|
m.put("value", value);
|
||||||
|
|||||||
@@ -46,9 +46,7 @@ spring:
|
|||||||
simple:
|
simple:
|
||||||
auto-startup: false
|
auto-startup: false
|
||||||
|
|
||||||
# 文件上传存储路径(Docker 部署时与 volume 挂载一致)
|
|
||||||
file:
|
|
||||||
upload-path: D:/data/upload
|
|
||||||
|
|
||||||
# 教师专业发展应用配置
|
# 教师专业发展应用配置
|
||||||
profession:
|
profession:
|
||||||
@@ -63,7 +61,9 @@ sms:
|
|||||||
# 单点登录 / 应用间签名鉴权(基础平台 yh-auth / yh-app-auth)
|
# 单点登录 / 应用间签名鉴权(基础平台 yh-auth / yh-app-auth)
|
||||||
yh:
|
yh:
|
||||||
sso:
|
sso:
|
||||||
server-url-prefix: http://sso.yunhaixingtong.com
|
# 单点登录服务地址:本地运行 CasService(E:\YHXT\java\user-cas-service\CasService,8443 根路径)。
|
||||||
|
# 远程地址为 http://sso.yunhaixingtong.com(生产/联调环境)
|
||||||
|
server-url-prefix: http://localhost:8443
|
||||||
client-host-url: http://localhost:8107
|
client-host-url: http://localhost:8107
|
||||||
allow-headers: all
|
allow-headers: all
|
||||||
allow-origins: all
|
allow-origins: all
|
||||||
@@ -78,5 +78,9 @@ yh:
|
|||||||
enabled: true
|
enabled: true
|
||||||
base-host: http://base.yunhaixingtong.com
|
base-host: http://base.yunhaixingtong.com
|
||||||
user-center-url: http://base.yunhaixingtong.com/userbase
|
user-center-url: http://base.yunhaixingtong.com/userbase
|
||||||
# 班级管理(bschclass)服务基址
|
# 班级管理(bschclass)服务基址:本地运行 UserclassService(E:\YHXT\java\UserclassService,8080 根路径,
|
||||||
|
# IDEA 启动;/oapi/** 免签名免登录)。远程为 http://base.yunhaixingtong.com/userclass
|
||||||
bschclass: http://base.yunhaixingtong.com/userclass
|
bschclass: http://base.yunhaixingtong.com/userclass
|
||||||
|
|
||||||
|
FILE_PATH: D:/data/upload
|
||||||
|
FILE_WEB_PATH: http://base.yunhaixingtong.com/userbase/upload/
|
||||||
Reference in New Issue
Block a user