Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
poppoppuppylove | 4240c272a5 | 1 month ago |
poppoppuppylove | 6ccc9f01f8 | 1 month ago |
@ -1,9 +0,0 @@
|
||||
<?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>
|
@ -1,6 +1,14 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="AliControlFlowStatementWithoutBraces" enabled="true" level="WEAK WARNING" enabled_by_default="true" editorAttributes="INFO_ATTRIBUTES" />
|
||||
<inspection_tool class="AliAccessStaticViaInstance" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AliArrayNamingShouldHaveBracket" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AliControlFlowStatementWithoutBraces" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AliDeprecation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AliEqualsAvoidNull" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AliLongLiteralsEndingWithLowercaseL" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AliMissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="AliWrapperTypeEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="MapOrSetKeyShouldOverrideHashCodeEquals" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Palette2">
|
||||
<group name="Swing">
|
||||
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
|
||||
</item>
|
||||
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
|
||||
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
|
||||
<initial-values>
|
||||
<property name="text" value="Button" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="RadioButton" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="CheckBox" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="Label" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
|
||||
<preferred-size width="-1" height="20" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
|
||||
</item>
|
||||
</group>
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,19 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
wrapperVersion=3.3.2
|
||||
distributionType=only-script
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
|
@ -1,99 +0,0 @@
|
||||
<?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="3b22332f-54e5-44db-aa06-c42dcb64501b" name="更改" comment="">
|
||||
<change afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/AttendanceApplication.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/config/SecurityConfig.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/controller/RollCallController.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/controller/StudentController.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/controller/TeacherController.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/entity/PointsRequest.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/entity/RollCallResponse.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/entity/RollCallSettings.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/entity/Student.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/entity/Teacher.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/mapper/StudentMapper.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/mapper/TeacherMapper.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/service/RollCallService.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/service/StudentService.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/service/TeacherService.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/service/impl/RollCallServiceImpl.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/service/impl/StudentServiceImpl.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/service/impl/TeacherServiceImpl.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/java/com/example/attendance/util/JWTUtil.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main/resources/application.properties" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/test/java/com/example/attendance/AttendanceApplicationTests.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/test/java/com/example/attendance/PasswordEncryptor.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/test/java/com/example/attendance/RollCallServiceImplTest.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/test/java/com/example/attendance/StudentServiceImplTest.java" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/test/java/com/example/attendance/TeacherServiceImplTest.java" beforeDir="false" />
|
||||
</list>
|
||||
<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="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"associatedIndex": 3
|
||||
}</component>
|
||||
<component name="ProjectId" id="2n7TontcBHYq0FZUtB4aOJMP8nL" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="openDirectoriesWithSingleClick" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"WebServerToolWindowFactoryState": "false",
|
||||
"git-widget-placeholder": "main",
|
||||
"last_opened_file_path": "C:/Users/onelastkiss/Desktop/项目/bigbigmarket",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}</component>
|
||||
<component name="RunManager">
|
||||
<configuration default="true" type="JetRunConfigurationType">
|
||||
<module name="BACKEND_ATTENDANCE" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration default="true" type="KotlinStandaloneScriptRunConfigurationType">
|
||||
<module name="BACKEND_ATTENDANCE" />
|
||||
<option name="filePath" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</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="3b22332f-54e5-44db-aa06-c42dcb64501b" name="更改" comment="" />
|
||||
<created>1728323291653</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1728323291653</updated>
|
||||
<workItem from="1728323292792" duration="28000" />
|
||||
<workItem from="1728372981142" duration="128000" />
|
||||
<workItem from="1728379806368" duration="345000" />
|
||||
<workItem from="1728386733302" duration="673000" />
|
||||
<workItem from="1728402818365" duration="864000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
</project>
|
@ -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
|
@ -1,33 +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()) // 关闭 CSRF 保护
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.authorizeHttpRequests(authz -> authz
|
||||
.requestMatchers("/api/teacher/register", "/api/teacher/login").permitAll() // 允许注册和登录接口匿名访问
|
||||
.anyRequest().authenticated() // 其他请求需要认证
|
||||
.requestMatchers(HttpMethod.POST, "/api/teacher/login", "/api/teacher/register").permitAll()
|
||||
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(form -> form.disable()) // 关闭表单登录
|
||||
.httpBasic(httpBasic -> httpBasic.disable()); // 关闭基本认证
|
||||
.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;
|
||||
}
|
||||
}
|
@ -1,13 +1,16 @@
|
||||
package com.example.attendance.entity;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public class PointsRequest {
|
||||
private BigDecimal pointsDelta;
|
||||
|
||||
public BigDecimal getPointsDelta() {
|
||||
return pointsDelta;
|
||||
}
|
||||
|
||||
public void setPointsDelta(BigDecimal pointsDelta) {
|
||||
this.pointsDelta = pointsDelta;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,7 +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
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package com.example.attendance;
|
||||
|
||||
import com.example.attendance.entity.RollCallResponse;
|
||||
import com.example.attendance.entity.RollCallSettings;
|
||||
import com.example.attendance.entity.Student;
|
||||
import com.example.attendance.service.impl.RollCallServiceImpl;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class RollCallServiceImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private RollCallServiceImpl rollCallService;
|
||||
|
||||
private List<Student> students;
|
||||
private RollCallSettings settings;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
|
||||
// 模拟学生列表
|
||||
students = Arrays.asList(
|
||||
createStudent("1", "Alice", 50),
|
||||
createStudent("2", "Bob", 90),
|
||||
createStudent("3", "Charlie", 30)
|
||||
);
|
||||
|
||||
// 初始化点名设置
|
||||
settings = new RollCallSettings();
|
||||
settings.setRollCallMode("点名");
|
||||
settings.setTriggerRandomEvent("不触发");
|
||||
settings.setWheelOfFortune("否");
|
||||
}
|
||||
|
||||
// 测试正常点名模式
|
||||
@Test
|
||||
void testStartRollCall_NormalMode() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
RollCallResponse response = rollCallService.startRollCall(students, settings);
|
||||
|
||||
// 控制台输出
|
||||
System.out.println("Test: Normal Mode");
|
||||
System.out.println("Student ID: " + response.getStudentId());
|
||||
System.out.println("Message: " + response.getMessage());
|
||||
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getStudentId());
|
||||
assertTrue(response.getMessage().contains("没有触发随机事件"));
|
||||
}
|
||||
}
|
||||
|
||||
// 测试提问模式
|
||||
@Test
|
||||
void testStartRollCall_QuestionMode() {
|
||||
settings.setRollCallMode("提问"); // 设置为提问模式
|
||||
RollCallResponse response = rollCallService.startRollCall(students, settings);
|
||||
|
||||
// 控制台输出
|
||||
System.out.println("Test: Question Mode");
|
||||
System.out.println("Student ID: " + response.getStudentId());
|
||||
System.out.println("Message: " + response.getMessage());
|
||||
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getStudentId());
|
||||
assertTrue(response.getMessage().contains("没有触发随机事件"));
|
||||
}
|
||||
|
||||
// 测试命运轮盘模式
|
||||
@Test
|
||||
void testStartRollCall_WheelOfFortune() {
|
||||
settings.setWheelOfFortune("是"); // 开启命运轮盘
|
||||
|
||||
RollCallResponse response = rollCallService.startRollCall(students, settings);
|
||||
|
||||
// 控制台输出
|
||||
System.out.println("Test: Wheel of Fortune Mode");
|
||||
System.out.println("Student ID: " + response.getStudentId());
|
||||
System.out.println("Message: " + response.getMessage());
|
||||
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getStudentId());
|
||||
assertTrue(response.getMessage().contains("命运轮盘"));
|
||||
}
|
||||
|
||||
// 测试触发随机事件
|
||||
@Test
|
||||
void testStartRollCall_TriggerRandomEvent() {
|
||||
settings.setTriggerRandomEvent("触发"); // 触发随机事件
|
||||
|
||||
RollCallResponse response = rollCallService.startRollCall(students, settings);
|
||||
|
||||
// 控制台输出
|
||||
System.out.println("Test: Random Event Triggered");
|
||||
System.out.println("Student ID: " + response.getStudentId());
|
||||
System.out.println("Message: " + response.getMessage());
|
||||
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getStudentId());
|
||||
assertTrue(response.getMessage().contains("触发了随机事件"));
|
||||
}
|
||||
|
||||
// 辅助方法,创建学生
|
||||
private Student createStudent(String id, String name, int points) {
|
||||
Student student = new Student();
|
||||
student.setStudentNumber(id);
|
||||
student.setName(name);
|
||||
student.setPoints(BigDecimal.valueOf(points));
|
||||
return student;
|
||||
}
|
||||
}
|
@ -1,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");
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/点名.svg">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
{
|
||||
"name": "name-nacing-system",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.7",
|
||||
"element-plus": "^2.8.4",
|
||||
"element-ui": "^2.15.14",
|
||||
"vue": "^3.4.29",
|
||||
"vue-router": "^4.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"vite": "^5.3.1"
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* 让页面铺满整个屏幕,不出现滚动条 */
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "Layout",
|
||||
|
||||
}
|
||||
</script>
|
@ -1,121 +0,0 @@
|
||||
.login-register-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-image: linear-gradient(to right, rgb(219, 219, 245), #f4dfe3);
|
||||
}
|
||||
|
||||
.form-box {
|
||||
background: rgba(211, 197, 252, 1);
|
||||
padding: 100px 110px;
|
||||
padding-top: 15px;
|
||||
border-radius: 40px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
color: #FFFFFF;
|
||||
margin-bottom: 30px;
|
||||
font-size: 45px;
|
||||
}
|
||||
|
||||
.tab-switch {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab-switch span {
|
||||
margin-right: 25px;
|
||||
font-size: 22px;
|
||||
padding: 3px 7px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.tab-switch span.active {
|
||||
border-bottom: 4px solid #FFFFFF;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 10px 0px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 0px 10px 0px #D5E8FF;
|
||||
background-color: rgba(243, 243, 243, 0.5);
|
||||
border: 1px solid #FFFFFF;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.input-group input:focus {
|
||||
outline: none;
|
||||
/* box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.5); */
|
||||
}
|
||||
.input-icon {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin-left: 15px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.placeholder {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
input {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0 10px;
|
||||
color: #615EF8;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.forgot-password {
|
||||
text-align: right;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.forgot-password a {
|
||||
color: #FFFFFF;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
padding: 12px 0;
|
||||
margin-top: 15px;
|
||||
background-color: #8755F2;
|
||||
color: #FFFFFF;
|
||||
border-radius: 20px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
box-shadow: 0px 0px 10px 0px #D5E8FF;
|
||||
border: 1px solid #FFFFFF;
|
||||
}
|
||||
|
||||
.submit-btn:hover {
|
||||
background-color: #6231F5;
|
||||
}
|
||||
|
||||
|
||||
/* 隐藏默认的密码切换图标 */
|
||||
input[type="password"]::-ms-reveal,
|
||||
input[type="password"]::-ms-clear,
|
||||
input[type="password"]::-webkit-contacts-auto-fill-button,
|
||||
input[type="password"]::-webkit-credentials-auto-fill-button {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 对于Chrome浏览器 */
|
||||
input[type="password"]::-webkit-inner-spin-button,
|
||||
input[type="password"]::-webkit-outer-spin-button,
|
||||
input[type="password"]::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.3 KiB |
@ -1,21 +0,0 @@
|
||||
// 引入createApp用于创建应用
|
||||
import { createApp } from 'vue'
|
||||
// 引入App.vue文件
|
||||
import App from './App.vue'
|
||||
|
||||
import router from './router'
|
||||
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
|
||||
|
||||
// new Vue({
|
||||
// el: '#app',
|
||||
// render: h => h(App)
|
||||
// });
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(router)
|
||||
app.use(ElementPlus)
|
||||
app.mount('#app')
|
@ -1,38 +0,0 @@
|
||||
import { createRouter, createWebHistory} from 'vue-router'
|
||||
|
||||
// 引入路由组件
|
||||
import login from '@/views/login2.vue'
|
||||
import home from '@/views/home.vue'
|
||||
import importFile from '@/views/importFile.vue'
|
||||
import ruleSetting from '@/views/ruleSetting.vue'
|
||||
|
||||
// 创建路由器
|
||||
const router = createRouter({
|
||||
// 路由器的工作模式
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
// name: 'home',
|
||||
component: login
|
||||
},
|
||||
{
|
||||
path:'/home',
|
||||
component: home,
|
||||
children:[
|
||||
{
|
||||
path:'importFile',
|
||||
component: importFile
|
||||
},
|
||||
{
|
||||
path:'ruleSetting',
|
||||
component: ruleSetting
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
})
|
||||
|
||||
export default router
|
@ -1,18 +0,0 @@
|
||||
// 存储、获取、删除token的文件
|
||||
// 定义 setToken 函数,用于将 token 存储到 localStorage
|
||||
export function setToken(token) {
|
||||
localStorage.setItem('authToken', token);
|
||||
}
|
||||
|
||||
// 定义 getToken 函数,用于从 localStorage 获取 token
|
||||
export function getToken() {
|
||||
return localStorage.getItem('authToken');
|
||||
}
|
||||
|
||||
// 定义 removeToken 函数,用于从 localStorage 删除 token
|
||||
export function removeToken() {
|
||||
localStorage.removeItem('authToken');
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,80 +0,0 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<div class="content">
|
||||
<!-- 展示区 -->
|
||||
<div class="leftIcon">
|
||||
<RouterView></RouterView>
|
||||
</div>
|
||||
<!-- 导航栏 -->
|
||||
<div class="select">
|
||||
<div @click="gotoImpt" class="selectItem">
|
||||
导入文件
|
||||
</div>
|
||||
<div class="selectItem">
|
||||
开始点名
|
||||
</div>
|
||||
<div @click="gotoRule" class="selectItem">
|
||||
规则设置
|
||||
</div>
|
||||
<div class="selectItem">
|
||||
查看排行
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { RouterLink, RouterView } from 'vue-router';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
gotoImpt(){
|
||||
this.$router.push('/home/importFile')
|
||||
},
|
||||
gotoRule() {
|
||||
this.$router.push('/home/ruleSetting')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home {
|
||||
background-image: url('../assets/image/bgcImg.png'); /* 图片路径 */
|
||||
width:100%;
|
||||
height:100%;
|
||||
position:fixed;
|
||||
background-size:100% 100%;
|
||||
}
|
||||
.content {
|
||||
height:100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 120px;
|
||||
}
|
||||
.leftIcon {
|
||||
width: 700px;
|
||||
height: 800px;
|
||||
/* background-color: red; */
|
||||
}
|
||||
.selectItem {
|
||||
padding: 25px 130px;
|
||||
border-radius: 50px;
|
||||
background-color: #A182FF;
|
||||
color: #FFFFFF;
|
||||
font-size: 35px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 65px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.selectItem:hover {
|
||||
background-color: #6231F5;
|
||||
}
|
||||
</style>
|
@ -1,16 +0,0 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
}
|
||||
})
|
@ -0,0 +1,141 @@
|
||||
<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>
|
||||
</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>
|
||||
<scope>runtime</scope>
|
||||
</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 -->
|
||||
<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>
|
||||
|
||||
</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>
|
||||
</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,33 @@
|
||||
package com.example.attendance.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(csrf -> csrf.disable()) // 关闭 CSRF 保护
|
||||
.authorizeHttpRequests(authz -> authz
|
||||
.requestMatchers("/api/teacher/register", "/api/teacher/login").permitAll() // 允许注册和登录接口匿名访问
|
||||
.anyRequest().authenticated() // 其他请求需要认证
|
||||
)
|
||||
.formLogin(form -> form.disable()) // 关闭表单登录
|
||||
.httpBasic(httpBasic -> httpBasic.disable()); // 关闭基本认证
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.example.attendance.controller;
|
||||
|
||||
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.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/rollcall")
|
||||
public class RollCallController {
|
||||
|
||||
private final RollCallService rollCallService;
|
||||
|
||||
public RollCallController(RollCallService rollCallService) {
|
||||
this.rollCallService = rollCallService;
|
||||
}
|
||||
|
||||
@PostMapping("/start")
|
||||
public RollCallResponse startRollCall(@RequestBody List<Student> students, @RequestBody RollCallSettings settings) {
|
||||
return rollCallService.startRollCall(students, settings);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.example.attendance.controller;
|
||||
|
||||
import com.example.attendance.service.TeacherService;
|
||||
import com.example.attendance.service.impl.TeacherServiceImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@RestController
|
||||
@RequestMapping("/api/teacher")
|
||||
public class TeacherController {
|
||||
|
||||
@Autowired
|
||||
private TeacherService teacherService;
|
||||
|
||||
// 注册接口
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<String> register(@RequestParam String username, @RequestParam String password) {
|
||||
try {
|
||||
// 调用 teacherService 的 register 方法注册用户
|
||||
teacherService.register(username, password);
|
||||
return ResponseEntity.ok("注册成功"); // 成功时返回 200 状态和消息
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.badRequest().body("注册失败: " + e.getMessage()); // 失败时返回 400 状态和错误信息
|
||||
}
|
||||
}
|
||||
|
||||
// 登录接口
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<String> login(@RequestParam String username, @RequestParam String password) {
|
||||
try {
|
||||
// 调用 teacherService 的 login 方法登录
|
||||
String token = teacherService.login(username, password);
|
||||
return ResponseEntity.ok("登录成功, Token: " + token); // 成功时返回 200 状态和 JWT token
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.badRequest().body("登录失败: " + e.getMessage()); // 失败时返回 400 状态和错误信息
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.example.attendance.entity;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public class PointsRequest {
|
||||
private BigDecimal 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,56 @@
|
||||
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 student WHERE id = #{id}")
|
||||
Student findById(Long id);
|
||||
|
||||
@Select("SELECT * FROM student WHERE student_number = #{studentNumber}")
|
||||
Student findByStudentNumber(String studentNumber);
|
||||
|
||||
@Select("SELECT * FROM student")
|
||||
List<Student> findAll();
|
||||
|
||||
@Insert("INSERT INTO student (student_number, name, points) VALUES (#{studentNumber}, #{name}, #{points})")
|
||||
@Options(useGeneratedKeys = true, keyProperty = "id")
|
||||
void save(Student student);
|
||||
|
||||
@Update("UPDATE student SET name = #{name}, points = #{points} WHERE student_number = #{studentNumber}")
|
||||
void update(Student student);
|
||||
|
||||
@Delete("DELETE FROM student WHERE id = #{id}")
|
||||
void delete(Long id);
|
||||
|
||||
@Insert({
|
||||
"<script>",
|
||||
"INSERT INTO student (student_number, name, points) VALUES ",
|
||||
"<foreach collection='students' item='student' separator=','>",
|
||||
"(#{student.studentNumber}, #{student.name}, #{student.points})",
|
||||
"</foreach>",
|
||||
"</script>"
|
||||
})
|
||||
void saveStudents(List<Student> students);
|
||||
|
||||
/**
|
||||
* 按积分从高到低排序的分页查询
|
||||
* @param offset 分页查询的偏移量
|
||||
* @param size 每页显示的记录数
|
||||
* @return 排行榜学生列表
|
||||
*/
|
||||
@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();
|
||||
}
|
@ -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(int page, int size);
|
||||
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();
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.example.attendance.service;
|
||||
|
||||
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;
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
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 {
|
||||
|
||||
@Autowired
|
||||
private TeacherMapper teacherMapper;
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.example.attendance.util;
|
||||
|
||||
import com.example.attendance.entity.Teacher;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Date;
|
||||
|
||||
public class JWTUtil {
|
||||
|
||||
private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
|
||||
|
||||
// 生成 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(SignatureAlgorithm.HS256, SECRET_KEY) // 使用 HS256 签名算法和 SECRET_KEY 签名
|
||||
.compact();
|
||||
}
|
||||
|
||||
// 从 Token 中提取声明
|
||||
public static Claims extractClaims(String token) {
|
||||
try {
|
||||
return Jwts.parser()
|
||||
.setSigningKey(SECRET_KEY) // 设置签名密钥
|
||||
.parseClaimsJws(token) // 解析 Token
|
||||
.getBody();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Invalid JWT token", 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,7 @@
|
||||
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
|
||||
|
||||
|
Before Width: | Height: | Size: 27 MiB After Width: | Height: | Size: 27 MiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB |
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/点名.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>点点小助手</title>
|
||||
<script type="module" crossorigin src="/assets/index-Cb5FnDWH.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-B1hDzaLh.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
After Width: | Height: | Size: 649 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@ -0,0 +1,7 @@
|
||||
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
|
||||
|
||||
|