Compare commits

...

2 Commits

@ -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,159 @@
package com.example.attendance.controller;
import com.example.attendance.entity.PointsRequest;
import com.example.attendance.entity.Student;
import com.example.attendance.service.StudentService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
@RestController
@RequestMapping("/api/students")
public class StudentController {
private static final Logger logger = LoggerFactory.getLogger(StudentController.class);
@Autowired
private StudentService studentService;
//根据id获取学生信息
@GetMapping("/{id}")
public ResponseEntity<Student> getStudentById(@PathVariable Long id) {
Student student = studentService.findById(id);
return ResponseEntity.ok(student);
}
//根据学生编号获取学生信息
@GetMapping("/studentNumber/{studentNumber}")
public ResponseEntity<Student> getStudentByStudentNumber(@PathVariable String studentNumber) {
Student student = studentService.findByStudentNumber(studentNumber);
return ResponseEntity.ok(student);
}
//获取所有学生信息
@GetMapping
public ResponseEntity<List<Student>> getAllStudents() {
List<Student> students = studentService.findAll();
return ResponseEntity.ok(students);
}
/**
*
* @return
*/
@GetMapping("/names")
public ResponseEntity<String[]> getAllStudentNames() {
String[] studentNames = studentService.getAllStudentNames();
return ResponseEntity.ok(studentNames);
}
//添加学生信息
@PostMapping
public ResponseEntity<String> addStudent(@RequestBody Student student) {
studentService.save(student);
return ResponseEntity.ok("Student added successfully");
}
//更新学生信息
@PutMapping("/{studentNumber}")
public ResponseEntity<String> updateStudent(@PathVariable String studentNumber, @RequestBody Student student) {
student.setStudentNumber(studentNumber);
studentService.update(student);
return ResponseEntity.ok("Student updated successfully");
}
//删除学生信息
@DeleteMapping("/{id}")
public ResponseEntity<String> deleteStudent(@PathVariable Long id) {
studentService.delete(id);
return ResponseEntity.ok("Student deleted successfully");
}
/**
*
* @return
*/
@GetMapping("/ranking")
public ResponseEntity<List<Student>> getStudentRanking() {
List<Student> ranking = studentService.getStudentRanking();
return ResponseEntity.ok(ranking);
}
/**
*
* @param studentNumber
* @param pointsRequest
* @return
*/
@PutMapping("/{studentNumber}/adjustPoints")
public ResponseEntity<String> adjustStudentPoints(
@PathVariable String studentNumber,
@RequestBody PointsRequest pointsRequest) {
try {
logger.info("调整的积分: {}", pointsRequest.getPointsDelta());
studentService.adjustPoints(studentNumber, pointsRequest.getPointsDelta());
return ResponseEntity.ok("积分调整成功");
} catch (Exception e) {
logger.error("积分调整失败: ", e);
return ResponseEntity.status(500).body("积分调整失败:" + e.getMessage());
}
}
/**
* Excel
* @param file Excel
* @return
*/
@PostMapping("/import")
public String importStudents(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return "请上传有效的Excel文件";
}
try {
studentService.importStudents(file);
return "学生数据导入成功";
} catch (Exception e) {
return "学生数据导入失败:" + e.getMessage();
}
}
/* 导出并清理学生数据 */
@GetMapping("/export-students")
public ResponseEntity<byte[]> exportStudents() throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
studentService.exportStudents(outputStream); // 导出并清理学生数据
// 设置 HTTP 头信息
HttpHeaders headers = new HttpHeaders();
headers.setContentDispositionFormData("attachment", "students.xlsx");
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 返回 Excel 文件内容作为响应
return ResponseEntity
.ok()
.headers(headers)
.body(outputStream.toByteArray());
} catch (Exception e) {
return ResponseEntity.badRequest().body(null);
} finally {
outputStream.close();
}
}
/**
* Excel
* @return
*/
@GetMapping("/download-template")
public ResponseEntity<String> getTemplateDownloadLink() {
// 返回百度网盘的模板下载链接
String downloadLink = "https://pan.baidu.com/s/1NUukdPo4qUVbM4V9MWTx2g?pwd=1234";
return ResponseEntity.ok(downloadLink);
}
}

