实现了两个dto

This commit is contained in:
zhangyuheng 2024-04-09 16:41:38 +08:00
parent bb716f84a5
commit 50e7b1125f
11 changed files with 681 additions and 12 deletions

55
README.md Normal file
View File

@ -0,0 +1,55 @@
<div style="text-align: center;">
# Architecture Competition 48 (AC48)
### [开源地址](https://ssl.lunadeer.cn:14446/zhangyuheng/AC48) | [文档地址](https://ssl.lunadeer.cn:14448/doc/28/)
### [下载页面](https://ssl.lunadeer.cn:14446/zhangyuheng/AC48/releases)
### [统计页面](https://bstats.org/plugin/bukkit/AC48/21550) | [Hangar]()
</div>
## 简介
## 说明
## 功能介绍
## 支持版本
- 1.20.1+ (Paper、Folia)
## 安装方法
1. 将插件放入服务器的 `plugins` 目录下
2. 重启服务器
3. 在 `plugins/XXXXXXX/config.yml` 中配置
4. 重启服务器
## 玩家使用方法
## 管理员指南
## 指令
### 玩家指令
| 指令 | 功能描述 |
|---------|------|
| `/ac48` | 打开菜单 |
### 管理员指令
| 指令 | 功能描述 |
|--------------------|-------------------|
| `/ac48 activate` | 从配置文件加载比赛设置并激活比赛 |
| `/ac48 update` | 从配置文件更新比赛设置 |
| `/ac48 deactivate` | 关闭比赛(关闭后不可恢复谨慎操作) |
## 配置文件参考
```yaml
```
## TODO

10
pom.xml
View File

@ -67,5 +67,15 @@
<version>1.20.1-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.34.0</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
</dependency>
</dependencies>
</project>

View File

