parent
f2086ebf35
commit
bdf0301721
12 changed files with 795 additions and 7 deletions
@ -0,0 +1,28 @@ |
|||||||
|
package com.teaching.backend.mapper.chapter; |
||||||
|
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||||
|
import com.teaching.backend.model.entity.chapter.Chapter; |
||||||
|
import com.teaching.backend.model.entity.chapter.TemporaryChapter; |
||||||
|
import io.lettuce.core.dynamic.annotation.Param; |
||||||
|
import org.apache.ibatis.annotations.Select; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* <p> |
||||||
|
* Mapper 接口 |
||||||
|
* </p> |
||||||
|
* |
||||||
|
* @author author |
||||||
|
* @since 2024-05-31 |
||||||
|
*/ |
||||||
|
public interface TemporaryChapterMapper extends BaseMapper<TemporaryChapter> { |
||||||
|
|
||||||
|
TemporaryChapter selectOneByPidAndOrderNumDesc(long l); |
||||||
|
|
||||||
|
List<TemporaryChapter> selectAll(); |
||||||
|
|
||||||
|
void deleteAll(); |
||||||
|
} |
@ -0,0 +1,95 @@ |
|||||||
|
package com.teaching.backend.model.entity.chapter; |
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType; |
||||||
|
import com.baomidou.mybatisplus.annotation.TableField; |
||||||
|
import com.baomidou.mybatisplus.annotation.TableId; |
||||||
|
import com.baomidou.mybatisplus.annotation.TableName; |
||||||
|
import com.teaching.backend.model.entity.Knowtemp.Knowtemp; |
||||||
|
import io.swagger.annotations.ApiModelProperty; |
||||||
|
import lombok.Data; |
||||||
|
import lombok.EqualsAndHashCode; |
||||||
|
import lombok.experimental.Accessors; |
||||||
|
|
||||||
|
import java.io.Serializable; |
||||||
|
import java.time.LocalDateTime; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* <p> |
||||||
|
* |
||||||
|
* </p> |
||||||
|
* |
||||||
|
* @author author |
||||||
|
* @since 2024-05-31 |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
@EqualsAndHashCode(callSuper = false) |
||||||
|
@Accessors(chain = true) |
||||||
|
@TableName("temporary_chapter") |
||||||
|
public class TemporaryChapter implements Serializable { |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "主键") |
||||||
|
@TableId(value = "id", type = IdType.AUTO) |
||||||
|
private Long id; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "创建人") |
||||||
|
private String createBy; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "创建日期") |
||||||
|
private LocalDateTime createTime; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "更新人") |
||||||
|
private String updateBy; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "更新日期") |
||||||
|
private LocalDateTime updateTime; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "所属部门") |
||||||
|
private String sysOrgCode; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "序号") |
||||||
|
private Double orderNum; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "名称") |
||||||
|
private String name; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "简介") |
||||||
|
private String content; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "父级节点") |
||||||
|
private Long pid; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "课程") |
||||||
|
|
||||||
|
private String courseId; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "课程目标") |
||||||
|
private String courseObjectivesId; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "总学时") |
||||||
|
private String totalClassHours; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "要求") |
||||||
|
private String requirement; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "线上学时") |
||||||
|
private String onlineClassHours; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "周次") |
||||||
|
private String zc; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "内部序号显示") |
||||||
|
private String numshow; |
||||||
|
|
||||||
|
@ApiModelProperty(value = "子章节") |
||||||
|
@TableField(exist = false) |
||||||
|
private List<TemporaryChapter> children; // 用于存储子章节
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "知识点") |
||||||
|
@TableField(exist = false) |
||||||
|
private List<Knowtemp> knowledgePoints; // 用于存储章节下的知识点
|
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
package com.teaching.backend.service.impl.chapter; |
||||||
|
|
||||||
|
import com.teaching.backend.utils.Chapter.ExcelParser; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
import org.springframework.web.multipart.MultipartFile; |
||||||
|
|
||||||
|
import java.io.InputStream; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* ClassName: ChapterExcelServiceImpl |
||||||
|
* Package: com.teaching.backend.service.impl.chapter |
||||||
|
* Description: |
||||||
|
* |
||||||
|
* @Author 姜钧瀚 |
||||||
|
* @Create 2024/8/4 10:50 |
||||||
|
* @Version 1.0 |
||||||
|
*/ |
||||||
|
@Service |
||||||
|
public class ChapterExcelServiceImpl { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
ExcelParser excelParser; |
||||||
|
|
||||||
|
public List<String> uploadExcel(MultipartFile file, String courseId) { |
||||||
|
List<String> validationErrors = new ArrayList<>(); |
||||||
|
try (InputStream inputStream = file.getInputStream()) { |
||||||
|
validationErrors = excelParser.parse(inputStream, courseId); |
||||||
|
} catch (Exception e) { |
||||||
|
validationErrors.add("系统错误: " + e.getMessage()); |
||||||
|
} |
||||||
|
return validationErrors; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,75 @@ |
|||||||
|
package com.teaching.backend.utils.Chapter; |
||||||
|
|
||||||
|
/** |
||||||
|
* ClassName: ExcelParser |
||||||
|
* Package: com.teaching |
||||||
|
* Description: |
||||||
|
* |
||||||
|
* @Author 姜钧瀚 |
||||||
|
* @Create 2024/8/3 14:58 |
||||||
|
* @Version 1.0 |
||||||
|
*/ |
||||||
|
|
||||||
|
import com.teaching.backend.service.chapter.IChapterService; |
||||||
|
import org.apache.poi.openxml4j.opc.OPCPackage; |
||||||
|
import org.apache.poi.xssf.eventusermodel.XSSFReader; |
||||||
|
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler; |
||||||
|
import org.apache.poi.xssf.model.SharedStringsTable; |
||||||
|
import org.apache.poi.xssf.model.StylesTable; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
import org.xml.sax.InputSource; |
||||||
|
import org.xml.sax.XMLReader; |
||||||
|
import org.xml.sax.helpers.XMLReaderFactory; |
||||||
|
|
||||||
|
import java.io.InputStream; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* 自定义Excel解析器 |
||||||
|
*/ |
||||||
|
@Component |
||||||
|
public class ExcelParser { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
IChapterService chapterService; |
||||||
|
|
||||||
|
public List<String> parse(InputStream inputStream, String courseId) throws Exception { |
||||||
|
OPCPackage pkg = OPCPackage.open(inputStream); |
||||||
|
List<String> validationErrors = new ArrayList<>(); |
||||||
|
try { |
||||||
|
XSSFReader reader = new XSSFReader(pkg); |
||||||
|
SharedStringsTable sst = reader.getSharedStringsTable(); |
||||||
|
StylesTable styles = reader.getStylesTable(); |
||||||
|
XMLReader parser = XMLReaderFactory.createXMLReader(); |
||||||
|
|
||||||
|
SheetHandler sheetHandler = new SheetHandler(chapterService, courseId); |
||||||
|
parser.setContentHandler(new XSSFSheetXMLHandler(styles, sst, sheetHandler, false)); |
||||||
|
|
||||||
|
XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) reader.getSheetsData(); |
||||||
|
while (sheets.hasNext()) { |
||||||
|
InputStream sheetstream = sheets.next(); |
||||||
|
InputSource sheetSource = new InputSource(sheetstream); |
||||||
|
try { |
||||||
|
parser.parse(sheetSource); |
||||||
|
} finally { |
||||||
|
sheetstream.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
validationErrors = sheetHandler.finalizeProcess(); |
||||||
|
if (!validationErrors.isEmpty()) { |
||||||
|
for (String error : validationErrors) { |
||||||
|
System.out.println("导入错误: " + error); |
||||||
|
} |
||||||
|
} else { |
||||||
|
System.out.println("数据导入成功!"); |
||||||
|
} |
||||||
|
|
||||||
|
} finally { |
||||||
|
pkg.close(); |
||||||
|
} |
||||||
|
return validationErrors; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,113 @@ |
|||||||
|
package com.teaching.backend.utils.Chapter; |
||||||
|
|
||||||
|
import com.teaching.backend.common.ErrorCode; |
||||||
|
import com.teaching.backend.exception.BusinessException; |
||||||
|
import com.teaching.backend.model.entity.chapter.Chapter; |
||||||
|
import org.apache.poi.ss.usermodel.Cell; |
||||||
|
import org.apache.poi.ss.usermodel.Row; |
||||||
|
import org.apache.poi.ss.usermodel.Sheet; |
||||||
|
import org.apache.poi.ss.usermodel.Workbook; |
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook; |
||||||
|
|
||||||
|
import javax.servlet.ServletOutputStream; |
||||||
|
import javax.servlet.http.HttpServletResponse; |
||||||
|
import java.io.IOException; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* ClassName: ExcelUtils |
||||||
|
* Package: com.teaching.backend.utils.Chapter |
||||||
|
* Description: |
||||||
|
* |
||||||
|
* @Author 姜钧瀚 |
||||||
|
* @Create 2024/8/7 18:57 |
||||||
|
* @Version 1.0 |
||||||
|
*/ |
||||||
|
public class ExcelUtils { |
||||||
|
|
||||||
|
public static void createTemplateExcel(HttpServletResponse response) throws IOException { |
||||||
|
String[] titles = new String[]{"编号", "章节", "上级编号"}; |
||||||
|
Sheet sheet = createWorkbookAndSheet("模版", titles); |
||||||
|
|
||||||
|
// 添加模板数据
|
||||||
|
Row templateRow = sheet.createRow(1); |
||||||
|
templateRow.createCell(0).setCellValue(1); |
||||||
|
templateRow.createCell(1).setCellValue("示例父章节名"); |
||||||
|
templateRow.createCell(2).setCellValue(0); |
||||||
|
|
||||||
|
Row templateRow2 = sheet.createRow(2); |
||||||
|
templateRow2.createCell(0).setCellValue(2); |
||||||
|
templateRow2.createCell(1).setCellValue("示例子章节名"); |
||||||
|
templateRow2.createCell(2).setCellValue(1); |
||||||
|
|
||||||
|
writeWorkbookToResponse(sheet.getWorkbook(), response, "章节模版.xlsx"); |
||||||
|
} |
||||||
|
|
||||||
|
public static void createDataExcel(HttpServletResponse response, List<Chapter> chapterList) throws IOException { |
||||||
|
String[] titles = new String[]{"编号", "章节", "上级编号"}; |
||||||
|
Sheet sheet = createWorkbookAndSheet("用户数据", titles); |
||||||
|
|
||||||
|
Map<Long, Integer> idMapping = createIdMapping(chapterList); |
||||||
|
|
||||||
|
int rowIndex = 1; |
||||||
|
for (Chapter chapter : chapterList) { |
||||||
|
Row row = sheet.createRow(rowIndex); |
||||||
|
Integer excelId = idMapping.get(chapter.getId()); |
||||||
|
|
||||||
|
if (excelId == null) { |
||||||
|
throw new BusinessException(ErrorCode.OPERATION_ERROR); |
||||||
|
} |
||||||
|
row.createCell(0).setCellValue(excelId); |
||||||
|
row.createCell(1).setCellValue(chapter.getName()); |
||||||
|
long parentId = chapter.getPid() != 0 ? idMapping.get(chapter.getPid()) : 0; |
||||||
|
row.createCell(2).setCellValue(parentId != 0 ? parentId : 0); |
||||||
|
rowIndex++; |
||||||
|
} |
||||||
|
|
||||||
|
writeWorkbookToResponse(sheet.getWorkbook(), response, "章节数据.xlsx"); |
||||||
|
} |
||||||
|
|
||||||
|
private static Map<Long, Integer> createIdMapping(List<Chapter> chapterList) { |
||||||
|
Map<Long, Integer> idMapping = new HashMap<>(); |
||||||
|
int newId = 1; |
||||||
|
for (Chapter chapter : chapterList) { |
||||||
|
idMapping.put(chapter.getId(), newId); |
||||||
|
newId++; |
||||||
|
} |
||||||
|
return idMapping; |
||||||
|
} |
||||||
|
|
||||||
|
private static Sheet createWorkbookAndSheet(String sheetName, String[] titles) throws IOException { |
||||||
|
Workbook workbook = new XSSFWorkbook(); |
||||||
|
Sheet sheet = workbook.createSheet(sheetName); |
||||||
|
|
||||||
|
// 设置列宽
|
||||||
|
sheet.setColumnWidth(0, 30 * 256); |
||||||
|
sheet.setColumnWidth(1, 60 * 256); |
||||||
|
sheet.setColumnWidth(2, 30 * 256); |
||||||
|
|
||||||
|
// 创建标题行
|
||||||
|
Row titleRow = sheet.createRow(0); |
||||||
|
for (int i = 0; i < titles.length; i++) { |
||||||
|
Cell cell = titleRow.createCell(i); |
||||||
|
cell.setCellValue(titles[i]); |
||||||
|
} |
||||||
|
|
||||||
|
return sheet; |
||||||
|
} |
||||||
|
|
||||||
|
private static void writeWorkbookToResponse(Workbook workbook, HttpServletResponse response, String filename) throws IOException { |
||||||
|
response.setHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes(), "ISO8859-1")); |
||||||
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); |
||||||
|
response.setCharacterEncoding("UTF-8"); |
||||||
|
|
||||||
|
try (ServletOutputStream outputStream = response.getOutputStream()) { |
||||||
|
workbook.write(outputStream); |
||||||
|
outputStream.flush(); |
||||||
|
} finally { |
||||||
|
workbook.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,134 @@ |
|||||||
|
package com.teaching.backend.utils.Chapter; |
||||||
|
|
||||||
|
import com.teaching.backend.common.ErrorCode; |
||||||
|
import com.teaching.backend.common.ResultUtils; |
||||||
|
import com.teaching.backend.exception.BusinessException; |
||||||
|
import com.teaching.backend.model.dto.chapter.ChapterDTO; |
||||||
|
import com.teaching.backend.service.chapter.IChapterService; |
||||||
|
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler; |
||||||
|
import org.apache.poi.xssf.usermodel.XSSFComment; |
||||||
|
|
||||||
|
import java.util.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* ClassName: SheetHandler |
||||||
|
* Package: com.teaching.backend.utils |
||||||
|
* Description: |
||||||
|
* |
||||||
|
* @Author 姜钧瀚 |
||||||
|
* @Create 2024/8/3 14:13 |
||||||
|
* @Version 1.0 |
||||||
|
*/ |
||||||
|
|
||||||
|
public class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler { |
||||||
|
|
||||||
|
private final IChapterService chapterService; |
||||||
|
private final String courseId; |
||||||
|
private ChapterDTO chapterDTO; |
||||||
|
private Map<Long, Long> excelIdToDatabaseIdMap = new HashMap<>(); |
||||||
|
private List<String> validationErrors = new ArrayList<>(); |
||||||
|
private boolean hasErrors = false; |
||||||
|
private List<ChapterDTO> collectedChapters = new ArrayList<>(); |
||||||
|
|
||||||
|
public SheetHandler(IChapterService chapterService, String courseId) { |
||||||
|
this.chapterService = chapterService; |
||||||
|
this.courseId = courseId; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void startRow(int rowIndex) { |
||||||
|
if (rowIndex == 0) { |
||||||
|
chapterDTO = null; |
||||||
|
} else { |
||||||
|
chapterDTO = new ChapterDTO(); |
||||||
|
chapterDTO.setCourseId(courseId); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void endRow(int rowIndex) { |
||||||
|
if (rowIndex != 0 && chapterDTO != null) { |
||||||
|
List<String> errors = validateChapter(chapterDTO); |
||||||
|
if (!errors.isEmpty()) { |
||||||
|
validationErrors.addAll(errors); |
||||||
|
hasErrors = true; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
collectedChapters.add(chapterDTO); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public List<String> finalizeProcess() { |
||||||
|
if (hasErrors) { |
||||||
|
return validationErrors; |
||||||
|
} |
||||||
|
|
||||||
|
for (ChapterDTO chapter : collectedChapters) { |
||||||
|
if (chapter.getParentExcelId() == null || chapter.getParentExcelId() == 0) { |
||||||
|
saveToTemporaryTable(chapter); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (ChapterDTO chapter : collectedChapters) { |
||||||
|
if (chapter.getParentExcelId() != null && chapter.getParentExcelId() != 0) { |
||||||
|
saveToTemporaryTable(chapter); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
chapterService.moveDataFromTemporaryToFinal(); |
||||||
|
return Collections.emptyList(); |
||||||
|
} |
||||||
|
|
||||||
|
private void saveToTemporaryTable(ChapterDTO chapterDTO) { |
||||||
|
Long pid = chapterDTO.getParentExcelId(); |
||||||
|
if (pid != null && pid != 0) { |
||||||
|
Long parentId = excelIdToDatabaseIdMap.get(pid); |
||||||
|
if (parentId != null) { |
||||||
|
chapterDTO.setPid(parentId); |
||||||
|
} else { |
||||||
|
throw new BusinessException(ErrorCode.PARAMS_ERROR, "未找到父章节,Excel ID 为: " + pid); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
Long savedChapterId = chapterService.saveToTemporaryTable(chapterDTO); |
||||||
|
excelIdToDatabaseIdMap.put(chapterDTO.getExcelId(), savedChapterId); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private List<String> validateChapter(ChapterDTO chapterDTO) { |
||||||
|
List<String> errors = new ArrayList<>(); |
||||||
|
if (chapterDTO.getName() == null || chapterDTO.getName().isEmpty()) { |
||||||
|
errors.add("编号:"+chapterDTO.getExcelId()+"的章节名字不能为空"); |
||||||
|
} |
||||||
|
if (chapterDTO.getExcelId() == null || chapterDTO.getExcelId() <= 0) { |
||||||
|
errors.add("编号不能小于0"); |
||||||
|
} |
||||||
|
if (chapterDTO.getParentExcelId() == null || chapterDTO.getParentExcelId() < 0) { |
||||||
|
errors.add("编号:"+chapterDTO.getExcelId()+"的上级编号不能小于0"); |
||||||
|
} |
||||||
|
return errors; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void cell(String cellReference, String formattedValue, XSSFComment comment) { |
||||||
|
if (chapterDTO != null && formattedValue != null) { |
||||||
|
String letter = cellReference.substring(0, 1); |
||||||
|
switch (letter) { |
||||||
|
case "A": |
||||||
|
chapterDTO.setExcelId(Long.valueOf(formattedValue)); |
||||||
|
break; |
||||||
|
case "B": |
||||||
|
chapterDTO.setName(formattedValue); |
||||||
|
break; |
||||||
|
case "C": |
||||||
|
chapterDTO.setParentExcelId(Long.valueOf(formattedValue)); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||||
|
<mapper namespace="com.teaching.backend.mapper.chapter.TemporaryChapterMapper"> |
||||||
|
<delete id="deleteAll"> |
||||||
|
DELETE FROM temporary_chapter |
||||||
|
|
||||||
|
</delete> |
||||||
|
|
||||||
|
<select id="selectOneByPidAndOrderNumDesc" resultType="com.teaching.backend.model.entity.chapter.TemporaryChapter"> |
||||||
|
SELECT * FROM temporary_chapter |
||||||
|
WHERE pid = #{pid} |
||||||
|
ORDER BY order_num DESC |
||||||
|
LIMIT 1 |
||||||
|
</select> |
||||||
|
<select id="selectAll" resultType="com.teaching.backend.model.entity.chapter.TemporaryChapter"> |
||||||
|
|
||||||
|
SELECT * FROM temporary_chapter |
||||||
|
|
||||||
|
|
||||||
|
</select> |
||||||
|
</mapper> |
Loading…
Reference in new issue