feat(config): 接入 Nacos 配置中心
- 新增 AppConfig、NacosAppConfigCenter 动态配置类 - 将 userRegisterProperties 的默认值改为运行时从 Nacos 读取 - 注册/创建用户时免费配额改为动态配置获取 - 增加 nacos-client 依赖并配置 dev 环境连接信息
This commit is contained in:
7
pom.xml
7
pom.xml
@@ -55,6 +55,13 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-client</artifactId>
|
||||
<version>3.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- qdrant向量数据库 sdk -->
|
||||
<dependency>
|
||||
<groupId>io.qdrant</groupId>
|
||||
|
||||
24
src/main/java/com/yolo/keyborad/config/AppConfig.java
Normal file
24
src/main/java/com/yolo/keyborad/config/AppConfig.java
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,6 @@ public class UserRegisterProperties {
|
||||
/**
|
||||
* 新用户注册时的免费使用次数
|
||||
*/
|
||||
private Integer freeTrialQuota = 5;
|
||||
private Integer freeTrialQuota;
|
||||
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -5,27 +5,27 @@ spring:
|
||||
username: root
|
||||
password: 123asd
|
||||
|
||||
# 日志配置
|
||||
# ????
|
||||
logging:
|
||||
level:
|
||||
# 设置 mapper 包的日志级别为 DEBUG,打印 SQL 语句
|
||||
# ?? mapper ??????? DEBUG??? SQL ??
|
||||
com.yolo.keyborad.mapper: DEBUG
|
||||
# 设置根日志级别
|
||||
# ???????
|
||||
root: INFO
|
||||
# Spring 框架日志
|
||||
# Spring ????
|
||||
org.springframework: INFO
|
||||
# MyBatis 日志
|
||||
# MyBatis ??
|
||||
org.mybatis: DEBUG
|
||||
pattern:
|
||||
# 彩色控制台日志格式
|
||||
# 时间-无颜色,日志级别-根据级别变色,进程ID-品红,线程-黄色,类名-青色,消息-默认色
|
||||
# ?????????
|
||||
# ??-????????-?????????ID-?????-?????-?????-???
|
||||
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} | %clr(%-5level){highlight} %clr(${PID:- }){magenta} | %clr(%-15thread){yellow} %clr(%-50logger{50}){cyan} | %msg%n"
|
||||
|
||||
|
||||
knife4j:
|
||||
enable: true
|
||||
openapi:
|
||||
title: "接口文档"
|
||||
title: "????"
|
||||
version: 1.0
|
||||
group:
|
||||
default:
|
||||
@@ -38,65 +38,71 @@ apple:
|
||||
issuer-id: "178b442e-b7be-4526-bd13-ab293d019df0"
|
||||
key-id: "Y7TF7BV74G"
|
||||
bundle-id: "com.loveKey.nyx"
|
||||
# app 在 App Store 的 Apple ID(数值),生产环境必填
|
||||
# app ? App Store ? Apple ID???????????
|
||||
app-apple-id: 1234567890
|
||||
|
||||
# p8 私钥文件路径(你可以放在 resources 下)
|
||||
# p8 ???????????? resources ??
|
||||
private-key-path: "classpath:SubscriptionKey_Y7TF7BV74G.p8"
|
||||
|
||||
# SANDBOX 或 PRODUCTION
|
||||
# SANDBOX ? PRODUCTION
|
||||
environment: "SANDBOX"
|
||||
|
||||
# 根证书路径(从 Apple PKI 下载)
|
||||
# ??????? Apple PKI ???
|
||||
root-certificates:
|
||||
- "classpath:AppleRootCA-G2.cer"
|
||||
- "classpath:AppleRootCA-G3.cer"
|
||||
|
||||
dromara:
|
||||
x-file-storage: #文件存储配置
|
||||
default-platform: cloudflare-r2 #默认使用的存储平台
|
||||
thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】
|
||||
x-file-storage: #??????
|
||||
default-platform: cloudflare-r2 #?????????
|
||||
thumbnail-suffix: ".min.jpg" #?????????.min.jpg??.png?
|
||||
enable-byte-file-wrapper: false
|
||||
#对应平台的配置写在这里,注意缩进要对齐
|
||||
#???????????????????
|
||||
amazon-s3-v2: # Amazon S3 V2
|
||||
- platform: cloudflare-r2 # 存储平台标识
|
||||
enable-storage: true # 启用存储
|
||||
- platform: cloudflare-r2 # ??????
|
||||
enable-storage: true # ????
|
||||
access-key: 550b33cc4d53e05c2e438601f8a0e209
|
||||
secret-key: df4d529cdae44e6f614ca04f4dc0f1f9a299e57367181243e8abdc7f7c28e99a
|
||||
region: ENAM # 必填
|
||||
end-point: https://b632a61caa85401f63c9b32eef3a74c8.r2.cloudflarestorage.com # 必填
|
||||
bucket-name: keyborad-resource #桶名称
|
||||
domain: https://resource.loveamorkey.com/ # 访问域名,注意“/”结尾,例如:https://abcd.s3.ap-east-1.amazonaws.com/
|
||||
base-path: avatar/ # 基础路径
|
||||
region: ENAM # ??
|
||||
end-point: https://b632a61caa85401f63c9b32eef3a74c8.r2.cloudflarestorage.com # ??
|
||||
bucket-name: keyborad-resource #???
|
||||
domain: https://resource.loveamorkey.com/ # ????????/???????https://abcd.s3.ap-east-1.amazonaws.com/
|
||||
base-path: avatar/ # ????
|
||||
|
||||
|
||||
mailgun:
|
||||
api-key: ${MAILGUN_API_KEY} # 你的 Private API Key
|
||||
domain: sandboxxxxxxx.mailgun.org # 或你自己的业务域名
|
||||
from-email: no-reply@yourdomain.com # 发件人邮箱
|
||||
from-name: Key Of Love # 发件人名称(可选)
|
||||
api-key: ${MAILGUN_API_KEY} # ?? Private API Key
|
||||
domain: sandboxxxxxxx.mailgun.org # ?????????
|
||||
from-email: no-reply@yourdomain.com # ?????
|
||||
from-name: Key Of Love # ?????????
|
||||
|
||||
# 用户注册配置
|
||||
# ??????
|
||||
user:
|
||||
register:
|
||||
# 新用户注册时的免费使用次数
|
||||
# ?????????????
|
||||
free-trial-quota: 5
|
||||
|
||||
|
||||
|
||||
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
|
||||
############## Sa-Token ?? (??: https://sa-token.cc) ##############
|
||||
sa-token:
|
||||
# token 名称(同时也是 cookie 名称)
|
||||
# token ??????? cookie ???
|
||||
token-name: auth-token
|
||||
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
|
||||
# token ????????? ??30??-1 ??????
|
||||
timeout: 2592000
|
||||
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
|
||||
# token ??????????????? token ???????????????????-1 ??????????
|
||||
active-timeout: -1
|
||||
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
|
||||
# ?????????????? ?? true ???????, ? false ??????????
|
||||
is-concurrent: true
|
||||
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
|
||||
# ????????????????? token ?? true ????????? token, ? false ????????? token?
|
||||
is-share: false
|
||||
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
|
||||
# token ?????????uuid?simple-uuid?random-32?random-64?random-128?tik?
|
||||
token-style: random-128
|
||||
# 是否输出操作日志
|
||||
# ????????
|
||||
is-log: true
|
||||
|
||||
nacos:
|
||||
config:
|
||||
server-addr: 127.0.0.1:8848
|
||||
group: DEFAULT_GROUP
|
||||
data-id: keyboard_default-config.yaml
|
||||
@@ -5,32 +5,32 @@ spring:
|
||||
username: root
|
||||
password: 123asd
|
||||
|
||||
# 生产环境日志配置
|
||||
# ????????
|
||||
logging:
|
||||
level:
|
||||
# 生产环境不打印 SQL 日志
|
||||
# ??????? SQL ??
|
||||
com.yolo.keyborad.mapper: INFO
|
||||
# 设置根日志级别
|
||||
# ???????
|
||||
root: INFO
|
||||
# Spring 框架日志
|
||||
# Spring ????
|
||||
org.springframework: WARN
|
||||
# MyBatis 日志
|
||||
# MyBatis ??
|
||||
org.mybatis: WARN
|
||||
pattern:
|
||||
# 生产环境控制台日志格式
|
||||
# ???????????
|
||||
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} | %clr(%-5level){highlight} %clr(${PID:- }){magenta} | %clr(%-15thread){yellow} %clr(%-50logger{50}){cyan} | %msg%n"
|
||||
# 文件日志格式(无颜色代码)
|
||||
# ?????????????
|
||||
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} | %-5level ${PID:- } | %-15thread %-50logger{50} | %msg%n"
|
||||
file:
|
||||
# 生产环境日志文件路径
|
||||
# ??????????
|
||||
name: logs/keyborad-backend.log
|
||||
# 日志文件大小限制
|
||||
# ????????
|
||||
max-size: 10MB
|
||||
# 保留的日志文件数量
|
||||
# ?????????
|
||||
max-history: 30
|
||||
|
||||
# 用户注册配置
|
||||
# ??????
|
||||
user:
|
||||
register:
|
||||
# 新用户注册时的免费使用次数
|
||||
# ?????????????
|
||||
free-trial-quota: 5
|
||||
|
||||
@@ -18,17 +18,17 @@ spring:
|
||||
mvc:
|
||||
pathmatch:
|
||||
matching-strategy: ANT_PATH_MATCHER
|
||||
# session 失效时间(分钟)
|
||||
# session ????????
|
||||
session:
|
||||
timeout: 86400
|
||||
store-type: redis
|
||||
# redis 配置
|
||||
# redis ??
|
||||
data:
|
||||
redis:
|
||||
port: 6379
|
||||
host: localhost
|
||||
database: 0
|
||||
# 启用 ANSI 彩色输出
|
||||
# ?? ANSI ????
|
||||
output:
|
||||
ansi:
|
||||
enabled: always
|
||||
@@ -47,10 +47,10 @@ mybatis-plus:
|
||||
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
|
||||
global-config:
|
||||
db-config:
|
||||
logic-delete-field: isDelete # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
|
||||
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
|
||||
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
|
||||
# 扫描 TypeHandler 包
|
||||
logic-delete-field: isDelete # ????????????(since 3.3.0,????????????2)
|
||||
logic-delete-value: 1 # ??????(??? 1)
|
||||
logic-not-delete-value: 0 # ??????(??? 0)
|
||||
# ?? TypeHandler ?
|
||||
type-handlers-package: com.yolo.keyborad.typehandler
|
||||
|
||||
appid: loveKeyboard
|
||||
|
||||
Reference in New Issue
Block a user