refactor:重构项目

master
flying_pig 1 year ago
parent 3f6abd6793
commit dce77d64af

@ -7,12 +7,18 @@
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="music-service" />
<module name="idempotent" />
<module name="sentinel" />
<module name="auth-service" />
<module name="common" />
<module name="cache" />
<module name="security" />
<module name="file" />
<module name="web" />
<module name="music-service" />
<module name="music-related-service" />
<module name="songlist-service" />
<module name="gateway" />
<module name="feign-api" />
<module name="songlist-service" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
@ -23,12 +29,19 @@
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="auth-service" options="-parameters" />
<module name="common" options="-parameters" />
<module name="cache" options="-parameters" />
<module name="common" options="" />
<module name="eureka-server" options="-parameters" />
<module name="feign-api" options="-parameters" />
<module name="file" options="-parameters" />
<module name="gateway" options="-parameters" />
<module name="idempotent" options="-parameters" />
<module name="music-related-service" options="-parameters" />
<module name="music-service" options="-parameters" />
<module name="security" options="-parameters" />
<module name="sentinel" options="-parameters" />
<module name="songlist-service" options="-parameters" />
<module name="web" options="-parameters" />
</option>
</component>
</project>

@ -6,6 +6,29 @@
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://localhost:3306/cloudmusic</jdbc-url>
<vm-options>-Djava.net.preferIPv4Stack=true</vm-options>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="postgres" uuid="6c8494e7-5d7f-4128-9f4e-2785d71a708f">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://8.210.250.29:5432/</jdbc-url>
<vm-options>-Djava.net.preferIPv4Stack=true</vm-options>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="@8.210.250.29" uuid="d49b167b-b85b-472a-ae80-068cc0b8961f">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://8.210.250.29:3306</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="@localhost" uuid="417deb03-8f32-42f9-bca8-0682793af9d9">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://localhost:3306</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>

@ -3,14 +3,28 @@
<component name="Encoding">
<file url="file://$PROJECT_DIR$/auth-service/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/auth-service/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/cache/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/cache/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/file/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/file/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/idempotent/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/idempotent/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/security/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/security/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/sentinel/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/sentinel/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/web/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/web/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/eureka-server/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/eureka-server/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/feign-api/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/feign-api/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/gateway/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/gateway/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/music-related-service/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/music-related-service/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/music-service/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/music-service/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/songlist-service/src/main/java" charset="UTF-8" />

@ -13,6 +13,13 @@
<option value="$PROJECT_DIR$/memorandum-note/pom.xml" />
<option value="$PROJECT_DIR$/gateway/pom.xml" />
<option value="$PROJECT_DIR$/feign-api/pom.xml" />
<option value="$PROJECT_DIR$/common/web/pom.xml" />
<option value="$PROJECT_DIR$/common/oss/pom.xml" />
<option value="$PROJECT_DIR$/common/security/pom.xml" />
<option value="$PROJECT_DIR$/common/sentinel/pom.xml" />
<option value="$PROJECT_DIR$/common/file/pom.xml" />
<option value="$PROJECT_DIR$/common/cache/pom.xml" />
<option value="$PROJECT_DIR$/common/idempotent/pom.xml" />
</list>
</option>
</component>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SonarLintProjectSettings">
<option name="moduleMapping">
<map>
<entry key="file" value="oss" />
</map>
</option>
</component>
</project>

@ -2,5 +2,12 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/common/cache" vcs="Git" />
<mapping directory="$PROJECT_DIR$/common/file" vcs="Git" />
<mapping directory="$PROJECT_DIR$/common/idempotent" vcs="Git" />
<mapping directory="$PROJECT_DIR$/common/security" vcs="Git" />
<mapping directory="$PROJECT_DIR$/common/sentinel" vcs="Git" />
<mapping directory="$PROJECT_DIR$/common/web" vcs="Git" />
<mapping directory="$PROJECT_DIR$/music-related-service" vcs="Git" />
</component>
</project>

@ -3,15 +3,13 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyingpig</groupId>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>cloud-music</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>auth-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloudemusic-auth</name>
<description>cloudemusic-auth</description>
<properties>
<java.version>17</java.version>
</properties>
@ -85,18 +83,31 @@
<groupId>com.flyingpig</groupId>
<artifactId>feign-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!-- 服务通用模块 -->
<dependency>
<groupId>com.flyingpig</groupId>
<artifactId>common</artifactId>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common-web</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common-security</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common-file</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common-cache</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>

@ -1,15 +1,14 @@
package com.flyingpig.cloudmusic;
package com.flyingpig.cloudmusic.auth;
import com.flyingpig.cloudmusic.config.MvcConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.EnableAsync;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Import(MvcConfig.class)
@EnableSwagger2
@SpringBootApplication(scanBasePackages = "com.flyingpig")
@SpringBootApplication
@EnableAsync
public class AuthApplication {
public static void main(String[] args) {

@ -1,4 +1,4 @@
package com.flyingpig.cloudmusic.config;
package com.flyingpig.cloudmusic.auth.config;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;

@ -0,0 +1,29 @@
package com.flyingpig.cloudmusic.auth.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host:localhost}")
private String redisHost;
@Value("${spring.redis.port:6379}")
private int redisPort;
@Bean
public RedissonClient redissonClient() {
// 配置
Config config = new Config();
config.useSingleServer().setAddress("redis://" + redisHost + ":" + redisPort);
// 创建RedissonClient对象
return Redisson.create(config);
}
}

@ -1,4 +1,4 @@
package com.flyingpig.cloudmusic.config;
package com.flyingpig.cloudmusic.auth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@ -0,0 +1,16 @@
package com.flyingpig.cloudmusic.auth.constant;
public class RedisConstants {
public static final String USER_INFO_KEY="user:info:";
public static final Long USER_INFO_TTL=30L;
public static final String USER_LOGIN_KEY="user:login:";
public static final Long USER_LOGIN_TTL = 30L;
public static final String EMAIL_VERIFYCODE_KEY="email:verifycode:";
public static final Long EMAIL_VERIFYCODE_TTL=120L;
}

