Browse Source

添加湖南大学选课系统分析项目到w4文件夹

w4
范馨遥 3 weeks ago
commit
89413b0f2a
  1. 23
      .gitignore
  2. 76
      pom.xml
  3. 11
      src/main/java/com/example/HnuCourseAnalysisApplication.java
  4. 88
      src/main/java/com/example/analyzer/CourseAnalyzer.java
  5. 9
      src/main/java/com/example/config/WebConfig.java
  6. 74
      src/main/java/com/example/controller/CourseController.java
  7. 95
      src/main/java/com/example/crawler/CourseCrawler.java
  8. 80
      src/main/java/com/example/crawler/LoginHandler.java
  9. 157
      src/main/java/com/example/entity/Course.java
  10. 25
      src/main/java/com/example/repository/CourseRepository.java
  11. 18
      src/main/resources/application.properties
  12. 24
      src/main/resources/schema.sql
  13. 171
      src/main/resources/templates/analysis.html
  14. 63
      src/main/resources/templates/course-list.html
  15. 24
      src/main/resources/templates/error.html
  16. 76
      src/main/resources/templates/index.html
  17. 13
      src/test/java/com/example/HnuCourseAnalysisApplicationTests.java

23
.gitignore

@ -0,0 +1,23 @@
# Maven构建产物
target/
# IDE相关文件
.idea/
.vscode/
# 数据库文件
*.db
# 系统文件
.DS_Store
Thumbs.db
# 日志文件
*.log
# 临时文件
*.tmp
*.temp
# 批处理文件
run.bat

76
pom.xml

@ -0,0 +1,76 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>hnu-course-analysis</artifactId>
<version>1.0-SNAPSHOT</version>
<name>湖南大学选课系统分析</name>
<description>湖南大学选课系统数据爬取、分析与可视化</description>
<properties>
<java.version>17</java.version>
<spring-boot.version>3.2.4</spring-boot.version>
<selenium.version>4.18.1</selenium.version>
<jsoup.version>1.17.2</jsoup.version>
<sqlite.version>3.45.3.0</sqlite.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>${jsoup.version}</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>${sqlite.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

11
src/main/java/com/example/HnuCourseAnalysisApplication.java

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

88
src/main/java/com/example/analyzer/CourseAnalyzer.java

@ -0,0 +1,88 @@
package com.example.analyzer;
import com.example.entity.Course;
import com.example.repository.CourseRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class CourseAnalyzer {
@Autowired
private CourseRepository courseRepository;
// 获取整体统计信息
public Map<String, Object> getOverallStatistics() {
Map<String, Object> statistics = new HashMap<>();
try {
List<Course> allCourses = courseRepository.findAll();
// 总课程数
statistics.put("totalCourses", allCourses.size());
// 总学分
double totalCredits = allCourses.stream()
.mapToDouble(Course::getCredit)
.sum();
statistics.put("totalCredits", totalCredits);
// 平均学分
double avgCredit = allCourses.isEmpty() ? 0 : totalCredits / allCourses.size();
statistics.put("averageCredit", avgCredit);
// 课程类型统计
List<Map<String, Object>> courseTypeStats = courseRepository.getCourseTypeStatistics();
statistics.put("courseTypeStatistics", courseTypeStats);
// 院系统计
List<Map<String, Object>> departmentStats = courseRepository.getDepartmentStatistics();
statistics.put("departmentStatistics", departmentStats);
// 选课情况统计
List<Map<String, Object>> enrollmentStats = courseRepository.getCourseEnrollmentStatistics();
statistics.put("enrollmentStatistics", enrollmentStats);
} catch (Exception e) {
System.err.println("获取整体统计信息失败:" + e.getMessage());
// 返回默认值
statistics.put("totalCourses", 0);
statistics.put("totalCredits", 0);
statistics.put("averageCredit", 0);
statistics.put("courseTypeStatistics", List.of());
statistics.put("departmentStatistics", List.of());
statistics.put("enrollmentStatistics", List.of());
}
return statistics;
}
// 获取院系统计信息
public List<Map<String, Object>> getDepartmentStatistics() {
try {
return courseRepository.getDepartmentStatistics();
} catch (Exception e) {
System.err.println("获取院系统计信息失败:" + e.getMessage());
return List.of();
}
}
// 获取课程类型统计信息
public List<Map<String, Object>> getCourseTypeStatistics() {
try {
return courseRepository.getCourseTypeStatistics();
} catch (Exception e) {
System.err.println("获取课程类型统计信息失败:" + e.getMessage());
return List.of();
}
}
// 获取选课情况统计信息
public List<Map<String, Object>> getEnrollmentStatistics() {
try {
return courseRepository.getCourseEnrollmentStatistics();
} catch (Exception e) {
System.err.println("获取选课情况统计信息失败:" + e.getMessage());
return List.of();
}
}
}

9
src/main/java/com/example/config/WebConfig.java

@ -0,0 +1,9 @@
package com.example.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 可以在这里添加Web配置
}