@ -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,109 @@
package com.example.attendance.service.impl;
import com.example.attendance.entity.RollCallResponse;
import com.example.attendance.entity.RollCallSettings;
import com.example.attendance.entity.Student;
import com.example.attendance.service.RollCallService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Random;
@Service
public class RollCallServiceImpl implements RollCallService {
@Override
public RollCallResponse startRollCall(List<Student> students, RollCallSettings settings) {
// 1. 根据设定选择点名或提问模式
String mode = "点名".equals(settings.getRollCallMode()) ? "点名" : "提问";
System.out.println("当前模式:" + mode);
// 2. 处理命运轮盘 (所有人概率相等)
if ("是".equals(settings.getWheelOfFortune())) {
return handleWheelOfFortune(students);
}
// 3. 正常点名逻辑,使用权重随机选择学生
Student selectedStudent = selectWeightedRandomStudent(students);
RollCallResponse response = new RollCallResponse();
response.setStudentId(selectedStudent.getStudentNumber());
response.setName(selectedStudent.getName());
response.setPoints(selectedStudent.getPoints());
// 4. 判断是否触发随机事件
if ("触发".equals(settings.getTriggerRandomEvent())) {
response.setMessage("触发了随机事件: " + triggerRandomEvent());
} else {
response.setMessage("没有触发随机事件," + selectedStudent.getName() + " 被点了!");
}
return response;
}
// 权重随机选择学生,积分越高概率越低
private Student selectWeightedRandomStudent(List<Student> students) {
Random random = new Random();
int totalWeight = 0;
// 计算所有学生的总权重,假设权重为 100 - currentPoints
for (Student student : students) {
int weight = 100 - student.getPoints().intValue();
if (weight < 1) {
weight = 1; // 确保权重最低为1
}
totalWeight += weight; // 累计总权重
}
// 如果 totalWeight 为 0 或负数,则无法进行随机选择
if (totalWeight <= 0) {
throw new IllegalArgumentException("总权重必须为正数");
}
// 随机生成一个 0 到 totalWeight 之间的随机数
int randomIndex = random.nextInt(totalWeight);
int currentWeightSum = 0;
// 遍历学生列表,根据累计权重确定选中的学生
for (Student student : students) {
int weight = 100 - student.getPoints().intValue();
if (weight < 1) {
weight = 1;
}
currentWeightSum += weight;
// 当累计权重超过随机数时,选择当前学生
if (currentWeightSum > randomIndex) {
return student;
}
}
// 如果没有选中任何学生,兜底返回第一个学生(理论上不会发生)
return students.get(0);
}
// 命运轮盘处理(所有学生概率相等)
private RollCallResponse handleWheelOfFortune(List<Student> students) {
Random random = new Random();
Student selectedStudent = students.get(random.nextInt(students.size()));
RollCallResponse response = new RollCallResponse();
response.setStudentId(selectedStudent.getStudentNumber());
response.setName(selectedStudent.getName());
response.setPoints(selectedStudent.getPoints());
response.setMessage("命运轮盘: " + selectedStudent.getName() + " 被选中了!");
return response;
}
// 触发随机事件:赌徒事件或倒霉事件
private String triggerRandomEvent() {
Random random = new Random();
int eventType = random.nextInt(2); // 0表示赌徒事件1表示倒霉事件
if (eventType == 0) {
return "赌徒事件--学生可以在回答问题前下注一定数量的积分,如果回答正确,则按赌注倍数获得积分;如果错误,则失去赌注积分"; // 触发赌徒事件
} else {
return "倒霉事件--左手边第3位同学替该同学回答问题加减分命运交到别人手中"; // 触发倒霉事件
}
}
}