@ -1,10 +1,11 @@
package com.flyingpig.cloudmusic.controller;
package com.flyingpig.cloudmusic.auth.controller;
import com.flyingpig.cloudmusic.constant.StatusCode;
import com.flyingpig.cloudmusic.dataobject.entity.User;
import com.flyingpig.cloudmusic.result.Result;
import com.flyingpig.cloudmusic.service.UserService;
import com.flyingpig.cloudmusic.util.JwtUtil;
import com.flyingpig.cloudmusic.auth.constant.RedisConstants;
import com.flyingpig.cloudmusic.auth.dataobject.entity.User;
import com.flyingpig.cloudmusic.security.util.JwtUtil;
import com.flyingpig.cloudmusic.auth.service.UserService;
import com.flyingpig.cloudmusic.web.Result;
import com.flyingpig.cloudmusic.web.StatusCode;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@ -15,8 +16,6 @@ import org.springframework.web.bind.annotation.*;
import java.util.Objects;
import static com.flyingpig.cloudmusic.constant.RedisConstants.USER_LOGIN_KEY;
@RestController
@RequestMapping("/users")
@Api("用户操作相关的api")
@ -53,7 +52,12 @@ public class AuthController {
@GetMapping("/whitelist")
public boolean uuidIsInWhiteListOrNot(String userId, String uuid) {
try {
return Objects.equals(stringRedisTemplate.opsForValue().get(USER_LOGIN_KEY + userId), uuid);
String redisValue = stringRedisTemplate.opsForValue().get(RedisConstants.USER_LOGIN_KEY + userId);
// 去掉引号和空格后再进行比较
if (redisValue != null) {
redisValue = redisValue.replace("\"", "").trim();
}
return Objects.equals(redisValue, uuid);
} catch (RedisConnectionFailureException e) {
log.error("redis崩溃啦啦啦啦啦");
}
@ -61,4 +65,5 @@ public class AuthController {
}
}

@ -0,0 +1,62 @@
package com.flyingpig.cloudmusic.auth.controller;
import com.flyingpig.cloudmusic.auth.constant.RedisConstants;
import com.flyingpig.cloudmusic.auth.dataobject.entity.User;
import com.flyingpig.cloudmusic.auth.dataobject.vo.EmailRegisterVO;
import com.flyingpig.cloudmusic.auth.service.UserService;
import com.flyingpig.cloudmusic.auth.util.EmailUtil;
import com.flyingpig.cloudmusic.web.Result;
import com.flyingpig.cloudmusic.web.StatusCode;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/email")
@Api("与邮件处理相关的api")
@Slf4j
public class MailController {
@Autowired
UserService userService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
PasswordEncoder passwordEncoder;
@GetMapping("/verificationCode")
@ApiOperation("用户获取验证码")
public Result sendEmailVerificationCode(String email) {
//检查email是否符合格式
if (!EmailUtil.judgeEmailFormat(email)) {
return Result.error(StatusCode.SERVERERROR, "邮箱不符合格式");
}
userService.sendVerificationCode(email);
return Result.success("验证码已发送");
}
@PostMapping("/register")
@ApiOperation("通过验证码完成注册")
public Result emailRegister(@RequestBody EmailRegisterVO emailRegisterVO) {
System.out.println(emailRegisterVO.getEmail());
String verificationCode = stringRedisTemplate.opsForValue().get(RedisConstants.EMAIL_VERIFYCODE_KEY + emailRegisterVO.getEmail());
System.out.println(verificationCode);
if (verificationCode != null && verificationCode.equals(emailRegisterVO.getVerificationCode())) {
// 添加用户
userService.addUser(new User()
.setUsername(emailRegisterVO.getUsername())
.setPassword(passwordEncoder.encode(emailRegisterVO.getPassword()))
.setEmail(emailRegisterVO.getEmail()));
return Result.success("添加成功,请联系管理员审核");
} else {
return Result.error(StatusCode.SERVERERROR, "验证码验证错误");
}
}
}

@ -1,10 +1,10 @@
package com.flyingpig.cloudmusic.controller;
package com.flyingpig.cloudmusic.auth.controller;
import com.flyingpig.cloudmusic.aop.BeforeAuthorize;
import com.flyingpig.cloudmusic.result.Result;
import com.flyingpig.cloudmusic.service.UserService;
import com.flyingpig.cloudmusic.util.AliOSSUtils;
import com.flyingpig.cloudmusic.util.UserContext;
import com.flyingpig.cloudmusic.auth.service.UserService;
import com.flyingpig.cloudmusic.security.aop.BeforeAuthorize;
import com.flyingpig.cloudmusic.security.util.UserContext;
import com.flyingpig.cloudmusic.web.Result;
import com.flyingpig.feign.dataobject.dto.UserInfo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@ -21,15 +21,6 @@ public class UserController {
@Autowired
private UserService userService;
@Autowired
private AliOSSUtils aliOSSUtils;
@GetMapping("/user-info/{userId}")
UserInfo selectUserInfoByUserId(@PathVariable("userId") Long userId) {
return userService.selectUserInfoByUserId(userId);
}
@GetMapping("/info")
@ApiOperation("获取用户信息")
@ -42,8 +33,7 @@ public class UserController {
@ApiOperation("修改用户头像")
public Result updateUserAvatar(@RequestParam MultipartFile avatar) throws IOException {
//封装完毕后调用service层的add方法
String avatarUrl = aliOSSUtils.upload(avatar);
userService.updateAvatar(UserContext.getUser().getUserId(), avatarUrl);
userService.updateAvatar(UserContext.getUser().getUserId(), avatar);
return Result.success();
}
@ -54,6 +44,12 @@ public class UserController {
return Result.success();
}
@ApiOperation("内部调用查询用户信息")
@GetMapping("/user-info/{userId}")
UserInfo selectUserInfoByUserId(@PathVariable("userId") Long userId) {
return userService.selectUserInfoByUserId(userId);
}
@DeleteMapping
@BeforeAuthorize(role = "admin")
@ApiOperation("管理员删除用户")

@ -1,7 +1,7 @@
package com.flyingpig.cloudmusic.dataobject.dto;
package com.flyingpig.cloudmusic.auth.dataobject.dto;
import com.flyingpig.cloudmusic.dataobject.entity.User;
import com.flyingpig.cloudmusic.auth.dataobject.entity.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@ -1,4 +1,4 @@
package com.flyingpig.cloudmusic.dataobject.entity;
package com.flyingpig.cloudmusic.auth.dataobject.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
@ -6,11 +6,13 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
@Accessors(chain = true)
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;

@ -1,4 +1,4 @@
package com.flyingpig.cloudmusic.dataobject.vo;
package com.flyingpig.cloudmusic.auth.dataobject.vo;
import lombok.AllArgsConstructor;
import lombok.Data;

@ -1,7 +1,7 @@
package com.flyingpig.cloudmusic.mapper;
package com.flyingpig.cloudmusic.auth.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.flyingpig.cloudmusic.dataobject.entity.User;
import com.flyingpig.cloudmusic.auth.dataobject.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper

@ -1,10 +1,10 @@
package com.flyingpig.cloudmusic.service;
package com.flyingpig.cloudmusic.auth.service;
import com.flyingpig.cloudmusic.dataobject.entity.User;
import com.flyingpig.cloudmusic.result.Result;
import com.flyingpig.cloudmusic.auth.dataobject.entity.User;
import com.flyingpig.cloudmusic.web.Result;
import com.flyingpig.feign.dataobject.dto.UserInfo;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
public interface UserService {
@ -18,7 +18,9 @@ public interface UserService {
void updateUserName(Long userId, String userName);
void updateAvatar(Long userId, String avatarUrl);
void updateAvatar(Long userId, MultipartFile avatarUrl);
void deleteUserById(Long userId);
void sendVerificationCode(String email);
}

@ -1,10 +1,10 @@
package com.flyingpig.cloudmusic.service.impl;
package com.flyingpig.cloudmusic.auth.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.flyingpig.cloudmusic.dataobject.dto.LoginUser;
import com.flyingpig.cloudmusic.dataobject.entity.User;
import com.flyingpig.cloudmusic.mapper.UserMapper;
import com.flyingpig.cloudmusic.auth.dataobject.entity.User;
import com.flyingpig.cloudmusic.auth.mapper.UserMapper;
import com.flyingpig.cloudmusic.auth.dataobject.dto.LoginUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;

@ -0,0 +1,169 @@
package com.flyingpig.cloudmusic.auth.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.flyingpig.cloudmusic.auth.constant.RedisConstants;
import com.flyingpig.cloudmusic.auth.dataobject.entity.User;
import com.flyingpig.cloudmusic.auth.mapper.UserMapper;
import com.flyingpig.cloudmusic.auth.service.UserService;
import com.flyingpig.cloudmusic.auth.util.EmailUtil;
import com.flyingpig.cloudmusic.cache.StringCacheUtil;
import com.flyingpig.cloudmusic.auth.dataobject.dto.LoginUser;
import com.flyingpig.cloudmusic.file.AliOSSUtils;
import com.flyingpig.cloudmusic.security.util.JwtUtil;
import com.flyingpig.cloudmusic.web.Result;
import com.flyingpig.feign.dataobject.dto.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class UserServiceImpl implements UserService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
StringCacheUtil myStringRedisTemplate;
@Autowired
private UserMapper userMapper;
@Autowired
AliOSSUtils aliOSSUtils;
@Value("${spring.mail.username}")
private String emailUserName;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Resource
private JavaMailSenderImpl mailSender;
@Override
public Result login(User user) {
//AuthenticationManager authenticate进行用户认证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//如果认证没通过,给出对应的提示
if (Objects.isNull(authenticate)) {
throw new RuntimeException("登录失败");
}
//如果认证通过了使用userid生成一个jwt jwt存入ResponseResult返回
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userid = loginUser.getUser().getId().toString();
String uuid = JwtUtil.getUUID();
String jwt = JwtUtil.createJWT(userid, JwtUtil.JWT_TTL, uuid);
//将jwt存入redis中键为userId,值为token的uuid不直接存jwt可以节省缓存
myStringRedisTemplate.set(RedisConstants.USER_LOGIN_KEY + userid, uuid, RedisConstants.USER_LOGIN_TTL, TimeUnit.DAYS);
//返回
Map<String, Object> map = new HashMap<>();
map.put("token", jwt);
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("email", user.getEmail());
User selectuser = userMapper.selectOne(userQueryWrapper);
map.put("email", selectuser.getEmail());
map.put("id", selectuser.getId());
return Result.success(map);
}
@Override
public Result logout(String userId) {
//将token加入黑名单
myStringRedisTemplate.delete(RedisConstants.USER_LOGIN_KEY + userId);
return new Result(200, "退出成功", null);
}
@Override
public void addUser(User user) {
userMapper.insert(user);
}
@Override
public UserInfo selectUserInfoByUserId(Long userId) {
return myStringRedisTemplate.safeGetWithLock(RedisConstants.USER_INFO_KEY + userId, UserInfo.class, () -> {
UserInfo result = new UserInfo();
User user = userMapper.selectById(userId);
if (user == null) {
return null;
}
BeanUtils.copyProperties(user, result);
return result;
}, RedisConstants.USER_INFO_TTL, TimeUnit.DAYS);
}
@Override
public void updateUserName(Long userId, String userName) {
User user = new User();
user.setId(userId);
user.setUsername(userName);
userMapper.updateById(user);
//删除缓存,防止数据不一致
myStringRedisTemplate.delete(RedisConstants.USER_INFO_KEY + userId);
}
@Override
public void updateAvatar(Long userId, MultipartFile avatarUrl) {
try {
// 删除原来的头像
aliOSSUtils.deleteFileByUrl(userMapper.selectById(userId).getAvatar());
// 更新现有的头像
userMapper.updateById(new User().setId(userId)
.setAvatar(aliOSSUtils.upload(avatarUrl)));
// 删除缓存,防止数据不一致
myStringRedisTemplate.delete(RedisConstants.USER_INFO_KEY + userId);
} catch (Exception e) {
log.error("修改头像异常{}", e.getMessage());
}
}
@Override
public void deleteUserById(Long userId) {
userMapper.deleteById(userId);
}
@Override
@Async
public void sendVerificationCode(String email) {
// 验证码 邮件主题 邮件正文
String verificationCode = EmailUtil.createVerificationCode();
String subject = "【喵听】验证码";
String text = String.format("【喵听】验证码:%s您正在申请注册飞猪聊天室账号" +
"(若非本人操作,请删除本邮件)", verificationCode);
// 发送邮件
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(emailUserName); // 设置发件邮箱
message.setTo(email); // 设置收件邮箱
message.setSubject(subject); // 设置邮件主题
message.setText(text); // 设置邮件正文
mailSender.send(message);
// 存入缓存
stringRedisTemplate.opsForValue().set(RedisConstants.EMAIL_VERIFYCODE_KEY + email, verificationCode, RedisConstants.EMAIL_VERIFYCODE_TTL, TimeUnit.SECONDS);
}
}

@ -1,4 +1,4 @@
package com.flyingpig.cloudmusic.util;
package com.flyingpig.cloudmusic.auth.util;
import java.util.Random;
@ -27,6 +27,6 @@ public class EmailUtil {
//测试
public static void main(String[] args) {
String result = createVerificationCode();
System.out.println(result);
System.out.println(judgeEmailFormat("1839976096@qq.com"));
}
}

@ -1,4 +1,4 @@
package com.flyingpig.cloudmusic.util;
package com.flyingpig.cloudmusic.auth.util;
import javax.servlet.http.HttpServletResponse;

@ -1,82 +0,0 @@
package com.flyingpig.cloudmusic.controller;
import com.flyingpig.cloudmusic.dataobject.entity.User;
import com.flyingpig.cloudmusic.dataobject.vo.EmailRegisterVO;
import com.flyingpig.cloudmusic.result.Result;
import com.flyingpig.cloudmusic.service.UserService;
import com.flyingpig.cloudmusic.util.EmailUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import static com.flyingpig.cloudmusic.constant.RedisConstants.EMAIL_VERIFYCODE_KEY;
import static com.flyingpig.cloudmusic.constant.RedisConstants.EMAIL_VERIFYCODE_TTL;
@RestController
@RequestMapping("/email")
@Api("与邮件处理相关的api")
public class MailController {
@Autowired
UserService loginService;
@Resource
private JavaMailSenderImpl mailSender;
//这里要使用工具类不然各个方法之间的redis数据无法共用
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
PasswordEncoder passwordEncoder;
@Value("${spring.mail.username}")
private String emailUserName;
@GetMapping("/verificationCode")
@ApiOperation("用户获取验证码")
public Result sendEmailVerificationCode(String email) {
//检查email是否符合格式
if (!EmailUtil.judgeEmailFormat(email)) {
return Result.error(500, "邮箱不符合格式");
}
String verificationCode = EmailUtil.createVerificationCode();
//发送
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(emailUserName);//设置发件qq邮箱
message.setTo(email); // 设置收件人邮箱地址
message.setSubject("验证码"); // 设置邮件主题
message.setText(verificationCode); // 设置邮件正文
mailSender.send(message);
//存入缓存
stringRedisTemplate.opsForValue().set(EMAIL_VERIFYCODE_KEY + email, verificationCode, EMAIL_VERIFYCODE_TTL, TimeUnit.SECONDS);
return Result.success("验证码已发送");
}
@PostMapping("/register")
@ApiOperation("通过验证码完成注册")
public Result emailRegister(@RequestBody EmailRegisterVO emailRegisterVO) {
System.out.println(emailRegisterVO.getEmail());
String verificationCode = stringRedisTemplate.opsForValue().get(EMAIL_VERIFYCODE_KEY + emailRegisterVO.getEmail());
System.out.println(verificationCode);
if (verificationCode != null && verificationCode.equals(emailRegisterVO.getVerificationCode())) {
//添加用户
User user = new User();
user.setUsername(emailRegisterVO.getUsername());
user.setPassword(passwordEncoder.encode(emailRegisterVO.getPassword()));
user.setEmail(emailRegisterVO.getEmail());
loginService.addUser(user);
return Result.success("添加成功,请联系管理员审核");
} else {
return Result.error(500, "验证码验证错误");
}
}
}

@ -1,121 +0,0 @@
package com.flyingpig.cloudmusic.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.flyingpig.cloudmusic.dataobject.dto.LoginUser;
import com.flyingpig.cloudmusic.dataobject.entity.User;
import com.flyingpig.cloudmusic.mapper.UserMapper;
import com.flyingpig.cloudmusic.result.Result;
import com.flyingpig.cloudmusic.service.UserService;
import com.flyingpig.cloudmusic.util.cache.MyStringRedisTemplate;
import com.flyingpig.feign.dataobject.dto.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import com.flyingpig.cloudmusic.util.JwtUtil;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import static com.flyingpig.cloudmusic.constant.RedisConstants.*;
@Service
@Slf4j
public class UserServiceImpl implements UserService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
MyStringRedisTemplate myStringRedisTemplate;
@Autowired
private UserMapper userMapper;
@Override
public Result login(User user) {
//AuthenticationManager authenticate进行用户认证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//如果认证没通过,给出对应的提示
if (Objects.isNull(authenticate)) {
throw new RuntimeException("登录失败");
}
//如果认证通过了使用userid生成一个jwt jwt存入ResponseResult返回
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userid = loginUser.getUser().getId().toString();
String uuid = JwtUtil.getUUID();
String jwt = JwtUtil.createJWT(userid, JwtUtil.JWT_TTL, uuid);
//将jwt存入redis中键为userId,值为token的uuid不直接存jwt可以节省缓存
myStringRedisTemplate.set(USER_LOGIN_KEY + userid, uuid, USER_LOGIN_TTL, TimeUnit.DAYS);
//返回
Map<String, Object> map = new HashMap<>();
map.put("token", jwt);
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("email", user.getEmail());
User selectuser = userMapper.selectOne(userQueryWrapper);
map.put("email", selectuser.getEmail());
map.put("id", selectuser.getId());
return Result.success(map);
}
@Override
public Result logout(String userId) {
//将token加入黑名单
myStringRedisTemplate.delete(USER_LOGIN_KEY + userId);
return new Result(200, "退出成功", null);
}
@Override
public void addUser(User user) {
userMapper.insert(user);
}
@Override
public UserInfo selectUserInfoByUserId(Long userId) {
return myStringRedisTemplate.safeGetWithLock(USER_INFO_KEY + userId, UserInfo.class, () -> {
UserInfo result = new UserInfo();
User user = userMapper.selectById(userId);
if (user == null) {
return null;
}
BeanUtils.copyProperties(user, result);
return result;
}, USER_INFO_TTL, TimeUnit.DAYS);
}
@Override
public void updateUserName(Long userId, String userName) {
User user = new User();
user.setId(userId);
user.setUsername(userName);
userMapper.updateById(user);
//删除缓存,防止数据不一致
myStringRedisTemplate.delete(USER_INFO_KEY + userId);
}
@Override
public void updateAvatar(Long userId, String avatarUrl) {
User user = new User();
user.setId(userId);
user.setAvatar(avatarUrl);
userMapper.updateById(user);
//删除缓存,防止数据不一致
myStringRedisTemplate.delete(USER_INFO_KEY + userId);
}
@Override
public void deleteUserById(Long userId) {
userMapper.deleteById(userId);
}
}

@ -1,6 +1,8 @@
server:
port: 9091
port: 9094
spring:
main:
allow-bean-definition-overriding: true
servlet:
multipart:
max-file-size: 10MB
@ -8,25 +10,25 @@ spring:
application:
name: auth-service # 服务名称
datasource:
url: jdbc:mysql://localhost:3306/cloudmusic
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://111.229.173.12:3306/cloudmusic
username: root
password: '@Aa123456'
cloud:
nacos:
server-addr: http://localhost:8848
server-addr: http://common-nacos-dev.magestack.cn:8848
discovery:
cluster-name: FJ # 集群名称
ip: localhost # 注册到nacos的ip与端口
port: 9091
ip: 8.210.250.29 # 注册到nacos的ip与端口
port: 9094
redis:
host: localhost
port: 6379
database: 0
host: localhost
port: 6379
database: 0
mail:
host: smtp.qq.com
username: flying_pig_z@qq.com
password:
password:
default-encoding: UTF-8
port: 465
properties:
@ -36,7 +38,6 @@ spring:
class: javax.net.ssl.SSLSocketFactory
ssl:
enable: true
debug: true
#??sql
@ -61,5 +62,4 @@ feign:
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
sentinel:
enabled: true
enabled: true

@ -1,4 +1,4 @@
package com.flyingpig.cloudmusic;
package com.flyingpig.cloudmusic.auth;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@ -6,7 +6,7 @@ public class Test {
public static void main(String[] args){
BCryptPasswordEncoder bCryptPasswordEncoder=new BCryptPasswordEncoder();
String encodeString=bCryptPasswordEncoder.encode("12345678");
String encodeString=bCryptPasswordEncoder.encode("1");
System.out.println(encodeString);
}
}

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>common-cache</artifactId>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.53</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.5.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version> <!-- 版本号可根据需要调整 -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.21.3</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

@ -1,4 +1,4 @@
package com.flyingpig.cloudmusic.util.cache;
package com.flyingpig.cloudmusic.cache;
/**
*

@ -0,0 +1,99 @@
package com.flyingpig.cloudmusic.cache;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.nio.channels.ReadableByteChannel;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReadWriteLock;
public class StringCacheUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedissonClient redissonClient;
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
// 加入缓存
public void set(String key, Object value, Long time, TimeUnit unit) {
stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(value), time, unit);
}
// 普通查询
public <T> T get(String key, Class<T> type) {
String value = stringRedisTemplate.opsForValue().get(key);
if (String.class.isAssignableFrom(type)) {
return (T) value;
}
return JSON.parseObject(value, type);
}
// 查询时候缓存空值防止缓存穿透,互斥锁查询防止缓存击穿
public <T> T safeGetWithLock(
String key, Class<T> type, CacheLoader<T> cacheLoader, Long time, TimeUnit unit) {
// 从redis查询缓存
String json = stringRedisTemplate.opsForValue().get(key);
// 1.命中且不为空字符串,直接返回;命中却为空字符串返回null
if (StrUtil.isNotBlank(json)) {
System.out.println(666);
return JSON.parseObject(json, type);
} else if (json != null) {
return null;
}
// 2.没有命中,去数据库查询,查到写入数据库,没查到则缓存空字符串
// 获取锁
String lockKey = "lock:" + key;
T result = null;
RLock rLock = redissonClient.getLock(lockKey);
rLock.lock();
try {
// 再次查询redis双重判定
if (StrUtil.isNotBlank(json)) {
return JSON.parseObject(json, type);
} else if (json != null) {
return null;
}
// 获取锁成功查询数据库。存在写入redis;不存在将空值写入redis返回null。
result = loadAndSet(key, cacheLoader, time, unit);
} finally {
// 释放锁
rLock.unlock();
}
// 返回
return result;
}
public void delete(String key) {
stringRedisTemplate.delete(key);
}
private <T> T loadAndSet(String key, CacheLoader<T> cacheLoader, Long time, TimeUnit unit) {
// 获取锁成功,查询数据库
T result = cacheLoader.load();
// 不存在将空值写入redis返回null
if (result == null) {
stringRedisTemplate.opsForValue().set(key, "", time, TimeUnit.MINUTES);
}
// 存在写入redis
this.set(key, result, time, unit);
return result;
}
}

@ -0,0 +1,13 @@
package com.flyingpig.cloudmusic.cache.config;
import com.flyingpig.cloudmusic.cache.StringCacheUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CacheAutoConfiguration {
@Bean
public StringCacheUtil stringCacheUtil() {
return new StringCacheUtil();
}
}

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.flyingpig.cloudmusic.cache.config.CacheAutoConfiguration

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>common-file</artifactId>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

@ -1,25 +1,23 @@
package com.flyingpig.cloudmusic.util;
package com.flyingpig.cloudmusic.file;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.internal.Mimetypes;
import com.aliyun.oss.model.*;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Component
public class AliOSSUtils {
private String endpoint = "";
private String accessKeyId = "";
private String accessKeySecret = "";
private String bucketName = "";
private static String endpoint = "";
private static String bucketName = "";
private static String accessKeyId = "";
private static String accessKeySecret = "";
/**
* OSS
@ -28,24 +26,45 @@ public class AliOSSUtils {
// 获取上传的文件的输入流
InputStream inputStream = multipartFile.getInputStream();
// 避免文件覆盖
String originalFilename = multipartFile.getOriginalFilename();
String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
// 生成新的文件名
String fileName = generateFileName(multipartFile);
//上传文件到 OSS
// 上传文件到 OSS
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
ossClient.putObject(bucketName, fileName, inputStream);
//文件访问路径
// 文件访问路径
String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
// 关闭ossClient
ossClient.shutdown();
return url;// 把上传到oss的路径返回
return url; // 把上传到oss的路径返回
}
/**
* + + UUID 6 +
*/
private String generateFileName(MultipartFile multipartFile) {
// 获取原始文件名和后缀
String originalFilename = multipartFile.getOriginalFilename();
String fileExtension = null;
if (originalFilename != null) {
fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
}
// 获取当前时间戳并格式化到毫秒
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
String timestamp = dateFormat.format(new Date());
// 生成 UUID 并取前 6 位
String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 6); // 取前 6 位
// 组合新的文件名
return originalFilename.substring(0, originalFilename.lastIndexOf("."))
+ "_" + timestamp
+ "_" + uuid
+ fileExtension;
}
/**
@ -88,7 +107,7 @@ public class AliOSSUtils {
// 设置分片大小。除了最后一个分片没有大小限制其他的分片最小为100 KB。
uploadPartRequest.setPartSize(curPartSize);
// 设置分片号。每一个上传的分片都有一个分片号取值范围是1~10000如果超出此范围OSS将返回InvalidArgument错误码。
uploadPartRequest.setPartNumber( i + 1);
uploadPartRequest.setPartNumber(i + 1);
// 每个分片不需要按顺序上传甚至可以在不同客户端上传OSS会按照分片号排序组成完整的文件。
UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
@ -99,7 +118,6 @@ public class AliOSSUtils {
return uploadPartResult.getPartETag();
}
@ -126,5 +144,36 @@ public class AliOSSUtils {
return url;// 把上传到oss的路径返回
}
/**
* URL OSS
*
* @param fileUrl URL
* @return
*/
public boolean deleteFileByUrl(String fileUrl) {
// 检查 URL 是否包含正确的 OSS 格式
if (!fileUrl.contains(bucketName) || !fileUrl.contains(endpoint)) {
System.out.println("URL 格式错误,不属于当前 OSS 存储桶");
return false;
}
// 从 URL 中提取文件路径和文件名
String fileKey = fileUrl.substring(fileUrl.indexOf(bucketName) + bucketName.length() + 1);
// 创建 OSS 客户端实例
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
// 删除指定文件
ossClient.deleteObject(bucketName, fileKey);
return true;
} catch (Exception e) {
System.out.println("删除文件失败: " + e.getMessage());
return false;
} finally {
// 关闭 OSS 客户端
ossClient.shutdown();
}
}
}

