Skip to content
Snippets Groups Projects
Commit 7c672902 authored by Xiemuqing Xiao's avatar Xiemuqing Xiao
Browse files

Merge branch 'main' into 'Xiemuqing-Xiao'

# Conflicts:
#   src/main/java/uk/ac/cf/spring/demo/sports/security/SecurityConfig.java
parents b90287e0 4b1bcd69
No related branches found
No related tags found
5 merge requests!51Resolves 64,!49Resolve issue 70,!48Resolve issue 70,!47Revert "Merge branch '26-as-a-user-i-want-to-see-the-rankings-of-each-member' into 'main'",!28front end styling
Showing
with 706 additions and 45 deletions
......@@ -2,11 +2,8 @@ package uk.ac.cf.spring.demo.sports.match;
import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.stereotype.Controller;
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 org.springframework.web.servlet.ModelAndView;
import org.springframework.web.bind.annotation.*;
//import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@RestController
......@@ -23,11 +20,33 @@ public class MatchController {
public List<MatchItem> getAllMenuItems() {
return matchService.getMatchItems();
}
// get Match by id
@GetMapping("/{id}")
public MatchItem getMenuItem(@PathVariable Long id) {
return matchService.getMatchItemById(id);
}
// @GetMapping("/match")
// add
@PostMapping
public void addMatchItem(@RequestBody MatchItem matchItem) {
matchService.addMatchItem(matchItem);
}
// update
@PutMapping("/{id}")
public void updateMatchItem(@PathVariable Long id, @RequestBody MatchItem matchItem) {
matchItem.setId(id);
matchService.updateMatchItem(matchItem);
}
//delete
@DeleteMapping("/{id}")
public void deleteMatchItem(@PathVariable Long id) {
matchService.deleteMatchItem(id);
}
// @GetMapping("/match")
// public ModelAndView getMatch() {
// ModelAndView modelAndView = new ModelAndView("match/allMatchList");
// List<MatchItem> matchItems = matchService.getMatchItems();
......@@ -41,39 +60,38 @@ public class MatchController {
// MatchItem matchItem = matchService.getMatchItemById(id);
// modelAndView.addObject("matchItem", matchItem);
// return modelAndView;
// }//matchItemForm add match
// @GetMapping("/match/add")
// public ModelAndView addMatchItem() {
// ModelAndView modelAndView = new ModelAndView("match/matchItemForm");
// MatchItemForm emptyMatchItem = new MatchItemForm();
// modelAndView.addObject("matchItem", emptyMatchItem);
// return modelAndView;
// }
//matchItemForm add match
@GetMapping("/match/add")
public ModelAndView addMatchItem() {
ModelAndView modelAndView = new ModelAndView("match/matchItemForm");
MatchItemForm emptyMatchItem = new MatchItemForm();
modelAndView.addObject("matchItem", emptyMatchItem);
return modelAndView;
}
//
@GetMapping("/match/edit/{id}")
public ModelAndView editMatchItemTh(@PathVariable("id") Long id) {
ModelAndView modelAndView = new ModelAndView("match/matchItemForm");
// construct matchToUpdate object to get matchItem by id
MatchItem matchToUpdate = matchService.getMatchItemById(id);
// Create the form object for the match
MatchItemForm matchItemForm = new MatchItemForm(
matchToUpdate.getId(),
matchToUpdate.getSport(),
matchToUpdate.getPlayerAId(),
matchToUpdate.getPlayerBId(),
matchToUpdate.getPlanTime(),
matchToUpdate.getStatus(),
matchToUpdate.getScoreA(),
matchToUpdate.getScoreB(),
matchToUpdate.getConfirmByA(),
matchToUpdate.getConfirmByB(),
matchToUpdate.getWinnerId()
);
modelAndView.addObject("matchItem", matchItemForm);
return modelAndView;
}
// @GetMapping("/match/edit/{id}")
// public ModelAndView editMatchItemTh(@PathVariable("id") Long id) {
// ModelAndView modelAndView = new ModelAndView("match/matchItemForm");
// // construct matchToUpdate object to get matchItem by id
// MatchItem matchToUpdate = matchService.getMatchItemById(id);
// // Create the form object for the match
// MatchItemForm matchItemForm = new MatchItemForm(
// matchToUpdate.getId(),
// matchToUpdate.getSport(),
// matchToUpdate.getPlayerAId(),
// matchToUpdate.getPlayerBId(),
// matchToUpdate.getPlanTime(),
// matchToUpdate.getStatus(),
// matchToUpdate.getScoreA(),
// matchToUpdate.getScoreB(),
// matchToUpdate.getConfirmByA(),
// matchToUpdate.getConfirmByB(),
// matchToUpdate.getWinnerId()
// );
//
// modelAndView.addObject("matchItem", matchItemForm);
// return modelAndView;
// }
}
......@@ -11,5 +11,9 @@ public interface MatchRepository {
List<MatchItem> getMatchItemBySport(String sport);
// add MatchItem
void addMatchItem(MatchItem matchItem);
// update MatchItem
void updateMatchItem(MatchItem matchItem);
// delete MatchItem
void deleteMatchItem(Long id);
}
......@@ -9,7 +9,7 @@ import java.util.List;
@Repository
public class MatchRepositoryImpl implements MatchRepository{
// jdbcTemplate - dependency injection
private JdbcTemplate jdbc;
private final JdbcTemplate jdbc;
private RowMapper<MatchItem> matchItemMapper;
public MatchRepositoryImpl(JdbcTemplate aJdbc) {
this.jdbc = aJdbc;
......@@ -70,4 +70,28 @@ public class MatchRepositoryImpl implements MatchRepository{
matchItem.getWinnerId()
);
}
@Override
public void updateMatchItem(MatchItem matchItem) {
String sql = "update match_item set sport = ?, player_AId = ?, player_BId = ?, plan_Time = ?, status = ?, score_a = ?, score_b = ?, confirm_a = ?, confirm_b = ?, winner_id = ? where id = ?";
jdbc.update(sql,
matchItem.getSport(),
matchItem.getPlayerAId(),
matchItem.getPlayerBId(),
Timestamp.valueOf(matchItem.getPlanTime()),
matchItem.getStatus(),
matchItem.getScoreA(),
matchItem.getScoreB(),
matchItem.getConfirmByA(),
matchItem.getConfirmByB(),
matchItem.getWinnerId(),
matchItem.getId()
);
}
@Override
public void deleteMatchItem(Long id) {
String sql = "DELETE FROM match_item WHERE id = ?";
jdbc.update(sql, id);
}
}
......@@ -11,4 +11,8 @@ public interface MatchService {
List<MatchItem> getMatchItemBySport(String sport);
// add MatchItem
void addMatchItem(MatchItem matchItem);
// update MatchItem
void updateMatchItem(MatchItem matchItem);
// delete MatchItem
void deleteMatchItem(Long id);
}
......@@ -27,9 +27,28 @@ public class MatchServiceListImpl implements MatchService{
public List<MatchItem> getMatchItemBySport(String sport) {
return matchRepository.getMatchItemBySport(sport);
}
// add
@Override
public void addMatchItem(MatchItem matchItem) {
matchRepository.addMatchItem(matchItem);
}
// update
@Override
public void updateMatchItem(MatchItem matchItem) {
if (matchItem.getId() == null || matchItem.getId() <= 0) {
throw new IllegalArgumentException("Invalid Match Item ID");
}
MatchItem existingItem = matchRepository.getMatchItemById(matchItem.getId());
if (existingItem != null) {
matchRepository.updateMatchItem(matchItem);
} else {
throw new IllegalArgumentException("Match item not found");
}
}
// delete
@Override
public void deleteMatchItem(Long id) {
matchRepository.deleteMatchItem(id);
}
}
package uk.ac.cf.spring.demo.sports.rankingtable;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/rankings") // 公共路径
public class RankingController {
private final RankingService rankingService;
public RankingController(RankingService rankingService) {
this.rankingService = rankingService;
}
/**
* get all rankings
*
* @return all rankings
*/
@GetMapping
public List<RankingTable> getAllRankings() {
return rankingService.getAllRankings();
}
@GetMapping("/byallandsort")
public List<TotalWinsDTO> getRankingsByAllSort(String order) {
//rankingService.processCompletedMatches();
return rankingService.getTotalWinsByAll(order);
}
/**
* get rankings filtered by sports and wins
*
* @return rankings
*/
@GetMapping("/bysportandsort")
public List<RankingTable> getRankingsBySportAndOrder(
@RequestParam(required = false) String sport,
@RequestParam(defaultValue = "desc") String order
) {
//rankingService.processCompletedMatches();
return rankingService.getRankingsBySportAndOrder(sport, order);
}
}
package uk.ac.cf.spring.demo.sports.rankingtable;
import lombok.Getter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class RankingRepository {
public List<RankingTable> findBySportAndOrder(String sport, String order) {
// 确保 order 参数是有效值
if (!"asc".equalsIgnoreCase(order) && !"desc".equalsIgnoreCase(order)) {
order = "desc";
}
String sql = "SELECT * FROM ranking WHERE sport = ? ORDER BY wins " + order.toUpperCase();
return jdbcTemplate.query(sql, mapRowToRanking(), sport);
}
@Getter
public static class MatchResult {
private final Long winnerId;
private final String sport;
public MatchResult(Long winnerId, String sport) {
this.winnerId = winnerId;
this.sport = sport;
}
}
private final JdbcTemplate jdbcTemplate;
public RankingRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// get all rankings sql code
public List<RankingTable> findAll() {
String sql = "SELECT * FROM ranking";
return jdbcTemplate.query(sql, mapRowToRanking());
}
public List<RankingTable> findBySport(String sport) {
System.out.println(sport);
String sql = "SELECT * FROM ranking WHERE sport = ?";
return jdbcTemplate.query(sql, mapRowToRanking(), sport);
}
// get ranking by userid and sport
public RankingTable findByUserIdAndSport(Long userId, String sport) {
String sql = "SELECT * FROM ranking WHERE user_id = ? AND sport = ?";
return jdbcTemplate.queryForObject(sql, mapRowToRanking(), userId, sport);
}
// update rankings
public void saveOrUpdate(Long userId, String sport, int newWins) {
String sql = """
INSERT INTO ranking (user_id, sport, wins)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE wins = ?
""";
jdbcTemplate.update(sql, userId, sport, newWins, newWins);
}
// get result from match where status is completed
public List<MatchResult> findWinnersAndSportsFromCompletedMatches() {
String sql = """
SELECT winner_id, sport
FROM match_item
WHERE status = 'completed'
""";
return jdbcTemplate.query(sql, (rs, rowNum) -> new MatchResult(
rs.getLong("winnerId"),
rs.getString("sport")
));
}
public List<RankingTable> findAllSortedByWins(String order) {
// 确保order参数的值是"asc"或"desc",如果不是则默认为"desc"
if (order == null || (!order.equalsIgnoreCase("asc") && !order.equalsIgnoreCase("desc"))) {
order = "asc"; // 默认降序
}
String sql = "SELECT * FROM ranking ORDER BY wins " + order.toUpperCase();
return jdbcTemplate.query(sql, mapRowToRanking());
}
//总胜利数查询
public List<TotalWinsDTO> findTotalWinsByAll(String order) {
// 验证 order 参数是否合法,只允许 "asc" 或 "desc"
String sortOrder = "desc"; // 默认值
if ("asc".equalsIgnoreCase(order)) {
sortOrder = "asc";
}
String sql = "SELECT user_id,username,'totalwins' AS sport, SUM(wins) AS wins FROM ranking GROUP BY user_id ORDER BY wins " + sortOrder;
return jdbcTemplate.query(sql, mapRowToTotalWinsDTO());
}
// a function can merge result into a ranking
private RowMapper<RankingTable> mapRowToRanking() {
return (rs, rowNum) -> {
RankingTable ranking = new RankingTable();
ranking.setId(rs.getLong("id")); // 映射 id
ranking.setUserId(rs.getLong("user_id")); // 映射 user_id
ranking.setSport(rs.getString("sport")); // 映射 sport
ranking.setWins(rs.getInt("wins")); // 映射 wins
ranking.setUsername(rs.getString("username")); // 映射 username
return ranking;
};
}
//聚合查询专用映射方法
private RowMapper<TotalWinsDTO> mapRowToTotalWinsDTO() {
return (rs, rowNum) -> {
TotalWinsDTO dto = new TotalWinsDTO();
dto.setUsername(rs.getString("username"));
dto.setUserId(rs.getLong("user_id"));
dto.setSport("totalwins"); // 聚合查询时 sport 固定为 "totalwins"
dto.setWins(rs.getInt("wins"));
return dto;
};
}
}
package uk.ac.cf.spring.demo.sports.rankingtable;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public interface RankingService {
List<RankingTable> getAllRankings();
List<RankingTable> getRankingsBySport(String sport);
void updateRanking(Long userId, String sport, int wins);
void processCompletedMatches();
List<RankingTable> getAllRankingsSortedByWins(String order);
List<RankingTable> getRankingsBySportAndOrder(String sport, String order);
List<TotalWinsDTO> getTotalWinsByAll(String order);
}
package uk.ac.cf.spring.demo.sports.rankingtable;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class RankingServiceImpl implements RankingService {
private final RankingRepository rankingRepository;
public RankingServiceImpl(RankingRepository rankingRepository) {
this.rankingRepository = rankingRepository;
}
@Override
public List<RankingTable> getAllRankings() {
return rankingRepository.findAll();
}
@Override
public List<RankingTable> getRankingsBySport(String sport) {
return rankingRepository.findBySport(sport);
}
@Override
public void updateRanking(Long userId, String sport, int wins) {
rankingRepository.saveOrUpdate(userId, sport, wins);
}
@Override
public void processCompletedMatches() {
List<RankingRepository.MatchResult> completedMatches = rankingRepository.findWinnersAndSportsFromCompletedMatches();
// loop every match result
for (RankingRepository.MatchResult result : completedMatches) {
Long winnerId = result.getWinnerId();
String sport = result.getSport();
// current user wins in particular sport
RankingTable currentRanking = rankingRepository.findByUserIdAndSport(winnerId, sport);
int currentWins = currentRanking != null ? currentRanking.getWins() : 0;
// update
rankingRepository.saveOrUpdate(winnerId, sport, currentWins + 1);
}
}
@Override
public List<RankingTable> getAllRankingsSortedByWins(String order) {
return rankingRepository.findAllSortedByWins(order);
}
@Override
public List<RankingTable> getRankingsBySportAndOrder(String sport, String order) {
if (sport == null || sport.equalsIgnoreCase("all")) {
// 如果没有选择特定运动,则按总胜利数排序
return rankingRepository.findAllSortedByWins(order);
} else {
// 按特定运动和排序方式筛选
return rankingRepository.findBySportAndOrder(sport, order);
}
}
public List<TotalWinsDTO> getTotalWinsByAll(String order) {
return rankingRepository.findTotalWinsByAll(order);
}
}
package uk.ac.cf.spring.demo.sports.rankingtable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class RankingTable {
//create entity class
private Long id; //
private Long userId; // 用户 ID
private int wins; // 获胜场次
private String sport;
private String username;
}
package uk.ac.cf.spring.demo.sports.rankingtable;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class TotalWinsDTO {
private Long userId;
private String sport;
private Integer wins;
private String username;
}
......@@ -27,7 +27,8 @@ public class SecurityConfig {
"/html/**", // static HTML
"/html/register.html", // register
"/html/login.html", // login
"/rankings",
"/html/login.html" , // login
"/rankings", //ranking data path
"/rankings/**"
};
......
delete from match_item;
delete from ranking;
insert into match_item (sport, player_AId, player_BId, plan_Time, status, score_a, score_b, confirm_a, confirm_b, winner_id)
values
-- test data
......@@ -14,3 +15,9 @@ values
('Darts', 1, 2, '2024-11-29 15:30:00', 'completed', 300, 280, TRUE, TRUE, 2),
-- 6: Confirmed status, Player 1 wins
('TableTennis', 1, 2, '2024-11-30 17:00:00', 'confirmed', 3, 1, TRUE, TRUE, 1);
INSERT INTO ranking (user_id,username,sport, wins) VALUES
(1, 'xzc','Pools', 5),
(2, 'shy','Darts', 3),
(3, 'xx','TableTennis', 10),
(2, 'shy','TableTennis', 4);
drop table if exists match_item;
drop table if exists rankings;
create table if not exists match_item (
-- Match ID
id BIGINT AUTO_INCREMENT PRIMARY KEY,
......@@ -24,4 +25,21 @@ CREATE TABLE information (
email VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role VARCHAR(20) DEFAULT 'USER' NOT NULL
);
\ No newline at end of file
);
CREATE TABLE IF NOT EXISTS ranking (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
sport ENUM('Pools', 'Darts', 'TableTennis') NOT NULL,
wins INT DEFAULT 0,
username VARCHAR(255)DEFAULT NULL
);
CREATE TABLE images (
id INT AUTO_INCREMENT PRIMARY KEY,
filename VARCHAR(255) NOT NULL, -- 图片文件名或路径
uploader VARCHAR(100) NOT NULL, -- 上传者名称
upload_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 上传时间
);
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
h1, h2 {
color: darkblue;
}
#matchForm {
/*display: grid;*/
align-items: center;
}
form input, form button {
margin: 5px;
padding: 10px;
font-size: 14px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
border: 1px solid white;
}
th, td {
padding: 10px;
text-align: left;
border: 1px solid white;
}
button {
background-color: dodgerblue;
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
margin: 5px;
padding: 10px;
width: 60px;
}
button:hover {
background-color: cornflowerblue;
}
* {
margin: 0;
padding: 0;
......
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #333;
}
.title-h1{
h1 {
background-color: #4a4a4a;
p {
font-size: 36px;
color: white;
font-weight: bold;
text-align: center;
}
}
}
.container {
width: 80%;
margin: 20px auto;
}
header {
text-align: center;
margin-bottom: 30px;
}
#matchDetails {
margin-left: 45px;
align-items: center;
background-color: #fff;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 900px;
padding: 30px;
box-sizing: border-box;
margin-top: 30px;
}
h2 {
text-align: center;
font-size: 2.5em;
color: #333;
margin-bottom: 20px;
}
#matchScore {
font-size: 2.4em;
line-height: 1.6;
color: #444;
margin: 20px 0;
}
#matchScore p {
text-align: center;
}
#matchInfo {
font-size: 1.2em;
line-height: 1.6;
color: #444;
}
#matchInfo p {
margin: 15px 0;
padding: 10px;
background-color: #f9f9f9;
border-radius: 6px;
}
strong {
color: #2d2d2d;
}
/*adjust for phone*/
@media screen and (max-width: 768px) {
.container {
width: 95%;
}
.match-item {
flex-direction: column;
align-items: flex-start;
}
}
......@@ -99,6 +99,7 @@ button {
justify-content: space-between;
align-items: center;
gap: 20px;
cursor: pointer;
}
.match-item p {
......
......@@ -93,7 +93,7 @@ body {
.navbar .auth-buttons .login-btn {
background-color: #007BFF;
width: 100px;
text-align-all: center;
text-align: center;
margin-bottom: 5px;
color: white;
font-weight: bold;
......@@ -102,7 +102,7 @@ body {
.navbar .auth-buttons .signup-btn {
background-color: #28a745;
width: 100px;
text-align-all: center;
text-align: center;
margin-bottom: 5px;
color: white;
font-weight: bold;
......@@ -110,7 +110,7 @@ body {
.navbar .auth-buttons .subscribe-btn {
background-color: #d68418;
width: 100px;
text-align-all: center;
text-align: center;
margin-bottom: 5px;
color: white;
font-weight: bold;
......
/* 全局样式重置 */
body {
font-family: Arial, sans-serif;
background-color: #f8f9fa;
margin: 0;
padding: 0;
box-sizing: border-box;
color: #333;
}
/* 滤镜部分 */
.filter-container {
text-align: center;
margin: 20px 0;
font-size: 14px;
}
.filter-container label {
margin: 0 10px;
font-weight: bold;
color: #555;
}
.filter-container select {
padding: 8px 12px;
border-radius: 25px;
border: 2px solid #ff758c;
outline: none;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
background: white;
}
.filter-container select:hover {
background: #ff758c;
color: white;
}
/* 表格容器样式 */
.table-container {
width: 90%;
margin: 20px auto;
text-align: center; /* 子元素居中 */
}
/* 表格标题 */
.table-title {
font-size: 24px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
/* 表格样式 */
table {
width: 100%;
border-collapse: collapse;
margin: 0 auto;
background: #2d2d2d;
color: #f9f9f9;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
}
thead {
background: linear-gradient(90deg, #ff7eb3, #ff758c);
color: white;
font-weight: bold;
}
thead th {
padding: 12px 15px;
text-align: left;
}
tbody tr:nth-child(odd) {
background: rgba(255, 255, 255, 0.05);
}
tbody tr:nth-child(even) {
background: rgba(0, 0, 0, 0.05);
}
tbody tr:hover {
background: #ff7eb3;
color: white;
cursor: pointer;
transform: scale(1.02);
transition: all 0.2s ease-in-out;
}
tbody td {
padding: 10px 15px;
text-align: left;
}
/* 动画效果 */
.fade-in {
animation: fadeIn 0.5s ease-in-out forwards;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment