完成实验部分后端

This commit is contained in:
li-chx 2025-05-03 19:37:28 +08:00
parent 086c034a95
commit a35ecd8a2f
37 changed files with 1336 additions and 0 deletions

2
.gitignore vendored
View File

@ -31,3 +31,5 @@ build/
### VS Code ###
.vscode/
/src/main/resources/application-dev.yml
/src/main/resources/dev.application.properties

32
pom.xml
View File

@ -61,6 +61,38 @@
<version>3.0.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.11</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.227</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,64 @@
package top.lichx.webclassbackend.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "mail.smtp")
public class MailProperties {
private String host;
private int port;
private String username;
private String password;
private boolean auth;
private boolean starttls;
// Getters and Setters
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isAuth() {
return auth;
}
public void setAuth(boolean auth) {
this.auth = auth;
}
public boolean isStarttls() {
return starttls;
}
public void setStarttls(boolean starttls) {
this.starttls = starttls;
}
}

View File

@ -0,0 +1,25 @@
package top.lichx.webclassbackend.config;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import top.lichx.webclassbackend.interceptor.JwtInterceptor;
@Configuration
@Slf4j
public class WebConfig implements WebMvcConfigurer {
@Resource
private JwtInterceptor jwtInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
log.info("addInterceptors");
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/login/**") // 放行登录接口
.excludePathPatterns("/file/uploadAvatar") // 放行头像上传接口
.excludePathPatterns("/error/**"); // 放行错误处理接口
}
}

View File

@ -0,0 +1,68 @@
package top.lichx.webclassbackend.controller;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import top.lichx.webclassbackend.pojo.vo.AvatarInfo;
import top.lichx.webclassbackend.result.Result;
import top.lichx.webclassbackend.util.COSIO;
import java.io.File;
import java.io.IOException;
import java.net.URL;
@RestController
@RequestMapping("/file")
public class FileController {
@Resource
private COSIO cosio;
@GetMapping("/test")
public String test() {
// 使用注入的 COSIO 实例
System.out.println("COSIO: " + cosio);
return "test";
}
// @PostMapping("/upload")
// public Result<?> uploadFile(@RequestParam("file") MultipartFile multipartFile) {
// try {
// // MultipartFile 转换为 File
// var file = File.createTempFile("upload-", multipartFile.getOriginalFilename());
// multipartFile.transferTo(file);
//
// // 调用 COSIO 的上传方法
// String filename = cosio.UploadFile(file);
//
// // 删除临时文件
// file.delete();
//
// return Result.success("文件上传成功,文件路径:", filename);
// } catch (IOException e) {
// e.printStackTrace();
// // 处理文件上传失败的情况
// return Result.error("文件上传失败:" + e.getMessage());
// }
// }
@PostMapping("/uploadAvatar")
public Result<?> uploadAvatar(@RequestParam("file") MultipartFile multipartFile) throws IOException {
var file = File.createTempFile("upload-", multipartFile.getOriginalFilename());
multipartFile.transferTo(file);
var fileName = cosio.UploadFile(file);
file.delete();
var fileURL = cosio.GetFileLink(fileName);
var avatarInfo = new AvatarInfo(fileName, fileURL);
return Result.success("文件上传成功", avatarInfo);
}
@GetMapping("/getFileLink")
public Result<?> getFileLink(@RequestParam String fileName) {
URL url = cosio.GetFileLink(fileName);
return Result.success("获取文件链接成功", url.toString());
}
}

View File

@ -0,0 +1,287 @@
package top.lichx.webclassbackend.controller;
import jakarta.annotation.Resource;
import jakarta.mail.MessagingException;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.*;
import top.lichx.webclassbackend.pojo.dto.LoginDTO;
import top.lichx.webclassbackend.pojo.dto.PasswordResetDTO;
import top.lichx.webclassbackend.pojo.dto.RegisterDTO;
import top.lichx.webclassbackend.pojo.entity.User;
import top.lichx.webclassbackend.result.Result;
import top.lichx.webclassbackend.service.IdentifyingCode;
import top.lichx.webclassbackend.service.UserService;
import top.lichx.webclassbackend.util.JwtUtil;
import top.lichx.webclassbackend.util.MailUtil;
import top.lichx.webclassbackend.util.PasswordUtil;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@RestController
@RequestMapping("/login")
public class Login {
private String mailTemplate = """
<body>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="p-80 mpy-35 mpx-15" bgcolor="#FFFFFF" style="padding: 80px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td
style="font-size:20px; line-height:42px; font-family:Arial, sans-serif, 'Motiva Sans'; text-align:left; padding-bottom: 30px; color:#002333; font-weight:bold;">
<span style="color: #007aff;">您好</span>
</td>
</tr>
</tbody>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="text-18 c-grey4 pb-30"
style="font-size:18px; line-height:25px; font-family:Arial, sans-serif, 'Motiva Sans'; text-align:left; color:#002333; padding-bottom: 30px;">
{tip}有效时间为 {ExpireTime} 分钟
</td>
</tr>
</tbody>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="pb-70 mpb-50" style="padding-bottom: 70px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0"
bgcolor="#eaebff">
<tbody>
<tr>
<td class="py-30 px-56"
style="padding-top: 30px; padding-bottom: 30px; padding-left: 56px; padding-right: 56px;">
<table width="100%" border="0" cellspacing="0"
cellpadding="0">
<tbody>
<tr>
<td style="font-size:18px; line-height:25px; font-family:Arial, sans-serif, 'Motiva Sans'; color:#8f98a0; text-align:center;">
验证码
</td>
</tr>
<tr>
<td style="padding-bottom: 16px"></td>
</tr>
<tr>
<td class="title-48 c-blue1 fw-b a-center"
style="font-size:48px; line-height:52px; font-family:Arial, sans-serif, 'Motiva Sans'; color:#007aff; font-weight:bold; text-align:center;">
{Captcha}
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="title-36 pb-30 c-grey6 fw-b"
style="font-size:30px; line-height:34px; font-family:Arial, sans-serif, 'Motiva Sans'; text-align:left; padding-bottom: 20px; color:#002333; font-weight:bold;">
不是您
</td>
</tr>
</tbody>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="text-18 c-grey4 pb-30"
style="font-size:18px; line-height:25px; font-family:Arial, sans-serif, 'Motiva Sans'; text-align:left; color:#002333; padding-bottom: 30px;">
\s
如果这不是来自您的验证请求请您忽略本邮件并<span style="color: #002333; font-weight: bold;">不要将验证码转发给任何人</span><br><br>
此电子邮件包含一个代码您需要用它验证您的帐户切勿与任何人分享此代码
</td>
</tr>
</tbody>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="pt-30" style="padding-top: 30px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="img" width="3" bgcolor="#007aff"
style="font-size:0pt; line-height:0pt; text-align:left;">
</td>
<td class="img" width="37"
style="font-size:0pt; line-height:0pt; text-align:left;">
</td>
<td>
<table width="100%" border="0" cellspacing="0"
cellpadding="0">
<tbody>
<tr>
<td class="text-16 py-20 c-grey4 fallback-font"
style="font-size:16px; line-height:22px; font-family:Arial, sans-serif, 'Motiva Sans'; text-align:left; padding-top: 20px; padding-bottom: 20px; color:#002333;">
祝您愉快<br>
Lichx
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
</tr>
</tbody>
</table>
</body>
""";
// @Resource
// private Test test;
@Resource
private MailUtil mailUtil;
@Resource
private IdentifyingCode identifyingCode;
@Resource
private UserService userService;
@Resource
private PasswordUtil passwordUtil;
// @GetMapping("/test")
// public String test() {
// test.test();
// return "test";
// }
//
// @PostMapping("/test/{id}")
// public String testPost(@PathVariable Integer id) {
// test.test();
// return "testPost";
// }
@PostMapping("/")
public Result<?> login(@RequestBody LoginDTO loginInfo, HttpServletResponse response) {
// 验证用户名和密码此处省略具体逻辑
String usernameOrEmail = loginInfo.getUsernameOrEmail();
String password = loginInfo.getPassword();
var user = usernameOrEmail.contains("@")?userService.getUserByEmail(usernameOrEmail) : userService.getUserByUserName(usernameOrEmail);
if (user != null && passwordUtil.matches(password,user.getPassword())) {
String token = JwtUtil.generateToken(user.getName());
// 设置HttpOnly和Secure属性的Cookie
response.setHeader("Set-Cookie", "Authorization=Bearer " + token + "; HttpOnly; Max-Age=86400; Path=/; SameSite=Strict");
// response.setHeader("Set-Cookie", "token=Bearer " + token + "; HttpOnly; Secure; Path=/; SameSite=Strict");
return Result.success("登录成功");
}
return Result.error("用户名或密码错误");
}
@GetMapping("/logout")
public Result<?> logout(HttpServletResponse response) {
// 清除Cookie
response.setHeader("Set-Cookie", "Authorization=; HttpOnly; Max-Age=0; Path=/; SameSite=Strict");
return Result.success("注销成功");
}
@GetMapping("/register/identifyingCode")
public Result<?> registerIdentifyingCode(String email) throws MessagingException {
var captcha = identifyingCode.getIdentifyingCode(email, IdentifyingCode.IdentifyingCodeType.REGISTER, 15 * 60);
if (captcha == null) {
return Result.error("验证码请求过于频繁,请稍后再试");
}
String mailTemplate = this.mailTemplate;
String tip = "您正在进行注册操作,这是您验证帐户所需的令牌验证码";
String expireTime = "15";
mailTemplate = mailTemplate.replace("{tip}", tip);
mailTemplate = mailTemplate.replace("{Captcha}", captcha);
mailTemplate = mailTemplate.replace("{ExpireTime}", expireTime);
mailUtil.sendHtmlEmail(
email,
"Lichx's WebClass",
mailTemplate);
return Result.success("验证码已发送到您的邮箱,请注意查收");
}
@PostMapping("/register")
public Result<?> register(@RequestBody RegisterDTO registerDTO) {
var user = new User();
user.setId(userService.getUserLastId());
user.setName(registerDTO.getUsername());
user.setPassword(passwordUtil.encrypt(registerDTO.getPassword()));
user.setEmail(registerDTO.getEmail());
user.setBirth(LocalDate.parse(registerDTO.getBirth(), DateTimeFormatter.ISO_LOCAL_DATE).atStartOfDay());
user.setAvatarURL(registerDTO.getAvatarFile());
if (!identifyingCode.testIdentifyingCode(registerDTO.getEmail(),
IdentifyingCode.IdentifyingCodeType.REGISTER,
registerDTO.getIdentifyingCode())) {
return Result.error("验证码错误或已过期");
}
if (userService.getUserByUserName(user.getName()) != null) {
return Result.error("用户名已存在");
}
userService.insertUser(user);
return Result.success("用户已注册");
}
@GetMapping("/passwordReset/identifyingCode")
public Result<?> resetIdentifyingCode(String usernameOrEmail) throws MessagingException {
var email = usernameOrEmail.contains("@") ? usernameOrEmail : userService.getUserByUserName(usernameOrEmail).getEmail();
var captcha = identifyingCode.getIdentifyingCode(email, IdentifyingCode.IdentifyingCodeType.PASSWORD_RESET, 15 * 60);
if (captcha == null) {
return Result.error("验证码请求过于频繁,请稍后再试");
}
String mailTemplate = this.mailTemplate;
String tip = "您正在进行密码重置操作,这是您验证帐户所需的令牌验证码";
String expireTime = "15";
mailTemplate = mailTemplate.replace("{tip}", tip);
mailTemplate = mailTemplate.replace("{Captcha}", captcha);
mailTemplate = mailTemplate.replace("{ExpireTime}", expireTime);
mailUtil.sendHtmlEmail(
email,
"Lichx's WebClass",
mailTemplate);
return Result.success("验证码已发送到您的邮箱,请注意查收");
}
@PostMapping("/passwordReset")
public Result<?> passwordReset(@RequestBody PasswordResetDTO passwordResetDTO) {
System.out.println();
String usernameOrEmail = passwordResetDTO.getUsernameOrEmail();
String password = passwordResetDTO.getPassword();
var user = usernameOrEmail.contains("@")?userService.getUserByEmail(usernameOrEmail) : userService.getUserByUserName(usernameOrEmail);
if (!identifyingCode.testIdentifyingCode(user.getEmail(),
IdentifyingCode.IdentifyingCodeType.PASSWORD_RESET,
passwordResetDTO.getIdentifyingCode())) {
return Result.error("验证码错误或已过期");
}
userService.changeUserPasswordByUserName(user.getName(), passwordUtil.encrypt(password));
return Result.success("密码已重置");
}
}

View File

@ -0,0 +1,71 @@
package top.lichx.webclassbackend.controller;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.lichx.webclassbackend.pojo.vo.VisibleUserInfoVO;
import top.lichx.webclassbackend.result.Result;
import top.lichx.webclassbackend.service.UserService;
import top.lichx.webclassbackend.util.COSIO;
import top.lichx.webclassbackend.util.JwtUtil;
@RestController
@RequestMapping("/user")
public class User {
@Resource
private UserService userService;
@Resource
private COSIO cosio;
@GetMapping("/info")
public Result<?> getUserInfo(HttpServletRequest request) {
String cookieHeader = request.getHeader("Cookie");
String token = null;
if (cookieHeader != null) {
String[] cookies = cookieHeader.split("; ");
for (String cookie : cookies) {
if (cookie.startsWith("Authorization=")) {
token = cookie.substring("Authorization=".length());
break;
}
}
}
token = token.substring(7); // 去掉 "Bearer " 前缀
String username = JwtUtil.parseToken(token);
var user = userService.getUserByUserName(username);
if (user == null)
return Result.error("用户不存在");
var returnVO = new VisibleUserInfoVO();
returnVO.setUserId("" + user.getId());
returnVO.setUserName(user.getName());
returnVO.setUserEmail(user.getEmail());
returnVO.setUserAvatar(cosio.GetFileLink(user.getAvatarURL()));
return Result.success("", returnVO);
}
@GetMapping("/avatar")
public Result<?> getUserAvatar(HttpServletRequest request) {
String cookieHeader = request.getHeader("Cookie");
String token = null;
if (cookieHeader != null) {
String[] cookies = cookieHeader.split("; ");
for (String cookie : cookies) {
if (cookie.startsWith("Authorization=")) {
token = cookie.substring("Authorization=".length());
break;
}
}
}
token = token.substring(7); // 去掉 "Bearer " 前缀
String username = JwtUtil.parseToken(token);
var user = userService.getUserByUserName(username);
if (user == null)
return Result.error("用户不存在");
return Result.success("", cosio.GetFileLink(user.getAvatarURL()));
}
}

View File

@ -0,0 +1,67 @@
package top.lichx.webclassbackend.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;
import top.lichx.webclassbackend.pojo.dto.UserManagerDTO;
import top.lichx.webclassbackend.pojo.entity.UserManagerInfo;
import top.lichx.webclassbackend.result.Result;
import top.lichx.webclassbackend.service.UserManagerService;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@RestController
@RequestMapping("/userManager")
public class UserManager {
@Resource
private UserManagerService userManagerService;
@PostMapping("/addUser")
public Result<?> addUser(@RequestBody UserManagerDTO userManagerDTO)
{
var info = new UserManagerInfo();
info.setId(userManagerService.getUserManagerInfoLastId());
info.setName(userManagerDTO.getName());
info.setDate(LocalDate.parse(userManagerDTO.getDate(), DateTimeFormatter.ISO_LOCAL_DATE));
info.setProvince(userManagerDTO.getProvince());
info.setCity(userManagerDTO.getCity());
info.setAddress(userManagerDTO.getAddress());
info.setZip(userManagerDTO.getZip());
userManagerService.insertUserManagerInfo(info);
return Result.success("添加用户成功");
}
@GetMapping("/page")
public Result<IPage<UserManagerInfo>> getUserManagerInfoPage(@RequestParam int page, @RequestParam int size, @RequestParam String situation) {
return Result.success("",userManagerService.getUserManagerInfoPage(page, size, situation));
}
@PostMapping("/updateUser")
public Result<?>updateUser(@RequestBody UserManagerDTO userManagerDTO)
{
var info = new UserManagerInfo();
info.setId(userManagerDTO.getId());
info.setName(userManagerDTO.getName());
info.setDate(LocalDate.parse(userManagerDTO.getDate(), DateTimeFormatter.ISO_LOCAL_DATE));
info.setProvince(userManagerDTO.getProvince());
info.setCity(userManagerDTO.getCity());
info.setAddress(userManagerDTO.getAddress());
info.setZip(userManagerDTO.getZip());
userManagerService.updateUserManagerInfo(info);
return Result.success("更新用户成功");
}
@GetMapping("deleteUser")
public Result<?> deleteUser(@RequestParam Integer id)
{
userManagerService.deleteUserManagerInfo(id);
return Result.success("删除用户成功");
}
@GetMapping("total")
public Result<Integer> total(@RequestParam String situation)
{
return Result.success("", userManagerService.getTotal(situation));
}
}

View File

@ -0,0 +1,24 @@
package top.lichx.webclassbackend.exception;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import top.lichx.webclassbackend.result.Result;
@RestControllerAdvice
public class GlobalExceptionHandler {
// // 处理空指针异常
// @ExceptionHandler(NullPointerException.class)
// public Result<?> handleNullPointerException(NullPointerException ex) {
// ex.printStackTrace();
// return Result.error("空指针异常,请检查代码!");
// }
// 处理其他异常
@ExceptionHandler(Exception.class)
public Result<?> handleException(Exception ex) {
ex.printStackTrace();
return Result.error("服务器内部错误:" + ex.getMessage());
}
}

View File

@ -0,0 +1,43 @@
package top.lichx.webclassbackend.interceptor;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import top.lichx.webclassbackend.util.JwtUtil;
@Component
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String cookieHeader = request.getHeader("Cookie");
String token = null;
if (cookieHeader != null) {
String[] cookies = cookieHeader.split("; ");
for (String cookie : cookies) {
if (cookie.startsWith("Authorization=")) {
token = cookie.substring("Authorization=".length());
break;
}
}
}
log.info("token:{}, URI:{}", token, request.getRequestURI());
if (token == null || !token.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
token = token.substring(7); // 去掉 "Bearer " 前缀
String username = JwtUtil.parseToken(token);
if (username == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
log.error("Token解析失败");
return false;
}
request.setAttribute("username", username);
log.info("用户 {} 访问了 {}", username, request.getRequestURI());
return true;
}
}

View File

@ -0,0 +1,7 @@
package top.lichx.webclassbackend.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import top.lichx.webclassbackend.pojo.entity.UserManagerInfo;
@Mapper
public interface userManagerInfoMapper extends BaseMapper<UserManagerInfo> { }

View File

@ -0,0 +1,9 @@
package top.lichx.webclassbackend.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import top.lichx.webclassbackend.pojo.entity.User;
@Mapper
public interface userMapper extends BaseMapper<User> { }

View File

@ -0,0 +1,9 @@
package top.lichx.webclassbackend.pojo.dto;
import lombok.Data;
@Data
public class In {
Long id;
String test;
}

View File

@ -0,0 +1,10 @@
package top.lichx.webclassbackend.pojo.dto;
import lombok.Data;
@Data
public class LoginDTO {
private String usernameOrEmail;
private String password;
}

View File

@ -0,0 +1,10 @@
package top.lichx.webclassbackend.pojo.dto;
import lombok.Data;
@Data
public class PasswordResetDTO {
private String usernameOrEmail;
private String identifyingCode;
private String password;
}

View File

@ -0,0 +1,8 @@
package top.lichx.webclassbackend.pojo.dto;
import lombok.Data;
@Data
public class RegisterCodeDTO {
private String email;
}

View File

@ -0,0 +1,13 @@
package top.lichx.webclassbackend.pojo.dto;
import lombok.Data;
@Data
public class RegisterDTO {
private String username;
private String password;
private String email;
private String identifyingCode;
private String birth;
private String avatarFile;
}

View File

@ -0,0 +1,16 @@
package top.lichx.webclassbackend.pojo.dto;
import lombok.Data;
import java.time.LocalDate;
@Data
public class UserManagerDTO {
private Integer id;
private String name;
private String date;
private String province;
private String city;
private String address;
private String zip;
}

View File

@ -0,0 +1,23 @@
package top.lichx.webclassbackend.pojo.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("userT")
public class User {
Integer id;
String name;
String password;
String email;
LocalDateTime birth;
@TableField("avatarURL")
String avatarURL;
}

View File

@ -0,0 +1,22 @@
package top.lichx.webclassbackend.pojo.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("User")
public class UserManagerInfo {
Integer id;
String name;
LocalDate date;
String province;
String city;
String address;
String zip;
}

View File

@ -0,0 +1,14 @@
package top.lichx.webclassbackend.pojo.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.net.URL;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AvatarInfo {
private String avatarFilePath;
private URL avatarFileURL;
}

View File

@ -0,0 +1,17 @@
package top.lichx.webclassbackend.pojo.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.net.URL;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class VisibleUserInfoVO {
private String userId;
private String userName;
private String userEmail;
private URL userAvatar;
}

View File

@ -0,0 +1,50 @@
package top.lichx.webclassbackend.result;
import lombok.Data;
import java.io.Serializable;
@Data
public class Result<T> implements Serializable {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success() {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.data = null;
return result;
}
public static <T> Result<T> success(String msg) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage(msg);
result.data = null;
return result;
}
public static <T> Result<T> success(String msg, T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage(msg);
result.setData(data);
return result;
}
public static Result<Object> error() {
Result<Object> result = new Result<>();
result.setCode(500);
result.setMessage("error");
return result;
}
public static Result<Object> error(String msg) {
Result<Object> result = new Result<>();
result.setCode(500);
result.setMessage(msg);
return result;
}
}

View File

@ -0,0 +1,13 @@
package top.lichx.webclassbackend.service;
import jakarta.mail.MessagingException;
public interface IdentifyingCode {
String getIdentifyingCode(String email, IdentifyingCodeType type, long expireTimeInSeconds);
boolean testIdentifyingCode(String email, IdentifyingCodeType type, String code );
enum IdentifyingCodeType {
REGISTER,
PASSWORD_RESET
}
}

View File

@ -0,0 +1,5 @@
package top.lichx.webclassbackend.service;
public interface Test {
void test();
}

View File

@ -0,0 +1,14 @@
package top.lichx.webclassbackend.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import top.lichx.webclassbackend.pojo.entity.UserManagerInfo;
public interface UserManagerService {
public void insertUserManagerInfo(UserManagerInfo userManagerInfo);
public void updateUserManagerInfo(UserManagerInfo userManagerInfo);
public void deleteUserManagerInfo(Integer id);
public IPage<UserManagerInfo> getUserManagerInfoPage(int page, int size, String situation);
public Integer getUserManagerInfoLastId();
public Integer getTotal(String situation);
}

View File

@ -0,0 +1,13 @@
package top.lichx.webclassbackend.service;
import top.lichx.webclassbackend.pojo.entity.User;
public interface UserService {
public void insertUser(User user);
public User getUserByUserName(String userName);
public void changeUserPasswordByUserName(String userName, String password);
public User getUserByEmail(String email);
public Integer getUserLastId();
public Integer getTotal();
}

View File

@ -0,0 +1,88 @@
package top.lichx.webclassbackend.service.impl;
import jakarta.mail.MessagingException;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Service;
import top.lichx.webclassbackend.service.IdentifyingCode;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Random;
@Service
public class IdentifyingCodeImpl implements IdentifyingCode {
Dictionary<String, IdentifyingCodeStructure> data = new Hashtable<>();
@Override
public String getIdentifyingCode(String email, IdentifyingCodeType type, long expireTimeInSeconds) {
dataClear();
var structure = data.get(IdentifyingCodeStructure.getId(email, type));
if (structure != null && System.currentTimeMillis() / 1000 < structure.startTimeInSeconds + 60)
return null;
structure = IdentifyingCodeStructure.createIdentifyingCodeStructure(email, type, expireTimeInSeconds);
data.put(structure.id, structure);
return structure.code;
}
@Override
public boolean testIdentifyingCode(String email, IdentifyingCodeType type, String code) {
dataClear();
var structure = data.get(IdentifyingCodeStructure.getId(email, type));
if (structure == null) {
return false;
}
if (structure.isExpired()) {
data.remove(structure.id);
return false;
}
if (structure.code.equals(code)) {
data.remove(structure.id);
return true;
}
return false;
}
private void dataClear() {
var keys = data.keys();
while (keys.hasMoreElements()) {
var key = keys.nextElement();
var structure = data.get(key);
if (structure.isExpired()) {
data.remove(key);
}
}
}
}
@NoArgsConstructor
class IdentifyingCodeStructure {
public String id;
public String username;
public IdentifyingCode.IdentifyingCodeType type;
public String code;
public long startTimeInSeconds;
public long expireTimeInSeconds;
boolean isExpired() {
long currentTimeInSeconds = System.currentTimeMillis() / 1000;
return currentTimeInSeconds > startTimeInSeconds + expireTimeInSeconds;
}
static String getId(String username, IdentifyingCode.IdentifyingCodeType type) {
return username + type;
}
static IdentifyingCodeStructure createIdentifyingCodeStructure(String email, IdentifyingCode.IdentifyingCodeType type, long expireTimeInSeconds) {
IdentifyingCodeStructure identifyingCodeStructure = new IdentifyingCodeStructure();
identifyingCodeStructure.id = email + type;
identifyingCodeStructure.username = email;
identifyingCodeStructure.type = type;
Random random = new Random();
int randomNumber = 100000 + random.nextInt(900000); // 范围是 100000 999999
identifyingCodeStructure.code = "" + randomNumber;
identifyingCodeStructure.startTimeInSeconds = System.currentTimeMillis() / 1000;
identifyingCodeStructure.expireTimeInSeconds = expireTimeInSeconds;
return identifyingCodeStructure;
}
}

View File

@ -0,0 +1,13 @@
package top.lichx.webclassbackend.service.impl;
import org.springframework.stereotype.Service;
import top.lichx.webclassbackend.service.Test;
@Service
public class TestImpl implements Test {
@Override
public void test() {
}
}

View File

@ -0,0 +1,60 @@
package top.lichx.webclassbackend.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import top.lichx.webclassbackend.mapper.userManagerInfoMapper;
import top.lichx.webclassbackend.pojo.entity.UserManagerInfo;
import top.lichx.webclassbackend.service.UserManagerService;
@Service
public class UserManagerServiceImpl implements UserManagerService {
@Resource
private userManagerInfoMapper userManagerInfoMapper;
public void insertUserManagerInfo(UserManagerInfo userManagerInfo) {
// 插入用户信息
userManagerInfoMapper.insert(userManagerInfo);
}
public void updateUserManagerInfo(UserManagerInfo userManagerInfo) {
// 更新用户信息
userManagerInfoMapper.updateById(userManagerInfo);
}
public void deleteUserManagerInfo(Integer id) {
// 删除用户信息
userManagerInfoMapper.deleteById(id);
}
public IPage<UserManagerInfo> getUserManagerInfoPage(int page, int size, String situation) {
// 使用 MyBatis-Plus 提供的分页功能
Page<UserManagerInfo> pageRequest = new Page<>(page, size);
QueryWrapper<UserManagerInfo> queryWrapper = new QueryWrapper<>();
// 如果 situation 不为空则添加模糊查询条件
if (situation != null && !situation.trim().isEmpty()) {
queryWrapper.like("name", situation);
}
return userManagerInfoMapper.selectPage(pageRequest, queryWrapper);
}
public Integer getUserManagerInfoLastId() {
UserManagerInfo lastRecord = userManagerInfoMapper.selectOne(
new QueryWrapper<UserManagerInfo>().orderByDesc("id").last("LIMIT 1")
);
// 如果记录为空返回 0否则返回最后一条记录的 ID
return lastRecord == null ? 0 : lastRecord.getId() + 1;
}
@Override
public Integer getTotal(String situation) {
QueryWrapper<UserManagerInfo> queryWrapper = new QueryWrapper<>();
if (situation != null && !situation.trim().isEmpty()) {
queryWrapper.like("name", situation);
}
return userManagerInfoMapper.selectCount(queryWrapper).intValue();
}
}

View File

@ -0,0 +1,55 @@
package top.lichx.webclassbackend.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import top.lichx.webclassbackend.mapper.userMapper;
import top.lichx.webclassbackend.pojo.entity.User;
import top.lichx.webclassbackend.pojo.entity.UserManagerInfo;
import top.lichx.webclassbackend.service.UserService;
@Service
public class UserServiceImpl implements UserService {
@Resource
private userMapper webclassMapper;
public void insertUser(User user) {
webclassMapper.insert(user);
}
public User getUserByUserName(String userName) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", userName);
return webclassMapper.selectOne(queryWrapper);
}
public void changeUserPasswordByUserName(String userName, String password) {
User user = getUserByUserName(userName);
if (user != null) {
user.setPassword(password);
webclassMapper.updateById(user);
}
}
@Override
public User getUserByEmail(String email) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("email", email);
return webclassMapper.selectOne(queryWrapper);
}
@Override
public Integer getUserLastId() {
User lastRecord = webclassMapper.selectOne(
new QueryWrapper<User>().orderByDesc("id").last("LIMIT 1")
);
// 如果记录为空返回 0否则返回最后一条记录的 ID
return lastRecord == null ? 0 : lastRecord.getId() + 1;
}
@Override
public Integer getTotal() {
return webclassMapper.selectCount(null).intValue();
}
}

View File

@ -0,0 +1,70 @@
package top.lichx.webclassbackend.util;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.Headers;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.http.HttpMethodName;
import com.qcloud.cos.model.GeneratePresignedUrlRequest;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.model.PutObjectResult;
import com.qcloud.cos.model.ResponseHeaderOverrides;
import com.qcloud.cos.region.Region;
import com.qcloud.cos.utils.DateUtils;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.net.URL;
import java.util.Date;
import java.util.UUID;
@Component
public class COSIO {
private COSClient cosClient;
@Value("${tencent.secretId}")
private String secretId;
@Value("${tencent.secretKey}")
private String secretKey;
@Value("${tencent.bucketName}")
private String bucketName;
@Value("${tencent.baseDIR}")
private String baseDir;
@PostConstruct
public void Init() {
var cred = new BasicCOSCredentials(secretId, secretKey);
var region = new Region("ap-shanghai");
var clientConfig = new ClientConfig(region);
cosClient = new COSClient(cred, clientConfig);
}
public String UploadFile(File file) {
String fileName = UUID.randomUUID().toString();
String key = baseDir + fileName;
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, file);
cosClient.putObject(putObjectRequest);
return fileName;
}
public URL GetFileLink(String fileName) {
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, baseDir + fileName, HttpMethodName.GET);
ResponseHeaderOverrides responseHeaderOverrides = new ResponseHeaderOverrides();
String responseCacheControl = "no-cache";
String cacheExpireStr =
DateUtils.formatRFC822Date(new Date(System.currentTimeMillis() + 10L * 60L * 1000L));
responseHeaderOverrides.setCacheControl(responseCacheControl);
responseHeaderOverrides.setExpires(cacheExpireStr);
request.setResponseHeaders(responseHeaderOverrides);
Date expirationDate = new Date(System.currentTimeMillis() + 10L * 60L * 1000L);
request.setExpiration(expirationDate);
request.putCustomRequestHeader(Headers.HOST, cosClient.getClientConfig().getEndpointBuilder().buildGeneralApiEndpoint(bucketName));
return cosClient.generatePresignedUrl(request);
}
}

View File

@ -0,0 +1,34 @@
package top.lichx.webclassbackend.util;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;
public class JwtUtil {
private static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); // 生成密钥
private static final long EXPIRATION_TIME = 86400000; // 1
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(key)
.compact();
}
public static String parseToken(String token) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
} catch (JwtException e) {
return null; // Token 无效或过期
}
}
}

View File

@ -0,0 +1,49 @@
package top.lichx.webclassbackend.util;
import jakarta.annotation.Resource;
import jakarta.mail.*;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import top.lichx.webclassbackend.config.MailProperties;
import java.util.Properties;
@Component
public class MailUtil {
@Resource
private MailProperties mailProperties;
public void sendHtmlEmail(String to, String subject, String htmlContent) throws MessagingException {
// 配置 SMTP 服务器
Properties props = new Properties();
props.put("mail.smtp.host", mailProperties.getHost());
props.put("mail.smtp.port", String.valueOf(mailProperties.getPort()));
props.put("mail.smtp.auth", String.valueOf(mailProperties.isAuth()));
props.put("mail.smtp.starttls.enable", String.valueOf(mailProperties.isStarttls()));
// 认证信息
String username = mailProperties.getUsername();
String password = mailProperties.getPassword();
// 创建会话
Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
// 创建邮件
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(username));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
message.setSubject(subject);
message.setContent(htmlContent, "text/html; charset=utf-8"); // 设置内容为 HTML 格式
// 发送邮件
Transport.send(message);
}
}

View File

@ -0,0 +1,18 @@
package top.lichx.webclassbackend.util;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Component;
@Component
public class PasswordUtil {
// 加密密码使用 SHA-256
public String encrypt(String rawPassword) {
return DigestUtils.sha256Hex(rawPassword);
}
// 验证密码
public boolean matches(String rawPassword, String encryptedPassword) {
return encrypt(rawPassword).equals(encryptedPassword);
}
}

View File

@ -1 +1,11 @@
spring.application.name=WebClassBackend
# ???????
spring.datasource.url=jdbc:mariadb://localhost:3306/webclass
spring.datasource.username=webclass
spring.datasource.password=123456
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=50MB
spring.servlet.multipart.max-request-size=50MB
p

View File

@ -0,0 +1,3 @@
spring:
profiles:
active: dev