diff --git a/docs/EMAIL_TEMPLATES.md b/docs/EMAIL_TEMPLATES.md index d0e8bfe..5d78c07 100644 --- a/docs/EMAIL_TEMPLATES.md +++ b/docs/EMAIL_TEMPLATES.md @@ -98,7 +98,7 @@ emailTemplateUtil.sendNotificationEmailWithAction( "您的订单已发货", "user@example.com", 1001, - "https://www.gxwebsoft.com/orders/12345", + "https://websoft.top/orders/12345", "查看订单" ); ``` diff --git a/pom.xml b/pom.xml index 3bca112..d32d3f6 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,12 @@ spring-boot-starter-web + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + org.springframework.boot diff --git a/src/main/java/com/gxwebsoft/auto/controller/QrLoginController.java b/src/main/java/com/gxwebsoft/auto/controller/QrLoginController.java new file mode 100644 index 0000000..5368fea --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/controller/QrLoginController.java @@ -0,0 +1,88 @@ +package com.gxwebsoft.auto.controller; + +import com.gxwebsoft.auto.dto.QrLoginConfirmRequest; +import com.gxwebsoft.auto.dto.QrLoginGenerateResponse; +import com.gxwebsoft.auto.dto.QrLoginStatusResponse; +import com.gxwebsoft.auto.service.QrLoginService; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.core.web.ApiResult; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +/** + * 认证模块 + * + * @author 科技小王子 + * @since 2025-03-06 22:50:25 + */ +@Tag(name = "认证模块") +@RestController +@RequestMapping("/api/qr-login") +public class QrLoginController extends BaseController { + + @Autowired + private QrLoginService qrLoginService; + + /** + * 生成扫码登录token + */ + @Operation(summary = "生成扫码登录token") + @PostMapping("/generate") + public ApiResult generateQrLoginToken() { + try { + QrLoginGenerateResponse response = qrLoginService.generateQrLoginToken(); + return success("生成成功", response); + } catch (Exception e) { + return fail(e.getMessage()); + } + } + + /** + * 检查扫码登录状态 + */ + @Operation(summary = "检查扫码登录状态") + @GetMapping("/status/{token}") + public ApiResult checkQrLoginStatus( + @Parameter(description = "扫码登录token") @PathVariable String token) { + try { + QrLoginStatusResponse response = qrLoginService.checkQrLoginStatus(token); + return success("查询成功", response); + } catch (Exception e) { + return fail(e.getMessage()); + } + } + + /** + * 确认扫码登录 + */ + @Operation(summary = "确认扫码登录") + @PostMapping("/confirm") + public ApiResult confirmQrLogin(@Valid @RequestBody QrLoginConfirmRequest request) { + try { + QrLoginStatusResponse response = qrLoginService.confirmQrLogin(request); + return success("确认成功", response); + } catch (Exception e) { + return fail(e.getMessage()); + } + } + + /** + * 扫码操作(可选接口,用于移动端扫码后更新状态) + */ + @Operation(summary = "扫码操作") + @PostMapping("/scan/{token}") + public ApiResult scanQrCode(@Parameter(description = "扫码登录token") @PathVariable String token) { + try { + boolean result = qrLoginService.scanQrCode(token); + return success("操作成功", result); + } catch (Exception e) { + return fail(e.getMessage()); + } + } + +} diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginConfirmRequest.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginConfirmRequest.java new file mode 100644 index 0000000..73244f0 --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/dto/QrLoginConfirmRequest.java @@ -0,0 +1,25 @@ +package com.gxwebsoft.auto.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 扫码登录确认请求 + * + * @author 科技小王子 + * @since 2025-08-31 + */ +@Data +@Schema(description = "扫码登录确认请求") +public class QrLoginConfirmRequest { + + @Schema(description = "扫码登录token") + @NotBlank(message = "token不能为空") + private String token; + + @Schema(description = "用户ID") + private Integer userId; + +} diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginData.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginData.java new file mode 100644 index 0000000..563bf1d --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/dto/QrLoginData.java @@ -0,0 +1,55 @@ +package com.gxwebsoft.auto.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 扫码登录数据模型 + * + * @author 科技小王子 + * @since 2025-08-31 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class QrLoginData { + + /** + * 扫码登录token + */ + private String token; + + /** + * 状态: pending-等待扫码, scanned-已扫码, confirmed-已确认, expired-已过期 + */ + private String status; + + /** + * 用户ID(扫码确认后设置) + */ + private Integer userId; + + /** + * 用户名(扫码确认后设置) + */ + private String username; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 过期时间 + */ + private LocalDateTime expireTime; + + /** + * JWT访问令牌(确认后生成) + */ + private String accessToken; + +} diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginGenerateResponse.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginGenerateResponse.java new file mode 100644 index 0000000..f0b69e5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/dto/QrLoginGenerateResponse.java @@ -0,0 +1,29 @@ +package com.gxwebsoft.auto.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 扫码登录生成响应 + * + * @author 科技小王子 + * @since 2025-08-31 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "扫码登录生成响应") +public class QrLoginGenerateResponse { + + @Schema(description = "扫码登录token") + private String token; + + @Schema(description = "二维码内容") + private String qrCode; + + @Schema(description = "过期时间(秒)") + private Long expiresIn; + +} diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginStatusResponse.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginStatusResponse.java new file mode 100644 index 0000000..1eb0d4a --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/dto/QrLoginStatusResponse.java @@ -0,0 +1,32 @@ +package com.gxwebsoft.auto.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 扫码登录状态响应 + * + * @author 科技小王子 + * @since 2025-08-31 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "扫码登录状态响应") +public class QrLoginStatusResponse { + + @Schema(description = "状态: pending-等待扫码, scanned-已扫码, confirmed-已确认, expired-已过期") + private String status; + + @Schema(description = "JWT访问令牌(仅在confirmed状态时返回)") + private String accessToken; + + @Schema(description = "用户信息(仅在confirmed状态时返回)") + private Object userInfo; + + @Schema(description = "剩余过期时间(秒)") + private Long expiresIn; + +} diff --git a/src/main/java/com/gxwebsoft/auto/service/QrLoginService.java b/src/main/java/com/gxwebsoft/auto/service/QrLoginService.java new file mode 100644 index 0000000..85ed28f --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/service/QrLoginService.java @@ -0,0 +1,46 @@ +package com.gxwebsoft.auto.service; + +import com.gxwebsoft.auto.dto.QrLoginConfirmRequest; +import com.gxwebsoft.auto.dto.QrLoginGenerateResponse; +import com.gxwebsoft.auto.dto.QrLoginStatusResponse; + +/** + * 扫码登录服务接口 + * + * @author 科技小王子 + * @since 2025-08-31 + */ +public interface QrLoginService { + + /** + * 生成扫码登录token + * + * @return QrLoginGenerateResponse + */ + QrLoginGenerateResponse generateQrLoginToken(); + + /** + * 检查扫码登录状态 + * + * @param token 扫码登录token + * @return QrLoginStatusResponse + */ + QrLoginStatusResponse checkQrLoginStatus(String token); + + /** + * 确认扫码登录 + * + * @param request 确认请求 + * @return QrLoginStatusResponse + */ + QrLoginStatusResponse confirmQrLogin(QrLoginConfirmRequest request); + + /** + * 扫码操作(更新状态为已扫码) + * + * @param token 扫码登录token + * @return boolean + */ + boolean scanQrCode(String token); + +} diff --git a/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java b/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java new file mode 100644 index 0000000..bd2aee7 --- /dev/null +++ b/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java @@ -0,0 +1,204 @@ +package com.gxwebsoft.auto.service.impl; + +import cn.hutool.core.lang.UUID; +import cn.hutool.core.util.StrUtil; +import com.gxwebsoft.auto.dto.*; +import com.gxwebsoft.auto.service.QrLoginService; +import com.gxwebsoft.common.core.security.JwtSubject; +import com.gxwebsoft.common.core.security.JwtUtil; +import com.gxwebsoft.common.core.utils.RedisUtil; +import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.common.system.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.TimeUnit; + +import static com.gxwebsoft.common.core.constants.RedisConstants.*; + +/** + * 扫码登录服务实现 + * + * @author 科技小王子 + * @since 2025-08-31 + */ +@Slf4j +@Service +public class QrLoginServiceImpl implements QrLoginService { + + @Autowired + private RedisUtil redisUtil; + + @Autowired + private UserService userService; + + @Value("${config.jwt.secret:websoft-jwt-secret-key-2025}") + private String jwtSecret; + + @Value("${config.jwt.expire:86400}") + private Long jwtExpire; + + @Override + public QrLoginGenerateResponse generateQrLoginToken() { + // 生成唯一的扫码登录token + String token = UUID.randomUUID().toString(true); + + // 创建扫码登录数据 + QrLoginData qrLoginData = new QrLoginData(); + qrLoginData.setToken(token); + qrLoginData.setStatus(QR_LOGIN_STATUS_PENDING); + qrLoginData.setCreateTime(LocalDateTime.now()); + qrLoginData.setExpireTime(LocalDateTime.now().plusSeconds(QR_LOGIN_TOKEN_TTL)); + + // 存储到Redis,设置过期时间 + String redisKey = QR_LOGIN_TOKEN_KEY + token; + redisUtil.set(redisKey, qrLoginData, QR_LOGIN_TOKEN_TTL, TimeUnit.SECONDS); + + log.info("生成扫码登录token: {}", token); + + // 构造二维码内容(这里可以是前端登录页面的URL + token参数) + String qrCodeContent = "qr-login:" + token; + + return new QrLoginGenerateResponse(token, qrCodeContent, QR_LOGIN_TOKEN_TTL); + } + + @Override + public QrLoginStatusResponse checkQrLoginStatus(String token) { + if (StrUtil.isBlank(token)) { + return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L); + } + + String redisKey = QR_LOGIN_TOKEN_KEY + token; + QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class); + + if (qrLoginData == null) { + return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L); + } + + // 检查是否过期 + if (LocalDateTime.now().isAfter(qrLoginData.getExpireTime())) { + // 删除过期的token + redisUtil.delete(redisKey); + return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L); + } + + // 计算剩余过期时间 + long expiresIn = ChronoUnit.SECONDS.between(LocalDateTime.now(), qrLoginData.getExpireTime()); + + QrLoginStatusResponse response = new QrLoginStatusResponse(); + response.setStatus(qrLoginData.getStatus()); + response.setExpiresIn(expiresIn); + + // 如果已确认,返回token和用户信息 + if (QR_LOGIN_STATUS_CONFIRMED.equals(qrLoginData.getStatus())) { + response.setAccessToken(qrLoginData.getAccessToken()); + + // 获取用户信息 + if (qrLoginData.getUserId() != null) { + User user = userService.getByIdRel(qrLoginData.getUserId()); + if (user != null) { + // 清除敏感信息 + user.setPassword(null); + response.setUserInfo(user); + } + } + + // 确认后删除token,防止重复使用 + redisUtil.delete(redisKey); + } + + return response; + } + + @Override + public QrLoginStatusResponse confirmQrLogin(QrLoginConfirmRequest request) { + String token = request.getToken(); + Integer userId = request.getUserId(); + + if (StrUtil.isBlank(token) || userId == null) { + throw new RuntimeException("参数不能为空"); + } + + String redisKey = QR_LOGIN_TOKEN_KEY + token; + QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class); + + if (qrLoginData == null) { + throw new RuntimeException("扫码登录token不存在或已过期"); + } + + // 检查是否过期 + if (LocalDateTime.now().isAfter(qrLoginData.getExpireTime())) { + redisUtil.delete(redisKey); + throw new RuntimeException("扫码登录token已过期"); + } + + // 获取用户信息 + User user = userService.getByIdRel(userId); + if (user == null) { + throw new RuntimeException("用户不存在"); + } + + // 检查用户状态 + if (user.getStatus() != null && user.getStatus() != 0) { + throw new RuntimeException("用户已被冻结"); + } + + // 生成JWT token + JwtSubject jwtSubject = new JwtSubject(user.getUsername(), user.getTenantId()); + String accessToken = JwtUtil.buildToken(jwtSubject, jwtExpire, jwtSecret); + + // 更新扫码登录数据 + qrLoginData.setStatus(QR_LOGIN_STATUS_CONFIRMED); + qrLoginData.setUserId(userId); + qrLoginData.setUsername(user.getUsername()); + qrLoginData.setAccessToken(accessToken); + + // 更新Redis中的数据 + redisUtil.set(redisKey, qrLoginData, 60L, TimeUnit.SECONDS); // 给前端60秒时间获取token + + log.info("用户 {} 确认扫码登录,token: {}", user.getUsername(), token); + + // 清除敏感信息 + user.setPassword(null); + + return new QrLoginStatusResponse(QR_LOGIN_STATUS_CONFIRMED, accessToken, user, 60L); + } + + @Override + public boolean scanQrCode(String token) { + if (StrUtil.isBlank(token)) { + return false; + } + + String redisKey = QR_LOGIN_TOKEN_KEY + token; + QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class); + + if (qrLoginData == null) { + return false; + } + + // 检查是否过期 + if (LocalDateTime.now().isAfter(qrLoginData.getExpireTime())) { + redisUtil.delete(redisKey); + return false; + } + + // 只有pending状态才能更新为scanned + if (QR_LOGIN_STATUS_PENDING.equals(qrLoginData.getStatus())) { + qrLoginData.setStatus(QR_LOGIN_STATUS_SCANNED); + + // 计算剩余过期时间 + long remainingSeconds = ChronoUnit.SECONDS.between(LocalDateTime.now(), qrLoginData.getExpireTime()); + redisUtil.set(redisKey, qrLoginData, remainingSeconds, TimeUnit.SECONDS); + + log.info("扫码登录token {} 状态更新为已扫码", token); + return true; + } + + return false; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/config/JacksonConfig.java b/src/main/java/com/gxwebsoft/common/core/config/JacksonConfig.java new file mode 100644 index 0000000..77ec1cd --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/config/JacksonConfig.java @@ -0,0 +1,36 @@ +package com.gxwebsoft.common.core.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +/** + * Jackson配置类 + * 解决Java 8时间类型序列化问题 + * + * @author WebSoft + * @since 2024-08-28 + */ +@Configuration +public class JacksonConfig { + + @Bean + @Primary + public ObjectMapper objectMapper() { + ObjectMapper mapper = new ObjectMapper(); + + // 注册JavaTimeModule + mapper.registerModule(new JavaTimeModule()); + + // 禁用将日期写为时间戳 + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + // 禁用将日期时间戳写为纳秒 + mapper.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS); + + return mapper; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/config/SwaggerConfig.java b/src/main/java/com/gxwebsoft/common/core/config/SwaggerConfig.java index 50b15b8..ae23ecd 100644 --- a/src/main/java/com/gxwebsoft/common/core/config/SwaggerConfig.java +++ b/src/main/java/com/gxwebsoft/common/core/config/SwaggerConfig.java @@ -49,7 +49,7 @@ public class SwaggerConfig { .title(config.getSwaggerTitle()) .description(config.getSwaggerDescription()) .version(config.getSwaggerVersion()) - .contact(new Contact("科技小王子","https://www.gxwebsoft.com","170083662@qq.com")) + .contact(new Contact("科技小王子","https://websoft.top","170083662@qq.com")) .termsOfServiceUrl("https://server.gxwebsoft.com/api") .build(); } diff --git a/src/main/java/com/gxwebsoft/common/core/constants/RedisConstants.java b/src/main/java/com/gxwebsoft/common/core/constants/RedisConstants.java index 2765754..68d8fef 100644 --- a/src/main/java/com/gxwebsoft/common/core/constants/RedisConstants.java +++ b/src/main/java/com/gxwebsoft/common/core/constants/RedisConstants.java @@ -27,6 +27,14 @@ public class RedisConstants { + // 扫码登录相关key + public static final String QR_LOGIN_TOKEN_KEY = "qr-login:token:"; // 扫码登录token前缀 + public static final Long QR_LOGIN_TOKEN_TTL = 300L; // 扫码登录token过期时间(5分钟) + public static final String QR_LOGIN_STATUS_PENDING = "pending"; // 等待扫码 + public static final String QR_LOGIN_STATUS_SCANNED = "scanned"; // 已扫码 + public static final String QR_LOGIN_STATUS_CONFIRMED = "confirmed"; // 已确认 + public static final String QR_LOGIN_STATUS_EXPIRED = "expired"; // 已过期 + // 哗啦啦key public static final String getAllShop = "allShop"; public static final String getBaseInfo = "baseInfo"; diff --git a/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java b/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java index af1cb4a..1bbb461 100644 --- a/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java +++ b/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java @@ -39,6 +39,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { .permitAll() .antMatchers( "/api/login", + "/api/qr-login/**", "/api/loginByUserId", "/api/register", "/api/superAdminRegister", diff --git a/src/main/java/com/gxwebsoft/common/system/controller/EmailTestController.java b/src/main/java/com/gxwebsoft/common/system/controller/EmailTestController.java new file mode 100644 index 0000000..3bf9fc4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/controller/EmailTestController.java @@ -0,0 +1,116 @@ +package com.gxwebsoft.common.system.controller; + +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.common.system.util.EmailTemplateUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * 邮件测试控制器 + * 用于测试邮件模板功能 + * + * @author WebSoft + * @since 2024-08-28 + */ +@Api(tags = "邮件测试") +@RestController +@RequestMapping("/api/email-test") +public class EmailTestController extends BaseController { + + @Resource + private EmailTemplateUtil emailTemplateUtil; + + @ApiOperation("测试注册成功邮件") + @PostMapping("/register-success") + public ApiResult testRegisterSuccessEmail(@RequestParam String email) { + try { + emailTemplateUtil.sendRegisterSuccessEmail( + "测试用户", + "13800138000", + "TestPassword123", + email, + getTenantId() + ); + return success("注册成功邮件发送成功"); + } catch (Exception e) { + return fail("邮件发送失败: " + e.getMessage()); + } + } + + @ApiOperation("测试密码重置邮件") + @PostMapping("/password-reset") + public ApiResult testPasswordResetEmail(@RequestParam String email) { + try { + emailTemplateUtil.sendPasswordResetEmail( + "测试用户", + "13800138000", + "NewPassword456", + email, + getTenantId() + ); + return success("密码重置邮件发送成功"); + } catch (Exception e) { + return fail("邮件发送失败: " + e.getMessage()); + } + } + + @ApiOperation("测试通知邮件") + @PostMapping("/notification") + public ApiResult testNotificationEmail(@RequestParam String email, + @RequestParam(required = false) String title, + @RequestParam(required = false) String content) { + try { + String emailTitle = title != null ? title : "WebSoft系统通知"; + String emailContent = content != null ? content : "这是一条测试通知消息,用于验证邮件模板功能是否正常工作。"; + + emailTemplateUtil.sendNotificationEmailWithAction( + emailTitle, + emailContent, + email, + getTenantId(), + "https://www.gxwebsoft.com", + "访问官网" + ); + return success("通知邮件发送成功"); + } catch (Exception e) { + return fail("邮件发送失败: " + e.getMessage()); + } + } + + @ApiOperation("测试安全提醒邮件") + @PostMapping("/security-alert") + public ApiResult testSecurityAlertEmail(@RequestParam String email) { + try { + emailTemplateUtil.sendSecurityAlertEmail( + "测试用户", + "13800138000", + email, + getTenantId(), + "异地登录检测" + ); + return success("安全提醒邮件发送成功"); + } catch (Exception e) { + return fail("邮件发送失败: " + e.getMessage()); + } + } + + @ApiOperation("测试系统维护通知邮件") + @PostMapping("/maintenance") + public ApiResult testMaintenanceEmail(@RequestParam String email) { + try { + emailTemplateUtil.sendMaintenanceNotificationEmail( + email, + getTenantId(), + "2024年8月28日 23:00-24:00", + "1小时" + ); + return success("维护通知邮件发送成功"); + } catch (Exception e) { + return fail("邮件发送失败: " + e.getMessage()); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/system/util/EmailTemplateUtil.java b/src/main/java/com/gxwebsoft/common/system/util/EmailTemplateUtil.java index c96c339..c547387 100644 --- a/src/main/java/com/gxwebsoft/common/system/util/EmailTemplateUtil.java +++ b/src/main/java/com/gxwebsoft/common/system/util/EmailTemplateUtil.java @@ -157,7 +157,7 @@ public class EmailTemplateUtil { String title = "WebSoft账户安全提醒"; String content = "您的账户发生了以下安全事件:" + event + "。如果这不是您本人的操作,请立即联系客服并修改密码。"; String infoMessage = "为了保障您的账户安全,建议您定期修改密码并开启双重验证。"; - String actionUrl = "https://www.gxwebsoft.com/security"; + String actionUrl = "https://websoft.top/security"; String actionText = "查看安全设置"; sendNotificationEmail(title, content, email, tenantId, "尊敬的用户", infoMessage, actionUrl, actionText); @@ -191,7 +191,7 @@ public class EmailTemplateUtil { public void sendOrderStatusEmail(String username, String orderNo, String status, String email, Integer tenantId) { String title = "WebSoft订单状态更新"; String content = "您的订单 " + orderNo + " 状态已更新为:" + status + "。"; - String actionUrl = "https://www.gxwebsoft.com/orders/" + orderNo; + String actionUrl = "https://websoft.top/orders/" + orderNo; String actionText = "查看订单详情"; sendNotificationEmailWithAction(title, content, email, tenantId, actionUrl, actionText); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d9db9e9..c10f9e9 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -41,6 +41,11 @@ spring: jackson: time-zone: GMT+8 date-format: yyyy-MM-dd HH:mm:ss + serialization: + write-dates-as-timestamps: false + write-date-timestamps-as-nanoseconds: false + deserialization: + read-date-timestamps-as-nanoseconds: false # 设置上传文件大小 servlet: @@ -101,6 +106,10 @@ config: bucketDomain: https://oss.wsdns.cn aliyunDomain: https://oss-gxwebsoft.oss-cn-shenzhen.aliyuncs.com + # JWT配置 + jwt: + secret: websoft-jwt-secret-key-2025-dev-environment + expire: 86400 # token过期时间(秒) 24小时 # 证书配置 certificate: diff --git a/src/main/resources/email-templates.yml b/src/main/resources/email-templates.yml index dac93af..8e0d27d 100644 --- a/src/main/resources/email-templates.yml +++ b/src/main/resources/email-templates.yml @@ -6,19 +6,19 @@ email: # 品牌信息 brand: name: "WebSoft" - company: "南宁网宿信息科技有限公司" + company: "南宁市网宿信息科技有限公司" description: "企业级数字化解决方案" - website: "https://www.gxwebsoft.com" + website: "https://websoft.top" support_email: "170083662@qq.com" - logo_url: "https://www.gxwebsoft.com/logo.png" - + logo_url: "https://websoft.top/logo.png" + # 链接配置 links: - login: "https://www.gxwebsoft.com/login" - help: "https://www.gxwebsoft.com/help" - contact: "https://www.gxwebsoft.com/contact" - security: "https://www.gxwebsoft.com/security" - + login: "https://websoft.top/login" + help: "https://websoft.top/help" + contact: "https://websoft.top/contact" + security: "https://websoft.top/security" + # 模板列表 templates: register-success: @@ -26,37 +26,37 @@ email: description: "用户注册成功后发送的欢迎邮件" file: "register-success.html" subject: "恭喜!您的WebSoft账号已注册成功" - + password-reset: name: "密码重置邮件" description: "用户密码重置后发送的通知邮件" file: "password-reset.html" subject: "WebSoft密码重置通知" - + notification: name: "通用通知邮件" description: "系统通知、公告等通用邮件模板" file: "notification.html" subject: "WebSoft系统通知" - + security-alert: name: "安全提醒邮件" description: "账户安全相关的提醒邮件" file: "notification.html" subject: "WebSoft账户安全提醒" - + maintenance: name: "系统维护通知" description: "系统维护时发送的通知邮件" file: "notification.html" subject: "WebSoft系统维护通知" - + order-status: name: "订单状态更新" description: "订单状态变更时发送的通知邮件" file: "notification.html" subject: "WebSoft订单状态更新" - + # 邮件样式配置 styles: primary_color: "#667eea" @@ -65,7 +65,7 @@ email: warning_color: "#ffa502" danger_color: "#ff6b6b" info_color: "#4facfe" - + # 邮件发送配置 settings: retry_times: 3 diff --git a/src/main/resources/templates/notification.html b/src/main/resources/templates/notification.html index 48f19f5..affb1d4 100644 --- a/src/main/resources/templates/notification.html +++ b/src/main/resources/templates/notification.html @@ -240,19 +240,18 @@ diff --git a/src/main/resources/templates/password-reset.html b/src/main/resources/templates/password-reset.html index c1b0262..5d13a39 100644 --- a/src/main/resources/templates/password-reset.html +++ b/src/main/resources/templates/password-reset.html @@ -303,7 +303,7 @@