Explorar el Código

实现修改密码功能

chenjiayao hace 1 año
padre
commit
75d8611ccd

+ 2 - 0
src/main/java/com/njuzr/eaibackend/config/SecurityConfig.java

@@ -51,7 +51,9 @@ public class SecurityConfig {
         http
                 .authorizeHttpRequests(authorize -> authorize
                         .requestMatchers("/api/auth/*",
+                                "/api/email/*",
                                 "/api/user/register",
+                                "/api/user/updatePassword",
                                 "/api/user/verifyCode",
                                 "/api/onlyoffice/**",
                                 "/api/assignment/**").permitAll() // 允许公开访问的路径

+ 25 - 22
src/main/java/com/njuzr/eaibackend/controller/AdminController.java

@@ -2,7 +2,7 @@ package com.njuzr.eaibackend.controller;
 
 import com.njuzr.eaibackend.dto.user.AdminRegisterDTO;
 import com.njuzr.eaibackend.dto.user.UserDTO;
-import com.njuzr.eaibackend.service.UserService;
+import com.njuzr.eaibackend.service.AdminService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -21,12 +21,9 @@ import org.springframework.web.multipart.MultipartFile;
 @RequestMapping("/api/admin")
 public class AdminController {
 
-    private final UserService userService;
-
     @Autowired
-    public AdminController(UserService userService) {
-        this.userService = userService;
-    }
+    private AdminService adminService;
+
 
     /**
      * 管理员创建用户(学生和老师)
@@ -36,7 +33,7 @@ public class AdminController {
     @PreAuthorize("hasRole('ROLE_ADMIN')")
     @PostMapping("/createUser")
     public MyResponse createUser(@Validated @RequestBody AdminRegisterDTO adminRegisterDTO) {
-        userService.adminCreateUser(adminRegisterDTO);
+        adminService.createUser(adminRegisterDTO);
         return MyResponse.success("创建成功");
     }
 
@@ -48,16 +45,33 @@ public class AdminController {
     @PreAuthorize("hasRole('ROLE_ADMIN')")
     @PostMapping("/updateUser")
     public MyResponse updateUser(@RequestParam("id") Long id, @RequestBody UserDTO userDTO) {
-        return MyResponse.success(userService.updateUser(id, userDTO));
+        return MyResponse.success(adminService.updateUser(id, userDTO));
     }
 
+    /**
+     * 管理员重置用户的密码,并将重置密码发送到指定邮箱
+     * @param id
+     * @return
+     */
     @PreAuthorize("hasRole('ROLE_ADMIN')")
     @PutMapping("/resetPassword")
     public MyResponse resetPassword(@RequestParam("id") Long id) {
-        userService.resetPassword(id);
+        adminService.resetPassword(id);
         return MyResponse.success("重置密码成功");
     }
 
+    /**
+     * (管理员)根据ID删除某个特定的用户
+     * @param id
+     * @return
+     */
+    @DeleteMapping()
+    @PreAuthorize("hasRole('ROLE_ADMIN')")
+    public MyResponse deleteById(@RequestParam Long id) {
+        adminService.deleteById(id);
+        return MyResponse.success("删除成功");
+    }
+
 
     /**
      * (管理员)通过上传excel/csv文件,批量创建用户,文件必须含有字段信息:姓名、学号、邮箱
@@ -71,9 +85,9 @@ public class AdminController {
         // 根据文件类型进行不同的处理
         try {
             if(fileName.endsWith(".xls") || fileName.endsWith(".xlsx")) {
-                userService.batchCreateUsersFromExcel(file);
+                adminService.batchCreateUsersFromExcel(file);
             } else if (fileName.endsWith(".csv")) {
-                userService.batchCreateUsersFromCsv(file);
+                adminService.batchCreateUsersFromCsv(file);
             }else {
                 return MyResponse.error(400, "文件类型不支持");
             }
@@ -85,15 +99,4 @@ public class AdminController {
         return MyResponse.success("批量创建用户成功");
     }
 
-    /**
-     * (管理员)根据ID删除某个特定的用户
-     * @param id
-     * @return
-     */
-    @DeleteMapping()
-    @PreAuthorize("hasRole('ROLE_ADMIN')")
-    public MyResponse deleteById(@RequestParam Long id) {
-        userService.deleteById(id);
-        return MyResponse.success("删除成功");
-    }
 }

+ 0 - 9
src/main/java/com/njuzr/eaibackend/controller/AuthenticationController.java

@@ -36,14 +36,5 @@ public class AuthenticationController {
         return authenticationService.loginByEmail(emailLoginDTO);
     }
 
-    /**
-     * 发送邮箱登录的验证码
-     */
-    @GetMapping("/loginVerifyCode")
-    public MyResponse sendLoginVerifyCode(@RequestParam(value = "officialEmail") String officialEmail) {
-        authenticationService.sendLoginVerifyCode(officialEmail);
-        return MyResponse.success("发送成功");
-    }
-
 
 }

+ 35 - 0
src/main/java/com/njuzr/eaibackend/controller/EmailController.java

@@ -0,0 +1,35 @@
+package com.njuzr.eaibackend.controller;
+
+import com.njuzr.eaibackend.service.EmailService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/email")
+public class EmailController {
+
+    @Autowired
+    private EmailService emailService;
+
+    @GetMapping("/registerVerifyCode")
+    public MyResponse sendRegisterVerifyCode(@RequestParam(value = "officialEmail") String officialEmail) {
+        emailService.sendRegisterVerifyCode(officialEmail);
+        return MyResponse.success("发送成功");
+    }
+
+    @GetMapping("/loginVerifyCode")
+    public MyResponse sendLoginVerifyCode(@RequestParam(value = "officialEmail") String officialEmail) {
+        emailService.sendLoginVerifyCode(officialEmail);
+        return MyResponse.success("发送成功");
+    }
+
+    @GetMapping("/passwordChangeVerifyCode")
+    public MyResponse sendPasswordChangeVerifyCode(@RequestParam(value = "officialEmail") String officialEmail) {
+        emailService.sendPasswordChangeVerifyCode(officialEmail);
+        return MyResponse.success("发送成功");
+    }
+
+}

+ 6 - 22
src/main/java/com/njuzr/eaibackend/controller/UserController.java

@@ -2,6 +2,7 @@ package com.njuzr.eaibackend.controller;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.njuzr.eaibackend.dto.user.PasswordChangeDTO;
 import com.njuzr.eaibackend.dto.user.UserRegisterDTO;
 import com.njuzr.eaibackend.dto.user.UserUpdateDTO;
 import com.njuzr.eaibackend.enums.Role;
@@ -16,23 +17,13 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-/**
- * @author: Leonezhurui
- * @Date: 2024/2/14 - 22:11
- * @Package: EAI-Backend
- */
 
 @Slf4j
 @RestController
 @RequestMapping("/api/user")
 public class UserController {
-
-    private final UserService userService;
-
     @Autowired
-    public UserController(UserService userService) {
-        this.userService = userService;
-    }
+    private UserService userService;
 
 
     /**
@@ -46,11 +37,6 @@ public class UserController {
         return MyResponse.success(userVO);
     }
 
-    @GetMapping("/verifyCode")
-    public MyResponse sendVerifyCode(@RequestParam(value = "officialEmail") String officialEmail) {
-        userService.sendVerifyCode(officialEmail);
-        return MyResponse.success("发送成功");
-    }
 
     /**
      * (学生、老师)更新自己的用户信息,固定信息不可更改,例如真实姓名、邮箱、学号、角色等
@@ -66,14 +52,12 @@ public class UserController {
         return MyResponse.success(userVO);
     }
 
-    @PreAuthorize("hasRole('ROLE_STUDENT') or hasRole('ROLE_TEACHER')")
-    @PutMapping("/updatePassword")
+
+    @PostMapping("/updatePassword")
     public MyResponse updatePassword(
-            @AuthenticationPrincipal(expression = "id") Long id,
-            @RequestParam("newPassword") String newPassword,
-            @RequestParam("oldPassword") String oldPassword) {
+            @Validated @RequestBody PasswordChangeDTO passwordChangeDTO) {
         try {
-            userService.updatePassword(id, newPassword, oldPassword);
+            userService.updatePassword(passwordChangeDTO);
             return MyResponse.success("用户密码更新成功");
         } catch (MyException e) {
             return MyResponse.error(e.getErrCode(), e.getMessage());

+ 10 - 0
src/main/java/com/njuzr/eaibackend/dto/user/PasswordChangeDTO.java

@@ -0,0 +1,10 @@
+package com.njuzr.eaibackend.dto.user;
+
+import lombok.Data;
+
+@Data
+public class PasswordChangeDTO {
+    String officialEmail;
+    String password;
+    String verifyCode;
+}

+ 21 - 0
src/main/java/com/njuzr/eaibackend/service/AdminService.java

@@ -0,0 +1,21 @@
+package com.njuzr.eaibackend.service;
+
+import com.njuzr.eaibackend.dto.user.AdminRegisterDTO;
+import com.njuzr.eaibackend.dto.user.UserDTO;
+import com.njuzr.eaibackend.vo.UserVO;
+import org.springframework.web.multipart.MultipartFile;
+
+public interface AdminService {
+
+    void createUser(AdminRegisterDTO adminRegisterDTO);
+
+    UserVO updateUser(Long id, UserDTO userDTO);
+
+    void deleteById(Long id);
+
+    void resetPassword(Long id);
+
+    void batchCreateUsersFromExcel(MultipartFile file) throws Exception;
+
+    void batchCreateUsersFromCsv(MultipartFile file) throws Exception;
+}

+ 0 - 2
src/main/java/com/njuzr/eaibackend/service/AuthenticationService.java

@@ -10,6 +10,4 @@ public interface AuthenticationService {
 
     MyResponse loginByEmail(EmailLoginDTO emailLoginDTO);
 
-    void sendLoginVerifyCode(String officialEmail);
-
 }

+ 0 - 152
src/main/java/com/njuzr/eaibackend/service/EmailService.java

@@ -1,152 +0,0 @@
-package com.njuzr.eaibackend.service;
-
-import com.njuzr.eaibackend.exception.MyException;
-import com.njuzr.eaibackend.utils.WebClientUtil;
-import jakarta.mail.MessagingException;
-import jakarta.mail.internet.MimeMessage;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.http.HttpStatus;
-import org.springframework.mail.SimpleMailMessage;
-import org.springframework.mail.javamail.JavaMailSender;
-import org.springframework.mail.javamail.JavaMailSenderImpl;
-import org.springframework.mail.javamail.MimeMessageHelper;
-import org.springframework.stereotype.Service;
-
-/**
- * @author: Leonezhurui
- * @Date: 2024/2/21 - 20:35
- * @Package: EAI-Backend
- */
-
-@Slf4j
-@Service
-public class EmailService {
-    private final WebClientUtil webClientUtil;
-
-    @Autowired
-    public EmailService(WebClientUtil webClientUtil) {
-        this.webClientUtil = webClientUtil;
-    }
-
-    private void sendEmail(MyRequestObject requestObject) {
-        MyResponseObject response = webClientUtil.post("https://message.seec.seecoder.cn/message/mail", requestObject, MyResponseObject.class);
-        if (response.code == 1) {
-            log.info("WebClient请求成功,邮件发送成功!");
-        } else {
-            log.error("WebClient请求失败,邮件发送失败~");
-            throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()+":"+"邮件发送失败");
-        }
-    }
-
-    /**
-     * 创建账号后发送初始密码
-     * @param to
-     * @param username
-     * @param password
-     */
-    public void sendInitialPasswordEmail(String to, String username, String password) {
-        sendEmail(new MyRequestObject(
-                new String[]{to},
-                "Your New Account",
-                "<h1>欢迎来到EAI天地!</h1>" +
-                        "<p>亲爱的<b>" + username + "</b>同学,</p>" +
-                        "<p>你的EAI账号已经创建,初始密码是<b>" + password + "</b></p>" +
-                        "<p>请在登陆后尽快修改密码!</p>" +
-                        "<p><a href='https://example.com/login'>登陆的虫洞~</a></p>" +
-                        "<p>祝你的专业写作能力不断提升,<br>Seecoder EAI开发团队</p>"
-        ));
-    }
-
-    /**
-     * 创建账号后发送初始密码
-     * @param to
-     * @param username
-     * @param password
-     */
-    public void sendResetPasswordEmail(String to, String username, String password) {
-        sendEmail(new MyRequestObject(
-                new String[]{to},
-                "Your Reset Password",
-                "<h1>欢迎来到EAI天地!</h1>" +
-                        "<p>亲爱的<b>" + username + "</b>同学,</p>" +
-                        "<p>你的密码已被重置,重置后的密码是<b>" + password + "</b></p>" +
-                        "<p><a href='https://example.com/login'>登陆的虫洞~</a></p>" +
-                        "<p>祝你的专业写作能力不断提升,<br>Seecoder EAI开发团队</p>"
-        ));
-    }
-
-
-    public void sendVerifyCodeEmail(String to, String code, int timeout) {
-        sendEmail(new MyRequestObject(
-                new String[]{to},
-                "Your Verify Code",
-                "<h1>欢迎来到EAI天地!</h1>" +
-                        "<p>亲爱的同学,</p>" +
-                        "<p>你的邮箱验证是<b>" + code + "</b></p>" +
-                        "<p>请尽快完成注册流程,邮箱验证码有效时间为<b>"+timeout+"分钟</b></p>" +
-                        "<p>祝你的专业写作能力不断提升,<br>Seecoder EAI开发团队</p>"
-        ));
-    }
-
-
-    @Data
-    @AllArgsConstructor
-    static class MyRequestObject {
-        private String[] receivers;
-        private String subject;
-        private String text;
-    }
-
-    @Data
-    static class MyResponseObject {
-        private int code;
-        private String msg;
-        private Object data;
-    }
-
-
-//    private final JavaMailSender javaMailSender = new JavaMailSenderImpl();
-//
-//    @Value("${spring.mail.username}")
-//    private String sourceEmail;
-
-//    /**
-//     * FIXME 邮箱服务暂时不可用,需要Seecoder的公邮和stmp秘钥
-//     * @param to
-//     * @param username
-//     * @param password
-//     * @return
-//     * @throws MessagingException
-//     */
-//    public int sendPasswordEmail(String to, String username, String password) throws MessagingException {
-//        try {
-//            MimeMessage message = javaMailSender.createMimeMessage();
-//            MimeMessageHelper helper = new MimeMessageHelper(message, true);
-//
-//            helper.setFrom(sourceEmail);
-//            helper.setTo(to);
-//            helper.setSubject("Your New Account");
-//
-//            String htmlContent = "<h1>欢迎来到EAI天地!</h1>" +
-//                    "<p>亲爱的<b>" + username + "</b>同学,</p>" +
-//                    "<p>你的EAI账号已经创建,初始密码是<b>" + password + "</b></p>" +
-//                    "<p>请在登陆后尽快修改密码!</p>" +
-//                    "<p><a href='https://example.com/login'>登陆的虫洞~</a></p>" +
-//                    "<p>祝你的专业写作能力不断提升,<br>Seecoder EAI开发团队</p>";
-//
-//            helper.setText(htmlContent, true);
-//
-//            javaMailSender.send(message);
-//            log.info("邮件发送成功!==> 收件人:{}", username);
-//            return 1;
-//        } catch (Exception e) {
-//            log.error("邮件发送失败!==> {}",e.getMessage());
-//            return 0;
-//        }
-//    }
-
-}

+ 8 - 48
src/main/java/com/njuzr/eaibackend/service/UserService.java

@@ -2,14 +2,12 @@ package com.njuzr.eaibackend.service;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.njuzr.eaibackend.dto.user.AdminRegisterDTO;
-import com.njuzr.eaibackend.dto.user.UserDTO;
+import com.njuzr.eaibackend.dto.user.PasswordChangeDTO;
 import com.njuzr.eaibackend.dto.user.UserRegisterDTO;
 import com.njuzr.eaibackend.dto.user.UserUpdateDTO;
 import com.njuzr.eaibackend.enums.Role;
 import com.njuzr.eaibackend.po.User;
 import com.njuzr.eaibackend.vo.UserVO;
-import org.springframework.web.multipart.MultipartFile;
 
 
 /**
@@ -19,6 +17,12 @@ import org.springframework.web.multipart.MultipartFile;
  */
 
 public interface UserService {
+    /**
+     * 通过用户Id找到用户
+     * @param id
+     * @return UserVO
+     */
+    IPage<UserVO> searchUsers(Page<User> page, Long id, String name, String officialNumber, Role role);
 
     /**
      * 创建用户
@@ -27,16 +31,6 @@ public interface UserService {
      */
     UserVO createUser(UserRegisterDTO userDTO);
 
-    void sendVerifyCode(String officialEmail);
-
-    void adminCreateUser(AdminRegisterDTO adminRegisterDTO);
-
-    /**
-     * 通过用户Id找到用户
-     * @param id
-     * @return UserVO
-     */
-    IPage<UserVO> searchUsers(Page<User> page, Long id, String name, String officialNumber, Role role);
 
     /**
      * 用户更新用户信息
@@ -46,44 +40,10 @@ public interface UserService {
      */
     UserVO updateUser(Long id, UserUpdateDTO userDTO);
 
-    /**
-     * 管理员更新用户信息
-     * @param id
-     * @param userDTO
-     * @return
-     */
-    UserVO updateUser(Long id, UserDTO userDTO);
 
     /**
      * 用户更新密码
-     * @param id
-     * @param newPassword
-     * @param oldPassword
-     */
-    void updatePassword(Long id, String newPassword, String oldPassword);
-
-    /**
-     * 管理员重置密码
-     * @param id
-     */
-    void resetPassword(Long id);
-
-
-    void deleteById(Long id);
-
-
-    /**
-     * 从Excel文件中读取数据,并批量创建用户
-     * @param file
-     * @throws Exception
-     */
-    void batchCreateUsersFromExcel(MultipartFile file) throws Exception;
-
-    /**
-     * 从Csv文件中读取数据,并批量创建用户
-     * @param file
-     * @throws Exception
      */
-    void batchCreateUsersFromCsv(MultipartFile file) throws Exception;
+    void updatePassword(PasswordChangeDTO passwordChangeDTO);
 
 }

+ 338 - 0
src/main/java/com/njuzr/eaibackend/service/impl/AdminServiceImpl.java

@@ -0,0 +1,338 @@
+package com.njuzr.eaibackend.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.njuzr.eaibackend.dto.user.AdminRegisterDTO;
+import com.njuzr.eaibackend.dto.user.UserDTO;
+import com.njuzr.eaibackend.enums.Role;
+import com.njuzr.eaibackend.exception.MyException;
+import com.njuzr.eaibackend.mapper.UserMapper;
+import com.njuzr.eaibackend.po.User;
+import com.njuzr.eaibackend.service.AdminService;
+import com.njuzr.eaibackend.service.EmailService;
+import com.njuzr.eaibackend.utils.ModelMapperUtil;
+import com.njuzr.eaibackend.vo.UserVO;
+import com.opencsv.CSVReader;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+@Slf4j
+@Service
+public class AdminServiceImpl implements AdminService {
+    @Autowired
+    private UserMapper userMapper;
+    @Autowired
+    private EmailService emailService;
+    private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+
+    /**
+     * 管理员创建用户,给出name、officialEmail、officialNumber、role
+     * 随机生成密码,将密码通过邮件服务发送到用户邮箱
+     * @param adminRegisterDTO
+     * @return 1表示成功,0表示失败
+     */
+    @Override
+    public void createUser(AdminRegisterDTO adminRegisterDTO) {
+        Role role = adminRegisterDTO.getRole();
+        if (role != Role.STUDENT && role != Role.TEACHER) { // 只能注册学生或老师账号
+            throw new MyException(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase() + ":" + "被拒绝");
+        }
+
+        User targetUser = ModelMapperUtil.map(adminRegisterDTO, User.class);
+
+        // 随机生成密码
+        String randomPassword = UUID.randomUUID().toString();
+        targetUser.setPassword(passwordEncoder.encode(randomPassword));
+
+        targetUser.setCreateTime(new Date());
+
+        // 同一个邮箱、学号只能注册一次
+        if (isOfficialNumberNotExists(targetUser.getOfficialNumber())
+                && isEmailNotExists(targetUser.getOfficialEmail())) {
+            int code = userMapper.insert(targetUser);
+            if (code == 0) {
+                log.error("createUser -- 数据库插入错误:");
+                throw new MyException(501, "数据库插入错误");
+            }
+            log.info("User创建完毕,User对象如下:" + targetUser);
+        }
+
+        // 发送初始密码
+        emailService.sendInitialPasswordEmail(targetUser.getOfficialEmail(), targetUser.getName(), randomPassword);
+    }
+
+    /**
+     * 管理员更新用户信息
+     * @param id
+     */
+    @Override
+    public UserVO updateUser(Long id, UserDTO userDTO) {
+        User opeUser = ModelMapperUtil.map(userDTO, User.class);
+        opeUser.setId(id);
+        try {
+            int status = userMapper.updateUser(opeUser);
+            if (status > 0) {
+                return ModelMapperUtil.map(userMapper.selectById(opeUser.getId()), UserVO.class);
+            }
+        } catch (Exception e) {
+            log.error("数据库更新错误,错误如下:" + e.getMessage());
+            throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase() + ":" + "数据库更新失败");
+        }
+        return null;
+    }
+
+    /**
+     * 管理员删除用户
+     * @param id
+     */
+    @Override
+    public void deleteById(Long id) {
+        if (isUserExists(id)) {
+            int res = userMapper.deleteById(id);
+            if (res == 0) {
+                throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "删除失败");
+            }
+        }
+    }
+
+    /**
+     * 管理员重置密码,并将重置密码发送到指定邮箱
+     *
+     * @param id
+     */
+    @Override
+    public void resetPassword(Long id) {
+        User targetUser = userMapper.selectById(id);
+        if (targetUser == null) {
+            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "用户不存在");
+        }
+        String targetName = targetUser.getName();
+        String targetEmail = targetUser.getOfficialEmail();
+        String randomPassword = UUID.randomUUID().toString();
+        int code = userMapper.updatePassword(id, passwordEncoder.encode(randomPassword));
+        if (code == 1) {
+            log.info("用户更新成功");
+        }
+        emailService.sendResetPasswordEmail(targetEmail, targetName, randomPassword);
+    }
+
+
+    /**
+     * 解析文件,批量创建用户。其中,文件必须包含姓名、邮箱、学号。
+     *
+     * @param file
+     * @return
+     */
+    @Override
+    public void batchCreateUsersFromExcel(MultipartFile file) throws Exception {
+        Workbook workbook = new XSSFWorkbook(file.getInputStream());
+        Sheet sheet = workbook.getSheetAt(0); // 默认只允许一张表
+        Row headerRow = sheet.getRow(0);
+        int nameCol = findColumnIndex(headerRow, "姓名");
+        int numberCol = findColumnIndex(headerRow, "学号");
+        int emailCol = findColumnIndex(headerRow, "邮箱");
+
+        if (nameCol < 0 || numberCol < 0 || emailCol < 0)
+            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "缺失必要字段信息");
+
+        List<User> users = new ArrayList<>();
+
+        for (Row row : sheet) {
+            if (row.getRowNum() == 0) continue; // 跳过表头
+
+            User user = new User();
+            user.setName(stringifyCellValue(row.getCell(nameCol)));
+            user.setOfficialNumber(stringifyCellValue(row.getCell(numberCol)));
+            user.setOfficialEmail(stringifyCellValue(row.getCell(emailCol)));
+            user.setRole(Role.STUDENT); // 批量创建只创建学生账号
+            user.setCreateTime(new Date());
+
+            users.add(user);
+        }
+
+        processUsers(users);
+    }
+
+
+    /**
+     * 批量创建用户,从csv文件中读取数据
+     *
+     * @param file csv文件
+     */
+    @Override
+    public void batchCreateUsersFromCsv(MultipartFile file) throws Exception {
+        CSVReader csvReader = new CSVReader(new InputStreamReader(file.getInputStream()));
+        String[] header = csvReader.readNext();
+        int nameCol = findColumnIndex(header, "姓名");
+        int numberCol = findColumnIndex(header, "学号");
+        int emailCol = findColumnIndex(header, "邮箱");
+
+        if (nameCol < 0 || numberCol < 0 || emailCol < 0)
+            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "缺失必要字段信息");
+
+        List<User> users = new ArrayList<>();
+        String[] nextRecord;
+        while ((nextRecord = csvReader.readNext()) != null) {
+            String officialNumber = String.valueOf(nextRecord[numberCol]);
+            String email = String.valueOf(nextRecord[emailCol]);
+            if (isOfficialNumberNotExists(officialNumber)
+                    && isEmailNotExists(email)) {
+                User user = new User();
+                user.setName(String.valueOf(nextRecord[nameCol]));
+                user.setOfficialNumber(officialNumber);
+                user.setOfficialEmail(String.valueOf(email));
+                user.setRole(Role.STUDENT); // 批量创建只创建学生账号
+                user.setCreateTime(new Date());
+
+                users.add(user);
+            }
+
+        }
+
+        // 统一生成随机密码,批量创建用户,创建成功后,将初始密码发送至邮箱
+        processUsers(users);
+
+    }
+
+
+    /**
+     * 查看学号是否不存在
+     *
+     * @param officialNumber 学号
+     * @return true表示不存在,false表示存在
+     */
+    private boolean isOfficialNumberNotExists(String officialNumber) throws MyException {
+        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("official_number", officialNumber);
+        User existingUser = userMapper.selectOne(queryWrapper);
+        if (existingUser != null) {
+            log.error("用户已存在,学号{}重复", officialNumber);
+            throw new MyException(400, "用户已存在,请检查学号信息");
+        }
+        return true;
+    }
+
+
+    /**
+     * 查看邮箱是否不存在
+     *
+     * @param email 邮箱
+     * @return true表示不存在,false表示存在
+     */
+    private boolean isEmailNotExists(String email) throws MyException {
+        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("official_email", email);
+        User existingUser = userMapper.selectOne(queryWrapper);
+        if (existingUser != null) {
+            log.error("用户已存在,邮箱{}重复", email);
+            throw new MyException(400, "用户已存在,请检查邮箱信息");
+        }
+        return true;
+    }
+
+
+    /**
+     * 检查用户ID是否存在,如果存在则返回true;不存在则报错
+     *
+     * @param id
+     * @return
+     * @throws MyException
+     */
+    private boolean isUserExists(Long id) throws MyException {
+        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("id", id);
+        User existingUser = userMapper.selectOne(queryWrapper);
+        if (existingUser == null) {
+            log.error("用户不存在,ID:{}", id);
+            throw new MyException(400, "用户不存在,请检查ID信息");
+        }
+        return true;
+    }
+
+
+    /**
+     * 在指定Excel Cell中找到具体列名的索引值
+     * @param headerRow
+     * @param columnName
+     * @return
+     */
+    private int findColumnIndex(Row headerRow, String columnName) {
+        for (Cell cell : headerRow) {
+            if (cell.getStringCellValue().trim().equalsIgnoreCase(columnName)) {
+                return cell.getColumnIndex();
+            }
+        }
+        return -1;
+    }
+
+    private String stringifyCellValue(Cell cell) {
+        String cellValue;
+        if (cell.getCellType() == CellType.STRING) {
+            cellValue = cell.getStringCellValue();
+        } else if (cell.getCellType() == CellType.NUMERIC) {
+            // 对于数值类型,可以将其转换为字符串
+            // 注意:这里直接使用了Double.toString,你可能需要根据实际情况调整格式化方式
+            cellValue = Double.toString(cell.getNumericCellValue());
+        } else if (cell.getCellType() == CellType.BOOLEAN) {
+            // 对于布尔类型,也可以转换为字符串
+            cellValue = Boolean.toString(cell.getBooleanCellValue());
+        } else {
+            // 其他类型,根据需要处理或转换为字符串
+            // 例如,对于公式类型,可以计算公式的值
+            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "上传失败:请检查excel单元格的类型");
+        }
+        return cellValue;
+    }
+
+    /**
+     * 在csv文件的header中找到具体列的索引值
+     *
+     * @param header
+     * @param columnName
+     * @return
+     */
+    private int findColumnIndex(String[] header, String columnName) {
+        for (int i = 0; i < header.length; i++) {
+            if (header[i].trim().equalsIgnoreCase(columnName)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+
+    /**
+     * 统一生成随机密码,批量创建用户,创建成功后,将初始密码发送至邮箱
+     *
+     * @param users
+     */
+    private void processUsers(List<User> users) {
+        List<String> passwordStage = new ArrayList<>();
+        for (User user : users) {
+            // 随机生成密码,并使用BCryptPasswordEncoder加密密码
+            String randomPassword = UUID.randomUUID().toString();
+            passwordStage.add(randomPassword);
+            String encodedPassword = passwordEncoder.encode(randomPassword);
+            user.setPassword(encodedPassword);
+        }
+        int res = userMapper.batchInsert(users);
+        if (res == 0)
+            throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase() + ":" + "批量创建失败");
+
+        for (int i = 0; i < users.size(); i++) {
+            emailService.sendInitialPasswordEmail(users.get(i).getOfficialEmail(), users.get(i).getName(), passwordStage.get(i));
+        }
+    }
+}

+ 0 - 34
src/main/java/com/njuzr/eaibackend/service/impl/AuthenticationServiceImpl.java

@@ -8,14 +8,12 @@ import com.njuzr.eaibackend.mapper.UserMapper;
 import com.njuzr.eaibackend.po.MyUserDetails;
 import com.njuzr.eaibackend.po.User;
 import com.njuzr.eaibackend.service.AuthenticationService;
-import com.njuzr.eaibackend.service.EmailService;
 import com.njuzr.eaibackend.utils.JWTTokenUtil;
 import com.njuzr.eaibackend.utils.ModelMapperUtil;
 import com.njuzr.eaibackend.vo.auth.UserLoginVO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.core.ValueOperations;
 import org.springframework.http.HttpStatus;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;
@@ -25,8 +23,6 @@ import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.stereotype.Service;
 
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
 
 @Slf4j
 @Service
@@ -37,13 +33,10 @@ public class AuthenticationServiceImpl implements AuthenticationService {
     @Autowired
     private JWTTokenUtil jwtTokenUtil;
     @Autowired
-    private EmailService emailService;
-    @Autowired
     private UserMapper userMapper;
     @Autowired
     private RedisTemplate<String, Object> redisTemplate;
 
-    private static final long SEND_INTERVAL = 60; // 允许再次发送的时间间隔,单位秒
 
     @Override
     public MyResponse login(UserLoginDTO userLoginDTO) {
@@ -101,31 +94,4 @@ public class AuthenticationServiceImpl implements AuthenticationService {
         return MyResponse.success(userLoginVO);
     }
 
-    @Override
-    public void sendLoginVerifyCode(String officialEmail) {
-        // 检验邮箱合法性,只允许南京大学邮箱
-        if (!officialEmail.endsWith("@smail.nju.edu.cn") && !officialEmail.endsWith("@nju.edu.cn")) {
-            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "只允许南京大学邮箱,请检查邮箱格式");
-        }
-
-        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
-        String intervalKey = "requestInterval:" + officialEmail;
-
-        // 检查是否已经发送过验证码并且时间间隔未过
-        if (ops.get(intervalKey) != null) {
-            throw new MyException(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase() + ":" + "需等待" + SEND_INTERVAL + "秒才能再次发送验证码");
-        }
-
-        // 生成验证码
-        String code = UUID.randomUUID().toString().substring(0, 6);
-        // 存储验证码到Redis,设置5分钟过期
-        int timeout = 5;
-        final String key = "loginVerifyCode:" + officialEmail;
-        // 存储验证码到Redis并设置过期时间
-        redisTemplate.opsForValue().set(key, code, timeout, TimeUnit.MINUTES);
-        // 设置请求间隔标记,防止频繁请求
-        redisTemplate.opsForValue().set(intervalKey, "requested", SEND_INTERVAL, TimeUnit.SECONDS);
-        emailService.sendVerifyCodeEmail(officialEmail, code, timeout);
-    }
-
 }

+ 139 - 0
src/main/java/com/njuzr/eaibackend/service/impl/EmailServiceImpl.java

@@ -0,0 +1,139 @@
+package com.njuzr.eaibackend.service.impl;
+
+import com.njuzr.eaibackend.exception.MyException;
+import com.njuzr.eaibackend.service.EmailService;
+import com.njuzr.eaibackend.utils.EmailUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Service
+public class EmailServiceImpl implements EmailService {
+    @Autowired
+    EmailUtil emailUtil;
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+    private static final long SEND_INTERVAL = 60; // 允许再次发送的时间间隔,单位秒
+
+    @Override
+    public void sendRegisterVerifyCode(String officialEmail) {
+        int timeout = 5;
+        String code = sendVerifyCodeHelper(officialEmail, "registerVerifyCode:", timeout);
+        // 发送邮件
+        String[] receivers = new String[]{officialEmail};
+        String subject = "注册验证码";
+        String text =   "<h1>欢迎来到EAI天地!</h1>" +
+                        "<p>亲爱的同学,</p>" +
+                        "<p>你的邮箱验证是<b>" + code + "</b></p>" +
+                        "<p>请尽快完成注册流程,邮箱验证码有效时间为<b>" + timeout + "分钟</b></p>" +
+                        "<p>祝你的专业写作能力不断提升,<br>SeeCoder EAI开发团队</p>";
+        emailUtil.sendEmail(receivers, subject, text);
+    }
+
+    @Override
+    public void sendLoginVerifyCode(String officialEmail) {
+        int timeout = 5;
+        String code = sendVerifyCodeHelper(officialEmail, "loginVerifyCode:", timeout);
+        // 发送邮件
+        String[] receivers = new String[]{officialEmail};
+        String subject = "登录验证码";
+        String text =
+                "<p>亲爱的同学,</p>" +
+                "<p>你的邮箱验证是<b>" + code + "</b></p>" +
+                "<p>邮箱验证码有效时间为<b>" + timeout + "分钟</b></p>";
+        emailUtil.sendEmail(receivers, subject, text);
+    }
+
+    @Override
+    public void sendPasswordChangeVerifyCode(String officialEmail) {
+        int timeout = 5;
+        String code = sendVerifyCodeHelper(officialEmail, "passwordChangeVerifyCode:", timeout);
+        // 发送邮件
+        String[] receivers = new String[]{officialEmail};
+        String subject = "修改密码验证码";
+        String text =
+                "<p>亲爱的同学,</p>" +
+                "<p>你的邮箱验证是<b>" + code + "</b></p>" +
+                "<p>邮箱验证码有效时间为<b>" + timeout + "分钟</b></p>";
+        emailUtil.sendEmail(receivers, subject, text);
+    }
+
+    /**
+     * 发生验证码的公共部分
+     * @param officialEmail
+     * @param prefix
+     */
+    private String sendVerifyCodeHelper(String officialEmail, String prefix, int timeout) {
+        // 检验邮箱合法性,只允许南京大学邮箱
+        if (!officialEmail.endsWith("@smail.nju.edu.cn") && !officialEmail.endsWith("@nju.edu.cn")) {
+            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "只允许南京大学邮箱,请检查邮箱格式");
+        }
+
+        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
+        String intervalKey = "requestInterval:" + officialEmail;
+
+        // 检查是否已经发送过验证码并且时间间隔未过
+        if (ops.get(intervalKey) != null) {
+            throw new MyException(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase() + ":" + "需等待" + SEND_INTERVAL + "秒才能再次发送验证码");
+        }
+
+        // 生成验证码
+        String code = UUID.randomUUID().toString().substring(0, 6);
+        // 存储验证码到Redis,设置5分钟过期(timeout)
+        final String key = prefix + officialEmail;
+        // 存储验证码到Redis并设置过期时间
+        redisTemplate.opsForValue().set(key, code, timeout, TimeUnit.MINUTES);
+        // 设置请求间隔标记,防止频繁请求
+        redisTemplate.opsForValue().set(intervalKey, "requested", SEND_INTERVAL, TimeUnit.SECONDS);
+        // 返回验证码
+        return code;
+    }
+
+
+
+    /**
+     * 创建账号后发送初始密码
+     * @param to
+     * @param username
+     * @param password
+     */
+    public void sendInitialPasswordEmail(String to, String username, String password) {
+        emailUtil.sendEmail(
+                new String[]{to},
+                "Your New Account",
+                "<h1>欢迎来到EAI天地!</h1>" +
+                        "<p>亲爱的<b>" + username + "</b>同学,</p>" +
+                        "<p>你的EAI账号已经创建,初始密码是<b>" + password + "</b></p>" +
+                        "<p>请在登陆后尽快修改密码!</p>" +
+                        "<p><a href='https://example.com/login'>登陆的虫洞~</a></p>" +
+                        "<p>祝你的专业写作能力不断提升,<br>Seecoder EAI开发团队</p>"
+        );
+    }
+
+    /**
+     * 创建账号后发送初始密码
+     * @param to
+     * @param username
+     * @param password
+     */
+    public void sendResetPasswordEmail(String to, String username, String password) {
+        emailUtil.sendEmail(
+                new String[]{to},
+                "Your Reset Password",
+                "<h1>欢迎来到EAI天地!</h1>" +
+                        "<p>亲爱的<b>" + username + "</b>同学,</p>" +
+                        "<p>你的密码已被重置,重置后的密码是<b>" + password + "</b></p>" +
+                        "<p><a href='https://example.com/login'>登陆的虫洞~</a></p>" +
+                        "<p>祝你的专业写作能力不断提升,<br>Seecoder EAI开发团队</p>"
+        );
+    }
+
+
+}

