Исправление багов: избегаем ограничение запросов, корректная получение вывода команды, корректные ошибки при осутствии соединения UserAPI.
Доработки: асинхронность для всех команд.
This commit is contained in:
parent
0ce332f050
commit
df0561fe51
@ -4,8 +4,11 @@ package main.VpcSpigotIntegration
|
||||
// Here is implemented capture of output command (for dynamic course)
|
||||
// ----------------------------------------------
|
||||
|
||||
import main.VpcSpigotIntegration.VpcServerIntegration.Companion.PLUGIN
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.command.CommandSender
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class CommandCapture {
|
||||
companion object {
|
||||
@ -34,7 +37,8 @@ class CommandCapture {
|
||||
}
|
||||
}
|
||||
|
||||
fun execute(command: String): List<String> {
|
||||
// Deprecated stupid version for sync version which catch output not every time
|
||||
fun executeOld(command: String): List<String> {
|
||||
val output = mutableListOf<String>()
|
||||
|
||||
val customSender = createCapturingSender { message ->
|
||||
@ -45,5 +49,61 @@ class CommandCapture {
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
const val MAX_WAIT_TIME_MS = 5000
|
||||
const val CHECK_INTERVAL_MS: Long = 300
|
||||
|
||||
fun execute(command: String): List<String> {
|
||||
val output = mutableListOf<String>()
|
||||
var isCommandFinished = false
|
||||
|
||||
val customSender = createCapturingSender { message ->
|
||||
output.add(message)
|
||||
}
|
||||
|
||||
Bukkit.getScheduler().runTask(PLUGIN) {
|
||||
Bukkit.dispatchCommand(customSender, command)
|
||||
}
|
||||
|
||||
// Wait for command completion with timeout
|
||||
val startTime = System.currentTimeMillis()
|
||||
while (!isCommandFinished && (System.currentTimeMillis() - startTime) < MAX_WAIT_TIME_MS) {
|
||||
try {
|
||||
Thread.sleep(CHECK_INTERVAL_MS)
|
||||
if (output.isNotEmpty()) {
|
||||
isCommandFinished = true
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// Untested another version
|
||||
fun executeV3(command: String): List<String> {
|
||||
val output = mutableListOf<String>()
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
val customSender = createCapturingSender { message ->
|
||||
synchronized(output) {
|
||||
output.add(message)
|
||||
}
|
||||
}
|
||||
|
||||
Bukkit.getScheduler().runTask(PLUGIN) {
|
||||
try {
|
||||
Bukkit.dispatchCommand(customSender, command)
|
||||
} finally {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for command completion with timeout
|
||||
latch.await(MAX_WAIT_TIME_MS/1000.toLong(), TimeUnit.SECONDS)
|
||||
|
||||
return output.toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11,8 +11,7 @@ class TotalBalanceModules {
|
||||
fun getEssentialsBalance(): Float {
|
||||
LOGGER.info("Calculating dynamic rate using baltop")
|
||||
// Force update balance top and delay to avoid empty string
|
||||
CommandCapture.execute("baltop force")
|
||||
val output = CommandCapture.execute("baltop")
|
||||
val output = CommandCapture.execute("baltop force")
|
||||
LOGGER.info("Balance top command output: $output")
|
||||
|
||||
var total = 0.0f
|
||||
|
||||
@ -23,10 +23,10 @@ import kotlin.math.max
|
||||
|
||||
class VpcApi {
|
||||
companion object {
|
||||
// Rate limiting: track timestamps of requests
|
||||
private val requestTimestamps = ConcurrentLinkedQueue<Long>()
|
||||
private const val MAX_REQUESTS_PER_MINUTE = 60
|
||||
private const val MINUTE_IN_MILLIS = 60 * 1000L
|
||||
// Rate limiting: lock and last execution time
|
||||
private val rateLimitLock = Object()
|
||||
private var lastExecutionTime = 0L
|
||||
private const val MIN_INTERVAL_MILLIS = 1000L // 1 second between requests
|
||||
|
||||
// Using Gson for JSON serialization - much cleaner than manual string building
|
||||
fun jsonify(args: List<String>): String {
|
||||
@ -49,37 +49,27 @@ class VpcApi {
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces rate limiting by checking if we've exceeded the request limit.
|
||||
* If so, sleeps until we're within the limit again.
|
||||
* Enforces rate limiting by ensuring requests are executed one at a time
|
||||
* with at least 1 second between them.
|
||||
*/
|
||||
private fun enforceRateLimit() {
|
||||
synchronized(rateLimitLock) {
|
||||
val now = System.currentTimeMillis()
|
||||
val timeSinceLastExecution = now - lastExecutionTime
|
||||
|
||||
// Remove timestamps older than 1 minute
|
||||
while (requestTimestamps.isNotEmpty() && now - requestTimestamps.peek() > MINUTE_IN_MILLIS) {
|
||||
requestTimestamps.poll()
|
||||
}
|
||||
|
||||
// If we've hit the limit, calculate sleep time
|
||||
if (requestTimestamps.size >= MAX_REQUESTS_PER_MINUTE) {
|
||||
val oldestRequestTime = requestTimestamps.peek()
|
||||
val sleepTime = max(0, MINUTE_IN_MILLIS - (now - oldestRequestTime)) + 100 // Add small buffer
|
||||
|
||||
// If less than 1 second has passed since last execution, wait
|
||||
if (timeSinceLastExecution < MIN_INTERVAL_MILLIS) {
|
||||
val sleepTime = MIN_INTERVAL_MILLIS - timeSinceLastExecution
|
||||
try {
|
||||
Thread.sleep(sleepTime)
|
||||
} catch (e: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
}
|
||||
|
||||
// After sleeping, clean up old timestamps again
|
||||
val afterSleep = System.currentTimeMillis()
|
||||
while (requestTimestamps.isNotEmpty() && afterSleep - requestTimestamps.peek() > MINUTE_IN_MILLIS) {
|
||||
requestTimestamps.poll()
|
||||
}
|
||||
}
|
||||
|
||||
// Add current timestamp
|
||||
requestTimestamps.offer(now)
|
||||
// Update last execution time to now
|
||||
lastExecutionTime = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
|
||||
fun sendPost(urlPoint: String, json: String, customUrl: Boolean = false, baseurl: String = ""): ApiResponse {
|
||||
|
||||
@ -21,6 +21,7 @@ import java.io.FileInputStream
|
||||
import java.util.Properties
|
||||
import kotlin.collections.mutableMapOf
|
||||
import kotlin.math.abs
|
||||
import kotlin.text.isEmpty
|
||||
|
||||
class VpcServerIntegration() : JavaPlugin(), CommandExecutor {
|
||||
companion object {
|
||||
@ -216,32 +217,31 @@ invoice_timeout_seconds=$DEFAULT_INVOICE_TIMEOUT_SECONDS
|
||||
}
|
||||
|
||||
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
|
||||
if (command.name.equals("vpi", ignoreCase = true)) {
|
||||
if (!command.name.equals("vpi", ignoreCase = true)) return false
|
||||
|
||||
if (sender !is Player) {
|
||||
sender.sendMessage("&cТолько игроки могут выполнять данные команды.")
|
||||
return true
|
||||
}
|
||||
|
||||
// Run all logic asynchronously
|
||||
Bukkit.getScheduler().runTaskAsynchronously(PLUGIN, Runnable {
|
||||
when {
|
||||
// Handle currency conversion command
|
||||
(args.size == 3 || args.size == 4) && args[0] == "convert" -> {
|
||||
// Currency conversion
|
||||
args.size in 3..4 && args[0] == "convert" -> {
|
||||
handleCurrencyConversion(sender, args)
|
||||
}
|
||||
args.isNotEmpty() && args[0] == "convert" -> {
|
||||
Utils.send(sender, "/vpi convert <vpc/lc> <сумма>")
|
||||
}
|
||||
|
||||
// Handle course command
|
||||
// Course info
|
||||
args.isNotEmpty() && args[0] == "course" -> {
|
||||
// Calculate exchange rate
|
||||
val course = calculateExchangeRate()
|
||||
LOGGER.info("Exchange rate calculated: $course")
|
||||
if (course.isInfinite()) {
|
||||
LOGGER.error("Zero global balance?")
|
||||
Utils.send(sender, "&cПроизошла ошибка при расчёте курса. Обратитесь к администратору или повторите позже.")
|
||||
|
||||
} else if (course == 0.0) {
|
||||
LOGGER.error("Infinite global balance?")
|
||||
if (course.isInfinite() || course == 0.0) {
|
||||
LOGGER.error(if (course.isInfinite()) "Zero global balance?" else "Infinite global balance?")
|
||||
Utils.send(sender, "&cПроизошла ошибка при расчёте курса. Обратитесь к администратору или повторите позже.")
|
||||
} else {
|
||||
val course2VPC = Utils.round(course * (1 - COURSE_COMMISSION / 100), NUM_AFTER_DOT)
|
||||
@ -250,7 +250,7 @@ invoice_timeout_seconds=$DEFAULT_INVOICE_TIMEOUT_SECONDS
|
||||
}
|
||||
}
|
||||
|
||||
// Handle authentication command
|
||||
// Authentication
|
||||
args.size == 2 && args[0] == "auth" -> {
|
||||
handleAuthentication(sender, args)
|
||||
}
|
||||
@ -258,15 +258,15 @@ invoice_timeout_seconds=$DEFAULT_INVOICE_TIMEOUT_SECONDS
|
||||
Utils.send(sender, "/vpi auth <ник>")
|
||||
}
|
||||
|
||||
// Show general help
|
||||
// Default help menu
|
||||
else -> {
|
||||
showHelpMenu(sender)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle currency conversion logic
|
||||
@ -320,7 +320,9 @@ invoice_timeout_seconds=$DEFAULT_INVOICE_TIMEOUT_SECONDS
|
||||
return if (COURSE_MODE == "static") {
|
||||
COURSE_STATIC_VALUE
|
||||
} else {
|
||||
Utils.round(calculateDynamicRate(), NUM_AFTER_DOT)
|
||||
val dynamicRate: Any = calculateDynamicRate()
|
||||
if (dynamicRate != 0.0) Utils.round(dynamicRate, NUM_AFTER_DOT)
|
||||
else return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
@ -334,7 +336,8 @@ invoice_timeout_seconds=$DEFAULT_INVOICE_TIMEOUT_SECONDS
|
||||
|
||||
val vpcUser = VpcApi.user_in_db(username = USERNAME)
|
||||
if (vpcUser == null) {
|
||||
throw Exception("VPC user not found")
|
||||
return 0.0
|
||||
// throw Exception("VPC user not found")
|
||||
}
|
||||
|
||||
val vpcBalance = vpcUser["balance"].toString().toDouble()
|
||||
@ -349,7 +352,8 @@ invoice_timeout_seconds=$DEFAULT_INVOICE_TIMEOUT_SECONDS
|
||||
val vpcUser = VpcApi.user_in_db(username = USERNAME)
|
||||
|
||||
if (vpcUser == null) {
|
||||
throw Exception("VPC user not found")
|
||||
return 0.0
|
||||
// throw Exception("VPC user not found")
|
||||
}
|
||||
|
||||
val vpcBalance = vpcUser["balance"].toString().toDouble()
|
||||
@ -423,6 +427,11 @@ invoice_timeout_seconds=$DEFAULT_INVOICE_TIMEOUT_SECONDS
|
||||
|
||||
// Create new invoice
|
||||
val invoiceId = VpcApi.create_invoice(amount).toString()
|
||||
LOGGER.info("Invoice id: $invoiceId")
|
||||
if (invoiceId == "null") {
|
||||
Utils.send(sender,"&cПроизошла ошибка при генерации счёта. Обратитесь к администратору или повторите позже.")
|
||||
return
|
||||
}
|
||||
TO_PAY_INVOICES[sender.name] = invoiceId
|
||||
INVOICES_AMOUNT[invoiceId] = amountLC
|
||||
INVOICE_CREATION_TIMES[invoiceId] = System.currentTimeMillis()
|
||||
@ -475,18 +484,23 @@ invoice_timeout_seconds=$DEFAULT_INVOICE_TIMEOUT_SECONDS
|
||||
}
|
||||
|
||||
// Start new authentication process
|
||||
val invoiceId = VpcApi.create_invoice(0.001)
|
||||
val invoiceId = VpcApi.create_invoice(0.001).toString()
|
||||
val vpcUsernameInput = args[1]
|
||||
LOGGER.info("Invoice id: $invoiceId")
|
||||
if (invoiceId == "null") {
|
||||
Utils.send(sender,"&cПроизошла ошибка при генерации счёта. Обратитесь к администратору или повторите позже.")
|
||||
return
|
||||
}
|
||||
|
||||
LOGGER.info("Adding player to authentication lists")
|
||||
synchronized(TO_AUTH_PLAYERS) {
|
||||
TO_AUTH_PLAYERS[sender.name] = vpcUsernameInput
|
||||
TO_AUTH_PLAYERS_INVOICES[sender.name] = invoiceId.toString()
|
||||
INVOICE_CREATION_TIMES[invoiceId.toString()] = System.currentTimeMillis()
|
||||
TO_AUTH_PLAYERS_INVOICES[sender.name] = invoiceId
|
||||
INVOICE_CREATION_TIMES[invoiceId] = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
LOGGER.info("Authentication lists updated")
|
||||
sendAuthPrompt(sender, invoiceId.toString())
|
||||
sendAuthPrompt(sender, invoiceId)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user