formula project

This commit is contained in:
colden
2025-12-20 12:20:43 +08:00
commit 28e1507889
156 changed files with 7444 additions and 0 deletions

110
backend/pom.xml Executable file
View File

@@ -0,0 +1,110 @@
<?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>f1</groupId>
<artifactId>f1db</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>3.0.2</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>f1.Main</mainClass>
<skip>false</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,46 @@
package f1;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import f1.db.*;
@Configuration
public class AppConfig {
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.username}")
private String dbUsername;
@Value("${spring.datasource.password}")
private String dbPassword;
@Bean
public Database database() {
Database db = new Database(dbUrl, dbUsername, dbPassword);
db.init();
return db;
}
@Bean
public UserDao userDao(Database db) {
return new UserDao(db);
}
@Bean
public DriverDao driverDao(Database db) {
return new DriverDao(db);
}
@Bean
public TeamDao teamDao(Database db) {
return new TeamDao(db);
}
@Bean
public RaceDao raceDao(Database db) {
return new RaceDao(db);
}
}

View File

@@ -0,0 +1,38 @@
package f1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import f1.db.Database;
import jakarta.annotation.PostConstruct;
@SpringBootApplication
@Component
public class Main {
public static String JDBC_URL;
public static String JDBC_USER;
public static String JDBC_PASSWORD;
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.username}")
private String dbUsername;
@Value("${spring.datasource.password}")
private String dbPassword;
@PostConstruct
public void init() {
JDBC_URL = dbUrl;
JDBC_USER = dbUsername;
JDBC_PASSWORD = dbPassword;
}
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
var db = new Database(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
db.init();
}
}

View File

@@ -0,0 +1,90 @@
package f1.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import f1.Main;
import f1.db.Database;
import f1.db.UserDao;
import f1.entity.User;
import f1.security.JwtUtil;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Map;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private Database db;
private UserDao userDao;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtUtil jwtUtil;
public AuthController() {
this.db = new Database(Main.JDBC_URL, Main.JDBC_USER, Main.JDBC_PASSWORD);
db.init();
this.userDao = new UserDao(db);
}
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody User user) {
if (userDao.getUserByUsername(user.getUsername()) != null) {
return ResponseEntity.status(HttpStatus.CONFLICT).body("Username already exists");
}
String encodedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodedPassword);
boolean success = userDao.createUser(user);
if (success) {
return ResponseEntity.status(HttpStatus.CREATED).body("User registered successfully");
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Registration failed");
}
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody Map<String, String> credentials, HttpServletResponse response) {
String username = credentials.get("username");
String password = credentials.get("password");
User user = userDao.getUserByUsername(username);
if (user == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid username or password");
}
if (!passwordEncoder.matches(password, user.getPassword())) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid username or password");
}
String token = jwtUtil.generateToken(user.getUsername());
Cookie cookie = new Cookie("auth_token", token);
cookie.setHttpOnly(true);
cookie.setSecure(false);
cookie.setPath("/"); // 全局有效
cookie.setMaxAge(24 * 60 * 60); // 24小时过期
response.addCookie(cookie);
return ResponseEntity.ok("Login successful");
}
@PostMapping("/logout")
public ResponseEntity<?> logout(HttpServletResponse response) {
Cookie cookie = new Cookie("auth_token", null);
cookie.setHttpOnly(true);
cookie.setPath("/");
cookie.setMaxAge(0); // 立即过期
response.addCookie(cookie);
return ResponseEntity.ok("Logged out");
}
}

View File

@@ -0,0 +1,82 @@
package f1.controller;
import f1.db.UserDao;
import f1.entity.CommentItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.security.Principal;
@RestController
@RequestMapping("/api/comments")
public class CommentController {
@Autowired
private UserDao userDao;
@GetMapping
public List<List<CommentItem>> getComments(@RequestParam(defaultValue = "10") int limit,
@RequestParam(defaultValue = "0") int offset,
@RequestParam(required = false) Integer pageSize) {
List<CommentItem> allComments = userDao.getRootComments(limit, offset);
List<List<CommentItem>> pages = new ArrayList<>();
if (allComments.isEmpty()) {
return pages;
}
int size = (pageSize == null || pageSize <= 0) ? allComments.size() : pageSize;
for (int i = 0; i < allComments.size(); i += size) {
int end = Math.min(allComments.size(), i + size);
pages.add(allComments.subList(i, end));
}
return pages;
}
@PostMapping
public boolean addRootComment(@RequestBody Map<String, Object> payload) {
int userId = (int) payload.get("user_id");
String content = (String) payload.get("content");
return userDao.addComment(userId, null, null, content);
}
@GetMapping("/{id}/replies")
public Map<String, Object> getChildComments(@PathVariable int id) {
Map<String, Object> response = new HashMap<>();
response.put("parent", userDao.getCommentById(id));
response.put("replies", userDao.getChildComments(id));
return response;
}
@PostMapping("/{id}/replies")
public boolean addChildComment(@PathVariable int id, @RequestBody Map<String, Object> payload) {
int userId = (int) payload.get("user_id");
String content = (String) payload.get("content");
Integer responseId = payload.containsKey("response_id") ? (Integer) payload.get("response_id") : null;
if (responseId == null) {
responseId = id;
}
return userDao.addComment(userId, responseId, id, content);
}
@DeleteMapping("/{id}")
public boolean deleteComment(@PathVariable int id, Principal principal) {
CommentItem comment = userDao.getCommentById(id);
if (comment == null) {
return false;
}
if (!comment.getUsername().equals(principal.getName())) {
return false;
}
return userDao.deleteComment(id);
}
}

View File

