From 9d4cbff7464ebfd7803c2ce53ded49d0f73fe011 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期一, 29 六月 2026 20:33:55 +0800
Subject: [PATCH] 初步调整fix
---
backend/src/main/java/com/sinata/appointment/service/ConfigApplicationService.java | 290 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 287 insertions(+), 3 deletions(-)
diff --git a/backend/src/main/java/com/sinata/appointment/service/ConfigApplicationService.java b/backend/src/main/java/com/sinata/appointment/service/ConfigApplicationService.java
index 839bcf0..9dc8214 100644
--- a/backend/src/main/java/com/sinata/appointment/service/ConfigApplicationService.java
+++ b/backend/src/main/java/com/sinata/appointment/service/ConfigApplicationService.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.alibaba.excel.EasyExcel;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sinata.appointment.constants.BizErrorCode;
@@ -15,12 +16,18 @@
import com.sinata.appointment.dto.ContentSettingsSaveRequest;
import com.sinata.appointment.dto.ContentSettingsVo;
import com.sinata.appointment.dto.FieldConfigPatchRequest;
+import com.sinata.appointment.dto.SensitiveWordCreateRequest;
+import com.sinata.appointment.dto.SensitiveWordEnabledPatchRequest;
+import com.sinata.appointment.dto.SensitiveWordImportResultVo;
+import com.sinata.appointment.dto.SensitiveWordListVo;
+import com.sinata.appointment.dto.SensitiveWordUpdateRequest;
import com.sinata.appointment.dto.VisitReasonCreateRequest;
import com.sinata.appointment.dto.VisitReasonListVo;
import com.sinata.appointment.dto.VisitReasonUpdateRequest;
import com.sinata.appointment.entity.BizApprovalNodeConfig;
import com.sinata.appointment.entity.BizFieldConfig;
import com.sinata.appointment.entity.BizHolidayDate;
+import com.sinata.appointment.entity.BizSensitiveWord;
import com.sinata.appointment.entity.BizVisitReason;
import com.sinata.appointment.entity.SysBizSetting;
import com.sinata.appointment.entity.SysDept;
@@ -28,15 +35,22 @@
import com.sinata.appointment.mapper.BizApprovalNodeConfigMapper;
import com.sinata.appointment.mapper.BizFieldConfigMapper;
import com.sinata.appointment.mapper.BizHolidayDateMapper;
+import com.sinata.appointment.mapper.BizSensitiveWordMapper;
import com.sinata.appointment.mapper.BizVisitReasonMapper;
import com.sinata.appointment.mapper.SysBizSettingMapper;
import com.sinata.appointment.mapper.SysDeptMapper;
import com.sinata.appointment.util.UuidAssert;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.time.LocalDate;
+import java.time.YearMonth;
import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -46,6 +60,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
@Service
@RequiredArgsConstructor
@@ -57,10 +72,12 @@
private final BizFieldConfigMapper bizFieldConfigMapper;
private final BizApprovalNodeConfigMapper bizApprovalNodeConfigMapper;
private final BizHolidayDateMapper bizHolidayDateMapper;
+ private final BizSensitiveWordMapper bizSensitiveWordMapper;
private final SysBizSettingMapper sysBizSettingMapper;
private final SysDeptMapper sysDeptMapper;
private final SysBizSettingCacheService sysBizSettingCacheService;
private final BizHolidayDateCacheService bizHolidayDateCacheService;
+ private final SensitiveWordFilterService sensitiveWordFilterService;
private final CacheManager cacheManager;
private final ObjectMapper objectMapper;
@@ -88,8 +105,8 @@
new Page<>(page, size),
new LambdaQueryWrapper<BizVisitReason>()
.eq(BizVisitReason::getDelFlag, 0)
- .orderByDesc(BizVisitReason::getCreateTime)
- .orderByDesc(BizVisitReason::getId));
+ .orderByAsc(BizVisitReason::getSortOrder)
+ .orderByAsc(BizVisitReason::getId));
return pg.convert(this::toVisitReasonListVo);
}
@@ -101,7 +118,7 @@
row.setReasonName(reasonName);
row.setReasonCode("VR_" + UUID.randomUUID().toString().replace("-", ""));
row.setEnabled(1);
- row.setSortOrder(0);
+ row.setSortOrder(normalizeSortOrder(req.getSortOrder()));
bizVisitReasonMapper.insert(row);
evictMiniVisitReasonCache();
}
@@ -112,6 +129,7 @@
String reasonName = req.getReasonName().trim();
assertReasonNameUnique(reasonName, id);
row.setReasonName(reasonName);
+ row.setSortOrder(normalizeSortOrder(req.getSortOrder()));
bizVisitReasonMapper.updateById(row);
evictMiniVisitReasonCache();
}
@@ -121,6 +139,123 @@
loadActiveVisitReason(id, uuid);
bizVisitReasonMapper.deleteById(id);
evictMiniVisitReasonCache();
+ }
+
+ public IPage<SensitiveWordListVo> pageSensitiveWords(long page, long size, String keyword) {
+ String normalizedKeyword = trimToNull(keyword);
+ Page<BizSensitiveWord> pg =
+ bizSensitiveWordMapper.selectPage(
+ new Page<>(page, size),
+ new LambdaQueryWrapper<BizSensitiveWord>()
+ .eq(BizSensitiveWord::getDelFlag, 0)
+ .like(StringUtils.hasText(normalizedKeyword), BizSensitiveWord::getWord, normalizedKeyword)
+ .orderByDesc(BizSensitiveWord::getCreateTime)
+ .orderByDesc(BizSensitiveWord::getId));
+ return pg.convert(this::toSensitiveWordListVo);
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ public void createSensitiveWord(SensitiveWordCreateRequest req) {
+ String word = normalizeSensitiveWord(req.getWord());
+ assertSensitiveWordUnique(word, null);
+ BizSensitiveWord row = new BizSensitiveWord();
+ row.setWord(word);
+ row.setEnabled(1);
+ bizSensitiveWordMapper.insert(row);
+ sensitiveWordFilterService.evictCache();
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ public void updateSensitiveWord(Long id, SensitiveWordUpdateRequest req) {
+ BizSensitiveWord row = loadActiveSensitiveWord(id, req.getUuid());
+ String word = normalizeSensitiveWord(req.getWord());
+ assertSensitiveWordUnique(word, id);
+ row.setWord(word);
+ bizSensitiveWordMapper.updateById(row);
+ sensitiveWordFilterService.evictCache();
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ public void updateSensitiveWordEnabled(Long id, SensitiveWordEnabledPatchRequest req) {
+ BizSensitiveWord row = loadActiveSensitiveWord(id, req.getUuid());
+ row.setEnabled(req.getEnabled());
+ bizSensitiveWordMapper.updateById(row);
+ sensitiveWordFilterService.evictCache();
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ public void deleteSensitiveWord(Long id, String uuid) {
+ loadActiveSensitiveWord(id, uuid);
+ bizSensitiveWordMapper.deleteById(id);
+ sensitiveWordFilterService.evictCache();
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ public SensitiveWordImportResultVo importSensitiveWords(MultipartFile file) {
+ if (file == null || file.isEmpty()) {
+ throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "导入文件不能为空");
+ }
+ String filename = file.getOriginalFilename() == null ? "" : file.getOriginalFilename().toLowerCase();
+ if (!filename.endsWith(".xlsx") && !filename.endsWith(".xls")) {
+ throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "请上传 xls 或 xlsx 文件");
+ }
+
+ List<Map<Integer, String>> rows;
+ try {
+ rows = EasyExcel.read(file.getInputStream()).sheet().headRowNumber(0).doReadSync();
+ } catch (IOException ex) {
+ throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "读取导入文件失败");
+ }
+
+ SensitiveWordImportResultVo result = new SensitiveWordImportResultVo();
+ Set<String> fileWords = new LinkedHashSet<>();
+ Set<String> existingWords = loadExistingSensitiveWords();
+ int rowNo = 0;
+ for (Map<Integer, String> row : rows) {
+ rowNo++;
+ String raw = row == null ? null : row.get(0);
+ if (rowNo == 1 && isSensitiveWordHeader(raw)) {
+ continue;
+ }
+ if (!StringUtils.hasText(raw)) {
+ continue;
+ }
+ result.setTotalRows(result.getTotalRows() + 1);
+ String word;
+ try {
+ word = normalizeSensitiveWord(raw);
+ } catch (BizException ex) {
+ result.setInvalidCount(result.getInvalidCount() + 1);
+ addImportError(result, "第" + rowNo + "行:" + ex.getMessage());
+ continue;
+ }
+ if (fileWords.contains(word) || existingWords.contains(word)) {
+ result.setDuplicateCount(result.getDuplicateCount() + 1);
+ continue;
+ }
+ BizSensitiveWord entity = new BizSensitiveWord();
+ entity.setWord(word);
+ entity.setEnabled(1);
+ bizSensitiveWordMapper.insert(entity);
+ fileWords.add(word);
+ existingWords.add(word);
+ result.setSuccessCount(result.getSuccessCount() + 1);
+ }
+ if (result.getSuccessCount() > 0) {
+ sensitiveWordFilterService.evictCache();
+ }
+ return result;
+ }
+
+ public byte[] sensitiveWordImportTemplateExcel() {
+ try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+ List<List<String>> head = List.of(List.of("敏感词"));
+ List<List<String>> rows = List.of(List.of("示例敏感词"));
+ EasyExcel.write(bos).head(head).sheet("敏感词导入").doWrite(rows);
+ return bos.toByteArray();
+ } catch (IOException ex) {
+ throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "生成导入模板失败");
+ }
}
public List<BizFieldConfig> listFieldConfigs() {
@@ -230,6 +365,62 @@
upsertSetting(
SystemSettingKeys.VEHICLE_BARRIER_GATE_ENABLED,
Boolean.TRUE.equals(req.getVehicleBarrierEnabled()) ? "1" : "0");
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ public void toggleHolidayDate(String date, boolean holiday) {
+ LocalDate target = parseDate(date);
+ if (holiday) {
+ Long exists =
+ bizHolidayDateMapper.selectCount(
+ new LambdaQueryWrapper<BizHolidayDate>()
+ .eq(BizHolidayDate::getDelFlag, 0)
+ .eq(BizHolidayDate::getHolidayDate, target));
+ if (exists == null || exists == 0L) {
+ BizHolidayDate row = new BizHolidayDate();
+ row.setHolidayDate(target);
+ bizHolidayDateMapper.insert(row);
+ }
+ } else {
+ bizHolidayDateMapper.deleteByHolidayDates(List.of(target));
+ }
+ bizHolidayDateCacheService.evictAllHolidayDates();
+ upsertSetting(SystemSettingKeys.BOOKING_HOLIDAY_DATES, "[]");
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ public void addMonthWeekends(String month) {
+ YearMonth yearMonth;
+ try {
+ yearMonth = YearMonth.parse(month.trim());
+ } catch (Exception ex) {
+ throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "非法月份: " + month);
+ }
+ Set<LocalDate> existsSet =
+ bizHolidayDateMapper.selectList(
+ new LambdaQueryWrapper<BizHolidayDate>()
+ .eq(BizHolidayDate::getDelFlag, 0)
+ .ge(BizHolidayDate::getHolidayDate, yearMonth.atDay(1))
+ .le(BizHolidayDate::getHolidayDate, yearMonth.atEndOfMonth()))
+ .stream()
+ .map(BizHolidayDate::getHolidayDate)
+ .collect(Collectors.toSet());
+
+ for (int d = 1; d <= yearMonth.lengthOfMonth(); d++) {
+ LocalDate current = yearMonth.atDay(d);
+ int dayOfWeek = current.getDayOfWeek().getValue(); // 1..7 (Mon..Sun)
+ if (dayOfWeek != 6 && dayOfWeek != 7) {
+ continue;
+ }
+ if (existsSet.contains(current)) {
+ continue;
+ }
+ BizHolidayDate row = new BizHolidayDate();
+ row.setHolidayDate(current);
+ bizHolidayDateMapper.insert(row);
+ }
+ bizHolidayDateCacheService.evictAllHolidayDates();
+ upsertSetting(SystemSettingKeys.BOOKING_HOLIDAY_DATES, "[]");
}
@Transactional(rollbackFor = Exception.class)
@@ -364,6 +555,13 @@
return html == null ? "" : html;
}
+ private static String trimToNull(String value) {
+ if (!StringUtils.hasText(value)) {
+ return null;
+ }
+ return value.trim();
+ }
+
private void upsertSetting(String key, String value) {
SysBizSetting s =
sysBizSettingMapper.selectOne(
@@ -397,6 +595,14 @@
return set.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList());
}
+ private LocalDate parseDate(String date) {
+ try {
+ return LocalDate.parse(date.trim());
+ } catch (Exception ex) {
+ throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "非法日期: " + date);
+ }
+ }
+
private BizVisitReason loadActiveVisitReason(Long id, String uuid) {
BizVisitReason row = bizVisitReasonMapper.selectById(id);
if (row == null || (row.getDelFlag() != null && row.getDelFlag() == 1)) {
@@ -407,6 +613,68 @@
UuidAssert.match(row, uuid);
}
return row;
+ }
+
+ private BizSensitiveWord loadActiveSensitiveWord(Long id, String uuid) {
+ BizSensitiveWord row = bizSensitiveWordMapper.selectById(id);
+ if (row == null || (row.getDelFlag() != null && row.getDelFlag() == 1)) {
+ throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "敏感词不存在");
+ }
+ UuidAssert.match(row, uuid);
+ return row;
+ }
+
+ private void assertSensitiveWordUnique(String word, Long excludeId) {
+ LambdaQueryWrapper<BizSensitiveWord> q =
+ new LambdaQueryWrapper<BizSensitiveWord>()
+ .eq(BizSensitiveWord::getDelFlag, 0)
+ .eq(BizSensitiveWord::getWord, word);
+ if (excludeId != null) {
+ q.ne(BizSensitiveWord::getId, excludeId);
+ }
+ if (bizSensitiveWordMapper.selectCount(q) > 0) {
+ throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "敏感词已存在");
+ }
+ }
+
+ private String normalizeSensitiveWord(String word) {
+ String normalized = trimToNull(word);
+ if (!StringUtils.hasText(normalized)) {
+ throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "敏感词不能为空");
+ }
+ if (normalized.length() > 50) {
+ throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "敏感词不能超过50个字");
+ }
+ return normalized;
+ }
+
+ private Set<String> loadExistingSensitiveWords() {
+ List<BizSensitiveWord> rows =
+ bizSensitiveWordMapper.selectList(
+ new LambdaQueryWrapper<BizSensitiveWord>()
+ .eq(BizSensitiveWord::getDelFlag, 0)
+ .select(BizSensitiveWord::getWord));
+ Set<String> words = new HashSet<>();
+ for (BizSensitiveWord row : rows) {
+ if (StringUtils.hasText(row.getWord())) {
+ words.add(row.getWord().trim());
+ }
+ }
+ return words;
+ }
+
+ private boolean isSensitiveWordHeader(String value) {
+ if (!StringUtils.hasText(value)) {
+ return false;
+ }
+ String text = value.trim();
+ return "敏感词".equals(text) || "敏感词库".equals(text) || "word".equalsIgnoreCase(text);
+ }
+
+ private void addImportError(SensitiveWordImportResultVo result, String message) {
+ if (result.getErrorMessages().size() < 20) {
+ result.getErrorMessages().add(message);
+ }
}
private void assertReasonNameUnique(String reasonName, Long excludeId) {
@@ -422,11 +690,27 @@
}
}
+ private int normalizeSortOrder(Integer sortOrder) {
+ return sortOrder == null ? 0 : Math.max(0, sortOrder);
+ }
+
private VisitReasonListVo toVisitReasonListVo(BizVisitReason e) {
VisitReasonListVo vo = new VisitReasonListVo();
vo.setId(e.getId());
vo.setUuid(e.getUuid());
+ vo.setReasonCode(e.getReasonCode());
vo.setReasonName(e.getReasonName());
+ vo.setSortOrder(e.getSortOrder());
+ vo.setCreateTime(e.getCreateTime());
+ return vo;
+ }
+
+ private SensitiveWordListVo toSensitiveWordListVo(BizSensitiveWord e) {
+ SensitiveWordListVo vo = new SensitiveWordListVo();
+ vo.setId(e.getId());
+ vo.setUuid(e.getUuid());
+ vo.setWord(e.getWord());
+ vo.setEnabled(e.getEnabled());
vo.setCreateTime(e.getCreateTime());
return vo;
}
--
Gitblit v1.7.1