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 @@