Compare commits

...

No commits in common. 'main' and 'master' have entirely different histories.
main ... master

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="17" project-jdk-type="JavaSDK" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/k-class-roll-call.iml" filepath="$PROJECT_DIR$/.idea/k-class-roll-call.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="b4c20391-644c-4ef8-93ae-166260c047e0" name="更改" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ProjectId" id="2mvSYUAwxILKz9wIrHwcmy17tTE" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"WebServerToolWindowFactoryState": "false",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="默认任务">
<changelist id="b4c20391-644c-4ef8-93ae-166260c047e0" name="更改" comment="" />
<created>1727955603618</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1727955603618</updated>
<workItem from="1727955604708" duration="5000" />
<workItem from="1728893452698" duration="322000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
</project>

@ -1,2 +0,0 @@
# k-class-roll-call

@ -0,0 +1,5 @@
部署地址:
http://8.210.250.29/
测试账号:
账号 -- flyingpig
密码 -- flyingpig

33
backend/.gitignore vendored

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>2.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.flyingpig</groupId>
<artifactId>k-class-roll-call</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>k-class-roll-call</name>
<description>Roll call system for k class.</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.8.0</version> <!-- 选择合适的版本 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version> <!-- 请根据需要选择合适的版本 -->
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.5.0</version> <!-- 替换为你想要的最新版本 -->
<scope>test</scope> <!-- 确保它是在测试范围内 -->
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.16</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.16</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version> <!-- 可以根据需要使用最新版本 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.21.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!-- 如果没有这个插件打包出来的jar包没有清单文件-->
<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>
</plugins>
<!-- 如果不添加此节点mybatis的mapper.xml文件都会被漏掉。 -->
</build>
</project>

@ -0,0 +1,13 @@
package com.flyingpig.kclassrollcall;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class KClassRollCallApplication {
public static void main(String[] args) {
SpringApplication.run(KClassRollCallApplication.class, args);
}
}

@ -0,0 +1,10 @@
package com.flyingpig.kclassrollcall.common;
public class RedisConstant {
public static final String STUDENT_LIST_KEY = "student:list:";
public static final String STUDENT_SCORE_KEY = "student:score:";
}

@ -0,0 +1,35 @@
package com.flyingpig.kclassrollcall.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Integer code;//响应码
private String msg;//响应信息,描述字符串
private Object data;//返回的数据
//增删改
public static Result success() {
return new Result(200, "success", null);
}
//查询 成功响应
public static Result success(Object data) {
return new Result(200, "success", data);
}
//查询 失败响应
public static Result error(Integer code, String msg) {
return new Result(code, msg, null);
}
public static Result error(String msg) {
return new Result(500, msg, null);
}
}

@ -0,0 +1,16 @@
package com.flyingpig.kclassrollcall.common;
/*
0124
*/
public class RollCallMode {
public static final String WEIGHT = "0";
public static final String EQUAL = "1";
public static final String DOUBLE = "2";
public static final String THURSDAY = "4";
}

@ -0,0 +1,34 @@
package com.flyingpig.kclassrollcall.common;
public class StatusCode {
/**
*
*/
public static final int OK = 200;
/**
*
*/
public static final int PARAMETERERROR = 400;
/**
*
*/
public static final int NOTFOUND = 404;
/**
*
*/
public static final int METHODERROR = 405;
/**
*
*/
public static final int SERVERERROR = 500;
/**
*
*/
public static final int UNAUTHORIZED = 401;
}

@ -0,0 +1,30 @@
package com.flyingpig.kclassrollcall.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
/**
*
*/
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
//允许白名单域名进行跨域调用
config.addAllowedOriginPattern("*");
//允许跨越发送cookie
config.setAllowCredentials(true);
//放行全部原始头信息
config.addAllowedHeader("*");
//允许所有请求方法跨域调用
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

@ -0,0 +1,30 @@
package com.flyingpig.kclassrollcall.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@Configuration
public class JacksonConfig {
@Bean
@Primary
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
// 全局配置序列化返回 JSON 处理
SimpleModule simpleModule = new SimpleModule();
//JSON Long ==> String
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
return objectMapper;
}
}

@ -0,0 +1,16 @@
package com.flyingpig.kclassrollcall.config;
import com.flyingpig.kclassrollcall.filter.AuthInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.excludePathPatterns("/user/login", "/user/register"); // 排除登录和注册接口
}
}

@ -0,0 +1,19 @@
package com.flyingpig.kclassrollcall.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient(){
// 配置
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
// 创建RedissonClient对象
return Redisson.create(config);
}
}

@ -0,0 +1,109 @@
package com.flyingpig.kclassrollcall.controller;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.flyingpig.kclassrollcall.common.RedisConstant;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.entity.Student;
import com.flyingpig.kclassrollcall.filter.UserContext;
import com.flyingpig.kclassrollcall.service.IStudentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.swing.text.html.parser.Element;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@RestController
@RequestMapping("/student")
@Slf4j
public class StudentController {
@Autowired
IStudentService studentService;
@Autowired
StringRedisTemplate stringRedisTemplate;
@PutMapping("/score")
public Result modifyScore(Long id, Double score) {
if (studentService.updateById(new Student(id,
null, null, null, studentService.getById(id).getScore() + score, null))) {
stringRedisTemplate.delete(RedisConstant.STUDENT_SCORE_KEY + id);
return Result.success();
} else {
return Result.error("修改分数出错");
}
}
@GetMapping("/roll-call")
public Result rollCall(String mode) {
return studentService.rollCall(mode);
}
@GetMapping("/search")
public Result search(String no, String name, Integer pageNo, Integer pageSize) {
return studentService.search(no, name, pageNo, pageSize);
}
@PostMapping("/import")
public Result importExcel(@RequestParam("file") MultipartFile file) {
System.out.println("用户上传文件");
// 如果 validateExcelHeader 返回 false则文件类型或格式错误
if (!validateExcelHeader(file)) {
return Result.error("文件类型或格式错误");
}
return studentService.importExcel(file);
}
private boolean validateExcelHeader(MultipartFile file) {
// 检查文件类型
String fileName = file.getOriginalFilename();
if (fileName == null || !(fileName.endsWith(".xls") || fileName.endsWith(".xlsx"))) {
return false; // 文件扩展名不正确
}
// 使用 EasyExcel 检查文件内容
// 自定义监听器用于验证标题
ReadListener<String[]> listener = new ReadListener<String[]>() {
@Override
public void invoke(String[] data, AnalysisContext context) {
// 检查第一行的标题
if (context.readRowHolder().getRowIndex() == 0) { // 仅检查第一行
if (!"学生学号".equals(data[0]) || !"姓名".equals(data[1]) || !"专业".equals(data[2])) {
throw new RuntimeException("标题不匹配");
}
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}
};
return true; // 如果没有异常抛出,标题验证通过
}
@DeleteMapping("/{id}")
public Result deleteStudent(@PathVariable String id) {
if (studentService.removeById(id)) {
stringRedisTemplate.delete(RedisConstant.STUDENT_LIST_KEY + UserContext.getUser());
stringRedisTemplate.delete(RedisConstant.STUDENT_SCORE_KEY + id);
return Result.success();
} else {
return Result.error("删除失败");
}
}
}

@ -0,0 +1,45 @@
package com.flyingpig.kclassrollcall.controller;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.dto.req.LoginReq;
import com.flyingpig.kclassrollcall.entity.Teacher;
import com.flyingpig.kclassrollcall.service.ITeacherService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@RestController
@RequestMapping("/user")
public class TeacherController {
@Autowired
ITeacherService teacherService;
@PostMapping("/login")
public Result login(@RequestBody LoginReq loginReq){
return teacherService.login(loginReq);
}
@PostMapping("/register")
public Result register(@RequestBody Teacher teacher){
return teacherService.register(teacher);
}
@PostMapping("/logout")
public Result logout(@RequestBody Teacher teacher){
return Result.success();
}
}

@ -0,0 +1,13 @@
package com.flyingpig.kclassrollcall.dto.req;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginReq {
private String username;
private String password;
}

@ -0,0 +1,14 @@
package com.flyingpig.kclassrollcall.dto.req;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StudentExcelModel {
private String no; // 学号
private String name; // 姓名
private String major; // 专业
}

@ -0,0 +1,13 @@
package com.flyingpig.kclassrollcall.dto.resp;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginResp {
private String name;
private String token;
}

@ -0,0 +1,19 @@
package com.flyingpig.kclassrollcall.dto.resp;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StudentInfoInCache {
private Long id;
private String name;
private Integer no;
private String major;
}

@ -0,0 +1,48 @@
package com.flyingpig.kclassrollcall.entity;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("student")
@ApiModel(value="Student对象", description="")
@NoArgsConstructor
@AllArgsConstructor
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
private String name;
private Integer no;
private String major;
private Double score;
private Long teacherId;
}

@ -0,0 +1,40 @@
package com.flyingpig.kclassrollcall.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("teacher")
@ApiModel(value="Teacher对象", description="")
public class Teacher implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
private String name;
private String username;
private String password;
}