@ -0,0 +1,13 @@
package com.flyingpig.cloudmusic.file.config;
import com.flyingpig.cloudmusic.file.AliOSSUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AutoFileConfiguration {
@Bean
public AliOSSUtils aliOSSUtils() {
return new AliOSSUtils();
}
}

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.flyingpig.cloudmusic.file.config.AutoFileConfiguration

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>common-idempotent</artifactId>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common-cache</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common-security</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.annotation;
import com.flyingpig.cloudmusic.idempotent.enums.IdempotentSceneEnum;
import com.flyingpig.cloudmusic.idempotent.enums.IdempotentTypeEnum;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Idempotent {
/**
* Key {@link Idempotent#type()} {@link IdempotentTypeEnum#SPEL}
*/
String key() default "";
/**
*
*/
String message() default "您操作太快,请稍后再试";
/**
*
* RestAPI 使 {@link IdempotentTypeEnum#TOKEN} {@link IdempotentTypeEnum#PARAM}
* 使 {@link IdempotentTypeEnum#SPEL}
*/
IdempotentTypeEnum type() default IdempotentTypeEnum.PARAM;
/**
* {@link IdempotentSceneEnum}
*/
IdempotentSceneEnum scene() default IdempotentSceneEnum.RESTAPI;
/**
* Key MQ
* {@link IdempotentSceneEnum#MQ} and {@link IdempotentTypeEnum#SPEL}
*/
String uniqueKeyPrefix() default "";
/**
* Key 1 MQ
* {@link IdempotentSceneEnum#MQ} and {@link IdempotentTypeEnum#SPEL}
*/
long keyTimeout() default 3600L;
}