74
src/main/java/com/example/controller/CourseController.java

@ -0,0 +1,74 @@
package com.example.controller;
import com.example.analyzer.CourseAnalyzer;
import com.example.crawler.CourseCrawler;
import com.example.entity.Course;
import com.example.repository.CourseRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
public class CourseController {
@Autowired
private CourseCrawler courseCrawler;
@Autowired
private CourseAnalyzer courseAnalyzer;
@Autowired
private CourseRepository courseRepository;
// 首页
@GetMapping("/")
public String index() {
return "index";
}
// 爬取课程数据
@PostMapping("/crawl")
@ResponseBody
public Map<String, Object> crawlCourses() {
List<Course> courses = courseCrawler.crawlCourses();
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "成功爬取 " + courses.size() + " 门课程");
response.put("data", courses);
return response;
}
// 查看课程列表
@GetMapping("/courses")
public String courseList(Model model) {
List<Course> courses = courseRepository.findAll();
model.addAttribute("courses", courses);
return "course-list";
}
// 查看数据分析
@GetMapping("/analysis")
public String analysis(Model model) {
Map<String, Object> statistics = courseAnalyzer.getOverallStatistics();
model.addAttribute("statistics", statistics);
return "analysis";
}
// 获取分析数据(用于前端图表)
@GetMapping("/api/analysis")
@ResponseBody
public Map<String, Object> getAnalysisData() {
return courseAnalyzer.getOverallStatistics();
}
// 错误处理
@GetMapping("/error")
public String error() {
return "error";
}
}

95
src/main/java/com/example/crawler/CourseCrawler.java

@ -0,0 +1,95 @@
package com.example.crawler;
import com.example.entity.Course;
import com.example.repository.CourseRepository;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Component
public class CourseCrawler {
@Autowired
private LoginHandler loginHandler;
@Autowired
private CourseRepository courseRepository;
@Value("${crawler.hnu.course.list.url}")
private String courseListUrl;
public List<Course> crawlCourses() {
WebDriver driver = null;
List<Course> courses = new ArrayList<>();
try {
// 登录
driver = loginHandler.login();
if (driver == null) {
System.err.println("登录失败,无法爬取课程数据");
return courses;
}
// 访问课程列表页面
driver.get(courseListUrl);
// 等待页面加载完成
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
wait.until(ExpectedConditions.presenceOfElementLocated(By.className("gridtable")));
// 获取页面源码
String pageSource = driver.getPageSource();
// 使用Jsoup解析页面
Document doc = Jsoup.parse(pageSource);
Elements courseRows = doc.select(".gridtable tr:not(:first-child)");
// 解析课程数据
for (Element row : courseRows) {
Elements cells = row.select("td");
if (cells.size() >= 10) {
Course course = new Course();
course.setCourseCode(cells.get(0).text().trim());
course.setCourseName(cells.get(1).text().trim());
course.setCredit(Double.parseDouble(cells.get(2).text().trim()));
course.setTeacher(cells.get(3).text().trim());
course.setDepartment(cells.get(4).text().trim());
course.setCapacity(Integer.parseInt(cells.get(5).text().trim()));
course.setEnrolled(Integer.parseInt(cells.get(6).text().trim()));
course.setClassTime(cells.get(7).text().trim());
course.setClassRoom(cells.get(8).text().trim());
course.setCourseType(cells.get(9).text().trim());
course.setSemester("2024-2025-2");
course.setCreateTime(LocalDateTime.now());
courses.add(course);
}
}
// 保存到数据库
courseRepository.saveAll(courses);
System.out.println("成功爬取并保存 " + courses.size() + " 门课程");
} catch (Exception e) {
System.err.println("爬取课程数据失败:" + e.getMessage());
e.printStackTrace();
} finally {
if (driver != null) {
loginHandler.logout();
}
}
return courses;
}
}