@ -0,0 +1,65 @@
package com.flyingpig.kclassrollcall.exception;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.common.StatusCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import java.net.BindException;
//全局异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* /--400
*
* @param e
* @return
*/
@ExceptionHandler({
MissingServletRequestParameterException.class,
MethodArgumentTypeMismatchException.class,
BindException.class}
)
public Result missingServletRequestParameterException(Exception e) {
return Result.error(StatusCode.PARAMETERERROR, "缺少参数或参数错误");
}
/**
* --405
* @param e
* @return
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Result httpRequestMethodNotSupportedExceptionHandler(Exception e){
log.error("请求方法错误");
return Result.error(StatusCode.METHODERROR,"请求方法错误");
}
/**
* Redis--500
*/
@ExceptionHandler(RedisConnectionFailureException.class)
public Result edisConnectionFailureExceptionHandler(RedisConnectionFailureException e) {
log.error("redis连接错误啦啦啦啦啦啦");
return Result.error(StatusCode.SERVERERROR,"redis连接错误啦啦啦啦啦啦");
}
/**
* --500
*/
@ExceptionHandler(Exception.class)
public Result exceptionHandler(Exception e) {
e.printStackTrace();
return Result.error(StatusCode.SERVERERROR,"对不起,操作失败,请联系管理员");
}
}

@ -0,0 +1,41 @@
package com.flyingpig.kclassrollcall.filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.flyingpig.kclassrollcall.util.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class AuthInterceptor implements HandlerInterceptor {
// 在控制器方法执行之前调用
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String tokenStr = request.getHeader("Authorization");
// 不为空
if (StringUtils.isBlank(tokenStr)) {
return false;
}
try {
// 判断是否是有效的token,如果是则存储到UserContext
UserContext.setUser(JwtUtil.parseJwt(tokenStr).getSubject());
System.out.println(UserContext.getUser());
} catch (Exception exception) {
exception.printStackTrace();
return false;
}
return true; // 返回 true 继续处理请求false 则中断请求
}
// 在控制器方法调用之后执行,但在视图渲染之前调用
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, org.springframework.web.servlet.ModelAndView modelAndView) throws Exception {
UserContext.removeUser();
}
}

@ -0,0 +1,19 @@
package com.flyingpig.kclassrollcall.filter;
public class UserContext {
private static final ThreadLocal<String> tl =new ThreadLocal<>();
//保存当前登录用户信息到ThreadLocal
public static void setUser(String userId){
tl.set(userId);
}
//获取当前登录的用户信息
public static String getUser(){
return tl.get();
}
//移除当前登录用户信息
public static void removeUser(){
tl.remove();
}
}

@ -0,0 +1,29 @@
package com.flyingpig.kclassrollcall.init;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.sql.DataSource;
@Slf4j(topic = "Initialize")
@RestController
@RequiredArgsConstructor
public class InitializeController {
private final DataSource dataSource;
@GetMapping("/initialize/application")
public void initializeDispatcherServletAndDataSource() {
log.info("开始初始化DispatcherServlet和数据源");
try {
log.info("初始化DispatcherServlet和数据源成功");
dataSource.getConnection().createStatement().execute("SELECT 1");
} catch (Exception e) {
log.error("初始化DispatcherServlet和数据源失败", e);
}
}
}

@ -0,0 +1,29 @@
package com.flyingpig.kclassrollcall.init;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;
@RequiredArgsConstructor
public final class InitializeDispatcherServletHandler implements CommandLineRunner {
private final RestTemplate restTemplate;
private final ConfigurableEnvironment configurableEnvironment;
@Override
public void run(String... args) throws Exception {
String url = String.format("http://127.0.0.1:%s%s",
configurableEnvironment.getProperty("server.port", "8080") + configurableEnvironment.getProperty("server.servlet.context-path", ""),
"/initialize/dispatcher-servlet");
try {
restTemplate.execute(url, HttpMethod.GET, null, null);
} catch (Throwable ignored) {
}
}
}

@ -0,0 +1,19 @@
package com.flyingpig.kclassrollcall.mapper;
import com.flyingpig.kclassrollcall.entity.Student;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@Mapper
public interface StudentMapper extends BaseMapper<Student> {
Student rollCall(String teacherId, int randomIndex);
}

@ -0,0 +1,18 @@
package com.flyingpig.kclassrollcall.mapper;
import com.flyingpig.kclassrollcall.entity.Teacher;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@Mapper
public interface TeacherMapper extends BaseMapper<Teacher> {
}

@ -0,0 +1,23 @@
package com.flyingpig.kclassrollcall.service;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.entity.Student;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.multipart.MultipartFile;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
public interface IStudentService extends IService<Student> {
Result rollCall(String mode);
Result search(String no, String name, Integer pageNo, Integer pageSize);
Result importExcel(MultipartFile file);
}

@ -0,0 +1,21 @@
package com.flyingpig.kclassrollcall.service;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.dto.req.LoginReq;
import com.flyingpig.kclassrollcall.entity.Teacher;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
public interface ITeacherService extends IService<Teacher> {
Result login(LoginReq loginReq);
Result register(Teacher teacher);
}

