feat(config): 接入 Nacos 配置中心

- 新增 AppConfig、NacosAppConfigCenter 动态配置类
- 将 userRegisterProperties 的默认值改为运行时从 Nacos 读取
- 注册/创建用户时免费配额改为动态配置获取
- 增加 nacos-client 依赖并配置 dev 环境连接信息
This commit is contained in:
2025-12-16 21:50:00 +08:00
parent f95762138b
commit 8e26488738
9 changed files with 195 additions and 69 deletions

View File

@@ -0,0 +1,24 @@
package com.yolo.keyborad.config;
import lombok.Data;
/*
* @author: ziin
* @date: 2025/12/16 21:18
*/
@Data
public class AppConfig {
private UserRegisterProperties userRegisterProperties = new UserRegisterProperties();
@Data
public static class UserRegisterProperties {
/**
* 新用户注册时的免费使用次数
*/
private Integer freeTrialQuota = 5;
}
}

View File

@@ -0,0 +1,82 @@
package com.yolo.keyborad.config;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
@Slf4j
@Configuration
public class NacosAppConfigCenter {
private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
@Bean
public ConfigService nacosConfigService(
@Value("${nacos.config.server-addr}") String serverAddr
) throws NacosException {
Properties p = new Properties();
p.put("serverAddr", serverAddr);
return NacosFactory.createConfigService(p);
}
@Bean
public DynamicAppConfig dynamicAppConfig(
ConfigService configService,
@Value("${nacos.config.group}") String group,
@Value("${nacos.config.data-id}") String dataId
) throws Exception {
DynamicAppConfig holder = new DynamicAppConfig();
// 启动先拉一次
String content = configService.getConfig(dataId, group, 3000);
if (content != null && !content.isBlank()) {
holder.ref.set(parse(content));
log.info("Loaded nacos config: dataId={}, group={}", dataId, group);
} else {
log.warn("Empty nacos config: dataId={}, group={}", dataId, group);
}
// 监听热更新
configService.addListener(dataId, group, new Listener() {
@Override public Executor getExecutor() { return null; }
@Override public void receiveConfigInfo(String configInfo) {
try {
AppConfig newCfg = parse(configInfo);
holder.ref.set(newCfg);
log.info("Refreshed nacos config: dataId={}, group={}", dataId, group);
log.info("New config: {}", newCfg.toString());
} catch (Exception e) {
// 解析失败不覆盖旧配置
log.error("Failed to refresh nacos config: dataId={}, keep old config.", dataId, e);
}
}
});
return holder;
}
private AppConfig parse(String yaml) throws Exception {
if (yaml == null || yaml.isBlank()) return new AppConfig();
return yamlMapper.readValue(yaml, AppConfig.class);
}
@Getter
public static class DynamicAppConfig {
private final AtomicReference<AppConfig> ref = new AtomicReference<>(new AppConfig());
}
}

View File

@@ -15,6 +15,6 @@ public class UserRegisterProperties {
/**
* 新用户注册时的免费使用次数
*/
private Integer freeTrialQuota = 5;
private Integer freeTrialQuota;
}

View File

