diff --git a/src/main/java/com/rj/controller/FanManagementController.java b/src/main/java/com/rj/controller/LbFanManagementController.java similarity index 69% rename from src/main/java/com/rj/controller/FanManagementController.java rename to src/main/java/com/rj/controller/LbFanManagementController.java index 8370ed1..37c90e3 100644 --- a/src/main/java/com/rj/controller/FanManagementController.java +++ b/src/main/java/com/rj/controller/LbFanManagementController.java @@ -1,7 +1,7 @@ package com.rj.controller; -import com.rj.entity.FanManagement; -import com.rj.service.IFanManagementService; +import com.rj.entity.LbFanManagement; +import com.rj.service.ILbFanManagementService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -12,39 +12,40 @@ import org.springframework.web.bind.annotation.*; import java.util.Map; /** - * 粉丝管理(fan_management)前端控制器 + * 粉丝管理(lb_fan_management)前端控制器 */ @RestController -@RequestMapping("/api/fanManagement") -@Tag(name = "粉丝管理", description = "fan_management 增删改查与分页") -public class FanManagementController { +@RequestMapping("/api/lbFanManagement") +@Tag(name = "粉丝管理", description = "lb_fan_management 增删改查与分页") +public class LbFanManagementController { @Autowired - private IFanManagementService fanManagementService; + private ILbFanManagementService lbFanManagementService; @PostMapping("/add") @Operation(summary = "新增粉丝") public ResponseEntity> add( - @Parameter(description = "粉丝实体(id 必填)", required = true) - @RequestBody FanManagement entity) { - Map result = fanManagementService.add(entity); + @Parameter(description = "粉丝实体(tenant_id、id 必填;id_tenant_id 可省略,自动生成 id_tenantId)", required = true) + @RequestBody LbFanManagement entity) { + Map result = lbFanManagementService.add(entity); return toResponse(result); } @PutMapping("/update") @Operation(summary = "修改粉丝") public ResponseEntity> update( - @Parameter(description = "粉丝实体(id 必填)", required = true) - @RequestBody FanManagement entity) { - Map result = fanManagementService.update(entity); + @Parameter(description = "粉丝实体(tenant_id、id 必填;也可用 id_tenant_id 定位)", required = true) + @RequestBody LbFanManagement entity) { + Map result = lbFanManagementService.update(entity); return toResponse(result); } - @DeleteMapping("/delete/{id}") + @DeleteMapping("/delete") @Operation(summary = "删除粉丝") public ResponseEntity> delete( - @Parameter(description = "粉丝ID", required = true) @PathVariable Long id) { - Map result = fanManagementService.deleteById(id); + @Parameter(description = "唯一ID(id_tenant_id,格式 id_tenantId)", required = true) + @RequestParam String idTenantId) { + Map result = lbFanManagementService.deleteByIdTenantId(idTenantId); return toResponse(result); } @@ -61,7 +62,7 @@ public class FanManagementController { @RequestParam(required = false) String createdAtStart, @Parameter(description = "创建时间止,格式 yyyy-MM-dd HH:mm:ss") @RequestParam(required = false) String createdAtEnd) { - Map result = fanManagementService.pageQuery( + Map result = lbFanManagementService.pageQuery( current, size, nickname, mobile, shareNumMin, shareNumMax, createdAtStart, createdAtEnd); Boolean success = (Boolean) result.get("success"); diff --git a/src/main/java/com/rj/entity/FanManagement.java b/src/main/java/com/rj/entity/LbFanManagement.java similarity index 74% rename from src/main/java/com/rj/entity/FanManagement.java rename to src/main/java/com/rj/entity/LbFanManagement.java index 9a598d5..9dd6d9b 100644 --- a/src/main/java/com/rj/entity/FanManagement.java +++ b/src/main/java/com/rj/entity/LbFanManagement.java @@ -14,28 +14,32 @@ import java.io.Serializable; import java.time.LocalDateTime; /** - * 粉丝管理表 fan_management + * 粉丝管理表 lb_fan_management */ @Data @EqualsAndHashCode(callSuper = false) -@TableName("fan_management") +@TableName("lb_fan_management") @Schema(description = "粉丝管理") @JsonIgnoreProperties(ignoreUnknown = true) -public class FanManagement implements Serializable { +public class LbFanManagement implements Serializable { private static final long serialVersionUID = 1L; /** - * 粉丝 id(与 {@link #tenantId} 组成复合主键 {@code (tenant_id, id)})。 + * 唯一主键,格式:{@code {id}_{tenantId}},如 {@code 98817_xxx-tenant-uuid}。 */ - @TableId(value = "id", type = IdType.INPUT) - @Schema(description = "粉丝ID(复合主键之一,须与 tenantId 一起使用)") - private Long id; + @TableId(value = "id_tenant_id", type = IdType.INPUT) + @Schema(description = "唯一ID(id_tenantId,新增时可不传由服务端生成)") + private String idTenantId; @TableField("tenant_id") @Schema(description = "租户ID") private String tenantId; + @TableField("id") + @Schema(description = "粉丝ID(可跨租户重复)") + private Long id; + @TableField("avatar") @Schema(description = "头像路径") private String avatar; diff --git a/src/main/java/com/rj/mapper/FanManagementMapper.java b/src/main/java/com/rj/mapper/FanManagementMapper.java deleted file mode 100644 index 7a00d67..0000000 --- a/src/main/java/com/rj/mapper/FanManagementMapper.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.rj.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.rj.entity.FanManagement; -import org.apache.ibatis.annotations.Mapper; - -/** - * 粉丝管理表 Mapper - */ -@Mapper -public interface FanManagementMapper extends BaseMapper { -} diff --git a/src/main/java/com/rj/mapper/LbFanManagementMapper.java b/src/main/java/com/rj/mapper/LbFanManagementMapper.java new file mode 100644 index 0000000..885f212 --- /dev/null +++ b/src/main/java/com/rj/mapper/LbFanManagementMapper.java @@ -0,0 +1,12 @@ +package com.rj.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.rj.entity.LbFanManagement; +import org.apache.ibatis.annotations.Mapper; + +/** + * 粉丝管理表 lb_fan_management Mapper + */ +@Mapper +public interface LbFanManagementMapper extends BaseMapper { +} diff --git a/src/main/java/com/rj/service/IFanManagementService.java b/src/main/java/com/rj/service/ILbFanManagementService.java similarity index 66% rename from src/main/java/com/rj/service/IFanManagementService.java rename to src/main/java/com/rj/service/ILbFanManagementService.java index 005ab72..485da5b 100644 --- a/src/main/java/com/rj/service/IFanManagementService.java +++ b/src/main/java/com/rj/service/ILbFanManagementService.java @@ -1,20 +1,20 @@ package com.rj.service; import com.baomidou.mybatisplus.extension.service.IService; -import com.rj.entity.FanManagement; +import com.rj.entity.LbFanManagement; import java.util.Map; /** * 粉丝管理服务 */ -public interface IFanManagementService extends IService { +public interface ILbFanManagementService extends IService { - Map add(FanManagement entity); + Map add(LbFanManagement entity); - Map update(FanManagement entity); + Map update(LbFanManagement entity); - Map deleteById(Long id); + Map deleteByIdTenantId(String idTenantId); Map pageQuery(Integer current, Integer size, diff --git a/src/main/java/com/rj/service/impl/FanManagementServiceImpl.java b/src/main/java/com/rj/service/impl/LbFanManagementServiceImpl.java similarity index 55% rename from src/main/java/com/rj/service/impl/FanManagementServiceImpl.java rename to src/main/java/com/rj/service/impl/LbFanManagementServiceImpl.java index 16d8de5..bd1130c 100644 --- a/src/main/java/com/rj/service/impl/FanManagementServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbFanManagementServiceImpl.java @@ -3,11 +3,12 @@ package com.rj.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.rj.entity.FanManagement; -import com.rj.mapper.FanManagementMapper; -import com.rj.service.IFanManagementService; +import com.rj.entity.LbFanManagement; +import com.rj.mapper.LbFanManagementMapper; +import com.rj.service.ILbFanManagementService; import com.rj.tenant.TenantContextHolder; import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @@ -20,30 +21,38 @@ import java.util.Map; */ @Slf4j @Service -public class FanManagementServiceImpl - extends ServiceImpl - implements IFanManagementService { +public class LbFanManagementServiceImpl + extends ServiceImpl + implements ILbFanManagementService { private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @Override - public Map add(FanManagement entity) { + public Map add(LbFanManagement entity) { Map result = new HashMap<>(); try { - if (entity.getId() == null) { + trimStringFields(entity); + fillTenantIdIfAbsent(entity); + Map keyError = validateCompositeKey(entity); + if (keyError != null) { + return keyError; + } + Map idTenantError = fillAndValidateIdTenantId(entity); + if (idTenantError != null) { + return idTenantError; + } + if (existsByIdTenantId(entity.getIdTenantId())) { result.put("success", false); - result.put("message", "粉丝ID不能为空"); + result.put("message", "唯一ID已存在:" + entity.getIdTenantId()); return result; } - trimStringFields(entity); if (entity.getShareNum() == null) { entity.setShareNum(0); } if (entity.getCreatedAt() == null) { entity.setCreatedAt(LocalDateTime.now()); } - fillTenantIdIfAbsent(entity); boolean ok = this.save(entity); result.put("success", ok); @@ -52,6 +61,11 @@ public class FanManagementServiceImpl result.put("data", entity); } return result; + } catch (DuplicateKeyException e) { + log.warn("粉丝管理新增重复键, idTenantId={}", entity.getIdTenantId()); + result.put("success", false); + result.put("message", "唯一ID已存在:" + entity.getIdTenantId()); + return result; } catch (Exception e) { log.error("粉丝管理新增异常", e); result.put("success", false); @@ -61,22 +75,38 @@ public class FanManagementServiceImpl } @Override - public Map update(FanManagement entity) { + public Map update(LbFanManagement entity) { Map result = new HashMap<>(); try { - if (entity.getId() == null) { - result.put("success", false); - result.put("message", "粉丝ID不能为空"); - return result; - } trimStringFields(entity); fillTenantIdIfAbsent(entity); + Map keyError = validateCompositeKey(entity); + if (keyError != null) { + return keyError; + } + Map idTenantError = fillAndValidateIdTenantId(entity); + if (idTenantError != null) { + return idTenantError; + } + LbFanManagement existing = this.getById(entity.getIdTenantId()); + if (existing == null) { + result.put("success", false); + result.put("message", "记录不存在,id_tenant_id=" + entity.getIdTenantId()); + return result; + } + if (entity.getCreatedAt() == null) { + entity.setCreatedAt(existing.getCreatedAt()); + } + // 唯一主键与联合键字段不可变更 + entity.setIdTenantId(existing.getIdTenantId()); + entity.setTenantId(existing.getTenantId()); + entity.setId(existing.getId()); boolean ok = this.updateById(entity); result.put("success", ok); - result.put("message", ok ? "修改成功" : "修改失败,记录可能不存在"); + result.put("message", ok ? "修改成功" : "修改失败"); if (ok) { - result.put("data", getOneByIdAndTenant(entity.getId(), entity.getTenantId())); + result.put("data", this.getById(entity.getIdTenantId())); } return result; } catch (Exception e) { @@ -88,20 +118,21 @@ public class FanManagementServiceImpl } @Override - public Map deleteById(Long id) { + public Map deleteByIdTenantId(String idTenantId) { Map result = new HashMap<>(); try { - if (id == null) { + if (idTenantId == null || idTenantId.trim().isEmpty()) { result.put("success", false); - result.put("message", "粉丝ID不能为空"); + result.put("message", "唯一ID(id_tenant_id)不能为空"); return result; } - boolean ok = this.removeById(id); + String key = idTenantId.trim(); + boolean ok = this.removeById(key); result.put("success", ok); result.put("message", ok ? "删除成功" : "删除失败,记录可能不存在"); return result; } catch (Exception e) { - log.error("粉丝管理删除异常, id={}", id, e); + log.error("粉丝管理删除异常, idTenantId={}", idTenantId, e); result.put("success", false); result.put("message", "删除异常:" + e.getMessage()); return result; @@ -126,18 +157,18 @@ public class FanManagementServiceImpl size = 10; } - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); if (nickname != null && !nickname.trim().isEmpty()) { - queryWrapper.like(FanManagement::getNickname, nickname.trim()); + queryWrapper.like(LbFanManagement::getNickname, nickname.trim()); } if (mobile != null && !mobile.trim().isEmpty()) { - queryWrapper.like(FanManagement::getMobile, mobile.trim()); + queryWrapper.like(LbFanManagement::getMobile, mobile.trim()); } if (shareNumMin != null) { - queryWrapper.ge(FanManagement::getShareNum, shareNumMin); + queryWrapper.ge(LbFanManagement::getShareNum, shareNumMin); } if (shareNumMax != null) { - queryWrapper.le(FanManagement::getShareNum, shareNumMax); + queryWrapper.le(LbFanManagement::getShareNum, shareNumMax); } LocalDateTime start = parseDateTime(createdAtStart); @@ -153,14 +184,14 @@ public class FanManagementServiceImpl return result; } if (start != null) { - queryWrapper.ge(FanManagement::getCreatedAt, start); + queryWrapper.ge(LbFanManagement::getCreatedAt, start); } if (end != null) { - queryWrapper.le(FanManagement::getCreatedAt, end); + queryWrapper.le(LbFanManagement::getCreatedAt, end); } - queryWrapper.orderByDesc(FanManagement::getCreatedAt); + queryWrapper.orderByDesc(LbFanManagement::getCreatedAt); - Page page = this.page(new Page<>(current, size), queryWrapper); + Page page = this.page(new Page<>(current, size), queryWrapper); result.put("success", true); result.put("message", "查询成功"); result.put("data", page.getRecords()); @@ -177,16 +208,53 @@ public class FanManagementServiceImpl } } - private FanManagement getOneByIdAndTenant(Long id, String tenantId) { - LambdaQueryWrapper q = new LambdaQueryWrapper<>(); - q.eq(FanManagement::getId, id); - if (tenantId != null && !tenantId.trim().isEmpty()) { - q.eq(FanManagement::getTenantId, tenantId.trim()); - } - return this.getOne(q, false); + /** + * 生成唯一ID:id + '_' + tenant_id + */ + public static String buildIdTenantId(Long id, String tenantId) { + return id + "_" + tenantId; } - private void fillTenantIdIfAbsent(FanManagement entity) { + private Map validateCompositeKey(LbFanManagement entity) { + Map result = new HashMap<>(); + if (entity.getTenantId() == null || entity.getTenantId().trim().isEmpty()) { + result.put("success", false); + result.put("message", "租户ID不能为空"); + return result; + } + if (entity.getId() == null) { + result.put("success", false); + result.put("message", "粉丝ID不能为空"); + return result; + } + return null; + } + + /** + * 填充 id_tenant_id,并与传入值校验一致。 + */ + private Map fillAndValidateIdTenantId(LbFanManagement entity) { + Map result = new HashMap<>(); + String expected = buildIdTenantId(entity.getId(), entity.getTenantId()); + if (entity.getIdTenantId() == null || entity.getIdTenantId().trim().isEmpty()) { + entity.setIdTenantId(expected); + return null; + } + String actual = entity.getIdTenantId().trim(); + if (!expected.equals(actual)) { + result.put("success", false); + result.put("message", "id_tenant_id 与 id、tenant_id 不一致,应为:" + expected); + return result; + } + entity.setIdTenantId(actual); + return null; + } + + private boolean existsByIdTenantId(String idTenantId) { + return this.getById(idTenantId) != null; + } + + private void fillTenantIdIfAbsent(LbFanManagement entity) { if (entity.getTenantId() == null || entity.getTenantId().trim().isEmpty()) { String tenantId = TenantContextHolder.getTenantId(); if (tenantId != null && !tenantId.trim().isEmpty()) { @@ -195,7 +263,10 @@ public class FanManagementServiceImpl } } - private void trimStringFields(FanManagement entity) { + private void trimStringFields(LbFanManagement entity) { + if (entity.getIdTenantId() != null) { + entity.setIdTenantId(entity.getIdTenantId().trim()); + } if (entity.getTenantId() != null) { entity.setTenantId(entity.getTenantId().trim()); } diff --git a/src/main/sql/fan_management.sql b/src/main/sql/fan_management.sql deleted file mode 100644 index 3b7482c..0000000 --- a/src/main/sql/fan_management.sql +++ /dev/null @@ -1,22 +0,0 @@ -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; - -DROP TABLE IF EXISTS `fan_management`; -CREATE TABLE `fan_management` ( - `id` BIGINT NOT NULL COMMENT '粉丝ID(外部系统主键,如 98817)', - `tenant_id` VARCHAR(36) NULL DEFAULT NULL COMMENT '租户ID,关联 tenant.id', - `avatar` VARCHAR(512) NULL DEFAULT NULL COMMENT '头像路径', - `nickname` VARCHAR(100) NULL DEFAULT NULL COMMENT '昵称(可为脱敏手机号,如 137****6199)', - `mobile` VARCHAR(32) NULL DEFAULT NULL COMMENT '手机号', - `created_at` DATETIME NULL DEFAULT NULL COMMENT '创建时间', - `share_num` INT NOT NULL DEFAULT 0 COMMENT '分享次数', - PRIMARY KEY (`tenant_id`, `id`) USING BTREE, - KEY `idx_fm_mobile` (`tenant_id`, `mobile`) USING BTREE, - KEY `idx_fm_created_at` (`tenant_id`, `created_at`) USING BTREE -) ENGINE=InnoDB - DEFAULT CHARSET=utf8mb4 - COLLATE=utf8mb4_0900_ai_ci - COMMENT='粉丝管理' - ROW_FORMAT=DYNAMIC; - -SET FOREIGN_KEY_CHECKS = 1; diff --git a/src/main/sql/lb_fan_management.sql b/src/main/sql/lb_fan_management.sql new file mode 100644 index 0000000..4db10aa --- /dev/null +++ b/src/main/sql/lb_fan_management.sql @@ -0,0 +1,24 @@ +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +DROP TABLE IF EXISTS `lb_fan_management`; +CREATE TABLE `lb_fan_management` ( + `id_tenant_id` VARCHAR(128) NOT NULL COMMENT '唯一ID,格式:id_tenantId(如 98817_xxx-tenant-uuid)', + `tenant_id` VARCHAR(36) NOT NULL COMMENT '租户ID', + `id` BIGINT NOT NULL COMMENT '粉丝ID(外部业务ID);仅 id 不唯一,可跨租户重复', + `avatar` VARCHAR(512) NULL DEFAULT NULL COMMENT '头像路径', + `nickname` VARCHAR(100) NULL DEFAULT NULL COMMENT '昵称(可为脱敏手机号,如 137****6199)', + `mobile` VARCHAR(32) NULL DEFAULT NULL COMMENT '手机号', + `created_at` DATETIME NULL DEFAULT NULL COMMENT '创建时间', + `share_num` INT NOT NULL DEFAULT 0 COMMENT '分享次数', + PRIMARY KEY (`id_tenant_id`) USING BTREE, + UNIQUE KEY `uk_lfm_tenant_id` (`tenant_id`, `id`) USING BTREE, + KEY `idx_lfm_mobile` (`tenant_id`, `mobile`) USING BTREE, + KEY `idx_lfm_created_at` (`tenant_id`, `created_at`) USING BTREE +) ENGINE=InnoDB + DEFAULT CHARSET=utf8mb4 + COLLATE=utf8mb4_0900_ai_ci + COMMENT='粉丝管理(唯一主键 id_tenant_id = id + _ + tenant_id)' + ROW_FORMAT=DYNAMIC; + +SET FOREIGN_KEY_CHECKS = 1;