| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- package com.cyksj.service.relation.impl;
- import cn.hutool.core.date.DateUtil;
- import com.cyksj.common.exception.BusinessRuntimeException;
- import com.cyksj.mapper.TicketDurationRepairMapper;
- import com.cyksj.mapper.manage.cms.CmsUserMapper;
- import com.cyksj.mapper.manage.statistics.IncrExpiryTimeRecordMapper;
- import com.cyksj.model.dto.TicketDurationRepairOrder;
- import com.cyksj.model.dto.TicketDurationRepairRow;
- import com.cyksj.model.entity.CmsUser;
- import com.cyksj.model.entity.GroupsRelation;
- import com.cyksj.model.entity.IncrExpiryTimeRecord;
- import com.cyksj.model.request.TicketDurationRepairReq;
- import com.cyksj.model.response.TicketDurationRepairItemResp;
- import com.cyksj.model.response.TicketDurationRepairPreviewResp;
- import com.cyksj.service.relation.TicketDurationRepairService;
- import lombok.RequiredArgsConstructor;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Isolation;
- import org.springframework.transaction.annotation.Transactional;
- import java.time.temporal.ChronoUnit;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.Objects;
- import java.util.Set;
- import java.util.stream.Collectors;
- @Service
- @RequiredArgsConstructor
- public class TicketDurationRepairServiceImpl implements TicketDurationRepairService {
- private final TicketDurationRepairMapper mapper;
- private final IncrExpiryTimeRecordMapper recordMapper;
- private final CmsUserMapper cmsUserMapper;
- @Override
- @Transactional(readOnly = true, isolation = Isolation.REPEATABLE_READ)
- public TicketDurationRepairPreviewResp preview(Long afterId, Integer limit, Long userId) {
- if (afterId == null || afterId < 0 || limit == null || limit < 1 || limit > 200) {
- throw BusinessRuntimeException.getInstance("afterId必须大于等于0,limit必须在1至200之间");
- }
- if (userId != null && userId <= 0) {
- throw BusinessRuntimeException.getInstance("userId必须大于0");
- }
- List<TicketDurationRepairRow> fetched = mapper.selectPage(afterId, limit + 1, userId);
- boolean hasMore = fetched.size() > limit;
- List<TicketDurationRepairRow> rows = hasMore ? fetched.subList(0, limit) : fetched;
- TicketDurationRepairPreviewResp response = new TicketDurationRepairPreviewResp();
- response.setUserId(userId);
- response.setAfterId(afterId);
- response.setNextAfterId(rows.isEmpty() ? afterId : rows.get(rows.size() - 1).getRelationId());
- response.setHasMore(hasMore);
- response.setItems(classifyAll(rows));
- return response;
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public List<TicketDurationRepairItemResp> execute(TicketDurationRepairReq req, Long adminId) {
- validateRequest(req, adminId);
- // Lock in a stable order. The entire batch is validated before the first mutation.
- List<TicketDurationRepairReq.Snapshot> snapshots = new ArrayList<>(req.getItems());
- snapshots.sort(Comparator.comparing(TicketDurationRepairReq.Snapshot::getRelationId));
- List<TicketDurationRepairRow> rows = new ArrayList<>();
- for (TicketDurationRepairReq.Snapshot snapshot : snapshots) {
- TicketDurationRepairRow row = mapper.selectForUpdate(snapshot.getRelationId());
- if (row == null || !matches(row, snapshot)) {
- throw BusinessRuntimeException.getInstance("车票" + snapshot.getRelationId() + "已变化或不属于目标规格,请重新预览");
- }
- rows.add(row);
- }
- List<TicketDurationRepairItemResp> results = classifyAll(rows);
- for (TicketDurationRepairItemResp result : results) {
- if (!"candidate".equals(result.getDecision())) {
- throw BusinessRuntimeException.getInstance("车票" + result.getRelationId() + "不能自动修复:" + result.getReason());
- }
- }
- CmsUser admin = cmsUserMapper.selectById(adminId);
- String operator = admin != null && admin.getNickname() != null
- ? admin.getNickname() : String.valueOf(adminId);
- for (int i = 0; i < rows.size(); i++) {
- TicketDurationRepairRow row = rows.get(i);
- TicketDurationRepairItemResp result = results.get(i);
- Date expiryTime = new Date(result.getNewExpiryTime());
- if (mapper.updateExpiry(row, expiryTime) != 1) {
- throw BusinessRuntimeException.getInstance("车票" + row.getRelationId() + "更新失败,整批修复已回滚");
- }
- IncrExpiryTimeRecord record = new IncrExpiryTimeRecord();
- record.setRelationId(row.getRelationId());
- record.setUserId(row.getUserId());
- record.setExpiryTimeInitial(row.getExpiryTime());
- // Existing audit schema records days; calculate the actual calendar-day difference.
- int days = (int) ChronoUnit.DAYS.between(DateUtil.toLocalDateTime(row.getExpiryTime()).toLocalDate(),
- DateUtil.toLocalDateTime(expiryTime).toLocalDate());
- record.setIncrExpiryDays(days);
- record.setOperator(operator);
- record.setRemark(req.getRemark() != null && !req.getRemark().isBlank()
- ? req.getRemark()
- : "规格772/774半年付时长修复:原到期时间减6个自然月;原到期="
- + DateUtil.formatDateTime(row.getExpiryTime()) + ";新到期=" + DateUtil.formatDateTime(expiryTime));
- if (recordMapper.insert(record) != 1) {
- throw BusinessRuntimeException.getInstance("车票" + row.getRelationId() + "修复记录保存失败,整批修复已回滚");
- }
- result.setDecision("updated");
- result.setReason("已从原到期时间扣减6个自然月");
- }
- return results;
- }
- private List<TicketDurationRepairItemResp> classifyAll(List<TicketDurationRepairRow> rows) {
- if (rows.isEmpty()) {
- return Collections.emptyList();
- }
- List<Long> ids = rows.stream().map(TicketDurationRepairRow::getRelationId).collect(Collectors.toList());
- Map<Long, Map<Long, TicketDurationRepairOrder>> orders = new HashMap<>();
- for (TicketDurationRepairOrder order : mapper.selectOrders(ids)) {
- Map<Long, TicketDurationRepairOrder> byId = orders.computeIfAbsent(order.getRelationId(), key -> new HashMap<>());
- TicketDurationRepairOrder previous = byId.putIfAbsent(order.getOrderId(), order);
- if (previous != null && Boolean.TRUE.equals(order.getSlot())) {
- previous.setSlot(true);
- }
- }
- Set<Long> adjusted = new HashSet<>(mapper.selectAdjustedRelationIds(ids));
- List<TicketDurationRepairItemResp> results = new ArrayList<>();
- for (TicketDurationRepairRow row : rows) {
- results.add(classify(row, new ArrayList<>(orders.getOrDefault(row.getRelationId(), Collections.emptyMap()).values()),
- adjusted.contains(row.getRelationId())));
- }
- return results;
- }
- private TicketDurationRepairItemResp classify(TicketDurationRepairRow row,
- List<TicketDurationRepairOrder> orders, boolean adjusted) {
- TicketDurationRepairItemResp result = new TicketDurationRepairItemResp();
- result.setRelationId(row.getRelationId());
- result.setGroupsId(row.getGroupsId());
- result.setSkuId(row.getSkuId());
- result.setUserId(row.getUserId());
- result.setStartTime(millis(row.getStartTime()));
- result.setExpiryTime(millis(row.getExpiryTime()));
- result.setUpdateTime(millis(row.getUpdateTime()));
- result.setOrderCount(orders.size());
- if (!isTarget(row.getSkuId())) {
- return decision(result, "skipped", "不是规格772或774");
- }
- if (row.getStartTime() == null || row.getExpiryTime() == null) {
- return decision(result, "skipped", "未设置完整起止时间");
- }
- Date expected = DateUtil.offsetMonth(row.getStartTime(), 12);
- long difference = row.getExpiryTime().getTime() - expected.getTime();
- result.setYearDifferenceMillis(difference);
- if (difference != 0) {
- if (Math.abs(difference) <= 60_000 || DateUtil.isSameDay(expected, row.getExpiryTime())) {
- return decision(result, "review", "接近一年但存在时间偏差,需核对");
- }
- return decision(result, "skipped", "已设置的时长不是12个自然月");
- }
- List<String> reasons = new ArrayList<>();
- if (row.getUserId() == null || row.getUserId() <= 0
- || (row.getStatus() != GroupsRelation.Status.validity && row.getStatus() != GroupsRelation.Status.outside)) {
- reasons.add("车票无当前用户或状态无效");
- }
- if (Boolean.TRUE.equals(row.getIsFrozen()) || Boolean.TRUE.equals(row.getIsRecover()) || nonzero(row.getReservedDays())) {
- reasons.add("有冻结/恢复/保留天数");
- }
- if (nonzero(row.getCompensationDays()) || adjusted) {
- reasons.add("有补偿或手工时长调整");
- }
- if (Boolean.TRUE.equals(row.getIsVip()) || !Objects.equals(row.getAqType(), 1)) {
- reasons.add("VIP或非普通购买车票");
- }
- if (orders.size() != 1) {
- reasons.add("有效关联订单不是一笔");
- } else {
- TicketDurationRepairOrder order = orders.get(0);
- if (!Objects.equals(order.getOrderType(), 1)) {
- reasons.add("续费或未知订单类型");
- }
- if (!Objects.equals(order.getSkuId(), row.getSkuId())) {
- reasons.add("订单规格与当前车票不一致");
- }
- if (order.getNum() == null || order.getNum() < 1
- || (order.getNum() > 1 && !Boolean.TRUE.equals(order.getSlot()))) {
- reasons.add("购买数量可能叠加时长");
- }
- }
- if (!reasons.isEmpty()) {
- return decision(result, "review", String.join(";", reasons));
- }
- Date newExpiry = DateUtil.offsetMonth(row.getExpiryTime(), -6);
- result.setNewExpiryTime(newExpiry.getTime());
- result.setExpiredAfterRepair(!newExpiry.after(new Date()));
- return decision(result, "candidate", "可修复:原到期时间减6个自然月");
- }
- private TicketDurationRepairItemResp decision(TicketDurationRepairItemResp result, String decision, String reason) {
- result.setDecision(decision);
- result.setReason(reason);
- return result;
- }
- private boolean matches(TicketDurationRepairRow row, TicketDurationRepairReq.Snapshot snapshot) {
- return Objects.equals(row.getRelationId(), snapshot.getRelationId())
- && Objects.equals(row.getGroupsId(), snapshot.getGroupsId())
- && Objects.equals(row.getSkuId(), snapshot.getSkuId()) && isTarget(row.getSkuId())
- && Objects.equals(row.getUserId(), snapshot.getUserId())
- && Objects.equals(millis(row.getStartTime()), snapshot.getStartTime())
- && Objects.equals(millis(row.getExpiryTime()), snapshot.getExpiryTime())
- && Objects.equals(millis(row.getUpdateTime()), snapshot.getUpdateTime());
- }
- private void validateRequest(TicketDurationRepairReq req, Long adminId) {
- if (adminId == null || adminId <= 0 || req == null || req.getItems() == null
- || req.getItems().isEmpty() || req.getItems().size() > 200) {
- throw BusinessRuntimeException.getInstance("请以后台账号提交1至200张预览中的车票");
- }
- Set<Long> ids = new HashSet<>();
- for (TicketDurationRepairReq.Snapshot item : req.getItems()) {
- if (item == null || item.getRelationId() == null || item.getRelationId() <= 0
- || item.getGroupsId() == null || item.getGroupsId() <= 0
- || item.getUserId() == null || item.getUserId() <= 0 || !isTarget(item.getSkuId())
- || item.getStartTime() == null || item.getExpiryTime() == null) {
- throw BusinessRuntimeException.getInstance("车票快照不完整或规格不属于772/774");
- }
- if (!ids.add(item.getRelationId())) {
- throw BusinessRuntimeException.getInstance("不能重复提交同一张车票");
- }
- }
- }
- private static Long millis(Date date) {
- return date == null ? null : date.getTime();
- }
- private static boolean nonzero(Integer value) {
- return value != null && value != 0;
- }
- private static boolean isTarget(Long skuId) {
- return Objects.equals(skuId, 772L) || Objects.equals(skuId, 774L);
- }
- }
|