Merge pull request 'master' (#3) from master into main

Reviewed-on: #3
This commit is contained in:
wzp 2024-04-20 04:13:56 +00:00
commit 5617d2d5ab
35 changed files with 677 additions and 88 deletions

13
.idea/dataSources.xml generated
View File

@ -15,5 +15,18 @@
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="0@server.wzpmc.cn" uuid="41d00101-c8a0-446a-a28b-8027776414f2">
<driver-ref>redis</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>jdbc.RedisDriver</jdbc-driver>
<jdbc-url>jdbc:redis://server.wzpmc.cn:6379/0</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.resource.type" value="Deployment" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@ -21,10 +21,10 @@ configurations {
}
repositories {
maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
maven { url = uri("https://maven.aliyun.com/repository/public/") }
maven { url = uri("https://repo.huaweicloud.com/repository/maven/") }
maven { url = uri("https://mirrors.cloud.tencent.com/repository/maven/") }
maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
mavenCentral()
}
@ -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")

View File

@ -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);

View File

@ -6,5 +6,5 @@ import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthorizationRequired {
int requiredPermissionId() default -1;
}

View File

@ -0,0 +1,74 @@
package org.mmga.clubs.configuration;
import com.alibaba.fastjson2.JSON;
import com.auth0.jwt.exceptions.JWTVerificationException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.mmga.clubs.annotations.AuthorizationRequired;
import org.mmga.clubs.entities.BaseResponse;
import org.mmga.clubs.service.UserService;
import org.mmga.clubs.utils.JwtUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Configuration
@Slf4j
public class AuthorizationConfiguration implements HandlerInterceptor, WebMvcConfigurer{
private final JwtUtils jwtUtils;
private final UserService userService;
public AuthorizationConfiguration(JwtUtils jwtUtils, UserService userService){
this.jwtUtils = jwtUtils;
this.userService = userService;
}
@Override
public boolean preHandle(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull Object handler) throws Exception {
if (handler instanceof HandlerMethod handlerMethod){
request.setAttribute("user", -1);
String authorization = request.getHeader("Authorization");
Integer userId;
AuthorizationRequired authorizationRequired = handlerMethod.getMethodAnnotation(AuthorizationRequired.class);
try{
userId = jwtUtils.verifyToken(authorization);
request.setAttribute("user", userId);
if (authorizationRequired != null){
int permissionId = authorizationRequired.requiredPermissionId();
if (permissionId != -1){
if (!userService.userHasPermission(userId, permissionId)) {
writeAuthorizationFailedResponse(response, null);
return false;
}
}
}
}catch (JWTVerificationException e){
if (authorizationRequired != null){
writeAuthorizationFailedResponse(response, e);
return false;
}
}
}
return true;
}
private void writeAuthorizationFailedResponse(HttpServletResponse response, JWTVerificationException e) throws IOException {
log.debug("用户鉴权时出现错误:", e);
ServletOutputStream outputStream = response.getOutputStream();
response.addHeader("Content-Encoding", "UTF-8");
response.addHeader("Content-Type", "application/json; charset=utf-8");
BaseResponse<Object> err = BaseResponse.failed(401, "token错误");
outputStream.write(JSON.toJSONString(err).getBytes(StandardCharsets.UTF_8));
outputStream.close();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this);
}
}

View File

@ -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("*");
}
}

View File

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

View File

@ -19,28 +19,7 @@ public class FastJsonConfiguration implements WebMvcConfigurer {
**/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> 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;
}
}

View File

