完成app接口国际化
This commit is contained in:
27
CLAUDE.md
27
CLAUDE.md
@@ -63,6 +63,7 @@ com.corewing.app/
|
|||||||
│ └── Result.java # 统一返回结果类
|
│ └── Result.java # 统一返回结果类
|
||||||
├── config/ # 配置类
|
├── config/ # 配置类
|
||||||
│ ├── DruidConfig.java # Druid 数据源监控配置
|
│ ├── DruidConfig.java # Druid 数据源监控配置
|
||||||
|
│ ├── I18nConfig.java # 国际化配置
|
||||||
│ ├── MybatisPlusConfig.java # MyBatis-Plus 配置
|
│ ├── MybatisPlusConfig.java # MyBatis-Plus 配置
|
||||||
│ ├── RedisConfig.java # Redis 配置
|
│ ├── RedisConfig.java # Redis 配置
|
||||||
│ └── SaTokenConfig.java # Sa-Token 权限配置
|
│ └── SaTokenConfig.java # Sa-Token 权限配置
|
||||||
@@ -87,6 +88,7 @@ com.corewing.app/
|
|||||||
│ └── VerifyCodeServiceImpl.java # 验证码服务实现
|
│ └── VerifyCodeServiceImpl.java # 验证码服务实现
|
||||||
└── util/ # 工具类
|
└── util/ # 工具类
|
||||||
├── EmailUtil.java # 邮件发送工具类
|
├── EmailUtil.java # 邮件发送工具类
|
||||||
|
├── I18nUtil.java # 国际化工具类
|
||||||
├── IpUtil.java # IP 工具类
|
├── IpUtil.java # IP 工具类
|
||||||
├── RedisUtil.java # Redis 工具类
|
├── RedisUtil.java # Redis 工具类
|
||||||
└── SmsBaoUtil.java # 短信宝工具类
|
└── SmsBaoUtil.java # 短信宝工具类
|
||||||
@@ -103,6 +105,10 @@ com.corewing.app/
|
|||||||
- 短信宝配置
|
- 短信宝配置
|
||||||
- 邮件配置
|
- 邮件配置
|
||||||
|
|
||||||
|
### 资源文件
|
||||||
|
- `src/main/resources/i18n/messages_zh_CN.properties` - 中文国际化消息
|
||||||
|
- `src/main/resources/i18n/messages_en_US.properties` - 英文国际化消息
|
||||||
|
|
||||||
### 数据库
|
### 数据库
|
||||||
- 建表 SQL:`src/main/resources/db/user.sql`
|
- 建表 SQL:`src/main/resources/db/user.sql`
|
||||||
- 主表:`app_user` - 应用用户表
|
- 主表:`app_user` - 应用用户表
|
||||||
@@ -159,6 +165,20 @@ com.corewing.app/
|
|||||||
- 验证码邮件采用精美的 HTML 模板
|
- 验证码邮件采用精美的 HTML 模板
|
||||||
- 支持多种邮箱服务商(QQ、163、Gmail 等)
|
- 支持多种邮箱服务商(QQ、163、Gmail 等)
|
||||||
|
|
||||||
|
### 9. 国际化支持
|
||||||
|
- 支持中文(zh_CN)和英文(en_US)双语
|
||||||
|
- 客户端通过 HTTP Header 的 `Accept-Language` 或 `lang` 指定语言
|
||||||
|
- 默认语言:中文
|
||||||
|
- 国际化范围:
|
||||||
|
- API 响应消息(成功/失败提示)
|
||||||
|
- 用户操作反馈(登录、注册、登出等)
|
||||||
|
- 所有异常和错误消息
|
||||||
|
- 邮件模板内容
|
||||||
|
- 短信模板内容
|
||||||
|
- 钉钉推送消息
|
||||||
|
- I18nUtil 工具类提供便捷的国际化消息获取
|
||||||
|
- 详细使用说明参见:`I18N_README.md`
|
||||||
|
|
||||||
## 开发规范
|
## 开发规范
|
||||||
|
|
||||||
### 1. 依赖注入
|
### 1. 依赖注入
|
||||||
@@ -183,6 +203,12 @@ com.corewing.app/
|
|||||||
- 使用 `IpUtil.getClientIp()` 获取真实 IP
|
- 使用 `IpUtil.getClientIp()` 获取真实 IP
|
||||||
- 支持多级代理
|
- 支持多级代理
|
||||||
|
|
||||||
|
### 6. 国际化消息
|
||||||
|
- 所有用户可见的消息都应使用国际化
|
||||||
|
- 使用 `I18nUtil.getMessage("message.key")` 获取国际化消息
|
||||||
|
- 支持消息参数:`I18nUtil.getMessage("message.key", param1, param2)`
|
||||||
|
- 新增消息时需同时更新中英文资源文件
|
||||||
|
|
||||||
## 项目特点
|
## 项目特点
|
||||||
|
|
||||||
- 项目使用 MyBatis Plus 作为 ORM 框架,支持自动代码生成
|
- 项目使用 MyBatis Plus 作为 ORM 框架,支持自动代码生成
|
||||||
@@ -192,3 +218,4 @@ com.corewing.app/
|
|||||||
- 开发环境支持热重载 (Spring Boot DevTools)
|
- 开发环境支持热重载 (Spring Boot DevTools)
|
||||||
- Druid 数据库连接池,提供 SQL 监控功能
|
- Druid 数据库连接池,提供 SQL 监控功能
|
||||||
- 统一返回结果封装,规范 API 响应格式
|
- 统一返回结果封装,规范 API 响应格式
|
||||||
|
- 完整的国际化支持,支持中英文双语切换
|
||||||
230
I18N_README.md
Normal file
230
I18N_README.md
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
# CoreWing 国际化使用说明
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
CoreWing 项目已完成国际化改造,支持中文(zh_CN)和英文(en_US)两种语言。
|
||||||
|
|
||||||
|
## 客户端使用方式
|
||||||
|
|
||||||
|
### HTTP Header 配置
|
||||||
|
|
||||||
|
客户端通过 HTTP Header 指定语言,支持以下两种方式:
|
||||||
|
|
||||||
|
**方式一:使用 Accept-Language(推荐)**
|
||||||
|
```
|
||||||
|
Accept-Language: zh_CN
|
||||||
|
```
|
||||||
|
|
||||||
|
**方式二:使用自定义 lang header**
|
||||||
|
```
|
||||||
|
lang: en_US
|
||||||
|
```
|
||||||
|
|
||||||
|
### 支持的语言代码
|
||||||
|
|
||||||
|
- `zh_CN` - 简体中文(默认)
|
||||||
|
- `en_US` - 英语
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
#### JavaScript/Axios
|
||||||
|
```javascript
|
||||||
|
// 中文请求
|
||||||
|
axios.get('/user/info', {
|
||||||
|
headers: {
|
||||||
|
'Accept-Language': 'zh_CN'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 英文请求
|
||||||
|
axios.get('/user/info', {
|
||||||
|
headers: {
|
||||||
|
'Accept-Language': 'en_US'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### cURL
|
||||||
|
```bash
|
||||||
|
# 中文
|
||||||
|
curl -H "Accept-Language: zh_CN" http://localhost:8080/user/info
|
||||||
|
|
||||||
|
# 英文
|
||||||
|
curl -H "Accept-Language: en_US" http://localhost:8080/user/info
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Java/OkHttp
|
||||||
|
```java
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url("http://localhost:8080/user/info")
|
||||||
|
.addHeader("Accept-Language", "zh_CN")
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 国际化功能范围
|
||||||
|
|
||||||
|
### 已国际化的内容
|
||||||
|
|
||||||
|
1. **API 响应消息**
|
||||||
|
- 成功/失败提示
|
||||||
|
- 用户操作反馈(登录、注册、登出等)
|
||||||
|
- 数据验证消息
|
||||||
|
|
||||||
|
2. **异常和错误消息**
|
||||||
|
- 用户认证异常
|
||||||
|
- 业务逻辑异常
|
||||||
|
- 服务器错误提示
|
||||||
|
|
||||||
|
3. **邮件模板**
|
||||||
|
- 验证码邮件内容
|
||||||
|
- 邮件主题和页脚
|
||||||
|
|
||||||
|
4. **短信模板**
|
||||||
|
- 验证码短信内容
|
||||||
|
- 短信发送结果消息
|
||||||
|
|
||||||
|
5. **钉钉推送消息**
|
||||||
|
- 反馈通知标题和内容
|
||||||
|
- 表单标签文本
|
||||||
|
|
||||||
|
## 技术实现
|
||||||
|
|
||||||
|
### 架构组成
|
||||||
|
|
||||||
|
1. **I18nConfig** - 国际化配置类
|
||||||
|
- 配置 MessageSource
|
||||||
|
- 实现自定义 LocaleResolver
|
||||||
|
- 支持从 Header 读取语言设置
|
||||||
|
|
||||||
|
2. **I18nUtil** - 国际化工具类
|
||||||
|
- 提供静态方法获取国际化消息
|
||||||
|
- 支持消息参数替换
|
||||||
|
|
||||||
|
3. **消息资源文件**
|
||||||
|
- `messages_zh_CN.properties` - 中文消息(约90条)
|
||||||
|
- `messages_en_US.properties` - 英文消息(约90条)
|
||||||
|
|
||||||
|
### 消息 Key 命名规范
|
||||||
|
|
||||||
|
```
|
||||||
|
common.* - 通用消息
|
||||||
|
user.* - 用户模块
|
||||||
|
feedback.* - 反馈模块
|
||||||
|
error.* - 错误消息
|
||||||
|
email.* - 邮件模板
|
||||||
|
sms.* - 短信模板
|
||||||
|
dingtalk.* - 钉钉推送
|
||||||
|
```
|
||||||
|
|
||||||
|
## 响应示例
|
||||||
|
|
||||||
|
### 中文响应 (Accept-Language: zh_CN)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "登录成功",
|
||||||
|
"data": {
|
||||||
|
"token": "xxx",
|
||||||
|
"userId": 123
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 英文响应 (Accept-Language: en_US)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "Login successful",
|
||||||
|
"data": {
|
||||||
|
"token": "xxx",
|
||||||
|
"userId": 123
|
||||||
|
},
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 开发指南
|
||||||
|
|
||||||
|
### 添加新的国际化消息
|
||||||
|
|
||||||
|
1. 在 `messages_zh_CN.properties` 中添加中文消息:
|
||||||
|
```properties
|
||||||
|
user.new.feature=这是一个新功能
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 在 `messages_en_US.properties` 中添加对应的英文消息:
|
||||||
|
```properties
|
||||||
|
user.new.feature=This is a new feature
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 在代码中使用:
|
||||||
|
```java
|
||||||
|
String message = I18nUtil.getMessage("user.new.feature");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 带参数的消息
|
||||||
|
|
||||||
|
**资源文件:**
|
||||||
|
```properties
|
||||||
|
# 中文
|
||||||
|
user.welcome=欢迎,{0}!您已成功登录。
|
||||||
|
|
||||||
|
# 英文
|
||||||
|
user.welcome=Welcome, {0}! You have successfully logged in.
|
||||||
|
```
|
||||||
|
|
||||||
|
**代码使用:**
|
||||||
|
```java
|
||||||
|
String message = I18nUtil.getMessage("user.welcome", username);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **默认语言**:如果客户端未指定语言,系统默认使用中文(zh_CN)
|
||||||
|
|
||||||
|
2. **URL 参数支持**:也可以通过 URL 参数切换语言
|
||||||
|
```
|
||||||
|
/user/info?lang=en_US
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **消息缓存**:资源文件会缓存 1 小时,修改后需要重启应用或等待缓存过期
|
||||||
|
|
||||||
|
4. **消息 Key 不存在**:如果找不到对应的消息 key,会返回 key 本身
|
||||||
|
|
||||||
|
## 修改文件清单
|
||||||
|
|
||||||
|
### 新增文件(3个)
|
||||||
|
- `src/main/java/com/corewing/app/config/I18nConfig.java`
|
||||||
|
- `src/main/java/com/corewing/app/util/I18nUtil.java`
|
||||||
|
- `src/main/resources/i18n/messages_zh_CN.properties`
|
||||||
|
- `src/main/resources/i18n/messages_en_US.properties`
|
||||||
|
|
||||||
|
### 修改文件(13个)
|
||||||
|
1. `Result.java` - 统一返回结果类
|
||||||
|
2. `AppUserController.java` - 用户控制器
|
||||||
|
3. `AppFeedbackController.java` - 反馈控制器
|
||||||
|
4. `AppUserServiceImpl.java` - 用户服务实现
|
||||||
|
5. `VerifyCodeServiceImpl.java` - 验证码服务实现
|
||||||
|
6. `AppFeedbackServiceImpl.java` - 反馈服务实现
|
||||||
|
7. `GlobalExceptionHandler.java` - 全局异常处理
|
||||||
|
8. `EmailUtil.java` - 邮件工具类
|
||||||
|
9. `SmsBaoUtil.java` - 短信工具类
|
||||||
|
10. `RedisUtil.java` - Redis工具类
|
||||||
|
|
||||||
|
## 测试建议
|
||||||
|
|
||||||
|
使用 Postman 或 curl 测试不同语言的响应:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 测试中文
|
||||||
|
curl -H "Accept-Language: zh_CN" http://localhost:8080/user/login
|
||||||
|
|
||||||
|
# 测试英文
|
||||||
|
curl -H "Accept-Language: en_US" http://localhost:8080/user/login
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**更新时间:** 2025-01-21
|
||||||
|
**版本:** 1.0
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.corewing.app.common;
|
package com.corewing.app.common;
|
||||||
|
|
||||||
|
import com.corewing.app.util.I18nUtil;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@@ -32,9 +33,6 @@ public class Result<T> implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private Boolean success;
|
private Boolean success;
|
||||||
|
|
||||||
public Result() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Result(Integer code, String message, T data, Boolean success) {
|
public Result(Integer code, String message, T data, Boolean success) {
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
@@ -46,14 +44,14 @@ public class Result<T> implements Serializable {
|
|||||||
* 成功返回(无数据)
|
* 成功返回(无数据)
|
||||||
*/
|
*/
|
||||||
public static <T> Result<T> success() {
|
public static <T> Result<T> success() {
|
||||||
return new Result<>(200, "操作成功", null, true);
|
return new Result<>(200, I18nUtil.getMessage("common.success"), null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 成功返回(带数据)
|
* 成功返回(带数据)
|
||||||
*/
|
*/
|
||||||
public static <T> Result<T> success(T data) {
|
public static <T> Result<T> success(T data) {
|
||||||
return new Result<>(200, "操作成功", data, true);
|
return new Result<>(200, I18nUtil.getMessage("common.success"), data, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,7 +65,7 @@ public class Result<T> implements Serializable {
|
|||||||
* 失败返回
|
* 失败返回
|
||||||
*/
|
*/
|
||||||
public static <T> Result<T> error() {
|
public static <T> Result<T> error() {
|
||||||
return new Result<>(500, "操作失败", null, false);
|
return new Result<>(500, I18nUtil.getMessage("common.error"), null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
110
src/main/java/com/corewing/app/config/I18nConfig.java
Normal file
110
src/main/java/com/corewing/app/config/I18nConfig.java
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package com.corewing.app.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.support.ResourceBundleMessageSource;
|
||||||
|
import org.springframework.web.servlet.LocaleResolver;
|
||||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 国际化配置类
|
||||||
|
* 从 HTTP Header 中的 Accept-Language 或 lang 参数读取语言设置
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class I18nConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置消息资源
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public ResourceBundleMessageSource messageSource() {
|
||||||
|
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
|
||||||
|
// 设置资源文件的基础名称(不包含语言代码和.properties后缀)
|
||||||
|
messageSource.setBasename("i18n/messages");
|
||||||
|
// 设置默认编码
|
||||||
|
messageSource.setDefaultEncoding("UTF-8");
|
||||||
|
// 设置缓存时间(秒),-1表示永久缓存,0表示不缓存
|
||||||
|
messageSource.setCacheSeconds(3600);
|
||||||
|
// 找不到对应的消息key时,返回key本身而不是抛出异常
|
||||||
|
messageSource.setUseCodeAsDefaultMessage(true);
|
||||||
|
return messageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置语言环境解析器
|
||||||
|
* 从 HTTP Header 的 Accept-Language 读取语言设置
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public LocaleResolver localeResolver() {
|
||||||
|
return new HeaderLocaleResolver();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置拦截器(可选)
|
||||||
|
* 支持通过URL参数 ?lang=zh_CN 切换语言
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public LocaleChangeInterceptor localeChangeInterceptor() {
|
||||||
|
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
|
||||||
|
// 设置请求参数名
|
||||||
|
interceptor.setParamName("lang");
|
||||||
|
return interceptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
registry.addInterceptor(localeChangeInterceptor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 LocaleResolver,从 Header 中读取语言设置
|
||||||
|
*/
|
||||||
|
private static class HeaderLocaleResolver implements LocaleResolver {
|
||||||
|
|
||||||
|
private static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Locale resolveLocale(HttpServletRequest request) {
|
||||||
|
// 1. 优先从 Accept-Language header 读取
|
||||||
|
String language = request.getHeader("Accept-Language");
|
||||||
|
|
||||||
|
// 2. 如果没有,尝试从自定义 lang header 读取
|
||||||
|
if (language == null || language.trim().isEmpty()) {
|
||||||
|
language = request.getHeader("lang");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 解析语言代码
|
||||||
|
if (language != null && !language.trim().isEmpty()) {
|
||||||
|
try {
|
||||||
|
// 支持格式: zh_CN, zh-CN, en_US, en-US
|
||||||
|
String normalizedLang = language.replace("-", "_");
|
||||||
|
String[] parts = normalizedLang.split("_");
|
||||||
|
|
||||||
|
if (parts.length == 2) {
|
||||||
|
return new Locale(parts[0], parts[1]);
|
||||||
|
} else if (parts.length == 1) {
|
||||||
|
return new Locale(parts[0]);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 解析失败,使用默认语言
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 返回默认语言(中文)
|
||||||
|
return DEFAULT_LOCALE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
|
||||||
|
// Header方式不支持设置语言环境
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"Cannot change HTTP header - use Accept-Language or lang header");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import com.corewing.app.dto.FeedbackRequest;
|
|||||||
import com.corewing.app.entity.AppFeedback;
|
import com.corewing.app.entity.AppFeedback;
|
||||||
import com.corewing.app.service.AppFeedbackService;
|
import com.corewing.app.service.AppFeedbackService;
|
||||||
import com.corewing.app.util.DingTalkUtil;
|
import com.corewing.app.util.DingTalkUtil;
|
||||||
|
import com.corewing.app.util.I18nUtil;
|
||||||
import com.corewing.app.util.Ip2RegionUtil;
|
import com.corewing.app.util.Ip2RegionUtil;
|
||||||
import com.corewing.app.util.IpUtil;
|
import com.corewing.app.util.IpUtil;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@@ -63,9 +64,9 @@ public class AppFeedbackController {
|
|||||||
|
|
||||||
// 推送到钉钉
|
// 推送到钉钉
|
||||||
sendFeedbackToDingTalk(feedback, submitIp, submitRegion);
|
sendFeedbackToDingTalk(feedback, submitIp, submitRegion);
|
||||||
return Result.success("反馈提交成功");
|
return Result.success(I18nUtil.getMessage("feedback.submit.success"));
|
||||||
}
|
}
|
||||||
return Result.error("反馈提交失败");
|
return Result.error(I18nUtil.getMessage("feedback.submit.failed"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Result.error(e.getMessage());
|
return Result.error(e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -96,7 +97,7 @@ public class AppFeedbackController {
|
|||||||
if (feedback != null) {
|
if (feedback != null) {
|
||||||
return Result.success(feedback);
|
return Result.success(feedback);
|
||||||
}
|
}
|
||||||
return Result.error("反馈不存在");
|
return Result.error(I18nUtil.getMessage("feedback.not.found"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Result.error(e.getMessage());
|
return Result.error(e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -135,9 +136,9 @@ public class AppFeedbackController {
|
|||||||
try {
|
try {
|
||||||
boolean success = feedbackService.updateStatus(id, status);
|
boolean success = feedbackService.updateStatus(id, status);
|
||||||
if (success) {
|
if (success) {
|
||||||
return Result.success("状态更新成功");
|
return Result.success(I18nUtil.getMessage("feedback.status.update.success"));
|
||||||
}
|
}
|
||||||
return Result.error("状态更新失败");
|
return Result.error(I18nUtil.getMessage("feedback.status.update.failed"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Result.error(e.getMessage());
|
return Result.error(e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -151,9 +152,9 @@ public class AppFeedbackController {
|
|||||||
try {
|
try {
|
||||||
boolean success = feedbackService.removeById(id);
|
boolean success = feedbackService.removeById(id);
|
||||||
if (success) {
|
if (success) {
|
||||||
return Result.success("删除成功");
|
return Result.success(I18nUtil.getMessage("feedback.delete.success"));
|
||||||
}
|
}
|
||||||
return Result.error("删除失败");
|
return Result.error(I18nUtil.getMessage("feedback.delete.failed"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Result.error(e.getMessage());
|
return Result.error(e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -165,18 +166,18 @@ public class AppFeedbackController {
|
|||||||
@GetMapping("/test-dingtalk")
|
@GetMapping("/test-dingtalk")
|
||||||
public Result<String> testDingTalk() {
|
public Result<String> testDingTalk() {
|
||||||
try {
|
try {
|
||||||
String title = "测试消息";
|
String title = I18nUtil.getMessage("feedback.dingtalk.test.message");
|
||||||
String text = "### 🔔 钉钉推送测试\n\n这是一条测试消息,用于验证钉钉推送功能是否正常工作。\n\n**测试时间:** " +
|
String text = "### 🔔 钉钉推送测试\n\n这是一条测试消息,用于验证钉钉推送功能是否正常工作。\n\n**测试时间:** " +
|
||||||
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
|
||||||
boolean success = dingTalkUtil.sendMarkdownMessage(title, text);
|
boolean success = dingTalkUtil.sendMarkdownMessage(title, text);
|
||||||
if (success) {
|
if (success) {
|
||||||
return Result.success("钉钉推送测试成功,请检查钉钉群消息");
|
return Result.success(I18nUtil.getMessage("feedback.dingtalk.test.success"));
|
||||||
} else {
|
} else {
|
||||||
return Result.error("钉钉推送测试失败,请检查配置和日志");
|
return Result.error(I18nUtil.getMessage("feedback.dingtalk.test.failed"));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Result.error("测试异常: " + e.getMessage());
|
return Result.error(I18nUtil.getMessage("feedback.dingtalk.test.error", e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,45 +186,53 @@ public class AppFeedbackController {
|
|||||||
*/
|
*/
|
||||||
private void sendFeedbackToDingTalk(AppFeedback feedback, String submitIp, String submitRegion) {
|
private void sendFeedbackToDingTalk(AppFeedback feedback, String submitIp, String submitRegion) {
|
||||||
try {
|
try {
|
||||||
String title = "📢 新的用户反馈";
|
String title = I18nUtil.getMessage("dingtalk.feedback.title");
|
||||||
StringBuilder text = new StringBuilder();
|
StringBuilder text = new StringBuilder();
|
||||||
text.append("### 📢 新的用户反馈\n\n");
|
text.append(I18nUtil.getMessage("dingtalk.feedback.header")).append("\n\n");
|
||||||
text.append("---\n\n");
|
text.append("---\n\n");
|
||||||
|
|
||||||
// 用户信息
|
// 用户信息
|
||||||
if (feedback.getUserId() != null) {
|
if (feedback.getUserId() != null) {
|
||||||
text.append("**用户ID:** ").append(feedback.getUserId()).append("\n\n");
|
text.append(I18nUtil.getMessage("dingtalk.feedback.user.id")).append(" ")
|
||||||
|
.append(feedback.getUserId()).append("\n\n");
|
||||||
} else {
|
} else {
|
||||||
text.append("**用户ID:** 匿名用户\n\n");
|
text.append(I18nUtil.getMessage("dingtalk.feedback.user.id")).append(" ")
|
||||||
|
.append(I18nUtil.getMessage("dingtalk.feedback.user.anonymous")).append("\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 反馈类型
|
// 反馈类型
|
||||||
text.append("**问题类型:** ").append(feedback.getFeedbackType()).append("\n\n");
|
text.append(I18nUtil.getMessage("dingtalk.feedback.type")).append(" ")
|
||||||
|
.append(feedback.getFeedbackType()).append("\n\n");
|
||||||
|
|
||||||
// 反馈标题
|
// 反馈标题
|
||||||
text.append("**问题标题:** ").append(feedback.getTitle()).append("\n\n");
|
text.append(I18nUtil.getMessage("dingtalk.feedback.title.label")).append(" ")
|
||||||
|
.append(feedback.getTitle()).append("\n\n");
|
||||||
|
|
||||||
// 反馈内容
|
// 反馈内容
|
||||||
if (feedback.getContent() != null && !feedback.getContent().trim().isEmpty()) {
|
if (feedback.getContent() != null && !feedback.getContent().trim().isEmpty()) {
|
||||||
text.append("**问题描述:** \n\n").append(feedback.getContent()).append("\n\n");
|
text.append(I18nUtil.getMessage("dingtalk.feedback.content.label")).append(" \n\n")
|
||||||
|
.append(feedback.getContent()).append("\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 联系方式
|
// 联系方式
|
||||||
if (feedback.getContact() != null && !feedback.getContact().trim().isEmpty()) {
|
if (feedback.getContact() != null && !feedback.getContact().trim().isEmpty()) {
|
||||||
text.append("**联系方式:** ").append(feedback.getContact()).append("\n\n");
|
text.append(I18nUtil.getMessage("dingtalk.feedback.contact")).append(" ")
|
||||||
|
.append(feedback.getContact()).append("\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// IP 和归属地
|
// IP 和归属地
|
||||||
text.append("**提交IP:** ").append(submitIp).append("\n\n");
|
text.append(I18nUtil.getMessage("dingtalk.feedback.submit.ip")).append(" ")
|
||||||
text.append("**IP归属地:** ").append(submitRegion).append("\n\n");
|
.append(submitIp).append("\n\n");
|
||||||
|
text.append(I18nUtil.getMessage("dingtalk.feedback.submit.region")).append(" ")
|
||||||
|
.append(submitRegion).append("\n\n");
|
||||||
|
|
||||||
// 提交时间
|
// 提交时间
|
||||||
text.append("**提交时间:** ").append(
|
text.append(I18nUtil.getMessage("dingtalk.feedback.submit.time")).append(" ").append(
|
||||||
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||||
).append("\n\n");
|
).append("\n\n");
|
||||||
|
|
||||||
text.append("---\n\n");
|
text.append("---\n\n");
|
||||||
text.append("> 请及时处理用户反馈!");
|
text.append(I18nUtil.getMessage("dingtalk.feedback.footer"));
|
||||||
|
|
||||||
// 异步发送钉钉消息(避免阻塞用户请求)
|
// 异步发送钉钉消息(避免阻塞用户请求)
|
||||||
new Thread(() -> dingTalkUtil.sendMarkdownMessage(title, text.toString())).start();
|
new Thread(() -> dingTalkUtil.sendMarkdownMessage(title, text.toString())).start();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.corewing.app.dto.UpdatePasswordRequest;
|
|||||||
import com.corewing.app.entity.AppUser;
|
import com.corewing.app.entity.AppUser;
|
||||||
import com.corewing.app.service.AppUserService;
|
import com.corewing.app.service.AppUserService;
|
||||||
import com.corewing.app.service.VerifyCodeService;
|
import com.corewing.app.service.VerifyCodeService;
|
||||||
|
import com.corewing.app.util.I18nUtil;
|
||||||
import com.corewing.app.util.IpUtil;
|
import com.corewing.app.util.IpUtil;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@@ -39,9 +40,9 @@ public class AppUserController {
|
|||||||
try {
|
try {
|
||||||
boolean success = verifyCodeService.sendCode(request.getAccount(), request.getType());
|
boolean success = verifyCodeService.sendCode(request.getAccount(), request.getType());
|
||||||
if (success) {
|
if (success) {
|
||||||
return Result.success("验证码发送成功");
|
return Result.success(I18nUtil.getMessage("user.code.send.success"));
|
||||||
}
|
}
|
||||||
return Result.error("验证码发送失败");
|
return Result.error(I18nUtil.getMessage("user.code.send.failed"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Result.error(e.getMessage());
|
return Result.error(e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -65,7 +66,7 @@ public class AppUserController {
|
|||||||
data.put("userId", user.getId());
|
data.put("userId", user.getId());
|
||||||
data.put("username", user.getUsername());
|
data.put("username", user.getUsername());
|
||||||
|
|
||||||
return Result.success("登录成功", data);
|
return Result.success(I18nUtil.getMessage("user.login.success"), data);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Result.error(e.getMessage());
|
return Result.error(e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -87,7 +88,7 @@ public class AppUserController {
|
|||||||
// 获取注册IP
|
// 获取注册IP
|
||||||
String registerIp = IpUtil.getClientIp(httpRequest);
|
String registerIp = IpUtil.getClientIp(httpRequest);
|
||||||
userService.register(user, request.getCode(), registerIp);
|
userService.register(user, request.getCode(), registerIp);
|
||||||
return Result.success("注册成功");
|
return Result.success(I18nUtil.getMessage("user.register.success"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Result.error(e.getMessage());
|
return Result.error(e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -99,7 +100,7 @@ public class AppUserController {
|
|||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
public Result<String> logout() {
|
public Result<String> logout() {
|
||||||
StpUtil.logout();
|
StpUtil.logout();
|
||||||
return Result.success("登出成功");
|
return Result.success(I18nUtil.getMessage("user.logout.success"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -125,7 +126,7 @@ public class AppUserController {
|
|||||||
user.setPassword(null);
|
user.setPassword(null);
|
||||||
return Result.success(user);
|
return Result.success(user);
|
||||||
}
|
}
|
||||||
return Result.error("用户不存在");
|
return Result.error(I18nUtil.getMessage("user.not.found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,9 +139,9 @@ public class AppUserController {
|
|||||||
|
|
||||||
boolean success = userService.updateById(user);
|
boolean success = userService.updateById(user);
|
||||||
if (success) {
|
if (success) {
|
||||||
return Result.success("更新成功");
|
return Result.success(I18nUtil.getMessage("user.update.success"));
|
||||||
}
|
}
|
||||||
return Result.error("更新失败");
|
return Result.error(I18nUtil.getMessage("user.update.failed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,7 +158,7 @@ public class AppUserController {
|
|||||||
request.getOldPassword().getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
request.getOldPassword().getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
||||||
|
|
||||||
if (!oldPasswordMd5.equals(user.getPassword())) {
|
if (!oldPasswordMd5.equals(user.getPassword())) {
|
||||||
return Result.error("原密码错误");
|
return Result.error(I18nUtil.getMessage("user.password.old.incorrect"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新新密码
|
// 更新新密码
|
||||||
@@ -167,7 +168,7 @@ public class AppUserController {
|
|||||||
user.setPassword(newPasswordMd5);
|
user.setPassword(newPasswordMd5);
|
||||||
userService.updateById(user);
|
userService.updateById(user);
|
||||||
|
|
||||||
return Result.success("密码修改成功");
|
return Result.success(I18nUtil.getMessage("user.password.update.success"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Result.error(e.getMessage());
|
return Result.error(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.corewing.app.handler;
|
|||||||
|
|
||||||
import cn.dev33.satoken.exception.NotLoginException;
|
import cn.dev33.satoken.exception.NotLoginException;
|
||||||
import com.corewing.app.common.Result;
|
import com.corewing.app.common.Result;
|
||||||
|
import com.corewing.app.util.I18nUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
@@ -25,22 +26,22 @@ public class GlobalExceptionHandler {
|
|||||||
String message;
|
String message;
|
||||||
switch (e.getType()) {
|
switch (e.getType()) {
|
||||||
case NotLoginException.NOT_TOKEN:
|
case NotLoginException.NOT_TOKEN:
|
||||||
message = "未提供登录凭证";
|
message = I18nUtil.getMessage("error.token.missing");
|
||||||
break;
|
break;
|
||||||
case NotLoginException.INVALID_TOKEN:
|
case NotLoginException.INVALID_TOKEN:
|
||||||
message = "登录凭证无效";
|
message = I18nUtil.getMessage("error.token.invalid");
|
||||||
break;
|
break;
|
||||||
case NotLoginException.TOKEN_TIMEOUT:
|
case NotLoginException.TOKEN_TIMEOUT:
|
||||||
message = "登录已过期,请重新登录";
|
message = I18nUtil.getMessage("error.token.expired");
|
||||||
break;
|
break;
|
||||||
case NotLoginException.BE_REPLACED:
|
case NotLoginException.BE_REPLACED:
|
||||||
message = "账号已在其他地方登录";
|
message = I18nUtil.getMessage("error.token.replaced");
|
||||||
break;
|
break;
|
||||||
case NotLoginException.KICK_OUT:
|
case NotLoginException.KICK_OUT:
|
||||||
message = "账号已被踢下线";
|
message = I18nUtil.getMessage("error.token.kicked.out");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
message = "未登录,请先登录";
|
message = I18nUtil.getMessage("error.not.login");
|
||||||
}
|
}
|
||||||
return Result.error(HttpStatus.UNAUTHORIZED.value(), message);
|
return Result.error(HttpStatus.UNAUTHORIZED.value(), message);
|
||||||
}
|
}
|
||||||
@@ -52,6 +53,7 @@ public class GlobalExceptionHandler {
|
|||||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
public Result<String> handleException(Exception e) {
|
public Result<String> handleException(Exception e) {
|
||||||
log.error(e.getMessage());
|
log.error(e.getMessage());
|
||||||
return Result.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务器内部错误:" + e.getMessage());
|
return Result.error(HttpStatus.INTERNAL_SERVER_ERROR.value(),
|
||||||
|
I18nUtil.getMessage("error.server.internal", e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|||||||
import com.corewing.app.entity.AppFeedback;
|
import com.corewing.app.entity.AppFeedback;
|
||||||
import com.corewing.app.mapper.AppFeedbackMapper;
|
import com.corewing.app.mapper.AppFeedbackMapper;
|
||||||
import com.corewing.app.service.AppFeedbackService;
|
import com.corewing.app.service.AppFeedbackService;
|
||||||
|
import com.corewing.app.util.I18nUtil;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ public class AppFeedbackServiceImpl extends ServiceImpl<AppFeedbackMapper, AppFe
|
|||||||
@Override
|
@Override
|
||||||
public List<AppFeedback> listByUserId(Long userId) {
|
public List<AppFeedback> listByUserId(Long userId) {
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
throw new RuntimeException("用户ID不能为空");
|
throw new RuntimeException(I18nUtil.getMessage("error.feedback.user.id.empty"));
|
||||||
}
|
}
|
||||||
LambdaQueryWrapper<AppFeedback> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<AppFeedback> wrapper = new LambdaQueryWrapper<>();
|
||||||
wrapper.eq(AppFeedback::getUserId, userId)
|
wrapper.eq(AppFeedback::getUserId, userId)
|
||||||
@@ -62,10 +63,10 @@ public class AppFeedbackServiceImpl extends ServiceImpl<AppFeedbackMapper, AppFe
|
|||||||
@Override
|
@Override
|
||||||
public boolean updateStatus(Long id, Integer status) {
|
public boolean updateStatus(Long id, Integer status) {
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
throw new RuntimeException("反馈ID不能为空");
|
throw new RuntimeException(I18nUtil.getMessage("error.feedback.id.empty"));
|
||||||
}
|
}
|
||||||
if (status == null) {
|
if (status == null) {
|
||||||
throw new RuntimeException("状态不能为空");
|
throw new RuntimeException(I18nUtil.getMessage("error.feedback.status.empty"));
|
||||||
}
|
}
|
||||||
|
|
||||||
AppFeedback feedback = new AppFeedback();
|
AppFeedback feedback = new AppFeedback();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.corewing.app.entity.AppUser;
|
|||||||
import com.corewing.app.mapper.AppUserMapper;
|
import com.corewing.app.mapper.AppUserMapper;
|
||||||
import com.corewing.app.service.AppUserService;
|
import com.corewing.app.service.AppUserService;
|
||||||
import com.corewing.app.service.VerifyCodeService;
|
import com.corewing.app.service.VerifyCodeService;
|
||||||
|
import com.corewing.app.util.I18nUtil;
|
||||||
import com.corewing.app.util.Ip2RegionUtil;
|
import com.corewing.app.util.Ip2RegionUtil;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.DigestUtils;
|
import org.springframework.util.DigestUtils;
|
||||||
@@ -75,18 +76,18 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> impl
|
|||||||
// 查询用户(支持用户名/邮箱/手机号)
|
// 查询用户(支持用户名/邮箱/手机号)
|
||||||
AppUser user = getByAccount(account);
|
AppUser user = getByAccount(account);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new RuntimeException("用户不存在");
|
throw new RuntimeException(I18nUtil.getMessage("error.user.not.found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证密码(MD5加密)
|
// 验证密码(MD5加密)
|
||||||
String encryptPassword = DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));
|
String encryptPassword = DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));
|
||||||
if (!encryptPassword.equals(user.getPassword())) {
|
if (!encryptPassword.equals(user.getPassword())) {
|
||||||
throw new RuntimeException("密码错误");
|
throw new RuntimeException(I18nUtil.getMessage("error.password.incorrect"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查用户状态
|
// 检查用户状态
|
||||||
if (user.getStatus() == 0) {
|
if (user.getStatus() == 0) {
|
||||||
throw new RuntimeException("账号已被禁用");
|
throw new RuntimeException(I18nUtil.getMessage("error.account.disabled"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录成功,使用 Sa-Token 生成 token
|
// 登录成功,使用 Sa-Token 生成 token
|
||||||
@@ -99,19 +100,19 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> impl
|
|||||||
// 检查用户名是否已存在
|
// 检查用户名是否已存在
|
||||||
AppUser existUser = getByUsername(user.getUsername());
|
AppUser existUser = getByUsername(user.getUsername());
|
||||||
if (existUser != null) {
|
if (existUser != null) {
|
||||||
throw new RuntimeException("用户名已存在");
|
throw new RuntimeException(I18nUtil.getMessage("error.username.exists"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 邮箱和手机号至少要有一个
|
// 邮箱和手机号至少要有一个
|
||||||
if (!StringUtils.hasText(user.getEmail()) && !StringUtils.hasText(user.getTelephone())) {
|
if (!StringUtils.hasText(user.getEmail()) && !StringUtils.hasText(user.getTelephone())) {
|
||||||
throw new RuntimeException("邮箱和手机号至少填写一个");
|
throw new RuntimeException(I18nUtil.getMessage("error.email.or.phone.required"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查邮箱是否已存在
|
// 检查邮箱是否已存在
|
||||||
if (StringUtils.hasText(user.getEmail())) {
|
if (StringUtils.hasText(user.getEmail())) {
|
||||||
existUser = getByEmail(user.getEmail());
|
existUser = getByEmail(user.getEmail());
|
||||||
if (existUser != null) {
|
if (existUser != null) {
|
||||||
throw new RuntimeException("邮箱已被使用");
|
throw new RuntimeException(I18nUtil.getMessage("error.email.exists"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +120,7 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> impl
|
|||||||
if (StringUtils.hasText(user.getTelephone())) {
|
if (StringUtils.hasText(user.getTelephone())) {
|
||||||
existUser = getByTelephone(user.getTelephone());
|
existUser = getByTelephone(user.getTelephone());
|
||||||
if (existUser != null) {
|
if (existUser != null) {
|
||||||
throw new RuntimeException("手机号已被使用");
|
throw new RuntimeException(I18nUtil.getMessage("error.phone.exists"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +128,7 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> impl
|
|||||||
String account = StringUtils.hasText(user.getTelephone()) ? user.getTelephone() : user.getEmail();
|
String account = StringUtils.hasText(user.getTelephone()) ? user.getTelephone() : user.getEmail();
|
||||||
boolean codeValid = verifyCodeService.verifyCode(account, code, "register");
|
boolean codeValid = verifyCodeService.verifyCode(account, code, "register");
|
||||||
if (!codeValid) {
|
if (!codeValid) {
|
||||||
throw new RuntimeException("验证码错误或已过期");
|
throw new RuntimeException(I18nUtil.getMessage("error.verify.code.invalid"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 密码加密(MD5)
|
// 密码加密(MD5)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.corewing.app.service.impl;
|
|||||||
|
|
||||||
import com.corewing.app.service.VerifyCodeService;
|
import com.corewing.app.service.VerifyCodeService;
|
||||||
import com.corewing.app.util.EmailUtil;
|
import com.corewing.app.util.EmailUtil;
|
||||||
|
import com.corewing.app.util.I18nUtil;
|
||||||
import com.corewing.app.util.RedisUtil;
|
import com.corewing.app.util.RedisUtil;
|
||||||
import com.corewing.app.util.SmsBaoUtil;
|
import com.corewing.app.util.SmsBaoUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -9,7 +10,6 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,11 +52,11 @@ public class VerifyCodeServiceImpl implements VerifyCodeService {
|
|||||||
@Override
|
@Override
|
||||||
public boolean sendCode(String account, String type) {
|
public boolean sendCode(String account, String type) {
|
||||||
if (!StringUtils.hasText(account)) {
|
if (!StringUtils.hasText(account)) {
|
||||||
throw new RuntimeException("账号不能为空");
|
throw new RuntimeException(I18nUtil.getMessage("error.account.empty"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!StringUtils.hasText(type)) {
|
if (!StringUtils.hasText(type)) {
|
||||||
throw new RuntimeException("验证码类型不能为空");
|
throw new RuntimeException(I18nUtil.getMessage("error.verify.type.empty"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断是手机号还是邮箱
|
// 判断是手机号还是邮箱
|
||||||
@@ -64,7 +64,7 @@ public class VerifyCodeServiceImpl implements VerifyCodeService {
|
|||||||
boolean isEmail = EMAIL_PATTERN.matcher(account).matches();
|
boolean isEmail = EMAIL_PATTERN.matcher(account).matches();
|
||||||
|
|
||||||
if (!isPhone && !isEmail) {
|
if (!isPhone && !isEmail) {
|
||||||
throw new RuntimeException("请输入正确的手机号或邮箱");
|
throw new RuntimeException(I18nUtil.getMessage("error.account.format.invalid"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成验证码
|
// 生成验证码
|
||||||
@@ -81,13 +81,13 @@ public class VerifyCodeServiceImpl implements VerifyCodeService {
|
|||||||
// 发送短信验证码
|
// 发送短信验证码
|
||||||
boolean success = smsBaoUtil.sendVerifyCode(account, code);
|
boolean success = smsBaoUtil.sendVerifyCode(account, code);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new RuntimeException("短信发送失败,请稍后重试");
|
throw new RuntimeException(I18nUtil.getMessage("error.sms.send.failed"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 发送邮件验证码
|
// 发送邮件验证码
|
||||||
boolean success = emailUtil.sendVerifyCode(account, code);
|
boolean success = emailUtil.sendVerifyCode(account, code);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new RuntimeException("邮件发送失败,请稍后重试");
|
throw new RuntimeException(I18nUtil.getMessage("error.email.send.failed"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public class EmailUtil {
|
|||||||
* @return 是否发送成功
|
* @return 是否发送成功
|
||||||
*/
|
*/
|
||||||
public boolean sendVerifyCode(String to, String code) {
|
public boolean sendVerifyCode(String to, String code) {
|
||||||
String subject = "【CoreWing】验证码";
|
String subject = I18nUtil.getMessage("email.subject.verify.code");
|
||||||
String content = buildVerifyCodeHtml(code);
|
String content = buildVerifyCodeHtml(code);
|
||||||
return sendHtmlMail(to, subject, content);
|
return sendHtmlMail(to, subject, content);
|
||||||
}
|
}
|
||||||
@@ -130,28 +130,28 @@ public class EmailUtil {
|
|||||||
"<h1 class=\"logo\">CoreWing</h1>" +
|
"<h1 class=\"logo\">CoreWing</h1>" +
|
||||||
"</div>" +
|
"</div>" +
|
||||||
"<div class=\"content\">" +
|
"<div class=\"content\">" +
|
||||||
"<div class=\"greeting\">Hi,你好!</div>" +
|
"<div class=\"greeting\">" + I18nUtil.getMessage("email.greeting") + "</div>" +
|
||||||
"<p style=\"color: #555; font-size: 14px; line-height: 1.8; margin-bottom: 20px;\">" +
|
"<p style=\"color: #555; font-size: 14px; line-height: 1.8; margin-bottom: 20px;\">" +
|
||||||
"感谢使用 CoreWing。我们收到了你的验证请求,请使用以下验证码完成操作:" +
|
I18nUtil.getMessage("email.verify.intro") +
|
||||||
"</p>" +
|
"</p>" +
|
||||||
"<div class=\"code-container\">" +
|
"<div class=\"code-container\">" +
|
||||||
"<div class=\"code-label\">验证码</div>" +
|
"<div class=\"code-label\">" + I18nUtil.getMessage("email.verify.code.label") + "</div>" +
|
||||||
"<div class=\"code\">" + code + "</div>" +
|
"<div class=\"code\">" + code + "</div>" +
|
||||||
"</div>" +
|
"</div>" +
|
||||||
"<div class=\"notice\">" +
|
"<div class=\"notice\">" +
|
||||||
"<div class=\"notice-title\">重要提示</div>" +
|
"<div class=\"notice-title\">" + I18nUtil.getMessage("email.verify.notice.title") + "</div>" +
|
||||||
"<div class=\"notice-text\">验证码 5 分钟内有效,请勿泄露给他人</div>" +
|
"<div class=\"notice-text\">" + I18nUtil.getMessage("email.verify.notice.content") + "</div>" +
|
||||||
"</div>" +
|
"</div>" +
|
||||||
"<div class=\"divider\"></div>" +
|
"<div class=\"divider\"></div>" +
|
||||||
"<div class=\"tips\">" +
|
"<div class=\"tips\">" +
|
||||||
"如果这不是你本人的操作,可以直接忽略这封邮件,你的账号仍然是安全的。<br>" +
|
I18nUtil.getMessage("email.verify.security.tips") + "<br>" +
|
||||||
"有任何问题都可以联系我们的客服团队。" +
|
I18nUtil.getMessage("email.verify.support.tips") +
|
||||||
"</div>" +
|
"</div>" +
|
||||||
"</div>" +
|
"</div>" +
|
||||||
"<div class=\"footer\">" +
|
"<div class=\"footer\">" +
|
||||||
"<div class=\"footer-text\">" +
|
"<div class=\"footer-text\">" +
|
||||||
"这是一封自动发送的邮件,请不要直接回复<br>" +
|
I18nUtil.getMessage("email.footer.notice") + "<br>" +
|
||||||
"© 2025 CoreWing · 保留所有权利" +
|
I18nUtil.getMessage("email.footer.copyright") +
|
||||||
"</div>" +
|
"</div>" +
|
||||||
"</div>" +
|
"</div>" +
|
||||||
"</div>" +
|
"</div>" +
|
||||||
|
|||||||
88
src/main/java/com/corewing/app/util/I18nUtil.java
Normal file
88
src/main/java/com/corewing/app/util/I18nUtil.java
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package com.corewing.app.util;
|
||||||
|
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 国际化工具类
|
||||||
|
* 提供便捷的静态方法获取国际化消息
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class I18nUtil {
|
||||||
|
|
||||||
|
private final MessageSource messageSource;
|
||||||
|
|
||||||
|
private static I18nUtil instance;
|
||||||
|
|
||||||
|
public I18nUtil(MessageSource messageSource) {
|
||||||
|
this.messageSource = messageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取国际化消息
|
||||||
|
*
|
||||||
|
* @param key 消息key
|
||||||
|
* @return 国际化消息
|
||||||
|
*/
|
||||||
|
public static String getMessage(String key) {
|
||||||
|
return getMessage(key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取国际化消息(带参数)
|
||||||
|
*
|
||||||
|
* @param key 消息key
|
||||||
|
* @param args 参数数组
|
||||||
|
* @return 国际化消息
|
||||||
|
*/
|
||||||
|
public static String getMessage(String key, Object[] args) {
|
||||||
|
try {
|
||||||
|
Locale locale = LocaleContextHolder.getLocale();
|
||||||
|
return instance.messageSource.getMessage(key, args, locale);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果获取失败,返回key本身
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取国际化消息(带单个参数)
|
||||||
|
*
|
||||||
|
* @param key 消息key
|
||||||
|
* @param arg 参数
|
||||||
|
* @return 国际化消息
|
||||||
|
*/
|
||||||
|
public static String getMessage(String key, Object arg) {
|
||||||
|
return getMessage(key, new Object[]{arg});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取国际化消息(带两个参数)
|
||||||
|
*
|
||||||
|
* @param key 消息key
|
||||||
|
* @param arg1 参数1
|
||||||
|
* @param arg2 参数2
|
||||||
|
* @return 国际化消息
|
||||||
|
*/
|
||||||
|
public static String getMessage(String key, Object arg1, Object arg2) {
|
||||||
|
return getMessage(key, new Object[]{arg1, arg2});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前语言环境
|
||||||
|
*
|
||||||
|
* @return 当前Locale
|
||||||
|
*/
|
||||||
|
public static Locale getCurrentLocale() {
|
||||||
|
return LocaleContextHolder.getLocale();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.corewing.app.util;
|
package com.corewing.app.util;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -78,7 +77,7 @@ public class RedisUtil {
|
|||||||
if (key.length == 1) {
|
if (key.length == 1) {
|
||||||
redisTemplate.delete(key[0]);
|
redisTemplate.delete(key[0]);
|
||||||
} else {
|
} else {
|
||||||
redisTemplate.delete((Collection<String>) List.of(key));
|
redisTemplate.delete(List.of(key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +141,7 @@ public class RedisUtil {
|
|||||||
*/
|
*/
|
||||||
public long incr(String key, long delta) {
|
public long incr(String key, long delta) {
|
||||||
if (delta < 0) {
|
if (delta < 0) {
|
||||||
throw new RuntimeException("递增因子必须大于0");
|
throw new RuntimeException(I18nUtil.getMessage("error.redis.increment.positive"));
|
||||||
}
|
}
|
||||||
Long result = redisTemplate.opsForValue().increment(key, delta);
|
Long result = redisTemplate.opsForValue().increment(key, delta);
|
||||||
return result != null ? result : 0L;
|
return result != null ? result : 0L;
|
||||||
@@ -156,7 +155,7 @@ public class RedisUtil {
|
|||||||
*/
|
*/
|
||||||
public long decr(String key, long delta) {
|
public long decr(String key, long delta) {
|
||||||
if (delta < 0) {
|
if (delta < 0) {
|
||||||
throw new RuntimeException("递减因子必须大于0");
|
throw new RuntimeException(I18nUtil.getMessage("error.redis.decrement.positive"));
|
||||||
}
|
}
|
||||||
Long result = redisTemplate.opsForValue().increment(key, -delta);
|
Long result = redisTemplate.opsForValue().increment(key, -delta);
|
||||||
return result != null ? result : 0L;
|
return result != null ? result : 0L;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.nio.charset.StandardCharsets;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 短信宝工具类
|
* 短信宝工具类
|
||||||
* 官方文档: https://www.smsbao.com/
|
* 官方文档: @<a href="https://www.smsbao.com/">...</a>
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@@ -83,7 +83,7 @@ public class SmsBaoUtil {
|
|||||||
* @return 是否发送成功
|
* @return 是否发送成功
|
||||||
*/
|
*/
|
||||||
public boolean sendVerifyCode(String phone, String code) {
|
public boolean sendVerifyCode(String phone, String code) {
|
||||||
String content = String.format("【CoreWing】您的验证码是%s,5分钟内有效。请勿泄露给他人!", code);
|
String content = String.format(I18nUtil.getMessage("sms.verify.code.template"), code);
|
||||||
return sendSms(phone, content);
|
return sendSms(phone, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,23 +96,23 @@ public class SmsBaoUtil {
|
|||||||
private String getErrorMessage(int code) {
|
private String getErrorMessage(int code) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 0:
|
case 0:
|
||||||
return "发送成功";
|
return I18nUtil.getMessage("sms.result.success");
|
||||||
case 30:
|
case 30:
|
||||||
return "密码错误";
|
return I18nUtil.getMessage("sms.result.password.error");
|
||||||
case 40:
|
case 40:
|
||||||
return "账号不存在";
|
return I18nUtil.getMessage("sms.result.account.not.exist");
|
||||||
case 41:
|
case 41:
|
||||||
return "余额不足";
|
return I18nUtil.getMessage("sms.result.balance.insufficient");
|
||||||
case 42:
|
case 42:
|
||||||
return "账户已过期";
|
return I18nUtil.getMessage("sms.result.account.expired");
|
||||||
case 43:
|
case 43:
|
||||||
return "IP地址限制";
|
return I18nUtil.getMessage("sms.result.ip.restricted");
|
||||||
case 50:
|
case 50:
|
||||||
return "内容含有敏感词";
|
return I18nUtil.getMessage("sms.result.sensitive.word");
|
||||||
case 51:
|
case 51:
|
||||||
return "手机号码不正确";
|
return I18nUtil.getMessage("sms.result.phone.incorrect");
|
||||||
default:
|
default:
|
||||||
return "未知错误";
|
return I18nUtil.getMessage("sms.result.unknown.error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
118
src/main/resources/i18n/messages_en_US.properties
Normal file
118
src/main/resources/i18n/messages_en_US.properties
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# ===================================
|
||||||
|
# CoreWing I18n Resource Bundle - English
|
||||||
|
# ===================================
|
||||||
|
|
||||||
|
# ==================== Common Messages ====================
|
||||||
|
common.success=Operation successful
|
||||||
|
common.error=Operation failed
|
||||||
|
|
||||||
|
# ==================== User Module ====================
|
||||||
|
# User Controller
|
||||||
|
user.code.send.success=Verification code sent successfully
|
||||||
|
user.code.send.failed=Failed to send verification code
|
||||||
|
user.login.success=Login successful
|
||||||
|
user.register.success=Registration successful
|
||||||
|
user.logout.success=Logout successful
|
||||||
|
user.not.found=User not found
|
||||||
|
user.update.success=Update successful
|
||||||
|
user.update.failed=Update failed
|
||||||
|
user.password.old.incorrect=Incorrect old password
|
||||||
|
user.password.update.success=Password updated successfully
|
||||||
|
|
||||||
|
# User Service Exceptions
|
||||||
|
error.user.not.found=User not found
|
||||||
|
error.password.incorrect=Incorrect password
|
||||||
|
error.account.disabled=Account has been disabled
|
||||||
|
error.username.exists=Username already exists
|
||||||
|
error.email.or.phone.required=At least one of email or phone number is required
|
||||||
|
error.email.exists=Email already in use
|
||||||
|
error.phone.exists=Phone number already in use
|
||||||
|
error.verify.code.invalid=Verification code is incorrect or expired
|
||||||
|
|
||||||
|
# ==================== Verification Code Module ====================
|
||||||
|
error.account.empty=Account cannot be empty
|
||||||
|
error.verify.type.empty=Verification type cannot be empty
|
||||||
|
error.account.format.invalid=Please enter a valid phone number or email
|
||||||
|
error.sms.send.failed=SMS sending failed, please try again later
|
||||||
|
error.email.send.failed=Email sending failed, please try again later
|
||||||
|
|
||||||
|
# ==================== Feedback Module ====================
|
||||||
|
# Feedback Controller
|
||||||
|
feedback.submit.success=Feedback submitted successfully
|
||||||
|
feedback.submit.failed=Failed to submit feedback
|
||||||
|
feedback.not.found=Feedback not found
|
||||||
|
feedback.status.update.success=Status updated successfully
|
||||||
|
feedback.status.update.failed=Failed to update status
|
||||||
|
feedback.delete.success=Deleted successfully
|
||||||
|
feedback.delete.failed=Failed to delete
|
||||||
|
feedback.dingtalk.test.message=Test message
|
||||||
|
feedback.dingtalk.test.success=DingTalk push test successful, please check the DingTalk group
|
||||||
|
feedback.dingtalk.test.failed=DingTalk push test failed, please check configuration and logs
|
||||||
|
feedback.dingtalk.test.error=Test exception: {0}
|
||||||
|
|
||||||
|
# Feedback Service Exceptions
|
||||||
|
error.feedback.user.id.empty=User ID cannot be empty
|
||||||
|
error.feedback.id.empty=Feedback ID cannot be empty
|
||||||
|
error.feedback.status.empty=Status cannot be empty
|
||||||
|
|
||||||
|
# ==================== Global Exception Handler ====================
|
||||||
|
error.token.missing=Login credentials not provided
|
||||||
|
error.token.invalid=Invalid login credentials
|
||||||
|
error.token.expired=Login has expired, please login again
|
||||||
|
error.token.replaced=Account has been logged in elsewhere
|
||||||
|
error.token.kicked.out=Account has been kicked offline
|
||||||
|
error.not.login=Not logged in, please login first
|
||||||
|
error.server.internal=Internal server error: {0}
|
||||||
|
|
||||||
|
# ==================== Utility Classes ====================
|
||||||
|
# Redis Utility
|
||||||
|
error.redis.increment.positive=Increment factor must be greater than 0
|
||||||
|
error.redis.decrement.positive=Decrement factor must be greater than 0
|
||||||
|
|
||||||
|
# ==================== Email Template ====================
|
||||||
|
email.subject.verify.code=[CoreWing] Verification Code
|
||||||
|
email.greeting=Hi there!
|
||||||
|
email.verify.intro=Thank you for using CoreWing. We received your verification request. Please use the following verification code to complete the operation:
|
||||||
|
email.verify.code.label=Verification Code
|
||||||
|
email.verify.notice.title=Important Notice
|
||||||
|
email.verify.notice.content=The verification code is valid for 5 minutes. Please do not share it with others
|
||||||
|
email.verify.security.tips=If this was not you, you can safely ignore this email. Your account is still secure.
|
||||||
|
email.verify.support.tips=If you have any questions, feel free to contact our support team.
|
||||||
|
email.footer.notice=This is an automated email. Please do not reply directly
|
||||||
|
email.footer.copyright=© 2025 CoreWing · All Rights Reserved
|
||||||
|
|
||||||
|
# ==================== SMS Template ====================
|
||||||
|
sms.verify.code.template=[CoreWing] Your verification code is {0}, valid for 5 minutes. Do not share with others!
|
||||||
|
sms.result.success=Sent successfully
|
||||||
|
sms.result.password.error=Password error
|
||||||
|
sms.result.account.not.exist=Account does not exist
|
||||||
|
sms.result.balance.insufficient=Insufficient balance
|
||||||
|
sms.result.account.expired=Account has expired
|
||||||
|
sms.result.ip.restricted=IP address restricted
|
||||||
|
sms.result.sensitive.word=Content contains sensitive words
|
||||||
|
sms.result.phone.incorrect=Incorrect phone number
|
||||||
|
sms.result.unknown.error=Unknown error
|
||||||
|
|
||||||
|
# ==================== DingTalk Push ====================
|
||||||
|
dingtalk.feedback.title=📢 New User Feedback
|
||||||
|
dingtalk.feedback.header=### 📢 New User Feedback
|
||||||
|
dingtalk.feedback.user.id=**User ID:**
|
||||||
|
dingtalk.feedback.user.anonymous=Anonymous User
|
||||||
|
dingtalk.feedback.type=**Feedback Type:**
|
||||||
|
dingtalk.feedback.title.label=**Title:**
|
||||||
|
dingtalk.feedback.content.label=**Description:**
|
||||||
|
dingtalk.feedback.contact=**Contact:**
|
||||||
|
dingtalk.feedback.submit.ip=**Submit IP:**
|
||||||
|
dingtalk.feedback.submit.region=**IP Location:**
|
||||||
|
dingtalk.feedback.submit.time=**Submit Time:**
|
||||||
|
dingtalk.feedback.footer=> Please handle user feedback promptly!
|
||||||
|
|
||||||
|
# DingTalk Log Messages (Optional)
|
||||||
|
dingtalk.webhook.not.configured=DingTalk Webhook not configured, skipping message push
|
||||||
|
dingtalk.text.send.failed=Failed to send DingTalk text message: {0}
|
||||||
|
dingtalk.markdown.send.failed=Failed to send DingTalk Markdown message: {0}
|
||||||
|
dingtalk.sign.mode=Using signature mode, timestamp: {0}
|
||||||
|
dingtalk.send.message=Sending DingTalk message: {0}
|
||||||
|
dingtalk.push.success=DingTalk message pushed successfully, response: {0}
|
||||||
|
dingtalk.push.failed=DingTalk message push failed, HTTP status code: {0}, response: {1}
|
||||||
|
dingtalk.push.exception=DingTalk message push exception: {0}
|
||||||
118
src/main/resources/i18n/messages_zh_CN.properties
Normal file
118
src/main/resources/i18n/messages_zh_CN.properties
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# ===================================
|
||||||
|
# CoreWing 国际化资源文件 - 中文
|
||||||
|
# ===================================
|
||||||
|
|
||||||
|
# ==================== 通用消息 ====================
|
||||||
|
common.success=操作成功
|
||||||
|
common.error=操作失败
|
||||||
|
|
||||||
|
# ==================== 用户模块 ====================
|
||||||
|
# 用户Controller
|
||||||
|
user.code.send.success=验证码发送成功
|
||||||
|
user.code.send.failed=验证码发送失败
|
||||||
|
user.login.success=登录成功
|
||||||
|
user.register.success=注册成功
|
||||||
|
user.logout.success=登出成功
|
||||||
|
user.not.found=用户不存在
|
||||||
|
user.update.success=更新成功
|
||||||
|
user.update.failed=更新失败
|
||||||
|
user.password.old.incorrect=原密码错误
|
||||||
|
user.password.update.success=密码修改成功
|
||||||
|
|
||||||
|
# 用户Service异常
|
||||||
|
error.user.not.found=用户不存在
|
||||||
|
error.password.incorrect=密码错误
|
||||||
|
error.account.disabled=账号已被禁用
|
||||||
|
error.username.exists=用户名已存在
|
||||||
|
error.email.or.phone.required=邮箱和手机号至少填写一个
|
||||||
|
error.email.exists=邮箱已被使用
|
||||||
|
error.phone.exists=手机号已被使用
|
||||||
|
error.verify.code.invalid=验证码错误或已过期
|
||||||
|
|
||||||
|
# ==================== 验证码模块 ====================
|
||||||
|
error.account.empty=账号不能为空
|
||||||
|
error.verify.type.empty=验证码类型不能为空
|
||||||
|
error.account.format.invalid=请输入正确的手机号或邮箱
|
||||||
|
error.sms.send.failed=短信发送失败,请稍后重试
|
||||||
|
error.email.send.failed=邮件发送失败,请稍后重试
|
||||||
|
|
||||||
|
# ==================== 反馈模块 ====================
|
||||||
|
# 反馈Controller
|
||||||
|
feedback.submit.success=反馈提交成功
|
||||||
|
feedback.submit.failed=反馈提交失败
|
||||||
|
feedback.not.found=反馈不存在
|
||||||
|
feedback.status.update.success=状态更新成功
|
||||||
|
feedback.status.update.failed=状态更新失败
|
||||||
|
feedback.delete.success=删除成功
|
||||||
|
feedback.delete.failed=删除失败
|
||||||
|
feedback.dingtalk.test.message=测试消息
|
||||||
|
feedback.dingtalk.test.success=钉钉推送测试成功,请检查钉钉群消息
|
||||||
|
feedback.dingtalk.test.failed=钉钉推送测试失败,请检查配置和日志
|
||||||
|
feedback.dingtalk.test.error=测试异常: {0}
|
||||||
|
|
||||||
|
# 反馈Service异常
|
||||||
|
error.feedback.user.id.empty=用户ID不能为空
|
||||||
|
error.feedback.id.empty=反馈ID不能为空
|
||||||
|
error.feedback.status.empty=状态不能为空
|
||||||
|
|
||||||
|
# ==================== 全局异常处理 ====================
|
||||||
|
error.token.missing=未提供登录凭证
|
||||||
|
error.token.invalid=登录凭证无效
|
||||||
|
error.token.expired=登录已过期,请重新登录
|
||||||
|
error.token.replaced=账号已在其他地方登录
|
||||||
|
error.token.kicked.out=账号已被踢下线
|
||||||
|
error.not.login=未登录,请先登录
|
||||||
|
error.server.internal=服务器内部错误:{0}
|
||||||
|
|
||||||
|
# ==================== 工具类 ====================
|
||||||
|
# Redis工具类
|
||||||
|
error.redis.increment.positive=递增因子必须大于0
|
||||||
|
error.redis.decrement.positive=递减因子必须大于0
|
||||||
|
|
||||||
|
# ==================== 邮件模板 ====================
|
||||||
|
email.subject.verify.code=【CoreWing】验证码
|
||||||
|
email.greeting=Hi,你好!
|
||||||
|
email.verify.intro=感谢使用 CoreWing。我们收到了你的验证请求,请使用以下验证码完成操作:
|
||||||
|
email.verify.code.label=验证码
|
||||||
|
email.verify.notice.title=重要提示
|
||||||
|
email.verify.notice.content=验证码 5 分钟内有效,请勿泄露给他人
|
||||||
|
email.verify.security.tips=如果这不是你本人的操作,可以直接忽略这封邮件,你的账号仍然是安全的。
|
||||||
|
email.verify.support.tips=有任何问题都可以联系我们的客服团队。
|
||||||
|
email.footer.notice=这是一封自动发送的邮件,请不要直接回复
|
||||||
|
email.footer.copyright=© 2025 CoreWing · 保留所有权利
|
||||||
|
|
||||||
|
# ==================== 短信模板 ====================
|
||||||
|
sms.verify.code.template=【CoreWing】您的验证码是{0},5分钟内有效。请勿泄露给他人!
|
||||||
|
sms.result.success=发送成功
|
||||||
|
sms.result.password.error=密码错误
|
||||||
|
sms.result.account.not.exist=账号不存在
|
||||||
|
sms.result.balance.insufficient=余额不足
|
||||||
|
sms.result.account.expired=账户已过期
|
||||||
|
sms.result.ip.restricted=IP地址限制
|
||||||
|
sms.result.sensitive.word=内容含有敏感词
|
||||||
|
sms.result.phone.incorrect=手机号码不正确
|
||||||
|
sms.result.unknown.error=未知错误
|
||||||
|
|
||||||
|
# ==================== 钉钉推送 ====================
|
||||||
|
dingtalk.feedback.title=📢 新的用户反馈
|
||||||
|
dingtalk.feedback.header=### 📢 新的用户反馈
|
||||||
|
dingtalk.feedback.user.id=**用户ID:**
|
||||||
|
dingtalk.feedback.user.anonymous=匿名用户
|
||||||
|
dingtalk.feedback.type=**问题类型:**
|
||||||
|
dingtalk.feedback.title.label=**问题标题:**
|
||||||
|
dingtalk.feedback.content.label=**问题描述:**
|
||||||
|
dingtalk.feedback.contact=**联系方式:**
|
||||||
|
dingtalk.feedback.submit.ip=**提交IP:**
|
||||||
|
dingtalk.feedback.submit.region=**IP归属地:**
|
||||||
|
dingtalk.feedback.submit.time=**提交时间:**
|
||||||
|
dingtalk.feedback.footer=> 请及时处理用户反馈!
|
||||||
|
|
||||||
|
# 钉钉日志消息(可选)
|
||||||
|
dingtalk.webhook.not.configured=钉钉 Webhook 未配置,跳过消息推送
|
||||||
|
dingtalk.text.send.failed=发送钉钉文本消息失败: {0}
|
||||||
|
dingtalk.markdown.send.failed=发送钉钉 Markdown 消息失败: {0}
|
||||||
|
dingtalk.sign.mode=使用加签模式,timestamp: {0}
|
||||||
|
dingtalk.send.message=发送钉钉消息: {0}
|
||||||
|
dingtalk.push.success=钉钉消息推送成功, 响应: {0}
|
||||||
|
dingtalk.push.failed=钉钉消息推送失败, HTTP 状态码: {0}, 响应: {1}
|
||||||
|
dingtalk.push.exception=钉钉消息推送异常: {0}
|
||||||
Reference in New Issue
Block a user