+ 36 - 322
src/main/java/com/njuzr/eaibackend/service/impl/UserServiceImpl.java

@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.njuzr.eaibackend.dto.user.AdminRegisterDTO;
+import com.njuzr.eaibackend.dto.user.PasswordChangeDTO;
 import com.njuzr.eaibackend.dto.user.UserDTO;
 import com.njuzr.eaibackend.dto.user.UserRegisterDTO;
 import com.njuzr.eaibackend.dto.user.UserUpdateDTO;
@@ -12,63 +12,38 @@ import com.njuzr.eaibackend.enums.Role;
 import com.njuzr.eaibackend.exception.MyException;
 import com.njuzr.eaibackend.mapper.UserMapper;
 import com.njuzr.eaibackend.po.User;
-import com.njuzr.eaibackend.service.EmailService;
 import com.njuzr.eaibackend.service.UserService;
 import com.njuzr.eaibackend.utils.ModelMapperUtil;
 import com.njuzr.eaibackend.utils.PageMapperUtil;
 import com.njuzr.eaibackend.vo.UserVO;
-import com.opencsv.CSVReader;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.poi.ss.usermodel.*;
-import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.core.ValueOperations;
 import org.springframework.http.HttpStatus;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
-import org.springframework.web.multipart.MultipartFile;
 