@ -0,0 +1,191 @@
package com.example.attendance.service.impl;
import com.example.attendance.entity.Student;
import com.example.attendance.mapper.StudentMapper;
import com.example.attendance.service.StudentService;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
@Override
public void importStudents(MultipartFile file) throws Exception {
List<Student> students = new ArrayList<>();
// 获取上传的 Excel 文件流
try (InputStream inputStream = file.getInputStream();
Workbook workbook = new XSSFWorkbook(inputStream)) {
// 读取第一个工作表
Sheet sheet = workbook.getSheetAt(0);
// 检查文件中是否包含积分列
boolean hasPointsColumn = sheet.getRow(0).getLastCellNum() > 2;
// 遍历每一行,从第二行开始(假设第一行是标题)
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
// 读取学生数据
String studentNumber = getCellValueAsString(row.getCell(0));
String name = getCellValueAsString(row.getCell(1));
// 如果有积分列,读取积分,如果没有积分列或者积分为空,则默认积分为 0
BigDecimal points = BigDecimal.ZERO;
if (hasPointsColumn && row.getCell(2) != null) {
Cell pointsCell = row.getCell(2);
if (pointsCell.getCellType() == CellType.NUMERIC) {
points = new BigDecimal(pointsCell.getNumericCellValue());
} else if (pointsCell.getCellType() == CellType.STRING) {
points = new BigDecimal(pointsCell.getStringCellValue());
}
}
Student student = new Student();
student.setStudentNumber(studentNumber);
student.setName(name);
student.setPoints(points);
students.add(student);
}
}
// 将学生列表保存到数据库
studentMapper.saveStudents(students);
}
// 辅助方法:获取单元格的字符串值
private String getCellValueAsString(Cell cell) {
if (cell == null) {
return "";
}
// 使用 DataFormatter 处理字符串和基本的数字格式问题
DataFormatter formatter = new DataFormatter();
String cellValue = formatter.formatCellValue(cell);
// 额外处理数值型,确保学号不会以科学计数法的形式返回
if (cell.getCellType() == CellType.NUMERIC) {
// 强制将数值转换为不使用科学计数法的字符串
BigDecimal bd = new BigDecimal(cell.getNumericCellValue());
cellValue = bd.toPlainString(); // 使用 toPlainString() 避免科学计数法
}
return cellValue;
}
@Override
public void exportStudents(OutputStream outputStream) throws Exception {
List<Student> students = studentMapper.findAll();
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("Students");
// 创建标题行
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("学号");
headerRow.createCell(1).setCellValue("姓名");
headerRow.createCell(2).setCellValue("积分");
// 填充数据
int rowNum = 1;
for (Student student : students) {
Row row = sheet.createRow(rowNum++);
row.createCell(0).setCellValue(student.getStudentNumber()); // 确保学号不为空
row.createCell(1).setCellValue(student.getName());
row.createCell(2).setCellValue(student.getPoints() != null ? student.getPoints().doubleValue() : 0.0);
}
// 将数据写入输出流
workbook.write(outputStream);
}
deleteAllStudents(); // 导出后删除所有学生
}
/**
*
*
* @param studentNumber
* @param pointsDelta
*/
@Override
public void adjustPoints(String studentNumber, BigDecimal pointsDelta) {
Student student = studentMapper.findByStudentNumber(studentNumber);
if (student == null) {
throw new RuntimeException("未找到该学生");
}
BigDecimal updatedPoints = student.getPoints().add(pointsDelta);
student.setPoints(updatedPoints);
studentMapper.update(student);
}
/**
*
* @return
*/
@Override
public List<Student> getStudentRanking() {
return studentMapper.findAllStudentsByRanking();
}
@Override
public Student findById(Long id) {
return studentMapper.findById(id);
}
@Override
public Student findByStudentNumber(String studentNumber) {
return studentMapper.findByStudentNumber(studentNumber);
}
@Override
public List<Student> findAll() {
return studentMapper.findAll();
}
@Override
public void save(Student student) {
studentMapper.save(student);
}
@Override
public void update(Student student) {
studentMapper.update(student);
}
@Override
public void delete(Long id) {
studentMapper.delete(id);
}
/**
*
* @return
*/
@Override
public String[] getAllStudentNames() {
return studentMapper.findAllStudentNames();
}
@Override
public void deleteAllStudents() {
studentMapper.deleteAllStudents();
}
}

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 KiB

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1727709759865" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6581" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M485.568 906.496c-0.64-0.192-145.088 10.368-209.216-16.256-86.528-35.968-117.184-62.592-117.184-79.488 0-15.936-2.944-98.112 77.248-123.2a4319.36 4319.36 0 0 1 155.264-43.136 25.216 25.216 0 0 0 22.08-24.832V541.888a24.064 24.064 0 0 0-7.68-17.664c-69.248-66.56-97.728-131.456-97.728-243.584 0-53.312 14.656-91.008 39.296-119.744 26.496-31.104 68.032-46.912 123.264-46.912 159.104 0 169.408 128.384 169.408 171.392a291.072 291.072 0 0 1-26.56 122.048c-13.056 28.544-26.624 44.736-26.752 45.12a24.256 24.256 0 0 0 3.776 35.2 26.88 26.88 0 0 0 36.672-3.648c2.56-3.072 65.088-77.696 65.088-198.72 0-65.024-16.832-109.376-52.544-150.72C600.256 88.448 541.76 64 470.976 64 335.808 64 256.256 140.416 256.256 280.704c0 60.864 8.192 104.256 25.024 148.928a340.48 340.48 0 0 0 80.256 122.368v46.784c-36.928 6.72-73.408 15.552-109.312 26.368-39.232 12.16-70.848 25.536-93.824 40.256-34.176 21.632-51.456 117.76-51.456 145.344 0 45.76 48.64 83.968 149.056 125.504 67.712 28.032 213.12 17.664 215.872 18.368l6.848 1.024a25.92 25.92 0 0 0 25.28-18.496 24.704 24.704 0 0 0-18.432-30.656z" p-id="6582" fill="#8755f2"></path><path d="M937.024 515.776a26.624 26.624 0 0 0-37.376 4.16c-9.152 11.456-199.808 266.624-228.224 268.928-27.712 2.432-122.752-90.112-122.752-90.112a26.496 26.496 0 0 0-36.224 4.032 26.56 26.56 0 0 0 1.856 36.48c10.304 9.664 101.056 82.048 123.072 98.304l25.6 18.816c5.12 3.264 10.752 5.376 16.576 4.736a23.68 23.68 0 0 0 17.536-9.152c6.208-7.36 35.52-41.344 35.776-41.792l208.448-257.216a26.56 26.56 0 0 0-4.288-37.184z" p-id="6583" fill="#8755f2"></path></svg>

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;
}
}