@@ -0,0 +1,115 @@
package f1.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import f1.db.DriverDao;
import f1.db.TeamDao;
import f1.entity.Driver;
import f1.entity.DriverHistoryItem;
import f1.entity.DriverStatistic;
import f1.entity.QualifyingResultItem;
import f1.entity.RaceResultItem;
import f1.entity.SeasonDriver;
import f1.entity.SeasonScheduleItem;
import f1.entity.Team;
import f1.entity.TeamHistoryItem;
import f1.entity.TeamStatistic;
import f1.db.RaceDao;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
@RestController
@RequestMapping("/api")
public class F1Controller {
@Autowired
private DriverDao driverDao;
@Autowired
private TeamDao teamDao;
@Autowired
private RaceDao raceDao;
@GetMapping("/drivers")
public List<Driver> getAllDrivers() {
return driverDao.getAllDrivers();
}
@GetMapping("/teams")
public List<Team> getAllTeams() {
return teamDao.getAllTeams();
}
@GetMapping("/season-drivers")
public List<SeasonDriver> getDriversBySeason(@RequestParam int season) {
return driverDao.getDriversBySeason(season);
}
@GetMapping("/standings/teams")
public List<Map<String, Object>> getSeasonTeamStandings(@RequestParam int season) {
return teamDao.getSeasonTop5TeamStandings(season);
}
@GetMapping("/standings/drivers")
public List<Map<String, Object>> getSeasonDriverStandings(@RequestParam int season) {
return driverDao.getSeasonTop5DriverStandings(season);
}
@GetMapping("/prix")
public List<SeasonScheduleItem> getSeasonSchedule(@RequestParam int season) {
return raceDao.getSeasonSchedule(season);
}
@GetMapping("/teams/{id}/drivers")
public List<String> getDriverNames(@PathVariable int id, @RequestParam int season) {
return driverDao.getDriverNamesByTeamAndSeason(id, season);
}
@GetMapping("/drivers/{id}/statistics")
public Map<String, DriverStatistic> getDriverStatistic(@PathVariable int id, @RequestParam int season) {
Map<String, DriverStatistic> result = new HashMap<>();
result.put("formal", driverDao.getDriverStatistic(id, season, false));
result.put("sprint", driverDao.getDriverStatistic(id, season, true));
return result;
}
@GetMapping("/teams/{id}/statistics")
public TeamStatistic getTeamStatistic(@PathVariable int id, @RequestParam int season) {
DriverStatistic formal = teamDao.getTeamStatistic(id, season, false);
DriverStatistic sprint = teamDao.getTeamStatistic(id, season, true);
List<String> drivers = driverDao.getDriverNamesByTeamAndSeason(id, season);
Map<String, Integer> standing = teamDao.getTeamStanding(id, season);
int rank = standing.getOrDefault("ranking", 0);
int totalScore = standing.getOrDefault("score", 0);
return new TeamStatistic(formal, sprint, drivers, rank, totalScore);
}
@GetMapping("/prix/{prixId}/race")
public List<RaceResultItem> getRaceResult(@PathVariable int prixId,
@RequestParam(defaultValue = "false") boolean isSprint) {
return raceDao.getRaceResults(prixId, isSprint);
}
@GetMapping("/prix/{prixId}/qualifying")
public List<QualifyingResultItem> getQualifyingResult(@PathVariable int prixId,
@RequestParam(defaultValue = "false") boolean isSprint) {
return raceDao.getQualifyingResults(prixId, isSprint);
}
@GetMapping("/drivers/{id}/results")
public List<DriverHistoryItem> getDriverHistory(@PathVariable int id, @RequestParam int season) {
return driverDao.getDriverHistory(id, season);
}
@GetMapping("/teams/{id}/results")
public List<TeamHistoryItem> getTeamHistory(@PathVariable int id, @RequestParam int season) {
return teamDao.getTeamHistory(id, season);
}
}

View File

@@ -0,0 +1,35 @@
package f1.controller;
import f1.db.UserDao;
import f1.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserDao userDao;
@GetMapping
public User getCurrentUser(Principal principal) {
if (principal == null) {
return null; // Or throw exception / return 401
}
String username = principal.getName();
// getUserByUsername returns full user, but password is now @JsonIgnore-d
return userDao.getUserByUsername(username);
}
@GetMapping("/{id}")
public User getUserById(@PathVariable int id) {
return userDao.getUserById(id);
}
}

View File

@@ -0,0 +1,16 @@
package f1.db;
import java.sql.Connection;
import java.sql.SQLException;
public abstract class BaseDao {
protected Database db;
public BaseDao(Database db) {
this.db = db;
}
protected Connection getConnection() throws SQLException {
return db.getConnection();
}
}

View File

