diff --git a/pom.xml b/pom.xml
index b51df0f..10b03ac 100644
--- a/pom.xml
+++ b/pom.xml
@@ -226,6 +226,13 @@
spring-boot-starter-security
+
+
+ com.mailersend
+ java-sdk
+ 1.4.1
+
+
org.hibernate.validator
diff --git a/src/main/java/com/yolo/keyborad/common/ErrorCode.java b/src/main/java/com/yolo/keyborad/common/ErrorCode.java
index 57e261a..1338d51 100644
--- a/src/main/java/com/yolo/keyborad/common/ErrorCode.java
+++ b/src/main/java/com/yolo/keyborad/common/ErrorCode.java
@@ -30,7 +30,9 @@ public enum ErrorCode {
FILE_NAME_ERROR(40002, "文件名错误"),
USER_NOT_FOUND(40401, "用户不存在"),
USER_INFO_UPDATE_FAILED(50002, "用户信息更新失败"),
- PASSWORD_OR_MAIL_ERROR(50003,"密码或邮箱错误" );
+ PASSWORD_OR_MAIL_ERROR(50003,"密码或邮箱错误" ),
+ SEND_MAIL_FAILED(50004,"邮件发送失败" ),
+ CONFIRM_PASSWORD_NOT_MATCH(50005,"重复密码不匹配" );
/**
* 状态码
*/
diff --git a/src/main/java/com/yolo/keyborad/config/AsyncConfig.java b/src/main/java/com/yolo/keyborad/config/AsyncConfig.java
new file mode 100644
index 0000000..34c7ed8
--- /dev/null
+++ b/src/main/java/com/yolo/keyborad/config/AsyncConfig.java
@@ -0,0 +1,10 @@
+package com.yolo.keyborad.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+@Configuration
+@EnableAsync
+public class AsyncConfig {
+
+}
diff --git a/src/main/java/com/yolo/keyborad/config/MailTaskExecutorConfig.java b/src/main/java/com/yolo/keyborad/config/MailTaskExecutorConfig.java
new file mode 100644
index 0000000..f2e4541
--- /dev/null
+++ b/src/main/java/com/yolo/keyborad/config/MailTaskExecutorConfig.java
@@ -0,0 +1,24 @@
+package com.yolo.keyborad.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+
+@Configuration
+public class MailTaskExecutorConfig {
+
+ @Bean(name = "mailTaskExecutor")
+ public Executor mailTaskExecutor() {
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+
+ executor.setCorePoolSize(2);
+ executor.setMaxPoolSize(5);
+ executor.setQueueCapacity(100);
+ executor.setThreadNamePrefix("mail-sender-");
+
+ executor.initialize();
+ return executor;
+ }
+}
diff --git a/src/main/java/com/yolo/keyborad/config/RedisConfig.java b/src/main/java/com/yolo/keyborad/config/RedisConfig.java
new file mode 100644
index 0000000..76fe01a
--- /dev/null
+++ b/src/main/java/com/yolo/keyborad/config/RedisConfig.java
@@ -0,0 +1,40 @@
+package com.yolo.keyborad.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * Redis配置类
+ *
+ * @author ziin
+ */
+@Configuration
+public class RedisConfig {
+
+ /**
+ * 配置StringRedisTemplate
+ * @param connectionFactory Redis连接工厂
+ * @return StringRedisTemplate实例
+ */
+ @Bean("redisTemplate")
+ public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
+ StringRedisTemplate template = new StringRedisTemplate();
+ template.setConnectionFactory(connectionFactory);
+
+ // 设置key序列化方式
+ template.setKeySerializer(new StringRedisSerializer());
+ // 设置value序列化方式
+ template.setValueSerializer(new StringRedisSerializer());
+ // 设置hash key序列化方式
+ template.setHashKeySerializer(new StringRedisSerializer());
+ // 设置hash value序列化方式
+ template.setHashValueSerializer(new StringRedisSerializer());
+
+ template.afterPropertiesSet();
+ return template;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yolo/keyborad/config/SaTokenConfigure.java b/src/main/java/com/yolo/keyborad/config/SaTokenConfigure.java
index fbe8909..b2fa793 100644
--- a/src/main/java/com/yolo/keyborad/config/SaTokenConfigure.java
+++ b/src/main/java/com/yolo/keyborad/config/SaTokenConfigure.java
@@ -74,7 +74,8 @@ public class SaTokenConfigure implements WebMvcConfigurer {
"/user/login",
"/character/listByUser",
"/user/updateInfo",
- "/user/detail"
+ "/user/detail",
+ "/user/register"
};
}
@Bean
diff --git a/src/main/java/com/yolo/keyborad/config/SecurityConfig.java b/src/main/java/com/yolo/keyborad/config/SecurityConfig.java
index 33c05d1..9a35d9c 100644
--- a/src/main/java/com/yolo/keyborad/config/SecurityConfig.java
+++ b/src/main/java/com/yolo/keyborad/config/SecurityConfig.java
@@ -2,8 +2,11 @@ package com.yolo.keyborad.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@@ -13,4 +16,21 @@ public class SecurityConfig {
// strength(cost)默认 10,够用了;可以视情况调高,比如 12
return new BCryptPasswordEncoder();
}
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http
+ // REST 接口通常会关掉 csrf(前后端分离的话)
+ .csrf(AbstractHttpConfigurer::disable)
+ // 配置请求权限
+ .authorizeHttpRequests(auth -> auth
+ .anyRequest().permitAll() // 所有请求都放行
+ )
+ // 关掉默认的登录页表单
+ .formLogin(AbstractHttpConfigurer::disable)
+ // 关掉 httpBasic 弹窗登录
+ .httpBasic(AbstractHttpConfigurer::disable);
+
+ return http.build();
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/yolo/keyborad/controller/UserController.java b/src/main/java/com/yolo/keyborad/controller/UserController.java
index 196e6af..869a7ac 100644
--- a/src/main/java/com/yolo/keyborad/controller/UserController.java
+++ b/src/main/java/com/yolo/keyborad/controller/UserController.java
@@ -7,6 +7,7 @@ import com.yolo.keyborad.common.ResultUtils;
import com.yolo.keyborad.model.dto.AppleLoginReq;
import com.yolo.keyborad.model.dto.user.KeyboardUserReq;
import com.yolo.keyborad.model.dto.user.UserLoginDTO;
+import com.yolo.keyborad.model.dto.user.UserRegisterDTO;
import com.yolo.keyborad.model.entity.KeyboardUser;
import com.yolo.keyborad.model.vo.user.KeyboardUserInfoRespVO;
import com.yolo.keyborad.model.vo.user.KeyboardUserRespVO;
@@ -78,4 +79,11 @@ public class UserController {
KeyboardUser keyboardUser = userService.getById(loginId);
return ResultUtils.success(BeanUtil.copyProperties(keyboardUser, KeyboardUserInfoRespVO.class));
}
+
+ @PostMapping("/register")
+ @Operation(summary = "用户注册",description = "用户注册接口")
+ public BaseResponse register(@RequestBody UserRegisterDTO userRegisterDTO) {
+ userService.userRegister(userRegisterDTO);
+ return ResultUtils.success(true);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/yolo/keyborad/model/dto/user/UserRegisterDTO.java b/src/main/java/com/yolo/keyborad/model/dto/user/UserRegisterDTO.java
new file mode 100644
index 0000000..a2da38a
--- /dev/null
+++ b/src/main/java/com/yolo/keyborad/model/dto/user/UserRegisterDTO.java
@@ -0,0 +1,17 @@
+package com.yolo.keyborad.model.dto.user;
+
+import lombok.Data;
+
+/*
+ * @author: ziin
+ * @date: 2025/12/3 20:56
+ */
+@Data
+public class UserRegisterDTO {
+
+ private String mailAddress;
+
+ private String password;
+
+ private String passwordConfirm;
+}
diff --git a/src/main/java/com/yolo/keyborad/service/UserService.java b/src/main/java/com/yolo/keyborad/service/UserService.java
index 5b557b0..f0c9508 100644
--- a/src/main/java/com/yolo/keyborad/service/UserService.java
+++ b/src/main/java/com/yolo/keyborad/service/UserService.java
@@ -3,6 +3,7 @@ package com.yolo.keyborad.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yolo.keyborad.model.dto.user.KeyboardUserReq;
import com.yolo.keyborad.model.dto.user.UserLoginDTO;
+import com.yolo.keyborad.model.dto.user.UserRegisterDTO;
import com.yolo.keyborad.model.entity.KeyboardUser;
import com.yolo.keyborad.model.vo.user.KeyboardUserRespVO;
@@ -19,4 +20,7 @@ public interface UserService extends IService {
KeyboardUserRespVO login(UserLoginDTO userLoginDTO);
Boolean updateUserInfo(KeyboardUserReq keyboardUser);
+
+ Boolean userRegister(UserRegisterDTO userRegisterDTO);
+
}
diff --git a/src/main/java/com/yolo/keyborad/service/impl/AppleServiceImpl.java b/src/main/java/com/yolo/keyborad/service/impl/AppleServiceImpl.java
index 30d7919..98b7fc1 100644
--- a/src/main/java/com/yolo/keyborad/service/impl/AppleServiceImpl.java
+++ b/src/main/java/com/yolo/keyborad/service/impl/AppleServiceImpl.java
@@ -34,7 +34,6 @@ import java.util.Objects;
*/
@Service
@Slf4j
-@AllArgsConstructor
public class AppleServiceImpl implements IAppleService {
@Resource
diff --git a/src/main/java/com/yolo/keyborad/service/impl/UserServiceImpl.java b/src/main/java/com/yolo/keyborad/service/impl/UserServiceImpl.java
index d6feea1..5be19a0 100644
--- a/src/main/java/com/yolo/keyborad/service/impl/UserServiceImpl.java
+++ b/src/main/java/com/yolo/keyborad/service/impl/UserServiceImpl.java
@@ -12,13 +12,21 @@ import com.yolo.keyborad.exception.BusinessException;
import com.yolo.keyborad.mapper.KeyboardUserMapper;
import com.yolo.keyborad.model.dto.user.KeyboardUserReq;
import com.yolo.keyborad.model.dto.user.UserLoginDTO;
+import com.yolo.keyborad.model.dto.user.UserRegisterDTO;
import com.yolo.keyborad.model.entity.KeyboardUser;
import com.yolo.keyborad.model.vo.user.KeyboardUserRespVO;
import com.yolo.keyborad.service.UserService;
+import com.yolo.keyborad.utils.RedisUtil;
+import com.yolo.keyborad.utils.SendMailUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.concurrent.TimeUnit;
/*
* @author: ziin
@@ -32,7 +40,14 @@ public class UserServiceImpl extends ServiceImpl 0;
+ }
}
diff --git a/src/main/java/com/yolo/keyborad/utils/RedisUtil.java b/src/main/java/com/yolo/keyborad/utils/RedisUtil.java
new file mode 100644
index 0000000..db1a6e0
--- /dev/null
+++ b/src/main/java/com/yolo/keyborad/utils/RedisUtil.java
@@ -0,0 +1,1359 @@
+package com.yolo.keyborad.utils;
+
+import jakarta.annotation.Resource;
+import org.springframework.data.redis.connection.DataType;
+import org.springframework.data.redis.core.Cursor;
+import org.springframework.data.redis.core.ScanOptions;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
+import org.springframework.stereotype.Component;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Redis工具类*/
+@Component
+public class RedisUtil {
+
+ @Resource
+ private StringRedisTemplate redisTemplate;
+
+ public void setRedisTemplate(StringRedisTemplate redisTemplate) {
+ this.redisTemplate = redisTemplate;
+ }
+
+ public StringRedisTemplate getRedisTemplate() {
+ return this.redisTemplate;
+ }
+
+ /** -------------------key相关操作--------------------- */
+
+ /**
+ * 删除key
+ *
+ * @param key
+ */
+ public void delete(String key) {
+ redisTemplate.delete(key);
+ }
+
+ /**
+ * 批量删除key
+ *
+ * @param keys
+ */
+ public void delete(Collection keys) {
+ redisTemplate.delete(keys);
+ }
+
+ /**
+ * 序列化key
+ *
+ * @param key
+ * @return
+ */
+ public byte[] dump(String key) {
+ return redisTemplate.dump(key);
+ }
+
+ /**
+ * 是否存在key
+ *
+ * @param key
+ * @return
+ */
+ public Boolean hasKey(String key) {
+ return redisTemplate.hasKey(key);
+ }
+
+ /**
+ * 设置过期时间
+ *
+ * @param key
+ * @param timeout
+ * @param unit
+ * @return
+ */
+ public Boolean expire(String key, long timeout, TimeUnit unit) {
+ return redisTemplate.expire(key, timeout, unit);
+ }
+
+ /**
+ * 设置过期时间
+ *
+ * @param key
+ * @param date
+ * @return
+ */
+ public Boolean expireAt(String key, Date date) {
+ return redisTemplate.expireAt(key, date);
+ }
+
+ /**
+ * 查找匹配的key
+ *
+ * @param pattern
+ * @return
+ */
+ public Set keys(String pattern) {
+ return redisTemplate.keys(pattern);
+ }
+
+ /**
+ * 将当前数据库的 key 移动到给定的数据库 db 当中
+ *
+ * @param key
+ * @param dbIndex
+ * @return
+ */
+ public Boolean move(String key, int dbIndex) {
+ return redisTemplate.move(key, dbIndex);
+ }
+
+ /**
+ * 移除 key 的过期时间,key 将持久保持
+ *
+ * @param key
+ * @return
+ */
+ public Boolean persist(String key) {
+ return redisTemplate.persist(key);
+ }
+
+ /**
+ * 返回 key 的剩余的过期时间
+ *
+ * @param key
+ * @param unit
+ * @return
+ */
+ public Long getExpire(String key, TimeUnit unit) {
+ return redisTemplate.getExpire(key, unit);
+ }
+
+ /**
+ * 返回 key 的剩余的过期时间
+ *
+ * @param key
+ * @return
+ */
+ public Long getExpire(String key) {
+ return redisTemplate.getExpire(key);
+ }
+
+ /**
+ * 从当前数据库中随机返回一个 key
+ *
+ * @return
+ */
+ public String randomKey() {
+ return redisTemplate.randomKey();
+ }
+
+ /**
+ * 修改 key 的名称
+ *
+ * @param oldKey
+ * @param newKey
+ */
+ public void rename(String oldKey, String newKey) {
+ redisTemplate.rename(oldKey, newKey);
+ }
+
+ /**
+ * 仅当 newkey 不存在时,将 oldKey 改名为 newkey
+ *
+ * @param oldKey
+ * @param newKey
+ * @return
+ */
+ public Boolean renameIfAbsent(String oldKey, String newKey) {
+ return redisTemplate.renameIfAbsent(oldKey, newKey);
+ }
+
+ /**
+ * 返回 key 所储存的值的类型
+ *
+ * @param key
+ * @return
+ */
+ public DataType type(String key) {
+ return redisTemplate.type(key);
+ }
+
+ /** -------------------string相关操作--------------------- */
+
+ /**
+ * 设置指定 key 的值
+ * @param key
+ * @param value
+ */
+ public void set(String key, String value) {
+ redisTemplate.opsForValue().set(key, value);
+ }
+
+ /**
+ * 获取指定 key 的值
+ * @param key
+ * @return
+ */
+ public String get(String key) {
+ return redisTemplate.opsForValue().get(key);
+ }
+
+ /**
+ * 返回 key 中字符串值的子字符
+ * @param key
+ * @param start
+ * @param end
+ * @return
+ */
+ public String getRange(String key, long start, long end) {
+ return redisTemplate.opsForValue().get(key, start, end);
+ }
+
+ /**
+ * 将给定 key 的值设为 value ,并返回 key 的旧值(old value)
+ *
+ * @param key
+ * @param value
+ * @return
+ */
+ public String getAndSet(String key, String value) {
+ return redisTemplate.opsForValue().getAndSet(key, value);
+ }
+
+ /**
+ * 对 key 所储存的字符串值,获取指定偏移量上的位(bit)
+ *
+ * @param key
+ * @param offset
+ * @return
+ */
+ public Boolean getBit(String key, long offset) {
+ return redisTemplate.opsForValue().getBit(key, offset);
+ }
+
+ /**
+ * 批量获取
+ *
+ * @param keys
+ * @return
+ */
+ public List multiGet(Collection keys) {
+ return redisTemplate.opsForValue().multiGet(keys);
+ }
+
+ /**
+ * 设置ASCII码, 字符串'a'的ASCII码是97, 转为二进制是'01100001', 此方法是将二进制第offset位值变为value
+ *
+ * @param key 位置
+ * @param value
+ * 值,true为1, false为0
+ * @return
+ */
+ public boolean setBit(String key, long offset, boolean value) {
+ return redisTemplate.opsForValue().setBit(key, offset, value);
+ }
+
+ /**
+ * 将值 value 关联到 key ,并将 key 的过期时间设为 timeout
+ *
+ * @param key
+ * @param value
+ * @param timeout
+ * 过期时间
+ * @param unit
+ * 时间单位, 天:TimeUnit.DAYS 小时:TimeUnit.HOURS 分钟:TimeUnit.MINUTES
+ * 秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS
+ */
+ public void setEx(String key, String value, long timeout, TimeUnit unit) {
+ redisTemplate.opsForValue().set(key, value, timeout, unit);
+ }
+
+ /**
+ * 只有在 key 不存在时设置 key 的值
+ *
+ * @param key
+ * @param value
+ * @return 之前已经存在返回false,不存在返回true
+ */
+ public boolean setIfAbsent(String key, String value) {
+ return redisTemplate.opsForValue().setIfAbsent(key, value);
+ }
+
+ /**
+ * 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始
+ *
+ * @param key
+ * @param value
+ * @param offset
+ * 从指定位置开始覆写
+ */
+ public void setRange(String key, String value, long offset) {
+ redisTemplate.opsForValue().set(key, value, offset);
+ }
+
+ /**
+ * 获取字符串的长度
+ *
+ * @param key
+ * @return
+ */
+ public Long size(String key) {
+ return redisTemplate.opsForValue().size(key);
+ }
+
+ /**
+ * 批量添加
+ *
+ * @param maps
+ */
+ public void multiSet(Map maps) {
+ redisTemplate.opsForValue().multiSet(maps);
+ }
+
+ /**
+ * 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在
+ *
+ * @param maps
+ * @return 之前已经存在返回false,不存在返回true
+ */
+ public boolean multiSetIfAbsent(Map maps) {
+ return redisTemplate.opsForValue().multiSetIfAbsent(maps);
+ }
+
+ /**
+ * 增加(自增长), 负数则为自减
+ *
+ * @param key
+ * @return
+ */
+ public Long incrBy(String key, long increment) {
+ return redisTemplate.opsForValue().increment(key, increment);
+ }
+
+ /**
+ *
+ * @param key
+ * @return
+ */
+ public Double incrByFloat(String key, double increment) {
+ return redisTemplate.opsForValue().increment(key, increment);
+ }
+
+ /**
+ * 追加到末尾
+ *
+ * @param key
+ * @param value
+ * @return
+ */
+ public Integer append(String key, String value) {
+ return redisTemplate.opsForValue().append(key, value);
+ }
+
+ /** -------------------hash相关操作------------------------- */
+
+ /**
+ * 获取存储在哈希表中指定字段的值
+ *
+ * @param key
+ * @param field
+ * @return
+ */
+ public Object hGet(String key, String field) {
+ return redisTemplate.opsForHash().get(key, field);
+ }
+
+ /**
+ * 获取所有给定字段的值
+ *
+ * @param key
+ * @return
+ */
+ public Map