-import java.io.InputStreamReader;
 import java.util.*;
-import java.util.concurrent.TimeUnit;
 
-/**
- * @author: Leonezhurui
- * @Date: 2024/2/15 - 09:00
- * @Package: EAI-Backend
- */
 
 @Slf4j
 @Service
 public class UserServiceImpl implements UserService {
-
-    private final UserMapper userMapper;
-
-    private final RedisTemplate<String, Object> redisTemplate;
-
-    private final EmailService emailService;
-
-    private static final long SEND_INTERVAL = 60; // 允许再次发送的时间间隔,单位秒
-
-    private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
-
     @Autowired
-    public UserServiceImpl(UserMapper userMapper, RedisTemplate<String, Object> redisTemplate, EmailService emailService) {
-        this.userMapper = userMapper;
-        this.redisTemplate = redisTemplate;
-        this.emailService = emailService;
-    }
+    private UserMapper userMapper;
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+    private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
 
 
     @Override
     public IPage<UserVO> searchUsers(Page<User> page, Long id, String name, String officialNumber, Role role) {
         QueryWrapper<User> queryWrapper = new QueryWrapper<>(); // 设置查询条件
-
-        if (role == Role.ADMIN) // 禁止访问查询ADMIN用户
+        // 禁止访问查询ADMIN用户
+        if (role == Role.ADMIN) {
             throw new MyException(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase() + ":" + "被拒绝");
-
+        }
         queryWrapper
                 .eq(Objects.nonNull(id), "id", id)
                 .like(StringUtils.isNotBlank(name), "name", name)
@@ -78,21 +53,19 @@ public class UserServiceImpl implements UserService {
         IPage<User> targets = userMapper.selectPage(page, queryWrapper); // selectPage是内置的方法
 
         // targets.total如果为0,则说明没有符合条件的用户,但是不报错,由前端自行处理异常。
-
         return PageMapperUtil.convert(targets, record -> ModelMapperUtil.map(record, UserVO.class));
     }
 
 
-    //TODO:   注册DEMO:学生老师都可自行注册账号,管理员那套不需要修改/弃用,学生注册这套已经有完整逻辑,直接使用
     @Override
     public UserVO createUser(UserRegisterDTO userDTO) throws MyException {
         // 从Redis中获取验证码
-        final String key = "verifyCode:" + userDTO.getOfficialEmail();
+        final String key = "registerVerifyCode:" + userDTO.getOfficialEmail();
         String storedCode = (String) redisTemplate.opsForValue().get(key);
 
-        if (storedCode == null)
+        if (storedCode == null) {
             throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "验证码已过期");
-
+        }
         // 验证码校验
         if (storedCode.equals(userDTO.getVerifyCode())) {
             User targetUser = ModelMapperUtil.map(userDTO, User.class);
@@ -121,90 +94,21 @@ public class UserServiceImpl implements UserService {
         }
     }
 
-    @Override
-    public void sendVerifyCode(String officialEmail) throws MyException {
-        // 检验邮箱合法性,只允许南京大学邮箱
-        if (!officialEmail.endsWith("@smail.nju.edu.cn") && !officialEmail.endsWith("@nju.edu.cn")) {
-            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "只允许南京大学邮箱,请检查邮箱格式");
-        }
-
-        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
-        String intervalKey = "requestInterval:" + officialEmail;
-
-        // 检查是否已经发送过验证码并且时间间隔未过
-        if (ops.get(intervalKey) != null) {
-            throw new MyException(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase() + ":" + "需等待" + SEND_INTERVAL + "秒才能再次发送验证码");
-        }
-
-        // 生成验证码
-        String code = UUID.randomUUID().toString().substring(0, 6);
-        // 存储验证码到Redis,设置5分钟过期
-        int timeout = 5;
-        final String key = "verifyCode:" + officialEmail;
-        // 存储验证码到Redis并设置过期时间
-        redisTemplate.opsForValue().set(key, code, timeout, TimeUnit.MINUTES);
-        // 设置请求间隔标记,防止频繁请求
-        redisTemplate.opsForValue().set(intervalKey, "requested", SEND_INTERVAL, TimeUnit.SECONDS);
-
-        emailService.sendVerifyCodeEmail(officialEmail, code, timeout);
-    }
-
-
-    /**
-     * 管理员创建用户,给出name、officialEmail、officialNumber、role,随机生成密码,将密码通过邮件服务发送到用户邮箱
-     *
-     * @param adminRegisterDTO
-     * @return 1表示成功,0表示失败
-     */
-    @Override
-    public void adminCreateUser(AdminRegisterDTO adminRegisterDTO) {
-        Role role = adminRegisterDTO.getRole();
-        if (role != Role.STUDENT && role != Role.TEACHER) { // 只能注册学生或老师账号
-            throw new MyException(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase() + ":" + "被拒绝");
-        }
-
-        User targetUser = ModelMapperUtil.map(adminRegisterDTO, User.class);
-
-        // 随机生成密码
-        String randomPassword = UUID.randomUUID().toString();
-        targetUser.setPassword(passwordEncoder.encode(randomPassword));
-
-        targetUser.setCreateTime(new Date());
-
-        // 同一个邮箱、学号只能注册一次
-        if (isOfficialNumberNotExists(targetUser.getOfficialNumber())
-                && isEmailNotExists(targetUser.getOfficialEmail())) {
-            int code = userMapper.insert(targetUser);
-            if (code == 0) {
-                log.error("createUser -- 数据库插入错误:");
-                throw new MyException(501, "数据库插入错误");
-            }
-            log.info("User创建完毕,User对象如下:" + targetUser);
-        }
-
-        // 发送初始密码
-        emailService.sendInitialPasswordEmail(targetUser.getOfficialEmail(), targetUser.getName(), randomPassword);
-
-    }
 
     /**
+     * 更新用户信息
      * @param userDTO
      * @return
      */
     @Override
     public UserVO updateUser(Long id, UserUpdateDTO userDTO) {
         UserDTO opeUser = ModelMapperUtil.map(userDTO, UserDTO.class);
-        return updateUser(id, opeUser);
-    }
-
-    @Override
-    public UserVO updateUser(Long id, UserDTO userDTO) {
-        User opeUser = ModelMapperUtil.map(userDTO, User.class);
-        opeUser.setId(id);
+        User user = ModelMapperUtil.map(userDTO, User.class);
+        user.setId(id);
         try {
-            int status = userMapper.updateUser(opeUser);
+            int status = userMapper.updateUser(user);
             if (status > 0) {
-                return ModelMapperUtil.map(userMapper.selectById(opeUser.getId()), UserVO.class);
+                return ModelMapperUtil.map(userMapper.selectById(user.getId()), UserVO.class);
             }
         } catch (Exception e) {
             log.error("数据库更新错误,错误如下:" + e.getMessage());
@@ -213,134 +117,38 @@ public class UserServiceImpl implements UserService {
         return null;
     }
 
-    @Override
-    public void updatePassword(Long id, String newPassword, String oldPassword) throws MyException {
-        User user = userMapper.selectById(id);
-        if (user == null) {
-            log.error("用户不存在");
-            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "找不到用户");
-        }
-
-        if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
-            log.error("用户就密码不正确");
-            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "旧密码不正确");
-        }
-
-        String encodedNewPassword = passwordEncoder.encode(newPassword);
-        int code = userMapper.updatePassword(id, encodedNewPassword);
-        if (code == 1) {
-            log.info("用户更新成功");
-        }
-    }
 
     /**
-     * 管理员重置密码,并将重置密码发送到指定邮箱
-     *
-     * @param id
+     * 更新密码
      */
     @Override