@@ -0,0 +1,66 @@
package f1.db;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
public class Database {
private HikariDataSource dataSource;
public Database(String url, String user, String password) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(user);
config.setPassword(password);
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.setMaximumPoolSize(10);
this.dataSource = new HikariDataSource(config);
}
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public void close() {
if (dataSource != null) {
dataSource.close();
}
}
public void init() {
try (Connection con = getConnection()) {
executeSqlFile(con, "/init.sql");
} catch (SQLException e) {
e.printStackTrace();
}
}
private void executeSqlFile(Connection con, String resourcePath) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(getClass().getResourceAsStream(resourcePath)));
var stmt = con.createStatement()) {
StringBuilder sql = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.startsWith("--") || line.isEmpty()) {
continue;
}
sql.append(line).append(" ");
if (line.endsWith(";")) {
stmt.executeUpdate(sql.toString().trim());
sql.setLength(0);
}
}
} catch (IOException | SQLException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,191 @@
package f1.db;
import f1.entity.Driver;
import f1.entity.DriverHistoryItem;
import f1.entity.DriverStatistic;
import f1.entity.SeasonDriver;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DriverDao extends BaseDao {
public DriverDao(Database db) {
super(db);
}
public ArrayList<Driver> getAllDrivers() {
ArrayList<Driver> drivers = new ArrayList<>();
try (var con = getConnection();
var stmt = con.createStatement();
var rs = stmt.executeQuery("SELECT * FROM driver")) {
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String country = rs.getString("country");
java.sql.Date birthday = rs.getDate("birthday");
drivers.add(new Driver(id, name, country, birthday));
}
} catch (SQLException e) {
e.printStackTrace();
}
return drivers;
}
public ArrayList<SeasonDriver> getDriversBySeason(int season) {
ArrayList<SeasonDriver> seasonDrivers = new ArrayList<>();
try (var con = getConnection();
var stmt = con
.prepareStatement("SELECT * FROM season_driver WHERE season = ? ORDER BY team, car_num")) {
stmt.setInt(1, season);
var rs = stmt.executeQuery();
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String team = rs.getString("team");
String country = rs.getString("country");
java.sql.Date birthday = rs.getDate("birthday");
int carNum = rs.getInt("car_num");
int seasonVal = rs.getInt("season");
seasonDrivers.add(new SeasonDriver(id, name, team, country, birthday, carNum, seasonVal));
}
} catch (SQLException e) {
e.printStackTrace();
}
return seasonDrivers;
}
public List<Map<String, Object>> getSeasonTop5DriverStandings(int season) {
List<Map<String, Object>> result = new ArrayList<>();
String sql = "SELECT sd.name, sd.team, standings.total_score, standings.ranking " +
"FROM season_driver sd " +
"JOIN ( " +
" SELECT rr.driver_id, SUM(rr.score) AS total_score, " +
" RANK() OVER (ORDER BY SUM(rr.score) DESC) AS ranking " +
" FROM race_result rr " +
" JOIN prix p ON rr.prix_id = p.id " +
" WHERE p.season = ? " +
" GROUP BY rr.driver_id " +
") standings ON sd.id = standings.driver_id " +
"WHERE sd.season = ? " +
"ORDER BY standings.ranking ASC " +
"LIMIT 5";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setInt(1, season);
stmt.setInt(2, season);
var rs = stmt.executeQuery();
while (rs.next()) {
Map<String, Object> item = new HashMap<>();
item.put("driver_name", rs.getString("name"));
item.put("team_name", rs.getString("team"));
item.put("total_score", rs.getInt("total_score"));
item.put("ranking", rs.getInt("ranking"));
result.add(item);
}
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
public ArrayList<String> getDriverNamesByTeamAndSeason(int teamId, int season) {
ArrayList<String> names = new ArrayList<>();
String sql = "SELECT d.name FROM driver d JOIN contract c ON d.id = c.driver_id WHERE c.season = ? AND c.team_id = ?";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setInt(1, season);
stmt.setInt(2, teamId);
var rs = stmt.executeQuery();
while (rs.next()) {
names.add(rs.getString("name"));
}
} catch (SQLException e) {
e.printStackTrace();
}
return names;
}
public DriverStatistic getDriverStatistic(int driverId, int season, boolean isSprint) {
return getStatistic("rr.driver_id = ?", driverId, season, isSprint);
}
private DriverStatistic getStatistic(String whereClause, int id, int season, boolean isSprint) {
DriverStatistic statistic = null;
String sql = """
SELECT
COUNT(DISTINCT prix_id) AS total_cnt,
SUM(score) AS score_sum,
SUM(end_position <= 3) AS medal,
SUM(end_position = 1) AS gold,
SUM(start_position = 1) AS pole,
SUM(end_position <= 10) AS top_ten,
SUM(finish_time IN ('DNF', 'DNS', 'DSQ')) AS unfinished,
SUM(fastest_time = fastest.fastest) AS fastest_lap
FROM
race_result rr
NATURAL JOIN (
SELECT
rr.prix_id ,
MIN(rr.fastest_time) AS fastest
FROM
race_result rr
WHERE
rr.fastest_time NOT IN ('DNF', 'DNS', 'DSQ')
AND rr.is_sprint = ?
AND rr.prix_id IN (SELECT id FROM prix WHERE season = ?)
GROUP BY
rr.prix_id) fastest
WHERE
%s
AND rr.is_sprint = ?
AND rr.prix_id IN (SELECT id FROM prix WHERE season = ?)
""".formatted(whereClause);
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setBoolean(1, isSprint);
stmt.setInt(2, season);
stmt.setInt(3, id);
stmt.setBoolean(4, isSprint);
stmt.setInt(5, season);
var rs = stmt.executeQuery();
if (rs.next()) {
int totalCnt = rs.getInt("total_cnt");
int scoreSum = rs.getInt("score_sum");
int medal = rs.getInt("medal");
int gold = rs.getInt("gold");
int pole = rs.getInt("pole");
int topTen = rs.getInt("top_ten");
int unfinished = rs.getInt("unfinished");
int fastestLap = rs.getInt("fastest_lap");
statistic = new DriverStatistic(totalCnt, scoreSum, medal, gold, pole, topTen, unfinished,
fastestLap);
}
} catch (SQLException e) {
e.printStackTrace();
}
return statistic;
}
public ArrayList<DriverHistoryItem> getDriverHistory(int driverId, int season) {
ArrayList<DriverHistoryItem> history = new ArrayList<>();
String sql = "SELECT prix_name, team_name, pos, score, is_sprint FROM prix_result WHERE driver_id = ? AND season = ? ORDER BY round, is_sprint";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setInt(1, driverId);
stmt.setInt(2, season);
var rs = stmt.executeQuery();
while (rs.next()) {
history.add(new DriverHistoryItem(
rs.getString("prix_name"),
rs.getString("team_name"),
rs.getInt("pos"),
rs.getInt("score"),
rs.getBoolean("is_sprint")));
}
} catch (SQLException e) {
e.printStackTrace();
}
return history;
}
}

View File

@@ -0,0 +1,120 @@
package f1.db;
import java.sql.SQLException;
import java.util.ArrayList;
import f1.entity.QualifyingResultItem;
import f1.entity.RaceResultItem;
import f1.entity.SeasonScheduleItem;
public class RaceDao extends BaseDao {
public RaceDao(Database db) {
super(db);
}
public ArrayList<RaceResultItem> getRaceResults(int prixId, boolean isSprint) {
ArrayList<RaceResultItem> results = new ArrayList<>();
String sql = "SELECT pos, car_num, driver_name, team_name, finish_time, score FROM prix_result WHERE prix_id = ? AND is_sprint = ? ORDER BY pos";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setInt(1, prixId);
stmt.setBoolean(2, isSprint);
var rs = stmt.executeQuery();
while (rs.next()) {
results.add(new RaceResultItem(
rs.getInt("pos"),
rs.getInt("car_num"),
rs.getString("driver_name"),
rs.getString("team_name"),
rs.getString("finish_time"),
rs.getInt("score")));
}
} catch (SQLException e) {
e.printStackTrace();
}
return results;
}
public ArrayList<QualifyingResultItem> getQualifyingResults(int prixId, boolean isSprint) {
ArrayList<QualifyingResultItem> results = new ArrayList<>();
String sql = """
SELECT
qr.position,
sd.car_num,
sd.name,
sd.team,
MAX(CASE WHEN section = 1 THEN fastest_time END) AS q1_time,
MAX(CASE WHEN section = 2 THEN fastest_time END) AS q2_time,
MAX(CASE WHEN section = 3 THEN fastest_time END) AS q3_time
FROM
qualifying_result qr
JOIN
prix p ON qr.prix_id = p.id
JOIN
season_driver sd ON qr.driver_id = sd.id AND sd.season = p.season
WHERE
qr.prix_id = ?
AND qr.is_sprint = ?
GROUP BY
qr.driver_id, qr.position, sd.car_num, sd.name, sd.team
ORDER BY
qr.position
""";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setInt(1, prixId);
stmt.setBoolean(2, isSprint);
var rs = stmt.executeQuery();
while (rs.next()) {
results.add(new QualifyingResultItem(
rs.getInt("position"),
rs.getInt("car_num"),
rs.getString("name"),
rs.getString("team"),
rs.getString("q1_time"),
rs.getString("q2_time"),
rs.getString("q3_time")));
}
} catch (SQLException e) {
e.printStackTrace();
}
return results;
}
public ArrayList<SeasonScheduleItem> getSeasonSchedule(int season) {
ArrayList<SeasonScheduleItem> schedule = new ArrayList<>();
String sql = """
SELECT
p.id,
p.round,
p.name,
p.have_sprint,
p.circuit_id,
c.country,
c.location
FROM
prix p
JOIN circuit c ON c.id = p.circuit_id
WHERE
p.season = ?
ORDER BY
p.round
""";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setInt(1, season);
var rs = stmt.executeQuery();
while (rs.next()) {
schedule.add(new SeasonScheduleItem(
rs.getInt("id"),
rs.getInt("round"),
rs.getString("name"),
rs.getBoolean("have_sprint"),
rs.getInt("circuit_id"),
rs.getString("country"),
rs.getString("location")));
}
} catch (SQLException e) {
e.printStackTrace();
}
return schedule;
}
}