@@ -94,8 +94,7 @@ public class UserController {
@PostMapping("/register")
@Operation(summary = "用户注册",description = "用户注册接口")
public BaseResponse<Boolean> register(@RequestBody UserRegisterDTO userRegisterDTO) {
userService.userRegister(userRegisterDTO);
return ResultUtils.success(true);
return ResultUtils.success(userService.userRegister(userRegisterDTO));
}
@PostMapping("/sendVerifyMail")

View File

@@ -8,16 +8,16 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yolo.keyborad.common.ErrorCode;
import com.yolo.keyborad.config.AppConfig;
import com.yolo.keyborad.config.NacosAppConfigCenter;
import com.yolo.keyborad.config.UserRegisterProperties;
import com.yolo.keyborad.exception.BusinessException;
import com.yolo.keyborad.mapper.KeyboardUserMapper;
import com.yolo.keyborad.model.dto.user.*;
import com.yolo.keyborad.model.entity.KeyboardUser;
import com.yolo.keyborad.model.entity.KeyboardUserWallet;
import com.yolo.keyborad.model.vo.user.KeyboardUserRespVO;
import com.yolo.keyborad.service.KeyboardCharacterService;
import com.yolo.keyborad.service.KeyboardUserLoginLogService;
import com.yolo.keyborad.service.KeyboardUserWalletService;
import com.yolo.keyborad.service.UserService;
import com.yolo.keyborad.service.*;
import jakarta.servlet.http.HttpServletRequest;
import com.yolo.keyborad.utils.RedisUtil;
import com.yolo.keyborad.utils.SendMailUtils;
@@ -61,10 +61,16 @@ public class UserServiceImpl extends ServiceImpl<KeyboardUserMapper, KeyboardUse
private KeyboardUserLoginLogService loginLogService;
@Resource
private com.yolo.keyborad.service.KeyboardUserQuotaTotalService quotaTotalService;
private KeyboardUserQuotaTotalService quotaTotalService;
@Resource
private com.yolo.keyborad.config.UserRegisterProperties userRegisterProperties;
private UserRegisterProperties userRegisterProperties;
private final NacosAppConfigCenter.DynamicAppConfig cfgHolder;
public UserServiceImpl(NacosAppConfigCenter.DynamicAppConfig cfgHolder) {
this.cfgHolder = cfgHolder;
}
@Override
public KeyboardUser selectUserWithSubjectId(String sub) {
@@ -97,7 +103,8 @@ public class UserServiceImpl extends ServiceImpl<KeyboardUserMapper, KeyboardUse
com.yolo.keyborad.model.entity.KeyboardUserQuotaTotal quotaTotal =
new com.yolo.keyborad.model.entity.KeyboardUserQuotaTotal();
quotaTotal.setUserId(keyboardUser.getId());
quotaTotal.setTotalQuota(userRegisterProperties.getFreeTrialQuota());
AppConfig appConfig = cfgHolder.getRef().get();
quotaTotal.setTotalQuota(appConfig.getUserRegisterProperties().getFreeTrialQuota());
quotaTotal.setUsedQuota(0);
quotaTotal.setVersion(0);
quotaTotal.setCreatedAt(new Date());
@@ -105,7 +112,7 @@ public class UserServiceImpl extends ServiceImpl<KeyboardUserMapper, KeyboardUse
quotaTotalService.save(quotaTotal);
log.info("User registered with Apple Sign-In, userId={}, freeQuota={}",
keyboardUser.getId(), userRegisterProperties.getFreeTrialQuota());
keyboardUser.getId(), appConfig.getUserRegisterProperties().getFreeTrialQuota());
return keyboardUser;
}
@@ -235,7 +242,8 @@ public class UserServiceImpl extends ServiceImpl<KeyboardUserMapper, KeyboardUse
com.yolo.keyborad.model.entity.KeyboardUserQuotaTotal quotaTotal =
new com.yolo.keyborad.model.entity.KeyboardUserQuotaTotal();
quotaTotal.setUserId(keyboardUser.getId());
quotaTotal.setTotalQuota(userRegisterProperties.getFreeTrialQuota());
AppConfig appConfig = cfgHolder.getRef().get();
quotaTotal.setTotalQuota(appConfig.getUserRegisterProperties().getFreeTrialQuota());
quotaTotal.setUsedQuota(0);
quotaTotal.setVersion(0);
quotaTotal.setCreatedAt(new Date());
@@ -243,7 +251,7 @@ public class UserServiceImpl extends ServiceImpl<KeyboardUserMapper, KeyboardUse
quotaTotalService.save(quotaTotal);
log.info("User registered with email, userId={}, email={}, freeQuota={}",
keyboardUser.getId(), keyboardUser.getEmail(), userRegisterProperties.getFreeTrialQuota());
keyboardUser.getId(), keyboardUser.getEmail(), appConfig.getUserRegisterProperties().getFreeTrialQuota());
}
return insertCount > 0;
}