-    public void resetPassword(Long id) {
-        User targetUser = userMapper.selectById(id);
-        if (targetUser == null)
-            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "用户不存在");
-        String targetName = targetUser.getName();
-        String targetEmail = targetUser.getOfficialEmail();
-        String randomPassword = UUID.randomUUID().toString();
-        int code = userMapper.updatePassword(id, passwordEncoder.encode(randomPassword));
-        if (code == 1) {
-            log.info("用户更新成功");
-        }
-        emailService.sendResetPasswordEmail(targetEmail, targetName, randomPassword);
-    }
-
-
-    @Override
-    public void deleteById(Long id) {
-        if (isUserExists(id)) {
-            int res = userMapper.deleteById(id);
-            if (res == 0)
-                throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "删除失败");
+    public void updatePassword(PasswordChangeDTO passwordChangeDTO) throws MyException {
+        User user = userMapper.selectByOfficialEmail(passwordChangeDTO.getOfficialEmail());
+        if (user == null) {
+            log.error("用户不存在");
+            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "找不到用户");
         }
-    }
 
-    /**
-     * 解析文件,批量创建用户。其中,文件必须包含姓名、邮箱、学号。
-     *
-     * @param file
-     * @return
-     */
-    @Override
-    public void batchCreateUsersFromExcel(MultipartFile file) throws Exception {
-        Workbook workbook = new XSSFWorkbook(file.getInputStream());
-        Sheet sheet = workbook.getSheetAt(0); // 默认只允许一张表
-        Row headerRow = sheet.getRow(0);
-        int nameCol = findColumnIndex(headerRow, "姓名");
-        int numberCol = findColumnIndex(headerRow, "学号");
-        int emailCol = findColumnIndex(headerRow, "邮箱");
-
-        if (nameCol < 0 || numberCol < 0 || emailCol < 0)
-            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "缺失必要字段信息");
-
-        List<User> users = new ArrayList<>();
-
-        for (Row row : sheet) {
-            if (row.getRowNum() == 0) continue; // 跳过表头
-
-            User user = new User();
-            user.setName(stringifyCellValue(row.getCell(nameCol)));
-            user.setOfficialNumber(stringifyCellValue(row.getCell(numberCol)));
-            user.setOfficialEmail(stringifyCellValue(row.getCell(emailCol)));
-            user.setRole(Role.STUDENT); // 批量创建只创建学生账号
-            user.setCreateTime(new Date());
+        // 从Redis中获取验证码
+        final String key = "passwordChangeVerifyCode:" + passwordChangeDTO.getOfficialEmail();
+        String storedCode = (String) redisTemplate.opsForValue().get(key);
 
-            users.add(user);
+        if (storedCode == null) {
+            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "验证码已过期");
         }