80
src/main/java/com/example/crawler/LoginHandler.java

@ -0,0 +1,80 @@
package com.example.crawler;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.time.Duration;
@Component
public class LoginHandler {
@Value("${crawler.hnu.login.url}")
private String loginUrl;
@Value("${crawler.hnu.username}")
private String username;
@Value("${crawler.hnu.password}")
private String password;
private WebDriver driver;
public WebDriver login() {
setupDriver();
try {
driver.get(loginUrl);
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
// 等待用户名输入框出现
WebElement usernameElement = wait.until(ExpectedConditions.presenceOfElementLocated(By.id("yhm")));
usernameElement.sendKeys(username);
// 输入密码
WebElement passwordElement = driver.findElement(By.id("mm"));
passwordElement.sendKeys(password);
// 点击登录按钮
WebElement loginButton = driver.findElement(By.id("dl"));
loginButton.click();
// 等待登录成功
wait.until(ExpectedConditions.titleContains("湖南大学"));
System.out.println("登录成功");
return driver;
} catch (Exception e) {
System.err.println("登录失败:" + e.getMessage());
e.printStackTrace();
if (driver != null) {
driver.quit();
}
return null;
}
}
public void logout() {
if (driver != null) {
try {
// 这里可以添加退出登录的逻辑
System.out.println("退出登录");
} finally {
driver.quit();
driver = null;
}
}
}
private void setupDriver() {
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless"); // 无头模式
options.addArguments("--disable-gpu");
options.addArguments("--no-sandbox");
options.addArguments("--disable-dev-shm-usage");
driver = new ChromeDriver(options);
}
}

157
src/main/java/com/example/entity/Course.java