@ -0,0 +1,170 @@
package com.flyingpig.kclassrollcall.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.flyingpig.kclassrollcall.common.RedisConstant;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.common.RollCallMode;
import com.flyingpig.kclassrollcall.dto.req.StudentExcelModel;
import com.flyingpig.kclassrollcall.dto.resp.StudentInfoInCache;
import com.flyingpig.kclassrollcall.entity.Student;
import com.flyingpig.kclassrollcall.filter.UserContext;
import com.flyingpig.kclassrollcall.mapper.StudentMapper;
import com.flyingpig.kclassrollcall.service.IStudentService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.flyingpig.kclassrollcall.util.cache.ListCacheUtil;
import com.flyingpig.kclassrollcall.util.cache.StringCacheUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements IStudentService {
@Autowired
ListCacheUtil listCacheUtil;
@Autowired
StringCacheUtil stringCacheUtil;
@Override
public Result rollCall(String mode) {
if (!mode.equals(RollCallMode.EQUAL)) {
return Result.success(rollBackStudentBaseScore());
} else {
List<Student> students = this.baseMapper.selectList(new LambdaQueryWrapper<Student>()
.eq(Student::getTeacherId, UserContext.getUser()));
return Result.success(students.get(new Random().nextInt(students.size())));
}
}
private List<Student> selectStudentByTeacherId() {
return listCacheUtil.safeGetWithLock(
RedisConstant.STUDENT_LIST_KEY + UserContext.getUser(), // Redis Key
Student.class,
() -> {
// 查询学生ID的逻辑
return this.baseMapper.selectList(new LambdaQueryWrapper<Student>()
.eq(Student::getTeacherId, UserContext.getUser()));
},
30L, // 缓存时间
TimeUnit.MINUTES
);
}
private Student rollBackStudentBaseScore() {
// 获取符合条件的学生列表
List<Student> students = this.baseMapper.selectList(new LambdaQueryWrapper<Student>()
.eq(Student::getTeacherId, UserContext.getUser()));
// 计算权重
double totalWeight = 0;
Map<Student, Double> weightMap = new HashMap<>();
for (Student student : students) {
double weight;
if (student.getScore() > 0) {
weight = 1.0 / student.getScore();
} else {
weight = 1; // 给分数为0的学生一个较大的固定权重
}
weightMap.put(student, weight);
totalWeight += weight;
}
// 生成随机数
double randomValue = Math.random() * totalWeight;
// 根据权重选择学生
double cumulativeWeight = 0;
for (Map.Entry<Student, Double> entry : weightMap.entrySet()) {
cumulativeWeight += entry.getValue();
if (cumulativeWeight >= randomValue) {
return entry.getKey();
}
}
return null; // 若无符合条件的学生返回null
}
public Result search(String no, String name, Integer pageNo, Integer pageSize) {
// 构建查询条件
LambdaQueryWrapper<Student> queryWrapper = new LambdaQueryWrapper<>();
// 如果 no 和 name 不为空,则分别进行模糊查询
queryWrapper
.like(StringUtils.isNotBlank(no), Student::getNo, no)
.or(StringUtils.isNotBlank(name)) // 仅在 name 不为空时才使用 or
.like(StringUtils.isNotBlank(name), Student::getName, name)
.eq(Student::getTeacherId, UserContext.getUser());
System.out.println(queryWrapper.getTargetSql());
// 分页查询
Page<Student> page = new Page<>(pageNo, pageSize);
Page<Student> result = page(page, queryWrapper);
result.setTotal(result.getRecords().size());
// 返回分页结果
return Result.success(result);
}
@Override
public Result importExcel(MultipartFile file) {
// 读取数据并转换为 Student 对象
List<Student> students = readStudentsFromExcel(file);
// 保存到数据库
if (!students.isEmpty()) {
saveBatch(students);
}
stringCacheUtil.getInstance().delete(RedisConstant.STUDENT_LIST_KEY + UserContext.getUser());
return Result.success("导入成功,已添加 " + students.size() + " 条记录");
}
private List<Student> readStudentsFromExcel(MultipartFile file) {
List<Student> students = new ArrayList<>();
// 创建 Excel 读取器并指定监听器
try {
EasyExcel.read(file.getInputStream(), StudentExcelModel.class, new ReadListener<StudentExcelModel>() {
@Override
public void invoke(StudentExcelModel data, AnalysisContext context) {
// 直接从第二行开始读取数据,跳过第一行
if (context.readRowHolder().getRowIndex() >= 1) {
Student student = new Student(null, null, null, null, (double) 0, Long.parseLong(UserContext.getUser()));
BeanUtil.copyProperties(data, student);
students.add(student);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}
}).sheet().doRead(); // 读取工作表
} catch (IOException e) {
System.out.println("读取错误");
}
return students;
}
}

@ -0,0 +1,50 @@
package com.flyingpig.kclassrollcall.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.dto.req.LoginReq;
import com.flyingpig.kclassrollcall.dto.resp.LoginResp;
import com.flyingpig.kclassrollcall.entity.Teacher;
import com.flyingpig.kclassrollcall.mapper.TeacherMapper;
import com.flyingpig.kclassrollcall.service.ITeacherService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.flyingpig.kclassrollcall.util.JwtUtil;
import org.apache.catalina.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
/**
* <p>
*
* </p>
*
* @author flyingpig
* @since 2024-09-25
*/
@Service
public class TeacherServiceImpl extends ServiceImpl<TeacherMapper, Teacher> implements ITeacherService {
@Override
public Result login(LoginReq loginReq) {
Teacher teacher = this.getOne(new LambdaQueryWrapper<Teacher>()
.eq(Teacher::getUsername, loginReq.getUsername()));
if (teacher == null || !new BCryptPasswordEncoder().matches(loginReq.getPassword(), teacher.getPassword())) {
return Result.error("账号或密码错误");
} else {
return Result.success(new LoginResp(teacher.getName(), JwtUtil.createJWT(teacher.getId().toString())));
}
}
@Override
public Result register(Teacher teacher) {
try {
save(teacher.setPassword(new BCryptPasswordEncoder().encode(teacher.getPassword())));
return Result.success("注册成功");
} catch (Exception e) {
log.error(e.getMessage());
return Result.error("注册失败,可能存在用户名已被注册等问题");
}
}
}

@ -0,0 +1,98 @@
package com.flyingpig.kclassrollcall.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
public class JwtUtil {
//有效期为
public static final Long JWT_TTL =15*24* 60 * 60 *1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "Zmx5aW5ncGln";//flyingpig
public static String getUUID(){
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
/**
* jtw
* @param subject tokenjson
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
/**
* jtw
* @param subject tokenjson
* @param ttlMillis token
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("flyingpig") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}
/**
* token
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
}
public static void main(String[] args) throws Exception {
String jwtKey = "flyingpig";
String encodedKey = Base64.getEncoder().encodeToString(jwtKey.getBytes());
System.out.println(encodedKey);
}
/**
* secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
//解析JWT令牌
public static Claims parseJwt(String jwt) {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}

@ -0,0 +1,14 @@
package com.flyingpig.kclassrollcall.util.cache;
/**
*
*
*/
@FunctionalInterface
public interface CacheLoader<T> {
/**
*
*/
T load();
}

@ -0,0 +1,101 @@
package com.flyingpig.kclassrollcall.util.cache;
import com.alibaba.fastjson.JSON;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Component
public class ListCacheUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedissonClient redissonClient;
public <T> void set(String key, List<T> values, Long time, TimeUnit unit) {
// 使用事务确保原子性
stringRedisTemplate.execute((RedisCallback<Void>) connection -> {
// 清空现有列表内容
stringRedisTemplate.opsForList().trim(key, 0, -1);
// 将列表中的每个值存储到 Redis 列表中
values.forEach(value -> stringRedisTemplate.opsForList().rightPush(key, JSON.toJSONString(value)));
// 设置列表的过期时间
stringRedisTemplate.expire(key, time, unit);
return null;
});
}
public <T> List<T> get(String key, Class<T> type) {
List<String> value = stringRedisTemplate.opsForList().range(key, 0, -1);
return (value == null || value.isEmpty()) ? null : value.stream()
.map(json -> JSON.parseObject(json, type))
.collect(Collectors.toList());
}
public <T> List<T> safeGetWithLock(String key, Class<T> type, CacheLoader<List<T>> cacheLoader, Long time, TimeUnit unit) {
List<T> cachedValues = get(key, type);
// 1. 命中且不为空,直接返回; 命中却为空,返回 null
if (cachedValues != null) {
return cachedValues;
}
// 获取锁
String lockKey = "lock:" + key;
RLock rLock = redissonClient.getLock(lockKey);
List<T> result = null;
try {
if (rLock.tryLock()) { // 尝试获取锁,避免死锁
// 再次查询 Redis双重判定
cachedValues = get(key, type);
if (cachedValues != null) {
return cachedValues;
}
// 获取锁成功,查询数据库
result = loadAndSet(key, cacheLoader, time, unit);
}
} catch (Exception e) {
// 记录日志
// logger.error("Error occurred while accessing cache with key: {}", key, e);
} finally {
// 确保释放锁
if (rLock.isHeldByCurrentThread()) {
rLock.unlock();
}
}
return result;
}
private <T> List<T> loadAndSet(String key, CacheLoader<List<T>> cacheLoader, Long time, TimeUnit unit) {
// 获取锁成功,查询数据库
List<T> result = cacheLoader.load(); // 返回 List<T>
if (result == null || result.isEmpty()) {
// 将空值写入 Redis返回 null
stringRedisTemplate.opsForList().rightPush(key, ""); // 存储一个空字符串作为占位符
stringRedisTemplate.expire(key, time, unit);
return null;
}
// 存在,写入 Redis
set(key, result, time, unit);
return result;
}
public ListOperations<String, String> getListOperations() {
return stringRedisTemplate.opsForList();
}
}

@ -0,0 +1,101 @@
package com.flyingpig.kclassrollcall.util.cache;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class StringCacheUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedissonClient redissonClient;
public ValueOperations<String, String> getValueOperations() {
return stringRedisTemplate.opsForValue();
}
public StringRedisTemplate getInstance() {
return stringRedisTemplate;
}
public void set(String key, Object value, Long time, TimeUnit unit) {
stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(value), time, unit);
}
public <T> T get(String key, Class<T> type) {
String value = stringRedisTemplate.opsForValue().get(key);
if (value != null) {
return JSON.parseObject(value, type);
}
return null; // 允许返回 null
}
// 查询时缓存空值防止缓存穿透,互斥锁查询防止缓存击穿
public <T> T safeGetWithLock(
String key, Class<T> type, CacheLoader<T> cacheLoader, Long time, TimeUnit unit) {
String json = stringRedisTemplate.opsForValue().get(key);
// 命中且不为空字符串,直接返回;命中却为空字符串,返回 null
if (StrUtil.isNotBlank(json)) {
return JSON.parseObject(json, type);
} else if (json != null) {
return null; // 允许返回 null
}
// 获取锁
String lockKey = String.format("lock:%s", key);
T result = null;
RLock rLock = redissonClient.getLock(lockKey);
try {
rLock.lock();
// 再次查询redis双重判定
json = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(json)) {
return JSON.parseObject(json, type);
} else if (json != null) {
return null; // 允许返回 null
}
// 获取锁成功,查询数据库
result = loadAndSet(key, cacheLoader, time, unit);
} catch (Exception e) {
// 记录日志
log.error("Error occurred while accessing cache with key: {}", key, e);
} finally {
if (rLock.isHeldByCurrentThread()) {
rLock.unlock();
}
}
return result;
}
private <T> T loadAndSet(String key, CacheLoader<T> cacheLoader, Long time, TimeUnit unit) {
T result = cacheLoader.load();
// 不存在将空值写入redis返回 null
if (result == null) {
stringRedisTemplate.opsForValue().set(key, "", time, TimeUnit.MINUTES);
} else {
this.set(key, result, time, unit);
}
return result;
}
}

@ -0,0 +1,22 @@
server:
port: 9090
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/k-class-roll-call
username: root
password: '@Aa123456'
hikari:
minimum-idle: 5 # 最小空闲连接数
maximum-pool-size: 10 # 连接池最大连接数
connection-timeout: 30000 # 连接超时时间
idle-timeout: 600000 # 空闲连接的存活时间
max-lifetime: 1800000 # 连接的最长存活时间
redis:
host: localhost
port: 6379
database: 0
logging:
level:
com.baomidou.mybatisplus: DEBUG # MyBatis-Plus 日志级别

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.flyingpig.kclassrollcall.mapper.StudentMapper">
<select id="rollCall" resultType="com.flyingpig.kclassrollcall.entity.Student">
SELECT * FROM student WHERE teacher_id = #{teacherId} LIMIT 1 OFFSET #{randomIndex};
</select>
</mapper>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.flyingpig.kclassrollcall.mapper.TeacherMapper">
</mapper>