-
-        processUsers(users);
-    }
-
-
-    /**
-     * 批量创建用户,从csv文件中读取数据
-     *
-     * @param file csv文件
-     */
-    @Override
-    public void batchCreateUsersFromCsv(MultipartFile file) throws Exception {
-        CSVReader csvReader = new CSVReader(new InputStreamReader(file.getInputStream()));
-        String[] header = csvReader.readNext();
-        int nameCol = findColumnIndex(header, "姓名");
-        int numberCol = findColumnIndex(header, "学号");
-        int emailCol = findColumnIndex(header, "邮箱");
-
-        if (nameCol < 0 || numberCol < 0 || emailCol < 0)
-            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "缺失必要字段信息");
-
-        List<User> users = new ArrayList<>();
-        String[] nextRecord;
-        while ((nextRecord = csvReader.readNext()) != null) {
-            String officialNumber = String.valueOf(nextRecord[numberCol]);
-            String email = String.valueOf(nextRecord[emailCol]);
-            if (isOfficialNumberNotExists(officialNumber)
-                    && isEmailNotExists(email)) {
-                User user = new User();
-                user.setName(String.valueOf(nextRecord[nameCol]));
-                user.setOfficialNumber(officialNumber);
-                user.setOfficialEmail(String.valueOf(email));
-                user.setRole(Role.STUDENT); // 批量创建只创建学生账号
-                user.setCreateTime(new Date());
-
-                users.add(user);
+        // 验证码校验
+        if (storedCode.equals(passwordChangeDTO.getVerifyCode())) {
+            String encodedNewPassword = passwordEncoder.encode(passwordChangeDTO.getPassword());
+            int code = userMapper.updatePassword(user.getId(), encodedNewPassword);
+            if (code == 1) {
+                log.info("用户更新成功");
             }
-
+            redisTemplate.delete(key); //成功后删除验证码
+        } else {
+            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "验证码错误");
         }
-
-        // 统一生成随机密码,批量创建用户,创建成功后,将初始密码发送至邮箱
-        processUsers(users);
-
     }
 
