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 hGetAll(String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 获取所有给定字段的值 + * + * @param key + * @param fields + * @return + */ + public List hMultiGet(String key, Collection fields) { + return redisTemplate.opsForHash().multiGet(key, fields); + } + + public void hPut(String key, String hashKey, String value) { + redisTemplate.opsForHash().put(key, hashKey, value); + } + + public void hPutAll(String key, Map maps) { + redisTemplate.opsForHash().putAll(key, maps); + } + + /** + * 仅当hashKey不存在时才设置 + * + * @param key + * @param hashKey + * @param value + * @return + */ + public Boolean hPutIfAbsent(String key, String hashKey, String value) { + return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value); + } + + /** + * 删除一个或多个哈希表字段 + * + * @param key + * @param fields + * @return + */ + public Long hDelete(String key, Object... fields) { + return redisTemplate.opsForHash().delete(key, fields); + } + + /** + * 查看哈希表 key 中,指定的字段是否存在 + * + * @param key + * @param field + * @return + */ + public boolean hExists(String key, String field) { + return redisTemplate.opsForHash().hasKey(key, field); + } + + /** + * 为哈希表 key 中的指定字段的整数值加上增量 increment + * + * @param key + * @param field + * @param increment + * @return + */ + public Long hIncrBy(String key, Object field, long increment) { + return redisTemplate.opsForHash().increment(key, field, increment); + } + + /** + * 为哈希表 key 中的指定字段的整数值加上增量 increment + * + * @param key + * @param field + * @param delta + * @return + */ + public Double hIncrByFloat(String key, Object field, double delta) { + return redisTemplate.opsForHash().increment(key, field, delta); + } + + /** + * 获取所有哈希表中的字段 + * + * @param key + * @return + */ + public Set hKeys(String key) { + return redisTemplate.opsForHash().keys(key); + } + + /** + * 获取哈希表中字段的数量 + * + * @param key + * @return + */ + public Long hSize(String key) { + return redisTemplate.opsForHash().size(key); + } + + /** + * 获取哈希表中所有值 + * + * @param key + * @return + */ + public List hValues(String key) { + return redisTemplate.opsForHash().values(key); + } + + /** + * 迭代哈希表中的键值对 + * + * @param key + * @param options + * @return + */ + public Cursor> hScan(String key, ScanOptions options) { + return redisTemplate.opsForHash().scan(key, options); + } + + /** ------------------------list相关操作---------------------------- */ + + /** + * 通过索引获取列表中的元素 + * + * @param key + * @param index + * @return + */ + public String lIndex(String key, long index) { + return redisTemplate.opsForList().index(key, index); + } + + /** + * 获取列表指定范围内的元素 + * + * @param key + * @param start + * 开始位置, 0是开始位置 + * @param end + * 结束位置, -1返回所有 + * @return + */ + public List lRange(String key, long start, long end) { + return redisTemplate.opsForList().range(key, start, end); + } + + /** + * 存储在list头部 + * + * @param key + * @param value + * @return + */ + public Long lLeftPush(String key, String value) { + return redisTemplate.opsForList().leftPush(key, value); + } + + /** + * + * @param key + * @param value + * @return + */ + public Long lLeftPushAll(String key, String... value) { + return redisTemplate.opsForList().leftPushAll(key, value); + } + + /** + * + * @param key + * @param value + * @return + */ + public Long lLeftPushAll(String key, Collection value) { + return redisTemplate.opsForList().leftPushAll(key, value); + } + + /** + * 当list存在的时候才加入 + * + * @param key + * @param value + * @return + */ + public Long lLeftPushIfPresent(String key, String value) { + return redisTemplate.opsForList().leftPushIfPresent(key, value); + } + + /** + * 如果pivot存在,再pivot前面添加 + * + * @param key + * @param pivot + * @param value + * @return + */ + public Long lLeftPush(String key, String pivot, String value) { + return redisTemplate.opsForList().leftPush(key, pivot, value); + } + + /** + * + * @param key + * @param value + * @return + */ + public Long lRightPush(String key, String value) { + return redisTemplate.opsForList().rightPush(key, value); + } + + /** + * + * @param key + * @param value + * @return + */ + public Long lRightPushAll(String key, String... value) { + return redisTemplate.opsForList().rightPushAll(key, value); + } + + /** + * + * @param key + * @param value + * @return + */ + public Long lRightPushAll(String key, Collection value) { + return redisTemplate.opsForList().rightPushAll(key, value); + } + + /** + * 为已存在的列表添加值 + * + * @param key + * @param value + * @return + */ + public Long lRightPushIfPresent(String key, String value) { + return redisTemplate.opsForList().rightPushIfPresent(key, value); + } + + /** + * 在pivot元素的右边添加值 + * + * @param key + * @param pivot + * @param value + * @return + */ + public Long lRightPush(String key, String pivot, String value) { + return redisTemplate.opsForList().rightPush(key, pivot, value); + } + + /** + * 通过索引设置列表元素的值 + * + * @param key + * @param index + * 位置 + * @param value + */ + public void lSet(String key, long index, String value) { + redisTemplate.opsForList().set(key, index, value); + } + + /** + * 移出并获取列表的第一个元素 + * + * @param key + * @return 删除的元素 + */ + public String lLeftPop(String key) { + return redisTemplate.opsForList().leftPop(key); + } + + /** + * 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 + * + * @param key + * @param timeout + * 等待时间 + * @param unit + * 时间单位 + * @return + */ + public String lBLeftPop(String key, long timeout, TimeUnit unit) { + return redisTemplate.opsForList().leftPop(key, timeout, unit); + } + + /** + * 移除并获取列表最后一个元素 + * + * @param key + * @return 删除的元素 + */ + public String lRightPop(String key) { + return redisTemplate.opsForList().rightPop(key); + } + + /** + * 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 + * + * @param key + * @param timeout + * 等待时间 + * @param unit + * 时间单位 + * @return + */ + public String lBRightPop(String key, long timeout, TimeUnit unit) { + return redisTemplate.opsForList().rightPop(key, timeout, unit); + } + + /** + * 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 + * + * @param sourceKey + * @param destinationKey + * @return + */ + public String lRightPopAndLeftPush(String sourceKey, String destinationKey) { + return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, + destinationKey); + } + + /** + * 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 + * + * @param sourceKey + * @param destinationKey + * @param timeout + * @param unit + * @return + */ + public String lBRightPopAndLeftPush(String sourceKey, String destinationKey, + long timeout, TimeUnit unit) { + return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, + destinationKey, timeout, unit); + } + + /** + * 删除集合中值等于value得元素 + * + * @param key + * @param index + * index=0, 删除所有值等于value的元素; index>0, 从头部开始删除第一个值等于value的元素; + * index<0, 从尾部开始删除第一个值等于value的元素; + * @param value + * @return + */ + public Long lRemove(String key, long index, String value) { + return redisTemplate.opsForList().remove(key, index, value); + } + + /** + * 裁剪list + * + * @param key + * @param start + * @param end + */ + public void lTrim(String key, long start, long end) { + redisTemplate.opsForList().trim(key, start, end); + } + + /** + * 获取列表长度 + * + * @param key + * @return + */ + public Long lLen(String key) { + return redisTemplate.opsForList().size(key); + } + + /** --------------------set相关操作-------------------------- */ + + /** + * set添加元素 + * + * @param key + * @param values + * @return + */ + public Long sAdd(String key, String... values) { + return redisTemplate.opsForSet().add(key, values); + } + + /** + * set移除元素 + * + * @param key + * @param values + * @return + */ + public Long sRemove(String key, Object... values) { + return redisTemplate.opsForSet().remove(key, values); + } + + /** + * 移除并返回集合的一个随机元素 + * + * @param key + * @return + */ + public String sPop(String key) { + return redisTemplate.opsForSet().pop(key); + } + + /** + * 将元素value从一个集合移到另一个集合 + * + * @param key + * @param value + * @param destKey + * @return + */ + public Boolean sMove(String key, String value, String destKey) { + return redisTemplate.opsForSet().move(key, value, destKey); + } + + /** + * 获取集合的大小 + * + * @param key + * @return + */ + public Long sSize(String key) { + return redisTemplate.opsForSet().size(key); + } + + /** + * 判断集合是否包含value + * + * @param key + * @param value + * @return + */ + public Boolean sIsMember(String key, Object value) { + return redisTemplate.opsForSet().isMember(key, value); + } + + /** + * 获取两个集合的交集 + * + * @param key + * @param otherKey + * @return + */ + public Set sIntersect(String key, String otherKey) { + return redisTemplate.opsForSet().intersect(key, otherKey); + } + + /** + * 获取key集合与多个集合的交集 + * + * @param key + * @param otherKeys + * @return + */ + public Set sIntersect(String key, Collection otherKeys) { + return redisTemplate.opsForSet().intersect(key, otherKeys); + } + + /** + * key集合与otherKey集合的交集存储到destKey集合中 + * + * @param key + * @param otherKey + * @param destKey + * @return + */ + public Long sIntersectAndStore(String key, String otherKey, String destKey) { + return redisTemplate.opsForSet().intersectAndStore(key, otherKey, + destKey); + } + + /** + * key集合与多个集合的交集存储到destKey集合中 + * + * @param key + * @param otherKeys + * @param destKey + * @return + */ + public Long sIntersectAndStore(String key, Collection otherKeys, + String destKey) { + return redisTemplate.opsForSet().intersectAndStore(key, otherKeys, + destKey); + } + + /** + * 获取两个集合的并集 + * + * @param key + * @param otherKeys + * @return + */ + public Set sUnion(String key, String otherKeys) { + return redisTemplate.opsForSet().union(key, otherKeys); + } + + /** + * 获取key集合与多个集合的并集 + * + * @param key + * @param otherKeys + * @return + */ + public Set sUnion(String key, Collection otherKeys) { + return redisTemplate.opsForSet().union(key, otherKeys); + } + + /** + * key集合与otherKey集合的并集存储到destKey中 + * + * @param key + * @param otherKey + * @param destKey + * @return + */ + public Long sUnionAndStore(String key, String otherKey, String destKey) { + return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey); + } + + /** + * key集合与多个集合的并集存储到destKey中 + * + * @param key + * @param otherKeys + * @param destKey + * @return + */ + public Long sUnionAndStore(String key, Collection otherKeys, + String destKey) { + return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey); + } + + /** + * 获取两个集合的差集 + * + * @param key + * @param otherKey + * @return + */ + public Set sDifference(String key, String otherKey) { + return redisTemplate.opsForSet().difference(key, otherKey); + } + + /** + * 获取key集合与多个集合的差集 + * + * @param key + * @param otherKeys + * @return + */ + public Set sDifference(String key, Collection otherKeys) { + return redisTemplate.opsForSet().difference(key, otherKeys); + } + + /** + * key集合与otherKey集合的差集存储到destKey中 + * + * @param key + * @param otherKey + * @param destKey + * @return + */ + public Long sDifference(String key, String otherKey, String destKey) { + return redisTemplate.opsForSet().differenceAndStore(key, otherKey, + destKey); + } + + /** + * key集合与多个集合的差集存储到destKey中 + * + * @param key + * @param otherKeys + * @param destKey + * @return + */ + public Long sDifference(String key, Collection otherKeys, + String destKey) { + return redisTemplate.opsForSet().differenceAndStore(key, otherKeys, + destKey); + } + + /** + * 获取集合所有元素 + * + * @param key + * @return + */ + public Set setMembers(String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 随机获取集合中的一个元素 + * + * @param key + * @return + */ + public String sRandomMember(String key) { + return redisTemplate.opsForSet().randomMember(key); + } + + /** + * 随机获取集合中count个元素 + * + * @param key + * @param count + * @return + */ + public List sRandomMembers(String key, long count) { + return redisTemplate.opsForSet().randomMembers(key, count); + } + + /** + * 随机获取集合中count个元素并且去除重复的 + * + * @param key + * @param count + * @return + */ + public Set sDistinctRandomMembers(String key, long count) { + return redisTemplate.opsForSet().distinctRandomMembers(key, count); + } + + /** + * + * @param key + * @param options + * @return + */ + public Cursor sScan(String key, ScanOptions options) { + return redisTemplate.opsForSet().scan(key, options); + } + + /**------------------zSet相关操作--------------------------------*/ + + /** + * 添加元素,有序集合是按照元素的score值由小到大排列 + * + * @param key + * @param value + * @param score + * @return + */ + public Boolean zAdd(String key, String value, double score) { + return redisTemplate.opsForZSet().add(key, value, score); + } + + /** + * + * @param key + * @param values + * @return + */ + public Long zAdd(String key, Set> values) { + return redisTemplate.opsForZSet().add(key, values); + } + + /** + * + * @param key + * @param values + * @return + */ + public Long zRemove(String key, Object... values) { + return redisTemplate.opsForZSet().remove(key, values); + } + + /** + * 增加元素的score值,并返回增加后的值 + * + * @param key + * @param value + * @param delta + * @return + */ + public Double zIncrementScore(String key, String value, double delta) { + return redisTemplate.opsForZSet().incrementScore(key, value, delta); + } + + /** + * 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列 + * + * @param key + * @param value + * @return 0表示第一位 + */ + public Long zRank(String key, Object value) { + return redisTemplate.opsForZSet().rank(key, value); + } + + /** + * 返回元素在集合的排名,按元素的score值由大到小排列 + * + * @param key + * @param value + * @return + */ + public Long zReverseRank(String key, Object value) { + return redisTemplate.opsForZSet().reverseRank(key, value); + } + + /** + * 获取集合的元素, 从小到大排序 + * + * @param key + * @param start + * 开始位置 + * @param end + * 结束位置, -1查询所有 + * @return + */ + public Set zRange(String key, long start, long end) { + return redisTemplate.opsForZSet().range(key, start, end); + } + + /** + * 获取集合元素, 并且把score值也获取 + * + * @param key + * @param start + * @param end + * @return + */ + public Set> zRangeWithScores(String key, long start, + long end) { + return redisTemplate.opsForZSet().rangeWithScores(key, start, end); + } + + /** + * 根据Score值查询集合元素 + * + * @param key + * @param min + * 最小值 + * @param max + * 最大值 + * @return + */ + public Set zRangeByScore(String key, double min, double max) { + return redisTemplate.opsForZSet().rangeByScore(key, min, max); + } + + /** + * 根据Score值查询集合元素, 从小到大排序 + * + * @param key + * @param min + * 最小值 + * @param max + * 最大值 + * @return + */ + public Set> zRangeByScoreWithScores(String key, + double min, double max) { + return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max); + } + + /** + * + * @param key + * @param min + * @param max + * @param start + * @param end + * @return + */ + public Set> zRangeByScoreWithScores(String key, + double min, double max, long start, long end) { + return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max, + start, end); + } + + /** + * 获取集合的元素, 从大到小排序 + * + * @param key + * @param start + * @param end + * @return + */ + public Set zReverseRange(String key, long start, long end) { + return redisTemplate.opsForZSet().reverseRange(key, start, end); + } + + /** + * 获取集合的元素, 从大到小排序, 并返回score值 + * + * @param key + * @param start + * @param end + * @return + */ + public Set> zReverseRangeWithScores(String key, + long start, long end) { + return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, + end); + } + + /** + * 根据Score值查询集合元素, 从大到小排序 + * + * @param key + * @param min + * @param max + * @return + */ + public Set zReverseRangeByScore(String key, double min, + double max) { + return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max); + } + + /** + * 根据Score值查询集合元素, 从大到小排序 + * + * @param key + * @param min + * @param max + * @return + */ + public Set> zReverseRangeByScoreWithScores( + String key, double min, double max) { + return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, + min, max); + } + + /** + * + * @param key + * @param min + * @param max + * @param start + * @param end + * @return + */ + public Set zReverseRangeByScore(String key, double min, + double max, long start, long end) { + return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max, + start, end); + } + + /** + * 根据score值获取集合元素数量 + * + * @param key + * @param min + * @param max + * @return + */ + public Long zCount(String key, double min, double max) { + return redisTemplate.opsForZSet().count(key, min, max); + } + + /** + * 获取集合大小 + * + * @param key + * @return + */ + public Long zSize(String key) { + return redisTemplate.opsForZSet().size(key); + } + + /** + * 获取集合大小 + * + * @param key + * @return + */ + public Long zZCard(String key) { + return redisTemplate.opsForZSet().zCard(key); + } + + /** + * 获取集合中value元素的score值 + * + * @param key + * @param value + * @return + */ + public Double zScore(String key, Object value) { + return redisTemplate.opsForZSet().score(key, value); + } + + /** + * 移除指定索引位置的成员 + * + * @param key + * @param start + * @param end + * @return + */ + public Long zRemoveRange(String key, long start, long end) { + return redisTemplate.opsForZSet().removeRange(key, start, end); + } + + /** + * 根据指定的score值的范围来移除成员 + * + * @param key + * @param min + * @param max + * @return + */ + public Long zRemoveRangeByScore(String key, double min, double max) { + return redisTemplate.opsForZSet().removeRangeByScore(key, min, max); + } + + /** + * 获取key和otherKey的并集并存储在destKey中 + * + * @param key + * @param otherKey + * @param destKey + * @return + */ + public Long zUnionAndStore(String key, String otherKey, String destKey) { + return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey); + } + + /** + * + * @param key + * @param otherKeys + * @param destKey + * @return + */ + public Long zUnionAndStore(String key, Collection otherKeys, + String destKey) { + return redisTemplate.opsForZSet() + .unionAndStore(key, otherKeys, destKey); + } + + /** + * 交集 + * + * @param key + * @param otherKey + * @param destKey + * @return + */ + public Long zIntersectAndStore(String key, String otherKey, + String destKey) { + return redisTemplate.opsForZSet().intersectAndStore(key, otherKey, + destKey); + } + + /** + * 交集 + * + * @param key + * @param otherKeys + * @param destKey + * @return + */ + public Long zIntersectAndStore(String key, Collection otherKeys, + String destKey) { + return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys, + destKey); + } + + /** + * 匹配获取键值对,ScanOptions.NONE为获取全部键值对;ScanOptions.scanOptions().match("C").build()匹配获取键位map1的键值对,不能模糊匹配。 + * @param key + * @param options + * @return + */ + public Cursor> zScan(String key, ScanOptions options) { + return redisTemplate.opsForZSet().scan(key, options); + } +} \ No newline at end of file diff --git a/src/main/java/com/yolo/keyborad/utils/SendMailUtils.java b/src/main/java/com/yolo/keyborad/utils/SendMailUtils.java new file mode 100644 index 0000000..b7e288e --- /dev/null +++ b/src/main/java/com/yolo/keyborad/utils/SendMailUtils.java @@ -0,0 +1,57 @@ +package com.yolo.keyborad.utils; + +import com.mailersend.sdk.MailerSend; +import com.mailersend.sdk.MailerSendResponse; +import com.mailersend.sdk.Recipient; +import com.mailersend.sdk.emails.Email; +import com.mailersend.sdk.exceptions.MailerSendException; +import com.yolo.keyborad.common.ErrorCode; +import com.yolo.keyborad.exception.BusinessException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/* + * @author: ziin + * @date: 2025/12/3 20:43 + */ +@Component +@Slf4j +public class SendMailUtils { + + @Value("${mail_access_token}") + private String accessToken; + + @Async("mailTaskExecutor") + public void sendEmail(String userName,String userMail,Integer code ) { + + Email email = new Email(); + + email.setFrom("verify code", "MS_JqR6MO@no-replay.loveamorkey.com"); + + Recipient recipient = new Recipient(userName, userMail); + + email.addRecipient(userName,userMail); + + email.setSubject("no-replay"); + + email.setTemplateId("k68zxl28q79lj905"); + + email.addPersonalization(recipient, "code", code); + + MailerSend ms = new MailerSend(); + + ms.setToken(accessToken); + + try { + MailerSendResponse response = ms.emails().send(email); + log.info("邮件发送成功 messageId={}", response.messageId); + } catch (MailerSendException e) { + log.error("邮件发送失败: {}", e.getMessage()); + // 异步任务不能再抛业务异常,避免影响主业务流程 + // 如果需要,你可以把失败记录写入 DB 或重试队列 + } + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 01c7aff..3975393 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -48,4 +48,6 @@ mybatis-plus: logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) appid: loveKeyboard -appsecret: kZJM39HYvhxwbJkG1fmquQRVkQiLAh2H \ No newline at end of file +appsecret: kZJM39HYvhxwbJkG1fmquQRVkQiLAh2H + +mail_access_token: mlsn.f2aafcaf4bccf01529f8636fa13a2c16c33a934d5e14be3adb0cc21c2fe40fe1 \ No newline at end of file