@ -1,55 +0,0 @@
package org.mmga.clubs.configuration;
import com.alibaba.fastjson2.JSON;
import com.auth0.jwt.exceptions.JWTVerificationException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.mmga.clubs.annotations.AuthorizationRequired;
import org.mmga.clubs.entities.BaseResponse;
import org.mmga.clubs.utils.JwtUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.charset.StandardCharsets;
@Configuration
@Slf4j
public class TokenConfiguration implements HandlerInterceptor, WebMvcConfigurer{
private final JwtUtils jwtUtils;
public TokenConfiguration(JwtUtils jwtUtils){
this.jwtUtils = jwtUtils;
}
@Override
public boolean preHandle(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull Object handler) throws Exception {
if (handler instanceof HandlerMethod handlerMethod){
request.setAttribute("user", -1);
String authorization = request.getHeader("Authorization");
try{
Integer userId = jwtUtils.verifyToken(authorization);
request.setAttribute("user", userId);
}catch (JWTVerificationException e){
if (handlerMethod.hasMethodAnnotation(AuthorizationRequired.class)) {
log.debug("用户鉴权时出现错误:", e);
ServletOutputStream outputStream = response.getOutputStream();
response.addHeader("Content-Encoding", "UTF-8");
response.addHeader("Content-Type", "application/json; charset=utf-8");
BaseResponse<Object> err = BaseResponse.failed(401, "token错误");
outputStream.write(JSON.toJSONString(err).getBytes(StandardCharsets.UTF_8));
outputStream.close();
return false;
}
}
}
return true;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this);
}
}

View File

@ -1,23 +1,28 @@
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;
import lombok.extern.slf4j.Slf4j;
import org.mmga.clubs.annotations.AuthorizationRequired;
import org.mmga.clubs.entities.BaseResponse;
import org.mmga.clubs.entities.user.User;
import org.mmga.clubs.entities.user.UserLoginVo;
import org.mmga.clubs.entities.user.UserRegVo;
import org.mmga.clubs.entities.Pager;
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
@ -39,4 +44,51 @@ public class UserController {
public BaseResponse<User> getUserInfo(@RequestAttribute("user") int userId){
return service.getUserInfo(userId);
}
@GetMapping("/all")
@Operation(description = "获取所有用户信息(分页)")
@AuthorizationRequired(requiredPermissionId = 4)
public BaseResponse<Pager<User>> getAllUserInfo(@RequestParam("num") int num, @RequestParam("page") int page){
return service.getAllUserInfo(num, page);
}
@PutMapping("/rename")
@Operation(description = "修改用户名")
@AuthorizationRequired
public BaseResponse<Boolean> changeUsername(@RequestBody UserRenameVo renameVo, @RequestAttribute("user") int userId){
return service.changeUsername(renameVo, userId);
}
@PutMapping("/password")
@Operation(description = "修改密码")
@AuthorizationRequired
public BaseResponse<Boolean> changePassword(@RequestBody UserChangePasswordVo changePasswordVo, @RequestAttribute("user") int userId){
return service.changePassword(changePasswordVo, userId);
}
@PutMapping("/auth")
@Operation(description = "修改用户权限组")
@AuthorizationRequired(requiredPermissionId = 4)
public BaseResponse<Boolean> changeAuth(@RequestBody UserChangeAuthVo userChangeAuthVo){
return service.changeAuth(userChangeAuthVo);
}
@PostMapping("/avatar")
@Operation(description = "上传头像")
@AuthorizationRequired
public BaseResponse<String> changeAvatar(MultipartFile file, @RequestAttribute("user") int userId){
return service.changeAvatar(file, userId);
}
@PutMapping("/avatar")
@Operation(description = "修改用户头像")
@AuthorizationRequired
public BaseResponse<Boolean> changeAvatar(@RequestParam("code") @Schema(description = "修改头像操作码,可以通过将图片文件上传至") String avatarOperationCode, @RequestAttribute("user") int uid){
return service.changeAvatar(avatarOperationCode, uid);
}
@GetMapping("/verify")
@Operation(description = "获取验证码")
public BaseResponse<VerifyResponseVo> 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);
}
}

View File

@ -9,4 +9,5 @@ import java.util.List;
@Mapper
public interface AuthPermissionDao {
List<PermissionVo> getAllPermissionByAuthId(int authId);
int countAuthPermission(int authId, int permissionId);
}

