diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index b813e49..63eda1b 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -15,5 +15,18 @@ $ProjectFileDir$ + + redis + true + jdbc.RedisDriver + jdbc:redis://server.wzpmc.cn:6379/0 + + + + + + + $ProjectFileDir$ + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 1b94cda..bb0505b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -48,6 +48,8 @@ dependencies { implementation("com.alibaba.fastjson2:fastjson2:2.0.48") // https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2-extension-spring6 implementation("com.alibaba.fastjson2:fastjson2-extension-spring6:2.0.48") + // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis + implementation("org.springframework.boot:spring-boot-starter-data-redis:3.2.4") implementation("com.auth0:java-jwt:4.4.0") compileOnly("org.projectlombok:lombok") developmentOnly("org.springframework.boot:spring-boot-devtools") diff --git a/src/main/java/org/mmga/clubs/ItzxClubsHomeServerApplication.java b/src/main/java/org/mmga/clubs/ItzxClubsHomeServerApplication.java index eb193e2..8a012f1 100644 --- a/src/main/java/org/mmga/clubs/ItzxClubsHomeServerApplication.java +++ b/src/main/java/org/mmga/clubs/ItzxClubsHomeServerApplication.java @@ -2,8 +2,10 @@ package org.mmga.clubs; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableScheduling public class ItzxClubsHomeServerApplication { public static void main(String[] args) { SpringApplication.run(ItzxClubsHomeServerApplication.class, args); diff --git a/src/main/java/org/mmga/clubs/configuration/AuthorizationConfiguration.java b/src/main/java/org/mmga/clubs/configuration/AuthorizationConfiguration.java index 55d1b22..9c8552b 100644 --- a/src/main/java/org/mmga/clubs/configuration/AuthorizationConfiguration.java +++ b/src/main/java/org/mmga/clubs/configuration/AuthorizationConfiguration.java @@ -57,6 +57,7 @@ public class AuthorizationConfiguration implements HandlerInterceptor, WebMvcCon } return true; } + private void writeAuthorizationFailedResponse(HttpServletResponse response, JWTVerificationException e) throws IOException { log.debug("用户鉴权时出现错误:", e); ServletOutputStream outputStream = response.getOutputStream(); diff --git a/src/main/java/org/mmga/clubs/configuration/CorsConfiguration.java b/src/main/java/org/mmga/clubs/configuration/CorsConfiguration.java new file mode 100644 index 0000000..abd5f35 --- /dev/null +++ b/src/main/java/org/mmga/clubs/configuration/CorsConfiguration.java @@ -0,0 +1,22 @@ +package org.mmga.clubs.configuration; + + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@Slf4j +public class CorsConfiguration implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("http://localhost:5173", "http://127.0.0.1:5173") + .allowedMethods("*") + .allowedHeaders("*") + .exposedHeaders("*"); + + } +} \ No newline at end of file diff --git a/src/main/java/org/mmga/clubs/configuration/DirConfiguration.java b/src/main/java/org/mmga/clubs/configuration/DirConfiguration.java new file mode 100644 index 0000000..e313608 --- /dev/null +++ b/src/main/java/org/mmga/clubs/configuration/DirConfiguration.java @@ -0,0 +1,39 @@ +package org.mmga.clubs.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.io.File; +import java.io.IOException; + +@Configuration +public class DirConfiguration { + private File tempDir; + private File avatarDir; + @Bean + public File tempDir() throws IOException { + if (this.tempDir != null) { + return this.tempDir; + } + this.tempDir = new File("tmp"); + if (!this.tempDir.exists()) { + if (!this.tempDir.mkdir()) { + throw new IOException("Failed to create temp dir"); + } + } + return this.tempDir; + } + @Bean + public File avatarDir() throws IOException { + if (this.avatarDir != null) { + return this.avatarDir; + } + this.avatarDir = new File("avatar"); + if (!this.avatarDir.exists()) { + if (!this.avatarDir.mkdir()) { + throw new IOException("Failed to create avatar dir"); + } + } + return this.avatarDir; + } +} diff --git a/src/main/java/org/mmga/clubs/configuration/FastJsonConfiguration.java b/src/main/java/org/mmga/clubs/configuration/FastJsonConfiguration.java index 1021ea9..4c6fdb2 100644 --- a/src/main/java/org/mmga/clubs/configuration/FastJsonConfiguration.java +++ b/src/main/java/org/mmga/clubs/configuration/FastJsonConfiguration.java @@ -19,28 +19,7 @@ public class FastJsonConfiguration implements WebMvcConfigurer { **/ @Override public void configureMessageConverters(List> converters) { - // 1. 配置fastjson - FastJsonConfig config = new FastJsonConfig(); - - config.setDateFormat("yyyy-MM-dd HH:mm:ss"); - config.setCharset(StandardCharsets.UTF_8); - config.setWriterFeatures( - JSONWriter.Feature.WriteNullListAsEmpty, - //json格式化 - JSONWriter.Feature.PrettyFormat, - //输出map中value为null的数据 - JSONWriter.Feature.WriteMapNullValue, - //输出boolean 为 false - JSONWriter.Feature.WriteNullBooleanAsFalse, - //输出list 为 [] - JSONWriter.Feature.WriteNullListAsEmpty, - //输出number 为 0 - JSONWriter.Feature.WriteNullNumberAsZero, - //输出字符串 为 "" - JSONWriter.Feature.WriteNullStringAsEmpty, - //对map进行排序 - JSONWriter.Feature.SortMapEntriesByKeys - ); + FastJsonConfig config = getFastJsonConfig(); // 2. 添加fastjson转换器 FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); @@ -70,4 +49,29 @@ public class FastJsonConfiguration implements WebMvcConfigurer { converter.setFastJsonConfig(config); converters.add(0,converter); } + + private static FastJsonConfig getFastJsonConfig() { + FastJsonConfig config = new FastJsonConfig(); + + config.setDateFormat("yyyy-MM-dd HH:mm:ss"); + config.setCharset(StandardCharsets.UTF_8); + config.setWriterFeatures( + JSONWriter.Feature.WriteNullListAsEmpty, + //json格式化 + JSONWriter.Feature.PrettyFormat, + //输出map中value为null的数据 + JSONWriter.Feature.WriteMapNullValue, + //输出boolean 为 false + JSONWriter.Feature.WriteNullBooleanAsFalse, + //输出list 为 [] + JSONWriter.Feature.WriteNullListAsEmpty, + //输出number 为 0 + JSONWriter.Feature.WriteNullNumberAsZero, + //输出字符串 为 "" + JSONWriter.Feature.WriteNullStringAsEmpty, + //对map进行排序 + JSONWriter.Feature.SortMapEntriesByKeys + ); + return config; + } } \ No newline at end of file diff --git a/src/main/java/org/mmga/clubs/controller/UserController.java b/src/main/java/org/mmga/clubs/controller/UserController.java index 2670ed4..16bb488 100644 --- a/src/main/java/org/mmga/clubs/controller/UserController.java +++ b/src/main/java/org/mmga/clubs/controller/UserController.java @@ -1,6 +1,7 @@ package org.mmga.clubs.controller; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; @@ -12,15 +13,16 @@ import org.mmga.clubs.entities.user.*; import org.mmga.clubs.entities.user.change.UserChangeAuthVo; import org.mmga.clubs.entities.user.change.UserChangePasswordVo; import org.mmga.clubs.entities.user.change.UserRenameVo; +import org.mmga.clubs.entities.verify.VerifyResponseVo; import org.mmga.clubs.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping("/api/user") @Tag(name = "用户", description = "用户相关接口") @Slf4j -@CrossOrigin(allowCredentials = "true", allowedHeaders = {"Set-Authorization", "Authorization"}, origins = {"http://localhost:5173"}) public class UserController { private final UserService service; @Autowired @@ -66,5 +68,27 @@ public class UserController { public BaseResponse changeAuth(@RequestBody UserChangeAuthVo userChangeAuthVo){ return service.changeAuth(userChangeAuthVo); } - + @PostMapping("/avatar") + @Operation(description = "上传头像") + @AuthorizationRequired + public BaseResponse changeAvatar(MultipartFile file, @RequestAttribute("user") int userId){ + return service.changeAvatar(file, userId); + } + @PutMapping("/avatar") + @Operation(description = "修改用户头像") + @AuthorizationRequired + public BaseResponse changeAvatar(@RequestParam("code") @Schema(description = "修改头像操作码,可以通过将图片文件上传至") String avatarOperationCode, @RequestAttribute("user") int uid){ + return service.changeAvatar(avatarOperationCode, uid); + } + @GetMapping("/verify") + @Operation(description = "获取验证码") + public BaseResponse generatorVerifyCode(){ + return service.getVerifyCode(); + } + @GetMapping("/avatar/{sha1}") + @Operation(description = "获取头像文件") + public void getAvatar(HttpServletResponse response, @Schema(description = "头像文件SHA1值") @PathVariable("sha1") String sha1){ + service.getAvatar(response, sha1); + } } + diff --git a/src/main/java/org/mmga/clubs/dao/UserDao.java b/src/main/java/org/mmga/clubs/dao/UserDao.java index 3b3b79a..92b3352 100644 --- a/src/main/java/org/mmga/clubs/dao/UserDao.java +++ b/src/main/java/org/mmga/clubs/dao/UserDao.java @@ -19,4 +19,5 @@ public interface UserDao { int changePassword(int id, String old, String password); void changePasswordAdmin(int id, String password); void changeAuth(int id, int authId); + void changeAvatar(int id, String sha); } diff --git a/src/main/java/org/mmga/clubs/dao/redis/AvatarDao.java b/src/main/java/org/mmga/clubs/dao/redis/AvatarDao.java new file mode 100644 index 0000000..e0b752e --- /dev/null +++ b/src/main/java/org/mmga/clubs/dao/redis/AvatarDao.java @@ -0,0 +1,8 @@ +package org.mmga.clubs.dao.redis; + +import org.mmga.clubs.entities.user.change.UserAvatarOperationVo; +import org.springframework.data.repository.CrudRepository; + +public interface AvatarDao extends CrudRepository { + UserAvatarOperationVo findByOperationCode(String operationCode); +} diff --git a/src/main/java/org/mmga/clubs/dao/redis/VerifyDao.java b/src/main/java/org/mmga/clubs/dao/redis/VerifyDao.java new file mode 100644 index 0000000..1e0d382 --- /dev/null +++ b/src/main/java/org/mmga/clubs/dao/redis/VerifyDao.java @@ -0,0 +1,8 @@ +package org.mmga.clubs.dao.redis; + +import org.mmga.clubs.entities.verify.VerifyVo; +import org.springframework.data.repository.CrudRepository; + +public interface VerifyDao extends CrudRepository { + VerifyVo getByKey(String key); +} diff --git a/src/main/java/org/mmga/clubs/entities/ClubUserVo.java b/src/main/java/org/mmga/clubs/entities/ClubUserVo.java new file mode 100644 index 0000000..9f955b6 --- /dev/null +++ b/src/main/java/org/mmga/clubs/entities/ClubUserVo.java @@ -0,0 +1,7 @@ +package org.mmga.clubs.entities; + +import java.util.Date; + +public record ClubUserVo(int clubId, int userId, Date createTime, Date updateTime) { +} + diff --git a/src/main/java/org/mmga/clubs/entities/club/ClubVo.java b/src/main/java/org/mmga/clubs/entities/club/ClubVo.java new file mode 100644 index 0000000..a13c90c --- /dev/null +++ b/src/main/java/org/mmga/clubs/entities/club/ClubVo.java @@ -0,0 +1,6 @@ +package org.mmga.clubs.entities.club; + +import java.util.Date; + +public record ClubVo(int id, String name, String commit, Date createTime, Date updateTime) { +} diff --git a/src/main/java/org/mmga/clubs/entities/user/UserLoginVo.java b/src/main/java/org/mmga/clubs/entities/user/UserLoginVo.java index 7883577..31e4a26 100644 --- a/src/main/java/org/mmga/clubs/entities/user/UserLoginVo.java +++ b/src/main/java/org/mmga/clubs/entities/user/UserLoginVo.java @@ -3,5 +3,5 @@ package org.mmga.clubs.entities.user; import io.swagger.v3.oas.annotations.media.Schema; @Schema(description = "用户登录参数类") -public record UserLoginVo(@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED) String username, @Schema(description = "用户密码(使用MD5摘要后的hex字符串)") String password) { +public record UserLoginVo(@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED) String username, @Schema(description = "用户密码(使用MD5摘要后的hex字符串)") String password, @Schema(description = "验证码密钥") String key, @Schema(description = "验证码") String code) { } diff --git a/src/main/java/org/mmga/clubs/entities/user/UserRegVo.java b/src/main/java/org/mmga/clubs/entities/user/UserRegVo.java index 8bc3033..e6af5b7 100644 --- a/src/main/java/org/mmga/clubs/entities/user/UserRegVo.java +++ b/src/main/java/org/mmga/clubs/entities/user/UserRegVo.java @@ -3,5 +3,5 @@ package org.mmga.clubs.entities.user; import io.swagger.v3.oas.annotations.media.Schema; @Schema(description = "用户注册参数") -public record UserRegVo(@Schema(description = "用户名") String username,@Schema(description = "密码(使用MD5摘要后的hex字符串)") String password,@Schema(description = "用户的权限组ID") int auth) { +public record UserRegVo(@Schema(description = "用户名") String username,@Schema(description = "密码(使用MD5摘要后的hex字符串)") String password,@Schema(description = "用户的权限组ID") int auth, @Schema(description = "验证码密钥") String key, @Schema(description = "验证码") String code) { } diff --git a/src/main/java/org/mmga/clubs/entities/user/change/UserAvatarOperationVo.java b/src/main/java/org/mmga/clubs/entities/user/change/UserAvatarOperationVo.java new file mode 100644 index 0000000..3fb675e --- /dev/null +++ b/src/main/java/org/mmga/clubs/entities/user/change/UserAvatarOperationVo.java @@ -0,0 +1,11 @@ +package org.mmga.clubs.entities.user.change; + +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; + +import java.util.Date; + +@RedisHash("avatar") +public record UserAvatarOperationVo(@Id String operationCode, @Indexed String avatarSHA, @Indexed int userId, @Indexed Date createTime) { +} diff --git a/src/main/java/org/mmga/clubs/entities/user/change/UserChangeAvatarVo.java b/src/main/java/org/mmga/clubs/entities/user/change/UserChangeAvatarVo.java deleted file mode 100644 index ec03e68..0000000 --- a/src/main/java/org/mmga/clubs/entities/user/change/UserChangeAvatarVo.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.mmga.clubs.entities.user.change; - -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "用户修改头像请求体") -public record UserChangeAvatarVo(@Schema(description = "被修改的用户ID") int userId,@Schema(description = "修改头像操作码,可以通过将图片文件上传至") String avatarOperationCode) { -} diff --git a/src/main/java/org/mmga/clubs/entities/verify/VerifyImgVo.java b/src/main/java/org/mmga/clubs/entities/verify/VerifyImgVo.java new file mode 100644 index 0000000..01485b1 --- /dev/null +++ b/src/main/java/org/mmga/clubs/entities/verify/VerifyImgVo.java @@ -0,0 +1,4 @@ +package org.mmga.clubs.entities.verify; + +public record VerifyImgVo(byte[] img, String code) { +} diff --git a/src/main/java/org/mmga/clubs/entities/verify/VerifyResponseVo.java b/src/main/java/org/mmga/clubs/entities/verify/VerifyResponseVo.java new file mode 100644 index 0000000..99f49de --- /dev/null +++ b/src/main/java/org/mmga/clubs/entities/verify/VerifyResponseVo.java @@ -0,0 +1,7 @@ +package org.mmga.clubs.entities.verify; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "验证码返回值") +public record VerifyResponseVo(@Schema(description = "验证码图像base64") String img,@Schema(description = "验证码验证密钥") String key) { +} diff --git a/src/main/java/org/mmga/clubs/entities/verify/VerifyVo.java b/src/main/java/org/mmga/clubs/entities/verify/VerifyVo.java new file mode 100644 index 0000000..3f9c8b2 --- /dev/null +++ b/src/main/java/org/mmga/clubs/entities/verify/VerifyVo.java @@ -0,0 +1,11 @@ +package org.mmga.clubs.entities.verify; + +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; + +import java.util.Date; + +@RedisHash("verify") +public record VerifyVo(@Id String key, @Indexed String answer, @Indexed Date createTime) { +} diff --git a/src/main/java/org/mmga/clubs/schedule/RemoveTempAvatarSchedule.java b/src/main/java/org/mmga/clubs/schedule/RemoveTempAvatarSchedule.java new file mode 100644 index 0000000..b468b2f --- /dev/null +++ b/src/main/java/org/mmga/clubs/schedule/RemoveTempAvatarSchedule.java @@ -0,0 +1,55 @@ +package org.mmga.clubs.schedule; + +import lombok.extern.slf4j.Slf4j; +import org.mmga.clubs.dao.redis.AvatarDao; +import org.mmga.clubs.dao.redis.VerifyDao; +import org.mmga.clubs.entities.user.change.UserAvatarOperationVo; +import org.mmga.clubs.entities.verify.VerifyVo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.util.Date; + +@Slf4j +@Service +public class RemoveTempAvatarSchedule { + private final AvatarDao avatarDao; + private final File tempDir; + private final VerifyDao verifyDao; + + @Autowired + public RemoveTempAvatarSchedule(AvatarDao avatarDao, File tempDir, VerifyDao verifyDao){ + this.avatarDao = avatarDao; + this.tempDir = tempDir; + this.verifyDao = verifyDao; + } + @Scheduled(fixedDelay = 1000) + public void removeTempAvatar() { + for (UserAvatarOperationVo userAvatarOperationVo : avatarDao.findAll()) { + long i = new Date().getTime() - userAvatarOperationVo.createTime().getTime(); + if (i >= 300000){ + avatarDao.delete(userAvatarOperationVo); + String s = userAvatarOperationVo.avatarSHA(); + File file = new File(this.tempDir, s); + String absolutePath = file.getAbsolutePath(); + log.info("删除了临时头像文件:{}", absolutePath); + if (file.exists()) { + if (!file.delete()) { + log.error("无法删除临时头像文件:{}", absolutePath); + } + } + } + } + } + @Scheduled(fixedDelay = 1000) + public void removeExpiredVerify() { + for (VerifyVo verifyVo : verifyDao.findAll()) { + long i = new Date().getTime() - verifyVo.createTime().getTime(); + if (i >= 300000){ + verifyDao.delete(verifyVo); + } + } + } +} diff --git a/src/main/java/org/mmga/clubs/service/UserService.java b/src/main/java/org/mmga/clubs/service/UserService.java index 862d02e..7c8ed92 100644 --- a/src/main/java/org/mmga/clubs/service/UserService.java +++ b/src/main/java/org/mmga/clubs/service/UserService.java @@ -1,33 +1,66 @@ package org.mmga.clubs.service; +import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; import org.mmga.clubs.dao.UserDao; +import org.mmga.clubs.dao.redis.AvatarDao; import org.mmga.clubs.entities.BaseResponse; import org.mmga.clubs.entities.Pager; import org.mmga.clubs.entities.user.*; +import org.mmga.clubs.entities.user.change.UserAvatarOperationVo; import org.mmga.clubs.entities.user.change.UserChangeAuthVo; import org.mmga.clubs.entities.user.change.UserChangePasswordVo; import org.mmga.clubs.entities.user.change.UserRenameVo; +import org.mmga.clubs.entities.verify.VerifyImgVo; +import org.mmga.clubs.entities.verify.VerifyResponseVo; import org.mmga.clubs.utils.JwtUtils; +import org.mmga.clubs.utils.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import org.springframework.util.StreamUtils; +import org.springframework.web.multipart.MultipartFile; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.util.Base64; +import java.util.Date; import java.util.List; @Service +@Slf4j public class UserService { private final UserDao userDao; private final AuthService authService; private final JwtUtils jwtUtils; + private final VerifyCodeService verifyCodeService; + private final File tempDir; + private final AvatarDao avatarDao; + private final File avatarDir; + @Autowired - public UserService(UserDao userDao, AuthService authService, JwtUtils jwtUtils){ + public UserService(UserDao userDao, AuthService authService, JwtUtils jwtUtils, VerifyCodeService verifyCodeService, File tempDir, AvatarDao avatarDao, @Qualifier("avatarDir") File avatarDir){ this.userDao = userDao; this.authService = authService; this.jwtUtils = jwtUtils; + this.verifyCodeService = verifyCodeService; + this.tempDir = tempDir; + this.avatarDao = avatarDao; + this.avatarDir = avatarDir; } public BaseResponse login(UserLoginVo user, HttpServletResponse response) { + if (verifyCodeService.invalid(user.key(), user.code())) { + return BaseResponse.failed(403, "验证码错误"); + } UserVo userVo = userDao.getUser(user.username(), DigestUtils.sha1Hex(user.password())); User u = packageUser(userVo); if (response != null && u != null){ @@ -37,6 +70,9 @@ public class UserService { } public BaseResponse createUser(UserRegVo user, HttpServletResponse response) { + if (verifyCodeService.invalid(user.key(), user.code())) { + return BaseResponse.failed(403, "验证码错误"); + } String username = user.username(); if (userDao.countUser(username) > 0) { return BaseResponse.failed(409, "用户已存在"); @@ -67,6 +103,7 @@ public class UserService { User result = new User(); result.setId(vo.id()); result.setName(vo.name()); + result.setAvatar(vo.avatar()); result.setAuth(authService.getAuthById(vo.auth())); return result; } @@ -118,4 +155,76 @@ public class UserService { userDao.changeAuth(userChangeAuthVo.id(), userChangeAuthVo.authId()); return BaseResponse.success(true); } + + public BaseResponse changeAvatar(String avatarOperationCode, int uid) { + UserAvatarOperationVo byOperationCode = avatarDao.findByOperationCode(avatarOperationCode); + if (byOperationCode == null) { + return BaseResponse.failed(404, "操作码错误!"); + } + int operationUserId = byOperationCode.userId(); + if (operationUserId != uid && !userHasPermission(uid, 4)) { + return BaseResponse.failed(403, "权限不足"); + } + avatarDao.delete(byOperationCode); + String sha = byOperationCode.avatarSHA(); + userDao.changeAvatar(operationUserId, sha); + File file = new File(avatarDir, sha); + File tmpFile = new File(tempDir, avatarOperationCode); + if (!tmpFile.exists()){ + return BaseResponse.failed(500, "无法找到已上传的头像信息"); + } + if (!file.exists()){ + if (!tmpFile.renameTo(file)) { + log.error("移动头像文件失败!"); + } + }else { + if (!tmpFile.delete()) { + log.error("无法删除临时头像文件"); + } + } + return BaseResponse.success(true); + } + + public BaseResponse getVerifyCode() { + Base64.Encoder mimeEncoder = Base64.getMimeEncoder(); + VerifyImgVo verifyImgVo = verifyCodeService.generateVerifyCode(); + String s = mimeEncoder.encodeToString(verifyImgVo.img()); + return BaseResponse.success(new VerifyResponseVo(s, verifyImgVo.code())); + } + + @SneakyThrows + public BaseResponse changeAvatar(MultipartFile file, int userId) { + InputStream inputStream = file.getInputStream(); + DigestInputStream sha1 = new DigestInputStream(inputStream, MessageDigest.getInstance("SHA1")); + String operationCode; + while (true) { + operationCode = StringUtils.generatorRandomString(32); + File tmpAvatarImg = new File(tempDir, operationCode); + if (tmpAvatarImg.exists()) { + continue; + } + FileOutputStream fileOutputStream = new FileOutputStream(tmpAvatarImg); + StreamUtils.copy(sha1, fileOutputStream); + fileOutputStream.close(); + sha1.close(); + break; + } + String sha1Result = Hex.encodeHexString(sha1.getMessageDigest().digest()); + UserAvatarOperationVo userAvatarOperationVo = new UserAvatarOperationVo(operationCode, sha1Result, userId, new Date()); + avatarDao.save(userAvatarOperationVo); + return BaseResponse.success(operationCode); + } + + @SneakyThrows + public void getAvatar(HttpServletResponse response, String sha1) { + File avatar = new File(avatarDir, sha1); + if (avatar.exists()){ + FileInputStream fileInputStream = new FileInputStream(avatar); + response.setContentType("image/png"); + ServletOutputStream outputStream = response.getOutputStream(); + StreamUtils.copy(fileInputStream, outputStream); + outputStream.close(); + fileInputStream.close(); + } + } } diff --git a/src/main/java/org/mmga/clubs/service/VerifyCodeService.java b/src/main/java/org/mmga/clubs/service/VerifyCodeService.java new file mode 100644 index 0000000..6daceb0 --- /dev/null +++ b/src/main/java/org/mmga/clubs/service/VerifyCodeService.java @@ -0,0 +1,70 @@ +package org.mmga.clubs.service; + +import lombok.SneakyThrows; +import org.mmga.clubs.dao.redis.VerifyDao; +import org.mmga.clubs.entities.verify.VerifyImgVo; +import org.mmga.clubs.entities.verify.VerifyVo; +import org.mmga.clubs.utils.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.util.Date; +import java.util.Random; + +import static org.mmga.clubs.utils.StringUtils.generatorRandomString; + +@Service +public class VerifyCodeService { + private final VerifyDao verifyDao; + + @Autowired + public VerifyCodeService(VerifyDao verifyDao) { + this.verifyDao = verifyDao; + } + + @SneakyThrows + public VerifyImgVo generateVerifyCode() { + int width = 100; + int height = 40; + BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB); + Graphics g = image.createGraphics(); + Font font = new Font("宋体",Font.BOLD,25); + g.setFont(font); + StringBuilder code = new StringBuilder(); + for (int i = 0; i < 4; i++) { + int index = new Random().nextInt(StringUtils.source.length() - 1); + char myCode = StringUtils.source.charAt(index); + Random random = new Random(); + g.setColor(new Color(20+random.nextInt(120),20+random.nextInt(120),20+random.nextInt(120))); + g.drawString(myCode+"",15+i*20,20+new Random().nextInt(10)); + for (int j = 0; j < 5; j++) { + g.drawLine(random.nextInt(width),random.nextInt(height),random.nextInt(width),random.nextInt(height)); + } + code.append(myCode); + } + g.dispose(); + String s = generatorRandomString(32); + String codeString = code.toString(); + verifyDao.save(new VerifyVo(s, codeString, new Date())); + try(ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()){ + ImageIO.write(image, "png", byteArrayOutputStream); + return new VerifyImgVo(byteArrayOutputStream.toByteArray(), s); + } + } + public boolean invalid(String code, String ans){ + if (code == null || ans == null){ + return true; + } + VerifyVo byKey = verifyDao.getByKey(code); + verifyDao.deleteById(code); + if (byKey == null){ + return true; + } + return !byKey.answer().equalsIgnoreCase(ans); + } +} + diff --git a/src/main/java/org/mmga/clubs/utils/StringUtils.java b/src/main/java/org/mmga/clubs/utils/StringUtils.java new file mode 100644 index 0000000..3d4d2e7 --- /dev/null +++ b/src/main/java/org/mmga/clubs/utils/StringUtils.java @@ -0,0 +1,15 @@ +package org.mmga.clubs.utils; + +import java.util.Random; + +public class StringUtils { + public final static String source = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + public static String generatorRandomString(int length) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < length; i++) { + int index = new Random().nextInt(source.length() - 1); + builder.append(source.charAt(index)); + } + return builder.toString(); + } +} diff --git a/src/main/resources/org/mmga/clubs/dao/UserDao.xml b/src/main/resources/org/mmga/clubs/dao/UserDao.xml index e5cbe29..8b50e07 100644 --- a/src/main/resources/org/mmga/clubs/dao/UserDao.xml +++ b/src/main/resources/org/mmga/clubs/dao/UserDao.xml @@ -18,6 +18,9 @@ update `user` set `auth` = #{authId} where `id` = #{id}; + + update `user` set `avatar` = #{sha} where `id` = #{id}; +