@ -0,0 +1,157 @@
package com.example.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Column;
import java.time.LocalDateTime;
@Entity
@Table(name = "courses")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "course_code")
private String courseCode;
@Column(name = "course_name")
private String courseName;
@Column(name = "credit")
private double credit;
@Column(name = "teacher")
private String teacher;
@Column(name = "department")
private String department;
@Column(name = "capacity")
private int capacity;
@Column(name = "enrolled")
private int enrolled;
@Column(name = "class_time")
private String classTime;
@Column(name = "class_room")
private String classRoom;
@Column(name = "course_type")
private String courseType;
@Column(name = "semester")
private String semester;
@Column(name = "create_time")
private LocalDateTime createTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCourseCode() {
return courseCode;
}
public void setCourseCode(String courseCode) {
this.courseCode = courseCode;
}
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public double getCredit() {
return credit;
}
public void setCredit(double credit) {
this.credit = credit;
}
public String getTeacher() {
return teacher;
}
public void setTeacher(String teacher) {
this.teacher = teacher;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public int getCapacity() {
return capacity;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
public int getEnrolled() {
return enrolled;
}
public void setEnrolled(int enrolled) {
this.enrolled = enrolled;
}
public String getClassTime() {
return classTime;
}
public void setClassTime(String classTime) {
this.classTime = classTime;
}
public String getClassRoom() {
return classRoom;
}
public void setClassRoom(String classRoom) {
this.classRoom = classRoom;
}
public String getCourseType() {
return courseType;
}
public void setCourseType(String courseType) {
this.courseType = courseType;
}
public String getSemester() {
return semester;
}
public void setSemester(String semester) {
this.semester = semester;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
}

25
src/main/java/com/example/repository/CourseRepository.java

@ -0,0 +1,25 @@
package com.example.repository;
import com.example.entity.Course;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
import java.util.Map;
@Repository
public interface CourseRepository extends JpaRepository<Course, Long> {
// 根据课程类型查询
List<Course> findByCourseType(String courseType);
// 获取所有课程类型统计
@Query(value = "SELECT course_type, COUNT(*) as count FROM courses GROUP BY course_type", nativeQuery = true)
List<Map<String, Object>> getCourseTypeStatistics();
// 获取各院系课程数量统计
@Query(value = "SELECT department, COUNT(*) as count FROM courses GROUP BY department", nativeQuery = true)
List<Map<String, Object>> getDepartmentStatistics();
// 获取课程容量与已选人数统计
@Query(value = "SELECT course_name, capacity, enrolled FROM courses", nativeQuery = true)
List<Map<String, Object>> getCourseEnrollmentStatistics();
}

18
src/main/resources/application.properties

@ -0,0 +1,18 @@
# 服务器配置
server.port=8084
# SQLite数据库配置
spring.datasource.url=jdbc:sqlite:course.db
spring.datasource.driver-class-name=org.sqlite.JDBC
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
spring.jpa.hibernate.ddl-auto=none
spring.jpa.open-in-view=false
# 爬虫配置
crawler.hnu.login.url=https://ids.hnu.edu.cn/authserver/login
crawler.hnu.course.list.url=https://jxgl.hnu.edu.cn/courseselect/welcome.do
crawler.hnu.username=test
crawler.hnu.password=test
# Thymeleaf配置
spring.thymeleaf.cache=false

24
src/main/resources/schema.sql

@ -0,0 +1,24 @@
CREATE TABLE IF NOT EXISTS courses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
course_code TEXT,
course_name TEXT,
credit REAL,
teacher TEXT,
department TEXT,
capacity INTEGER,
enrolled INTEGER,
class_time TEXT,
class_room TEXT,
course_type TEXT,
semester TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入测试数据
INSERT INTO courses (course_code, course_name, credit, teacher, department, capacity, enrolled, class_time, class_room, course_type, semester)
VALUES
('CS101', '计算机基础', 3.0, '张老师', '计算机学院', 100, 85, '周一 1-2节', '科技楼101', '必修课', '2024-2025-2'),
('MA201', '高等数学', 4.0, '李老师', '数学学院', 120, 110, '周二 3-4节', '教学楼201', '必修课', '2024-2025-2'),
('EN301', '英语口语', 2.0, '王老师', '外国语学院', 60, 55, '周三 5-6节', '外语楼301', '选修课', '2024-2025-2'),
('PH101', '大学物理', 3.0, '刘老师', '物理学院', 90, 80, '周四 1-2节', '物理楼101', '必修课', '2024-2025-2'),
('HI201', '中国历史', 2.0, '陈老师', '历史学院', 80, 75, '周五 3-4节', '文科楼201', '选修课', '2024-2025-2');

171
src/main/resources/templates/analysis.html

@ -0,0 +1,171 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据分析</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">湖南大学选课系统分析</a>
<div class="collapse navbar-collapse">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="/">首页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/courses">课程列表</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/analysis">数据分析</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-5">
<h2>数据分析</h2>
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">总课程数</h5>
<p class="card-text display-4" th:text="${statistics.totalCourses}"></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">总学分</h5>
<p class="card-text display-4" th:text="${statistics.totalCredits}"></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">平均学分</h5>
<p class="card-text display-4" th:text="${statistics.averageCredit}"></p>
</div>
</div>
</div>
</div>
<div class="row mt-5">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">课程类型分布</h5>
<div id="courseTypeChart" style="width: 100%; height: 400px;"></div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">院系统计</h5>
<div id="departmentChart" style="width: 100%; height: 400px;"></div>
</div>
</div>
</div>
</div>
</div>
<script>
// 初始化图表
document.addEventListener('DOMContentLoaded', function() {
fetch('/api/analysis')
.then(response => response.json())
.then(data => {
// 课程类型分布图表
const courseTypeChart = echarts.init(document.getElementById('courseTypeChart'));
const courseTypeData = data.courseTypeStatistics.map(item => [item.course_type, item.count]);
courseTypeChart.setOption({
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: '课程类型',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '18',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: courseTypeData
}
]
});
// 院系统计图表
const departmentChart = echarts.init(document.getElementById('departmentChart'));
const departmentData = data.departmentStatistics.map(item => [item.department, item.count]);
departmentChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: departmentData.map(item => item[0]),
axisLabel: {
rotate: 45
}
},
yAxis: {
type: 'value'
},
series: [
{
name: '课程数量',
type: 'bar',
data: departmentData.map(item => item[1])
}
]
});
// 响应式调整
window.addEventListener('resize', function() {
courseTypeChart.resize();
departmentChart.resize();
});
});
});
</script>
</body>
</html>