@ -0,0 +1,59 @@
/*
Navicat Premium Data Transfer
Source Server : k-class-roll-call
Source Server Type : MySQL
Source Server Version : 80032 (8.0.32)
Source Host : localhost:3306
Source Schema : k-class-roll-call
Target Server Type : MySQL
Target Server Version : 80032 (8.0.32)
File Encoding : 65001
Date: 28/09/2024 11:06:20
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` bigint NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`no` int NULL DEFAULT NULL,
`major` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`score` decimal(10, 2) NULL DEFAULT NULL,
`teacher_id` bigint NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES (1, '朱嘉翔', 102201604, '计算机科学与技术', 25.50, 1);
INSERT INTO `student` VALUES (2, '范国鑫', 102201525, '计算机科学与技术', 34.00, 1);
INSERT INTO `student` VALUES (1839635335860477954, '庄学鹏', 102201605, '计算机科学与技术', 9.00, 1);
-- ----------------------------
-- Table structure for teacher
-- ----------------------------
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (
`id` bigint NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of teacher
-- ----------------------------
INSERT INTO `teacher` VALUES (1, '1', '1', '1');
INSERT INTO `teacher` VALUES (1839133469267496961, 'flyingpig', 'flyingpig', 'flyingpig');
SET FOREIGN_KEY_CHECKS = 1;

@ -0,0 +1,13 @@
package com.flyingpig.kclassrollcall;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class KClassRollCallApplicationTests {
@Test
void contextLoads() {
}
}

@ -0,0 +1,70 @@
package com.flyingpig.kclassrollcall;
import com.flyingpig.kclassrollcall.common.Result;
import com.flyingpig.kclassrollcall.dto.req.LoginReq;
import com.flyingpig.kclassrollcall.dto.resp.LoginResp;
import com.flyingpig.kclassrollcall.entity.Teacher;
import com.flyingpig.kclassrollcall.mapper.TeacherMapper;
import com.flyingpig.kclassrollcall.service.ITeacherService;
import com.flyingpig.kclassrollcall.service.impl.TeacherServiceImpl;
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.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@SpringBootTest
public class TeacherServiceImplTest {
@InjectMocks
private TeacherServiceImpl teacherService; // 替换为你的实际服务类
@Mock
private TeacherMapper teacherMapper; // 替换为你的 TeacherMapper 类
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
public void testLoginSuccess() {
// Arrange
LoginReq loginReq = new LoginReq("flyingpig", "flyingpig");
Teacher teacher = new Teacher();
teacher.setId(1L);
teacher.setName("John Doe");
teacher.setUsername("flyingpig");
teacher.setPassword("flyingpig");
when(teacherMapper.selectOne(any())).thenReturn(teacher); // 模拟数据库返回教师对象
// Act
Result result = teacherService.login(loginReq);
// Assert
assertEquals(200, result.getCode()); // 检查返回代码
assertEquals("John Doe", ((LoginResp) result.getData()).getName()); // 检查返回的教师名称
}
@Test
public void testLoginFailure() {
// Arrange
LoginReq loginReq = new LoginReq("username", "wrongpassword");
when(teacherMapper.selectOne(any())).thenReturn(null);
// Act
Result result = teacherService.login(loginReq);
// Assert
assertEquals(500, result.getCode()); // 检查返回代码
assertEquals("账号或密码错误", result.getMsg()); // 检查返回的错误信息
}
}

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 ROBINRUGAN
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,48 @@
{
"name": "k-calss-rall-call",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^1.5.1",
"core-js": "^3.8.3",
"element-ui": "^2.15.14",
"jszip": "^3.10.1",
"vue": "^2.6.14",
"vue-router": "^3.6.5"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"babel-plugin-component": "^1.1.1",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"vue-template-compiler": "^2.6.14"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

@ -0,0 +1,135 @@
function clickEffect() {
let balls = [];
let longPressed = false;
let longPress;
let multiplier = 0;
let width, height;
let origin;
let normal;
let ctx;
const colours = ["#F73859", "#14FFEC", "#00E0FF", "#FF99FE", "#FAF15D"];
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
canvas.setAttribute("style", "width: 100%; height: 100%; top: 0; left: 0; z-index: 99999; position: fixed; pointer-events: none;");
const pointer = document.createElement("span");
pointer.classList.add("pointer");
document.body.appendChild(pointer);
if (canvas.getContext && window.addEventListener) {
ctx = canvas.getContext("2d");
updateSize();
window.addEventListener('resize', updateSize, false);
loop();
window.addEventListener("mousedown", function(e) {
pushBalls(randBetween(10, 20), e.clientX, e.clientY);
document.body.classList.add("is-pressed");
longPress = setTimeout(function() {
document.body.classList.add("is-longpress");
longPressed = true;
}, 500);
}, false);
window.addEventListener("mouseup", function(e) {
clearInterval(longPress);
if (longPressed == true) {
document.body.classList.remove("is-longpress");
pushBalls(randBetween(50 + Math.ceil(multiplier), 100 + Math.ceil(multiplier)), e.clientX, e.clientY);
longPressed = false;
}
document.body.classList.remove("is-pressed");
}, false);
window.addEventListener("mousemove", function(e) {
let x = e.clientX;
let y = e.clientY;
pointer.style.top = y + "px";
pointer.style.left = x + "px";
}, false);
} else {
console.log("canvas or addEventListener is unsupported!");
}
function updateSize() {
canvas.width = window.innerWidth * 2;
canvas.height = window.innerHeight * 2;
canvas.style.width = window.innerWidth + 'px';
canvas.style.height = window.innerHeight + 'px';
ctx.scale(2, 2);
width = (canvas.width = window.innerWidth);
height = (canvas.height = window.innerHeight);
origin = {
x: width / 2,
y: height / 2
};
normal = {
x: width / 2,
y: height / 2
};
}
class Ball {
constructor(x = origin.x, y = origin.y) {
this.x = x;
this.y = y;
this.angle = Math.PI * 2 * Math.random();
if (longPressed == true) {
this.multiplier = randBetween(14 + multiplier, 15 + multiplier);
} else {
this.multiplier = randBetween(6, 12);
}
this.vx = (this.multiplier + Math.random() * 0.5) * Math.cos(this.angle);
this.vy = (this.multiplier + Math.random() * 0.5) * Math.sin(this.angle);
this.r = randBetween(8, 12) + 3 * Math.random();
this.color = colours[Math.floor(Math.random() * colours.length)];
}
update() {
this.x += this.vx - normal.x;
this.y += this.vy - normal.y;
normal.x = -2 / window.innerWidth * Math.sin(this.angle);
normal.y = -2 / window.innerHeight * Math.cos(this.angle);
this.r -= 0.3;
this.vx *= 0.9;
this.vy *= 0.9;
}
}
function pushBalls(count = 1, x = origin.x, y = origin.y) {
for (let i = 0; i < count; i++) {
balls.push(new Ball(x, y));
}
}
function randBetween(min, max) {
return Math.floor(Math.random() * max) + min;
}
function loop() {
ctx.fillStyle = "rgba(255, 255, 255, 0)";
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < balls.length; i++) {
let b = balls[i];
if (b.r < 0) continue;
ctx.fillStyle = b.color;
ctx.beginPath();
ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2, false);
ctx.fill();
b.update();
}
if (longPressed == true) {
multiplier += 0.2;
} else if (!longPressed && multiplier >= 0) {
multiplier -= 0.4;
}
removeBall();
requestAnimationFrame(loop);
}
function removeBall() {
for (let i = 0; i < balls.length; i++) {
let b = balls[i];
if (b.x + b.r < 0 || b.x - b.r > width || b.y + b.r < 0 || b.y - b.r > height || b.r < 0) {
balls.splice(i, 1);
}
}
}
}
clickEffect();//调用

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>K班点名系统ヾ(≧▽≦*)o</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="1.js"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

@ -0,0 +1,42 @@
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "App",
components: {},
};
</script>
<style>
* {
margin: 0;
padding: 0;
scroll-behavior: smooth;
font-family: "微软雅黑";
}
.el-submenu__title {
margin: 0px !important;
padding: 0px !important;
color: #fff !important;
background-color: #a6e0fe !important;
font-size: 18px!important;
}
.el-submenu__title.is-active {
padding: 0px !important;
background-color: #6fc5f1 !important;
}
.el-menu-item-group__title {
padding: 0px !important;
}
.el-submenu__title i {
color: #ffffff!important;
}
.el-submenu__icon-arrow {
font-size: 20px!important;
margin-top: -11px!important;
}
</style>

@ -0,0 +1,81 @@
import {
service
} from "@/utils/request";
export function Semester() {
return service.request({
method: "get",
url: `/courseDetails/dataColumn`,
})
}
export function Login(data) {
return service.request({
method: "post",
url: `/user/login`,
data
})
}
export function Register(data) {
return service.request({
method: "post",
url: `/user/register`,
data
})
}
export function Logout() {
return service.request({
method: "post",
url: `/user/logout`,
})
}
export function DeleteStudent(studentId) {
return service.request({
method: "delete",
url: `/student/${studentId}` // 确保 studentId 是字符串
});
}
export function ModifyStudentScore(id, score) {
return service.request({
method: "put",
url: `/student/score?id=${id}&&score=${score}`,
})
}
export function StudentSearch(data) {
const {
no,
name,
pageSize,
pageNo
} = data;
// 使用条件运算符来确保查询参数不是 undefined
const queryParams = [
`no=${no}`,
`name=${name}`,
`pageSize=${pageSize}`,
`pageNo=${pageNo}`
].filter(param => param !== undefined).join('&');
const url = `/student/search?${queryParams}`;
return service.request({
method: "get",
url: url,
});
}
export function GetRollBackStudent(mode) {
const url = `/student/roll-call?mode=${mode}`;
return service.request({
method: "get",
url: url
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

@ -0,0 +1,266 @@
<template>
<div id="home-part">
<div id="banner">
<div id="info-time">
<div class="title">当前时间</div>
<div class="content">{{ date }}</div>
</div>
<div id="info-sche">
<div class="title">当前学期</div>
<div class="content">2024.01</div>
</div>
</div>
<div id="hello">{{ name }}老师您好</div>
<router-link to="/student">
<div id="cloud2">
<div class="func-title" id="title2">学生<br />查询</div>
<div class="func-content" id="content2">
搜索获取班级学生名单以及学生积分情况导入学生名单
</div>
</div>
</router-link>
<router-link to="/student">
<div id="cloud3">
<div class="func-title" id="title2">学生<br />查询</div>
<div class="func-content" id="content2">
搜索获取班级学生名单以及学生积分情况导入学生名单
</div>
</div>
</router-link>
<router-link to="/roll-call">
<div id="cloud4">
<div class="func-title" id="title4">点名<br />提问</div>
<div class="func-content" id="content4">对学生进行点名并对到场的学生进行提问</div>
</div>
</router-link>
<router-link to="/roll-call">
<div id="cloud5">
<div class="func-title" id="title5">点名<br />提问</div>
<div class="func-content" id="content5">
对学生进行点名并对到场的学生进行提问
</div>
</div>
</router-link>
</div>
</template>
<script>
export default {
data() {
return {
name: "FlyingPig",
semester: "",
};
},
beforeMount() {
if (localStorage.getItem("name") == null) {
alert("您还未登录或登录已过期,请重新登录!");
if (this.$route.path !== "/login") {
this.$router.push("/login");
}
} else {
this.name = localStorage.getItem("name");
}
},
methods: {},
computed: {
date: () => {
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
return `${year}${month}${day}`;
},
},
};
</script>
<style scoped>
#home-part {
box-sizing: border-box;
position: relative;
width: 85.4%;
height: 100vh;
background-image: url("../assets/home/backgound2.png");
background-size: cover;
display: flex;
overflow: hidden; /* 隐藏溢出内容 */
}
#banner {
box-sizing: border-box;
position: absolute;
margin-left: 5.5%;
margin-top: 15px;
width: 90%;
height: 50px;
background-color: #b9c1c4;
border-radius: 20px;
justify-content: center;
background-size: cover;
display: flex;
justify-content: space-evenly;
align-items: center;
}
#info-sche {
display: flex;
}
#info-time {
display: flex;
}
.title {
font-size: 20px;
text-align: center;
font-weight: bold;
}
.content {
font-size: 20px;
font-weight: 400;
}
#hello {
margin-left: 50px;
margin-top: 90px;
font-size: 20px;
align-self: flex-start;
font-weight: bold;
}
@keyframes move2 {
from {
transform: translate(120%, -80%);
}
to {
transform: translate(0%, 0%);
}
}
@keyframes move3 {
from {
transform: translate(100%, 10%);
}
to {
transform: translate(0%, 0%);
}
}
@keyframes move4 {
from {
transform: translate(110%, 50%);
}
to {
transform: translate(0%, 0%);
}
}
@keyframes move5 {
from {
transform: translate(20%, 80%);
}
to {
transform: translate(0%, 0%);
}
}
#cloud2 {
background-size: contain;
position: absolute;
z-index: 100;
width: 439px;
height: 249px;
top: 50.9%;
left: 0%;
animation: move2 1.5s ease;
}
#cloud3 {
background-size: contain;
position: absolute;
z-index: 100;
width: 439px;
height: 279px;
top: 24.9%;
left: 10%;
animation: move3 1.5s ease;
}
#cloud4 {
background-size: contain;
position: absolute;
z-index: 100;
width: 333px;
height: 319px;
top: 10.9%;
left: 38%;
animation: move4 1.5s ease;
}
#cloud5 {
background-size: contain;
position: absolute;
z-index: 100;
width: 430px;
height: 319px;
top: 12.9%;
left: 61%;
animation: move5 1.5s ease;
}
.func-title {
font-size: 22px;
font-weight: bold;
margin-top: 25px;
margin-left: 20px;
position: absolute;
color: black;
}
.func-content {
font-size: 18px;
font-weight: 550;
margin-top: 10px;
margin-left: 20px;
position: absolute;
}
#title2 {
top: 23%;
left: 60%;
letter-spacing: 2px;
}
#content2 {
width: 190px;
top: 33%;
left: 14%;
color: #000;
}
#content2:hover {
color: white;
}
#title3 {
top: 38%;
left: 60%;
letter-spacing: 2px;
}
#title4 {
top: 45%;
left: 37%;
letter-spacing: 2px;
}
#content4 {
width: 190px;
top: 21%;
left: 14%;
color: black;
}
#content4:hover {
color: white;
}
#title5 {
top: 35%;
left: 15%;
letter-spacing: 2px;
}
#content5 {
width: 190px;
top: 21%;
left: 30%;
color: #000;
}
#content5:hover {
color: white;
}
</style>

