From f632c54b7f997a230ee5249b254e9c04b663a4da Mon Sep 17 00:00:00 2001 From: 2991692032 Date: Sun, 4 May 2025 16:53:44 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF=E7=AE=A1?= =?UTF-8?q?=E7=90=86,=E5=B8=96=E5=AD=90=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/dataSources.xml | 31 +++ unilife-server/pom.xml | 10 + .../com/unilife/common/result/Result.java | 2 +- .../unilife/controller/UserController.java | 75 +++++- .../unilife/interceptor/JwtInterceptor.java | 19 +- .../java/com/unilife/mapper/UserMapper.java | 16 +- .../java/com/unilife/service/UserService.java | 19 +- .../unilife/service/impl/UserServiceImpl.java | 223 +++++++++++++++--- .../main/java/com/unilife/utils/JwtUtil.java | 4 +- .../src/main/resources/mappers/UserMapper.xml | 78 +++++- 10 files changed, 428 insertions(+), 49 deletions(-) diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 5e22704..b31b831 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -9,6 +9,37 @@ + + + + $ProjectFileDir$ + + + redis + true + true + $PROJECT_DIR$/unilife-server/src/main/resources/application.yml + jdbc.RedisDriver + jdbc:redis://127.0.0.1:6379/0 + + + + + + + $ProjectFileDir$ + + + mysql.8 + true + true + $PROJECT_DIR$/unilife-server/src/main/resources/application.yml + com.mysql.cj.jdbc.Driver + jdbc:mysql://localhost:3306/UniLife?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8 + + + + $ProjectFileDir$ diff --git a/unilife-server/pom.xml b/unilife-server/pom.xml index a0bdd72..b8029f4 100644 --- a/unilife-server/pom.xml +++ b/unilife-server/pom.xml @@ -36,6 +36,12 @@ spring-boot-starter-web + + com.github.pagehelper + pagehelper-spring-boot-starter + 2.1.0 + + org.mybatis.spring.boot @@ -117,6 +123,10 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-actuator + diff --git a/unilife-server/src/main/java/com/unilife/common/result/Result.java b/unilife-server/src/main/java/com/unilife/common/result/Result.java index f16d1a5..1e91ff2 100644 --- a/unilife-server/src/main/java/com/unilife/common/result/Result.java +++ b/unilife-server/src/main/java/com/unilife/common/result/Result.java @@ -40,7 +40,7 @@ public class Result{ } public static Result success(T data, String message) { - return new Result<>(200, message, null); + return new Result<>(200, message, data); } /** diff --git a/unilife-server/src/main/java/com/unilife/controller/UserController.java b/unilife-server/src/main/java/com/unilife/controller/UserController.java index e26704c..78a3bcd 100644 --- a/unilife-server/src/main/java/com/unilife/controller/UserController.java +++ b/unilife-server/src/main/java/com/unilife/controller/UserController.java @@ -5,6 +5,9 @@ import com.unilife.model.dto.EmailDTO; import com.unilife.model.dto.LoginDTO; import com.unilife.model.dto.LoginEmailDTO; import com.unilife.model.dto.RegisterDTO; +import com.unilife.model.dto.UpdateEmailDTO; +import com.unilife.model.dto.UpdatePasswordDTO; +import com.unilife.model.dto.UpdateProfileDTO; import com.unilife.model.vo.LoginVO; import com.unilife.service.UserService; import com.unilife.utils.BaseContext; @@ -13,10 +16,15 @@ import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.Operation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import jakarta.servlet.http.HttpServletRequest; @@ -63,7 +71,72 @@ public class UserController { @Operation(summary = "邮箱验证码登录") @PostMapping("login/code") public Result loginWithEmailCode(@RequestBody LoginEmailDTO loginEmailDTO, HttpServletRequest request) { - return userService.loginWithEmail(loginEmailDTO, request); + Result login = userService.loginWithEmail(loginEmailDTO, request); + LoginVO vo = (LoginVO) login.getData(); + if (vo == null) { + return login; + } + Long id = vo.getId(); + String token = jwtUtil.generateToken(id); + vo.setToken(token); + BaseContext.setId(id); + return Result.success(vo); + } + + // 用户信息管理相关API + + @Operation(summary = "获取用户个人信息") + @GetMapping("profile") + public Result getUserProfile() { + // 从当前上下文获取用户ID + Long userId = BaseContext.getId(); + if (userId == null) { + return Result.error(401, "未登录"); + } + return userService.getUserProfile(userId); + } + + @Operation(summary = "更新用户个人信息") + @PutMapping("profile") + public Result updateUserProfile(@RequestBody UpdateProfileDTO profileDTO) { + // 从当前上下文获取用户ID + Long userId = BaseContext.getId(); + if (userId == null) { + return Result.error(401, "未登录"); + } + return userService.updateUserProfile(userId, profileDTO); + } + + @Operation(summary = "修改用户密码") + @PutMapping("password") + public Result updatePassword(@RequestBody UpdatePasswordDTO passwordDTO) { + // 从当前上下文获取用户ID + Long userId = BaseContext.getId(); + if (userId == null) { + return Result.error(401, "未登录"); + } + return userService.updatePassword(userId, passwordDTO); + } + + @Operation(summary = "上传用户头像") + @PostMapping("avatar") + public Result updateAvatar(@RequestParam("file") MultipartFile file) { + // 从当前上下文获取用户ID + Long userId = BaseContext.getId(); + if (userId == null) { + return Result.error(401, "未登录"); + } + return userService.updateAvatar(userId, file); } + @Operation(summary = "更新用户邮箱") + @PutMapping("email") + public Result updateEmail(@RequestBody UpdateEmailDTO emailDTO) { + // 从当前上下文获取用户ID + Long userId = BaseContext.getId(); + if (userId == null) { + return Result.error(401, "未登录"); + } + return userService.updateEmail(userId, emailDTO); + } } diff --git a/unilife-server/src/main/java/com/unilife/interceptor/JwtInterceptor.java b/unilife-server/src/main/java/com/unilife/interceptor/JwtInterceptor.java index 5230f15..168f9c8 100644 --- a/unilife-server/src/main/java/com/unilife/interceptor/JwtInterceptor.java +++ b/unilife-server/src/main/java/com/unilife/interceptor/JwtInterceptor.java @@ -25,15 +25,22 @@ public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - log.info("JwtInterceptor preHandle"); - String token = request.getHeader("Authorization"); - if(StrUtil.isBlank(token)){ + String authHeader = request.getHeader("Authorization"); + + if(StrUtil.isBlank(authHeader)){ response.setStatus(401); return false; } + // 处理Bearer token格式 + String token = authHeader; + if(authHeader.startsWith("Bearer ")){ + token = authHeader.substring(7); + } + log.info("Extracted token:{}", token); + boolean verified = jwtUtil.verifyToken(token); if (!verified) { response.setStatus(401); @@ -42,9 +49,11 @@ public class JwtInterceptor implements HandlerInterceptor { //从token中获取userid并存入threadlocal Long userId = jwtUtil.getUserIdFromToken(token); + if(userId == null) { + response.setStatus(401); + return false; + } BaseContext.setId(userId); - - return true; } diff --git a/unilife-server/src/main/java/com/unilife/mapper/UserMapper.java b/unilife-server/src/main/java/com/unilife/mapper/UserMapper.java index a5020f4..29f2ffa 100644 --- a/unilife-server/src/main/java/com/unilife/mapper/UserMapper.java +++ b/unilife-server/src/main/java/com/unilife/mapper/UserMapper.java @@ -4,11 +4,21 @@ import com.unilife.model.entity.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import java.util.Date; + @Mapper public interface UserMapper { void insert(User user); - User FindByEmail(@Param("email") String email, @Param("password") String password); - User getUserByEmail(String email); + User findByEmail(String email); + void updateLoginInfo(@Param("userId") Long userId, + @Param("ipLocation") String ipLocation, + @Param("loginTime") Date loginTime); void UpdateIPLocation(@Param("email") String email,@Param("loginIp") String loginIp); - User FindByOnlyEmail(@Param("email") String email); + + // 用户信息管理相关方法 + User getUserById(Long id); + void updateUserProfile(User user); + void updatePassword(@Param("id") Long id, @Param("newPassword") String newPassword); + void updateAvatar(@Param("id") Long id, @Param("avatar") String avatarUrl); + void updateEmail(@Param("id") Long id, @Param("email") String email); } diff --git a/unilife-server/src/main/java/com/unilife/service/UserService.java b/unilife-server/src/main/java/com/unilife/service/UserService.java index 9b4a078..d211512 100644 --- a/unilife-server/src/main/java/com/unilife/service/UserService.java +++ b/unilife-server/src/main/java/com/unilife/service/UserService.java @@ -4,7 +4,11 @@ import com.unilife.common.result.Result; import com.unilife.model.dto.LoginDTO; import com.unilife.model.dto.LoginEmailDTO; import com.unilife.model.dto.RegisterDTO; +import com.unilife.model.dto.UpdateEmailDTO; +import com.unilife.model.dto.UpdatePasswordDTO; +import com.unilife.model.dto.UpdateProfileDTO; import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.multipart.MultipartFile; public interface UserService { @@ -12,7 +16,18 @@ public interface UserService { Result login(LoginDTO loginDTO, HttpServletRequest request); - Result sendVerificationCode(String email,HttpServletRequest request); + Result sendVerificationCode(String email, HttpServletRequest request); - Result loginWithEmail(LoginEmailDTO loginEmailDTO,HttpServletRequest request); + Result loginWithEmail(LoginEmailDTO loginEmailDTO, HttpServletRequest request); + + // 用户信息管理相关方法 + Result getUserProfile(Long userId); + + Result updateUserProfile(Long userId, UpdateProfileDTO profileDTO); + + Result updatePassword(Long userId, UpdatePasswordDTO passwordDTO); + + Result updateAvatar(Long userId, MultipartFile file); + + Result updateEmail(Long userId, UpdateEmailDTO emailDTO); } diff --git a/unilife-server/src/main/java/com/unilife/service/impl/UserServiceImpl.java b/unilife-server/src/main/java/com/unilife/service/impl/UserServiceImpl.java index 2867527..b2f103c 100644 --- a/unilife-server/src/main/java/com/unilife/service/impl/UserServiceImpl.java +++ b/unilife-server/src/main/java/com/unilife/service/impl/UserServiceImpl.java @@ -8,6 +8,9 @@ import com.unilife.mapper.UserMapper; import com.unilife.model.dto.LoginDTO; import com.unilife.model.dto.LoginEmailDTO; import com.unilife.model.dto.RegisterDTO; +import com.unilife.model.dto.UpdateEmailDTO; +import com.unilife.model.dto.UpdatePasswordDTO; +import com.unilife.model.dto.UpdateProfileDTO; import com.unilife.model.entity.User; import com.unilife.model.vo.LoginVO; import com.unilife.model.vo.RegisterVO; @@ -18,7 +21,9 @@ import com.unilife.utils.RegexUtils; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; import jakarta.servlet.http.HttpServletRequest; +import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; @@ -26,10 +31,13 @@ import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -69,7 +77,7 @@ public class UserServiceImpl implements UserService { if(registerDTO.getPassword().length() < 6) { return Result.error(400,"密码长度过短!"); } - User getuser = userMapper.FindByOnlyEmail(registerDTO.getEmail()); + User getuser = userMapper.findByEmail(registerDTO.getEmail()); if(getuser != null) { return Result.error(400,"用户已存在!"); } @@ -86,24 +94,36 @@ public class UserServiceImpl implements UserService { @Override public Result login(LoginDTO loginDTO,HttpServletRequest request) { - User user = new User(); - BeanUtil.copyProperties(loginDTO,user);//将登录的前端传来的消息拷贝给这个user - User getuser = userMapper.FindByEmail(user.getEmail(),user.getPassword()); - if(getuser == null) - { - return Result.error(loginDTO,"用户不存在,登录失败!"); + if(loginDTO==null|| StringUtils.isEmpty(loginDTO.getEmail())||StringUtils.isEmpty(loginDTO.getPassword())){ + return Result.error(400,"邮箱或密码不能为空"); } - if(!user.getPassword().equals(getuser.getPassword())) - { - return Result.error(loginDTO,"密码错误,登录失败!"); + + User user = userMapper.findByEmail(loginDTO.getEmail()); + if (user == null) { + return Result.error(400, "账号或密码错误"); } - String LastLogIpLocation = getuser.getLoginIp(); - String IPAddress = ipLocationService.getClientIP(request); - String Location = ipLocationService.getIPLocation(IPAddress); - getuser.setLoginIp(Location); - userMapper.UpdateIPLocation(getuser.getEmail(), getuser.getLoginIp()); + + if (!loginDTO.getPassword().equals(user.getPassword())) { + return Result.error(400, "账号或密码错误"); + } + + if (user.getStatus() != 1) { + return Result.error(403, "账号已被禁用,请联系管理员"); + } + + + String LastLogIpLocation = user.getLoginIp(); + String currentIp = ipLocationService.getClientIP(request); + String ipLocation = ipLocationService.getIPLocation(currentIp); + user.setLoginIp(ipLocation); + user.setLoginTime(LocalDateTime.now()); + userMapper.updateLoginInfo(user.getId(),ipLocation,new Date()); + LoginVO loginVO = new LoginVO(); - return Result.success(loginVO,"上次登录IP归属地为" + LastLogIpLocation); + BeanUtil.copyProperties(user,loginVO); + String message = StringUtils.isEmpty(LastLogIpLocation) ? "首次登录" : "上次登录IP归属地为" + LastLogIpLocation; + return Result.success(loginVO, message); + } @Override @@ -177,39 +197,43 @@ public class UserServiceImpl implements UserService { String email=loginEmailDTO.getEmail(); if(RegexUtils.isEmailInvalid(email)){ - return Result.error(null,"请输入正确的邮箱"); + return Result.error(400,"请输入正确的邮箱"); } String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstant.LOGIN_EMAIL_KEY + email); if (cacheCode == null) { - return Result.error(null, "验证码已过期或未发送,请重新获取"); + return Result.error(400, "验证码已过期或未发送,请重新获取"); } // 3. 校验验证码是否正确 String code = loginEmailDTO.getCode(); if (!cacheCode.equals(code)) { - return Result.error(null, "验证码错误"); + return Result.error(400, "验证码错误"); } // 4. 验证通过,删除验证码 stringRedisTemplate.delete(RedisConstant.LOGIN_EMAIL_KEY + email); // 5. 查询用户是否存在 - User user=userMapper.getUserByEmail(email); + User user=userMapper.findByEmail(email); if(user == null){ user = createUserWithEmail(email,request); } - //6.生成登录凭证 - //TODO + // 更新登录信息 + String currentIp = ipLocationService.getClientIP(request); + String ipLocation = ipLocationService.getIPLocation(currentIp); + user.setLoginIp(ipLocation); + user.setLoginTime(LocalDateTime.now()); + userMapper.updateLoginInfo(user.getId(), ipLocation, new Date()); - String token = jwtUtil.generateToken(user.getId()); - // 8. 返回用户信息和登录凭证 - Map userInfo = new HashMap<>(); - userInfo.put("token", token); - userInfo.put("user", user); + // 创建LoginVO对象,与普通登录保持一致的返回格式 + LoginVO loginVO = new LoginVO(); + BeanUtil.copyProperties(user, loginVO); - return Result.success(userInfo); + // 返回结果,不在这里生成token,由Controller统一处理 + String message = "邮箱验证码登录成功"; + return Result.success(loginVO, message); } /** @@ -243,4 +267,147 @@ public class UserServiceImpl implements UserService { return user; } + @Override + public Result getUserProfile(Long userId) { + // 根据用户ID获取用户信息 + User user = userMapper.getUserById(userId); + if (user == null) { + return Result.error(404, "用户不存在"); + } + + // 出于安全考虑,不返回密码字段 + user.setPassword(null); + + return Result.success(user); + } + + @Override + public Result updateUserProfile(Long userId, UpdateProfileDTO profileDTO) { + // 检查用户是否存在 + User user = userMapper.getUserById(userId); + if (user == null) { + return Result.error(404, "用户不存在"); + } + + // 更新用户信息 + user.setNickname(profileDTO.getNickname()); + user.setBio(profileDTO.getBio()); + user.setGender(profileDTO.getGender()); + user.setDepartment(profileDTO.getDepartment()); + user.setMajor(profileDTO.getMajor()); + user.setGrade(profileDTO.getGrade()); + + // 保存更新 + userMapper.updateUserProfile(user); + + return Result.success(null, "个人资料更新成功"); + } + + @Override + public Result updatePassword(Long userId, UpdatePasswordDTO passwordDTO) { + // 检查用户是否存在 + User user = userMapper.getUserById(userId); + if (user == null) { + return Result.error(404, "用户不存在"); + } + + // 验证验证码 + String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstant.LOGIN_EMAIL_KEY + user.getEmail()); + if (cacheCode == null) { + return Result.error(400, "验证码已过期或未发送,请重新获取"); + } + + if (!cacheCode.equals(passwordDTO.getCode())) { + return Result.error(400, "验证码错误"); + } + + // 验证通过,删除验证码 + stringRedisTemplate.delete(RedisConstant.LOGIN_EMAIL_KEY + user.getEmail()); + + // 更新密码 + userMapper.updatePassword(userId, passwordDTO.getNewPassword()); + + return Result.success(null, "密码修改成功"); + } + + @Override + public Result updateAvatar(Long userId, MultipartFile file) { + // 检查用户是否存在 + User user = userMapper.getUserById(userId); + if (user == null) { + return Result.error(404, "用户不存在"); + } + + // 检查文件是否为空 + if (file.isEmpty()) { + return Result.error(400, "上传文件不能为空"); + } + + // 检查文件类型 + String contentType = file.getContentType(); + if (contentType == null || !contentType.startsWith("image/")) { + return Result.error(400, "只能上传图片文件"); + } + + try { + // 生成文件名 + String originalFilename = file.getOriginalFilename(); + String suffix = originalFilename != null ? originalFilename.substring(originalFilename.lastIndexOf(".")) : ".jpg"; + String filename = "avatar_" + userId + "_" + System.currentTimeMillis() + suffix; + + // TODO: 实际项目中应该将文件保存到云存储或服务器指定目录 + // 这里简化处理,假设保存成功并返回URL + String avatarUrl = "https://example.com/avatars/" + filename; + + // 更新用户头像URL + userMapper.updateAvatar(userId, avatarUrl); + + Map data = new HashMap<>(); + data.put("avatar", avatarUrl); + + return Result.success(data, "头像上传成功"); + } catch (Exception e) { + log.error("头像上传失败", e); + return Result.error(500, "头像上传失败"); + } + } + + @Override + public Result updateEmail(Long userId, UpdateEmailDTO emailDTO) { + // 检查用户是否存在 + User user = userMapper.getUserById(userId); + if (user == null) { + return Result.error(404, "用户不存在"); + } + + // 检查邮箱格式 + String email = emailDTO.getEmail(); + if (RegexUtils.isEmailInvalid(email)) { + return Result.error(400, "邮箱格式不正确"); + } + + // 检查邮箱是否已被使用 + User existingUser = userMapper.findByEmail(email); + if (existingUser != null && !existingUser.getId().equals(userId)) { + return Result.error(400, "该邮箱已被其他用户使用"); + } + + // 验证验证码 + String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstant.LOGIN_EMAIL_KEY + email); + if (cacheCode == null) { + return Result.error(400, "验证码已过期或未发送,请重新获取"); + } + + if (!cacheCode.equals(emailDTO.getCode())) { + return Result.error(400, "验证码错误"); + } + + // 验证通过,删除验证码 + stringRedisTemplate.delete(RedisConstant.LOGIN_EMAIL_KEY + email); + + // 更新邮箱 + userMapper.updateEmail(userId, email); + + return Result.success(null, "邮箱更新成功"); + } } diff --git a/unilife-server/src/main/java/com/unilife/utils/JwtUtil.java b/unilife-server/src/main/java/com/unilife/utils/JwtUtil.java index 6d3974d..c33d5fd 100644 --- a/unilife-server/src/main/java/com/unilife/utils/JwtUtil.java +++ b/unilife-server/src/main/java/com/unilife/utils/JwtUtil.java @@ -2,6 +2,7 @@ package com.unilife.utils; import cn.hutool.core.date.DateTime; import cn.hutool.jwt.JWTUtil; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -9,6 +10,7 @@ import java.util.HashMap; import java.util.Map; @Component +@Slf4j public class JwtUtil { @Value("${jwt.secret}") private String secret; @@ -36,7 +38,7 @@ public class JwtUtil { } public Long getUserIdFromToken(String token) { try { - return (Long)JWTUtil.parseToken(token).getPayload("userId"); + return Long.valueOf(JWTUtil.parseToken(token).getPayload("userId").toString()); }catch (Exception e){ return null; } diff --git a/unilife-server/src/main/resources/mappers/UserMapper.xml b/unilife-server/src/main/resources/mappers/UserMapper.xml index 2caed2f..053ff25 100644 --- a/unilife-server/src/main/resources/mappers/UserMapper.xml +++ b/unilife-server/src/main/resources/mappers/UserMapper.xml @@ -37,18 +37,22 @@ ) - - - SELECT id, email, password, username, nickname, avatar, role, is_verified, status,login_ip FROM users WHERE email = #{email} + + UPDATE users + SET login_ip = #{ipLocation}, + login_time = #{loginTime}, + updated_at = NOW() + WHERE id = #{userId} + + + + UPDATE users SET login_ip = #{loginIp} @@ -75,4 +79,62 @@ login_time from users where email=#{email}; - \ No newline at end of file + + + + + + UPDATE users + SET nickname = #{nickname}, + bio = #{bio}, + gender = #{gender}, + department = #{department}, + major = #{major}, + grade = #{grade}, + updated_at = NOW() + WHERE id = #{id} + + + + UPDATE users + SET password = #{newPassword}, + updated_at = NOW() + WHERE id = #{id} + + + + UPDATE users + SET avatar = #{avatar}, + updated_at = NOW() + WHERE id = #{id} + + + + UPDATE users + SET email = #{email}, + updated_at = NOW() + WHERE id = #{id} + +