@ -0,0 +1,68 @@
package com.flyingpig.cloudmusic.idempotent.config;
import com.flyingpig.cloudmusic.cache.StringCacheUtil;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentAspect;
import com.flyingpig.cloudmusic.idempotent.core.bases.ApplicationContextHolder;
import com.flyingpig.cloudmusic.idempotent.core.param.IdempotentParamExecuteHandler;
import com.flyingpig.cloudmusic.idempotent.core.param.IdempotentParamService;
import com.flyingpig.cloudmusic.idempotent.core.spel.IdempotentSpELByMQExecuteHandler;
import com.flyingpig.cloudmusic.idempotent.core.spel.IdempotentSpELByRestAPIExecuteHandler;
import com.flyingpig.cloudmusic.idempotent.core.spel.IdempotentSpELService;
import org.redisson.api.RedissonClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
/**
*
*/
@EnableConfigurationProperties(IdempotentProperties.class)
public class IdempotentAutoConfiguration {
/**
*
*/
@Bean
public IdempotentAspect idempotentAspect() {
return new IdempotentAspect();
}
/**
* RestAPI
*/
@Bean
@ConditionalOnMissingBean
public IdempotentParamService idempotentParamExecuteHandler(RedissonClient redissonClient) {
return new IdempotentParamExecuteHandler(redissonClient);
}
/**
* SpEL RestAPI
*/
@Bean
@ConditionalOnMissingBean
public IdempotentSpELService idempotentSpELByRestAPIExecuteHandler(RedissonClient redissonClient) {
return new IdempotentSpELByRestAPIExecuteHandler(redissonClient);
}
/**
* SpEL MQ
*/
@Bean
@ConditionalOnMissingBean
public IdempotentSpELByMQExecuteHandler idempotentSpELByMQExecuteHandler(StringCacheUtil stringCacheUtil, StringRedisTemplate stringRedisTemplate) {
return new IdempotentSpELByMQExecuteHandler(stringCacheUtil, stringRedisTemplate);
}
/**
*
* 12306
*/
@Bean
@ConditionalOnMissingBean
public ApplicationContextHolder congoApplicationContextHolder() {
return new ApplicationContextHolder();
}
}

@ -0,0 +1,16 @@
package com.flyingpig.cloudmusic.idempotent.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.concurrent.TimeUnit;
/**
*
*/
@Data
@ConfigurationProperties(prefix = IdempotentProperties.PREFIX)
public class IdempotentProperties {
public static final String PREFIX = "flyingpig.idempotent.token";
}

@ -0,0 +1,28 @@
package com.flyingpig.cloudmusic.idempotent.core;
import com.flyingpig.cloudmusic.idempotent.annotation.Idempotent;
import org.aspectj.lang.ProceedingJoinPoint;
public abstract class AbstractIdempotentExecuteHandler implements IdempotentExecuteHandler {
/**
*
*
* @param joinPoint AOP
* @return
*/
protected abstract IdempotentParamWrapper buildWrapper(ProceedingJoinPoint joinPoint);
/**
*
*
* @param joinPoint AOP
* @param idempotent
*/
public void execute(ProceedingJoinPoint joinPoint, Idempotent idempotent) {
// 模板方法模式:构建幂等参数包装器
IdempotentParamWrapper idempotentParamWrapper = buildWrapper(joinPoint).setIdempotent(idempotent);
handler(idempotentParamWrapper);
}
}

@ -0,0 +1,60 @@
package com.flyingpig.cloudmusic.idempotent.core;
import com.flyingpig.cloudmusic.idempotent.annotation.Idempotent;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
/**
* AOP
*/
@Aspect
public final class IdempotentAspect {
/**
*
*/
@Around("@annotation(com.flyingpig.cloudmusic.idempotent.annotation.Idempotent)")
public Object idempotentHandler(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取幂等注解
Idempotent idempotent = getIdempotent(joinPoint);
// 根据幂等注解的场景(http or mq)和实现类型(三种实现类型)获取对应实例
IdempotentExecuteHandler instance = IdempotentExecuteHandlerFactory.getInstance(idempotent.scene(), idempotent.type());
Object resultObj;
try {
// 执行实例
instance.execute(joinPoint, idempotent);
// 执行原有方法流程
resultObj = joinPoint.proceed();
// 实例后置处理
instance.postProcessing();
} catch (RepeatConsumptionException ex) {
/*
*
*
* 1. 便 RocketMQ
* 2.
*/
if (!ex.getError()) {
return null;
}
throw ex;
} catch (Throwable ex) {
// 客户端消费存在异常,需要删除幂等标识方便下次 RocketMQ 再次通过重试队列投递
instance.exceptionProcessing();
throw ex;
} finally {
IdempotentContext.clean();
}
return resultObj;
}
public static Idempotent getIdempotent(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method targetMethod = joinPoint.getTarget().getClass().getDeclaredMethod(methodSignature.getName(), methodSignature.getMethod().getParameterTypes());
return targetMethod.getAnnotation(Idempotent.class);
}
}

@ -0,0 +1,62 @@
package com.flyingpig.cloudmusic.idempotent.core;
import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.Maps;
import java.util.Map;
/**
*
*/
public final class IdempotentContext {
private static final ThreadLocal<Map<String, Object>> CONTEXT = new ThreadLocal<>();
public static Map<String, Object> get() {
return CONTEXT.get();
}
public static Object getKey(String key) {
Map<String, Object> context = get();
if (CollUtil.isNotEmpty(context)) {
return context.get(key);
}
return null;
}
public static String getString(String key) {
Object actual = getKey(key);
if (actual != null) {
return actual.toString();
}
return null;
}
public static void put(String key, Object val) {
// 获得上下文
Map<String, Object> context = get();
if (CollUtil.isEmpty(context)) {
// 如果为空,创建一个新的上下文
context = Maps.newHashMap();
}
// 锁
context.put(key, val);
putContext(context);
}
public static void putContext(Map<String, Object> context) {
// 获得上下文
Map<String, Object> threadContext = CONTEXT.get();
if (CollUtil.isNotEmpty(threadContext)) {
// 如果不为空,放入所有的值
threadContext.putAll(context);
return;
}
// 设置上下文
CONTEXT.set(context);
}
public static void clean() {
CONTEXT.remove();
}
}

@ -0,0 +1,39 @@
package com.flyingpig.cloudmusic.idempotent.core;
import com.flyingpig.cloudmusic.idempotent.annotation.Idempotent;
import org.aspectj.lang.ProceedingJoinPoint;
/**
*
*/
public interface IdempotentExecuteHandler {
/**
*
*
* @param wrapper
*/
void handler(IdempotentParamWrapper wrapper);
/**
*
*
* @param joinPoint AOP
* @param idempotent
*/
void execute(ProceedingJoinPoint joinPoint, Idempotent idempotent);
/**
*
*/
default void exceptionProcessing() {
}
/**
*
*/
default void postProcessing() {
}
}

@ -0,0 +1,64 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.core;
import com.flyingpig.cloudmusic.idempotent.core.bases.ApplicationContextHolder;
import com.flyingpig.cloudmusic.idempotent.core.param.IdempotentParamService;
import com.flyingpig.cloudmusic.idempotent.core.spel.IdempotentSpELByMQExecuteHandler;
import com.flyingpig.cloudmusic.idempotent.core.spel.IdempotentSpELByRestAPIExecuteHandler;
import com.flyingpig.cloudmusic.idempotent.enums.IdempotentSceneEnum;
import com.flyingpig.cloudmusic.idempotent.enums.IdempotentTypeEnum;
/**
*
*/
public final class IdempotentExecuteHandlerFactory {
/**
*
*
* @param scene
* @param type
* @return
*/
public static IdempotentExecuteHandler getInstance(IdempotentSceneEnum scene, IdempotentTypeEnum type) {
IdempotentExecuteHandler result = null;
switch (scene) {
case RESTAPI:
switch (type) {
case PARAM:
result = ApplicationContextHolder.getBean(IdempotentParamService.class);
break;
case SPEL:
result = ApplicationContextHolder.getBean(IdempotentSpELByRestAPIExecuteHandler.class);
break;
default:
break;
}
break;
case MQ:
result = ApplicationContextHolder.getBean(IdempotentSpELByMQExecuteHandler.class);
break;
default:
break;
}
return result;
}
}

@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.core;
import com.flyingpig.cloudmusic.idempotent.annotation.Idempotent;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.aspectj.lang.ProceedingJoinPoint;
/**
*
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public final class IdempotentParamWrapper {
/**
*
*/
private Idempotent idempotent;
/**
* AOP
*/
private ProceedingJoinPoint joinPoint;
/**
*
*/
private String lockKey;
}

@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.core;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
*
*/
@RequiredArgsConstructor
public class RepeatConsumptionException extends RuntimeException {
/**
*
* <p>
*
* 1. 便 RocketMQ
* 2.
*/
@Getter
private final Boolean error;
}

@ -0,0 +1,63 @@
package com.flyingpig.cloudmusic.idempotent.core.bases;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.lang.annotation.Annotation;
import java.util.Map;
/**
* Application context holder.
*/
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext CONTEXT;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextHolder.CONTEXT = applicationContext;
}
/**
* Get ioc container bean by type.
*/
public static <T> T getBean(Class<T> clazz) {
return CONTEXT.getBean(clazz);
}
/**
* Get ioc container bean by name.
*/
public static Object getBean(String name) {
return CONTEXT.getBean(name);
}
/**
* Get ioc container bean by name and type.
*/
public static <T> T getBean(String name, Class<T> clazz) {
return CONTEXT.getBean(name, clazz);
}
/**
* Get a set of ioc container beans by type.
*/
public static <T> Map<String, T> getBeansOfType(Class<T> clazz) {
return CONTEXT.getBeansOfType(clazz);
}
/**
* Find whether the bean has annotations.
*/
public static <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType) {
return CONTEXT.findAnnotationOnBean(beanName, annotationType);
}
/**
* Get application context.
*/
public static ApplicationContext getInstance() {
return CONTEXT;
}
}

@ -0,0 +1,102 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.core.param;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson2.JSON;
import com.flyingpig.cloudmusic.idempotent.core.AbstractIdempotentExecuteHandler;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentContext;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentParamWrapper;
import com.flyingpig.cloudmusic.security.util.UserContext;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
*
*/
@RequiredArgsConstructor
public final class IdempotentParamExecuteHandler extends AbstractIdempotentExecuteHandler implements IdempotentParamService {
private final RedissonClient redissonClient;
private final static String LOCK = "lock:param:restAPI";
@Override
protected IdempotentParamWrapper buildWrapper(ProceedingJoinPoint joinPoint) {
String lockKey = String.format("idempotent:path:%s:currentUserId:%s:md5:%s", getServletPath(), getCurrentUserId(), calcArgsMD5(joinPoint));
return IdempotentParamWrapper.builder().lockKey(lockKey).joinPoint(joinPoint).build();
}
/**
* @return 线 ServletPath
*/
private String getServletPath() {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return sra.getRequest().getServletPath();
}
/**
* @return ID
*/
private String getCurrentUserId() {
String userId = String.valueOf(UserContext.getUser().getUserId());
if(StrUtil.isBlank(userId)){
throw new RuntimeException("用户ID获取失败请登录");
}
return userId;
}
/**
* @return joinPoint md5
*/
private String calcArgsMD5(ProceedingJoinPoint joinPoint) {
return DigestUtil.md5Hex(JSON.toJSONBytes(joinPoint.getArgs()));
}
@Override
public void handler(IdempotentParamWrapper wrapper) {
String lockKey = wrapper.getLockKey();
RLock lock = redissonClient.getLock(lockKey);
if (!lock.tryLock()) {
throw new RuntimeException(wrapper.getIdempotent().message());
}
IdempotentContext.put(LOCK, lock);
}
@Override
public void postProcessing() {
RLock lock = null;
try {
lock = (RLock) IdempotentContext.getKey(LOCK);
} finally {
if (lock != null) {
lock.unlock();
}
}
}
@Override
public void exceptionProcessing() {
postProcessing();
}
}

@ -0,0 +1,28 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.core.param;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentExecuteHandler;
/**
*
* 12306
*/
public interface IdempotentParamService extends IdempotentExecuteHandler {
}