@ -3,6 +3,8 @@ package cn.lunadeer.ac48;
import cn.lunadeer.ac48.utils.*;
import org.bukkit.plugin.java.JavaPlugin;
import java.sql.Connection;
public final class AC48 extends JavaPlugin {
@Override
@ -10,6 +12,8 @@ public final class AC48 extends JavaPlugin {
// Plugin startup logic
instance = this;
config = new ConfigManager(this);
dbConnection = Database.createConnection();
Database.migrate();
scheduler = new Scheduler(this);
Metrics metrics = new Metrics(this, 21550);
@ -40,5 +44,6 @@ public final class AC48 extends JavaPlugin {
public static AC48 instance;
public static ConfigManager config;
public static Scheduler scheduler;
public static Connection dbConnection;
private GiteaReleaseCheck giteaReleaseCheck;
}

View File

@ -0,0 +1,32 @@
package cn.lunadeer.ac48;
import cn.lunadeer.ac48.utils.XLogger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CompetitorTeam {
/**
* 从配置字符串构造一个队伍
*
* @param configString team1:leader:player1
*/
public CompetitorTeam(String configString) {
String[] parts = configString.split(":");
if (parts.length < 2) {
XLogger.err("队伍配置错误: " + configString);
XLogger.err("格式: 应当至少包含队伍名和队长");
return;
}
team_name = parts[0];
team_leader = parts[1];
if (parts.length > 2) {
members.addAll(Arrays.asList(parts).subList(2, parts.length));
}
}
private String team_name;
private String team_leader;
private List<String> members = new ArrayList<>();
}

View File

@ -0,0 +1,165 @@
package cn.lunadeer.ac48;
import cn.lunadeer.ac48.utils.XLogger;
import java.sql.*;
public class Database {
public static Connection createConnection() {
try {
String connectionUrl;
if (AC48.config.getDbType().equals("pgsql")) {
XLogger.info("正在连接到 PostgreSQL 数据库");
Class.forName("org.postgresql.Driver");
connectionUrl = "jdbc:postgresql://" + AC48.config.getDbHost() + ":" + AC48.config.getDbPort();
connectionUrl += "/" + AC48.config.getDbName();
return DriverManager.getConnection(connectionUrl, AC48.config.getDbUser(), AC48.config.getDbPass());
} else if (AC48.config.getDbType().equals("sqlite")) {
XLogger.info("正在连接到 SQLite 数据库");
Class.forName("org.sqlite.JDBC");
connectionUrl = "jdbc:sqlite:" + AC48.instance.getDataFolder() + "/" + AC48.config.getDbName() + ".db";
return DriverManager.getConnection(connectionUrl);
} else {
XLogger.err("=== 严重错误 ===");
XLogger.err("数据库类型错误,只能为 pgsql 或 sqlite");
XLogger.err("===============");
return null;
}
} catch (ClassNotFoundException | SQLException e) {
XLogger.err("=== 严重错误 ===");
XLogger.err("Database connection failed: " + e.getMessage());
XLogger.err("===============");
return null;
}
}
public static ResultSet query(String sql) {
Connection conn = AC48.dbConnection;
if (conn == null) {
return null;
}
try {
Statement stmt = conn.createStatement();
if (sql.contains("SERIAL PRIMARY KEY") && AC48.config.getDbType().equals("sqlite")) {
sql = sql.replace("SERIAL PRIMARY KEY", "INTEGER PRIMARY KEY AUTOINCREMENT");
}
// if query with no result return null
if (stmt.execute(sql)) {
return stmt.getResultSet();
}
} catch (SQLException e) {
handleDatabaseError("Database query failed: ", e, sql);
}
return null;
}
private static void handleDatabaseError(String errorMessage, SQLException e, String sql) {
XLogger.err("=== 严重错误 ===");
XLogger.err(errorMessage + e.getMessage());
XLogger.err("SQL: " + sql);
XLogger.err("===============");
}
private static void addColumnIfNotExists(String tableName, String columnName, String columnDefinition) {
if (AC48.config.getDbType().equals("pgsql")) {
String sql = "ALTER TABLE " + tableName + " ADD COLUMN IF NOT EXISTS " + columnName + " " + columnDefinition + ";";
query(sql);
} else if (AC48.config.getDbType().equals("sqlite")) {
try {
ResultSet rs = query("PRAGMA table_info(" + tableName + ");");
boolean columnExists = false;
if (rs != null) {
while (rs.next()) {
if (columnName.equals(rs.getString("name"))) {
columnExists = true;
break;
}
}
}
if (!columnExists) {
query("ALTER TABLE " + tableName + " ADD COLUMN " + columnName + " " + columnDefinition + ";");
}
} catch (SQLException e) {
handleDatabaseError("Database operation failed: ", e, "");
}
}
}
public static void migrate() {
String sql = "";
// player name
// - mode: NORMAL, COMPETITOR, MATERIAL_SUPPORT, AUDIENCE
sql = "CREATE TABLE IF NOT EXISTS player_name (" +
" id SERIAL PRIMARY KEY," +
" uuid VARCHAR(36) NOT NULL UNIQUE," +
" last_known_name TEXT NOT NULL," +
" mode TEXT NOT NULL DEFAULT 'normal'" +
");";
query(sql);
// theme
sql = "CREATE TABLE IF NOT EXISTS theme (" +
" id SERIAL PRIMARY KEY," +
" name TEXT NOT NULL UNIQUE," +
" begin_time TIMESTAMP NOT NULL," +
" duration_hour INTEGER NOT NULL DEFAULT 48," +
" team_vote_limit INTEGER NOT NULL," +
" activated BOOLEAN NOT NULL DEFAULT FALSE" +
");";
query(sql);
// team
sql = "CREATE TABLE IF NOT EXISTS team (" +
" id SERIAL PRIMARY KEY," +
" theme_id INTEGER NOT NULL," +
" name TEXT NOT NULL," +
" captain_id INTEGER NOT NULL," +
" vote_used INTEGER NOT NULL DEFAULT 0," +
" vote_gained INTEGER NOT NULL DEFAULT 0," +
" schematic_base64 TEXT," +
" FOREIGN KEY (theme_id) REFERENCES theme(id) ON DELETE CASCADE," +
" FOREIGN KEY (captain_id) REFERENCES player_name(id) ON DELETE CASCADE" +
");";
query(sql);
// team_member
sql = "CREATE TABLE IF NOT EXISTS team_member (" +
" id SERIAL PRIMARY KEY," +
" team_id INTEGER NOT NULL," +
" player_id INTEGER NOT NULL," +
" FOREIGN KEY (team_id) REFERENCES team(id) ON DELETE CASCADE," +
" FOREIGN KEY (player_id) REFERENCES player_name(id) ON DELETE CASCADE" +
");";
query(sql);
// team invitation
sql = "CREATE TABLE IF NOT EXISTS team_invitation (" +
" id SERIAL PRIMARY KEY," +
" team_id INTEGER NOT NULL," +
" player_id INTEGER NOT NULL," +
" invited_by INTEGER NOT NULL," +
" expire_at TIMESTAMP NOT NULL," +
" FOREIGN KEY (team_id) REFERENCES team(id) ON DELETE CASCADE," +
" FOREIGN KEY (player_id) REFERENCES player_name(id) ON DELETE CASCADE," +
" FOREIGN KEY (invited_by) REFERENCES player_name(id) ON DELETE CASCADE" +
");";
query(sql);
// material_sponsor
sql = "CREATE TABLE IF NOT EXISTS material_sponsor (" +
" id SERIAL PRIMARY KEY," +
" theme_id INTEGER NOT NULL," +
" sponsor_id INTEGER NOT NULL," +
" material_name TEXT NOT NULL," +
" FOREIGN KEY (theme_id) REFERENCES theme(id) ON DELETE CASCADE," +
" FOREIGN KEY (sponsor_id) REFERENCES player_name(id) ON DELETE CASCADE" +
");";
query(sql);
}
}

View File

@ -0,0 +1,17 @@
package cn.lunadeer.ac48;
import cn.lunadeer.ac48.dtos.PlayerDTO;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
public class Events implements Listener {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player bukkitPlayer = event.getPlayer();
PlayerDTO player = PlayerDTO.getFromBukkitPlayer(bukkitPlayer);
player = player.updateName(bukkitPlayer.getName());
// todo
}
}

View File

@ -0,0 +1,28 @@
package cn.lunadeer.ac48;
import cn.lunadeer.ac48.utils.XLogger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MaterialSupport {
/**
* 从配置字符串构造一个材料赞助商
*
* @param configString name1:material1:material2
*/
public MaterialSupport(String configString) {
String[] parts = configString.split(":");
if (parts.length < 2) {
XLogger.err("材料赞助商配置错误: " + configString);
XLogger.err("格式: 应当至少包含玩家名和一个材料种类");
return;
}
player_name = parts[0];
materials.addAll(Arrays.asList(parts).subList(1, parts.length));
}
private String player_name;
private List<String> materials = new ArrayList<>();
}

View File

@ -0,0 +1,125 @@
package cn.lunadeer.ac48.dtos;
import cn.lunadeer.ac48.Database;
import cn.lunadeer.ac48.utils.XLogger;
import org.bukkit.entity.Player;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class PlayerDTO {
public enum Mode {
NORMAL,
COMPETITOR,
MATERIAL_SUPPORT,
AUDIENCE
}
private final Integer id;
private final UUID uuid;
private String lastKnownName;
private Mode mode;
public Integer getId() {
return id;
}
public UUID getUuid() {
return uuid;
}
public String getLastKnownName() {
return lastKnownName;
}
public Mode getMode() {
return mode;
}
public PlayerDTO setMode(Mode mode) {
this.mode = mode;
return update(this);
}
private static List<PlayerDTO> query(String sql) {
List<PlayerDTO> players = new ArrayList<>();
try (ResultSet rs = Database.query(sql)) {
if (rs == null) return players;
while (rs.next()) {
Integer id = rs.getInt("id");
UUID uuid = UUID.fromString(rs.getString("uuid"));
String lastKnownName = rs.getString("last_known_name");
Mode mode = Mode.valueOf(rs.getString("mode"));
PlayerDTO player = new PlayerDTO(id, uuid, lastKnownName, mode);
players.add(player);
}
} catch (SQLException e) {
XLogger.err("Database query failed: " + e.getMessage());
XLogger.err("SQL: " + sql);
}
return players;
}
private PlayerDTO(Integer id, UUID uuid, String lastKnownName, Mode mode) {
this.id = id;
this.uuid = uuid;
this.lastKnownName = lastKnownName;
this.mode = mode;
}
public static PlayerDTO getFromBukkitPlayer(Player bukkitPlayer) {
PlayerDTO player = select(bukkitPlayer.getUniqueId());
if (player == null) {
player = insert(bukkitPlayer);
}
return player;
}
public PlayerDTO updateName(String name) {
this.lastKnownName = name;
return update(this);
}
public static PlayerDTO select(UUID uuid) {
String sql = "SELECT * FROM player_name WHERE uuid = '" + uuid.toString() + "';";
List<PlayerDTO> players = query(sql);
if (players.size() == 0) return null;
return players.get(0);
}
public static PlayerDTO select(String name) {
String sql = "SELECT * FROM player_name WHERE last_known_name = '" + name + "';";
List<PlayerDTO> players = query(sql);
if (players.size() == 0) return null;
return players.get(0);
}
public static List<PlayerDTO> all() {
String sql = "SELECT * FROM player_name WHERE id > 0;";
return query(sql);
}
public static PlayerDTO insert(Player bukkitPlayer) {
String sql = "INSERT INTO player_name (uuid, last_known_name, mode) " +
"VALUES " +
"('" + bukkitPlayer.getUniqueId() + "', '" + bukkitPlayer.getName() + "', 'NORMAL') " +
"RETURNING *;";
List<PlayerDTO> players = query(sql);
if (players.size() != 1) return null;
return players.get(0);
}
public static PlayerDTO update(PlayerDTO player) {
String sql = "UPDATE player_name SET " +
"last_known_name = '" + player.lastKnownName + "', " +
"mode = '" + player.mode + "' " +
"WHERE id = " + player.id + " " +
"RETURNING *;";
List<PlayerDTO> players = query(sql);
if (players.size() != 1) return null;
return players.get(0);
}
}

View File

@ -0,0 +1,113 @@
package cn.lunadeer.ac48.dtos;
import cn.lunadeer.ac48.AC48;
import cn.lunadeer.ac48.Database;
import cn.lunadeer.ac48.utils.XLogger;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
public class ThemeDTO {
private final Integer id;
private final String name;
private final LocalDateTime beginTime;
private final Integer durationHours;
private final Integer teamVoteLimit;
private final Boolean activated;
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public LocalDateTime getBeginTime() {
return beginTime;
}
public Integer getDurationHours() {
return durationHours;
}
public Integer getTeamVoteLimit() {
return teamVoteLimit;
}
public Boolean getActivated() {
return activated;
}
private static List<ThemeDTO> query(String sql) {
List<ThemeDTO> themes = new ArrayList<>();
try (ResultSet rs = Database.query(sql)) {
if (rs == null) return themes;
while (rs.next()) {
Integer id = rs.getInt("id");
String name = rs.getString("name");
LocalDateTime beginTime = rs.getTimestamp("begin_time").toLocalDateTime();
Integer durationHours = rs.getInt("duration_hour");
Integer teamVoteLimit = rs.getInt("team_vote_limit");
Boolean activated = rs.getBoolean("activated");
ThemeDTO theme = new ThemeDTO(id, name, beginTime, durationHours, teamVoteLimit, activated);
themes.add(theme);
}
} catch (SQLException e) {
XLogger.err("Database query failed: " + e.getMessage());
XLogger.err("SQL: " + sql);
}
return themes;
}
private ThemeDTO(Integer id, String name, LocalDateTime beginTime, Integer durationHours, Integer teamVoteLimit, Boolean activated) {
this.id = id;
this.name = name;
this.beginTime = beginTime;
this.durationHours = durationHours;
this.teamVoteLimit = teamVoteLimit;
this.activated = activated;
}
public static ThemeDTO createThemeFromConfig() {
String sql = "INSERT INTO theme (name, begin_time, duration_hour, team_vote_limit, activated) " +
"VALUES" +
" ('" + AC48.config.getTheme() + "'," +
" '" + AC48.config.getBeginTime() + "'," +
" " + AC48.config.getDurationHour() + "," +
" " + AC48.config.getTeamVoteLimit() + "," +
" TRUE) " +
"RETURNING *;";
List<ThemeDTO> themes = query(sql);
if (themes.size() != 1) return null;
return themes.get(0);
}
public static ThemeDTO updateThemeFromConfig() {
String sql = "UPDATE theme SET " +
"name = '" + AC48.config.getTheme() + "', " +
"begin_time = '" + AC48.config.getBeginTime() + "', " +
"duration_hour = " + AC48.config.getDurationHour() + ", " +
"team_vote_limit = " + AC48.config.getTeamVoteLimit() + " " +
"WHERE activated = TRUE " +
"RETURNING *;";
List<ThemeDTO> themes = query(sql);
if (themes.size() != 1) return null;
return themes.get(0);
}
public static ThemeDTO getCurrentActivateTheme() {
String sql = "SELECT * FROM theme WHERE activated = TRUE;";
List<ThemeDTO> themes = query(sql);
if (themes.size() != 1) return null;
return themes.get(0);
}
public static void deactivateCurrentTheme() {
String sql = "UPDATE theme SET activated = FALSE WHERE activated = TRUE;";
Database.query(sql);
}
}

View File

@ -3,6 +3,8 @@ package cn.lunadeer.ac48.utils;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.time.LocalDateTime;
public class ConfigManager {
public ConfigManager(JavaPlugin plugin) {
_plugin = plugin;
@ -16,6 +18,23 @@ public class ConfigManager {
_file = _plugin.getConfig();
_debug = _file.getBoolean("Debug", false);
_check_update = _file.getBoolean("CheckUpdate", true);
_theme = _file.getString("Theme", "AC48");
_begin_time = LocalDateTime.parse(_file.getString("BeginTime", "2021-01-01T00:00:00"));
_duration_hour = _file.getInt("DurationHour", 48);
_team_vote_limit = _file.getInt("TeamVoteLimit", 1);
_tag_competitor = _file.getString("Tag.Competitor", "参赛人员");
_tag_material = _file.getString("Tag.Material", "建材支持");
_tag_audience = _file.getString("Tag.Audience", "观众");
_db_type = _file.getString("Database.Type", "sqlite");
if (!_db_type.equals("pgsql") && !_db_type.equals("sqlite")) {
XLogger.err("当前数据库只支持 pgsql 或 sqlite已重置为 sqlite");
setDbType("sqlite");
}
_db_host = _file.getString("Database.Host", "localhost");
_db_port = _file.getString("Database.Port", "5432");
_db_name = _file.getString("Database.Name", "dominion");
_db_user = _file.getString("Database.User", "postgres");
_db_pass = _file.getString("Database.Pass", "postgres");
}
public Boolean isDebug() {
@ -32,9 +51,99 @@ public class ConfigManager {
return _check_update;
}
public String getDbType() {
return _db_type;
}
public void setDbType(String db_type) {
_db_type = db_type;
_file.set("Database.Type", db_type);
_plugin.saveConfig();
}
public String getDbHost() {
return _db_host;
}
public String getDbPort() {
return _db_port;
}
public String getDbName() {
return _db_name;
}
public void setDbUser(String db_user) {
_db_user = db_user;
_file.set("Database.User", db_user);
_plugin.saveConfig();
}
public String getDbUser() {
if (_db_user.contains("@")) {
setDbUser("'" + _db_user + "'");
}
return _db_user;
}
public void setDbPass(String db_pass) {
_db_pass = db_pass;
_file.set("Database.Pass", db_pass);
_plugin.saveConfig();
}
public String getDbPass() {
if (_db_pass.contains("@")) {
setDbPass("'" + _db_pass + "'");
}
return _db_pass;
}
public String getTheme() {
return _theme;
}
public LocalDateTime getBeginTime() {
return _begin_time;
}
public Integer getDurationHour() {
return _duration_hour;
}
public Integer getTeamVoteLimit() {
return _team_vote_limit;
}
public String getTagCompetitor() {
return _tag_competitor;
}
public String getTagMaterial() {
return _tag_material;
}
public String getTagAudience() {
return _tag_audience;
}
private final JavaPlugin _plugin;
private FileConfiguration _file;
private Boolean _debug;
private Boolean _check_update;
private String _theme;
private LocalDateTime _begin_time;
private Integer _duration_hour;
private Integer _team_vote_limit;
private String _tag_competitor;
private String _tag_material;
private String _tag_audience;
private String _db_type;
private String _db_host;
private String _db_port;
private String _db_user;
private String _db_pass;
private String _db_name;
}

View File

@ -1,26 +1,36 @@
# 比赛开始时间
# 报名时间从比赛开始时间的前两周开始
# 参赛人员报名截止为比赛开始
# 材料供应商报名截止在比赛开始前一天
# T - 14d ~ T + 48h: 赛程时间
# T - 14d ~ T - 1d: 材料供应商报名起止时间
# T - 14d ~ T: 参赛团队报名起止时间
# T: 比赛开始时间
# T + 48h: 比赛结束时间
BeginTime: "2024-07-01T18:00:00"
# 比赛主题
Theme: "Default"
# 比赛开始时间
BeginTime: "2024-07-01 18:00:00"
# 比赛时长(小时)
DurationHour: 48
# 队伍名:票数:队长:队员1:队员2
CompetitorTeam:
- team1:vote:leader:player1
- team2:vote:leader:player1:player2
# 队伍可投票数
TeamVoteLimit: 1
# 建材赞助商名:建材1:建材2
MaterialSupport:
- name1:material1:material2
- name2:material1:material2:material3
# 数据库配置
Database:
Type: sqlite # pgsql, sqlite
Host: localhost
Port: 5432
Name: dominion
User: dominion
Pass: dominion
# 标签内容
Tag:
Competitor: "参赛人员"
Material: "建材赞助"
Material: "建材支持"
Audience: "观众"
CheckUpdate: true