@ -0,0 +1,136 @@
<template>
<div id="question">
<!-- 添加 "进入提问环节" 文本 -->
<div id="question-phase">
进入提问环节
</div>
<!-- 显示正在回答问题的学生姓名 -->
<div id="current-student">
现在回答问题的是 {{ currentStudent.name || "某某同学" }}
</div>
<div id="circle-container">
<button v-for="(score, index) in scores" :key="index" class="circle-button" :style="getButtonStyle(index)"
@click="selectScore(score)">
{{ score }}
</button>
</div>
</div>
</template>
<script>
import { ModifyStudentScore } from "@/api/api"; //
export default {
data() {
return {
scores: [-1, 0.5, 1, 1.5, 2, 2.5, 3],
selectedScore: null,
currentStudent: this.$route.params.student, //
detailModeType: this.$route.params.detailModeType //
};
},
methods: {
selectScore(score) {
this.selectedScore = score; //
if (this.detailModeType != 0 && this.detailModeType != 2) {
score = this.detailModeType * score;
}
// 使 this.detailModeType 访 detailModeType
ModifyStudentScore(this.currentStudent.id, score).then((res) => {
if (res.code == 500) {
this.$message.error("修改分数发生错误");
} else if (res.code == 200) {
this.$message.success(this.currentStudent.name + "分数修改成功, 增加" + score + "分");
if (this.$route.path !== "/roll-call") {
this.$router.push("/roll-call");
}
} else if (res.code == 401) {
alert("登录过期,请重新登录!");
if (this.$route.path !== "/login") {
this.$router.push("/login");
}
}
});
},
getButtonStyle(index) {
const angle = (index / this.scores.length) * 2 * Math.PI; //
const radius = 150; //
const x = radius * Math.cos(angle); // x
const y = radius * Math.sin(angle); // y
return {
position: 'absolute',
left: `${150 + x}px`, // x
top: `${150 + y}px`, // y
};
},
},
};
</script>
<style scoped>
#question {
box-sizing: border-box;
position: relative;
width: 85.4%;
height: 100vh;
background-image: url("../assets/login/login-background.png");
background-size: cover;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: hidden;
/* 隐藏溢出内容 */
}
#question-phase {
font-size: 28px;
color: black;
margin-bottom: 10px;
}
#current-student {
font-size: 24px;
color: black;
margin-bottom: 30px;
}
#circle-container {
position: relative;
width: 300px;
/* 容器宽度,圆环直径 */
height: 300px;
position: relative;
}
.circle-button {
width: 60px;
height: 60px;
border-radius: 50%;
background-color: #87CEEB;
color: white;
border: none;
font-size: 18px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: background-color 0.3s;
}
.circle-button:hover {
background-color: #1E90FF;
}
#selected-score {
margin-top: 20px;
font-size: 24px;
color: white;
}
</style>

