Initial empty repository
Change-Id: Ie0685414be5495d9da50d659d9ec16ae51487e46
diff --git a/src/main/java/com/example/myproject/MyProjectApplication.java b/src/main/java/com/example/myproject/MyProjectApplication.java
new file mode 100644
index 0000000..49cd5f0
--- /dev/null
+++ b/src/main/java/com/example/myproject/MyProjectApplication.java
@@ -0,0 +1,14 @@
+
+package com.example.myproject;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+@MapperScan("com.example.myproject.mapper") // 扫描 Mapper 接口
+public class MyProjectApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(MyProjectApplication.class, args);
+ }
+}
diff --git a/src/main/java/com/example/myproject/config/GlobalCorsConfig.java b/src/main/java/com/example/myproject/config/GlobalCorsConfig.java
new file mode 100644
index 0000000..909ead8
--- /dev/null
+++ b/src/main/java/com/example/myproject/config/GlobalCorsConfig.java
@@ -0,0 +1,32 @@
+package com.example.myproject.config;
+
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@EnableWebMvc
+@Configuration
+public class GlobalCorsConfig {
+ @Bean
+ public WebMvcConfigurer corsConfigurer() {
+ return new WebMvcConfigurer() {
+ @Override
+ public void addCorsMappings(CorsRegistry registry) {
+ registry.addMapping("/**") //添加映射路径,“/**”表示对所有的路径实行全局跨域访问权限的设置
+ .allowedOriginPatterns("*") //开放哪些ip、端口、域名的访问权限 SpringBoot2.4.0以后allowedOrigins被allowedOriginPatterns代替
+ .allowCredentials(true) //是否允许发送Cookie信息
+ .allowedMethods("GET", "POST", "PUT", "DELETE") //开放哪些Http方法,允许跨域访问
+ .allowedHeaders("*") //允许HTTP请求中的携带哪些Header信息
+ .exposedHeaders("*"); //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
+ }
+ };
+ }
+
+
+
+
+}
diff --git a/src/main/java/com/example/myproject/config/SecurityConfig.java b/src/main/java/com/example/myproject/config/SecurityConfig.java
new file mode 100644
index 0000000..05cba5d
--- /dev/null
+++ b/src/main/java/com/example/myproject/config/SecurityConfig.java
@@ -0,0 +1,57 @@
+package com.example.myproject.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@EnableWebSecurity //注解开启Spring Security的功能
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
+ return authenticationConfiguration.getAuthenticationManager();
+ }
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();//passwordEncoder的实现类
+ }
+
+ //构造一个内存框架对象,获取数据库中的数据
+/* @Bean
+ public UserDetailsService myUserDetailsService(){
+ return new TestUserServerImpl();
+ }*/
+ //也可以自动注入
+
+ //用户授权
+
+
+ //用户权限认证
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http
+ .csrf().disable() // 禁用 CSRF 保护
+ .authorizeRequests()
+ .antMatchers("/swagger-ui.html", "/webjars/**", "/v2/**", "/swagger-resources/**","/**").permitAll() // 允许无条件访问
+ .anyRequest().authenticated(); // 其他所有路径都需要身份验证
+ }
+
+
+ /**
+ * 核心过滤器配置,更多使用ignoring()用来忽略对静态资源的控制
+ */
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ web
+ .ignoring()
+ .antMatchers("/image/**");
+ }
+}
diff --git a/src/main/java/com/example/myproject/controller/UserController.java b/src/main/java/com/example/myproject/controller/UserController.java
new file mode 100644
index 0000000..4bf6adf
--- /dev/null
+++ b/src/main/java/com/example/myproject/controller/UserController.java
@@ -0,0 +1,153 @@
+package com.example.myproject.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.example.myproject.mapper.UserMapper;
+import com.example.myproject.mapper.VerificationTokenMapper;
+import com.example.myproject.entity.User;
+import com.example.myproject.entity.VerificationToken;
+import com.example.myproject.service.EmailService;
+import com.example.myproject.service.UserService;
+import com.example.myproject.utils.Result;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+@RestController
+@RequestMapping("/user")
+@Api(value = "用户管理接口", tags = {"用户管理"})
+public class UserController {
+
+ @Resource
+ private UserService userService;
+
+ @Autowired
+ private AuthenticationManager authenticationManager;
+
+ @Autowired
+ private UserMapper userMapper; // 使用 MyBatis-Plus
+
+ @Autowired
+ private VerificationTokenMapper verificationTokenMapper; // 替换 JPA
+
+ private static final Logger logger = LoggerFactory.getLogger(UserController.class);
+
+ @PostMapping("/login")
+ @ApiOperation(value = "用户登录", notes = "使用用户名和密码进行登录")
+ public Result loginController(@RequestParam @ApiParam(value = "用户名", required = true) String username,
+ @RequestParam @ApiParam(value = "密码", required = true) String password) {
+ try {
+ Authentication authentication = authenticationManager.authenticate(
+ new UsernamePasswordAuthenticationToken(username, password)
+ );
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+
+ // 使用 MyBatis-Plus 查询
+ User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", username));
+
+ System.out.println("Login successful for user: " + username);
+ return Result.success(user);
+ } catch (AuthenticationException e) {
+ return Result.error("401", "登录失败:" + e.getMessage());
+ }
+ }
+
+ @PostMapping("/register")
+ @ApiOperation(value = "用户注册", notes = "使用用户信息进行注册")
+ public Result registerController(@RequestBody @ApiParam(value = "新用户信息", required = true) User newUser) {
+ if (userService.checkEmailExists(newUser.getEmail())) {
+ return Result.error("邮箱冲突", "邮箱已被使用,请使用其他邮箱注册或找回密码!");
+ }
+ boolean success = userService.preRegisterUser(newUser);
+ if (success) {
+ User responseUser = new User();
+ responseUser.setEmail(newUser.getEmail());
+ return Result.success(responseUser, "验证邮件已发送,请检查您的邮箱。");
+ } else {
+ return Result.error("注册失败", "账号已存在或注册失败!");
+ }
+ }
+
+ public static class VerificationRequest {
+ private String email;
+ private String code;
+
+ public String getEmail() { return email; }
+ public void setEmail(String email) { this.email = email; }
+ public String getCode() { return code; }
+ public void setCode(String code) { this.code = code; }
+ }
+
+ @PostMapping("/verify-code")
+ @ApiOperation(value = "验证邮箱验证码", notes = "验证用户邮箱的验证码")
+ public Result verifyEmailCode(@RequestBody @ApiParam(value = "验证请求信息", required = true) VerificationRequest verificationRequest) {
+ String email = verificationRequest.getEmail();
+ String code = verificationRequest.getCode();
+ boolean isVerified = userService.verifyEmail(email, code);
+ if (isVerified) {
+ return Result.success(null, "邮箱验证成功!");
+ } else {
+ return Result.error("验证失败", "验证码错误或已过期!");
+ }
+ }
+
+ @Autowired
+ private EmailService emailService;
+
+ public static class EmailRequest {
+ private String email;
+ public String getEmail() { return email; }
+ public void setEmail(String email) { this.email = email; }
+ }
+
+ @PostMapping("/get-verification-email")
+ @ApiOperation(value = "发送验证邮件", notes = "通过电子邮件发送验证邮件")
+ public ResponseEntity<Result> sendVerificationEmail(@RequestBody @ApiParam(value = "发送验证请求", required = true) EmailRequest emailVerificationRequest) {
+ String email = emailVerificationRequest.getEmail();
+ User user = userMapper.selectOne(new QueryWrapper<User>().eq("email", email));
+ if (user == null) {
+ logger.error("未找到与该邮箱地址相关联的用户: {}", email);
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST)
+ .body(Result.error("1","未找到与该邮箱地址相关联的用户"));
+ }
+
+ // 生成验证码
+ String token = RandomStringUtils.randomNumeric(6);
+ Instant expiryDate = Instant.now().plus(1, ChronoUnit.HOURS);
+ logger.info("生成的验证令牌: {}, 过期时间: {}", token, expiryDate);
+
+ VerificationToken verificationToken = new VerificationToken(token, user.getUsername(), email, user.getPassword(), expiryDate);
+
+ // 保存到 MyBatis-Plus 数据库
+ verificationTokenMapper.insert(verificationToken);
+
+ logger.info("验证令牌已保存,用户: {}", user.getUsername());
+ emailService.sendVerificationEmail(email, token);
+
+ return ResponseEntity.ok(Result.success(200, "验证邮件已发送!"));
+ }
+ @PostMapping("/checkPassword")
+ public Result<String> checkPassword(@RequestParam Long userId, @RequestParam String password) {
+ boolean isPasswordCorrect = userService.checkPassword(userId, password);
+ if (isPasswordCorrect) {
+ return Result.success("200","原始密码输入正确");
+ } else {
+ return Result.error("305","原始密码输入错误");
+ }
+ }
+}
diff --git a/src/main/java/com/example/myproject/entity/User.java b/src/main/java/com/example/myproject/entity/User.java
new file mode 100644
index 0000000..20f7138
--- /dev/null
+++ b/src/main/java/com/example/myproject/entity/User.java
@@ -0,0 +1,55 @@
+package com.example.myproject.entity;
+
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@TableName("user") // 指定数据库表名
+@ApiModel("用户实体类") // 用于描述模型
+public class User {
+
+ @TableId(type = IdType.AUTO) // 指定主键策略
+ @ApiModelProperty(value = "用户ID", example = "1")
+ private Long id;
+
+ @JsonProperty("username")
+ @ApiModelProperty(value = "用户名", example = "22301115")
+ private String username;
+
+ @JsonProperty("nickname")
+ @ApiModelProperty(value = "昵称", example = "cyl")
+ private String nickname;
+
+ @JsonProperty("role")
+ @ApiModelProperty(value = "角色", example = "Student")
+ private String role;
+
+ @JsonProperty("password")
+ @ApiModelProperty(value = "密码", example = "123")
+ private String password;
+
+ @JsonProperty("status")
+ @ApiModelProperty(value = "用户状态", example = "1")
+ private int status;
+
+ @JsonProperty("email")
+ @ApiModelProperty(value = "电子邮件地址", example = "john_doe@example.com")
+ private String email;
+
+ @JsonProperty("email_verified")
+ @ApiModelProperty(value = "邮箱验证状态", example = "true")
+ private boolean emailVerified;
+
+ @JsonProperty("avatar")
+ @ApiModelProperty(value = "头像")
+ private String avatar;
+
+ public User() {
+ }
+}
diff --git a/src/main/java/com/example/myproject/entity/UserDetails.java b/src/main/java/com/example/myproject/entity/UserDetails.java
new file mode 100644
index 0000000..9af35f0
--- /dev/null
+++ b/src/main/java/com/example/myproject/entity/UserDetails.java
@@ -0,0 +1,23 @@
+package com.example.myproject.entity;
+
+import org.springframework.security.core.GrantedAuthority;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+public interface UserDetails extends Serializable {
+ Collection<? extends GrantedAuthority> getAuthorities();
+
+ String getPassword();
+
+ String getUsername();
+
+ boolean isAccountNonExpired();
+
+ boolean isAccountNonLocked();
+
+ boolean isCredentialsNonExpired();
+
+ boolean isEnabled();
+}
+
diff --git a/src/main/java/com/example/myproject/entity/VerificationToken.java b/src/main/java/com/example/myproject/entity/VerificationToken.java
new file mode 100644
index 0000000..231fd95
--- /dev/null
+++ b/src/main/java/com/example/myproject/entity/VerificationToken.java
@@ -0,0 +1,58 @@
+package com.example.myproject.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+
+import java.time.Instant;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName("verification_token")
+@ApiModel("验证令牌实体类")
+public class VerificationToken {
+
+ @TableId(type = IdType.AUTO)
+ @ApiModelProperty(value = "令牌ID")
+ private Long id;
+
+ @ApiModelProperty(value = "令牌字符串")
+ @TableField("token")
+ private String token;
+
+ @ApiModelProperty(value = "令牌过期日期")
+ @TableField("expiry_date")
+ private Instant expiryDate;
+
+ @ApiModelProperty(value = "用户名")
+ @TableField("username")
+ private String username;
+
+ @ApiModelProperty(value = "电子邮件地址")
+ @TableField("email")
+ private String email;
+
+ @ApiModelProperty(value = "加密后的密码")
+ @TableField("password")
+ private String password;
+
+ public VerificationToken(String token, String username, String email, String password, Instant expiryDate) {
+ this.token = token;
+ this.username = username;
+ this.email = email;
+ this.password = password;
+ this.expiryDate = expiryDate;
+ }
+
+ /**
+ * 检查令牌是否过期
+ * @return true 如果令牌已过期
+ */
+ public boolean isExpired() {
+ return expiryDate.isBefore(Instant.now());
+ }
+}
diff --git a/src/main/java/com/example/myproject/mapper/UserMapper.java b/src/main/java/com/example/myproject/mapper/UserMapper.java
new file mode 100644
index 0000000..a070bb5
--- /dev/null
+++ b/src/main/java/com/example/myproject/mapper/UserMapper.java
@@ -0,0 +1,25 @@
+package com.example.myproject.mapper;
+import com.example.myproject.entity.User;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+import org.springframework.data.repository.query.Param;
+
+import java.util.List;
+
+@Mapper
+public interface UserMapper extends BaseMapper<User> {
+
+
+ User selectByUsername(@Param("username") String username);
+
+ User selectByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
+
+ User selectByEmail(@Param("email") String email);
+
+
+ // 根据用户名包含查找用户
+ List<User> selectByUsernameContaining(@Param("name") String name);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/mapper/VerificationTokenMapper.java b/src/main/java/com/example/myproject/mapper/VerificationTokenMapper.java
new file mode 100644
index 0000000..94a25ca
--- /dev/null
+++ b/src/main/java/com/example/myproject/mapper/VerificationTokenMapper.java
@@ -0,0 +1,13 @@
+package com.example.myproject.mapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.myproject.entity.VerificationToken;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+
+@Mapper
+public interface VerificationTokenMapper extends BaseMapper<VerificationToken> {
+
+ VerificationToken findByTokenAndEmail(String token, String email);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/service/EmailService.java b/src/main/java/com/example/myproject/service/EmailService.java
new file mode 100644
index 0000000..1ecc8c4
--- /dev/null
+++ b/src/main/java/com/example/myproject/service/EmailService.java
@@ -0,0 +1,20 @@
+
+package com.example.myproject.service;
+public interface EmailService {
+ /**
+ * 发送邮件给指定的邮箱。
+ *
+ * @param to 收件人邮箱地址
+ * @param subject 邮件主题
+ * @param text 邮件内容
+ * @return 发送是否成功
+ */
+ boolean sendEmail(String to, String subject, String text);
+ /**
+ * 生成并发送验证邮件
+ *
+ * @param email 收件人邮箱
+ * @param token
+ */
+ void sendVerificationEmail(String email, String token);
+}
diff --git a/src/main/java/com/example/myproject/service/UserDetailsService.java b/src/main/java/com/example/myproject/service/UserDetailsService.java
new file mode 100644
index 0000000..85d4da2
--- /dev/null
+++ b/src/main/java/com/example/myproject/service/UserDetailsService.java
@@ -0,0 +1,4 @@
+package com.example.myproject.service;
+
+public class UserDetailsService {
+}
diff --git a/src/main/java/com/example/myproject/service/UserService.java b/src/main/java/com/example/myproject/service/UserService.java
new file mode 100644
index 0000000..535f635
--- /dev/null
+++ b/src/main/java/com/example/myproject/service/UserService.java
@@ -0,0 +1,24 @@
+package com.example.myproject.service;
+
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import com.example.myproject.entity.User;
+
+public interface UserService extends IService<User> {
+ User loginService(String username, String password);
+
+
+ boolean preRegisterUser(User user);
+
+
+ boolean verifyEmail(String email, String token);
+
+
+ boolean checkEmailExists(String email);
+
+ boolean checkPassword(Long userId, String rawPassword);
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/service/serviceImpl/EmailServiceImpl.java b/src/main/java/com/example/myproject/service/serviceImpl/EmailServiceImpl.java
new file mode 100644
index 0000000..83500ea
--- /dev/null
+++ b/src/main/java/com/example/myproject/service/serviceImpl/EmailServiceImpl.java
@@ -0,0 +1,51 @@
+
+package com.example.myproject.service.serviceImpl;
+import com.example.myproject.mapper.VerificationTokenMapper;
+import com.example.myproject.entity.VerificationToken;
+import com.example.myproject.service.EmailService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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.Service;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import java.time.Instant;
+@Service
+public class EmailServiceImpl implements EmailService {
+ private static final Logger log = LoggerFactory.getLogger(EmailServiceImpl.class);
+ @Autowired
+ private JavaMailSender mailSender;
+ @Autowired
+ private VerificationTokenMapper verificationTokenMapper;
+ @Value("${spring.mail.username}") // 从配置文件中注入发件人邮箱地址
+ private String fromEmail;
+ @Override
+ public boolean sendEmail(String to, String subject, String text) {
+ log.debug("开始发送验证邮件到:{},主题:{}", to, subject);
+ try {
+ MimeMessage message = mailSender.createMimeMessage();
+ MimeMessageHelper helper = new MimeMessageHelper(message, true);
+ helper.setFrom(fromEmail);
+ helper.setTo(to);
+ helper.setSubject(subject);
+ helper.setText(text, true);
+ mailSender.send(message);
+ log.info("邮件成功发送到:{}", to);
+ return true;
+ } catch (MessagingException e) {
+ log.error("发送邮件失败,收件人:{},错误信息:{}", to, e.getMessage(), e);
+ return false;
+ }
+ }
+ @Override
+ public void sendVerificationEmail(String email, String token){
+ // 发送邮件
+ String message = "<h1>Email Verification</h1>" +
+ "<p>Your verification code is: <b>" + token + "</b></p>" +
+ "<p>This code will expire in 1 hour.</p>";
+ sendEmail(email, "Verify Your Email", message);
+ }
+}
diff --git a/src/main/java/com/example/myproject/service/serviceImpl/UserDetailsServiceImpl.java b/src/main/java/com/example/myproject/service/serviceImpl/UserDetailsServiceImpl.java
new file mode 100644
index 0000000..4bbd5c3
--- /dev/null
+++ b/src/main/java/com/example/myproject/service/serviceImpl/UserDetailsServiceImpl.java
@@ -0,0 +1,47 @@
+package com.example.myproject.service.serviceImpl;
+
+import com.example.myproject.mapper.UserMapper;
+import com.example.myproject.entity.User;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService {
+
+ @Autowired
+ private UserMapper userMapper;
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ //username参数,是在登陆时,用户传递的表单数据username
+ //主要读取数据库3个值 username password authorities
+ User user= userMapper.selectByUsername(username);
+ if (user == null) {
+ throw new UsernameNotFoundException("用户名未找到");
+ }
+
+ String authorityName = user.getRole();
+ //为了返回一个UserDetails 使用User
+ List<GrantedAuthority> authorities = new ArrayList<>();
+ GrantedAuthority authority = new SimpleGrantedAuthority(authorityName);
+ authorities.add(authority);
+ return new org.springframework.security.core.userdetails.User(
+ user.getUsername(),
+ user.getPassword(),
+ true, // accountEnabled
+ true, // accountNonExpired
+ true, // credentialsNonExpired
+ true, // accountNonLocked
+ AuthorityUtils.createAuthorityList("ROLE_USER") // 设置用户的角色或权限
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/service/serviceImpl/UserServiceImpl.java b/src/main/java/com/example/myproject/service/serviceImpl/UserServiceImpl.java
new file mode 100644
index 0000000..d0718fe
--- /dev/null
+++ b/src/main/java/com/example/myproject/service/serviceImpl/UserServiceImpl.java
@@ -0,0 +1,122 @@
+
+package com.example.myproject.service.serviceImpl;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.example.myproject.mapper.UserMapper;
+import com.example.myproject.mapper.VerificationTokenMapper;
+import com.example.myproject.entity.User;
+import com.example.myproject.entity.VerificationToken;
+import com.example.myproject.service.EmailService;
+import com.example.myproject.service.UserService;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+//登录注册
+@Service
+public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
+ private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
+ @Autowired
+ private UserMapper userMapper; // Using MyBatis-Plus mapper
+ @Autowired
+ private EmailServiceImpl emailService;
+ @Autowired
+ private VerificationTokenMapper verificationTokenMapper; // Using MyBatis-Plus mapper
+ @Autowired
+ private PasswordEncoder passwordEncoder; // Injecting password encoder
+ @Override
+ public User loginService(String username, String password) {
+ log.debug("Attempting login with username: {}, password: {}", username, password);
+ User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", username).eq("password", password));
+ if (user != null) {
+ user.setPassword(""); // Clear the password before returning
+ log.debug("Login successful, User ID: {}", user.getId());
+ } else {
+ log.debug("Login failed, incorrect username or password.");
+ }
+ return user;
+ }
+ @Override
+ public boolean preRegisterUser(User user) {
+ log.debug("Pre-registering user, username: {}, email: {}", user.getUsername(), user.getEmail());
+ // 检查用户名或邮箱是否已存在
+ boolean userExists = userMapper.selectOne(new QueryWrapper<User>().eq("username", user.getUsername())) != null ||
+ userMapper.selectOne(new QueryWrapper<User>().eq("email", user.getEmail())) != null;
+ if (userExists) {
+ log.debug("Pre-registration failed, username or email already exists.");
+ return false; // 用户名或邮箱已经存在
+ }
+ // 加密密码
+ String encryptedPassword = passwordEncoder.encode(user.getPassword());
+ // 生成验证码
+ String token = RandomStringUtils.randomNumeric(6);
+ // 设置过期时间为当前时间加一小时
+ Instant expiryDate = Instant.now().plus(1, ChronoUnit.HOURS);
+ // 创建验证令牌对象
+ VerificationToken verificationToken = new VerificationToken(
+ token,
+ user.getUsername(),
+ user.getEmail(),
+ encryptedPassword,
+ expiryDate
+ );
+ // 插入验证令牌
+ int rowsInserted = verificationTokenMapper.insert(verificationToken);
+ if (rowsInserted > 0) {
+ // 发送验证邮件
+ emailService.sendVerificationEmail(user.getEmail(), token);
+ log.debug("Pre-registration successful, verification code: {}, expiry date: {}", token, expiryDate);
+ return true; // 注册前验证成功
+ } else {
+ log.error("Failed to insert verification token into database.");
+ return false; // 如果插入验证令牌失败,返回失败
+ }
+ }
+ @Override
+ public boolean verifyEmail(String email, String token) {
+ log.debug("Verifying email, email: {}, token: {}", email, token);
+ VerificationToken verificationToken = verificationTokenMapper.selectOne(
+ new QueryWrapper<VerificationToken>().eq("token", token).eq("email", email)
+ );
+ if (verificationToken != null && !verificationToken.isExpired()) {
+ log.debug("Verification code is valid, username: {}", verificationToken.getUsername());
+ User user = userMapper.selectOne(new QueryWrapper<User>().eq("email", email));
+ if (user == null) {
+ user = new User();
+ user.setEmail(email);
+ user.setUsername(verificationToken.getUsername());
+ user.setPassword(verificationToken.getPassword());
+ user.setEmailVerified(true);
+ userMapper.insert(user); // Save new user
+ log.debug("New user created, User ID: {}", user.getId());
+ } else {
+ user.setEmailVerified(true);
+ userMapper.updateById(user); // Update existing user
+ log.debug("User email verified, User ID: {}", user.getId());
+ }
+ verificationTokenMapper.delete(new QueryWrapper<VerificationToken>().eq("token", token)); // Clean up the token
+ log.debug("Verification code deleted.");
+ return true;
+ }
+ log.debug("Verification code is invalid or expired.");
+ return false;
+ }
+ public boolean checkEmailExists(String email) {
+ log.debug("检查邮箱是否存在,邮箱:{}", email);
+ boolean exists = userMapper.selectCount(new QueryWrapper<User>().eq("email", email)) > 0;
+ log.debug("邮箱存在状态:{}", exists);
+ return exists;
+ }
+ public boolean checkPassword(Long userId, String password) {
+ User user = userMapper.selectById(userId);
+ if (user == null) {
+ throw new RuntimeException("用户不存在");
+ }
+ String encryptedPassword = user.getPassword();
+ return passwordEncoder.matches(password, encryptedPassword);
+ }
+}
diff --git a/src/main/java/com/example/myproject/utils/Result.java b/src/main/java/com/example/myproject/utils/Result.java
new file mode 100644
index 0000000..e838a1c
--- /dev/null
+++ b/src/main/java/com/example/myproject/utils/Result.java
@@ -0,0 +1,129 @@
+package com.example.myproject.utils;
+
+
+public class Result<T> {
+ private String code;
+ private String msg;
+ private T data;
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setCode(String code) {
+ this.code = code;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+
+ public void setMsg(String msg) {
+ this.msg = msg;
+ }
+
+ public T getData() {
+ return data;
+ }
+
+ public void setData(T data) {
+ this.data = data;
+ }
+
+ public Result() {
+ }
+
+ public Result(T data) {
+ this.data = data;
+ }
+
+ public static Result success() {
+ Result result = new Result<>();
+ result.setCode("200");
+ result.setMsg("成功");
+ return result;
+ }
+
+ public static <T> Result<T> success(T data) {
+ Result<T> result = new Result<>(data);
+ result.setCode("200");
+ result.setMsg("成功");
+ return result;
+ }
+ public static <T> Result<T> success(String msg) {
+ Result result = new Result();
+ result.setCode("200");
+ result.setMsg("成功");
+ return result;
+ }
+
+ public static <T> Result<T> success(T data,String msg) {
+ Result<T> result = new Result<>(data);
+ result.setCode("200");
+ result.setMsg(msg);
+ return result;
+ }
+
+ public static Result error(String code, String msg) {
+ Result result = new Result();
+ result.setCode(code);
+ result.setMsg(msg);
+ return result;
+ }
+ /**
+ * 创建一个表示文件大小超出限制的结果对象。
+ *
+ * @return 构造的文件过大错误结果对象
+ */
+ public static Result fileTooLarge() {
+ Result result = new Result();
+ result.setCode("413");
+ result.setMsg("文件大小超出限制。");
+ return result;
+ }
+
+ /**
+ * 创建一个表示文件格式不支持的结果对象。
+ *
+ * @return 构造的文件格式错误结果对象
+ */
+ public static Result unsupportedFileType() {
+ Result result = new Result();
+ result.setCode("415");
+ result.setMsg("不支持的文件格式。");
+ return result;
+ }
+
+ /**
+ * 创建一个表示文件未找到的结果对象。
+ *
+ * @return 构造的文件未找到错误结果对象
+ */
+ public static Result fileNotFound() {
+ Result result = new Result();
+ result.setCode("404");
+ result.setMsg("文件未找到。");
+ return result;
+ }
+
+ /**
+ * 创建一个表示文件存储错误的结果对象。
+ *
+ * @param errorMsg 详细错误信息
+ * @return 构造的文件存储错误结果对象
+ */
+ public static Result fileStorageError(String errorMsg) {
+ Result result = new Result();
+ result.setCode("500");
+ result.setMsg("文件存储错误: " + errorMsg);
+ return result;
+ }
+
+ public static Result permissionDenied() {
+ Result result = new Result();
+ result.setCode("401");
+ result.setMsg("权限不足,无法执行该操作。");
+ return result;
+ }
+
+}
diff --git a/src/main/java/com/example/myproject/utils/VerifyCode.java b/src/main/java/com/example/myproject/utils/VerifyCode.java
new file mode 100644
index 0000000..689b8ce
--- /dev/null
+++ b/src/main/java/com/example/myproject/utils/VerifyCode.java
@@ -0,0 +1,156 @@
+package com.example.myproject.utils;
+
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Random;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Random;
+
+/*
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Random;
+
+/**
+ * @author 阿楠
+ * code生成工具类
+ */
+public class VerifyCode {
+ /**
+ * 生成验证码图片的宽度
+ */
+ private int width = 100;
+
+ /**
+ * 生成验证码图片的高度
+ */
+ private int height = 30;
+
+ /**
+ * 字符样式
+ */
+ private String[] fontNames = { "宋体", "楷体", "隶书", "微软雅黑" };
+
+ /**
+ * 定义验证码图片的背景颜色为白色
+ */
+ private Color bgColor = new Color(255, 255, 255);
+
+ /**
+ * 生成随机
+ */
+ private Random random = new Random();
+
+ /**
+ * 定义code字符
+ */
+ private String codes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ /**
+ * 记录随机字符串
+ */
+ private String text;
+
+ /**
+ * 获取一个随意颜色
+ * @return
+ */
+ private Color randomColor() {
+ int red = random.nextInt(150);
+ int green = random.nextInt(150);
+ int blue = random.nextInt(150);
+ return new Color(red, green, blue);
+ }
+
+ /**
+ * 获取一个随机字体
+ *
+ * @return
+ */
+ private Font randomFont() {
+ String name = fontNames[random.nextInt(fontNames.length)];
+ int style = random.nextInt(4);
+ int size = random.nextInt(5) + 24;
+ return new Font(name, style, size);
+ }
+
+ /**
+ * 获取一个随机字符
+ *
+ * @return
+ */
+ private char randomChar() {
+ return codes.charAt(random.nextInt(codes.length()));
+ }
+
+ /**
+ * 创建一个空白的BufferedImage对象
+ *
+ * @return
+ */
+ private BufferedImage createImage() {
+ BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ Graphics2D g2 = (Graphics2D) image.getGraphics();
+ //设置验证码图片的背景颜色
+ g2.setColor(bgColor);
+ g2.fillRect(0, 0, width, height);
+ return image;
+ }
+
+ public BufferedImage getImage() {
+ BufferedImage image = createImage();
+ Graphics2D g2 = (Graphics2D) image.getGraphics();
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < 4; i++) {
+ String s = randomChar() + "";
+ sb.append(s);
+ g2.setColor(randomColor());
+ g2.setFont(randomFont());
+ float x = i * width * 1.0f / 4;
+ g2.drawString(s, x, height - 8);
+ }
+ this.text = sb.toString();
+ drawLine(image);
+ return image;
+ }
+
+ /**
+ * 绘制干扰线
+ *
+ * @param image
+ */
+ private void drawLine(BufferedImage image) {
+ Graphics2D g2 = (Graphics2D) image.getGraphics();
+ int num = 5;
+ for (int i = 0; i < num; i++) {
+ int x1 = random.nextInt(width);
+ int y1 = random.nextInt(height);
+ int x2 = random.nextInt(width);
+ int y2 = random.nextInt(height);
+ g2.setColor(randomColor());
+ g2.setStroke(new BasicStroke(1.5f));
+ g2.drawLine(x1, y1, x2, y2);
+ }
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public static void output(BufferedImage image, OutputStream out) throws IOException {
+ ImageIO.write(image, "JPEG", out);
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..b753da7
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,21 @@
+server.port=8080
+spring.datasource.url=jdbc:mysql://localhost:3306/pt
+spring.datasource.username=root
+spring.datasource.password=root
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+
+mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
+
+
+# Mail configuration
+spring.mail.password=tljnebonhzhqecda
+spring.mail.username=2370523716@qq.com
+spring.mail.host=smtp.qq.com
+spring.mail.properties.mail.smtp.ssl.enable=true
+spring.mail.properties.mail.smtp.auth=true
+spring.mail.properties.mail.smtp.starttls.enable=true
+
+
+spring.jpa.enabled=false
+spring.jpa.hibernate.ddl-auto=none
+spring.jpa.open-in-view=false
diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml
new file mode 100644
index 0000000..f03ab0a
--- /dev/null
+++ b/src/main/resources/mapper/UserMapper.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.example.myproject.mapper.UserMapper">
+ <!-- 根据用户名查找用户 -->
+ <select id="selectByUsername" parameterType="string" resultType="com.example.myproject.entity.User">
+ SELECT * FROM user WHERE username = #{username}
+ </select>
+ <!-- 根据用户名和密码查找用户 -->
+ <select id="selectByUsernameAndPassword" parameterType="map" resultType="com.example.myproject.entity.User">
+ SELECT * FROM user WHERE username = #{username} AND password = #{password}
+ </select>
+ <!-- 根据邮箱查找用户 -->
+ <select id="selectByEmail" parameterType="string" resultType="com.example.myproject.entity.User">
+ SELECT * FROM user WHERE email = #{email}
+ </select>
+ <!-- 根据用户名包含查找用户 -->
+ <select id="selectByUsernameContaining" parameterType="string" resultType="com.example.myproject.entity.User">
+ SELECT * FROM user WHERE username LIKE CONCAT('%', #{name}, '%')
+ </select>
+</mapper>
diff --git a/src/main/resources/mapper/VerificationTokenMapper.xml b/src/main/resources/mapper/VerificationTokenMapper.xml
new file mode 100644
index 0000000..53b19a5
--- /dev/null
+++ b/src/main/resources/mapper/VerificationTokenMapper.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+ "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.example.myproject.mapper.VerificationTokenMapper">
+ <!-- 通过 token 和 email 查询 VerificationToken -->
+ <select id="findByTokenAndEmail" resultType="com.example.myproject.entity.VerificationToken">
+ SELECT * FROM verification_token
+ WHERE token = #{token} AND email = #{email}
+ </select>
+</mapper>
diff --git a/src/test/java/com/example/myproject/controller/UserControllerTest.java b/src/test/java/com/example/myproject/controller/UserControllerTest.java
new file mode 100644
index 0000000..8a09e53
--- /dev/null
+++ b/src/test/java/com/example/myproject/controller/UserControllerTest.java
@@ -0,0 +1,163 @@
+package com.example.myproject.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.example.myproject.entity.User;
+import com.example.myproject.service.EmailService;
+import com.example.myproject.service.UserService;
+import com.example.myproject.mapper.UserMapper;
+import com.example.myproject.mapper.VerificationTokenMapper;
+import com.example.myproject.utils.Result;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.*;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class UserControllerTest {
+
+ @InjectMocks
+ private UserController userController;
+
+ @Mock
+ private UserService userService;
+ @Mock
+ private UserMapper userMapper;
+ @Mock
+ private EmailService emailService;
+ @Mock
+ private AuthenticationManager authenticationManager;
+ @Mock
+ private VerificationTokenMapper verificationTokenMapper;
+
+ @BeforeEach
+ void setup() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ void loginController_success() {
+ String username = "testuser";
+ String password = "testpass";
+ User mockUser = new User();
+ mockUser.setUsername(username);
+
+ Authentication mockAuth = mock(Authentication.class);
+ when(authenticationManager.authenticate(any())).thenReturn(mockAuth);
+ when(userMapper.selectOne(any(QueryWrapper.class))).thenReturn(mockUser);
+
+ Result result = userController.loginController(username, password);
+
+ assertEquals("200", result.getCode());
+ assertEquals(mockUser, result.getData());
+ }
+
+
+ @Test
+ void loginController_failure() {
+ String username = "user";
+ String password = "wrongpass";
+
+ // 模拟认证失败,抛出 Bad credentials 异常
+ when(authenticationManager.authenticate(any()))
+ .thenThrow(new AuthenticationException("Bad credentials") {});
+
+
+ // 调用登录接口
+ Result result = userController.loginController(username, password);
+
+ // 断言返回的状态码和消息
+ assertEquals("401", result.getCode());
+ assertTrue(result.getMsg().contains("登录失败"));
+ }
+
+ @Test
+ void registerController_emailExists() {
+ User user = new User();
+ user.setEmail("test@example.com");
+
+ when(userService.checkEmailExists(user.getEmail())).thenReturn(true);
+
+ Result result = userController.registerController(user);
+
+ assertEquals("邮箱冲突", result.getCode());
+ }
+
+ @Test
+ void registerController_success() {
+ User user = new User();
+ user.setEmail("test@example.com");
+
+ when(userService.checkEmailExists(user.getEmail())).thenReturn(false);
+ when(userService.preRegisterUser(user)).thenReturn(true);
+
+ Result result = userController.registerController(user);
+
+ assertEquals("200", result.getCode());
+ assertEquals(user.getEmail(), ((User) result.getData()).getEmail());
+ }
+
+ @Test
+ void verifyEmailCode_success() {
+ when(userService.verifyEmail("test@example.com", "123456")).thenReturn(true);
+
+ UserController.VerificationRequest request = new UserController.VerificationRequest();
+ request.setEmail("test@example.com");
+ request.setCode("123456");
+
+ Result result = userController.verifyEmailCode(request);
+
+ assertEquals("200", result.getCode());
+ }
+
+ @Test
+ void verifyEmailCode_failure() {
+ when(userService.verifyEmail("test@example.com", "000000")).thenReturn(false);
+
+ UserController.VerificationRequest request = new UserController.VerificationRequest();
+ request.setEmail("test@example.com");
+ request.setCode("000000");
+
+ Result result = userController.verifyEmailCode(request);
+
+ assertEquals("验证失败", result.getCode());
+ }
+
+ @Test
+ void checkPassword_success() {
+ when(userService.checkPassword(1L, "abc123")).thenReturn(true);
+
+ Result<String> result = userController.checkPassword(1L, "abc123");
+
+ assertEquals("200", result.getCode());
+ assertEquals("原始密码输入正确", result.getMsg());
+ }
+
+ @Test
+ void checkPassword_failure() {
+ when(userService.checkPassword(1L, "wrong")).thenReturn(false);
+
+ Result<String> result = userController.checkPassword(1L, "wrong");
+
+ assertEquals("305", result.getCode());
+ assertEquals("原始密码输入错误", result.getMsg());
+ }
+
+ @Test
+ void sendVerificationEmail_userNotFound() {
+ UserController.EmailRequest request = new UserController.EmailRequest();
+ request.setEmail("notfound@example.com");
+
+ when(userMapper.selectOne(any())).thenReturn(null);
+
+ ResponseEntity<Result> response = userController.sendVerificationEmail(request);
+
+ assertEquals(400, response.getStatusCodeValue());
+ assertEquals("1", response.getBody().getCode());
+ }
+}