-
     /**
      * 查看学号是否不存在
      *
@@ -376,98 +184,4 @@ public class UserServiceImpl implements UserService {
         return true;
     }
 
-
-    /**
-     * 检查用户ID是否存在,如果存在则返回true;不存在则报错
-     *
-     * @param id
-     * @return
-     * @throws MyException
-     */
-    private boolean isUserExists(Long id) throws MyException {
-        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
-        queryWrapper.eq("id", id);
-        User existingUser = userMapper.selectOne(queryWrapper);
-        if (existingUser == null) {
-            log.error("用户不存在,ID:{}", id);
-            throw new MyException(400, "用户不存在,请检查ID信息");
-        }
-        return true;
-    }
-
-
-    /**
-     * 在指定Excel Cell中找到具体列名的索引值
-     * @param headerRow
-     * @param columnName
-     * @return
-     */
-    private int findColumnIndex(Row headerRow, String columnName) {
-        for (Cell cell : headerRow) {
-            if (cell.getStringCellValue().trim().equalsIgnoreCase(columnName)) {
-                return cell.getColumnIndex();
-            }
-        }
-        return -1;
-    }
-
-    private String stringifyCellValue(Cell cell) {
-        String cellValue;
-        if (cell.getCellType() == CellType.STRING) {
-            cellValue = cell.getStringCellValue();
-        } else if (cell.getCellType() == CellType.NUMERIC) {
-            // 对于数值类型,可以将其转换为字符串
-            // 注意:这里直接使用了Double.toString,你可能需要根据实际情况调整格式化方式
-            cellValue = Double.toString(cell.getNumericCellValue());
-        } else if (cell.getCellType() == CellType.BOOLEAN) {
-            // 对于布尔类型,也可以转换为字符串
-            cellValue = Boolean.toString(cell.getBooleanCellValue());
-        } else {
-            // 其他类型,根据需要处理或转换为字符串
-            // 例如,对于公式类型,可以计算公式的值
-            throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "上传失败:请检查excel单元格的类型");
-        }
-        return cellValue;
-    }
-
-    /**
-     * 在csv文件的header中找到具体列的索引值
-     *
-     * @param header
-     * @param columnName
-     * @return
-     */
-    private int findColumnIndex(String[] header, String columnName) {
-        for (int i = 0; i < header.length; i++) {
-            if (header[i].trim().equalsIgnoreCase(columnName)) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-
-    /**
-     * 统一生成随机密码,批量创建用户,创建成功后,将初始密码发送至邮箱
-     *
-     * @param users
-     */
-    private void processUsers(List<User> users) {
-        List<String> passwordStage = new ArrayList<>();
-        for (User user : users) {
-            // 随机生成密码,并使用BCryptPasswordEncoder加密密码
-            String randomPassword = UUID.randomUUID().toString();
-            passwordStage.add(randomPassword);
-            String encodedPassword = passwordEncoder.encode(randomPassword);
-            user.setPassword(encodedPassword);
-        }
-        int res = userMapper.batchInsert(users);
-        if (res == 0)
-            throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase() + ":" + "批量创建失败");
-
-        for (int i = 0; i < users.size(); i++) {
-            emailService.sendInitialPasswordEmail(users.get(i).getOfficialEmail(), users.get(i).getName(), passwordStage.get(i));
-        }
-    }
-
 }

