forked from liweijie/education
专业添加导入模板下载和数据上传
This commit is contained in:
@@ -4,7 +4,9 @@ import com.roomroot.jwgl.entity.XKZYXX;
|
||||
import com.roomroot.jwgl.entity.ZYB;
|
||||
import com.roomroot.jwgl.unit.PageQuery;
|
||||
import com.roomroot.jwgl.unit.PageResult;
|
||||
import com.roomroot.jwgl.vo.discipline.MajorImportResultVO;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 学科专业管理服务接口
|
||||
@@ -89,4 +91,26 @@ public interface DisciplineService {
|
||||
* @return 分页结果
|
||||
*/
|
||||
PageResult<ZYB> pageMajor(PageQuery query, ZYB cond);
|
||||
|
||||
/**
|
||||
* 从 Excel 导入专业(.xls/.xlsx)。
|
||||
* <p>
|
||||
* 表头按专业导入模板的列名匹配。已存在的专业(专业代号已存在,或未填代号但
|
||||
* 专业名称+专业方向+培训类型已存在)直接跳过,不覆盖库中已有数据;
|
||||
* 校验不通过的行跳过并记录原因,其余合格行正常导入。
|
||||
* </p>
|
||||
*
|
||||
* @param file 上传的 Excel 文件
|
||||
* @return 导入结果(新增/跳过/失败与明细)
|
||||
* @throws Exception 解析异常
|
||||
*/
|
||||
MajorImportResultVO importMajorExcel(MultipartFile file) throws Exception;
|
||||
|
||||
/**
|
||||
* 生成专业导入模板(xlsx 字节流),列头与导入解析保持一致。
|
||||
*
|
||||
* @return xlsx 文件字节数组
|
||||
* @throws Exception 生成异常
|
||||
*/
|
||||
byte[] buildMajorImportTemplate() throws Exception;
|
||||
}
|
||||
|
||||
+142
@@ -11,14 +11,25 @@ import com.roomroot.jwgl.mapper.ZYBMapper;
|
||||
import com.roomroot.jwgl.service.DisciplineService;
|
||||
import com.roomroot.jwgl.unit.PageQuery;
|
||||
import com.roomroot.jwgl.unit.PageResult;
|
||||
import com.roomroot.jwgl.utils.ExcelExportUtil;
|
||||
import com.roomroot.jwgl.utils.ExcelParseUtil;
|
||||
import com.roomroot.jwgl.utils.UuidUtil;
|
||||
import com.roomroot.jwgl.dto.discipline.MajorImportRowDTO;
|
||||
import com.roomroot.jwgl.vo.discipline.MajorImportResultVO;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.io.InputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.roomroot.jwgl.utils.BasicManagementConstants.BAD_REQUEST;
|
||||
import static com.roomroot.jwgl.utils.BasicManagementConstants.CONFLICT;
|
||||
@@ -167,6 +178,137 @@ public class DisciplineServiceImpl implements DisciplineService {
|
||||
return new PageResult<>(result.getRecords(), result.getTotal(), pageNum, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public MajorImportResultVO importMajorExcel(MultipartFile file) throws Exception {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new ServiceException("请选择要导入的文件", BAD_REQUEST);
|
||||
}
|
||||
|
||||
List<MajorImportRowDTO> rows;
|
||||
try (InputStream inputStream = file.getInputStream()) {
|
||||
rows = ExcelParseUtil.parseMajorExcel(inputStream, file.getOriginalFilename());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// 文件格式不支持、表头未识别等:转为业务异常,前端可展示明确提示
|
||||
throw new ServiceException(ex.getMessage(), BAD_REQUEST);
|
||||
}
|
||||
if (rows.isEmpty()) {
|
||||
throw new ServiceException("Excel 中未解析到有效数据,请使用「专业导入模板」填写", BAD_REQUEST);
|
||||
}
|
||||
|
||||
MajorImportResultVO result = new MajorImportResultVO();
|
||||
result.setTotalCount(rows.size());
|
||||
|
||||
// 库中已有专业:代号集合 + (专业名称|专业方向|培训类型)业务键集合,用于跳过判定
|
||||
List<ZYB> existingMajors = zybMapper.selectList(new LambdaQueryWrapper<ZYB>()
|
||||
.select(ZYB::getZydh, ZYB::getZymc, ZYB::getZyfx, ZYB::getPxlx));
|
||||
Set<String> existingZydhSet = new HashSet<>();
|
||||
Set<String> existingKeySet = new HashSet<>();
|
||||
for (ZYB existing : existingMajors) {
|
||||
if (StringUtils.isNotEmpty(existing.getZydh())) {
|
||||
existingZydhSet.add(existing.getZydh().trim());
|
||||
}
|
||||
existingKeySet.add(buildMajorKey(existing.getZymc(), existing.getZyfx(), existing.getPxlx()));
|
||||
}
|
||||
|
||||
List<ZYB> toInsert = new ArrayList<>();
|
||||
for (MajorImportRowDTO row : rows) {
|
||||
ZYB entity = row.getEntity();
|
||||
// 1. 必填与长度校验(与新增专业的校验口径一致)
|
||||
try {
|
||||
validateMajor(entity, true);
|
||||
} catch (ServiceException ex) {
|
||||
result.addFail("第" + row.getRowNum() + "行:" + ex.getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. 已存在则跳过,不覆盖库中数据
|
||||
String zydh = entity.getZydh() == null ? null : entity.getZydh().trim();
|
||||
if (StringUtils.isNotEmpty(zydh) && existingZydhSet.contains(zydh)) {
|
||||
result.addSkip("第" + row.getRowNum() + "行:专业代号 " + zydh + " 已存在,已跳过");
|
||||
continue;
|
||||
}
|
||||
String key = buildMajorKey(entity.getZymc(), entity.getZyfx(), entity.getPxlx());
|
||||
if (existingKeySet.contains(key)) {
|
||||
result.addSkip("第" + row.getRowNum() + "行:" + describeMajor(entity) + " 已存在,已跳过");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. 补齐主键与必要字段后入队
|
||||
if (StringUtils.isEmpty(zydh)) {
|
||||
entity.setZydh(UuidUtil.getOriginalUUID());
|
||||
} else {
|
||||
entity.setZydh(zydh);
|
||||
}
|
||||
fillMajorDefaultsForImport(entity);
|
||||
|
||||
existingZydhSet.add(entity.getZydh());
|
||||
existingKeySet.add(key);
|
||||
toInsert.add(entity);
|
||||
}
|
||||
|
||||
for (ZYB entity : toInsert) {
|
||||
zybMapper.insert(entity);
|
||||
result.setInsertCount(result.getInsertCount() + 1);
|
||||
}
|
||||
|
||||
result.setMessage(String.format("共解析 %d 行,新增 %d 条,跳过 %d 条,失败 %d 条",
|
||||
result.getTotalCount(), result.getInsertCount(),
|
||||
result.getSkipCount(), result.getFailCount()));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] buildMajorImportTemplate() throws Exception {
|
||||
return ExcelExportUtil.export("专业导入", ExcelParseUtil.MAJOR_IMPORT_HEADERS,
|
||||
Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入专用兜底。
|
||||
* <p>
|
||||
* 只补"库中没有非空缺省值"或需要按业务推导的字段(专业标识号、规范名称),
|
||||
* 其余空值保持 null,交由数据库缺省值生效(如 节次类别='双节次'、系统模式='军校模式'、
|
||||
* 培训类型2/学员类别='其他'),避免写成空串。
|
||||
* </p>
|
||||
*/
|
||||
private void fillMajorDefaultsForImport(ZYB entity) {
|
||||
entity.setTy(false);
|
||||
entity.setQysj(LocalDateTime.now());
|
||||
if (entity.getZgzy() == null) {
|
||||
entity.setZgzy(false);
|
||||
}
|
||||
if (StringUtils.isEmpty(entity.getZybsh())) {
|
||||
entity.setZybsh(entity.getZydh());
|
||||
}
|
||||
if (StringUtils.isEmpty(entity.getGfmc())) {
|
||||
entity.setGfmc(entity.getZymc());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务键:专业名称 + 专业方向 + 培训类型(多版本通过培训类型区分)。
|
||||
*/
|
||||
private String buildMajorKey(String zymc, String zyfx, String pxlx) {
|
||||
return normalize(zymc) + "|" + normalize(zyfx) + "|" + normalize(pxlx);
|
||||
}
|
||||
|
||||
private String normalize(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
private String describeMajor(ZYB entity) {
|
||||
StringBuilder text = new StringBuilder("专业「");
|
||||
text.append(normalize(entity.getZymc())).append("」");
|
||||
if (StringUtils.isNotEmpty(entity.getZyfx())) {
|
||||
text.append("(方向:").append(entity.getZyfx().trim()).append(")");
|
||||
}
|
||||
if (StringUtils.isNotEmpty(entity.getPxlx())) {
|
||||
text.append("(培训类型:").append(entity.getPxlx().trim()).append(")");
|
||||
}
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
private void fillMajorDefaults(ZYB entity) {
|
||||
if (entity.getTy() == null) {
|
||||
entity.setTy(false);
|
||||
|
||||
Reference in New Issue
Block a user