diff --git a/src/main/java/com/teaching/backend/controller/chapter/ChapterController2.java b/src/main/java/com/teaching/backend/controller/chapter/ChapterController2.java index 68f58f1..d77e609 100644 --- a/src/main/java/com/teaching/backend/controller/chapter/ChapterController2.java +++ b/src/main/java/com/teaching/backend/controller/chapter/ChapterController2.java @@ -1,15 +1,21 @@ package com.teaching.backend.controller.chapter; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.teaching.backend.common.BaseResponse; +import com.teaching.backend.common.ErrorCode; import com.teaching.backend.common.ResultUtils; import com.teaching.backend.model.dto.chapter.ChapterDTO; import com.teaching.backend.model.entity.chapter.Chapter; import com.teaching.backend.service.chapter.IChapterService; +import com.teaching.backend.service.impl.chapter.ChapterExcelServiceImpl; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.time.LocalDateTime; import java.util.List; @@ -29,21 +35,29 @@ public class ChapterController2 { @Autowired IChapterService chapterService; + @Autowired + ChapterExcelServiceImpl chapterExcelService; @ApiOperation("查询全部的章节") @GetMapping("/list") - public BaseResponse> AllList(){ - List list= chapterService.list(); - return ResultUtils.success(list); + public BaseResponse> AllList(@RequestParam(value = "page",defaultValue = "1") int page, + @RequestParam(value = "pageSize",defaultValue = "5") int pageSize){ + return ResultUtils.success(chapterService.getAllChapters(page,pageSize)); } + @ApiOperation("根据课程id查出对应的父子章节") @GetMapping("/chapter") public BaseResponse> getCourseChapters2(@RequestParam String courseId){ return ResultUtils.success(chapterService.getChapterTree(courseId)); - } + @ApiOperation("根据课程id查出对应的详细章节信息") + @GetMapping("/select/chapter/{chapterId}") + public BaseResponseselectChapter(@PathVariable Long chapterId){ + return ResultUtils.success(chapterService.selectChapter(chapterId)); + + } @ApiOperation("添加章节") @PostMapping("/add") @@ -65,7 +79,36 @@ public BaseResponse> getCourseChapters2(@RequestParam String cours return ResultUtils.success("删除成功"); } + @ApiOperation("下载Excel模版") + @GetMapping("/downExcel") + public void downExcel2(HttpServletResponse response) throws IOException { + chapterService.downExcel(response); + } + + @ApiOperation("导入excel") + @PostMapping("/uploadExcel") + public BaseResponse addChapter3(MultipartFile file, String courseId) { + try { + List validationErrors = chapterExcelService.uploadExcel(file, courseId); + if (!validationErrors.isEmpty()) { + StringBuilder errorMessage = new StringBuilder(); + for (String error : validationErrors) { + errorMessage.append(error).append("\n"); + } + return ResultUtils.error(ErrorCode.OPERATION_ERROR, errorMessage.toString()); + } else { + return ResultUtils.success("添加成功!!!!!!!!"); + } + } catch (Exception e) { + return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误: " + e.getMessage()); + } + } + @ApiOperation("导出数据") + @GetMapping(value = "/downLoadExcel") + public void downLoadXlsx(HttpServletResponse response,String courseId) throws Exception{ + chapterService.downLoadXlsx(response,courseId); + } @ApiOperation("修改章节") @PutMapping("/update") public BaseResponse udChapter(@RequestBody Chapter chapter){ diff --git a/src/main/java/com/teaching/backend/mapper/chapter/ChapterMapper.java b/src/main/java/com/teaching/backend/mapper/chapter/ChapterMapper.java index 6825636..01859f9 100644 --- a/src/main/java/com/teaching/backend/mapper/chapter/ChapterMapper.java +++ b/src/main/java/com/teaching/backend/mapper/chapter/ChapterMapper.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.teaching.backend.model.entity.chapter.Chapter; import io.lettuce.core.dynamic.annotation.Param; import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; import java.util.List; @@ -22,5 +23,14 @@ public interface ChapterMapper extends BaseMapper { @Select("SELECT * FROM course_chapter WHERE course_id = #{courseId} AND (pid = #{parentId} )") List selectSectionsWithChildren(@Param("courseId") String courseId, @Param("parentId") String parentId); + @Select("SELECT * FROM chapter WHERE id = #{excelId}") + Chapter findByExcelId(Long excelId); + @Update("UPDATE chapter SET pid = #{newPid} WHERE id = #{id}") + void updatePidById(@Param("id") Long id, @Param("newPid") Long newPid); + + @Select("SELECT id, name, course_id FROM chapter WHERE name = #{name} AND course_id = #{courseId}") + Chapter selectByNameAndCourseId(@Param("name") String name, @Param("courseId") String courseId); +@Select("select pid ,course_id,total_class_hours from chapter where pid = #{chapterId} and course_id = #{courseId} ") + List selectByIdAndPid(@Param("chapterId") Long chapterId, @Param("courseId")String courseId); } diff --git a/src/main/java/com/teaching/backend/mapper/chapter/TemporaryChapterMapper.java b/src/main/java/com/teaching/backend/mapper/chapter/TemporaryChapterMapper.java new file mode 100644 index 0000000..54c2347 --- /dev/null +++ b/src/main/java/com/teaching/backend/mapper/chapter/TemporaryChapterMapper.java @@ -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; + +/** + *

+ * Mapper 接口 + *

+ * + * @author author + * @since 2024-05-31 + */ +public interface TemporaryChapterMapper extends BaseMapper { + + TemporaryChapter selectOneByPidAndOrderNumDesc(long l); + + List selectAll(); + + void deleteAll(); +} diff --git a/src/main/java/com/teaching/backend/model/dto/chapter/ChapterDTO.java b/src/main/java/com/teaching/backend/model/dto/chapter/ChapterDTO.java index 7ceb140..de5a912 100644 --- a/src/main/java/com/teaching/backend/model/dto/chapter/ChapterDTO.java +++ b/src/main/java/com/teaching/backend/model/dto/chapter/ChapterDTO.java @@ -1,5 +1,7 @@ package com.teaching.backend.model.dto.chapter; +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; @@ -28,12 +30,14 @@ public class ChapterDTO implements Serializable { @ApiModelProperty(value = "创建日期") + @JsonFormat(pattern = "yyyy/M/d HH:mm:ss") private LocalDateTime createTime; @ApiModelProperty(value = "更新人") private String updateBy; @ApiModelProperty(value = "更新日期") + @JsonFormat(pattern = "yyyy/M/d HH-mm-ss") private LocalDateTime updateTime; @ApiModelProperty(value = "所属部门") @@ -74,7 +78,12 @@ public class ChapterDTO implements Serializable { @ApiModelProperty(value = "内部序号显示") private String numshow; + @ApiModelProperty(value = "excel上级序号") + @TableField(exist = false) + private Long ParentExcelId; - + @ApiModelProperty(value = "excel序号") + @TableField(exist = false) + private Long ExcelId; } diff --git a/src/main/java/com/teaching/backend/model/entity/chapter/TemporaryChapter.java b/src/main/java/com/teaching/backend/model/entity/chapter/TemporaryChapter.java new file mode 100644 index 0000000..972b21c --- /dev/null +++ b/src/main/java/com/teaching/backend/model/entity/chapter/TemporaryChapter.java @@ -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; + +/** + *

+ * + *

+ * + * @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 children; // 用于存储子章节 + + @ApiModelProperty(value = "知识点") + @TableField(exist = false) + private List knowledgePoints; // 用于存储章节下的知识点 + + +} diff --git a/src/main/java/com/teaching/backend/service/chapter/IChapterService.java b/src/main/java/com/teaching/backend/service/chapter/IChapterService.java index 9f50a31..6eafd1f 100644 --- a/src/main/java/com/teaching/backend/service/chapter/IChapterService.java +++ b/src/main/java/com/teaching/backend/service/chapter/IChapterService.java @@ -1,11 +1,15 @@ package com.teaching.backend.service.chapter; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import com.teaching.backend.model.dto.chapter.ChapterDTO; import com.teaching.backend.model.entity.chapter.Chapter; import com.teaching.backend.model.vo.chapter.ChapterVo; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.util.LinkedList; import java.util.List; @@ -32,6 +36,9 @@ public interface IChapterService extends IService { void saveChapter(ChapterDTO chapterDTO); + + Long saveExcelChapter2(ChapterDTO chapterDTO, boolean b); + void insertChapter(Long chapterId1, Long chapterId2, ChapterDTO newChapterDTO); void deleteChapter(Long id); @@ -41,4 +48,17 @@ public interface IChapterService extends IService { void downChapter(Long chapterId); + void downLoadXlsx(HttpServletResponse response, String courseId) throws IOException; + + + + void moveDataFromTemporaryToFinal(); + + Long saveToTemporaryTable(ChapterDTO chapterDTO); + + void downExcel(HttpServletResponse response) throws IOException; + + Page getAllChapters(int page, int pageSize); + + Chapter selectChapter(Long chapterId); } diff --git a/src/main/java/com/teaching/backend/service/impl/chapter/ChapterExcelServiceImpl.java b/src/main/java/com/teaching/backend/service/impl/chapter/ChapterExcelServiceImpl.java new file mode 100644 index 0000000..a1a7af4 --- /dev/null +++ b/src/main/java/com/teaching/backend/service/impl/chapter/ChapterExcelServiceImpl.java @@ -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 uploadExcel(MultipartFile file, String courseId) { + List validationErrors = new ArrayList<>(); + try (InputStream inputStream = file.getInputStream()) { + validationErrors = excelParser.parse(inputStream, courseId); + } catch (Exception e) { + validationErrors.add("系统错误: " + e.getMessage()); + } + return validationErrors; + } +} \ No newline at end of file diff --git a/src/main/java/com/teaching/backend/service/impl/chapter/ChapterServiceImpl.java b/src/main/java/com/teaching/backend/service/impl/chapter/ChapterServiceImpl.java index eea5e3d..b82deab 100644 --- a/src/main/java/com/teaching/backend/service/impl/chapter/ChapterServiceImpl.java +++ b/src/main/java/com/teaching/backend/service/impl/chapter/ChapterServiceImpl.java @@ -4,21 +4,29 @@ package com.teaching.backend.service.impl.chapter; import cn.hutool.core.bean.BeanUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.teaching.backend.common.ErrorCode; import com.teaching.backend.exception.BusinessException; import com.teaching.backend.mapper.Knowtemp.KnowtempMapper; import com.teaching.backend.mapper.chapter.ChapterMapper; +import com.teaching.backend.mapper.chapter.TemporaryChapterMapper; import com.teaching.backend.model.dto.chapter.ChapterDTO; import com.teaching.backend.model.entity.Knowtemp.Knowtemp; import com.teaching.backend.model.entity.chapter.Chapter; +import com.teaching.backend.model.entity.chapter.TemporaryChapter; import com.teaching.backend.model.vo.chapter.ChapterVo; import com.teaching.backend.service.KnowGraph.KnowService; import com.teaching.backend.service.chapter.IChapterService; +import com.teaching.backend.utils.Chapter.ExcelUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; @@ -30,9 +38,12 @@ import java.util.stream.Collectors; */ @Service public class ChapterServiceImpl extends ServiceImpl implements IChapterService { + @Autowired private ChapterMapper chapterMapper; + @Autowired + private TemporaryChapterMapper temporaryChapterMapper; @Autowired private KnowtempMapper knowtempMapper; // private int count=0; @@ -217,7 +228,7 @@ public class ChapterServiceImpl extends ServiceImpl impl } @Override public void saveChapter(ChapterDTO chapterDTO) { - + System.out.println("我导入Excel成功进入到了这一步"); Chapter chapter = new Chapter(); try { BeanUtils.copyProperties(chapterDTO, chapter); @@ -225,7 +236,7 @@ public class ChapterServiceImpl extends ServiceImpl impl throw new RuntimeException("复制数据出错", e); } Long pid = chapter.getPid(); - if (pid == null ) { + if (pid == null ) { chapter.setPid(0L); LambdaQueryWrapper maxSortWrapper = new LambdaQueryWrapper<>(); maxSortWrapper.eq(Chapter::getPid, 0).orderByDesc(Chapter::getOrderNum).last("limit 1"); @@ -266,6 +277,59 @@ public class ChapterServiceImpl extends ServiceImpl impl } } } + @Override + public Long saveExcelChapter2(ChapterDTO chapterDTO, boolean isParent) { + Chapter chapter = new Chapter(); + try { + BeanUtils.copyProperties(chapterDTO, chapter); + } catch (Exception e) { + throw new RuntimeException("复制数据出错", e); // 使用自定义异常 + } + + LocalDateTime now = LocalDateTime.now(); + chapter.setCreateTime(now); + chapter.setUpdateTime(now); + + Long pid = chapter.getPid(); + if (isParent) { + if (pid == null || pid == 0) { + chapter.setPid(0L); + + LambdaQueryWrapper maxSortWrapper = new LambdaQueryWrapper<>(); + maxSortWrapper.eq(Chapter::getPid, 0).orderByDesc(Chapter::getOrderNum).last("limit 1"); + + Chapter maxSortChapter = this.getOne(maxSortWrapper); + double newSort = (maxSortChapter != null) ? maxSortChapter.getOrderNum() + 1 : 1; + + chapter.setOrderNum(newSort); + this.save(chapter); + + return chapter.getId(); + } + }else { + Chapter parentChapter = this.getById(pid); + if (parentChapter != null) { + LambdaQueryWrapper maxSortWrapper = new LambdaQueryWrapper<>(); + maxSortWrapper.eq(Chapter::getPid, pid).orderByDesc(Chapter::getOrderNum).last("limit 1"); + + Chapter maxSortChapter = this.getOne(maxSortWrapper); + double newSort = (maxSortChapter != null) ? maxSortChapter.getOrderNum() + 1 : 1; + + chapter.setOrderNum(newSort); + + parentChapter.setUpdateTime(now); + this.updateById(parentChapter); + + this.save(chapter); + + return chapter.getId(); + } else { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "未找到父章节,ID 为: " + pid); + } + } return null; + } + + @Override public void insertChapter(Long chapterId1, Long chapterId2, ChapterDTO newChapterDTO) { @@ -346,6 +410,146 @@ public class ChapterServiceImpl extends ServiceImpl impl moveChapter(chapterId, false); } + @Override + public void downExcel(HttpServletResponse response) throws IOException { + ExcelUtils.createTemplateExcel(response); + } + + @Override + public PagegetAllChapters(int page, int pageSize) { + + Page pageInfo=new Page<>(page,pageSize); + LambdaQueryWrapper queryWrapper=new LambdaQueryWrapper<>(); + this.page(pageInfo,queryWrapper); + + return pageInfo; + } + + @Override + public Chapter selectChapter(Long chapterId) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(Chapter::getId, chapterId); + + Chapter chapter = this.getOne(lambdaQueryWrapper); + if (chapter == null) { + throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "请求数据不存在"); + } + + if (chapter.getPid() == 0) { + String courseId = chapter.getCourseId(); + List subChapters = chapterMapper.selectByIdAndPid(chapterId,courseId); + + + BigDecimal totalClassHours = BigDecimal.ZERO; + for (Chapter subChapter : subChapters) { + try { + BigDecimal classHours = new BigDecimal(subChapter.getTotalClassHours()); + totalClassHours = totalClassHours.add(classHours); + } catch (NumberFormatException e) { + throw new BusinessException(ErrorCode.OPERATION_ERROR, "无效的总学时数据"); + } + } + + chapter.setTotalClassHours(String.valueOf(totalClassHours)); + }else { + chapter.setTotalClassHours(chapter.getTotalClassHours()); + } + + + return chapter; + } + + @Override + public void downLoadXlsx(HttpServletResponse response, String courseId) throws IOException { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(Chapter::getCourseId, courseId); + + List chapterList = this.list(lambdaQueryWrapper); + ExcelUtils.createDataExcel(response, chapterList); + } + + + @Override + @Transactional(rollbackFor = Exception.class) + public void moveDataFromTemporaryToFinal() { + List temporaryChapters = temporaryChapterMapper.selectAll(); + Map tempIdToFinalIdMap = new HashMap<>(); + + for (TemporaryChapter tempChapter : temporaryChapters) { + + Chapter existingChapter = chapterMapper.selectByNameAndCourseId(tempChapter.getName(), tempChapter.getCourseId()); + if (existingChapter != null) { + tempIdToFinalIdMap.put(tempChapter.getId(), existingChapter.getId()); + continue; + } + + + + Chapter finalChapter = new Chapter(); + try { + BeanUtils.copyProperties(tempChapter, finalChapter); + } catch (Exception e) { + throw new RuntimeException("复制数据出错", e); + } + finalChapter.setId(null); + this.save(finalChapter); + tempIdToFinalIdMap.put(tempChapter.getId(), finalChapter.getId()); + } + + for (TemporaryChapter tempChapter : temporaryChapters) { + if (tempChapter.getPid() != 0) { + Long newPid = tempIdToFinalIdMap.get(tempChapter.getPid()); + if (newPid != null) { + chapterMapper.updatePidById(tempIdToFinalIdMap.get(tempChapter.getId()), newPid); + } else { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "未找到父章节,临时表 ID 为: " + tempChapter.getPid()); + } + } + } + + temporaryChapterMapper.deleteAll(); + } + + @Transactional(rollbackFor = Exception.class) + + @Override + public Long saveToTemporaryTable(ChapterDTO chapterDTO) { + TemporaryChapter temporaryChapter = new TemporaryChapter(); + try { + BeanUtils.copyProperties(chapterDTO, temporaryChapter); + } catch (Exception e) { + throw new RuntimeException("复制数据出错", e); + } + + Long pid = temporaryChapter.getPid(); + if (pid == null || pid == 0) { + temporaryChapter.setPid(0L); + setChapterOrder(temporaryChapter, 0L); + } else { + TemporaryChapter parentChapter = temporaryChapterMapper.selectById(pid); + if (parentChapter != null) { + temporaryChapter.setPid(parentChapter.getId()); + setChapterOrder(temporaryChapter, pid); + } else { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "未找到父章节,ID 为: " + pid); + } + } + + temporaryChapterMapper.insert(temporaryChapter); + return temporaryChapter.getId(); + + } + + + private void setChapterOrder(TemporaryChapter chapter, Long pid) { + TemporaryChapter maxSortChapter = temporaryChapterMapper.selectOneByPidAndOrderNumDesc(pid); + double newSort = (maxSortChapter != null) ? maxSortChapter.getOrderNum() + 1 : 1; + chapter.setOrderNum(newSort); + chapter.setCreateTime(LocalDateTime.now()); + chapter.setUpdateTime(LocalDateTime.now()); + } + + public void moveChapter(Long chapterId, boolean moveUp) { diff --git a/src/main/java/com/teaching/backend/utils/Chapter/ExcelParser.java b/src/main/java/com/teaching/backend/utils/Chapter/ExcelParser.java new file mode 100644 index 0000000..4e764b6 --- /dev/null +++ b/src/main/java/com/teaching/backend/utils/Chapter/ExcelParser.java @@ -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 parse(InputStream inputStream, String courseId) throws Exception { + OPCPackage pkg = OPCPackage.open(inputStream); + List 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; + } +} \ No newline at end of file diff --git a/src/main/java/com/teaching/backend/utils/Chapter/ExcelUtils.java b/src/main/java/com/teaching/backend/utils/Chapter/ExcelUtils.java new file mode 100644 index 0000000..1f08f3b --- /dev/null +++ b/src/main/java/com/teaching/backend/utils/Chapter/ExcelUtils.java @@ -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 chapterList) throws IOException { + String[] titles = new String[]{"编号", "章节", "上级编号"}; + Sheet sheet = createWorkbookAndSheet("用户数据", titles); + + Map 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 createIdMapping(List chapterList) { + Map 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(); + } + } +} diff --git a/src/main/java/com/teaching/backend/utils/Chapter/SheetHandler.java b/src/main/java/com/teaching/backend/utils/Chapter/SheetHandler.java new file mode 100644 index 0000000..c78d062 --- /dev/null +++ b/src/main/java/com/teaching/backend/utils/Chapter/SheetHandler.java @@ -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 excelIdToDatabaseIdMap = new HashMap<>(); + private List validationErrors = new ArrayList<>(); + private boolean hasErrors = false; + private List 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 errors = validateChapter(chapterDTO); + if (!errors.isEmpty()) { + validationErrors.addAll(errors); + hasErrors = true; + return; + } + + collectedChapters.add(chapterDTO); + } + } + + public List 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 validateChapter(ChapterDTO chapterDTO) { + List 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; + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/mapper/TemporaryChapterMapper.xml b/src/main/resources/mapper/TemporaryChapterMapper.xml new file mode 100644 index 0000000..54932b1 --- /dev/null +++ b/src/main/resources/mapper/TemporaryChapterMapper.xml @@ -0,0 +1,21 @@ + + + + + DELETE FROM temporary_chapter + + + + + + \ No newline at end of file