补充用户默认酒店唯一约束

This commit is contained in:
andy
2026-07-10 16:19:43 +08:00
parent 8484b44e87
commit 4745e20b9a
5 changed files with 237 additions and 6 deletions

View File

@@ -10,6 +10,7 @@ import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.Optional;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
/**
@@ -75,10 +76,10 @@ public class MybatisPlatformHotelRepository implements PlatformHotelRepository {
@Override
public void ensureUserHotel(Long userId, String hotelId, boolean defaultHotel) {
PlatformUserHotelEntity existing = userHotelMapper.selectOne(Wrappers.<PlatformUserHotelEntity>lambdaQuery()
.eq(PlatformUserHotelEntity::getUserId, userId)
.eq(PlatformUserHotelEntity::getHotelId, hotelId)
.last("LIMIT 1"));
if (defaultHotel) {
clearOtherDefaultHotels(userId, hotelId);
}
PlatformUserHotelEntity existing = findUserHotel(userId, hotelId).orElse(null);
if (existing != null) {
if (defaultHotel && !Boolean.TRUE.equals(existing.getDefaultHotel())) {
existing.setDefaultHotel(true);
@@ -91,7 +92,39 @@ public class MybatisPlatformHotelRepository implements PlatformHotelRepository {
relation.setHotelId(hotelId);
relation.setDefaultHotel(defaultHotel);
relation.setCreatedAt(nowUtc());
userHotelMapper.insert(relation);
try {
userHotelMapper.insert(relation);
} catch (DuplicateKeyException ignored) {
// 多实例并发初始化时唯一索引已经保证授权关系存在,重复插入后只需补齐默认酒店状态。
findUserHotel(userId, hotelId).ifPresent(concurrentExisting -> {
if (defaultHotel && !Boolean.TRUE.equals(concurrentExisting.getDefaultHotel())) {
concurrentExisting.setDefaultHotel(true);
userHotelMapper.updateById(concurrentExisting);
}
});
}
}
/**
* 查询单个用户酒店授权关系,用于写入前检查和并发重复插入后的状态补齐。
*/
private Optional<PlatformUserHotelEntity> findUserHotel(Long userId, String hotelId) {
return Optional.ofNullable(userHotelMapper.selectOne(Wrappers.<PlatformUserHotelEntity>lambdaQuery()
.eq(PlatformUserHotelEntity::getUserId, userId)
.eq(PlatformUserHotelEntity::getHotelId, hotelId)
.last("LIMIT 1")));
}
/**
* 清理同一用户其他默认酒店标记,保证普通用户默认酒店在写入逻辑上保持唯一。
*/
private void clearOtherDefaultHotels(Long userId, String hotelId) {
PlatformUserHotelEntity update = new PlatformUserHotelEntity();
update.setDefaultHotel(false);
userHotelMapper.update(update, Wrappers.<PlatformUserHotelEntity>lambdaUpdate()
.eq(PlatformUserHotelEntity::getUserId, userId)
.ne(PlatformUserHotelEntity::getHotelId, hotelId)
.eq(PlatformUserHotelEntity::getDefaultHotel, true));
}
private LocalDateTime nowUtc() {

View File

@@ -0,0 +1,24 @@
-- M003 登录权限底座:数据库级约束普通用户只能有一个默认酒店。
-- 上线前如已有脏数据,同一用户多个默认酒店时保留 id 最大的一条,其余改为非默认。
UPDATE platform_user_hotel
SET default_hotel = 0
WHERE default_hotel = 1
AND id NOT IN (
SELECT keep_id
FROM (
SELECT MAX(id) AS keep_id
FROM platform_user_hotel
WHERE default_hotel = 1
GROUP BY user_id
) kept_defaults
);
-- default_hotel_user_id 是默认酒店唯一约束辅助字段default_hotel=1 时等于用户 ID否则为空。
ALTER TABLE platform_user_hotel
ADD COLUMN default_hotel_user_id BIGINT GENERATED ALWAYS AS (
CASE WHEN default_hotel = 1 THEN user_id ELSE NULL END
);
-- MySQL 唯一索引允许多个 NULL因此只会限制同一用户最多一条 default_hotel=1。
CREATE UNIQUE INDEX uk_platform_user_hotel_one_default
ON platform_user_hotel (default_hotel_user_id);