@ -0,0 +1,113 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.core.spel;
import com.flyingpig.cloudmusic.cache.StringCacheUtil;
import com.flyingpig.cloudmusic.idempotent.annotation.Idempotent;
import com.flyingpig.cloudmusic.idempotent.core.*;
import com.flyingpig.cloudmusic.idempotent.enums.IdempotentMQConsumeStatusEnum;
import com.flyingpig.cloudmusic.idempotent.toolkit.LogUtil;
import com.flyingpig.cloudmusic.idempotent.toolkit.SpELUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* SpEL MQ
*/
@RequiredArgsConstructor
public final class IdempotentSpELByMQExecuteHandler extends AbstractIdempotentExecuteHandler implements IdempotentSpELService {
private final static int TIMEOUT = 600;
private final static String WRAPPER = "wrapper:spEL:MQ";
private final static String LUA_SCRIPT_SET_IF_ABSENT_AND_GET_PATH = "lua/set_if_absent_and_get.lua";
private final StringCacheUtil stringCacheUtil;
private final StringRedisTemplate stringRedisTemplate;
@SneakyThrows
@Override
protected IdempotentParamWrapper buildWrapper(ProceedingJoinPoint joinPoint) {
Idempotent idempotent = IdempotentAspect.getIdempotent(joinPoint);
String key = (String) SpELUtil.parseKey(idempotent.key(), ((MethodSignature) joinPoint.getSignature()).getMethod(), joinPoint.getArgs());
return IdempotentParamWrapper.builder().lockKey(key).joinPoint(joinPoint).build();
}
@Override
public void handler(IdempotentParamWrapper wrapper) {
String uniqueKey = wrapper.getIdempotent().uniqueKeyPrefix() + wrapper.getLockKey();
String absentAndGet = this.setIfAbsentAndGet(uniqueKey, IdempotentMQConsumeStatusEnum.CONSUMING.getCode(), TIMEOUT, TimeUnit.SECONDS);
if (Objects.nonNull(absentAndGet)) {
boolean error = IdempotentMQConsumeStatusEnum.isError(absentAndGet);
LogUtil.getLog(wrapper.getJoinPoint()).warn("[{}] MQ repeated consumption, {}.", uniqueKey, error ? "Wait for the client to delay consumption" : "Status is completed");
throw new RepeatConsumptionException(error);
}
IdempotentContext.put(WRAPPER, wrapper);
}
public String setIfAbsentAndGet(String key, String value, long timeout, TimeUnit timeUnit) {
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
ClassPathResource resource = new ClassPathResource(LUA_SCRIPT_SET_IF_ABSENT_AND_GET_PATH);
redisScript.setScriptSource(new ResourceScriptSource(resource));
redisScript.setResultType(String.class);
long millis = timeUnit.toMillis(timeout);
return stringRedisTemplate.execute(redisScript, List.of(key), value, String.valueOf(millis));
}
@Override
public void exceptionProcessing() {
IdempotentParamWrapper wrapper = (IdempotentParamWrapper) IdempotentContext.getKey(WRAPPER);
if (wrapper != null) {
Idempotent idempotent = wrapper.getIdempotent();
String uniqueKey = idempotent.uniqueKeyPrefix() + wrapper.getLockKey();
try {
stringCacheUtil.delete(uniqueKey);
} catch (Throwable ex) {
LogUtil.getLog(wrapper.getJoinPoint()).error("[{}] Failed to del MQ anti-heavy token.", uniqueKey);
}
}
}
@Override
public void postProcessing() {
IdempotentParamWrapper wrapper = (IdempotentParamWrapper) IdempotentContext.getKey(WRAPPER);
if (wrapper != null) {
Idempotent idempotent = wrapper.getIdempotent();
String uniqueKey = idempotent.uniqueKeyPrefix() + wrapper.getLockKey();
try {
stringCacheUtil.set(uniqueKey, IdempotentMQConsumeStatusEnum.CONSUMED.getCode(), idempotent.keyTimeout(), TimeUnit.SECONDS);
} catch (Throwable ex) {
LogUtil.getLog(wrapper.getJoinPoint()).error("[{}] Failed to set MQ anti-heavy token.", uniqueKey);
}
}
}
}

@ -0,0 +1,65 @@
package com.flyingpig.cloudmusic.idempotent.core.spel;
import com.flyingpig.cloudmusic.idempotent.annotation.Idempotent;
import com.flyingpig.cloudmusic.idempotent.core.AbstractIdempotentExecuteHandler;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentAspect;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentContext;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentParamWrapper;
import com.flyingpig.cloudmusic.idempotent.toolkit.SpELUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
/**
* SpEL RestAPI
*/
@RequiredArgsConstructor
public final class IdempotentSpELByRestAPIExecuteHandler extends AbstractIdempotentExecuteHandler implements IdempotentSpELService {
private final RedissonClient redissonClient;
private final static String LOCK = "lock:spEL:restAPI";
@SneakyThrows
@Override
protected IdempotentParamWrapper buildWrapper(ProceedingJoinPoint joinPoint) {
Idempotent idempotent = IdempotentAspect.getIdempotent(joinPoint);
String key = (String) SpELUtil.parseKey(idempotent.key(), ((MethodSignature) joinPoint.getSignature()).getMethod(), joinPoint.getArgs());
return IdempotentParamWrapper.builder().lockKey(key).joinPoint(joinPoint).build();
}
@Override
public void handler(IdempotentParamWrapper wrapper) {
String uniqueKey = wrapper.getIdempotent().uniqueKeyPrefix() + wrapper.getLockKey();
RLock lock = redissonClient.getLock(uniqueKey);
if (!lock.tryLock()) {
throw new RuntimeException(wrapper.getIdempotent().message());
}
IdempotentContext.put(LOCK, lock);
}
private void releaseLock() {
RLock lock = null;
try {
lock = (RLock) IdempotentContext.getKey(LOCK);
} finally {
if (lock != null) {
lock.unlock();
}
}
}
@Override
public void postProcessing() {
releaseLock();
}
@Override
public void exceptionProcessing() {
releaseLock();
}
}

@ -0,0 +1,27 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.core.spel;
import com.flyingpig.cloudmusic.idempotent.core.IdempotentExecuteHandler;
/**
* SpEL
*/
public interface IdempotentSpELService extends IdempotentExecuteHandler {
}

@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Objects;
/**
* MQ
*/
@RequiredArgsConstructor
public enum IdempotentMQConsumeStatusEnum {
/**
*
*/
CONSUMING("0"),
/**
*
*/
CONSUMED("1");
@Getter
private final String code;
/**
*
*
* @param consumeStatus
* @return
*/
public static boolean isError(String consumeStatus) {
return Objects.equals(CONSUMING.code, consumeStatus);
}
}

@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.enums;
/**
*
*/
public enum IdempotentSceneEnum {
/**
* RestAPI
*/
RESTAPI,
/**
* MQ
*/
MQ
}

@ -0,0 +1,39 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.enums;
/**
*
*/
public enum IdempotentTypeEnum {
/**
* Token
*/
TOKEN,
/**
*
*/
PARAM,
/**
* SpEL
*/
SPEL
}

@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.toolkit;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*/
public class LogUtil {
/**
* Logger
*/
public static Logger getLog(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
return LoggerFactory.getLogger(methodSignature.getDeclaringType());
}
}

@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyingpig.cloudmusic.idempotent.toolkit;
import cn.hutool.core.util.ArrayUtil;
import com.google.common.collect.Lists;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Optional;
/**
* SpEL
*/
public class SpELUtil {
/**
* 使 spEL
*
* @param spEl spEL
* @return 使 spEL
*/
public static Object parseKey(String spEl, Method method, Object[] contextObj) {
// 校验,是否包含 # 或 T(,如果包含则需要解析
ArrayList<String> spELFlag = Lists.newArrayList("#", "T(");
Optional<String> optional = spELFlag.stream().filter(spEl::contains).findFirst();
if (optional.isPresent()) {
// 解析并返回
return parse(spEl, method, contextObj);
}
// 直接返回
return spEl;
}
/**
*
*
* @param spEl spEl
* @param contextObj
* @return
*/
public static Object parse(String spEl, Method method, Object[] contextObj) {
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
ExpressionParser parser = new SpelExpressionParser();
// 解析 SpEL 表达式
Expression exp = parser.parseExpression(spEl);
// 获取方法参数名
String[] params = discoverer.getParameterNames(method);
StandardEvaluationContext context = new StandardEvaluationContext();
if (ArrayUtil.isNotEmpty(params)) {
// 设置参数
for (int len = 0; len < params.length; len++) {
context.setVariable(params[len], contextObj[len]);
}
}
// 解析并返回
return exp.getValue(context);
}
}

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.flyingpig.cloudmusic.idempotent.config.IdempotentAutoConfiguration

@ -2,48 +2,22 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyingpig</groupId>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>cloud-music</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>common</artifactId>
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.5.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>io.projectreactor.addons</groupId>
<artifactId>reactor-extra</artifactId>
</dependency>
</dependencies>
<packaging>pom</packaging>
<modules>
<module>/security</module>
<module>/file</module>
<module>/cache</module>
<module>/sentinel</module>
<module>/web</module>
</modules>
</project>

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>common-security</artifactId>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common-web</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
</dependencies>
</project>

