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