View File

@ -4,10 +4,20 @@ import org.apache.ibatis.annotations.Mapper;
import org.mmga.clubs.entities.user.UserRegResponseVo;
import org.mmga.clubs.entities.user.UserVo;
import java.util.List;
@Mapper
public interface UserDao {
UserVo getUser(String username, String password);
void addUser(UserRegResponseVo userVo);
int countUser(String username);
UserVo getUserById(int id);
int getUserAuthId(int id);
long queryTotalUserCount();
List<UserVo> getUser(int lastId, int count);
void changeUsername(int id, String name);
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);
}

View File

@ -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, String> {
UserAvatarOperationVo findByOperationCode(String operationCode);
}

View File

@ -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, String> {
VerifyVo getByKey(String key);
}

View File

@ -16,7 +16,7 @@ public class BaseResponse<T> {
private final Date time;
@Schema(description = "返回数据")
private T data;
private BaseResponse(int code, String msg, T data){
protected BaseResponse(int code, String msg, T data){
this.code = code;
this.msg = msg;
this.data = data;
@ -29,4 +29,7 @@ public class BaseResponse<T> {
public static<T> BaseResponse<T> failed(int code, String msg) {
return new BaseResponse<>(code, msg, null);
}
public static<T> BaseResponse<T> failed(int code, String msg, T data) {
return new BaseResponse<>(code, msg, data);
}
}

View File

@ -0,0 +1,7 @@
package org.mmga.clubs.entities;
import java.util.Date;
public record ClubUserVo(int clubId, int userId, Date createTime, Date updateTime) {
}

View File

@ -0,0 +1,9 @@
package org.mmga.clubs.entities;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
@Schema(description = "分页数据")
public record Pager<T>(@Schema(description = "总数据") long total,@Schema(description = "当前页面数据") List<T> data) {
}

View File

@ -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) {
}

View File

@ -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) {
}

View File

@ -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) {
}

View File

@ -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) {
}

View File

@ -0,0 +1,7 @@
package org.mmga.clubs.entities.user.change;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "用户修改权限组请求体")
public record UserChangeAuthVo(@Schema(description = "被修改的用户ID") int id, @Schema(description = "新的权限组ID") int authId) {
}

View File

@ -0,0 +1,15 @@
package org.mmga.clubs.entities.user.change;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户修改密码请求体")
@Data
public final class UserChangePasswordVo {
@Schema(description = "被修改的用户ID")
private final int id;
@Schema(description = "修改前的密码MD5值", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String oldPassword;
@Schema(description = "修改后的密码MD5值")
private final String newPassword;
}

View File

@ -0,0 +1,6 @@
package org.mmga.clubs.entities.user.change;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "用户修改用户名请求体")
public record UserRenameVo(@Schema(description = "用户ID") int id, @Schema(description = "新用户名") String newName) { }

View File

@ -0,0 +1,4 @@
package org.mmga.clubs.entities.verify;
public record VerifyImgVo(byte[] img, String code) {
}

View File

@ -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) {
}

View File

@ -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) {
}

View File

@ -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);
}
}
}
}

View File

@ -29,4 +29,8 @@ public class AuthService {
List<Permission> permissions = permissionVos.stream().map(permissionService::packagePermission).toList();
return new Auth(authVo.id(), authVo.name(), permissions);
}
public boolean authHasPermission(int authId, int permissionId) {
return authPermissionDao.countAuthPermission(authId, permissionId) > 0;
}
}

View File

