From 928bb078f0d00566e28260c1c4bb28d978eb0e3f Mon Sep 17 00:00:00 2001 From: zhoujinhua Date: Tue, 21 Oct 2025 10:15:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90app=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=9B=BD=E9=99=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 29 ++- I18N_README.md | 230 ++++++++++++++++++ .../java/com/corewing/app/common/Result.java | 10 +- .../com/corewing/app/config/I18nConfig.java | 110 +++++++++ .../app/controller/AppFeedbackController.java | 55 +++-- .../app/controller/AppUserController.java | 21 +- .../app/handler/GlobalExceptionHandler.java | 16 +- .../service/impl/AppFeedbackServiceImpl.java | 7 +- .../app/service/impl/AppUserServiceImpl.java | 17 +- .../service/impl/VerifyCodeServiceImpl.java | 12 +- .../java/com/corewing/app/util/EmailUtil.java | 20 +- .../java/com/corewing/app/util/I18nUtil.java | 88 +++++++ .../java/com/corewing/app/util/RedisUtil.java | 7 +- .../com/corewing/app/util/SmsBaoUtil.java | 22 +- .../resources/i18n/messages_en_US.properties | 118 +++++++++ .../resources/i18n/messages_zh_CN.properties | 118 +++++++++ 16 files changed, 791 insertions(+), 89 deletions(-) create mode 100644 I18N_README.md create mode 100644 src/main/java/com/corewing/app/config/I18nConfig.java create mode 100644 src/main/java/com/corewing/app/util/I18nUtil.java create mode 100644 src/main/resources/i18n/messages_en_US.properties create mode 100644 src/main/resources/i18n/messages_zh_CN.properties diff --git a/CLAUDE.md b/CLAUDE.md index 463e192..a38bbe8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,6 +63,7 @@ com.corewing.app/ │ └── Result.java # 统一返回结果类 ├── config/ # 配置类 │ ├── DruidConfig.java # Druid 数据源监控配置 +│ ├── I18nConfig.java # 国际化配置 │ ├── MybatisPlusConfig.java # MyBatis-Plus 配置 │ ├── RedisConfig.java # Redis 配置 │ └── SaTokenConfig.java # Sa-Token 权限配置 @@ -87,6 +88,7 @@ com.corewing.app/ │ └── VerifyCodeServiceImpl.java # 验证码服务实现 └── util/ # 工具类 ├── EmailUtil.java # 邮件发送工具类 + ├── I18nUtil.java # 国际化工具类 ├── IpUtil.java # IP 工具类 ├── RedisUtil.java # Redis 工具类 └── 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` - 主表:`app_user` - 应用用户表 @@ -159,6 +165,20 @@ com.corewing.app/ - 验证码邮件采用精美的 HTML 模板 - 支持多种邮箱服务商(QQ、163、Gmail 等) +### 9. 国际化支持 +- 支持中文(zh_CN)和英文(en_US)双语 +- 客户端通过 HTTP Header 的 `Accept-Language` 或 `lang` 指定语言 +- 默认语言:中文 +- 国际化范围: + - API 响应消息(成功/失败提示) + - 用户操作反馈(登录、注册、登出等) + - 所有异常和错误消息 + - 邮件模板内容 + - 短信模板内容 + - 钉钉推送消息 +- I18nUtil 工具类提供便捷的国际化消息获取 +- 详细使用说明参见:`I18N_README.md` + ## 开发规范 ### 1. 依赖注入 @@ -183,6 +203,12 @@ com.corewing.app/ - 使用 `IpUtil.getClientIp()` 获取真实 IP - 支持多级代理 +### 6. 国际化消息 +- 所有用户可见的消息都应使用国际化 +- 使用 `I18nUtil.getMessage("message.key")` 获取国际化消息 +- 支持消息参数:`I18nUtil.getMessage("message.key", param1, param2)` +- 新增消息时需同时更新中英文资源文件 + ## 项目特点 - 项目使用 MyBatis Plus 作为 ORM 框架,支持自动代码生成 @@ -191,4 +217,5 @@ com.corewing.app/ - 使用 Lombok 简化实体类代码 - 开发环境支持热重载 (Spring Boot DevTools) - Druid 数据库连接池,提供 SQL 监控功能 -- 统一返回结果封装,规范 API 响应格式 \ No newline at end of file +- 统一返回结果封装,规范 API 响应格式 +- 完整的国际化支持,支持中英文双语切换 \ No newline at end of file diff --git a/I18N_README.md b/I18N_README.md new file mode 100644 index 0000000..bf2486b --- /dev/null +++ b/I18N_README.md @@ -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 diff --git a/src/main/java/com/corewing/app/common/Result.java b/src/main/java/com/corewing/app/common/Result.java index b08c5bf..8b0a8ca 100644 --- a/src/main/java/com/corewing/app/common/Result.java +++ b/src/main/java/com/corewing/app/common/Result.java @@ -1,5 +1,6 @@ package com.corewing.app.common; +import com.corewing.app.util.I18nUtil; import lombok.Data; import java.io.Serializable; @@ -32,9 +33,6 @@ public class Result implements Serializable { */ private Boolean success; - public Result() { - } - public Result(Integer code, String message, T data, Boolean success) { this.code = code; this.message = message; @@ -46,14 +44,14 @@ public class Result implements Serializable { * 成功返回(无数据) */ public static Result success() { - return new Result<>(200, "操作成功", null, true); + return new Result<>(200, I18nUtil.getMessage("common.success"), null, true); } /** * 成功返回(带数据) */ public static Result 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 implements Serializable { * 失败返回 */ public static Result error() { - return new Result<>(500, "操作失败", null, false); + return new Result<>(500, I18nUtil.getMessage("common.error"), null, false); } /** diff --git a/src/main/java/com/corewing/app/config/I18nConfig.java b/src/main/java/com/corewing/app/config/I18nConfig.java new file mode 100644 index 0000000..03599a5 --- /dev/null +++ b/src/main/java/com/corewing/app/config/I18nConfig.java @@ -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"); + } + } +} diff --git a/src/main/java/com/corewing/app/controller/AppFeedbackController.java b/src/main/java/com/corewing/app/controller/AppFeedbackController.java index 25d1a83..a52acdf 100644 --- a/src/main/java/com/corewing/app/controller/AppFeedbackController.java +++ b/src/main/java/com/corewing/app/controller/AppFeedbackController.java @@ -8,6 +8,7 @@ import com.corewing.app.dto.FeedbackRequest; import com.corewing.app.entity.AppFeedback; import com.corewing.app.service.AppFeedbackService; import com.corewing.app.util.DingTalkUtil; +import com.corewing.app.util.I18nUtil; import com.corewing.app.util.Ip2RegionUtil; import com.corewing.app.util.IpUtil; import org.springframework.web.bind.annotation.*; @@ -63,9 +64,9 @@ public class AppFeedbackController { // 推送到钉钉 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) { return Result.error(e.getMessage()); } @@ -96,7 +97,7 @@ public class AppFeedbackController { if (feedback != null) { return Result.success(feedback); } - return Result.error("反馈不存在"); + return Result.error(I18nUtil.getMessage("feedback.not.found")); } catch (Exception e) { return Result.error(e.getMessage()); } @@ -135,9 +136,9 @@ public class AppFeedbackController { try { boolean success = feedbackService.updateStatus(id, status); 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) { return Result.error(e.getMessage()); } @@ -151,9 +152,9 @@ public class AppFeedbackController { try { boolean success = feedbackService.removeById(id); 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) { return Result.error(e.getMessage()); } @@ -165,18 +166,18 @@ public class AppFeedbackController { @GetMapping("/test-dingtalk") public Result testDingTalk() { try { - String title = "测试消息"; + String title = I18nUtil.getMessage("feedback.dingtalk.test.message"); String text = "### 🔔 钉钉推送测试\n\n这是一条测试消息,用于验证钉钉推送功能是否正常工作。\n\n**测试时间:** " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); boolean success = dingTalkUtil.sendMarkdownMessage(title, text); if (success) { - return Result.success("钉钉推送测试成功,请检查钉钉群消息"); + return Result.success(I18nUtil.getMessage("feedback.dingtalk.test.success")); } else { - return Result.error("钉钉推送测试失败,请检查配置和日志"); + return Result.error(I18nUtil.getMessage("feedback.dingtalk.test.failed")); } } 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) { try { - String title = "📢 新的用户反馈"; + String title = I18nUtil.getMessage("dingtalk.feedback.title"); StringBuilder text = new StringBuilder(); - text.append("### 📢 新的用户反馈\n\n"); + text.append(I18nUtil.getMessage("dingtalk.feedback.header")).append("\n\n"); text.append("---\n\n"); // 用户信息 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 { - 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()) { - 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()) { - text.append("**联系方式:** ").append(feedback.getContact()).append("\n\n"); + text.append(I18nUtil.getMessage("dingtalk.feedback.contact")).append(" ") + .append(feedback.getContact()).append("\n\n"); } // IP 和归属地 - text.append("**提交IP:** ").append(submitIp).append("\n\n"); - text.append("**IP归属地:** ").append(submitRegion).append("\n\n"); + text.append(I18nUtil.getMessage("dingtalk.feedback.submit.ip")).append(" ") + .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")) ).append("\n\n"); text.append("---\n\n"); - text.append("> 请及时处理用户反馈!"); + text.append(I18nUtil.getMessage("dingtalk.feedback.footer")); // 异步发送钉钉消息(避免阻塞用户请求) new Thread(() -> dingTalkUtil.sendMarkdownMessage(title, text.toString())).start(); diff --git a/src/main/java/com/corewing/app/controller/AppUserController.java b/src/main/java/com/corewing/app/controller/AppUserController.java index bd42d4f..de8c92a 100644 --- a/src/main/java/com/corewing/app/controller/AppUserController.java +++ b/src/main/java/com/corewing/app/controller/AppUserController.java @@ -9,6 +9,7 @@ import com.corewing.app.dto.UpdatePasswordRequest; import com.corewing.app.entity.AppUser; import com.corewing.app.service.AppUserService; import com.corewing.app.service.VerifyCodeService; +import com.corewing.app.util.I18nUtil; import com.corewing.app.util.IpUtil; import org.springframework.web.bind.annotation.*; @@ -39,9 +40,9 @@ public class AppUserController { try { boolean success = verifyCodeService.sendCode(request.getAccount(), request.getType()); 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) { return Result.error(e.getMessage()); } @@ -65,7 +66,7 @@ public class AppUserController { data.put("userId", user.getId()); data.put("username", user.getUsername()); - return Result.success("登录成功", data); + return Result.success(I18nUtil.getMessage("user.login.success"), data); } catch (Exception e) { return Result.error(e.getMessage()); } @@ -87,7 +88,7 @@ public class AppUserController { // 获取注册IP String registerIp = IpUtil.getClientIp(httpRequest); userService.register(user, request.getCode(), registerIp); - return Result.success("注册成功"); + return Result.success(I18nUtil.getMessage("user.register.success")); } catch (Exception e) { return Result.error(e.getMessage()); } @@ -99,7 +100,7 @@ public class AppUserController { @PostMapping("/logout") public Result logout() { StpUtil.logout(); - return Result.success("登出成功"); + return Result.success(I18nUtil.getMessage("user.logout.success")); } /** @@ -125,7 +126,7 @@ public class AppUserController { user.setPassword(null); 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); 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)); 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); userService.updateById(user); - return Result.success("密码修改成功"); + return Result.success(I18nUtil.getMessage("user.password.update.success")); } catch (Exception e) { return Result.error(e.getMessage()); } diff --git a/src/main/java/com/corewing/app/handler/GlobalExceptionHandler.java b/src/main/java/com/corewing/app/handler/GlobalExceptionHandler.java index ebc47c1..d11a4c1 100644 --- a/src/main/java/com/corewing/app/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/corewing/app/handler/GlobalExceptionHandler.java @@ -2,6 +2,7 @@ package com.corewing.app.handler; import cn.dev33.satoken.exception.NotLoginException; import com.corewing.app.common.Result; +import com.corewing.app.util.I18nUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -25,22 +26,22 @@ public class GlobalExceptionHandler { String message; switch (e.getType()) { case NotLoginException.NOT_TOKEN: - message = "未提供登录凭证"; + message = I18nUtil.getMessage("error.token.missing"); break; case NotLoginException.INVALID_TOKEN: - message = "登录凭证无效"; + message = I18nUtil.getMessage("error.token.invalid"); break; case NotLoginException.TOKEN_TIMEOUT: - message = "登录已过期,请重新登录"; + message = I18nUtil.getMessage("error.token.expired"); break; case NotLoginException.BE_REPLACED: - message = "账号已在其他地方登录"; + message = I18nUtil.getMessage("error.token.replaced"); break; case NotLoginException.KICK_OUT: - message = "账号已被踢下线"; + message = I18nUtil.getMessage("error.token.kicked.out"); break; default: - message = "未登录,请先登录"; + message = I18nUtil.getMessage("error.not.login"); } return Result.error(HttpStatus.UNAUTHORIZED.value(), message); } @@ -52,6 +53,7 @@ public class GlobalExceptionHandler { @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Result handleException(Exception e) { 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())); } } diff --git a/src/main/java/com/corewing/app/service/impl/AppFeedbackServiceImpl.java b/src/main/java/com/corewing/app/service/impl/AppFeedbackServiceImpl.java index 79766d1..5791158 100644 --- a/src/main/java/com/corewing/app/service/impl/AppFeedbackServiceImpl.java +++ b/src/main/java/com/corewing/app/service/impl/AppFeedbackServiceImpl.java @@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.corewing.app.entity.AppFeedback; import com.corewing.app.mapper.AppFeedbackMapper; import com.corewing.app.service.AppFeedbackService; +import com.corewing.app.util.I18nUtil; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -30,7 +31,7 @@ public class AppFeedbackServiceImpl extends ServiceImpl listByUserId(Long userId) { if (userId == null) { - throw new RuntimeException("用户ID不能为空"); + throw new RuntimeException(I18nUtil.getMessage("error.feedback.user.id.empty")); } LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(AppFeedback::getUserId, userId) @@ -62,10 +63,10 @@ public class AppFeedbackServiceImpl extends ServiceImpl impl // 查询用户(支持用户名/邮箱/手机号) AppUser user = getByAccount(account); if (user == null) { - throw new RuntimeException("用户不存在"); + throw new RuntimeException(I18nUtil.getMessage("error.user.not.found")); } // 验证密码(MD5加密) String encryptPassword = DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8)); if (!encryptPassword.equals(user.getPassword())) { - throw new RuntimeException("密码错误"); + throw new RuntimeException(I18nUtil.getMessage("error.password.incorrect")); } // 检查用户状态 if (user.getStatus() == 0) { - throw new RuntimeException("账号已被禁用"); + throw new RuntimeException(I18nUtil.getMessage("error.account.disabled")); } // 登录成功,使用 Sa-Token 生成 token @@ -99,19 +100,19 @@ public class AppUserServiceImpl extends ServiceImpl impl // 检查用户名是否已存在 AppUser existUser = getByUsername(user.getUsername()); if (existUser != null) { - throw new RuntimeException("用户名已存在"); + throw new RuntimeException(I18nUtil.getMessage("error.username.exists")); } // 邮箱和手机号至少要有一个 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())) { existUser = getByEmail(user.getEmail()); if (existUser != null) { - throw new RuntimeException("邮箱已被使用"); + throw new RuntimeException(I18nUtil.getMessage("error.email.exists")); } } @@ -119,7 +120,7 @@ public class AppUserServiceImpl extends ServiceImpl impl if (StringUtils.hasText(user.getTelephone())) { existUser = getByTelephone(user.getTelephone()); if (existUser != null) { - throw new RuntimeException("手机号已被使用"); + throw new RuntimeException(I18nUtil.getMessage("error.phone.exists")); } } @@ -127,7 +128,7 @@ public class AppUserServiceImpl extends ServiceImpl impl String account = StringUtils.hasText(user.getTelephone()) ? user.getTelephone() : user.getEmail(); boolean codeValid = verifyCodeService.verifyCode(account, code, "register"); if (!codeValid) { - throw new RuntimeException("验证码错误或已过期"); + throw new RuntimeException(I18nUtil.getMessage("error.verify.code.invalid")); } // 密码加密(MD5) diff --git a/src/main/java/com/corewing/app/service/impl/VerifyCodeServiceImpl.java b/src/main/java/com/corewing/app/service/impl/VerifyCodeServiceImpl.java index 34d7ff4..4b7981a 100644 --- a/src/main/java/com/corewing/app/service/impl/VerifyCodeServiceImpl.java +++ b/src/main/java/com/corewing/app/service/impl/VerifyCodeServiceImpl.java @@ -2,6 +2,7 @@ package com.corewing.app.service.impl; import com.corewing.app.service.VerifyCodeService; import com.corewing.app.util.EmailUtil; +import com.corewing.app.util.I18nUtil; import com.corewing.app.util.RedisUtil; import com.corewing.app.util.SmsBaoUtil; import lombok.extern.slf4j.Slf4j; @@ -9,7 +10,6 @@ import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.Random; -import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; /** @@ -52,11 +52,11 @@ public class VerifyCodeServiceImpl implements VerifyCodeService { @Override public boolean sendCode(String account, String type) { if (!StringUtils.hasText(account)) { - throw new RuntimeException("账号不能为空"); + throw new RuntimeException(I18nUtil.getMessage("error.account.empty")); } 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(); 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); if (!success) { - throw new RuntimeException("短信发送失败,请稍后重试"); + throw new RuntimeException(I18nUtil.getMessage("error.sms.send.failed")); } } else { // 发送邮件验证码 boolean success = emailUtil.sendVerifyCode(account, code); if (!success) { - throw new RuntimeException("邮件发送失败,请稍后重试"); + throw new RuntimeException(I18nUtil.getMessage("error.email.send.failed")); } } diff --git a/src/main/java/com/corewing/app/util/EmailUtil.java b/src/main/java/com/corewing/app/util/EmailUtil.java index 3e5a630..f9fae62 100644 --- a/src/main/java/com/corewing/app/util/EmailUtil.java +++ b/src/main/java/com/corewing/app/util/EmailUtil.java @@ -86,7 +86,7 @@ public class EmailUtil { * @return 是否发送成功 */ public boolean sendVerifyCode(String to, String code) { - String subject = "【CoreWing】验证码"; + String subject = I18nUtil.getMessage("email.subject.verify.code"); String content = buildVerifyCodeHtml(code); return sendHtmlMail(to, subject, content); } @@ -130,28 +130,28 @@ public class EmailUtil { "