@ -1,17 +1,13 @@
package com.flyingpig.cloudmusic.aop;
package com.flyingpig.cloudmusic.security.aop;
import com.flyingpig.cloudmusic.constant.StatusCode;
import com.flyingpig.cloudmusic.interceptor.LocalUserInfo;
import com.flyingpig.cloudmusic.result.Result;
import com.flyingpig.cloudmusic.util.UserContext;
import com.flyingpig.cloudmusic.security.util.UserContext;
import com.flyingpig.cloudmusic.web.Result;
import com.flyingpig.cloudmusic.web.StatusCode;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Aspect
@Component
@ -20,7 +16,6 @@ public class AuthorizationAspect {
@Around("@annotation(beforeAuthorize)")
public Object checkAuthorization(ProceedingJoinPoint proceedingJoinPoint, BeforeAuthorize beforeAuthorize) throws Throwable {
String role = UserContext.getUser().getRole();
System.out.println(role);
String requiredRoles = beforeAuthorize.role();

@ -1,4 +1,4 @@
package com.flyingpig.cloudmusic.aop;
package com.flyingpig.cloudmusic.security.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;

@ -1,6 +1,6 @@
package com.flyingpig.cloudmusic.config;
package com.flyingpig.cloudmusic.security.config;
import com.flyingpig.cloudmusic.interceptor.UserInfoInterceptor;
import com.flyingpig.cloudmusic.security.interceptor.UserInfoInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@ -1,4 +1,5 @@
package com.flyingpig.cloudmusic.interceptor;
package com.flyingpig.cloudmusic.security.interceptor;
import lombok.AllArgsConstructor;
import lombok.Data;

@ -1,19 +1,20 @@
package com.flyingpig.cloudmusic.interceptor;
package com.flyingpig.cloudmusic.security.interceptor;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.flyingpig.cloudmusic.util.UserContext;
import com.flyingpig.cloudmusic.security.util.UserContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class UserInfoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 访问uri
log.info("访问后端: {}", request.getRequestURI());
log.info("访问后端: {} {}", request.getMethod(), request.getRequestURI());
// 1.获取请求头中的用户信息
String userId = request.getHeader("userId");
@ -21,8 +22,7 @@ public class UserInfoInterceptor implements HandlerInterceptor {
// 2.判断是否为空
if (StringUtil.isNotBlank(userId)) {
// 不为空保存到ThreadLocal
LocalUserInfo localUserInfo = new LocalUserInfo(Long.valueOf(userId), role);
UserContext.setUser(localUserInfo);
UserContext.setUser(new LocalUserInfo(Long.valueOf(userId), role));
}
// 3.放行
return true;

@ -1,4 +1,4 @@
package com.flyingpig.cloudmusic.util;
package com.flyingpig.cloudmusic.security.util;
import io.jsonwebtoken.*;

@ -1,6 +1,7 @@
package com.flyingpig.cloudmusic.util;
package com.flyingpig.cloudmusic.security.util;
import com.flyingpig.cloudmusic.interceptor.LocalUserInfo;
import com.flyingpig.cloudmusic.security.interceptor.LocalUserInfo;
public class UserContext {
private static final ThreadLocal<LocalUserInfo> tl =new ThreadLocal<>();

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.flyingpig.cloudmusic.security.config.MvcConfig

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyingpig.cloud-music</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>common-sentinel</artifactId>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

@ -5,7 +5,6 @@ import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.flyingpig.cloudmusic.sentinel.exception.BlockExceptionHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;

@ -1,17 +0,0 @@
package com.flyingpig.cloudmusic.constant;
public class RabbitMQConstants {
public static final String MUSIC_UPLOAD_EXCHANGE_NAME = "music_upload_exchange";
public static final String MUSIC_UPLOAD_QUEUE_NAME1 = "music_queue1";
public static final String MUSIC_UPLOAD_QUEUE_NAME2 = "music_queue2";
public static final String MUSIC_LIKE_EXCHANGE_NAME = "music_like_exchange";
public static final String MUSIC_LIKE_QUEUE_NAME1 = "music_like_queue1";
public static final String MUSIC_LIKE_QUEUE_NAME2 = "music_like_queue2";
public static final String MUSIC_DISLIKE_EXCHANGE_NAME = "music_dislike_exchange";
public static final String MUSIC_DISLIKE_QUEUE_NAME1 = "music_dislike_queue1";
public static final String MUSIC_DISLIKE_QUEUE_NAME2 = "music_dislike_queue2";
}

@ -1,40 +0,0 @@
package com.flyingpig.cloudmusic.constant;
public class RedisConstants {
public static final String USER_INFO_KEY="user:info:";
public static final Long USER_INFO_TTL=30L;
public static final String USER_LOGIN_KEY="user:login:";
public static final Long USER_LOGIN_TTL = 30L;
public static final String EMAIL_VERIFYCODE_KEY="email:verifycode:";
public static final Long EMAIL_VERIFYCODE_TTL=120L;
public static final String MUSIC_RANKLIST_KEY="music:rankList:";
public static final String MUSIC_LIKE_KEY="music:like:";
public static final Long MUSIC_LIKE_TTL=30L;
public static final String MUSIC_LIKENUM_KEY="music:like-num:";
public static final Long MUSIC_LIKENUM_TTL=30L;
public static final String LIKE_LOCK_KEY = "lock:like:";
public static final String DISLIKE_LOCK_KEY = "lock:dislike:";
public static final String MUSIC_INFO_KEY="music:like-num:";
public static final Long MUSIC_INFO_TTL=30L;
public static final Long CACHE_NULL_TTL=30L;
}

@ -1,117 +0,0 @@
package com.flyingpig.cloudmusic.util.cache;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
import static com.flyingpig.cloudmusic.constant.RedisConstants.CACHE_NULL_TTL;
@Slf4j
@Component
public class MyStringRedisTemplate {
private final StringRedisTemplate stringRedisTemplate;
public MyStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
// 加入缓存
public void set(String key, Object value, Long time, TimeUnit unit) {
stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(value), time, unit);
}
// 普通查询
public <T> T get(String key, Class<T> type) {
String value = stringRedisTemplate.opsForValue().get(key);
if (String.class.isAssignableFrom(type)) {
return (T) value;
}
return JSON.parseObject(value, type);
}
// 查询时候缓存空值防止缓存穿透,互斥锁查询防止缓存击穿
public <T> T safeGetWithLock(
String key, Class<T> type, CacheLoader<T> cacheLoader, Long time, TimeUnit unit) {
// 1.从redis查询缓存
String json = stringRedisTemplate.opsForValue().get(key);
// 2.判断是否存在
if (StrUtil.isNotBlank(json)) {
// 3.存在,直接返回
return JSON.parseObject(json, type);
}
// 判断命中的是否是空值
if (json != null) {
// 返回一个错误信息
return null;
}
// 4.实现缓存重建
// 4.1.获取互斥锁
String lockKey = "lock:" + key;
T t;
try {
boolean isLock = tryLock(lockKey, 10);
// 4.2.判断是否获取成功
if (!isLock) {
// 4.3.获取锁失败,休眠并重试
Thread.sleep(50);
return safeGetWithLock(key, type, cacheLoader, time, unit);
}
// 4.4.获取锁成功根据id查询数据库
t = cacheLoader.load();
// 5.不存在,返回错误
if (t == null) {
// 将空值写入redis
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
// 返回错误信息
return null;
}
// 6.存在写入redis
this.set(key, t, time, unit);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
// 7.释放锁
unlock(lockKey);
}
// 8.返回
return t;
}
public void delete(String key) {
stringRedisTemplate.delete(key);
}
public boolean tryLock(String lockKey, long timeoutSec) {
// 获取线程标示
String threadId = Thread.currentThread().getId()+"";
// 获取锁并判断是否成功
// setIfAbsent方法是原子的只有一个线程能够获取到锁对应redis的setNx命令
return Boolean.TRUE.equals(
stringRedisTemplate.opsForValue().setIfAbsent(lockKey, threadId, timeoutSec, TimeUnit.SECONDS));
}
public void unlock(String lockKey) {
//通过del删除锁
stringRedisTemplate.delete(lockKey);
}
}

@ -1,11 +0,0 @@
package com.flyingpig.cloudmusic.util.cache;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class RedisData {
private LocalDateTime expireTime;
private Object data;
}

@ -1,5 +0,0 @@
spring:
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB

@ -1,5 +0,0 @@
spring:
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save