第十批:邀请存量数据自愈(回填绑定时间+重算月度计数)、本月邀请头颅明细GUI、修复Commission跨库查询失效(bond_transaction在bond.db)
This commit is contained in:
@@ -53,6 +53,16 @@ public class BondManager {
|
||||
initDB();
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 第十批(2026-10-12):暴露 bond.db 连接。
|
||||
* bond_transaction 在 bond.db(财务债券/),不在 login.db——
|
||||
* 跨库查询必须走这里(CommissionManager 日上限/翻倍统计、
|
||||
* InviteManager 存量绑定时间回填均依赖)。
|
||||
*/
|
||||
public Connection getConnection() {
|
||||
return db;
|
||||
}
|
||||
|
||||
// ======================== 数据库初始化 ========================
|
||||
|
||||
private void initDB() {
|
||||
|
||||
@@ -239,7 +239,7 @@ public class CommissionManager {
|
||||
.atStartOfDay(ZoneId.systemDefault())
|
||||
.toInstant().toEpochMilli();
|
||||
try {
|
||||
PreparedStatement ps = main.getDb()
|
||||
PreparedStatement ps = main.getBonds()
|
||||
.getConnection().prepareStatement(
|
||||
"SELECT COALESCE(SUM(amount),0) "
|
||||
+ "FROM bond_transaction "
|
||||
@@ -266,7 +266,7 @@ public class CommissionManager {
|
||||
.atStartOfDay(ZoneId.systemDefault())
|
||||
.toInstant().toEpochMilli();
|
||||
try {
|
||||
PreparedStatement ps = main.getDb()
|
||||
PreparedStatement ps = main.getBonds()
|
||||
.getConnection().prepareStatement(
|
||||
"SELECT COUNT(*) "
|
||||
+ "FROM bond_transaction "
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.bukkit.event.inventory.*;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.inventory.meta.SkullMeta;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
@@ -43,6 +44,8 @@ public class GUIManager implements Listener {
|
||||
"§6§l管理员面板";
|
||||
public static final String T_INVITE =
|
||||
"§6§l邀请数据";
|
||||
public static final String T_MONTHLY_INVITE =
|
||||
"§6§l本月邀请";
|
||||
public static final String T_USER_MGMT =
|
||||
"§6§l用户管理";
|
||||
public static final String T_USER_GROUP =
|
||||
@@ -2419,10 +2422,9 @@ public class GUIManager implements Listener {
|
||||
if (code == null || code.isEmpty()) {
|
||||
code = plugin.getInvite().generateCode(p);
|
||||
}
|
||||
int monthly = ((Number) plugin.getDb()
|
||||
.getField(p.getName(),
|
||||
"monthly_invite_count"))
|
||||
.intValue();
|
||||
// ★ 第十批:计数改派生口径(invited_at 回填后与头颅明细同源、跨月自愈)
|
||||
int monthly = plugin.getInvite()
|
||||
.getMonthlyInviteCount(p.getName());
|
||||
g.setItem(10, mkItem(Material.PAPER,
|
||||
"§b§l我的邀请码",
|
||||
"§7" + code,
|
||||
@@ -2430,7 +2432,9 @@ public class GUIManager implements Listener {
|
||||
"§e点击刷新邀请码"));
|
||||
g.setItem(12, mkItem(Material.PLAYER_HEAD,
|
||||
"§a本月邀请",
|
||||
"§7已邀请 §e" + monthly + " §7人"));
|
||||
"§7已邀请 §e" + monthly + " §7人",
|
||||
"",
|
||||
"§e点击查看邀请明细"));
|
||||
// ★ 第九批:已绑定邀请人则显示「邀请成功时间+邀请人」,
|
||||
// 否则显示「输入邀请码」按钮
|
||||
String invitedBy = (String) plugin.getDb()
|
||||
@@ -2470,6 +2474,67 @@ public class GUIManager implements Listener {
|
||||
p.openInventory(g);
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 第十批(2026-10-12):「本月邀请」明细 GUI——
|
||||
* 本月绑定我邀请码的玩家,一人一个真实玩家头颅。
|
||||
* 标题 T_MONTHLY_INVITE 已加入 Main.onInvClick 排除列表,
|
||||
* 点击全部由该分支自行 cancel(防拿走头颅)。
|
||||
*/
|
||||
public void openMonthlyInvite(Player p) {
|
||||
Inventory g = Bukkit.createInventory(
|
||||
null, 54, T_MONTHLY_INVITE);
|
||||
fillBg(g);
|
||||
List<Map<String, Object>> list = plugin.getInvite()
|
||||
.getMonthlyInvitedPlayers(p.getName());
|
||||
if (list.isEmpty()) {
|
||||
g.setItem(22, mkItem(Material.BARRIER,
|
||||
"§c本月暂无邀请记录",
|
||||
"§7邀请好友绑定你的邀请码后",
|
||||
"§7这里会展示他们的头颅"));
|
||||
} else {
|
||||
// 内容区 0-44 共 45 格;超出 45 人时留 1 格提示
|
||||
int limit = Math.min(list.size(),
|
||||
list.size() > 45 ? 44 : 45);
|
||||
for (int i = 0; i < limit; i++) {
|
||||
Map<String, Object> row = list.get(i);
|
||||
String name = String.valueOf(row.get("name"));
|
||||
long at = ((Number) row.get("at")).longValue();
|
||||
ItemStack head = new ItemStack(
|
||||
Material.PLAYER_HEAD);
|
||||
SkullMeta sm = (SkullMeta)
|
||||
head.getItemMeta();
|
||||
if (sm != null) {
|
||||
// 在线优先用精确档案;离线按玩家名解析真实皮肤
|
||||
Player target =
|
||||
Bukkit.getPlayerExact(name);
|
||||
if (target != null) {
|
||||
sm.setOwningPlayer(target);
|
||||
} else {
|
||||
sm.setOwner(name);
|
||||
}
|
||||
sm.setDisplayName("§e§l" + name);
|
||||
sm.setLore(Arrays.asList(
|
||||
"§7绑定时间: §e"
|
||||
+ new java.text.SimpleDateFormat(
|
||||
"yyyy-MM-dd HH:mm")
|
||||
.format(new java.util.Date(at)),
|
||||
"",
|
||||
"§7已计入你的本月邀请"));
|
||||
head.setItemMeta(sm);
|
||||
}
|
||||
g.setItem(i, head);
|
||||
}
|
||||
if (list.size() > 45) {
|
||||
g.setItem(44, mkItem(Material.PAPER,
|
||||
"§e还有 " + (list.size() - 44)
|
||||
+ " 人未显示"));
|
||||
}
|
||||
}
|
||||
g.setItem(49, mkItem(Material.ARROW,
|
||||
"§7返回", "§7返回「邀请数据」"));
|
||||
p.openInventory(g);
|
||||
}
|
||||
|
||||
|
||||
private String getInviteRate(int count) {
|
||||
if (count > 15) return "60%";
|
||||
|
||||
@@ -89,4 +89,191 @@ public class InviteManager {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ==================== 第十批(2026-10-12)存量自愈 + 派生口径 ====================
|
||||
|
||||
/** 本月 1 号 00:00(本地时区)毫秒时间戳 */
|
||||
private long monthStartTime() {
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.set(Calendar.DAY_OF_MONTH, 1);
|
||||
c.set(Calendar.HOUR_OF_DAY, 0);
|
||||
c.set(Calendar.MINUTE, 0);
|
||||
c.set(Calendar.SECOND, 0);
|
||||
c.set(Calendar.MILLISECOND, 0);
|
||||
return c.getTimeInMillis();
|
||||
}
|
||||
|
||||
private static String str(Object o) {
|
||||
return o == null ? "" : o.toString();
|
||||
}
|
||||
|
||||
private static long lng(Object o) {
|
||||
return (o instanceof Number)
|
||||
? ((Number) o).longValue() : 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 第十批(2026-10-12):存量邀请数据自愈,
|
||||
* onEnable 启动时调用一次(幂等)。
|
||||
*
|
||||
* <p>① 回填 invited_at=0 的存量绑定时间——绑定功能自项目
|
||||
* 初始化(2026-05)就存在但没记时间,用户 GUI 看到
|
||||
* 「绑定时间: 未知」。回填优先级:</p>
|
||||
* <ol>
|
||||
* <li>bond_transaction type='invitee_reward'
|
||||
* (第九批起被邀请人收款,time=绑定时刻,最精确)</li>
|
||||
* <li>bond_transaction type='invite_reward' 且
|
||||
* target_player=被邀请人(第八批起邀请人收款)</li>
|
||||
* <li>注册时间(早期绑定大多发生在注册前后,最佳代理)</li>
|
||||
* <li>最后登录时间 → 当前时间</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>② 按回填后的口径重算 monthly_invite_count / invite_month
|
||||
* ——修复存量绑定不计入导致的「已邀请 0 人」。</p>
|
||||
*/
|
||||
public void reconcileInviteData() {
|
||||
try {
|
||||
List<Map<String, Object>> users =
|
||||
plugin.getDb().getAllUsers();
|
||||
long now = System.currentTimeMillis();
|
||||
int fixedAt = 0;
|
||||
// ---- ① 回填存量绑定时间 ----
|
||||
for (Map<String, Object> u : users) {
|
||||
String name = str(u.get("player_name"));
|
||||
if (name.isEmpty()) continue;
|
||||
if (str(u.get("invited_by")).isEmpty()) continue;
|
||||
if (lng(u.get("invited_at")) > 0) continue;
|
||||
long ts = lookupBondTx(
|
||||
"player_name", name,
|
||||
"invitee_reward");
|
||||
if (ts <= 0) ts = lookupBondTx(
|
||||
"target_player", name,
|
||||
"invite_reward");
|
||||
if (ts <= 0) ts = lng(u.get("register_time"));
|
||||
if (ts <= 0) ts = lng(u.get("last_login_time"));
|
||||
if (ts <= 0) ts = now;
|
||||
plugin.getDb().setField(
|
||||
name, "invited_at", ts);
|
||||
u.put("invited_at", ts);
|
||||
fixedAt++;
|
||||
}
|
||||
// ---- ② 重算月度计数(仅维护邀请人,与 GUI 派生口径同源) ----
|
||||
long monthStart = monthStartTime();
|
||||
Map<String, Integer> counter = new HashMap<>();
|
||||
for (Map<String, Object> u : users) {
|
||||
String inviter = str(u.get("invited_by"));
|
||||
if (inviter.isEmpty()) continue;
|
||||
if (lng(u.get("invited_at")) >= monthStart) {
|
||||
counter.merge(inviter, 1, Integer::sum);
|
||||
}
|
||||
}
|
||||
String curMonth = new SimpleDateFormat("yyyy-MM")
|
||||
.format(new Date());
|
||||
int fixedCnt = 0;
|
||||
for (Map<String, Object> u : users) {
|
||||
String name = str(u.get("player_name"));
|
||||
if (name.isEmpty()) continue;
|
||||
// 只维护邀请人(有邀请码的玩家)
|
||||
if (str(u.get("invite_code")).isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
int want = counter.getOrDefault(name, 0);
|
||||
if (lng(u.get("monthly_invite_count")) != want) {
|
||||
plugin.getDb().setField(
|
||||
name, "monthly_invite_count",
|
||||
want);
|
||||
fixedCnt++;
|
||||
}
|
||||
if (!curMonth.equals(
|
||||
str(u.get("invite_month")))) {
|
||||
plugin.getDb().setField(
|
||||
name, "invite_month", curMonth);
|
||||
}
|
||||
}
|
||||
if (fixedAt > 0 || fixedCnt > 0) {
|
||||
plugin.getLogger().info(
|
||||
"[Invite] 存量自愈: 回填绑定时间 "
|
||||
+ fixedAt + " 条, 重算月度计数 "
|
||||
+ fixedCnt + " 人");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning(
|
||||
"[Invite] 存量邀请数据自愈失败: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 bond.db 的 bond_transaction 里查该玩家某类流水的最早时间
|
||||
* (跨库:必须走 BondManager.getConnection(),
|
||||
* bond_transaction 不在 login.db)
|
||||
*/
|
||||
private long lookupBondTx(String col, String player,
|
||||
String type) {
|
||||
try {
|
||||
java.sql.PreparedStatement ps = plugin.getBonds()
|
||||
.getConnection().prepareStatement(
|
||||
"SELECT MIN(time) "
|
||||
+ "FROM bond_transaction "
|
||||
+ "WHERE type=? AND "
|
||||
+ col + "=?");
|
||||
ps.setString(1, type);
|
||||
ps.setString(2, player);
|
||||
java.sql.ResultSet rs = ps.executeQuery();
|
||||
long t = rs.next() ? rs.getLong(1) : 0L;
|
||||
rs.close();
|
||||
ps.close();
|
||||
// 兼容以秒为单位的历史行
|
||||
if (t > 0 && t < 1000000000000L) t *= 1000L;
|
||||
return t;
|
||||
} catch (Exception e) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 第十批:派生口径的「本月邀请人数」——
|
||||
* 按 invited_at >= 本月 1 号实时统计(GUI 显示用,
|
||||
* 跨月自愈,不依赖增量列 monthly_invite_count)。
|
||||
*/
|
||||
public int getMonthlyInviteCount(String inviter) {
|
||||
if (inviter == null || inviter.isEmpty()) return 0;
|
||||
long monthStart = monthStartTime();
|
||||
int n = 0;
|
||||
for (Map<String, Object> u
|
||||
: plugin.getDb().getAllUsers()) {
|
||||
if (!inviter.equals(
|
||||
str(u.get("invited_by")))) continue;
|
||||
if (lng(u.get("invited_at")) >= monthStart) n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ 第十批:本月绑定该玩家邀请码的玩家列表(头颅明细 GUI)。
|
||||
*
|
||||
* @return 每项含 name(被邀请人)与 at(绑定时间戳),
|
||||
* 按绑定时间倒序
|
||||
*/
|
||||
public List<Map<String, Object>> getMonthlyInvitedPlayers(
|
||||
String inviter) {
|
||||
List<Map<String, Object>> result =
|
||||
new ArrayList<>();
|
||||
if (inviter == null || inviter.isEmpty()) return result;
|
||||
long monthStart = monthStartTime();
|
||||
for (Map<String, Object> u
|
||||
: plugin.getDb().getAllUsers()) {
|
||||
if (!inviter.equals(
|
||||
str(u.get("invited_by")))) continue;
|
||||
long ts = lng(u.get("invited_at"));
|
||||
if (ts < monthStart) continue;
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
row.put("name", str(u.get("player_name")));
|
||||
row.put("at", ts);
|
||||
result.add(row);
|
||||
}
|
||||
result.sort((a, b) -> Long.compare(
|
||||
lng(b.get("at")), lng(a.get("at"))));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,6 +623,9 @@ public class Main extends JavaPlugin
|
||||
webManager.registerTransactionListener(bondManager);
|
||||
}
|
||||
|
||||
// ★ 第十批:存量邀请数据自愈(回填 invited_at + 重算月度计数)
|
||||
invite.reconcileInviteData();
|
||||
|
||||
cdkManager = new CDKManager(this);
|
||||
userGroupManager = new UserGroupManager(this);
|
||||
cypayCommand = new CypayCommand(this);
|
||||
@@ -6604,6 +6607,8 @@ public class Main extends JavaPlugin
|
||||
&& !title.equals(GUIManager.T_MY_INFO)
|
||||
&& !title.equals(GUIManager.T_INVITE)
|
||||
&& !title.equals(
|
||||
GUIManager.T_MONTHLY_INVITE)
|
||||
&& !title.equals(
|
||||
GUIManager.T_TASK_CENTER)
|
||||
&& !title.equals(
|
||||
GUIManager.T_GIFT_STAGES)
|
||||
@@ -7255,6 +7260,9 @@ public class Main extends JavaPlugin
|
||||
invite.generateCode(p);
|
||||
p.sendMessage("§e邀请码: " + code);
|
||||
gui.openInvite(p);
|
||||
} else if (slot == 12) {
|
||||
// ★ 第十批:「本月邀请」→ 头颅明细 GUI
|
||||
gui.openMonthlyInvite(p);
|
||||
} else if (slot == 16) {
|
||||
// ★ 第九批:已绑定邀请人则拦截,不再进入输入
|
||||
Object boundInvite = db.getField(
|
||||
@@ -7275,6 +7283,13 @@ public class Main extends JavaPlugin
|
||||
return;
|
||||
}
|
||||
|
||||
// ==================== 本月邀请明细(第十批) ====================
|
||||
if (title.equals(GUIManager.T_MONTHLY_INVITE)) {
|
||||
e.setCancelled(true);
|
||||
if (slot == 49) gui.openInvite(p);
|
||||
return;
|
||||
}
|
||||
|
||||
// ==================== 用户管理 ====================
|
||||
if (title.equals(GUIManager.T_USER_MGMT)) {
|
||||
e.setCancelled(true);
|
||||
|
||||
Reference in New Issue
Block a user