View File

@@ -0,0 +1,158 @@
package f1.db;
import f1.entity.DriverStatistic;
import f1.entity.Team;
import f1.entity.TeamHistoryItem;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.time.LocalDate;
public class TeamDao extends BaseDao {
public TeamDao(Database db) {
super(db);
}
public ArrayList<Team> getAllTeams() {
ArrayList<Team> teams = new ArrayList<>();
try (var con = getConnection();
var stmt = con.createStatement();
var rs = stmt.executeQuery("SELECT * FROM team")) {
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String country = rs.getString("country");
String engineSupplier = rs.getString("engine_supplier");
int setupYear = rs.getInt("setup_time");
java.sql.Date setupTime = java.sql.Date.valueOf(LocalDate.of(setupYear, 1, 1));
teams.add(new Team(id, name, country, engineSupplier, setupTime));
}
} catch (SQLException e) {
e.printStackTrace();
}
return teams;
}
public Map<String, Integer> getTeamStanding(int teamId, int season) {
Map<String, Integer> result = new HashMap<>();
String sql = "SELECT total_score, ranking FROM season_team_standings WHERE season = ? AND team_id = ?";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setInt(1, season);
stmt.setInt(2, teamId);
var rs = stmt.executeQuery();
if (rs.next()) {
result.put("score", rs.getInt("total_score"));
result.put("ranking", rs.getInt("ranking"));
}
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
public List<Map<String, Object>> getSeasonTop5TeamStandings(int season) {
List<Map<String, Object>> result = new ArrayList<>();
String sql = "SELECT t.name, s.total_score, s.ranking " +
"FROM season_team_standings s " +
"JOIN team t ON s.team_id = t.id " +
"WHERE s.season = ? " +
"ORDER BY s.ranking ASC " +
"LIMIT 5";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setInt(1, season);
var rs = stmt.executeQuery();
while (rs.next()) {
Map<String, Object> item = new HashMap<>();
item.put("team_name", rs.getString("name"));
item.put("total_score", rs.getInt("total_score"));
item.put("ranking", rs.getInt("ranking"));
result.add(item);
}
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
public DriverStatistic getTeamStatistic(int teamId, int season, boolean isSprint) {
return getStatistic("rr.team_id = ?", teamId, season, isSprint);
}
private DriverStatistic getStatistic(String whereClause, int id, int season, boolean isSprint) {
DriverStatistic statistic = null;
String sql = """
SELECT
COUNT(DISTINCT prix_id) AS total_cnt,
SUM(score) AS score_sum,
SUM(end_position <= 3) AS medal,
SUM(end_position = 1) AS gold,
SUM(start_position = 1) AS pole,
SUM(end_position <= 10) AS top_ten,
SUM(finish_time IN ('DNF', 'DNS', 'DSQ')) AS unfinished,
SUM(fastest_time = fastest.fastest) AS fastest_lap
FROM
race_result rr
NATURAL JOIN (
SELECT
rr.prix_id ,
MIN(rr.fastest_time) AS fastest
FROM
race_result rr
WHERE
rr.fastest_time NOT IN ('DNF', 'DNS', 'DSQ')
AND rr.is_sprint = ?
AND rr.prix_id IN (SELECT id FROM prix WHERE season = ?)
GROUP BY
rr.prix_id) fastest
WHERE
%s
AND rr.is_sprint = ?
AND rr.prix_id IN (SELECT id FROM prix WHERE season = ?)
""".formatted(whereClause);
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setBoolean(1, isSprint);
stmt.setInt(2, season);
stmt.setInt(3, id);
stmt.setBoolean(4, isSprint);
stmt.setInt(5, season);
var rs = stmt.executeQuery();
if (rs.next()) {
int totalCnt = rs.getInt("total_cnt");
int scoreSum = rs.getInt("score_sum");
int medal = rs.getInt("medal");
int gold = rs.getInt("gold");
int pole = rs.getInt("pole");
int topTen = rs.getInt("top_ten");
int unfinished = rs.getInt("unfinished");
int fastestLap = rs.getInt("fastest_lap");
statistic = new DriverStatistic(totalCnt, scoreSum, medal, gold, pole, topTen, unfinished,
fastestLap);
}
} catch (SQLException e) {
e.printStackTrace();
}
return statistic;
}
public ArrayList<TeamHistoryItem> getTeamHistory(int teamId, int season) {
ArrayList<TeamHistoryItem> history = new ArrayList<>();
String sql = "SELECT prix_name, SUM(score) as total_score FROM prix_result WHERE team_id = ? AND season = ? GROUP BY prix_id, prix_name, round ORDER BY round";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setInt(1, teamId);
stmt.setInt(2, season);
var rs = stmt.executeQuery();
while (rs.next()) {
history.add(new TeamHistoryItem(
rs.getString("prix_name"),
rs.getInt("total_score")));
}
} catch (SQLException e) {
e.printStackTrace();
}
return history;
}
}

View File

