@ -0,0 +1,34 @@
|
||||
@startuml
|
||||
|
||||
' 定义实体类
|
||||
class Student {
|
||||
+Long id
|
||||
+String studentNumber
|
||||
+String name
|
||||
+BigDecimal points
|
||||
}
|
||||
|
||||
class Teacher {
|
||||
+Long id
|
||||
+String username
|
||||
+String password
|
||||
}
|
||||
|
||||
class RollCallSettings {
|
||||
+String rollCallMode
|
||||
+String triggerRandomEvent
|
||||
+String wheelOfFortune
|
||||
}
|
||||
|
||||
class RollCallResponse {
|
||||
+String studentId
|
||||
+String name
|
||||
+BigDecimal points
|
||||
+String message
|
||||
}
|
||||
|
||||
class PointsRequest {
|
||||
+BigDecimal pointsDelta
|
||||
}
|
||||
|
||||
@enduml
|
@ -0,0 +1,170 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.3.3</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>attendance</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>attendance</name>
|
||||
<description>attendance</description>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Spring Boot Starter Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<version>3.3.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Starter -->
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>3.0.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot DevTools -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- MySQL Connector -->
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<version>8.0.33</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Starter Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Starter Test -->
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter-test</artifactId>
|
||||
<version>3.0.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- EasyExcel -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
<version>3.0.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security for password encryption -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT Library -->
|
||||
<!-- JWT Library -->
|
||||
<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>
|
||||
|
||||
<!-- BCrypt for password hashing -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-crypto</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-core</artifactId>
|
||||
<version>9.0.75</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>3.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-core</artifactId>
|
||||
<version>10.1.28</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Compiler Plugin -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<release>17</release> <!-- 设置 release 参数来简化配置 -->
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Spring Boot Maven Plugin -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- Spring Boot Maven Plugin -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>3.3.3</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -0,0 +1,13 @@
|
||||
package com.example.attendance;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class AttendanceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AttendanceApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package com.example.attendance.config;
|
||||
|
||||
//import com.example.attendance.filter.JwtAuthenticationFilter;
|
||||
import com.example.attendance.filter.JwtAuthenticationFilter;
|
||||
import com.example.attendance.service.TeacherService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
private final TeacherService teacherService; // 使用构造函数注入
|
||||
|
||||
@Autowired
|
||||
public SecurityConfig(@Lazy TeacherService teacherService) { // 添加 @Lazy 注解
|
||||
this.teacherService = teacherService;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.authorizeHttpRequests(authz -> authz
|
||||
.requestMatchers(HttpMethod.POST, "/api/teacher/login", "/api/teacher/register").permitAll()
|
||||
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.formLogin(form -> form.disable())
|
||||
.httpBasic(httpBasic -> httpBasic.disable());
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtAuthenticationFilter jwtAuthenticationFilter() {
|
||||
return new JwtAuthenticationFilter(teacherService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(Arrays.asList("*")); // 允许所有来源
|
||||
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||
configuration.setAllowedHeaders(Arrays.asList("*"));
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.example.attendance.controller;
|
||||
|
||||
import com.example.attendance.entity.RollCallResponse;
|
||||
import com.example.attendance.entity.RollCallSettings;
|
||||
import com.example.attendance.service.RollCallService;
|
||||
import com.example.attendance.service.StudentService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/rollcall")
|
||||
public class RollCallController {
|
||||
@Autowired
|
||||
private StudentService studentService;
|
||||
|
||||
@Autowired
|
||||
private RollCallService rollCallService;
|
||||
|
||||
@PostMapping("/start")
|
||||
public RollCallResponse startRollCall(@RequestBody RollCallSettings settings) {
|
||||
// 从数据库获取学生列表
|
||||
var students = studentService.findAll();
|
||||
return rollCallService.startRollCall(students, settings);
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package com.example.attendance.controller;
|
||||
|
||||
import com.example.attendance.entity.Teacher;
|
||||
import com.example.attendance.service.TeacherService;
|
||||
import com.example.attendance.util.JWTUtil;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/teacher")
|
||||
public class TeacherController {
|
||||
|
||||
@Autowired
|
||||
private TeacherService teacherService;
|
||||
|
||||
// 注册接口
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<?> register(@RequestParam String username, @RequestParam String password) {
|
||||
try {
|
||||
teacherService.register(username, password);
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"code", 200,
|
||||
"message", "注册成功",
|
||||
"data", Map.of("username", username)
|
||||
));
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.badRequest().body(Map.of(
|
||||
"code", 400,
|
||||
"message", "注册失败",
|
||||
"error", e.getMessage()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// 登录接口
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<?> login(@RequestParam String username, @RequestParam String password) {
|
||||
try {
|
||||
String token = teacherService.login(username, password);
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"message", "登录成功",
|
||||
"token", token
|
||||
));
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.badRequest().body(Map.of(
|
||||
"message", "登录失败: " + e.getMessage()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前用户信息接口
|
||||
@GetMapping("/current")
|
||||
public ResponseEntity<?> getCurrentUser(@RequestHeader("Authorization") String token) {
|
||||
try {
|
||||
String username = JWTUtil.getUsernameFromToken(token.replace("Bearer ", ""));
|
||||
Teacher teacher = teacherService.findByUsername(username);
|
||||
if (teacher != null) {
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"code", 200,
|
||||
"message", "获取用户信息成功",
|
||||
"data", Map.of("username", teacher.getUsername())
|
||||
));
|
||||
} else {
|
||||
return ResponseEntity.badRequest().body(Map.of(
|
||||
"code", 400,
|
||||
"message", "用户不存在"
|
||||
));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.badRequest().body(Map.of(
|
||||
"code", 400,
|
||||
"message", "获取用户信息失败",
|
||||
"error", e.getMessage()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
public ResponseEntity<?> logout(@RequestHeader("Authorization") String token) {
|
||||
String processedToken = token.replace("Bearer ", "");
|
||||
Claims claims = JWTUtil.extractClaims(processedToken);
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"code", 200,
|
||||
"message", "退出登录成功"
|
||||
));
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.example.attendance.entity;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class PointsRequest {
|
||||
private BigDecimal pointsDelta;
|
||||
|
||||
public BigDecimal getPointsDelta() {
|
||||
return pointsDelta;
|
||||
}
|
||||
|
||||
public void setPointsDelta(BigDecimal pointsDelta) {
|
||||
this.pointsDelta = pointsDelta;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
package com.example.attendance.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public class RollCallResponse {
|
||||
private String studentId;
|
||||
private String name;
|
||||
private BigDecimal points;
|
||||
private String message; // 显示结果信息
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.example.attendance.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public class RollCallSettings {
|
||||
private String rollCallMode; // 点名("点名")或提问("提问")
|
||||
private String triggerRandomEvent; // 触发("触发")或不触发("不触发")随机事件
|
||||
private String wheelOfFortune; // 开启("是")或关闭("否")命运轮盘
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
package com.example.attendance.entity;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public class Student {
|
||||
private Long id;
|
||||
private String studentNumber; // 学号
|
||||
private String name; // 姓名
|
||||
private BigDecimal points; // 积分
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.example.attendance.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public class Teacher {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
|
||||
package com.example.attendance.filter;
|
||||
|
||||
import com.example.attendance.service.TeacherService;
|
||||
import com.example.attendance.util.JWTUtil;
|
||||
import com.example.attendance.entity.Teacher;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final TeacherService teacherService;
|
||||
|
||||
public JwtAuthenticationFilter(TeacherService teacherService) {
|
||||
this.teacherService = teacherService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, jakarta.servlet.FilterChain filterChain) throws jakarta.servlet.ServletException, IOException {
|
||||
String authHeader = request.getHeader("Authorization");
|
||||
|
||||
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
||||
String token = authHeader.substring(7);
|
||||
try {
|
||||
String username = JWTUtil.getUsernameFromToken(token);
|
||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
Teacher teacher = teacherService.findByUsername(username);
|
||||
if (teacher != null && !JWTUtil.isTokenExpired(token)) {
|
||||
UserDetails userDetails = new org.springframework.security.core.userdetails.User(
|
||||
teacher.getUsername(), teacher.getPassword(), new ArrayList<>());
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
|
||||
userDetails, null, userDetails.getAuthorities());
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("无法设置用户认证: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package com.example.attendance.mapper;
|
||||
|
||||
import com.example.attendance.entity.Student;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface StudentMapper {
|
||||
|
||||
@Select("SELECT * FROM students WHERE id = #{id}")
|
||||
Student findById(Long id);
|
||||
|
||||
@Select("SELECT * FROM students WHERE student_number = #{studentNumber}")
|
||||
@Results({
|
||||
@Result(property = "id", column = "id"),
|
||||
@Result(property = "studentNumber", column = "student_number"), // 确保映射
|
||||
@Result(property = "name", column = "name"),
|
||||
@Result(property = "points", column = "points")
|
||||
})
|
||||
Student findByStudentNumber(String studentNumber);
|
||||
|
||||
@Select("SELECT id, student_number, name, points FROM students")
|
||||
@Results({
|
||||
@Result(property = "id", column = "id"),
|
||||
@Result(property = "studentNumber", column = "student_number"),
|
||||
@Result(property = "name", column = "name"),
|
||||
@Result(property = "points", column = "points")
|
||||
})
|
||||
List<Student> findAll();
|
||||
|
||||
@Insert("INSERT INTO students (student_number, name, points) VALUES (#{studentNumber}, #{name}, #{points})")
|
||||
@Options(useGeneratedKeys = true, keyProperty = "id")
|
||||
void save(Student student);
|
||||
|
||||
@Update("UPDATE students SET name = #{name}, points = #{points} WHERE student_number = #{studentNumber}")
|
||||
@Results({
|
||||
@Result(property = "id", column = "id"),
|
||||
@Result(property = "studentNumber", column = "student_number"), // 确保映射
|
||||
@Result(property = "name", column = "name"),
|
||||
@Result(property = "points", column = "points")
|
||||
})
|
||||
void update(Student student);
|
||||
|
||||
@Delete("DELETE FROM students WHERE id = #{id}")
|
||||
void delete(Long id);
|
||||
|
||||
@Insert({
|
||||
"<script>",
|
||||
"INSERT INTO students (student_number, name, points) VALUES ",
|
||||
"<foreach collection='students' item='student' separator=','>",
|
||||
"(#{student.studentNumber}, #{student.name}, #{student.points})",
|
||||
"</foreach>",
|
||||
"</script>"
|
||||
})
|
||||
void saveStudents(List<Student> students);
|
||||
|
||||
@Select("SELECT * FROM students ORDER BY points DESC")
|
||||
@Results({
|
||||
@Result(property = "id", column = "id"),
|
||||
@Result(property = "studentNumber", column = "student_number"), // 确保映射
|
||||
@Result(property = "name", column = "name"),
|
||||
@Result(property = "points", column = "points")
|
||||
})
|
||||
List<Student> findAllStudentsByRanking();
|
||||
/**
|
||||
* 查询所有学生的姓名
|
||||
* @return 学生姓名的字符串数组
|
||||
*/
|
||||
@Select("SELECT name FROM students")
|
||||
String[] findAllStudentNames();
|
||||
|
||||
// 删除所有学生数据
|
||||
@Delete("DELETE FROM students")
|
||||
void deleteAllStudents();
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.example.attendance.mapper;
|
||||
|
||||
import com.example.attendance.entity.Teacher;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
@Mapper
|
||||
public interface TeacherMapper {
|
||||
|
||||
@Insert("INSERT INTO teacher (username, password) VALUES (#{username}, #{password})")
|
||||
void save(Teacher teacher);
|
||||
|
||||
@Select("SELECT * FROM teacher WHERE username = #{username}")
|
||||
Teacher findByUsername(String username);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.example.attendance.service;
|
||||
import com.example.attendance.entity.RollCallResponse;
|
||||
import com.example.attendance.entity.RollCallSettings;
|
||||
import com.example.attendance.entity.Student;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface RollCallService {
|
||||
RollCallResponse startRollCall(List<Student> students, RollCallSettings settings); // 处理点名逻辑
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.example.attendance.service;
|
||||
|
||||
import com.example.attendance.entity.Student;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
public interface StudentService {
|
||||
void importStudents(MultipartFile file) throws Exception ;
|
||||
void exportStudents(OutputStream outputStream) throws Exception;
|
||||
void adjustPoints(String studentNumber, BigDecimal pointsDelta);
|
||||
List<Student> getStudentRanking();
|
||||
Student findById(Long id);
|
||||
Student findByStudentNumber(String studentNumber);
|
||||
List<Student> findAll();
|
||||
void save(Student student);
|
||||
void update(Student student);
|
||||
void delete(Long id);
|
||||
String[] getAllStudentNames();
|
||||
void deleteAllStudents();
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.example.attendance.service;
|
||||
|
||||
import com.example.attendance.entity.Teacher;
|
||||
|
||||
public interface TeacherService {
|
||||
|
||||
/**
|
||||
* 注册新教师
|
||||
*
|
||||
* @param username 教师的用户名
|
||||
* @param password 教师的密码
|
||||
* @throws Exception 注册过程中可能抛出的异常
|
||||
*/
|
||||
void register(String username, String password) throws Exception;
|
||||
|
||||
/**
|
||||
* 教师登录
|
||||
*
|
||||
* @param username 教师的用户名
|
||||
* @param password 教师的密码
|
||||
* @return 返回 JWT token
|
||||
* @throws Exception 登录失败时抛出的异常
|
||||
*/
|
||||
String login(String username, String password) throws Exception;
|
||||
|
||||
Teacher findByUsername(String username);
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.example.attendance.service.impl;
|
||||
|
||||
import com.example.attendance.mapper.TeacherMapper;
|
||||
import com.example.attendance.entity.Teacher;
|
||||
import com.example.attendance.service.TeacherService;
|
||||
import com.example.attendance.util.JWTUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class TeacherServiceImpl implements TeacherService {
|
||||
|
||||
private final TeacherMapper teacherMapper;
|
||||
private final PasswordEncoder passwordEncoder; // 构造函数注入
|
||||
|
||||
@Autowired
|
||||
public TeacherServiceImpl(TeacherMapper teacherMapper, PasswordEncoder passwordEncoder) {
|
||||
this.teacherMapper = teacherMapper;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(String username, String password) {
|
||||
Teacher teacher = new Teacher();
|
||||
teacher.setUsername(username);
|
||||
teacher.setPassword(passwordEncoder.encode(password)); // 密码加密
|
||||
teacherMapper.save(teacher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String login(String username, String password) {
|
||||
Teacher teacher = teacherMapper.findByUsername(username);
|
||||
if (teacher == null || !passwordEncoder.matches(password, teacher.getPassword())) {
|
||||
throw new RuntimeException("用户名或密码不正确");
|
||||
}
|
||||
// 返回 JWT token
|
||||
return JWTUtil.generateToken(teacher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Teacher findByUsername(String username) {
|
||||
return teacherMapper.findByUsername(username);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package com.example.attendance.util;
|
||||
|
||||
import com.example.attendance.entity.Teacher;
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
|
||||
@Slf4j
|
||||
public class JWTUtil {
|
||||
|
||||
private static final String SECRET_KEY_STRING = "MineVeryLongAndSecureSecretKeyHere1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(SECRET_KEY_STRING.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// 生成 Token
|
||||
public static String generateToken(Teacher teacher) {
|
||||
return Jwts.builder()
|
||||
.setSubject(teacher.getUsername()) // 设置 Token 主题(用户名)
|
||||
.setIssuedAt(new Date()) // 设置 Token 签发时间
|
||||
.setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 1000)) // Token 有效期 1 小时
|
||||
.signWith(SECRET_KEY, SignatureAlgorithm.HS256) // 使用 HS256 签名算法和 SECRET_KEY 签名
|
||||
.compact();
|
||||
}
|
||||
|
||||
public static Claims extractClaims(String token) {
|
||||
try {
|
||||
return Jwts.parser()
|
||||
.setSigningKey(SECRET_KEY)
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
} catch (io.jsonwebtoken.io.DecodingException e) {
|
||||
log.error("JWT解码失败: {}", e.getMessage());
|
||||
throw new RuntimeException("无效的JWT token: 解码失败", e);
|
||||
} catch (io.jsonwebtoken.security.SecurityException | io.jsonwebtoken.MalformedJwtException e) {
|
||||
log.error("无效的JWT签名: {}", e.getMessage());
|
||||
throw new RuntimeException("无效的JWT token: 签名验证失败", e);
|
||||
} catch (io.jsonwebtoken.ExpiredJwtException e) {
|
||||
log.error("JWT token已过期: {}", e.getMessage());
|
||||
throw new RuntimeException("JWT token已过期", e);
|
||||
} catch (io.jsonwebtoken.UnsupportedJwtException e) {
|
||||
log.error("不支持的JWT token: {}", e.getMessage());
|
||||
throw new RuntimeException("不支持的JWT token", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("JWT claims字符串为空: {}", e.getMessage());
|
||||
throw new RuntimeException("JWT claims字符串为空", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 从 Token 中获取用户名
|
||||
public static String getUsernameFromToken(String token) {
|
||||
return extractClaims(token).getSubject();
|
||||
}
|
||||
|
||||
// 判断 Token 是否过期
|
||||
public static boolean isTokenExpired(String token) {
|
||||
return extractClaims(token).getExpiration().before(new Date());
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
server.port=8080
|
||||
server.address=0.0.0.0
|
||||
spring.application.name=attendance
|
||||
spring.datasource.url=jdbc:mysql://localhost:3306/roll_call_system
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=123456789jk
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
# JWT ???????????
|
||||
jwt.expiration=6048000
|
||||
# JWT ?????
|
||||
jwt.tokenHead=Bearer
|
||||
mybatis.configuration.map-underscore-to-camel-case=true
|
||||
|
After Width: | Height: | Size: 27 MiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 134 KiB |
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/点名.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>点点小助手</title>
|
||||
<script type="module" crossorigin src="/assets/index-Cb5FnDWH.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-B1hDzaLh.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
After Width: | Height: | Size: 649 KiB |
After Width: | Height: | Size: 1.9 KiB |
@ -0,0 +1,119 @@
|
||||
package com.example.attendance;
|
||||
|
||||
import com.example.attendance.entity.RollCallResponse;
|
||||
import com.example.attendance.entity.RollCallSettings;
|
||||
import com.example.attendance.entity.Student;
|
||||
import com.example.attendance.service.impl.RollCallServiceImpl;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class RollCallServiceImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private RollCallServiceImpl rollCallService;
|
||||
|
||||
private List<Student> students;
|
||||
private RollCallSettings settings;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
|
||||
// 模拟学生列表
|
||||
students = Arrays.asList(
|
||||
createStudent("1", "Alice", 50),
|
||||
createStudent("2", "Bob", 90),
|
||||
createStudent("3", "Charlie", 30)
|
||||
);
|
||||
|
||||
// 初始化点名设置
|
||||
settings = new RollCallSettings();
|
||||
settings.setRollCallMode("点名");
|
||||
settings.setTriggerRandomEvent("不触发");
|
||||
settings.setWheelOfFortune("否");
|
||||
}
|
||||
|
||||
// 测试正常点名模式
|
||||
@Test
|
||||
void testStartRollCall_NormalMode() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
RollCallResponse response = rollCallService.startRollCall(students, settings);
|
||||
|
||||
// 控制台输出
|
||||
System.out.println("Test: Normal Mode");
|
||||
System.out.println("Student ID: " + response.getStudentId());
|
||||
System.out.println("Message: " + response.getMessage());
|
||||
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getStudentId());
|
||||
assertTrue(response.getMessage().contains("没有触发随机事件"));
|
||||
}
|
||||
}
|
||||
|
||||
// 测试提问模式
|
||||
@Test
|
||||
void testStartRollCall_QuestionMode() {
|
||||
settings.setRollCallMode("提问"); // 设置为提问模式
|
||||
RollCallResponse response = rollCallService.startRollCall(students, settings);
|
||||
|
||||
// 控制台输出
|
||||
System.out.println("Test: Question Mode");
|
||||
System.out.println("Student ID: " + response.getStudentId());
|
||||
System.out.println("Message: " + response.getMessage());
|
||||
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getStudentId());
|
||||
assertTrue(response.getMessage().contains("没有触发随机事件"));
|
||||
}
|
||||
|
||||
// 测试命运轮盘模式
|
||||
@Test
|
||||
void testStartRollCall_WheelOfFortune() {
|
||||
settings.setWheelOfFortune("是"); // 开启命运轮盘
|
||||
|
||||
RollCallResponse response = rollCallService.startRollCall(students, settings);
|
||||
|
||||
// 控制台输出
|
||||
System.out.println("Test: Wheel of Fortune Mode");
|
||||
System.out.println("Student ID: " + response.getStudentId());
|
||||
System.out.println("Message: " + response.getMessage());
|
||||
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getStudentId());
|
||||
assertTrue(response.getMessage().contains("命运轮盘"));
|
||||
}
|
||||
|
||||
// 测试触发随机事件
|
||||
@Test
|
||||
void testStartRollCall_TriggerRandomEvent() {
|
||||
settings.setTriggerRandomEvent("触发"); // 触发随机事件
|
||||
|
||||
RollCallResponse response = rollCallService.startRollCall(students, settings);
|
||||
|
||||
// 控制台输出
|
||||
System.out.println("Test: Random Event Triggered");
|
||||
System.out.println("Student ID: " + response.getStudentId());
|
||||
System.out.println("Message: " + response.getMessage());
|
||||
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getStudentId());
|
||||
assertTrue(response.getMessage().contains("触发了随机事件"));
|
||||
}
|
||||
|
||||
// 辅助方法,创建学生
|
||||
private Student createStudent(String id, String name, int points) {
|
||||
Student student = new Student();
|
||||
student.setStudentNumber(id);
|
||||
student.setName(name);
|
||||
student.setPoints(BigDecimal.valueOf(points));
|
||||
return student;
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
package com.example.attendance;
|
After Width: | Height: | Size: 27 MiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 134 KiB |
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/点名.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>点点小助手</title>
|
||||
<script type="module" crossorigin src="/assets/index-Cb5FnDWH.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-B1hDzaLh.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
After Width: | Height: | Size: 649 KiB |
After Width: | Height: | Size: 1.9 KiB |
@ -1,13 +0,0 @@
|
||||
package com.example.attendance;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class AttendanceApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package com.example.attendance;
|
||||
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
public class PasswordEncryptor {
|
||||
public static void main(String[] args) {
|
||||
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
|
||||
// 这里使用你希望的明文密码
|
||||
String rawPassword1 = "123456";
|
||||
String rawPassword2 = "654321";
|
||||
String rawPassword3 = "admin123";
|
||||
|
||||
// 加密密码
|
||||
String encodedPassword1 = passwordEncoder.encode(rawPassword1);
|
||||
String encodedPassword2 = passwordEncoder.encode(rawPassword2);
|
||||
String encodedPassword3 = passwordEncoder.encode(rawPassword3);
|
||||
|
||||
// 打印加密后的密码
|
||||
System.out.println("teacher1 密码: " + encodedPassword1);
|
||||
System.out.println("teacher2 密码: " + encodedPassword2);
|
||||
System.out.println("teacher3 密码: " + encodedPassword3);
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
package com.example.attendance;
|
||||
|
||||
import com.example.attendance.entity.RollCallResponse;
|
||||
import com.example.attendance.entity.RollCallSettings;
|
||||
import com.example.attendance.entity.Student;
|
||||
import com.example.attendance.service.impl.RollCallServiceImpl;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class RollCallServiceImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private RollCallServiceImpl rollCallService;
|
||||
|
||||
private List<Student> students;
|
||||
private RollCallSettings settings;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
|
||||
// 模拟学生列表
|
||||
students = Arrays.asList(
|
||||
createStudent("1", "Alice", 50),
|
||||
createStudent("2", "Bob", 90),
|
||||
createStudent("3", "Charlie", 30)
|
||||
);
|
||||
|
||||
// 初始化点名设置
|
||||
settings = new RollCallSettings();
|
||||
settings.setRollCall(true);
|
||||
settings.setTriggerRandomEvent(false);
|
||||
settings.setWheelOfFortune(false);
|
||||
}
|
||||
|
||||
// 测试正常点名模式
|
||||
@Test
|
||||
void testStartRollCall_NormalMode() {
|
||||
RollCallResponse response = rollCallService.startRollCall(students, settings);
|
||||
|
||||
// 控制台输出
|
||||
System.out.println("Test: Normal Mode");
|
||||
System.out.println("Student ID: " + response.getStudentId());
|
||||
System.out.println("Message: " + response.getMessage());
|
||||
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getStudentId());
|
||||
assertTrue(response.getMessage().contains("没有触发随机事件"));
|
||||
}
|
||||
|
||||
// 测试提问模式
|
||||
@Test
|
||||
void testStartRollCall_QuestionMode() {
|
||||
settings.setRollCall(false); // 设置为提问模式
|
||||
RollCallResponse response = rollCallService.startRollCall(students, settings);
|
||||
|
||||
// 控制台输出
|
||||
System.out.println("Test: Question Mode");
|
||||
System.out.println("Student ID: " + response.getStudentId());
|
||||
System.out.println("Message: " + response.getMessage());
|
||||
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getStudentId());
|
||||
assertTrue(response.getMessage().contains("没有触发随机事件"));
|
||||
}
|
||||
|
||||
// 测试命运轮盘模式
|
||||
@Test
|
||||
void testStartRollCall_WheelOfFortune() {
|
||||
settings.setWheelOfFortune(true); // 开启命运轮盘
|
||||
|
||||
RollCallResponse response = rollCallService.startRollCall(students, settings);
|
||||
|
||||
// 控制台输出
|
||||
System.out.println("Test: Wheel of Fortune Mode");
|
||||
System.out.println("Student ID: " + response.getStudentId());
|
||||
System.out.println("Message: " + response.getMessage());
|
||||
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getStudentId());
|
||||
assertTrue(response.getMessage().contains("命运轮盘"));
|
||||
}
|
||||
|
||||
// 测试触发随机事件
|
||||
@Test
|
||||
void testStartRollCall_TriggerRandomEvent() {
|
||||
settings.setTriggerRandomEvent(true); // 触发随机事件
|
||||
|
||||
RollCallResponse response = rollCallService.startRollCall(students, settings);
|
||||
|
||||
// 控制台输出
|
||||
System.out.println("Test: Random Event Triggered");
|
||||
System.out.println("Student ID: " + response.getStudentId());
|
||||
System.out.println("Message: " + response.getMessage());
|
||||
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getStudentId());
|
||||
assertTrue(response.getMessage().contains("触发了随机事件"));
|
||||
}
|
||||
|
||||
// 辅助方法,创建学生
|
||||
private Student createStudent(String id, String name, int points) {
|
||||
Student student = new Student();
|
||||
student.setStudentNumber(id);
|
||||
student.setName(name);
|
||||
student.setPoints(BigDecimal.valueOf(points));
|
||||
return student;
|
||||
}
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
package com.example.attendance;
|
||||
|
||||
import com.example.attendance.entity.Student;
|
||||
import com.example.attendance.mapper.StudentMapper;
|
||||
import com.example.attendance.service.impl.StudentServiceImpl;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class StudentServiceImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private StudentServiceImpl studentService;
|
||||
|
||||
@Mock
|
||||
private StudentMapper studentMapper;
|
||||
|
||||
@Mock
|
||||
private MultipartFile file;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testImportStudents() throws Exception {
|
||||
// 模拟 Excel 文件内容
|
||||
XSSFWorkbook workbook = new XSSFWorkbook();
|
||||
var sheet = workbook.createSheet("Sheet 1");
|
||||
var header = sheet.createRow(0);
|
||||
header.createCell(0).setCellValue("Student Number");
|
||||
header.createCell(1).setCellValue("Name");
|
||||
header.createCell(2).setCellValue("Points");
|
||||
|
||||
var row1 = sheet.createRow(1);
|
||||
row1.createCell(0).setCellValue("1");
|
||||
row1.createCell(1).setCellValue("Alice");
|
||||
row1.createCell(2).setCellValue(50);
|
||||
|
||||
var row2 = sheet.createRow(2);
|
||||
row2.createCell(0).setCellValue("2");
|
||||
row2.createCell(1).setCellValue("Bob");
|
||||
row2.createCell(2).setCellValue(80);
|
||||
|
||||
// 将 workbook 写入 ByteArrayOutputStream
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
workbook.write(outputStream);
|
||||
workbook.close(); // 关闭 workbook
|
||||
|
||||
// 将 ByteArrayOutputStream 转换为 InputStream
|
||||
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
|
||||
when(file.getInputStream()).thenReturn(inputStream);
|
||||
|
||||
// 调用 importStudents 方法
|
||||
studentService.importStudents(file);
|
||||
|
||||
// 验证是否保存了学生数据
|
||||
verify(studentMapper, times(1)).saveStudents(anyList());
|
||||
}
|
||||
|
||||
// 测试调整积分
|
||||
@Test
|
||||
void testAdjustPoints() {
|
||||
// 模拟学生数据
|
||||
Student student = new Student();
|
||||
student.setStudentNumber("1");
|
||||
student.setPoints(BigDecimal.valueOf(50));
|
||||
|
||||
when(studentMapper.findByStudentNumber("1")).thenReturn(student);
|
||||
|
||||
// 调用 adjustPoints 方法
|
||||
studentService.adjustPoints("1", BigDecimal.valueOf(10));
|
||||
|
||||
// 验证是否更新了学生数据
|
||||
assertEquals(BigDecimal.valueOf(60), student.getPoints());
|
||||
verify(studentMapper, times(1)).update(student);
|
||||
}
|
||||
|
||||
// 测试获取学生排行榜
|
||||
@Test
|
||||
void testGetStudentRanking() {
|
||||
List<Student> mockStudents = new ArrayList<>();
|
||||
Student student1 = new Student();
|
||||
student1.setName("Alice");
|
||||
student1.setPoints(BigDecimal.valueOf(100));
|
||||
|
||||
Student student2 = new Student();
|
||||
student2.setName("Bob");
|
||||
student2.setPoints(BigDecimal.valueOf(90));
|
||||
|
||||
mockStudents.add(student1);
|
||||
mockStudents.add(student2);
|
||||
|
||||
when(studentMapper.findStudentsByRanking(0, 2)).thenReturn(mockStudents);
|
||||
|
||||
// 调用 getStudentRanking 方法
|
||||
List<Student> ranking = studentService.getStudentRanking(0, 2);
|
||||
|
||||
// 验证返回结果是否正确
|
||||
assertEquals(2, ranking.size());
|
||||
assertEquals("Alice", ranking.get(0).getName());
|
||||
assertEquals("Bob", ranking.get(1).getName());
|
||||
}
|
||||
|
||||
// 测试根据学生编号查找
|
||||
@Test
|
||||
void testFindByStudentNumber() {
|
||||
Student student = new Student();
|
||||
student.setStudentNumber("1");
|
||||
student.setName("Alice");
|
||||
|
||||
when(studentMapper.findByStudentNumber("1")).thenReturn(student);
|
||||
|
||||
// 调用 findByStudentNumber 方法
|
||||
Student result = studentService.findByStudentNumber("1");
|
||||
|
||||
// 验证结果
|
||||
assertNotNull(result);
|
||||
assertEquals("Alice", result.getName());
|
||||
}
|
||||
|
||||
// 测试保存学生
|
||||
@Test
|
||||
void testSave() {
|
||||
Student student = new Student();
|
||||
student.setName("Alice");
|
||||
|
||||
// 调用 save 方法
|
||||
studentService.save(student);
|
||||
|
||||
// 验证是否保存了学生
|
||||
verify(studentMapper, times(1)).save(student);
|
||||
}
|
||||
|
||||
// 测试删除学生
|
||||
@Test
|
||||
void testDelete() {
|
||||
Long id = 1L;
|
||||
|
||||
// 调用 delete 方法
|
||||
studentService.delete(id);
|
||||
|
||||
// 验证是否删除了学生
|
||||
verify(studentMapper, times(1)).delete(id);
|
||||
}
|
||||
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
package com.example.attendance;
|
||||
|
||||
import com.example.attendance.entity.Teacher;
|
||||
import com.example.attendance.mapper.TeacherMapper;
|
||||
import com.example.attendance.service.impl.TeacherServiceImpl;
|
||||
import com.example.attendance.util.JWTUtil;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class TeacherServiceImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private TeacherServiceImpl teacherService; // 使用 @InjectMocks 注入测试的服务类
|
||||
|
||||
@Mock
|
||||
private TeacherMapper teacherMapper; // 模拟 TeacherMapper
|
||||
|
||||
@Mock
|
||||
private PasswordEncoder passwordEncoder; // 模拟 PasswordEncoder
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this); // 初始化 mocks
|
||||
}
|
||||
|
||||
// 测试注册功能
|
||||
@Test
|
||||
void testRegister() {
|
||||
// 模拟加密的密码
|
||||
String rawPassword = "password123";
|
||||
String encodedPassword = "encodedPassword123";
|
||||
|
||||
when(passwordEncoder.encode(rawPassword)).thenReturn(encodedPassword);
|
||||
|
||||
// 调用注册方法
|
||||
teacherService.register("testUser", rawPassword);
|
||||
|
||||
// 验证 teacherMapper.save 是否被调用,并且密码是加密的
|
||||
verify(teacherMapper, times(1)).save(any(Teacher.class));
|
||||
verify(passwordEncoder, times(1)).encode(rawPassword);
|
||||
}
|
||||
|
||||
// 测试登录功能
|
||||
@Test
|
||||
void testLogin_Success() {
|
||||
// 模拟数据库中保存的教师
|
||||
Teacher teacher = new Teacher();
|
||||
teacher.setUsername("testUser");
|
||||
teacher.setPassword("encodedPassword123");
|
||||
|
||||
// 模拟找到用户,并且密码匹配
|
||||
when(teacherMapper.findByUsername("testUser")).thenReturn(teacher);
|
||||
when(passwordEncoder.matches("password123", "encodedPassword123")).thenReturn(true);
|
||||
|
||||
// 模拟生成的 JWT token
|
||||
String token = "mockedJWTToken";
|
||||
mockStatic(JWTUtil.class); // mock 静态方法
|
||||
when(JWTUtil.generateToken(teacher)).thenReturn(token);
|
||||
|
||||
// 调用登录方法
|
||||
String resultToken = teacherService.login("testUser", "password123");
|
||||
|
||||
// 验证生成的 token 是否正确
|
||||
assertEquals(token, resultToken);
|
||||
|
||||
// 验证方法调用
|
||||
verify(teacherMapper, times(1)).findByUsername("testUser");
|
||||
verify(passwordEncoder, times(1)).matches("password123", "encodedPassword123");
|
||||
}
|
||||
|
||||
// 测试登录失败(用户名不正确)
|
||||
@Test
|
||||
void testLogin_Fail_UserNotFound() {
|
||||
// 模拟未找到用户
|
||||
when(teacherMapper.findByUsername("nonExistentUser")).thenReturn(null);
|
||||
|
||||
// 调用登录方法并捕获异常
|
||||
Exception exception = assertThrows(RuntimeException.class, () -> {
|
||||
teacherService.login("nonExistentUser", "password123");
|
||||
});
|
||||
|
||||
// 验证异常消息
|
||||
assertEquals("用户名或密码不正确", exception.getMessage());
|
||||
|
||||
// 验证 teacherMapper.findByUsername 被调用一次
|
||||
verify(teacherMapper, times(1)).findByUsername("nonExistentUser");
|
||||
}
|
||||
|
||||
// 测试登录失败(密码不匹配)
|
||||
@Test
|
||||
void testLogin_Fail_WrongPassword() {
|
||||
// 模拟找到用户,但密码不匹配
|
||||
Teacher teacher = new Teacher();
|
||||
teacher.setUsername("testUser");
|
||||
teacher.setPassword("encodedPassword123");
|
||||
|
||||
when(teacherMapper.findByUsername("testUser")).thenReturn(teacher);
|
||||
when(passwordEncoder.matches("wrongPassword", "encodedPassword123")).thenReturn(false);
|
||||
|
||||
// 调用登录方法并捕获异常
|
||||
Exception exception = assertThrows(RuntimeException.class, () -> {
|
||||
teacherService.login("testUser", "wrongPassword");
|
||||
});
|
||||
|
||||
// 验证异常消息
|
||||
assertEquals("用户名或密码不正确", exception.getMessage());
|
||||
|
||||
// 验证方法调用
|
||||
verify(teacherMapper, times(1)).findByUsername("testUser");
|
||||
verify(passwordEncoder, times(1)).matches("wrongPassword", "encodedPassword123");
|
||||
}
|
||||
}
|