CoreWing

" + "" + "
" + - "
Hi,你好!
" + + "
" + I18nUtil.getMessage("email.greeting") + "
" + "

" + - "感谢使用 CoreWing。我们收到了你的验证请求,请使用以下验证码完成操作:" + + I18nUtil.getMessage("email.verify.intro") + "

" + "
" + - "
验证码
" + + "
" + I18nUtil.getMessage("email.verify.code.label") + "
" + "
" + code + "
" + "
" + "
" + - "
重要提示
" + - "
验证码 5 分钟内有效,请勿泄露给他人
" + + "
" + I18nUtil.getMessage("email.verify.notice.title") + "
" + + "
" + I18nUtil.getMessage("email.verify.notice.content") + "
" + "
" + "
" + "
" + - "如果这不是你本人的操作,可以直接忽略这封邮件,你的账号仍然是安全的。
" + - "有任何问题都可以联系我们的客服团队。" + + I18nUtil.getMessage("email.verify.security.tips") + "
" + + I18nUtil.getMessage("email.verify.support.tips") + "
" + "
" + "
" + "
" + - "这是一封自动发送的邮件,请不要直接回复
" + - "© 2025 CoreWing · 保留所有权利" + + I18nUtil.getMessage("email.footer.notice") + "
" + + I18nUtil.getMessage("email.footer.copyright") + "
" + "
" + "" + diff --git a/src/main/java/com/corewing/app/util/I18nUtil.java b/src/main/java/com/corewing/app/util/I18nUtil.java new file mode 100644 index 0000000..5454c2e --- /dev/null +++ b/src/main/java/com/corewing/app/util/I18nUtil.java @@ -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(); + } +} diff --git a/src/main/java/com/corewing/app/util/RedisUtil.java b/src/main/java/com/corewing/app/util/RedisUtil.java index 2797f7e..2ba1cc8 100644 --- a/src/main/java/com/corewing/app/util/RedisUtil.java +++ b/src/main/java/com/corewing/app/util/RedisUtil.java @@ -1,6 +1,5 @@ package com.corewing.app.util; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @@ -78,7 +77,7 @@ public class RedisUtil { if (key.length == 1) { redisTemplate.delete(key[0]); } else { - redisTemplate.delete((Collection) List.of(key)); + redisTemplate.delete(List.of(key)); } } } @@ -142,7 +141,7 @@ public class RedisUtil { */ public long incr(String key, long delta) { if (delta < 0) { - throw new RuntimeException("递增因子必须大于0"); + throw new RuntimeException(I18nUtil.getMessage("error.redis.increment.positive")); } Long result = redisTemplate.opsForValue().increment(key, delta); return result != null ? result : 0L; @@ -156,7 +155,7 @@ public class RedisUtil { */ public long decr(String key, long delta) { if (delta < 0) { - throw new RuntimeException("递减因子必须大于0"); + throw new RuntimeException(I18nUtil.getMessage("error.redis.decrement.positive")); } Long result = redisTemplate.opsForValue().increment(key, -delta); return result != null ? result : 0L; diff --git a/src/main/java/com/corewing/app/util/SmsBaoUtil.java b/src/main/java/com/corewing/app/util/SmsBaoUtil.java index 81612e5..75ce241 100644 --- a/src/main/java/com/corewing/app/util/SmsBaoUtil.java +++ b/src/main/java/com/corewing/app/util/SmsBaoUtil.java @@ -13,7 +13,7 @@ import java.nio.charset.StandardCharsets; /** * 短信宝工具类 - * 官方文档: https://www.smsbao.com/ + * 官方文档: @... */ @Slf4j @Component @@ -83,7 +83,7 @@ public class SmsBaoUtil { * @return 是否发送成功 */ 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); } @@ -96,23 +96,23 @@ public class SmsBaoUtil { private String getErrorMessage(int code) { switch (code) { case 0: - return "发送成功"; + return I18nUtil.getMessage("sms.result.success"); case 30: - return "密码错误"; + return I18nUtil.getMessage("sms.result.password.error"); case 40: - return "账号不存在"; + return I18nUtil.getMessage("sms.result.account.not.exist"); case 41: - return "余额不足"; + return I18nUtil.getMessage("sms.result.balance.insufficient"); case 42: - return "账户已过期"; + return I18nUtil.getMessage("sms.result.account.expired"); case 43: - return "IP地址限制"; + return I18nUtil.getMessage("sms.result.ip.restricted"); case 50: - return "内容含有敏感词"; + return I18nUtil.getMessage("sms.result.sensitive.word"); case 51: - return "手机号码不正确"; + return I18nUtil.getMessage("sms.result.phone.incorrect"); default: - return "未知错误"; + return I18nUtil.getMessage("sms.result.unknown.error"); } } } diff --git a/src/main/resources/i18n/messages_en_US.properties b/src/main/resources/i18n/messages_en_US.properties new file mode 100644 index 0000000..a6648ce --- /dev/null +++ b/src/main/resources/i18n/messages_en_US.properties @@ -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} diff --git a/src/main/resources/i18n/messages_zh_CN.properties b/src/main/resources/i18n/messages_zh_CN.properties new file mode 100644 index 0000000..a3fc2f6 --- /dev/null +++ b/src/main/resources/i18n/messages_zh_CN.properties @@ -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}