实现登录注册接口

Change-Id: I3d57cca89cac8945d562f6a39127b3454c1cd9ac
diff --git a/src/main/java/edu/bjtu/groupone/backend/BackendApplication.java b/src/main/java/edu/bjtu/groupone/backend/BackendApplication.java
index 32033e0..1cc2086 100644
--- a/src/main/java/edu/bjtu/groupone/backend/BackendApplication.java
+++ b/src/main/java/edu/bjtu/groupone/backend/BackendApplication.java
@@ -1,13 +1,19 @@
 package edu.bjtu.groupone.backend;
 
+import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.ServletComponentScan;
+import org.springframework.scheduling.annotation.EnableScheduling;
 
+@EnableScheduling
+@ServletComponentScan
 @SpringBootApplication
+@MapperScan("edu.bjtu.groupone.backend.mapper")
 public class BackendApplication {
 
-	public static void main(String[] args) {
-		SpringApplication.run(BackendApplication.class, args);
-	}
+    public static void main(String[] args) {
+        SpringApplication.run(BackendApplication.class, args);
+    }
 
 }
diff --git a/src/main/java/edu/bjtu/groupone/backend/config/Config.java b/src/main/java/edu/bjtu/groupone/backend/config/Config.java
new file mode 100644
index 0000000..cfc3745
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/config/Config.java
@@ -0,0 +1,34 @@
+// src/main/java/edu/bjtu/groupone/backend/config/Config.java
+package edu.bjtu.groupone.backend.config;
+
+import edu.bjtu.groupone.backend.interceptor.Interceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.*;
+
+@Configuration
+public class Config implements WebMvcConfigurer {
+
+    @Autowired
+    private Interceptor authInterceptor;
+
+    /** 全局 CORS 配置 */
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                // Vite 默认端口 5173,如果你的 React 在 3000 也一并写上
+                .allowedOrigins("http://localhost:5173", "http://localhost:3000")
+                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
+                .allowedHeaders("*")
+                // 如果需要前端带 Cookie(如 JWT 存在 Cookie)就打开
+                .allowCredentials(true)
+                .maxAge(3600);
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(authInterceptor)
+                .addPathPatterns("/**");
+    }
+    // 其它 CORS、静态资源、Swagger 配置保持不动
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/config/SwaggerConfig.java b/src/main/java/edu/bjtu/groupone/backend/config/SwaggerConfig.java
new file mode 100644
index 0000000..042c278
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/config/SwaggerConfig.java
@@ -0,0 +1,39 @@
+package edu.bjtu.groupone.backend.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+
+@Configuration
+//@EnableOpenApi
+public class SwaggerConfig {
+    @Bean
+    public Docket docket(){
+        return new Docket(DocumentationType.OAS_30)
+                .apiInfo(apiInfo())
+                .enable(true)
+                .groupName("CJB")
+                .select()
+                .apis(RequestHandlerSelectors.basePackage("com.example.eliteedu_prism.controller"))
+                .paths(PathSelectors.ant("/controller/**"))
+                .build();
+    }
+
+
+    @SuppressWarnings("all")
+    public ApiInfo apiInfo(){
+        return new ApiInfo(
+                "zrj's api",
+                "redis project",
+                "v1.0",
+                "2261839618@qq.com", //开发者团队的邮箱
+                "ZRJ",
+                "Apache 2.0",  //许可证
+                "http://www.apache.org/licenses/LICENSE-2.0" //许可证链接
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/bjtu/groupone/backend/controller/UserController.java b/src/main/java/edu/bjtu/groupone/backend/controller/UserController.java
new file mode 100644
index 0000000..1388940
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/controller/UserController.java
@@ -0,0 +1,113 @@
+package edu.bjtu.groupone.backend.controller;
+
+import edu.bjtu.groupone.backend.model.InviteCode;
+import edu.bjtu.groupone.backend.model.Result;
+import edu.bjtu.groupone.backend.model.User;
+import edu.bjtu.groupone.backend.service.InviteCodeService;
+import edu.bjtu.groupone.backend.service.UserService;
+import edu.bjtu.groupone.backend.utils.JwtUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.Map;
+
+@CrossOrigin
+@Slf4j
+@RestController
+public class UserController {
+
+    @Autowired
+    private UserService userService;
+    @Autowired
+    private InviteCodeService inviteCodeService;
+
+    /** 登录:接收表单字段 identificationNumber、password */
+    @PostMapping("/login")
+    public Result login(
+            @RequestParam Integer identificationNumber,
+            @RequestParam String password
+    ) {
+        User u = new User();
+        u.setIdentificationNumber(identificationNumber);
+        u.setPassword(password);
+        User user = userService.login(u);
+        if (user != null) {
+            Map<String, Object> payload = new HashMap<>();
+            payload.put("id", user.getUserId());
+            payload.put("username", user.getUsername());
+            payload.put("identificationNumber", user.getIdentificationNumber());
+            String token = JwtUtils.generateJwt(payload);
+
+            // 打印到控制台
+            log.info("【登录成功】生成的 JWT token = {}", token);
+            return Result.success(token);
+        }
+        return Result.error("用户名或密码错误");
+    }
+
+    /** 注册:接收表单字段 username、email、password、verificationCode、identificationNumber、inviteCode */
+    @PostMapping("/register")
+    public Result register(
+            @RequestParam String username,
+            @RequestParam String email,
+            @RequestParam String password,
+            @RequestParam String verificationCode,
+            @RequestParam Integer identificationNumber,
+            @RequestParam String inviteCode
+    ) {
+        // 1. 验证邀请码
+        if (inviteCodeService.validateInviteCode(inviteCode) == null) {
+            return Result.error("无效邀请码或已过期");
+        }
+        // 2. 验证邮箱验证码
+        if (!userService.verifyCode(email, verificationCode)) {
+            return Result.error("验证码错误或已过期");
+        }
+        // 3. 检查邮箱唯一
+        if (userService.isEmailExists(email)) {
+            return Result.error("邮箱已被注册");
+        }
+        // 4. 构造用户实体并设置所有非空字段
+        User newUser = new User();
+        newUser.setUsername(username);
+        newUser.setEmail(email);
+        newUser.setPassword(password);
+        newUser.setIdentificationNumber(identificationNumber);
+        // registrationDate 格式要和你的 Mapper SQL 对应,如果是 String 存储:
+        newUser.setRegistrationDate(
+                LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
+        );
+        // 5. 插入到数据库
+        userService.register(newUser);
+        // 6. 扣减邀请码次数
+        if (!inviteCodeService.useInviteCode(inviteCode)) {
+            log.error("邀请码扣减失败, code={}", inviteCode);
+        }
+        return Result.success("注册成功");
+    }
+
+    /** 发送邮箱验证码:接收表单字段 email、inviteCode */
+    @PostMapping("/sendVerification")
+    public Result sendVerification(
+            @RequestParam String email,
+            @RequestParam String inviteCode
+    ) {
+        if (inviteCodeService.validateInviteCode(inviteCode) == null) {
+            return Result.error("无效邀请码");
+        }
+        userService.sendVerificationCode(email);
+        return Result.success("验证码已发送");
+    }
+
+    @GetMapping("/api/me")
+    public Result me(@RequestHeader("token") String token) {
+        // 从 token 解析出用户信息,JwtUtils.parseJwt 返回一个 Map
+        var claims = JwtUtils.parseJwt(token);
+        // 你可以根据需要返回完整 User,也可以直接返回用户名
+        return Result.success(claims);
+    }
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/interceptor/Interceptor.java b/src/main/java/edu/bjtu/groupone/backend/interceptor/Interceptor.java
new file mode 100644
index 0000000..f3b9d2d
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/interceptor/Interceptor.java
@@ -0,0 +1,58 @@
+// src/main/java/edu/bjtu/groupone/backend/interceptor/Interceptor.java
+package edu.bjtu.groupone.backend.interceptor;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import edu.bjtu.groupone.backend.model.Result;
+import edu.bjtu.groupone.backend.utils.JwtUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+@Slf4j
+@Component
+public class Interceptor implements HandlerInterceptor {
+    @Override
+    public boolean preHandle(HttpServletRequest req,
+                             HttpServletResponse resp,
+                             Object handler) throws Exception {
+        String url = req.getRequestURI();
+        log.info("Request URI: {}", url);
+        // 放行无需鉴权的路径:登录、注册、发码、静态资源、HTML 页面
+        if (url.equals("/") ||
+                url.endsWith(".html") ||
+                url.contains("login") ||
+                url.contains("register") ||
+                url.equals("/api/me") ||      // 放行用户信息接口
+                url.equals("/error") ||       // 放行错误页面
+                url.contains("sendVerification") ||
+                url.startsWith("/swagger-ui") ||
+                url.startsWith("/v3/api-docs") ||
+                url.startsWith("/favicon.ico") ||
+                url.startsWith("/static/")) {
+            return true;
+        }
+        // 从请求头读取 token
+        String token = req.getHeader("token");
+        if (!StringUtils.hasLength(token)) {
+            resp.setContentType("application/json;charset=UTF-8");
+            resp.getWriter()
+                    .write(new ObjectMapper()
+                            .writeValueAsString(Result.error("NOT_LOGIN")));
+            return false;
+        }
+        try {
+            JwtUtils.parseJwt(token);
+            return true;
+        } catch (Exception e) {
+            resp.setContentType("application/json;charset=UTF-8");
+            resp.getWriter()
+                    .write(new ObjectMapper()
+                            .writeValueAsString(Result.error("NOT_LOGIN")));
+            return false;
+        }
+    }
+
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/mapper/InviteCodeMapper.java b/src/main/java/edu/bjtu/groupone/backend/mapper/InviteCodeMapper.java
new file mode 100644
index 0000000..78bb7e0
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/mapper/InviteCodeMapper.java
@@ -0,0 +1,14 @@
+package edu.bjtu.groupone.backend.mapper;
+
+import edu.bjtu.groupone.backend.model.InviteCode;
+import org.apache.ibatis.annotations.*;
+
+@Mapper
+public interface InviteCodeMapper {
+    @Select("SELECT * FROM invite_code WHERE code = #{code}")
+    InviteCode selectByCode(String code);
+
+    @Update("UPDATE invite_code SET remaining_uses = remaining_uses - 1 " +
+            "WHERE code = #{code} AND remaining_uses > 0")
+    int decrementRemainingUses(String code);
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/mapper/UserMapper.java b/src/main/java/edu/bjtu/groupone/backend/mapper/UserMapper.java
new file mode 100644
index 0000000..3cbf2c1
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/mapper/UserMapper.java
@@ -0,0 +1,22 @@
+package edu.bjtu.groupone.backend.mapper;
+
+
+import edu.bjtu.groupone.backend.model.User;
+import org.apache.ibatis.annotations.*;
+
+@Mapper
+public interface UserMapper {
+     @Select("select * from user where identification_number=#{identificationNumber} and password=#{password}")
+     User login(User user);
+
+     @Select("SELECT * FROM user WHERE username = #{username}")
+    User selectByUsername(String username);
+
+    @Select("SELECT * FROM user WHERE email = #{email}")
+    User selectByEmail(String email);
+
+    @Insert("INSERT INTO user(username, email, password, registration_date, identification_number) " +
+            "VALUES(#{username}, #{email}, #{password}, #{registrationDate}, #{identificationNumber})")
+    @Options(useGeneratedKeys = true, keyProperty = "userId")
+    void insertUser(User user);
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/model/InviteCode.java b/src/main/java/edu/bjtu/groupone/backend/model/InviteCode.java
new file mode 100644
index 0000000..f1019f9
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/model/InviteCode.java
@@ -0,0 +1,18 @@
+package edu.bjtu.groupone.backend.model;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class InviteCode {
+    private String code;
+    private int maxUses;
+    private int remainingUses;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime expiryTime;  //失效时间
+    private int createdBy;  //创建者的id
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/model/Result.java b/src/main/java/edu/bjtu/groupone/backend/model/Result.java
new file mode 100644
index 0000000..f3d44ec
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/model/Result.java
@@ -0,0 +1,27 @@
+package edu.bjtu.groupone.backend.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Result {
+    private Integer code;//0表示成功,1表示失败
+    private String msg;//提示信息
+    private Object data;//要返回的数据
+    public static Result success() {//增删改响应成功
+
+        return new Result(0, "success", null);
+    }
+    public static Result success(Object data) {//增删改响应成功
+
+        return new Result(0, "success", data);
+    }
+    public static Result error(String msg) {//增删改响应成功
+
+        return new Result(1, msg, null);
+    }
+
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/model/User.java b/src/main/java/edu/bjtu/groupone/backend/model/User.java
new file mode 100644
index 0000000..95b2f00
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/model/User.java
@@ -0,0 +1,23 @@
+package edu.bjtu.groupone.backend.model;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class User {
+    private int userId;
+    private String username;
+    private String email;
+    private String password;
+    private String address;
+    private String role;
+    private String profilePic;
+    private String registrationDate;
+    private int identificationNumber;
+    private String avatar;// 头像
+    private boolean isfollowed = false;
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/model/UserDTO.java b/src/main/java/edu/bjtu/groupone/backend/model/UserDTO.java
new file mode 100644
index 0000000..279ac26
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/model/UserDTO.java
@@ -0,0 +1,16 @@
+package edu.bjtu.groupone.backend.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class UserDTO {
+    private User user;  // 原来的 User 对象
+    private boolean isFollowed;  // 是否关注的状态
+
+    // 构造函数
+
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/service/InviteCodeService.java b/src/main/java/edu/bjtu/groupone/backend/service/InviteCodeService.java
new file mode 100644
index 0000000..9391e14
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/service/InviteCodeService.java
@@ -0,0 +1,8 @@
+package edu.bjtu.groupone.backend.service;
+
+import edu.bjtu.groupone.backend.model.InviteCode;
+
+public interface InviteCodeService {
+    InviteCode validateInviteCode(String code);
+    boolean useInviteCode(String code);
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/service/UserService.java b/src/main/java/edu/bjtu/groupone/backend/service/UserService.java
new file mode 100644
index 0000000..662dafe
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/service/UserService.java
@@ -0,0 +1,15 @@
+package edu.bjtu.groupone.backend.service;
+
+
+import edu.bjtu.groupone.backend.model.User;
+
+public interface UserService {
+
+    User login(User user);
+
+    boolean isUsernameExists(String username);
+    boolean isEmailExists(String email);
+    void register(User user);
+    void sendVerificationCode(String email);
+    boolean verifyCode(String email, String code);
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/service/impl/InviteCodeServiceImpl.java b/src/main/java/edu/bjtu/groupone/backend/service/impl/InviteCodeServiceImpl.java
new file mode 100644
index 0000000..b08b7a5
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/service/impl/InviteCodeServiceImpl.java
@@ -0,0 +1,45 @@
+package edu.bjtu.groupone.backend.service.impl;
+
+import edu.bjtu.groupone.backend.mapper.InviteCodeMapper;
+import edu.bjtu.groupone.backend.model.InviteCode;
+import edu.bjtu.groupone.backend.service.InviteCodeService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+
+@Service
+public class InviteCodeServiceImpl implements InviteCodeService {
+
+    @Autowired
+    private InviteCodeMapper inviteCodeMapper;
+
+    /**
+     * 验证邀请码是否存在、未过期且剩余次数大于 0
+     */
+    @Override
+    public InviteCode validateInviteCode(String code) {
+        InviteCode inviteCode = inviteCodeMapper.selectByCode(code);
+        if (inviteCode == null
+                || inviteCode.getRemainingUses() <= 0
+                || inviteCode.getExpiryTime().isBefore(LocalDateTime.now())) {
+            return null;
+        }
+        return inviteCode;
+    }
+
+    /**
+     * 使用一次邀请码:先验证再扣减剩余次数
+     */
+    @Override
+    public boolean useInviteCode(String code) {
+        // 先调用 validateInviteCode 保证业务合理性
+        InviteCode valid = validateInviteCode(code);
+        if (valid == null) {
+            return false;
+        }
+        // 再执行扣减
+        int updated = inviteCodeMapper.decrementRemainingUses(code);
+        return updated > 0;
+    }
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/service/impl/UserServImpl.java b/src/main/java/edu/bjtu/groupone/backend/service/impl/UserServImpl.java
new file mode 100644
index 0000000..828b78d
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/service/impl/UserServImpl.java
@@ -0,0 +1,91 @@
+package edu.bjtu.groupone.backend.service.impl;
+
+import edu.bjtu.groupone.backend.mapper.UserMapper;
+import edu.bjtu.groupone.backend.model.User;
+import edu.bjtu.groupone.backend.service.UserService;
+import edu.bjtu.groupone.backend.utils.EmailUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Service
+public class UserServImpl implements UserService {
+
+    @Autowired
+    private UserMapper userMapper;
+    @Autowired
+    private EmailUtil emailUtil;
+    private final ConcurrentHashMap<String, String> emailCodes = new ConcurrentHashMap<>();
+    private static final long CODE_EXPIRE_MINUTES = 5;
+
+    @Override
+    public User login(User user) {
+        return userMapper.login(user);
+    }
+
+    @Override
+    public boolean isUsernameExists(String username) {
+        return userMapper.selectByUsername(username) != null;
+    }
+
+    @Override
+    public boolean isEmailExists(String email) {
+        return userMapper.selectByEmail(email) != null;
+    }
+
+    @Override
+    public void register(User user) {
+        // 如果前端未提供用户名,可生成默认
+        String username = user.getUsername();
+        if (username == null || username.isBlank()) {
+            do {
+                username = generateDefaultUsername(user.getEmail());
+            } while (isUsernameExists(username));
+            user.setUsername(username);
+        }
+        // 设置注册时间
+        user.setRegistrationDate(
+                LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
+        );
+        // 插入用户记录
+        userMapper.insertUser(user);
+    }
+
+    @Override
+    public void sendVerificationCode(String email) {
+        String code = generateRandomCode();
+        emailUtil.sendVerificationEmail(email, code);
+        long expireAt = System.currentTimeMillis() + CODE_EXPIRE_MINUTES * 60 * 1000;
+        emailCodes.put(email, code + "|" + expireAt);
+    }
+
+    @Override
+    public boolean verifyCode(String email, String inputCode) {
+        String stored = emailCodes.get(email);
+        if (stored == null) {
+            return false;
+        }
+        String[] parts = stored.split("\\|");
+        if (parts.length != 2) {
+            return false;
+        }
+        String code = parts[0];
+        long expireTime = Long.parseLong(parts[1]);
+        return System.currentTimeMillis() <= expireTime && code.equals(inputCode);
+    }
+
+    private String generateRandomCode() {
+        return String.format("%06d", new Random().nextInt(1_000000));
+    }
+
+    private String generateDefaultUsername(String email) {
+        String prefix = email != null && email.contains("@")
+                ? email.substring(0, email.indexOf("@"))
+                : "user";
+        return "user_" + prefix + "_" + String.format("%04d", new Random().nextInt(10000));
+    }
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/utils/AiUtils.java b/src/main/java/edu/bjtu/groupone/backend/utils/AiUtils.java
new file mode 100644
index 0000000..8dd8a8b
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/utils/AiUtils.java
@@ -0,0 +1,55 @@
+// ChatCompletionsService.java
+package edu.bjtu.groupone.backend.utils;
+
+import com.volcengine.ark.runtime.model.completion.chat.ChatCompletionRequest;
+import com.volcengine.ark.runtime.model.completion.chat.ChatMessage;
+import com.volcengine.ark.runtime.model.completion.chat.ChatMessageRole;
+import com.volcengine.ark.runtime.service.ArkService;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+public class AiUtils {
+
+    private ArkService service;
+
+    public void ChatCompletionsService(String apiKey) {
+        this.service = new ArkService(apiKey);
+    }
+
+    public String getAIResponse(String userInput) {
+        List<ChatMessage> messages = new ArrayList<>();
+        messages.add(ChatMessage.builder()
+                .role(ChatMessageRole.SYSTEM)
+                .content("你是豆包,是由字节跳动开发的 AI 人工智能助手")
+                .build());
+
+        messages.add(ChatMessage.builder()
+                .role(ChatMessageRole.USER)
+                .content(userInput)
+                .build());
+
+        ChatCompletionRequest request = ChatCompletionRequest.builder()
+                .model("ep-20241106142047-5g46t")  // 使用指定的模型
+                .messages(messages)
+                .build();
+
+        final StringBuilder aiResponse = new StringBuilder();
+
+        service.streamChatCompletion(request)
+                .doOnError(Throwable::printStackTrace)
+                .blockingForEach(choice -> {
+                    if (choice.getChoices().size() > 0) {
+                        aiResponse.append(choice.getChoices().get(0).getMessage().getContent());
+                    }
+                });
+
+        return aiResponse.toString();
+    }
+
+    public void shutdown() {
+        service.shutdownExecutor();
+    }
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/utils/AliOSSUtils.java b/src/main/java/edu/bjtu/groupone/backend/utils/AliOSSUtils.java
new file mode 100644
index 0000000..bedda4f
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/utils/AliOSSUtils.java
@@ -0,0 +1,45 @@
+package edu.bjtu.groupone.backend.utils;
+
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.UUID;
+
+/**
+ * 阿里云 OSS 工具类
+ */
+@Component
+public class AliOSSUtils {
+
+    private String endpoint = "https://oss-cn-beijing.aliyuncs.com";
+    private String accessKeyId = "LTAI5tQjb3fy8hTxxRGoS9b3";
+    private String accessKeySecret = "luBkGCqe3mqfYuiSOawC0KgPCi1yI4";
+    private String bucketName = "avatar-wzh";
+
+    /**
+     * 实现上传图片到OSS
+     */
+    public String upload(MultipartFile file) throws IOException {
+        // 获取上传的文件的输入流
+        InputStream inputStream = file.getInputStream();
+
+        // 避免文件覆盖
+        String originalFilename = file.getOriginalFilename();
+        String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
+
+        //上传文件到 OSS
+        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
+        ossClient.putObject(bucketName, fileName, inputStream);
+
+        //文件访问路径
+        String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
+        // 关闭ossClient
+        ossClient.shutdown();
+        return url;// 把上传到oss的路径返回
+    }
+
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/utils/EmailUtil.java b/src/main/java/edu/bjtu/groupone/backend/utils/EmailUtil.java
new file mode 100644
index 0000000..3ff7822
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/utils/EmailUtil.java
@@ -0,0 +1,39 @@
+package edu.bjtu.groupone.backend.utils;
+
+import jakarta.mail.MessagingException;
+import jakarta.mail.internet.MimeMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+public class EmailUtil {
+    @Autowired
+    private JavaMailSender mailSender;
+
+    @Value("${spring.mail.username}") // 确保从配置读取
+    private String from; 
+
+    public void sendVerificationEmail(String to, String code) {
+        try {
+            MimeMessage message = mailSender.createMimeMessage();
+            MimeMessageHelper helper = new MimeMessageHelper(message);
+            
+            // 关键修改:使用配置的授权邮箱作为发件人
+            helper.setFrom(from); 
+            helper.setTo(to);
+            helper.setSubject("[PT SITE] 验证码通知");
+            helper.setText("您的验证码是:" + code + ",5分钟内有效");
+            
+            mailSender.send(message);
+            log.info("邮件发送成功:{}", to);
+        } catch (MessagingException e) {
+            log.error("邮件发送失败", e);
+            throw new RuntimeException("邮件服务异常");
+        }
+    }
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/utils/GetTokenUserId.java b/src/main/java/edu/bjtu/groupone/backend/utils/GetTokenUserId.java
new file mode 100644
index 0000000..b798f05
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/utils/GetTokenUserId.java
@@ -0,0 +1,18 @@
+package edu.bjtu.groupone.backend.utils;
+
+import io.jsonwebtoken.Claims;
+import jakarta.servlet.http.HttpServletRequest;
+
+public class GetTokenUserId {
+
+    public static String getUserId(HttpServletRequest request) {
+        String token = request.getHeader("Authorization");
+        if (token == null || !token.startsWith("Bearer ")) {
+            return null;
+        }
+        // 解析 JWT Token,获取用户 ID
+        String jwt = token.substring(7); // 去掉 'Bearer ' 前缀
+        Claims claims = JwtUtils.parseJwt(jwt); // 从 JWT 中获取用户 ID
+        return claims.get("id").toString();
+    }
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/utils/JwtUtils.java b/src/main/java/edu/bjtu/groupone/backend/utils/JwtUtils.java
new file mode 100644
index 0000000..65e917a
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/utils/JwtUtils.java
@@ -0,0 +1,34 @@
+package edu.bjtu.groupone.backend.utils;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+
+import java.util.Date;
+import java.util.Map;
+
+public class JwtUtils {
+
+    // 生成JWT// 生成JWT
+    public static String generateJwt(Map<String,Object> claims) {
+
+        String jwt = Jwts.builder().
+                addClaims(claims)
+                .signWith(io.jsonwebtoken.SignatureAlgorithm.HS256, "Bjtu")
+                .setExpiration(new Date(System.currentTimeMillis() + 43200000L))
+                .compact();
+
+
+        return jwt;
+
+
+    }
+
+    public static Claims parseJwt(String jwt) {
+
+        return Jwts.parser()
+                .setSigningKey("Bjtu")
+                .parseClaimsJws(jwt)
+                .getBody();
+    }
+
+}
diff --git a/src/main/java/edu/bjtu/groupone/backend/utils/OpenAiUtil.java b/src/main/java/edu/bjtu/groupone/backend/utils/OpenAiUtil.java
new file mode 100644
index 0000000..1dbf943
--- /dev/null
+++ b/src/main/java/edu/bjtu/groupone/backend/utils/OpenAiUtil.java
@@ -0,0 +1,35 @@
+package edu.bjtu.groupone.backend.utils;
+
+import com.theokanning.openai.OpenAiService;
+import com.theokanning.openai.completion.CompletionChoice;
+import com.theokanning.openai.completion.CompletionRequest;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Value;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.List;
+
+
+public class OpenAiUtil {
+    @Value("${openai.secret_key}")
+    private String token;
+
+    private OpenAiService service;
+
+    @PostConstruct
+    public void init(){
+        service= new OpenAiService(token, Duration.ofSeconds(60L));
+    }
+    public List<CompletionChoice> sendComplete(String prompt) {
+        CompletionRequest completionRequest = CompletionRequest.builder()
+                .model("text-davinci-003") //采用最强模型,达芬奇模型3
+                .maxTokens(1500)
+                .prompt(prompt)
+                .user("testing")
+                .logitBias(new HashMap<>())
+                .build();
+
+        return service.createCompletion(completionRequest).getChoices();
+    }
+}
\ No newline at end of file