@ -0,0 +1,240 @@
<template>
<div id="check-attendance">
<div id="header">
<button id="mode-button" @click="toggleMode"
:style="{ backgroundColor: isRandomMode ? '#87CEEB' : '#d74e3e', borderRadius: '20px', color: 'white' }">
{{ modeName }}
</button>
</div>
<p v-if="randomEvent" style="text-align: center; font-size: 18px;">
目前随机事件为{{ randomEvent }}
</p>
<div id="attendance-button-container">
<button id="attendance-button" :style="{ backgroundColor: isRandomMode ? '#87CEEB' : '#d74e3e' }"
@click="getRandomStudent">
点名
</button>
</div>
<div v-if="currentStudent" id="student-info">
<p style="font-size: 24px;">
学号: <span style="margin-right: 50px;">{{ currentStudent.no }}</span>
姓名: {{ currentStudent.name }}
</p>
<div id="action-buttons">
<button id="absentBtn" @click="markAbsent"></button>
<button id="presentBtn" @click="markPresent"></button>
<button id="transferBtn" @click="transferAttendance"></button>
</div>
</div>
</div>
</template>
<script>
import { GetRollBackStudent, ModifyStudentScore } from '@/api/api';
export default {
data() {
return {
modeName: '生成随机事件',
isRandomMode: true, /* true为正常模式false为随机事件模式 */
detailModeType: 0, /* 0为权重模式1为众生平等2为两倍积分模式3为幸运星期三模式 */
currentStudent: null,
students: [],
randomEvent: null, //
randomEvents: ['双倍扣分', '幸运星期三', '众生平等'], //
};
},
methods: {
toggleMode() {
this.isRandomMode = !this.isRandomMode;
this.modeName = this.isRandomMode ? '生成随机事件' : '返回正常模式';
if (!this.isRandomMode) {
this.randomEvent = this.randomEvents[Math.floor(Math.random() * this.randomEvents.length)]; //
if (this.randomEvent == '双倍扣分') {
this.detailModeType = 2;
} else if (this.randomEvent == '幸运星期三') {
this.detailModeType = 3;
} else if (this.randomEvent == '众生平等') {
this.detailModeType = 1;
}
} else {
this.randomEvent = null; // 退
}
},
getRandomStudent() {
GetRollBackStudent(
this.detailModeType
).then((res) => {
if (res.code == 500) {
this.$message.error("获取点名学生发生错误");
} else if (res.code == 200) {
this.currentStudent = res.data;
this.$message.success("查询成功");
} else if (res.code == 401) {
alert("登录过期,请重新登录!");
if (this.$route.path !== "/login") {
this.$router.push("/login");
}
}
});
},
markAbsent() {
let score = -2;
if (this.detailModeType == 2) {
score = -4;
}
ModifyStudentScore(this.currentStudent.id, score).then((res) => {
if (res.code == 500) {
this.$message.error("修改分数发生错误");
this.currentStudent = null; //
} else if (res.code == 200) {
if (score == -2) {
alert(`${this.currentStudent.name} 未到教室,已被登记,扣除两分`);
} else {
alert(`${this.currentStudent.name} 未到教室,已被登记,扣除四分`);
}
this.currentStudent = null; //
} else if (res.code == 401) {
alert("登录过期,请重新登录!");
if (this.$route.path !== "/login") {
this.$router.push("/login");
}
}
});
},
markPresent() {
let score = 1;
if (this.detailModeType == 3) {
score = score * 3;
}
ModifyStudentScore(this.currentStudent.id, score).then((res) => {
if (res.code == 500) {
this.$message.error("修改分数发生错误");
this.currentStudent = null; //
} else if (res.code == 200) {
this.$message.success(this.currentStudent.name + " 到达教室增加"+score+"分,进入提问环节");
if (this.$route.path !== "/question") {
this.$router.push({ name: 'question', params: { student: this.currentStudent, detailModeType: this.detailModeType } });
}
} else if (res.code == 401) {
alert("登录过期,请重新登录!");
if (this.$route.path !== "/login") {
this.$router.push("/login");
}
}
});
},
transferAttendance() {
alert(`${this.currentStudent.name} 转移点名,重新进行点名`);
this.currentStudent = null; //
},
},
};
</script>
<style scoped>
#check-attendance {
box-sizing: border-box;
position: relative;
width: 85.4%;
height: 100vh;
/* background-image: url("../assets/background.png"); */
background-image: url("../assets/login/login-background.png");
background-size: cover;
display: flex;
flex-direction: column;
justify-content: flex-start;
overflow: hidden;
/* 隐藏溢出内容 */
}
#header {
display: flex;
justify-content: flex-end;
margin: 10px;
height: 50px;
}
#mode-button {
border: none;
border-radius: 20px;
/* 圆角按钮 */
padding: 10px 20px;
/* 增加内边距 */
font-size: 16px;
/* 调整字体大小 */
cursor: pointer;
/* 鼠标指针样式 */
}
#attendance-button-container {
display: flex;
justify-content: center;
align-items: center;
margin: 10px;
}
#attendance-button {
border: none;
border-radius: 50%;
width: 100px;
height: 100px;
font-size: 20px;
color: white;
margin: 10px;
}
#student-info {
text-align: center;
margin-top: 0;
padding-top: 0;
}
#action-buttons {
display: flex;
justify-content: center;
margin-top: 30px;
}
#action-buttons button {
flex: 0 0 100px;
/* 增加宽度 */
height: 70px;
/* 增加高度 */
margin: 0 15px;
/* 增加按钮之间的间距 */
border-radius: 10px;
font-size: 18px;
/* 增加字体大小 */
}
#absentBtn {
background-color: #d74e3e;
/* 警告红色 */
color: white;
border: none;
}
#presentBtn {
background-color: #87CEEB;
/* 蓝色 */
color: white;
border: none;
}
#transferBtn {
background-color: #FFD700;
/* 黄色 */
color: white;
border: none;
}
</style>

@ -0,0 +1,175 @@
<template>
<div id="sidebar">
<div id="info">
<img id="logo" src="../assets/home/avatar.png" alt="" />
<h1 id="title">KX点名</h1>
</div>
<el-menu :default-active="$route.path" class="menu">
<router-link to="/home">
<el-menu-item index="/home" >
<div class="menu-item">
<img src="../assets/home/home-icon.png" alt="" />
首页
</div>
</el-menu-item>
</router-link>
<router-link to="/student">
<el-menu-item index="/student">
<div class="menu-item">
<img src="../assets/home/search-menu.png" alt="" />
学生名单与积分查询
</div>
</el-menu-item>
</router-link>
<router-link to="/roll-call">
<el-menu-item index="/roll-call">
<div class="menu-item">
<img src="../assets/home/pass-icon.png" alt="" />
点名与提问
</div>
</el-menu-item>
</router-link>
</el-menu>
<div id="logout" @click="logout">
<img src="../assets/home/logout-icon.png" alt="" />
<span>退出登录</span>
</div>
</div>
</template>
<script>
import { Logout } from "@/api/api";
export default {
methods: {
logout() {
Logout().then(
(res) => {
this.$message.success("退出成功!");
localStorage.removeItem("token");
localStorage.removeItem("name");
if (this.$route.path !== "/login") {
this.$router.push("/login");
}
},
(err) => {
this.$message.error(err.msg);
console.log(err);
}
);
},
},
};
</script>
<style scoped>
#sidebar {
background-color: #c5d2d8;
height: 100vh;
width: 260px;
box-shadow: 8px 0px 11px 0px rgba(0, 0, 0, 0.16);
border-radius: 5px;
display: flex;
flex-direction: column;
}
#info {
height: 95px;
width: 260px;
justify-content: center;
align-items: center;
position: relative;
background: #c5d2d8;
}
#logo {
position: absolute;
left: 10px;
top: 5%;
height: 71px;
width: 71px;
}
#title {
position: absolute;
left: 105px;
top: 25%;
width: 135px;
height: 30px;
font-weight: 600;
}
.menu {
height: 100%;
width: 100%;
flex-direction: column;
justify-content: flex-start;
position: relative;
color: white;
font-size: 20px;
font-weight: 550;
}
.menu-item {
height: 100%;
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
padding-left: 40px;
box-sizing: border-box;
}
a {
text-decoration: none;
color: white;
}
.sub-item {
padding-left: 50px;
}
li {
padding: 0px !important;
}
.menu-item img {
height: 30px;
width: 30px;
margin-right: 10px;
}
.el-menu {
background: transparent !important;
border: 0 !important;
}
.el-menu-item {
color: white !important;
background-color: #c5d2d8 !important;
font-size: 18px !important;
}
.el-menu-item:hover {
outline: 0;
background-color: #b8c1c5 !important;
}
.el-menu-item.is-active {
background-color: #c0cbd0 !important;
padding: 0px !important;
}
#logout {
bottom: 0;
width: 100%;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 20px;
font-weight: 550;
cursor: pointer;
border-radius: 5px;
}
#logout img {
height: 35px;
width: 35px;
margin-right: 15px;
margin-left: -30px;
}
#logout:hover {
background: #6fc5f1;
}
</style>