@ -1,27 +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<Boolean> 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){
@ -31,6 +70,9 @@ public class UserService {
}
public BaseResponse<Boolean> 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, "用户已存在");
@ -61,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;
}
@ -71,4 +114,117 @@ public class UserService {
}
return BaseResponse.success(getUserById(userId));
}
public boolean userHasPermission(int userId, int permissionId){
return authService.authHasPermission(userDao.getUserAuthId(userId), permissionId);
}
public BaseResponse<Pager<User>> getAllUserInfo(int num, int page) {
long totalCount = userDao.queryTotalUserCount();
List<UserVo> user = userDao.getUser((page - 1) * num, num);
List<User> list = user.stream().map(this::packageUser).toList();
Pager<User> userPager = new Pager<>(totalCount, list);
return BaseResponse.success(userPager);
}
public BaseResponse<Boolean> changeUsername(UserRenameVo renameVo, int userId) {
int changeUserId = renameVo.id();
if (changeUserId == userId || userHasPermission(userId, 4)) {
userDao.changeUsername(changeUserId, renameVo.newName());
return BaseResponse.success(true);
}
return BaseResponse.failed(401, "权限不足,修改失败!", false);
}
public BaseResponse<Boolean> changePassword(UserChangePasswordVo changePasswordVo, int userId){
int changeUserId = changePasswordVo.getId();
String oldPassword = changePasswordVo.getOldPassword();
String newPassword = changePasswordVo.getNewPassword();
if (changeUserId == userId) {
if (userDao.changePassword(changeUserId, oldPassword, newPassword) > 0){
return BaseResponse.success(true);
}
return BaseResponse.failed(404,"旧密码错误!",false);
}
if (userHasPermission(userId, 4)){
userDao.changePasswordAdmin(changeUserId, newPassword);
return BaseResponse.success(true);
}
return BaseResponse.failed(401, "权限不足,修改失败!", false);
}
public BaseResponse<Boolean> changeAuth(UserChangeAuthVo userChangeAuthVo) {
userDao.changeAuth(userChangeAuthVo.id(), userChangeAuthVo.authId());
return BaseResponse.success(true);
}
public BaseResponse<Boolean> 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<VerifyResponseVo> 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<String> 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();
}
}
}

View File

@ -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);
}
}

View File

@ -6,7 +6,6 @@ import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.mmga.clubs.entities.user.User;
import org.mmga.clubs.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

View File

@ -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();
}
}

View File

@ -6,4 +6,7 @@
<select id="getAllPermissionByAuthId" resultType="org.mmga.clubs.entities.permission.PermissionVo">
select `permission`.* from `auth_permission` left join `permission` on `auth_permission`.`permission_id` = `permission`.`id` where `auth_id` = #{authId};
</select>
<select id="countAuthPermission" resultType="java.lang.Integer">
select count(*) from `auth_permission` where `auth_id` = #{authId} and `permission_id` = #{permissionId};
</select>
</mapper>

View File

@ -6,6 +6,21 @@
<insert id="addUser" useGeneratedKeys="true" keyProperty="id">
insert into `user`(`name`, `password`, `auth`) values(#{username}, #{password}, #{auth});
</insert>
<update id="changeUsername">
update `user` set `name` = #{name} where `id` = #{id};
</update>
<update id="changePassword">
update `user` set `password` = #{password} where `id` = #{id} and `password` = #{old};
</update>
<update id="changePasswordAdmin">
update `user` set `password` = #{password} where `id` = #{id};
</update>
<update id="changeAuth">
update `user` set `auth` = #{authId} where `id` = #{id};
</update>
<update id="changeAvatar">
update `user` set `avatar` = #{sha} where `id` = #{id};
</update>
<select id="getUser" resultType="org.mmga.clubs.entities.user.UserVo">
select * from `user` where `name` = #{username} and `password` = #{password};
</select>
@ -15,4 +30,10 @@
<select id="getUserById" resultType="org.mmga.clubs.entities.user.UserVo">
select * from `user` where `id` = #{id};
</select>
<select id="getUserAuthId" resultType="java.lang.Integer">
select `auth` from `user` where `id` = #{id};
</select>
<select id="queryTotalUserCount" resultType="java.lang.Long">
select count(*) from `user`;
</select>
</mapper>