UserEmailQuotaService.java

package org.darkroomlibrary.service.impl;

import jakarta.annotation.Resource;
import org.darkroomlibrary.mapper.UserEmailQuotaMapper;
import org.darkroomlibrary.mapper.UserMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class UserEmailQuotaService {

    public static final int MAX_ACCOUNTS_PER_EMAIL = 3;
    public static final String LIMIT_MESSAGE = "同一邮箱最多关联 3 个账号,请更换邮箱";

    @Resource
    private UserEmailQuotaMapper quotaMapper;

    @Resource
    private UserMapper userMapper;

    public String normalize(String email) {
        if (email == null || email.trim().isEmpty()) {
            return null;
        }
        return email.trim().toLowerCase(Locale.ROOT);
    }

    @Transactional(propagation = Propagation.MANDATORY)
    public boolean reserveNewAccount(String email) {
        String normalized = requireEmail(email);
        if (initializeAndLockQuota(normalized) >= MAX_ACCOUNTS_PER_EMAIL) {
            return false;
        }
        return quotaMapper.incrementIfBelowLimit(normalized, MAX_ACCOUNTS_PER_EMAIL) == 1;
    }

    @Transactional(propagation = Propagation.MANDATORY)
    public boolean moveAccount(String currentEmail, String requestedEmail) {
        String current = normalize(currentEmail);
        String requested = requireEmail(requestedEmail);
        if (Objects.equals(current, requested)) {
            return true;
        }

        List<String> lockOrder = java.util.stream.Stream.of(current, requested)
                .filter(Objects::nonNull)
                .distinct()
                .sorted()
                .toList();
        Map<String, Integer> lockedCounts = new java.util.LinkedHashMap<>();
        for (String email : lockOrder) {
            lockedCounts.put(email, initializeAndLockQuota(email));
        }

        if (lockedCounts.getOrDefault(requested, 0) >= MAX_ACCOUNTS_PER_EMAIL) {
            return false;
        }
        if (current != null && quotaMapper.decrement(current, 1) != 1) {
            throw new IllegalStateException("邮箱账号配额与用户数据不一致");
        }
        if (quotaMapper.incrementIfBelowLimit(requested, MAX_ACCOUNTS_PER_EMAIL) != 1) {
            throw new IllegalStateException("邮箱账号配额并发更新失败");
        }
        return true;
    }

    @Transactional(propagation = Propagation.MANDATORY)
    public void releaseAccounts(Collection<String> emails) {
        Map<String, Long> releaseCounts = emails.stream()
                .map(this::normalize)
                .filter(Objects::nonNull)
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
        List<String> lockOrder = releaseCounts.keySet().stream().sorted().toList();

        for (String email : lockOrder) {
            initializeAndLockQuota(email);
        }
        for (String email : lockOrder) {
            int amount = Math.toIntExact(releaseCounts.get(email));
            if (quotaMapper.decrement(email, amount) != 1) {
                throw new IllegalStateException("邮箱账号配额与待删除用户不一致");
            }
        }
    }

    private int lockCount(String email) {
        Integer count = quotaMapper.findCountForUpdate(email);
        if (count == null) {
            throw new IllegalStateException("邮箱账号配额初始化失败");
        }
        return count;
    }

    private int initializeAndLockQuota(String email) {
        quotaMapper.ensureExists(email);
        int lockedCount = lockCount(email);
        if (lockedCount > 0) {
            return lockedCount;
        }
        int existingAccounts = Math.min(
                userMapper.countByNormalizedEmail(email),
                MAX_ACCOUNTS_PER_EMAIL);
        if (existingAccounts > 0 && quotaMapper.setCount(email, existingAccounts) != 1) {
            throw new IllegalStateException("邮箱账号配额初始化失败");
        }
        return existingAccounts;
    }

    private String requireEmail(String email) {
        String normalized = normalize(email);
        if (normalized == null) {
            throw new IllegalArgumentException("邮箱不能为空");
        }
        return normalized;
    }
}