+ 50 - 0
src/main/java/com/njuzr/eaibackend/utils/EmailUtil.java

@@ -0,0 +1,50 @@
+package com.njuzr.eaibackend.utils;
+
+import com.njuzr.eaibackend.exception.MyException;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class EmailUtil {
+
+    @Autowired
+    private WebClientUtil webClientUtil;
+
+    /**
+     * 发生邮件
+     * @param receivers 待发生的邮箱列表
+     * @param subject 邮件标题
+     * @param text 邮件内容
+     */
+    public void sendEmail(String[] receivers, String subject, String text) {
+        MyRequestObject requestObject = new MyRequestObject(receivers, subject, text);
+        MyResponseObject response = webClientUtil.post("https://message.seec.seecoder.cn/message/mail",
+                requestObject, MyResponseObject.class);
+        if (response.code == 1) {
+            log.info("WebClient请求成功,邮件发送成功!");
+        } else {
+            log.error("WebClient请求失败,邮件发送失败~");
+            throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()+":"+"邮件发送失败");
+        }
+    }
+
+    @Data
+    @AllArgsConstructor
+    static class MyRequestObject {
+        private String[] receivers;
+        private String subject;
+        private String text;
+    }
+
+    @Data
+    static class MyResponseObject {
+        private int code;
+        private String msg;
+        private Object data;
+    }
+}