63
src/main/resources/templates/course-list.html

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>课程列表</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">湖南大学选课系统分析</a>
<div class="collapse navbar-collapse">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="/">首页</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/courses">课程列表</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/analysis">数据分析</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-5">
<h2>课程列表</h2>
<table class="table table-striped">
<thead>
<tr>
<th>课程代码</th>
<th>课程名称</th>
<th>学分</th>
<th>教师</th>
<th>院系</th>
<th>容量</th>
<th>已选</th>
<th>上课时间</th>
<th>上课地点</th>
<th>课程类型</th>
</tr>
</thead>
<tbody>
<tr th:each="course : ${courses}">
<td th:text="${course.courseCode}"></td>
<td th:text="${course.courseName}"></td>
<td th:text="${course.credit}"></td>
<td th:text="${course.teacher}"></td>
<td th:text="${course.department}"></td>
<td th:text="${course.capacity}"></td>
<td th:text="${course.enrolled}"></td>
<td th:text="${course.classTime}"></td>
<td th:text="${course.classRoom}"></td>
<td th:text="${course.courseType}"></td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

24
src/main/resources/templates/error.html

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>错误页面</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body text-center">
<h1 class="text-danger">500 - 服务器内部错误</h1>
<p class="mt-3">服务器遇到了一个内部错误,请稍后再试。</p>
<a href="/" class="btn btn-primary mt-4">返回首页</a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

76
src/main/resources/templates/index.html

@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>湖南大学选课系统分析</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">湖南大学选课系统分析</a>
<div class="collapse navbar-collapse">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="/">首页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/courses">课程列表</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/analysis">数据分析</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-5">
<div class="jumbotron">
<h1 class="display-4">湖南大学选课系统分析</h1>
<p class="lead">本系统用于爬取、分析和可视化湖南大学选课系统的数据</p>
<hr class="my-4">
<p>点击下方按钮开始爬取课程数据</p>
<button id="crawlBtn" class="btn btn-primary btn-lg">爬取课程数据</button>
</div>
<div id="result" class="mt-4 d-none">
<div class="alert alert-success" role="alert">
<h4 class="alert-heading">爬取成功!</h4>
<p id="resultMessage"></p>
<hr>
<a href="/courses" class="btn btn-secondary">查看课程列表</a>
<a href="/analysis" class="btn btn-primary">查看数据分析</a>
</div>
</div>
</div>
<script>
document.getElementById('crawlBtn').addEventListener('click', function() {
this.disabled = true;
this.textContent = '爬取中...';
fetch('/crawl', {
method: 'POST'
})
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('resultMessage').textContent = data.message;
document.getElementById('result').classList.remove('d-none');
}
})
.catch(error => {
console.error('Error:', error);
alert('爬取失败,请稍后重试');
})
.finally(() => {
this.disabled = false;
this.textContent = '爬取课程数据';
});
});
</script>
</body>
</html>

13
src/test/java/com/example/HnuCourseAnalysisApplicationTests.java

@ -0,0 +1,13 @@
package com.example;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class HnuCourseAnalysisApplicationTests {
@Test
void contextLoads() {
}
}
Loading…
Cancel
Save