@ -0,0 +1,310 @@
<template>
<div id="student">
<div id="search-box">
<div class="search">
学号
<input type="text" id="number" v-model="no" @keyup.enter="search" />
</div>
<div class="search">
姓名
<input type="text" id="number" v-model="name" @keyup.enter="search" />
</div>
<button id="searchbtn" @click="search"></button>
<el-upload class="upload-demo" :action="`${baseURL}student/import`" :headers="{ 'Authorization': token }"
:before-upload="beforeUpload" :on-success="handleSuccess" :on-error="handleError" :show-file-list="false"
accept=".xls,.xlsx">
<el-button id="importbtn" type="primary">导入学生名单</el-button>
</el-upload>
</div>
<div id="banner">
<span id="span-no">学生学号</span>
<span id="span-name">姓名</span>
<span id="span-major">专业</span>
<span id="span-score">积分</span>
<span id="delete">删除按钮</span>
</div>
<div id="list">
<div class="list-item" v-for="(student, i) in pagedOrders" :key="i">
<span>{{ student.no }}</span>
<span>{{ student.name }}</span>
<span>{{ student.major }}</span>
<span>{{ student.score }}</span>
<!-- 按下修改按钮将索引 i 传递给方法 -->
<el-button type="danger" icon="el-icon-delete" circle @click="deleteStudent(student.id)"></el-button>
</div>
</div>
<el-pagination id="page" @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="currentPage" :page-sizes="[1, 2, 5, 10]" :page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="studentList.length">
</el-pagination>
</div>
</template>
<script>
import { DeleteStudent, StudentSearch } from "@/api/api";
import service from "@/utils/request.js"; //
export default {
name: "student",
data() {
return {
id: "",
no: "",
name: "",
course: "",
courseList: [],
studentList: [],
currentPage: 1,
pageSize: 5,
fileList: [],
baseURL: service.defaults.baseURL,
token: window.localStorage.getItem("token")
};
},
methods: {
handleSizeChange(val) {
this.pageSize = val;
},
handleCurrentChange(val) {
this.currentPage = val;
},
beforeUpload(file) {
const isExcel = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || file.type === 'application/vnd.ms-excel';
if (!isExcel) {
this.$message.error('只能上传 Excel 文件!');
}
return isExcel; // false
},
handleSuccess(response, file) {
if (response.code === 200) {
this.$message.success('文件上传成功');
} else {
this.$message.error('文件上传失败:' + response.message || '未知错误');
}
},
handleError(err, file) {
this.$message.error('文件上传失败');
},
search() {
StudentSearch({
no: this.no,
name: this.name,
pageSize: 10000,
pageNo: 1,
}).then((res) => {
if (res.code == 500) {
this.$message.error("请确保信息填写完整");
} else if (res.code == 200) {
if (res.data.total == 0) {
this.$message.error("没有查询到数据");
} else {
this.studentList = res.data.records;
console.log(this.studentList)
this.$message.success("查询成功");
}
} else if (res.code == 401) {
alert("登录过期,请重新登录!");
if (this.$route.path !== "/login") {
this.$router.push("/login");
}
}
});
},
deleteStudent(studentId) {
DeleteStudent(studentId) // studentId
.then((res) => {
if (res.code == 500) {
this.$message.error("删除失败");
} else if (res.code == 200) {
this.$message.success("删除成功");
//
this.studentList.splice(index, 1); //
}
})
.catch((error) => {
console.error(error); //
this.$message.error("请求失败,请稍后重试");
});
}
},
computed: {
pagedOrders() {
const startIndex = (this.currentPage - 1) * this.pageSize;
const endIndex = startIndex + this.pageSize;
return this.studentList.slice(startIndex, endIndex);
},
},
};
</script>
<style scoped>
#student {
box-sizing: border-box;
position: relative;
width: 85.4%;
height: 100vh;
/* background-image: url("../assets/background.png"); */
background-image: url("../assets/login/login-background.png");
background-size: cover;
display: flex;
flex-direction: column;
justify-content: flex-start;
overflow: hidden;
/* 隐藏溢出内容 */
}
#search-box {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
justify-content: center;
}
.search {
width: 20%;
margin-left: 20px;
font-size: 15px;
font-weight: bold;
}
#number {
width: 70%;
height: 40px;
border-radius: 10px;
border: 1px solid #ccc;
margin-top: 50px;
opacity: 0.7;
font-size: 15px;
text-align: center;
}
#searchbtn {
width: 80px;
height: 40px;
border-radius: 10px;
border: 0px;
margin-top: 50px;
margin-left: 20px;
opacity: 0.74;
font-size: 15px;
font-weight: bold;
background: #7e8081;
}
#searchbtn:hover {
background: #666769;
}
#searchbtn:active {
background: #666769;
}
#importbtn {
width: 150px;
height: 40px;
border-radius: 10px;
border: 0px;
margin-top: 50px;
margin-left: 20px;
opacity: 0.74;
font-size: 15px;
font-weight: bold;
background: #7e8081;
color: black;
}
#importbtn:hover {
background: #79797a;
}
#importbtn:active {
background: #666769;
}
#banner {
box-sizing: border-box;
position: absolute;
margin-left: 5.5%;
margin-top: 125px;
width: 90%;
height: 50px;
border-radius: 20px;
display: flex;
align-items: center;
background: #70707055;
justify-content: space-evenly;
}
#span-no {
margin: 0 0 0 35.5px;
}
#span-name {
margin: 0 0 0 60px;
}
#span-major {
margin: 0 0 0 50px;
}
#span-score {
margin: 0 15px 0 50px;
}
#delete {
margin: 0 30px 0 15px;
}
#list {
box-sizing: border-box;
position: absolute;
margin-left: 5.5%;
margin-top: 200px;
width: 90%;
height: 600px;
border-radius: 20px;
display: flex;
flex-direction: column;
justify-content: flex-start;
background: transparent;
}
.list-item {
box-sizing: border-box;
margin-top: 10px;
width: 100%;
height: 50px;
border-radius: 20px;
display: flex;
justify-content: space-evenly;
align-items: center;
background: #fffffff5;
box-shadow: 0 -3px 3px 0 #d4d2d2 inset;
}
.list-item:hover {
background: #95daff;
}
#page {
margin: auto;
margin-top: -80px;
z-index: 10;
/* 添加此行 */
position: relative;
/* 确保 z-index 生效 */
}
</style>

@ -0,0 +1,13 @@
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';
Vue.config.productionTip = false
Vue.use(ElementUI)
Vue.use(VueRouter)
new Vue({
render: h => h(App),
router,
}).$mount('#app')

@ -0,0 +1,54 @@
<template>
<div id="home">
<Sidebar id="sidebar"/>
<router-view></router-view>
<!-- <HomePart id="home-part"/> -->
</div>
</template>
<script>
import Sidebar from '../components/Sidebar'
import HomePart from '../components/HomePart'
export default {
name: 'Home',
components: {
Sidebar,
HomePart
}
}
</script>
<style scoped>
* {
margin: 0;
padding: 0;
scroll-behavior: smooth;
font-family: "微软雅黑";
}
#home {
display: flex;
flex-direction: row;
}
#home-part {
margin-left: -10px;
}
#overview {
margin-left: -10px;
}
#check-attendance{
margin-left: -10px;
}
#check-leave{
margin-left: -10px;
}
#empower{
margin-left: -10px;
}
#student{
margin-left: -10px;
}
#sidebar {
z-index: 2;
}
</style>

@ -0,0 +1,165 @@
<template>
<div id="background">
<div id="image">
<div id="cover">
<h1>登录</h1>
<input type="text" id="id" v-model="username" placeholder="用户名" /><br />
<input type="password" id="pwd" v-model="password" placeholder="密码" @keyup.enter="login" />
<button id="loginbtn" @click="login" >登录</button>
<router-link to="/register">
<button id="registerbtn">注册</button>
</router-link>
</div>
</div>
</div>
</template>
<script>
import { Login } from "@/api/api";
export default {
data() {
return {
username: "",
password: "",
};
},
methods: {
login() {
let loginData = {
username: this.username,
password: this.password,
};
Login(loginData).then(
(res) => {
if (res.code == 200) {
localStorage.setItem("token", res.data.token);
localStorage.setItem("name", res.data.name);
this.$message.success("登录成功!");
this.$router.push("/home");
} else {
this.$message.error(res.msg);
console.log(res);
}
},
(err) => {
this.$message.error(err.msg);
console.log(err);
}
);
},
},
};
</script>
<style scoped>
#background {
height: 100vh;
width: 100vw;
min-width: 1000px;
min-height: 500px;
background-image: url("../assets/login/login-background.png");
background-size: cover;
position: relative;
justify-content: center;
align-items: center;
display: flex;
}
#image {
width: 65.8%;
height: 60%;
min-width: 1000px;
min-height: 500px;
background-image: url("../assets/login/login-image.png");
background-size: cover;
box-sizing: border-box;
border-radius: 20px;
display: flex;
position: absolute;
justify-content: flex-end;
}
@keyframes move {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(0%);
}
}
#cover {
width: 50%;
height: 100%;
min-width: 500px;
background: rgba(255, 255, 255, 1);
background-size: fill;
box-sizing: border-box;
display: block;
justify-content: center;
align-items: center;
position: absolute;
border-radius: 20px;
animation: move 1s ease-in-out;
}
#cover h1 {
position: absolute;
left: 40%;
top: 10%;
font-size: 40px;
font-weight: normal;
line-height: 52px;
text-align: center;
letter-spacing: 0.1em;
color: #000000;
display: block;
}
#id {
box-sizing: border-box;
position: absolute;
padding: 5px 10px;
top: 32%;
left: 11%;
width: 75%;
height: 10%;
font-size: 20px;
border-radius: 15px;
border-width: 1px;
}
#pwd {
box-sizing: border-box;
position: absolute;
padding: 5px 10px;
top: 50%;
left: 11%;
width: 75%;
height: 10%;
font-size: 20px;
border-radius: 15px;
border-width: 1px;
}
button {
top: 70%;
left: 25%;
width: 100px;
height: 50px;
position: absolute;
font-size: 20px;
border-radius: 15px;
cursor: pointer;
border-width: 0px;
background-color: #77b6e1;
color: white;
transition: background-color ease-in-out 0.3s;
}
#registerbtn {
left: 52%;
}
button:hover {
background-color: #d69898;
color: #ffffff;
border-color: #000000;
}
button:active {
border-style: inset;
background-color: #d28686;
}
</style>

