@@ -12,6 +12,7 @@ import com.xboe.constants.CacheName;
import com.xboe.core.CurrentUser ;
import com.xboe.core.JsonResponse ;
import com.xboe.core.api.ApiBaseController ;
import com.xboe.core.upload.XFileUploader ;
import com.xboe.data.outside.IOutSideDataService ;
import com.xboe.module.course.entity.* ;
import com.xboe.module.course.service.ICourseContentService ;
@@ -27,6 +28,7 @@ import com.xboe.school.study.dto.*;
import com.xboe.school.study.entity.* ;
import com.xboe.school.study.service.* ;
import com.xboe.school.vo.StudyTimeVo ;
import com.xboe.system.organization.entity.Organization ;
import com.xboe.system.organization.service.IOrganizationService ;
import com.xboe.system.user.entity.User ;
import com.xboe.system.user.service.IUserService ;
@@ -42,7 +44,7 @@ import javax.annotation.Resource;
import javax.persistence.EntityManager ;
import javax.servlet.http.HttpServletRequest ;
import javax.servlet.http.HttpServletResponse ;
import java.io.OutputStream ;
import java.io.* ;
import java.math.BigDecimal ;
import java.math.RoundingMode ;
import java.net.URLEncoder ;
@@ -51,6 +53,8 @@ import java.util.*;
import java.util.concurrent.TimeUnit ;
import java.util.function.Function ;
import java.util.stream.Collectors ;
import java.util.zip.ZipEntry ;
import java.util.zip.ZipOutputStream ;
/**
* 课程学习的内容
@@ -110,6 +114,9 @@ public class StudyCourseApi extends ApiBaseController{
@Resource
private IOrganizationService organizationService ;
@Resource
private XFileUploader fileUploader ;
/**
* 用于避免JPA查询后修改entity实体字段, 自动更新到数据库
*/
@@ -194,7 +201,9 @@ public class StudyCourseApi extends ApiBaseController{
? userInfo . getUserNo ( )
: " " ) ;
// 3. 填部门: 通过departId查部门名称
String departName = organizationService . getName ( userInfo . getDepartId ( ) ) ;
// 获取部门信息( 参考现有机构表, 获取namePath字段)
Organization organization = organizationService . get ( userInfo . getDepartId ( ) ) ;
String departName = organization ! = null ? organization . getNamePath ( ) : " " ;
resultVo . setOrgInfo ( StringUtils . isNotBlank ( departName )
? departName
: " " ) ;
@@ -336,7 +345,7 @@ public class StudyCourseApi extends ApiBaseController{
exportMap . put ( " 学习进度 " , " 学习进度 " ) ;
// 2.查询课程的所有考试答卷信息, 拼接动态表头( XXX考试成绩)
// 注意:这里的考试信息每个学生每门课程只有一条,实际数据可能多次考试,因此根据考试配置中的(最高分/最新数据)获取对应列表数据即可(本次新增接口)
List < StudyExam > studyExams = studyExamService . getByCourseId ( courseId ) ;
List < StudyExam > studyExams = studyExamService . getByCourseIdAndContentId ( courseId , null );
// courseExam和studyExam表都有testName字段, 查看现有逻辑后, 选择studyExam表的testName字段作为考试名称
// 因为需求没提, 因此查询按默认id排序
// 查询学习记录接口,学习记录默认存储获取最高/最新的分数,备用
@@ -412,7 +421,10 @@ public class StudyCourseApi extends ApiBaseController{
if ( Objects . equals ( userId , studyCourse1 . getAid ( ) ) ) {
log . info ( " 【导出课程学习记录】查询boe人员表, 机构id: {} " , userInfo . getDepartId ( ) ) ;
// 和技术沟通后, 确认这里机构名称是redis获取的, 获取不到返回null, 因此正常遍历没有效率和空值问题
studyCourse1 . setOrgInfo ( organizationService . getName ( userInfo . getDepartId ( ) ) ) ;
// 获取部门信息( 参考现有机构表, 获取namePath字段)
Organization organization = organizationService . get ( userInfo . getDepartId ( ) ) ;
String departName = organization ! = null ? organization . getNamePath ( ) : " " ;
studyCourse1 . setOrgInfo ( departName ) ;
// 取userNo为工号
studyCourse1 . setUserNo ( userInfo . getUserNo ( ) ) ;
}
@@ -1233,14 +1245,14 @@ public class StudyCourseApi extends ApiBaseController{
}
try {
// 查询当前课程的考试信息
List < StudyExam > studyExams = studyExamService . getByCourseId( course Id ) ;
List < StudyExam > studyExams = studyExamService . getByCourseIdAndContentId ( courseId , content Id ) ;
// 空值校验
if ( studyExams = = null | | studyExams . isEmpty ( ) ) {
return success ( new PageList < > ( ) ) ;
}
List < String > studyCourseItemIds = studyExams . stream ( ) . map ( StudyExam : : getStudyItemId ) . collect ( Collectors . toList ( ) ) ;
// 分页查询资源学习信息
PageList < StudyCourseItem > rs = studyService . findItemPage ( pager . getPageIndex ( ) , pager . getPageSize ( ) , studyCourseItemIds , contentId , courseId , name , status ) ;
// 分页查询资源学习信息(只查询有考试信息的部分)
PageList < StudyCourseItem > rs = studyService . findItemPage ( pager . getPageIndex ( ) , pager . getPageSize ( ) , null , contentId , courseId , name , status ) ;
// 拼接考试信息
List < StudyCourseItem > studyCourseItems = rs . getList ( ) ;
if ( studyCourseItems ! = null & & ! studyCourseItems . isEmpty ( ) & & ! studyExams . isEmpty ( ) ) {
@@ -1256,6 +1268,347 @@ public class StudyCourseApi extends ApiBaseController{
}
}
/**
* 资源学习情况导出-考试信息
* 导出单个考试信息
*
* @param courseId 课程id
* @param contentId 内容id
*/
@RequestMapping ( value = " /contents-exam-export " , method = { RequestMethod . GET , RequestMethod . POST } )
public void findPageExam ( String courseId , String contentId , HttpServletResponse response ) {
if ( StringUtils . isBlank ( courseId ) ) {
log . error ( " 【资源学习情况导出-考试信息】课程id不能为空 " ) ;
return ;
}
if ( StringUtils . isBlank ( contentId ) ) {
log . error ( " 【资源学习情况导出-考试信息】内容id不能为空 " ) ;
return ;
}
OutputStream outputStream = null ;
try {
outputStream = response . getOutputStream ( ) ;
LinkedHashMap < String , String > exportMap = new LinkedHashMap < > ( ) ;
// 1.拼接固定表头(课程名称、资源名称、姓名、工号、部门、考试状态、考试成绩、完成时间)
// 获取逻辑: 课程名称从studyCourse表获取, 资源名称从courseContent表获取, 姓名、工号、部门从用户接口获取, 考试状态、考试成绩、完成时间从studyExam表获取
exportMap . put ( " 课程名称 " , " 课程名称 " ) ;
exportMap . put ( " 资源名称 " , " 资源名称 " ) ;
exportMap . put ( " 姓名 " , " 姓名 " ) ;
exportMap . put ( " 工号 " , " 工号 " ) ;
exportMap . put ( " 部门 " , " 部门 " ) ;
exportMap . put ( " 考试状态 " , " 考试状态 " ) ;
exportMap . put ( " 考试成绩 " , " 考试成绩 " ) ;
exportMap . put ( " 完成时间 " , " 完成时间 " ) ;
// 查询课程名称
StudyCourse studyCourse = new StudyCourse ( ) ;
studyCourse . setCourseId ( courseId ) ;
List < StudyCourse > studyCourses = service . findList ( studyCourse , null , null ) ;
String courseName ;
if ( studyCourses ! = null & & ! studyCourses . isEmpty ( ) ) {
courseName = studyCourses . get ( 0 ) . getCourseName ( ) ;
// 查询资源名称
List < CourseContent > courseContents = contentService . getByCourseId ( courseId ) ;
String contentName = " " ;
if ( courseContents ! = null & & ! courseContents . isEmpty ( ) ) {
for ( CourseContent cc : courseContents ) {
if ( contentId . equals ( cc . getId ( ) ) ) {
contentName = cc . getContentName ( ) ;
break ;
}
}
}
// 查询特定考试信息(这里同一个学员,同一次考试可以导出多次考试信息)
List < StudyExam > studyExams = studyExamService . getByCourseIdAndContentId ( courseId , contentId ) ;
// 通过studyExams中的人员id集合(去重),调用用户中心接口获取人员信息,填充部门字段
Set < String > userIds = studyExams . stream ( ) . map ( StudyExam : : getStudentId ) . filter ( Objects : : nonNull ) . collect ( Collectors . toSet ( ) ) ;
List < UserSimpleVo > userList = new ArrayList < > ( ) ;
if ( ! userIds . isEmpty ( ) ) {
// 调用用户中心接口
List < UserSimpleVo > userSimpleVos = outsideService . findByIds ( new ArrayList < > ( userIds ) ) ;
if ( userSimpleVos ! = null & & ! userSimpleVos . isEmpty ( ) ) {
userList . addAll ( userSimpleVos ) ;
} else {
log . error ( " 【资源学习情况导出-考试信息】用户信息查询失败, 查询boe人员表作为兜底方案 " ) ;
// 查询boe的人员表作为兜底方案
for ( String userId : userIds ) {
// 用户信息也是redis获取的
User userInfo = userService . get ( userId ) ;
if ( userInfo ! = null ) {
UserSimpleVo userSimpleVo = new UserSimpleVo ( ) ;
userSimpleVo . setAid ( userInfo . getId ( ) ) ;
userSimpleVo . setCode ( userInfo . getUserNo ( ) ) ;
// 获取部门信息( 参考现有机构表, 获取namePath字段)
Organization organization = organizationService . get ( userInfo . getDepartId ( ) ) ;
String departName = organization ! = null ? organization . getNamePath ( ) : " " ;
userSimpleVo . setOrgInfo ( StringUtils . isNotBlank ( departName ) ? departName : " " ) ;
userList . add ( userSimpleVo ) ;
log . info ( " 【资源学习情况导出-考试信息】查询boe人员表, 用户id: {} " , userId ) ;
} else {
log . error ( " 【资源学习情况导出-考试信息】用户信息查询boe人员表失败, 用户id: {} " , userId ) ;
}
}
}
}
// 将考试信息与用户信息拼接为map
String finalContentName = contentName ;
List < Map < String , Object > > dataList = studyExams . stream ( ) . map ( exam - > {
Map < String , Object > map = new HashMap < > ( ) ;
// 拼接基本信息
map . put ( " 课程名称 " , courseName ) ;
map . put ( " 资源名称 " , finalContentName ) ;
map . put ( " 姓名 " , exam . getStudentName ( ) ) ;
// 工号和部门信息需要从用户信息中获取
String userNo = " " ;
String orgInfo = " " ;
for ( UserSimpleVo user : userList ) {
if ( exam . getStudentId ( ) . equals ( user . getAid ( ) ) ) {
userNo = user . getCode ( ) ;
orgInfo = user . getOrgInfo ( ) ;
break ;
}
}
map . put ( " 工号 " , userNo ) ;
map . put ( " 部门 " , orgInfo ) ;
// 考试状态需要转换(根据成绩和及格线判断)
String examStatus = " " ;
if ( exam . getScore ( ) ! = null & & exam . getPassLine ( ) ! = null ) {
examStatus = exam . getScore ( ) > = exam . getPassLine ( ) ? " 通过 " : " 未通过 " ;
}
map . put ( " 考试状态 " , examStatus ) ;
// 考试成绩
map . put ( " 考试成绩 " , exam . getScore ( ) ) ;
// 完成时间
map . put ( " 完成时间 " , exam . getEndTime ( ) ) ;
return map ;
} ) . collect ( Collectors . toList ( ) ) ;
// 4.拼接消息类型和响应头信息
response . setContentType ( " application/octet-stream " ) ;
response . setHeader ( " Content-disposition " , " attachment;filename=ExamRecord.xlsx " ) ;
// 5.调用动态列excel导出接口
ExportsExcelSenderUtil . exportDynamic ( exportMap , dataList , outputStream , " yyyy-MM-dd HH:mm:ss " ) ;
}
} catch ( Exception e ) {
log . error ( " 【资源学习情况导出-考试信息】导出资源学习情况错误:{} " , e . getMessage ( ) ) ;
} finally {
if ( outputStream ! = null ) {
try {
outputStream . close ( ) ;
} catch ( IOException e ) {
log . error ( " 【资源学习情况导出-考试信息】关闭输出流失败:{} " , e . getMessage ( ) ) ;
}
}
}
}
/**
* 资源学习情况导出-作业信息
* 导出单个作业信息
*
* @param courseId 课程id
* @param contentId 内容id
*/
@RequestMapping ( value = " /contents-homework-export " , method = { RequestMethod . GET , RequestMethod . POST } )
public void findPageHomework ( String courseId , String contentId , HttpServletResponse response ) {
if ( StringUtils . isBlank ( courseId ) ) {
log . error ( " 【资源学习情况导出-作业信息】课程id不能为空 " ) ;
return ;
}
if ( StringUtils . isBlank ( contentId ) ) {
log . error ( " 【资源学习情况导出-作业信息】内容id不能为空 " ) ;
return ;
}
OutputStream outputStream = null ;
try {
// 创建临时文件用于存储Excel
File tempExcelFile = File . createTempFile ( " HomeWorkRecord " , " .xlsx " ) ;
String tempExcelPath = tempExcelFile . getAbsolutePath ( ) ;
LinkedHashMap < String , String > exportMap = new LinkedHashMap < > ( ) ;
// 1.拼接固定表头(课程名称、资源名称、姓名、工号、部门、作业状态、作业成绩、完成时间)
exportMap . put ( " 课程名称 " , " 课程名称 " ) ;
exportMap . put ( " 资源名称 " , " 资源名称 " ) ;
exportMap . put ( " 姓名 " , " 姓名 " ) ;
exportMap . put ( " 工号 " , " 工号 " ) ;
exportMap . put ( " 部门 " , " 部门 " ) ;
exportMap . put ( " 作业状态 " , " 作业状态 " ) ;
exportMap . put ( " 作业成绩 " , " 作业成绩 " ) ;
exportMap . put ( " 完成时间 " , " 完成时间 " ) ;
// 2.整理导出数据
// 查询课程名称
StudyCourse studyCourse = new StudyCourse ( ) ;
studyCourse . setCourseId ( courseId ) ;
List < StudyCourse > studyCourses = service . findList ( studyCourse , null , null ) ;
String courseName ;
if ( studyCourses ! = null & & ! studyCourses . isEmpty ( ) ) {
courseName = studyCourses . get ( 0 ) . getCourseName ( ) ;
} else {
courseName = " " ;
}
// 查询资源名称
List < CourseContent > courseContents = contentService . getByCourseId ( courseId ) ;
String contentName = " " ;
if ( courseContents ! = null & & ! courseContents . isEmpty ( ) ) {
for ( CourseContent cc : courseContents ) {
if ( contentId . equals ( cc . getId ( ) ) ) {
contentName = cc . getContentName ( ) ;
break ;
}
}
}
// 查询特定作业信息
List < StudyHomeWork > studyHomeWorks = studyHomeWorkService . getByCourseIdAndContentId ( courseId , contentId ) ;
// 通过studyHomeWorks中的人员id集合(去重),调用用户中心接口获取人员信息,填充部门字段
Set < String > userIds = studyHomeWorks . stream ( ) . map ( StudyHomeWork : : getStudentId ) . filter ( Objects : : nonNull ) . collect ( Collectors . toSet ( ) ) ;
List < UserSimpleVo > userList = new ArrayList < > ( ) ;
if ( ! userIds . isEmpty ( ) ) {
// 调用用户中心接口
List < UserSimpleVo > userSimpleVos = outsideService . findByIds ( new ArrayList < > ( userIds ) ) ;
if ( userSimpleVos ! = null & & ! userSimpleVos . isEmpty ( ) ) {
userList . addAll ( userSimpleVos ) ;
} else {
log . error ( " 【资源学习情况导出-作业信息】用户信息查询失败, 查询boe人员表作为兜底方案 " ) ;
// 查询boe的人员表作为兜底方案
for ( String userId : userIds ) {
// 用户信息也是redis获取的
User userInfo = userService . get ( userId ) ;
if ( userInfo ! = null ) {
UserSimpleVo userSimpleVo = new UserSimpleVo ( ) ;
userSimpleVo . setAid ( userInfo . getId ( ) ) ;
userSimpleVo . setCode ( userInfo . getUserNo ( ) ) ;
// 获取部门信息( 参考现有机构表, 获取namePath字段)
Organization organization = organizationService . get ( userInfo . getDepartId ( ) ) ;
String departName = organization ! = null ? organization . getNamePath ( ) : " " ;
userSimpleVo . setOrgInfo ( StringUtils . isNotBlank ( departName ) ? departName : " " ) ;
userList . add ( userSimpleVo ) ;
log . info ( " 【资源学习情况导出-作业信息】查询boe人员表, 用户id: {} " , userId ) ;
} else {
log . error ( " 【资源学习情况导出-作业信息】用户信息查询boe人员表失败, 用户id: {} " , userId ) ;
}
}
}
}
// 3.将作业信息与用户信息拼接为map
String finalContentName = contentName ;
List < Map < String , Object > > dataList = studyHomeWorks . stream ( ) . map ( hw - > {
Map < String , Object > map = new HashMap < > ( ) ;
// 拼接基本信息
map . put ( " 课程名称 " , courseName ) ;
map . put ( " 资源名称 " , finalContentName ) ;
map . put ( " 姓名 " , hw . getStudentName ( ) ) ;
// 工号和部门信息需要从用户信息中获取
String userNo = " " ;
String orgInfo = " " ;
for ( UserSimpleVo user : userList ) {
if ( hw . getStudentId ( ) . equals ( user . getAid ( ) ) ) {
userNo = user . getCode ( ) ;
orgInfo = user . getOrgInfo ( ) ;
break ;
}
}
map . put ( " 工号 " , userNo ) ;
map . put ( " 部门 " , orgInfo ) ;
// 作业状态(如果有成绩则为已完成,否则为未完成)
String hwStatus = ( hw . getScore ( ) ! = null ) ? " 已完成 " : " 未完成 " ;
map . put ( " 作业状态 " , hwStatus ) ;
// 作业成绩
map . put ( " 作业成绩 " , hw . getScore ( ) ) ;
// 完成时间
map . put ( " 完成时间 " , hw . getEndTime ( ) ) ;
return map ;
} ) . collect ( Collectors . toList ( ) ) ;
// 先将Excel数据写入临时文件
try ( FileOutputStream excelOutputStream = new FileOutputStream ( tempExcelFile ) ) {
ExportsExcelSenderUtil . exportDynamic ( exportMap , dataList , excelOutputStream , " yyyy-MM-dd HH:mm:ss " ) ;
}
// 4.整理作业的附件信息备用
// 获取附件信息集合
Map < String , String > homeworkFiles = new HashMap < > ( ) ;
String savePath = fileUploader . getSavePath ( ) ;
for ( StudyHomeWork studyHomeWork : studyHomeWorks ) {
String filePath = studyHomeWork . getFilePath ( ) ;
if ( StringUtils . isNotBlank ( filePath ) ) {
String fullPath = savePath + filePath ;
log . info ( " 【资源学习情况导出-作业信息】下载附件路径:{} " , fullPath ) ;
File file = new File ( fullPath ) ;
if ( file . exists ( ) ) {
// 使用文件名作为zip中的条目名
String fileName = file . getName ( ) ;
// 避免文件名冲突,如果已存在则添加序号
String zipEntryName = fileName ;
int counter = 1 ;
while ( homeworkFiles . containsKey ( zipEntryName ) ) {
int dotIndex = fileName . lastIndexOf ( '.' ) ;
if ( dotIndex > 0 ) {
zipEntryName = fileName . substring ( 0 , dotIndex ) + " _ " + counter + fileName . substring ( dotIndex ) ;
} else {
zipEntryName = fileName + " _ " + counter ;
}
counter + + ;
}
homeworkFiles . put ( zipEntryName , fullPath ) ;
}
}
}
// 5.设置响应头信息为zip文件
response . setContentType ( " application/zip " ) ;
response . setHeader ( " Content-disposition " , " attachment;filename=HomeWorkRecord.zip " ) ;
outputStream = response . getOutputStream ( ) ;
// 6.把输出的附件和导出的excel文件拼接为一个zip导出
try ( ZipOutputStream zos = new ZipOutputStream ( outputStream ) ) {
// 添加Excel文件到zip
byte [ ] buffer = new byte [ 1024 ] ;
// 添加Excel文件
File excelFile = new File ( tempExcelPath ) ;
if ( excelFile . exists ( ) ) {
FileInputStream fis = new FileInputStream ( excelFile ) ;
ZipEntry zipEntry = new ZipEntry ( " HomeWorkRecord.xlsx " ) ;
zos . putNextEntry ( zipEntry ) ;
int length ;
while ( ( length = fis . read ( buffer ) ) > 0 ) {
zos . write ( buffer , 0 , length ) ;
}
zos . closeEntry ( ) ;
fis . close ( ) ;
}
// 添加作业附件文件
// 25.11.27标记:和技术沟通后,按业务此处调用频率不会过高,因此暂不采用多线程下载方案
// 为了防止命名冲突, 附件放在zip的/附件目录下
// zip规范要求使用 正斜杠/作为路径分隔符, 不要用File.separator( 在windows上是\, 会导致zip解压异常)
String zipFilePath = " 附件/ " ;
for ( Map . Entry < String , String > entry : homeworkFiles . entrySet ( ) ) {
String zipEntryName = zipFilePath + entry . getKey ( ) ;
String filePath = entry . getValue ( ) ;
File file = new File ( filePath ) ;
if ( file . exists ( ) ) {
FileInputStream fis = new FileInputStream ( file ) ;
ZipEntry zipEntry = new ZipEntry ( zipEntryName ) ;
zos . putNextEntry ( zipEntry ) ;
int len ;
while ( ( len = fis . read ( buffer ) ) > 0 ) {
zos . write ( buffer , 0 , len ) ;
}
zos . closeEntry ( ) ;
fis . close ( ) ;
}
}
}
// 删除临时Excel文件
if ( tempExcelFile . exists ( ) ) {
tempExcelFile . delete ( ) ;
}
} catch ( Exception e ) {
log . error ( " 【资源学习情况导出-作业信息】导出资源学习情况错误:{} " , e . getMessage ( ) ) ;
} finally {
if ( outputStream ! = null ) {
try {
outputStream . close ( ) ;
} catch ( IOException e ) {
log . error ( " 关闭输出流失败:{} " , e . getMessage ( ) ) ;
}
}
}
}
@RequestMapping ( value = " /study-course-content " , method = { RequestMethod . GET , RequestMethod . POST } )
public JsonResponse < StudyCourseItem > findStudyCourseItem ( String studyId , String contentId ) {
if ( StringUtils . isBlank ( studyId ) ) {