@@ -0,0 +1,200 @@
package f1.db;
import f1.entity.CommentItem;
import f1.entity.User;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class UserDao extends BaseDao {
public UserDao(Database db) {
super(db);
}
public boolean createUser(User user) {
String sql = "INSERT INTO user (username, password, email, country, avatar) VALUES (?, ?, ?, ?, ?)";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setString(1, user.getUsername());
stmt.setString(2, user.getPassword());
stmt.setString(3, user.getEmail());
stmt.setString(4, user.getCountry());
stmt.setString(5, user.getAvatar());
int affectedRows = stmt.executeUpdate();
return affectedRows > 0;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
public User getUserByUsername(String username) {
String sql = "SELECT * FROM user WHERE username = ?";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setString(1, username);
var rs = stmt.executeQuery();
if (rs.next()) {
return new User(
rs.getInt("id"),
rs.getString("username"),
rs.getString("password"),
rs.getString("email"),
rs.getString("country"),
rs.getString("avatar"));
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public User getUserById(int id) {
String sql = "SELECT username, email, country, avatar FROM user WHERE id = ?";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setInt(1, id);
var rs = stmt.executeQuery();
if (rs.next()) {
// Password is not selected, so we pass null
return new User(
id,
rs.getString("username"),
null, // password
rs.getString("email"),
rs.getString("country"),
rs.getString("avatar"));
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public boolean addComment(int userId, Integer responseId, Integer rootId, String content) {
String sql = "INSERT INTO comment (user_id, response_id, root_id, content) VALUES (?, ?, ?, ?)";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setInt(1, userId);
if (responseId == null) {
stmt.setNull(2, java.sql.Types.INTEGER);
} else {
stmt.setInt(2, responseId);
}
if (rootId == null) {
stmt.setNull(3, java.sql.Types.INTEGER);
} else {
stmt.setInt(3, rootId);
}
stmt.setString(4, content);
int affectedRows = stmt.executeUpdate();
return affectedRows > 0;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
public boolean deleteComment(int commentId) {
String sql = "DELETE FROM comment WHERE id = ?";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setInt(1, commentId);
int affectedRows = stmt.executeUpdate();
return affectedRows > 0;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
public List<CommentItem> getRootComments(int limit, int offset) {
List<CommentItem> comments = new ArrayList<>();
String sql = "SELECT c.id, c.user_id, u.username, c.content, COUNT(replies.id) AS reply_count " +
"FROM comment c " +
"JOIN user u ON c.user_id = u.id " +
"LEFT JOIN comment replies ON replies.root_id = c.id " +
"WHERE c.root_id IS NULL " +
"GROUP BY c.id " +
"ORDER BY c.id DESC " +
"LIMIT ? OFFSET ?";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setInt(1, limit);
stmt.setInt(2, offset);
var rs = stmt.executeQuery();
while (rs.next()) {
comments.add(new CommentItem(
rs.getInt("id"),
rs.getInt("user_id"),
rs.getString("username"),
null, // response_id is null for root
null, // root_id is null for root
rs.getString("content"),
rs.getInt("reply_count")));
}
} catch (SQLException e) {
e.printStackTrace();
}
return comments;
}
public List<CommentItem> getChildComments(int rootId) {
List<CommentItem> comments = new ArrayList<>();
String sql = "SELECT c.id, c.user_id, u.username, c.response_id, c.content, " +
"parent_c.user_id AS reply_to_user_id, parent_u.username AS reply_to_username " +
"FROM comment c " +
"JOIN user u ON c.user_id = u.id " +
"LEFT JOIN comment parent_c ON c.response_id = parent_c.id " +
"LEFT JOIN user parent_u ON parent_c.user_id = parent_u.id " +
"WHERE c.root_id = ? " +
"ORDER BY c.id ASC";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setInt(1, rootId);
var rs = stmt.executeQuery();
while (rs.next()) {
comments.add(new CommentItem(
rs.getInt("id"),
rs.getInt("user_id"),
rs.getString("username"),
(Integer) rs.getObject("response_id"),
null, // root_id is known and not needed in response
rs.getString("content"),
(Integer) rs.getObject("reply_to_user_id"),
rs.getString("reply_to_username")));
}
} catch (SQLException e) {
e.printStackTrace();
}
return comments;
}
public CommentItem getCommentById(int id) {
String sql = "SELECT c.id, c.user_id, u.username, c.content, COUNT(replies.id) AS reply_count " +
"FROM comment c " +
"JOIN user u ON c.user_id = u.id " +
"LEFT JOIN comment replies ON replies.root_id = c.id " +
"WHERE c.id = ? " +
"GROUP BY c.id";
try (var con = getConnection(); var stmt = con.prepareStatement(sql)) {
stmt.setInt(1, id);
var rs = stmt.executeQuery();
if (rs.next()) {
return new CommentItem(
rs.getInt("id"),
rs.getInt("user_id"),
rs.getString("username"),
null,
null,
rs.getString("content"),
rs.getInt("reply_count")
);
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}

View File

@@ -0,0 +1,90 @@
package f1.entity;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CommentItem {
private int id;
@JsonProperty("user_id")
private int userId;
private String username;
@JsonProperty("response_id")
private Integer responseId;
@JsonProperty("root_id")
private Integer rootId;
private String content;
@JsonProperty("reply_count")
private int replyCount;
@JsonProperty("reply_to_user_id")
private Integer replyToUserId;
@JsonProperty("reply_to_username")
private String replyToUsername;
public CommentItem(int id, int userId, String username, Integer responseId, Integer rootId, String content,
int replyCount) {
this.id = id;
this.userId = userId;
this.username = username;
this.responseId = responseId;
this.rootId = rootId;
this.content = content;
this.replyCount = replyCount;
}
public CommentItem(int id, int userId, String username, Integer responseId, Integer rootId, String content,
Integer replyToUserId, String replyToUsername) {
this.id = id;
this.userId = userId;
this.username = username;
this.responseId = responseId;
this.rootId = rootId;
this.content = content;
this.replyToUserId = replyToUserId;
this.replyToUsername = replyToUsername;
}
public int getId() {
return id;
}
public int getUserId() {
return userId;
}
public String getUsername() {
return username;
}
public Integer getResponseId() {
return responseId;
}
public Integer getRootId() {
return rootId;
}
public String getContent() {
return content;
}
public int getReplyCount() {
return replyCount;
}
public Integer getReplyToUserId() {
return replyToUserId;
}
public String getReplyToUsername() {
return replyToUsername;
}
}

View File

@@ -0,0 +1,49 @@
package f1.entity;
import java.sql.Date;
public class Driver {
private int id;
private String name;
private String country;
private Date birthday;
public Driver(int id, String name, String country, Date birthday) {
this.id = id;
this.name = name;
this.country = country;
this.birthday = birthday;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getCountry() {
return country;
}
public Date getBirthday() {
return birthday;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setCountry(String country) {
this.country = country;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}

View File

@@ -0,0 +1,45 @@
package f1.entity;
import com.fasterxml.jackson.annotation.JsonProperty;
public class DriverHistoryItem {
@JsonProperty("prix_name")
private String prixName;
@JsonProperty("team_name")
private String teamName;
private int position;
private int score;
@JsonProperty("is_sprint")
private boolean isSprint;
public DriverHistoryItem(String prixName, String teamName, int position, int score, boolean isSprint) {
this.prixName = prixName;
this.teamName = teamName;
this.position = position;
this.score = score;
this.isSprint = isSprint;
}
public String getPrixName() {
return prixName;
}
public String getTeamName() {
return teamName;
}
public int getPosition() {
return position;
}
public int getScore() {
return score;
}
public boolean isSprint() {
return isSprint;
}
}

View File

@@ -0,0 +1,88 @@
package f1.entity;
public class DriverStatistic {
private int totalCnt;
private int scoreSum;
private int medal;
private int gold;
private int pole;
private int topTen;
private int unfinished;
private int fastestLap;
public DriverStatistic(int totalCnt, int scoreSum, int medal, int gold, int pole, int topTen, int unfinished,
int fastestLap) {
this.totalCnt = totalCnt;
this.scoreSum = scoreSum;
this.medal = medal;
this.gold = gold;
this.pole = pole;
this.topTen = topTen;
this.unfinished = unfinished;
this.fastestLap = fastestLap;
}
public int getTotalCnt() {
return totalCnt;
}
public int getScoreSum() {
return scoreSum;
}
public int getMedal() {
return medal;
}
public int getGold() {
return gold;
}
public int getPole() {
return pole;
}
public int getTopTen() {
return topTen;
}
public int getUnfinished() {
return unfinished;
}
public int getFastestLap() {
return fastestLap;
}
public void setTotalCnt(int totalCnt) {
this.totalCnt = totalCnt;
}
public void setScoreSum(int scoreSum) {
this.scoreSum = scoreSum;
}
public void setMedal(int medal) {
this.medal = medal;
}
public void setGold(int gold) {
this.gold = gold;
}
public void setPole(int pole) {
this.pole = pole;
}
public void setTopTen(int topTen) {
this.topTen = topTen;
}
public void setUnfinished(int unfinished) {
this.unfinished = unfinished;
}
public void setFastestLap(int fastestLap) {
this.fastestLap = fastestLap;
}
}

View File

@@ -0,0 +1,89 @@
package f1.entity;
import com.fasterxml.jackson.annotation.JsonProperty;
public class QualifyingResultItem {
private int position;
@JsonProperty("car_num")
private int carNum;
private String name;
private String team;
@JsonProperty("q1")
private String q1Time;
@JsonProperty("q2")
private String q2Time;
@JsonProperty("q3")
private String q3Time;
public QualifyingResultItem(int position, int carNum, String name, String team, String q1Time, String q2Time,
String q3Time) {
this.position = position;
this.carNum = carNum;
this.name = name;
this.team = team;
this.q1Time = q1Time;
this.q2Time = q2Time;
this.q3Time = q3Time;
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
public int getCarNum() {
return carNum;
}
public void setCarNum(int carNum) {
this.carNum = carNum;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTeam() {
return team;
}
public void setTeam(String team) {
this.team = team;
}
public String getQ1Time() {
return q1Time;
}
public void setQ1Time(String q1Time) {
this.q1Time = q1Time;
}
public String getQ2Time() {
return q2Time;
}
public void setQ2Time(String q2Time) {
this.q2Time = q2Time;
}
public String getQ3Time() {
return q3Time;
}
public void setQ3Time(String q3Time) {
this.q3Time = q3Time;
}
}

View File

@@ -0,0 +1,75 @@
package f1.entity;
import com.fasterxml.jackson.annotation.JsonProperty;
public class RaceResultItem {
private int pos;
@JsonProperty("car_num")
private int carNum;
private String name;
private String team;
@JsonProperty("finish_time")
private String finishTime;
private int score;
public RaceResultItem(int pos, int carNum, String name, String team, String finishTime, int score) {
this.pos = pos;
this.carNum = carNum;
this.name = name;
this.team = team;
this.finishTime = finishTime;
this.score = score;
}
public int getPos() {
return pos;
}
public void setPos(int pos) {
this.pos = pos;
}
public int getCarNum() {
return carNum;
}
public void setCarNum(int carNum) {
this.carNum = carNum;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTeam() {
return team;
}
public void setTeam(String team) {
this.team = team;
}
public String getFinishTime() {
return finishTime;
}
public void setFinishTime(String finishTime) {
this.finishTime = finishTime;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}

View File

@@ -0,0 +1,40 @@
package f1.entity;
import java.sql.Date;
public class SeasonDriver extends Driver {
private String team;
private int carNum;
private int season;
public SeasonDriver(int id, String name, String team, String country, Date birthday, int carNum, int season) {
super(id, name, country, birthday);
this.team = team;
this.carNum = carNum;
this.season = season;
}
public String getTeam() {
return team;
}
public int getCarNum() {
return carNum;
}
public int getSeason() {
return season;
}
public void setTeam(String team) {
this.team = team;
}
public void setCarNum(int carNum) {
this.carNum = carNum;
}
public void setSeason(int season) {
this.season = season;
}
}

View File

@@ -0,0 +1,85 @@
package f1.entity;
import com.fasterxml.jackson.annotation.JsonProperty;
public class SeasonScheduleItem {
private int id;
private int round;
private String name;
@JsonProperty("have_sprint")
private boolean haveSprint;
@JsonProperty("circuit_id")
private int circuitId;
private String country;
private String location;
public SeasonScheduleItem(int id, int round, String name, boolean haveSprint, int circuitId, String country,
String location) {
this.id = id;
this.round = round;
this.name = name;
this.haveSprint = haveSprint;
this.circuitId = circuitId;
this.country = country;
this.location = location;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getRound() {
return round;
}
public void setRound(int round) {
this.round = round;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isHaveSprint() {
return haveSprint;
}
public void setHaveSprint(boolean haveSprint) {
this.haveSprint = haveSprint;
}
public int getCircuitId() {
return circuitId;
}
public void setCircuitId(int circuitId) {
this.circuitId = circuitId;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}

View File

@@ -0,0 +1,59 @@
package f1.entity;
import java.sql.Date;
public class Team {
private int id;
private String name;
private String country;
private String engineSupplier;
private Date setupTime;
public Team(int id, String name, String country, String engineSupplier, Date setupTime) {
this.id = id;
this.name = name;
this.country = country;
this.engineSupplier = engineSupplier;
this.setupTime = setupTime;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getCountry() {
return country;
}
public String getEngineSupplier() {
return engineSupplier;
}
public Date getSetupTime() {
return setupTime;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setCountry(String country) {
this.country = country;
}
public void setEngineSupplier(String engineSupplier) {
this.engineSupplier = engineSupplier;
}
public void setSetupTime(Date setupTime) {
this.setupTime = setupTime;
}
}

View File

@@ -0,0 +1,24 @@
package f1.entity;
import com.fasterxml.jackson.annotation.JsonProperty;
public class TeamHistoryItem {
@JsonProperty("prix_name")
private String prixName;
@JsonProperty("total_score")
private int totalScore;
public TeamHistoryItem(String prixName, int totalScore) {
this.prixName = prixName;
this.totalScore = totalScore;
}
public String getPrixName() {
return prixName;
}
public int getTotalScore() {
return totalScore;
}
}

View File

@@ -0,0 +1,64 @@
package f1.entity;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class TeamStatistic {
private DriverStatistic formal;
private DriverStatistic sprint;
private List<String> drivers;
private int rank;
@JsonProperty("total_score")
private int totalScore;
public TeamStatistic(DriverStatistic formal, DriverStatistic sprint, List<String> drivers, int rank,
int totalScore) {
this.formal = formal;
this.sprint = sprint;
this.drivers = drivers;
this.rank = rank;
this.totalScore = totalScore;
}
public DriverStatistic getFormal() {
return formal;
}
public void setFormal(DriverStatistic formal) {
this.formal = formal;
}
public DriverStatistic getSprint() {
return sprint;
}
public void setSprint(DriverStatistic sprint) {
this.sprint = sprint;
}
public List<String> getDrivers() {
return drivers;
}
public void setDrivers(List<String> drivers) {
this.drivers = drivers;
}
public int getRank() {
return rank;
}
public void setRank(int rank) {
this.rank = rank;
}
public int getTotalScore() {
return totalScore;
}
public void setTotalScore(int totalScore) {
this.totalScore = totalScore;
}
}

View File

@@ -0,0 +1,73 @@
package f1.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
public class User {
private int id;
private String username;
@JsonIgnore
private String password;
private String email;
private String country;
private String avatar;
public User() {
}
public User(int id, String username, String password, String email, String country, String avatar) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
this.country = country;
this.avatar = avatar;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
}

View File

@@ -0,0 +1,50 @@
package f1.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.ArrayList;
@Component
public class JwtFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = null;
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if ("auth_token".equals(cookie.getName())) {
token = cookie.getValue();
break;
}
}
}
if (token != null && jwtUtil.validateToken(token)) {
String username = jwtUtil.getUsernameFromToken(token);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
username, null, new ArrayList<>());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,56 @@
package f1.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import java.security.Key;
import java.util.Date;
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secretKeyString;
private Key secretKey;
private static final long EXPIRATION_TIME = 86400000; // 24小时 (毫秒)
@PostConstruct
public void init() {
byte[] keyBytes = Decoders.BASE64.decode(secretKeyString);
this.secretKey = Keys.hmacShaKeyFor(keyBytes);
}
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(secretKey, SignatureAlgorithm.HS256)
.compact();
}
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
}

View File

@@ -0,0 +1,39 @@
package f1.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.http.HttpMethod;
@Configuration
public class SecurityConfig {
@Autowired
private JwtFilter jwtFilter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests()
// Blacklist: Protect specific endpoints
.requestMatchers(HttpMethod.GET, "/api/user").authenticated()
.requestMatchers(HttpMethod.POST, "/api/comments/**").authenticated()
.requestMatchers(HttpMethod.DELETE, "/api/comments/**").authenticated()
// Whitelist: Allow everything else (static resources, other APIs)
.anyRequest().permitAll()
.and()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}

View File

@@ -0,0 +1,4 @@
spring.datasource.url=jdbc:mysql://124.70.86.207:3306/h_db23373332
spring.datasource.username=u23373332
spring.datasource.password=
jwt.secret=

View File

@@ -0,0 +1,6 @@
spring.datasource.url=jdbc:mysql://your-host:3306/your-db
spring.datasource.username=your-username
spring.datasource.password=your-password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
jwt.secret=24VSSNj6bDX7mwjIsidnJQYg3Lk6Ht9YvqYZYj7aDTc=
jwt.secret=your-base64-encoded-secret-key-min-32-bytes

View File

@@ -0,0 +1,148 @@
-- 车手表
CREATE TABLE IF NOT EXISTS driver(
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
country VARCHAR(100),
birthday DATE
);
-- 车队表
CREATE TABLE IF NOT EXISTS team(
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
country VARCHAR(100),
engine_supplier VARCHAR(100),
setup_time YEAR
);
-- 合同表
CREATE TABLE IF NOT EXISTS contract(
id INT AUTO_INCREMENT PRIMARY KEY,
driver_id INT,
team_id INT,
season YEAR,
car_num INT,
is_reserve BOOLEAN,
FOREIGN KEY(driver_id) REFERENCES driver(id),
FOREIGN KEY(team_id) REFERENCES team(id)
);
-- 赛道表
CREATE TABLE IF NOT EXISTS circuit(
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
location VARCHAR(100),
country VARCHAR(100),
drs_zones INT
);
-- 赛事表
CREATE TABLE IF NOT EXISTS prix(
id INT AUTO_INCREMENT PRIMARY KEY,
season YEAR,
round INT,
name VARCHAR(100),
circuit_id INT,
have_sprint BOOLEAN,
FOREIGN KEY(circuit_id) REFERENCES circuit(id)
);
-- 排位赛结果表
CREATE TABLE IF NOT EXISTS qualifying_result(
prix_id INT,
driver_id INT,
team_id INT,
fastest_time VARCHAR(20),
position INT,
is_sprint BOOLEAN,
section INT,
FOREIGN KEY(prix_id) REFERENCES prix(id),
FOREIGN KEY(driver_id) REFERENCES driver(id),
FOREIGN KEY(team_id) REFERENCES team(id)
);
-- 正赛结果表
CREATE TABLE IF NOT EXISTS race_result(
prix_id INT,
driver_id INT,
team_id INT,
start_position INT,
end_position INT,
finish_time VARCHAR(20),
fastest_time VARCHAR(20),
score INT,
is_sprint BOOLEAN,
FOREIGN KEY(prix_id) REFERENCES prix(id),
FOREIGN KEY(driver_id) REFERENCES driver(id),
FOREIGN KEY(team_id) REFERENCES team(id)
);
-- 用户表
CREATE TABLE IF NOT EXISTS user(
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) UNIQUE,
password VARCHAR(100),
email VARCHAR(100),
country VARCHAR(100),
avatar VARCHAR(500)
);
-- 评论表
CREATE TABLE IF NOT EXISTS comment(
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
response_id INT,
root_id INT,
content VARCHAR(500),
FOREIGN KEY(user_id) REFERENCES user(id),
FOREIGN KEY(response_id) REFERENCES comment(id) ON DELETE CASCADE,
FOREIGN KEY(root_id) REFERENCES comment(id) ON DELETE CASCADE
);
-- 赛季车手信息视图
CREATE OR REPLACE VIEW season_driver(
id, name, team, country, birthday, car_num, season
) AS
SELECT D.id,
D.name,
T.name,
D.country,
D.birthday,
C.car_num,
C.season
FROM contract C
JOIN team T ON T.id = C.team_id
JOIN driver D ON C.driver_id = D.id;
-- 赛季车队积分榜视图
CREATE OR REPLACE VIEW season_team_standings AS
SELECT
p.season,
rr.team_id,
SUM(rr.score) AS total_score,
RANK() OVER (PARTITION BY p.season ORDER BY SUM(rr.score) DESC) AS ranking
FROM race_result rr
JOIN prix p ON rr.prix_id = p.id
GROUP BY p.season, rr.team_id;
CREATE OR REPLACE VIEW prix_result AS
SELECT
p.season,
p.id AS prix_id,
rr.driver_id,
rr.team_id,
p.name AS prix_name,
p.round,
sd.name AS driver_name,
sd.car_num,
sd.team AS team_name,
rr.end_position AS pos,
rr.finish_time,
rr.score,
rr.is_sprint
FROM
race_result rr
JOIN
prix p ON rr.prix_id = p.id
JOIN
season_driver sd ON rr.driver_id = sd.id AND sd.season = p.season;

View File

@@ -0,0 +1,4 @@
spring.datasource.url=jdbc:mysql://124.70.86.207:3306/h_db23373332
spring.datasource.username=u23373332
spring.datasource.password=
jwt.secret=

View File

@@ -0,0 +1,6 @@
spring.datasource.url=jdbc:mysql://your-host:3306/your-db
spring.datasource.username=your-username
spring.datasource.password=your-password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
jwt.secret=24VSSNj6bDX7mwjIsidnJQYg3Lk6Ht9YvqYZYj7aDTc=
jwt.secret=your-base64-encoded-secret-key-min-32-bytes

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

148
backend/target/classes/init.sql Executable file
View File

@@ -0,0 +1,148 @@
-- 车手表
CREATE TABLE IF NOT EXISTS driver(
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
country VARCHAR(100),
birthday DATE
);
-- 车队表
CREATE TABLE IF NOT EXISTS team(
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
country VARCHAR(100),
engine_supplier VARCHAR(100),
setup_time YEAR
);
-- 合同表
CREATE TABLE IF NOT EXISTS contract(
id INT AUTO_INCREMENT PRIMARY KEY,
driver_id INT,
team_id INT,
season YEAR,
car_num INT,
is_reserve BOOLEAN,
FOREIGN KEY(driver_id) REFERENCES driver(id),
FOREIGN KEY(team_id) REFERENCES team(id)
);
-- 赛道表
CREATE TABLE IF NOT EXISTS circuit(
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
location VARCHAR(100),
country VARCHAR(100),
drs_zones INT
);
-- 赛事表
CREATE TABLE IF NOT EXISTS prix(
id INT AUTO_INCREMENT PRIMARY KEY,
season YEAR,
round INT,
name VARCHAR(100),
circuit_id INT,
have_sprint BOOLEAN,
FOREIGN KEY(circuit_id) REFERENCES circuit(id)
);
-- 排位赛结果表
CREATE TABLE IF NOT EXISTS qualifying_result(
prix_id INT,
driver_id INT,
team_id INT,
fastest_time VARCHAR(20),
position INT,
is_sprint BOOLEAN,
section INT,
FOREIGN KEY(prix_id) REFERENCES prix(id),
FOREIGN KEY(driver_id) REFERENCES driver(id),
FOREIGN KEY(team_id) REFERENCES team(id)
);
-- 正赛结果表
CREATE TABLE IF NOT EXISTS race_result(
prix_id INT,
driver_id INT,
team_id INT,
start_position INT,
end_position INT,
finish_time VARCHAR(20),
fastest_time VARCHAR(20),
score INT,
is_sprint BOOLEAN,
FOREIGN KEY(prix_id) REFERENCES prix(id),
FOREIGN KEY(driver_id) REFERENCES driver(id),
FOREIGN KEY(team_id) REFERENCES team(id)
);
-- 用户表
CREATE TABLE IF NOT EXISTS user(
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) UNIQUE,
password VARCHAR(100),
email VARCHAR(100),
country VARCHAR(100),
avatar VARCHAR(500)
);
-- 评论表
CREATE TABLE IF NOT EXISTS comment(
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
response_id INT,
root_id INT,
content VARCHAR(500),
FOREIGN KEY(user_id) REFERENCES user(id),
FOREIGN KEY(response_id) REFERENCES comment(id) ON DELETE CASCADE,
FOREIGN KEY(root_id) REFERENCES comment(id) ON DELETE CASCADE
);
-- 赛季车手信息视图
CREATE OR REPLACE VIEW season_driver(
id, name, team, country, birthday, car_num, season
) AS
SELECT D.id,
D.name,
T.name,
D.country,
D.birthday,
C.car_num,
C.season
FROM contract C
JOIN team T ON T.id = C.team_id
JOIN driver D ON C.driver_id = D.id;
-- 赛季车队积分榜视图
CREATE OR REPLACE VIEW season_team_standings AS
SELECT
p.season,
rr.team_id,
SUM(rr.score) AS total_score,
RANK() OVER (PARTITION BY p.season ORDER BY SUM(rr.score) DESC) AS ranking
FROM race_result rr
JOIN prix p ON rr.prix_id = p.id
GROUP BY p.season, rr.team_id;
CREATE OR REPLACE VIEW prix_result AS
SELECT
p.season,
p.id AS prix_id,
rr.driver_id,
rr.team_id,
p.name AS prix_name,
p.round,
sd.name AS driver_name,
sd.car_num,
sd.team AS team_name,
rr.end_position AS pos,
rr.finish_time,
rr.score,
rr.is_sprint
FROM
race_result rr
JOIN
prix p ON rr.prix_id = p.id
JOIN
season_driver sd ON rr.driver_id = sd.id AND sd.season = p.season;

View File

@@ -0,0 +1,27 @@
/Users/colden/编程/web/formula1/backend/src/main/java/f1/entity/Driver.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/entity/QualifyingResultItem.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/controller/UserController.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/db/Database.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/security/SecurityConfig.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/security/JwtFilter.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/Main.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/db/TeamDao.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/entity/User.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/AppConfig.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/entity/SeasonDriver.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/security/JwtUtil.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/entity/CommentItem.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/entity/RaceResultItem.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/controller/AuthController.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/entity/DriverStatistic.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/db/UserDao.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/entity/TeamHistoryItem.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/entity/Team.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/db/DriverDao.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/entity/TeamStatistic.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/entity/SeasonScheduleItem.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/entity/DriverHistoryItem.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/controller/CommentController.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/db/RaceDao.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/controller/F1Controller.java
/Users/colden/编程/web/formula1/backend/src/main/java/f1/db/BaseDao.java