@ -47,6 +47,16 @@ public class StudentController {
return ResponseEntity.ok(students);
}
/**
*
* @return
*/
@GetMapping("/names")
public ResponseEntity<String[]> getAllStudentNames() {
String[] studentNames = studentService.getAllStudentNames();
return ResponseEntity.ok(studentNames);
}
//添加学生信息
@PostMapping
public ResponseEntity<String> addStudent(@RequestBody Student student) {

@ -8,8 +8,8 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
@Data
public class RollCallSettings {
private boolean isRollCall; // 点名(true)或提问(false)
private boolean triggerRandomEvent; // 是否触发随机事件
private boolean wheelOfFortune; // 是否开启命运轮盘
private String rollCallMode; // 点名("点名")或提问("提问")
private String triggerRandomEvent; // 触发("触发")或不触发("不触发")随机事件
private String wheelOfFortune; // 开启("是")或关闭("否")命运轮盘
}

@ -46,4 +46,11 @@ public interface StudentMapper {
*/
@Select("SELECT * FROM student ORDER BY points DESC LIMIT #{size} OFFSET #{offset}")
List<Student> findStudentsByRanking(@Param("offset") int offset, @Param("size") int size);
/**
*
* @return
*/
@Select("SELECT name FROM student")
String[] findAllStudentNames();
}

@ -19,4 +19,5 @@ public interface StudentService {
void update(Student student);
void delete(Long id);
String[] getAllStudentNames();
}

@ -6,7 +6,6 @@ import com.example.attendance.entity.Student;
import com.example.attendance.service.RollCallService;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.List;
import java.util.Random;
@ -16,11 +15,11 @@ public class RollCallServiceImpl implements RollCallService {
@Override
public RollCallResponse startRollCall(List<Student> students, RollCallSettings settings) {
// 1. 根据设定选择点名或提问模式
String mode = settings.isRollCall() ? "点名" : "提问";
String mode = "点名".equals(settings.getRollCallMode()) ? "点名" : "提问";
System.out.println("当前模式:" + mode);
// 2. 处理命运轮盘 (所有人概率相等)
if (settings.isWheelOfFortune()) {
if ("是".equals(settings.getWheelOfFortune())) {
return handleWheelOfFortune(students);
}
@ -32,7 +31,7 @@ public class RollCallServiceImpl implements RollCallService {
response.setPoints(selectedStudent.getPoints());
// 4. 判断是否触发随机事件
if (settings.isTriggerRandomEvent()) {
if ("触发".equals(settings.getTriggerRandomEvent())) {
response.setMessage("触发了随机事件: " + triggerRandomEvent());
} else {
response.setMessage("没有触发随机事件," + selectedStudent.getName() + " 被点了!");

@ -98,6 +98,7 @@ public class StudentServiceImpl implements StudentService {
* @param studentNumber
* @param pointsDelta
*/
@Override
public void adjustPoints(String studentNumber, BigDecimal pointsDelta) {
Student student = studentMapper.findByStudentNumber(studentNumber);
if (student == null) {
@ -114,6 +115,7 @@ public class StudentServiceImpl implements StudentService {
* @param size
* @return
*/
@Override
public List<Student> getStudentRanking(int page, int size) {
int offset = page * size;
return studentMapper.findStudentsByRanking(offset, size);
@ -148,4 +150,13 @@ public class StudentServiceImpl implements StudentService {
public void delete(Long id) {
studentMapper.delete(id);
}
/**
*
* @return
*/
@Override
public String[] getAllStudentNames() {
return studentMapper.findAllStudentNames();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 KiB

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1727709759865" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6581" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M485.568 906.496c-0.64-0.192-145.088 10.368-209.216-16.256-86.528-35.968-117.184-62.592-117.184-79.488 0-15.936-2.944-98.112 77.248-123.2a4319.36 4319.36 0 0 1 155.264-43.136 25.216 25.216 0 0 0 22.08-24.832V541.888a24.064 24.064 0 0 0-7.68-17.664c-69.248-66.56-97.728-131.456-97.728-243.584 0-53.312 14.656-91.008 39.296-119.744 26.496-31.104 68.032-46.912 123.264-46.912 159.104 0 169.408 128.384 169.408 171.392a291.072 291.072 0 0 1-26.56 122.048c-13.056 28.544-26.624 44.736-26.752 45.12a24.256 24.256 0 0 0 3.776 35.2 26.88 26.88 0 0 0 36.672-3.648c2.56-3.072 65.088-77.696 65.088-198.72 0-65.024-16.832-109.376-52.544-150.72C600.256 88.448 541.76 64 470.976 64 335.808 64 256.256 140.416 256.256 280.704c0 60.864 8.192 104.256 25.024 148.928a340.48 340.48 0 0 0 80.256 122.368v46.784c-36.928 6.72-73.408 15.552-109.312 26.368-39.232 12.16-70.848 25.536-93.824 40.256-34.176 21.632-51.456 117.76-51.456 145.344 0 45.76 48.64 83.968 149.056 125.504 67.712 28.032 213.12 17.664 215.872 18.368l6.848 1.024a25.92 25.92 0 0 0 25.28-18.496 24.704 24.704 0 0 0-18.432-30.656z" p-id="6582" fill="#8755f2"></path><path d="M937.024 515.776a26.624 26.624 0 0 0-37.376 4.16c-9.152 11.456-199.808 266.624-228.224 268.928-27.712 2.432-122.752-90.112-122.752-90.112a26.496 26.496 0 0 0-36.224 4.032 26.56 26.56 0 0 0 1.856 36.48c10.304 9.664 101.056 82.048 123.072 98.304l25.6 18.816c5.12 3.264 10.752 5.376 16.576 4.736a23.68 23.68 0 0 0 17.536-9.152c6.208-7.36 35.52-41.344 35.776-41.792l208.448-257.216a26.56 26.56 0 0 0-4.288-37.184z" p-id="6583" fill="#8755f2"></path></svg>

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");
}
}
Loading…
Cancel
Save