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};
+