@ -0,0 +1,194 @@
<template>
<div id="background">
<div id="image">
<div id="cover">
<h1>注册</h1>
<input type="text" id="id" placeholder="用户名" v-model="username" /><br />
<input
type="password"
id="pwd"
placeholder="密码"
v-model="password"
/><br />
<input
type="password"
id="repwd"
placeholder="确认密码"
v-model="rePassword"
/><br />
<input
type="text"
id="name"
placeholder="真实姓名"
v-model="name"
/><br />
<router-link to="/login">
<button id="loginbtn">登录</button>
</router-link>
<button id="registerbtn" @click="register"></button>
</div>
</div>
</div>
</template>
<script>
import { GetCode, Register } from "@/api/api";
export default {
data() {
return {
id: "",
password: "",
rePassword: "",
name: "",
};
},
methods: {
register() {
if (this.username == "") {
this.$message.error("请输入用户名");
} else if (this.password == "") {
this.$message.error("请输入密码");
} else if (this.rePassword == "") {
this.$message.error("请确认密码");
} else if(this.password != this.rePassword){
this.$message.error("两次密码不一致,请重新输入");
} else {
Register({
username: this.username,
password: this.password,
name: this.name
}).then((res) => {
if (res.code == 200) {
this.$message.success("注册成功");
this.$router.push("/login");
} else {
this.$message.error(res.msg);
}
});
}
},
},
};
</script>
<style scoped>
#background {
height: 100vh;
width: 100vw;
min-width: 1000px;
min-height: 500px;
background-image: url("../assets/login/login-background.png");
background-size: cover;
position: relative;
justify-content: center;
align-items: center;
display: flex;
}
#image {
width: 65.8%;
height: 60%;
min-width: 1000px;
min-height: 500px;
background-image: url("../assets/login/login-image.png");
background-size: cover;
box-sizing: border-box;
border-radius: 20px;
display: flex;
position: absolute;
justify-content: flex-start;
}
@keyframes move {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0%);
}
}
#cover {
width: 50%;
height: 100%;
min-width: 500px;
background: rgba(255, 255, 255, 1);
background-size: fill;
box-sizing: border-box;
display: block;
justify-content: center;
align-items: center;
position: absolute;
border-radius: 20px;
animation: move 1s ease-in-out;
}
#cover h1 {
position: absolute;
left: 40%;
top: 10%;
font-size: 40px;
font-weight: normal;
line-height: 52px;
text-align: center;
letter-spacing: 0.1em;
color: #000000;
display: block;
}
input {
box-sizing: border-box;
position: absolute;
padding: 5px 10px;
left: 11%;
width: 75%;
height: 10%;
font-size: 20px;
border-radius: 15px;
border-width: 1px;
}
#id {
top: 24%;
}
#pwd {
top: 36%;
}
#repwd {
top: 48%;
}
#name {
top: 60%;
}
button {
top: 87.5%;
left: 25%;
width: 100px;
height: 50px;
position: absolute;
font-size: 20px;
border-radius: 15px;
cursor: pointer;
border-width: 0px;
background-color: #77b6e1;
color: white;
transition: background-color ease-in-out 0.3s;
}
#codeform {
display: flex;
}
#registerbtn {
left: 52%;
}
#getcode {
width: 30%;
top: 72.5%;
left: 56%;
}
button:hover {
background-color: #d69898;
color: #ffffff;
border-color: #000000;
}
button:active {
border-style: inset;
background-color: #d28686;
}
</style>

@ -0,0 +1,45 @@
import VueRouter from "vue-router";
import Login from "../pages/Login";
import Register from "../pages/Register";
import Home from "../pages/Home";
import HomePart from "../components/HomePart";
import Student from "../components/Student";
import RollCall from "../components/RollCall";
import Question from "../components/Question";
export default new VueRouter({
mode: 'history',
routes: [{
path: '/login',
component: Login
},
{
path: '/register',
component: Register
},
{
path: '/',
component: Home,
children: [
{
path: 'home',
component: HomePart
},
{
path: 'student',
component: Student
},
{
path: 'roll-call',
component: RollCall
},
{
path: '/question',
name: 'question',
component: Question, // 这是你的组件
}
],
redirect: '/home'
}
]
})

@ -0,0 +1,48 @@
import axios from "axios";
// 创建axios赋给变量service
export const service = axios.create({
baseURL: `http://8.210.250.29:9090/`,
// baseURL: `http://localhost:9090/`,
timeout: 150000, // 超时
crossDomain: true,
});
//添加请求拦截器
service.interceptors.request.use(
//在发请求之前打上token和跨域的标记
function (config) {
//在这里,代码将 "Access-Control-Allow-Origin" 的值设置为 "*"
// 意味着允许所有的源(即任意域名)进行跨域访问。这种配置通常用于开发
// 和测试环境,以便允许任何源发起跨域请求。
config.headers['Access-Control-Allow-Origin'] = '*';
config.headers['Access-Control-Allow-Credentials'] = true;
let token = window.localStorage.getItem("token")
if (token) {
//我们headers里面的令牌取名叫Authorization
config.headers.Authorization = token;
}
return config;
},
function (error) {
//返回一个被拒绝的 Promise 对象,并将错误 error 作为拒绝的原因
return Promise.resolve(error.response.data);
}
);
//添加响应拦截器
service.interceptors.response.use(
function (response) {
//使用响应数据返回响应;
const data = response;
// 如果data有东西就返回data.data如果没有就返回response
if (data) {
return Promise.resolve(data.data);
}
return response;
},
function (error) {
//返回一个被拒绝的 Promise 对象,并将错误 error 作为拒绝的原因
return Promise.resolve(error.response.data);
}
);
// 将service 导出
export default service;

@ -0,0 +1,62 @@
import re
from pathlib import Path
from pathlib import WindowsPath
from typing import Optional, List
class DirectionTree:
def __init__(self,
direction_name: str = 'WorkingDirection',
direction_path: str = '.',
ignore_list: Optional[List[str]] = None):
self.owner: WindowsPath = Path(direction_path)
self.tree: str = direction_name + '/\n'
self.ignore_list = ignore_list
if ignore_list is None:
self.ignore_list = []
self.direction_ergodic(path_object=self.owner, n=0)
def tree_add(self, path_object: WindowsPath, n=0, last=False):
if n > 0:
if last:
self.tree += '' + ('' * (n - 1)) + ' └────' + path_object.name
else:
self.tree += '' + ('' * (n - 1)) + ' ├────' + path_object.name
else:
if last:
self.tree += '' + ('──' * 2) + path_object.name
else:
self.tree += '' + ('──' * 2) + path_object.name
if path_object.is_file():
self.tree += '\n'
return False
elif path_object.is_dir():
self.tree += '/\n'
return True
def filter_file(self, file):
for item in self.ignore_list:
if re.fullmatch(item, file.name):
return False
return True
def direction_ergodic(self, path_object: WindowsPath, n=0):
dir_file: list = list(path_object.iterdir())
dir_file.sort(key=lambda x: x.name.lower())
dir_file = [f for f in filter(self.filter_file, dir_file)]
for i, item in enumerate(dir_file):
if i + 1 == len(dir_file):
if self.tree_add(item, n, last=True):
self.direction_ergodic(item, n + 1)
else:
if self.tree_add(item, n, last=False):
self.direction_ergodic(item, n + 1)
if __name__ == '__main__':
i_l = [
'\.git', '__pycache__', 'test.+', 'venv', '.+\.whl', '\.idea', '.+\.jpg', '.+\.png',
'image', 'css', 'admin', 'db.sqlite3'
]
tree = DirectionTree(ignore_list=i_l, direction_path='./')
print(tree.tree)

@ -0,0 +1,16 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false,
devServer: {
open: false, // 编译完成是否打开网页
host: '0.0.0.0', // 指定使用地址默认localhost,0.0.0.0代表可以被外界访问
port: 9091, // 访问端口
proxy: {
}
},
})
Loading…
Cancel
Save