package com.ymwk.supervisionserver.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ymwk.supervisionserver.component.PageResult; import com.ymwk.supervisionserver.component.ResponseDTO; import com.ymwk.supervisionserver.component.SmartPageUtil; import com.ymwk.supervisionserver.entity.ApplicationCredentials; import com.ymwk.supervisionserver.mapper.ApplicationCredentialsMapper; import com.ymwk.supervisionserver.model.CredentialsAddRequest; import com.ymwk.supervisionserver.model.CredentialsListRequest; import com.ymwk.supervisionserver.model.CredentialsUpdateRequest; import com.ymwk.supervisionserver.service.ApplicationCredentialsService; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import java.util.List; import java.util.UUID; @Service @Slf4j public class ApplicationCredentialsServiceImpl implements ApplicationCredentialsService { private static final String DEFAULT_OPERATOR = "admin"; private final ApplicationCredentialsMapper credentialsMapper; public ApplicationCredentialsServiceImpl(ApplicationCredentialsMapper credentialsMapper) { this.credentialsMapper = credentialsMapper; } @Override public ResponseDTO> list(CredentialsListRequest request) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.hasText(request.getName()), ApplicationCredentials::getName, request.getName()); wrapper.like(StringUtils.hasText(request.getCredIden()), ApplicationCredentials::getCredIden, request.getCredIden()); wrapper.eq(request.getStatus() != null, ApplicationCredentials::getStatus, request.getStatus()); wrapper.orderByDesc(ApplicationCredentials::getId); Page page = new Page<>(request.getPageNum(), request.getPageSize()); Page resultPage = credentialsMapper.selectPage(page, wrapper); // api_secret 脱敏:保留前4后4,中间以 * 代替 List records = resultPage.getRecords(); records.forEach(record -> record.setApiSecret(maskSecret(record.getApiSecret()))); PageResult pageResult = SmartPageUtil.convert2PageResult(resultPage, records); return ResponseDTO.ok(pageResult); } @Override @Transactional(rollbackFor = Exception.class) public ResponseDTO create(CredentialsAddRequest request) { String name = request.getName().trim(); // name 有唯一索引,先查重给出友好提示 Long dupCount = credentialsMapper.selectCount( new LambdaQueryWrapper().eq(ApplicationCredentials::getName, name)); if (dupCount != null && dupCount > 0) { return ResponseDTO.error(100001, "应用名称已存在:" + name); } ApplicationCredentials entity = new ApplicationCredentials(); entity.setCredIden(uuid32()); entity.setName(name); entity.setApiKey(uuid32()); entity.setApiSecret(uuid32()); entity.setHmacAlgo(ApplicationCredentials.HMAC_ALGO); entity.setStatus(ApplicationCredentials.STATUS_ENABLED); entity.setDescription(request.getDescription()); entity.setCreatedBy(DEFAULT_OPERATOR); entity.setUpdatedBy(DEFAULT_OPERATOR); try { credentialsMapper.insert(entity); } catch (DuplicateKeyException e) { // 并发下撞唯一索引(credIden/apiKey 理论上不可能重复,name 则已在前面查过) log.warn("创建应用凭证撞唯一索引: name={}", name); return ResponseDTO.error(100001, "应用名称已存在:" + name); } log.info("应用凭证创建成功: id={}, name={}, credIden={}", entity.getId(), entity.getName(), entity.getCredIden()); // 返回内容中秘密同样脱敏,需要明文走专用查询接口 entity.setApiSecret(maskSecret(entity.getApiSecret())); return ResponseDTO.ok(entity); } @Override public ResponseDTO delete(Long id) { if (id == null) { return ResponseDTO.error(100001, "凭证ID不能为空"); } ApplicationCredentials entity = credentialsMapper.selectById(id); if (entity == null) { return ResponseDTO.error(100001, "凭证不存在"); } credentialsMapper.deleteById(id); log.info("应用凭证删除成功: id={}, name={}", id, entity.getName()); return ResponseDTO.okMsg("删除成功"); } @Override @Transactional(rollbackFor = Exception.class) public ResponseDTO update(CredentialsUpdateRequest request) { if (request == null || request.getId() == null) { return ResponseDTO.error(100001, "凭证ID不能为空"); } ApplicationCredentials entity = credentialsMapper.selectById(request.getId()); if (entity == null) { return ResponseDTO.error(100001, "凭证不存在"); } String name = request.getName().trim(); // name 有唯一索引,查重时排除自己 Long dupCount = credentialsMapper.selectCount( new LambdaQueryWrapper() .eq(ApplicationCredentials::getName, name) .ne(ApplicationCredentials::getId, request.getId())); if (dupCount != null && dupCount > 0) { return ResponseDTO.error(100001, "应用名称已存在:" + name); } entity.setName(name); entity.setDescription(request.getDescription()); entity.setUpdatedBy(DEFAULT_OPERATOR); try { credentialsMapper.updateById(entity); } catch (DuplicateKeyException e) { log.warn("修改应用凭证撞唯一索引: id={}, name={}", request.getId(), name); return ResponseDTO.error(100001, "应用名称已存在:" + name); } log.info("应用凭证修改成功: id={}, name={}", entity.getId(), name); entity.setApiSecret(maskSecret(entity.getApiSecret())); return ResponseDTO.ok(entity); } @Override @Transactional(rollbackFor = Exception.class) public ResponseDTO resetSecret(Long id) { if (id == null) { return ResponseDTO.error(100001, "凭证ID不能为空"); } ApplicationCredentials entity = credentialsMapper.selectById(id); if (entity == null) { return ResponseDTO.error(100001, "凭证不存在"); } entity.setApiSecret(uuid32()); entity.setUpdatedBy(DEFAULT_OPERATOR); credentialsMapper.updateById(entity); log.info("应用凭证密钥已重新生成: id={}, name={}", id, entity.getName()); entity.setApiSecret(maskSecret(entity.getApiSecret())); return ResponseDTO.ok(entity, "api_secret 已重新生成,请通过专用查询接口获取明文"); } @Override public ResponseDTO getSecret(Long id) { if (id == null) { return ResponseDTO.error(100001, "凭证ID不能为空"); } ApplicationCredentials entity = credentialsMapper.selectById(id); if (entity == null) { return ResponseDTO.error(100001, "凭证不存在"); } return ResponseDTO.ok(entity.getApiSecret()); } @Override @Transactional(rollbackFor = Exception.class) public ResponseDTO changeStatus(Long id, Integer status) { if (id == null) { return ResponseDTO.error(100001, "凭证ID不能为空"); } if (!ApplicationCredentials.STATUS_ENABLED.equals(status) && !ApplicationCredentials.STATUS_DISABLED.equals(status)) { return ResponseDTO.error(100001, "状态值只支持 1(启用)或 2(禁用)"); } ApplicationCredentials entity = credentialsMapper.selectById(id); if (entity == null) { return ResponseDTO.error(100001, "凭证不存在"); } if (!status.equals(entity.getStatus())) { entity.setStatus(status); entity.setUpdatedBy(DEFAULT_OPERATOR); credentialsMapper.updateById(entity); } entity.setApiSecret(maskSecret(entity.getApiSecret())); return ResponseDTO.ok(entity); } /** * api_secret 脱敏:保留前4后4,中间替换为 * */ private String maskSecret(String secret) { if (secret == null) { return null; } int length = secret.length(); if (length <= 8) { return "*".repeat(length); } return secret.substring(0, 4) + "*".repeat(length - 8) + secret.substring(length - 4); } /** * 生成32位无连字符大写UUID */ private String uuid32() { return UUID.randomUUID().toString().replace("-", "").toUpperCase(); } }