first blood
This commit is contained in:
355
cmd/server/main.go
Normal file
355
cmd/server/main.go
Normal file
@@ -0,0 +1,355 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"usdtman"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var (
|
||||
tronMonitor *usdtman.TronUSDTMonitor
|
||||
paymentEvents []usdtman.USDTPayment
|
||||
paymentLock sync.RWMutex
|
||||
upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
}
|
||||
clients = make(map[*websocket.Conn]bool)
|
||||
clientsLock sync.RWMutex
|
||||
)
|
||||
|
||||
func main() {
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "8084"
|
||||
}
|
||||
|
||||
apiKey := os.Getenv("TRON_API_KEY")
|
||||
if apiKey == "" {
|
||||
apiKey = "da1e77dc-b35b-4458-846a-5a551b9df4b2"
|
||||
}
|
||||
|
||||
tronMonitor = usdtman.NewTronUSDTMonitor(apiKey)
|
||||
tronMonitor.SetPaymentCallback(func(payment *usdtman.USDTPayment) {
|
||||
log.Printf("💰 收到 USDT: %s -> %.6f USDT (TxID: %s)",
|
||||
payment.From, payment.Amount, payment.TxID)
|
||||
|
||||
paymentLock.Lock()
|
||||
paymentEvents = append(paymentEvents, *payment)
|
||||
if len(paymentEvents) > 100 {
|
||||
paymentEvents = paymentEvents[len(paymentEvents)-100:]
|
||||
}
|
||||
paymentLock.Unlock()
|
||||
|
||||
broadcastPayment(payment)
|
||||
})
|
||||
|
||||
http.HandleFunc("/start", startMonitor)
|
||||
http.HandleFunc("/stop", stopMonitor)
|
||||
http.HandleFunc("/add-address", addAddress)
|
||||
http.HandleFunc("/remove-address", removeAddress)
|
||||
http.HandleFunc("/list-addresses", listAddresses)
|
||||
http.HandleFunc("/payments", getPayments)
|
||||
http.HandleFunc("/ws", handleWebSocket)
|
||||
http.HandleFunc("/", serveIndex)
|
||||
|
||||
log.Printf("🚀 USDTMan Server running on :%s", port)
|
||||
log.Fatal(http.ListenAndServe(":"+port, nil))
|
||||
}
|
||||
|
||||
func jsonResponse(w http.ResponseWriter, success bool, message string, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": success,
|
||||
"message": message,
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
|
||||
func startMonitor(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
jsonResponse(w, false, "Method not allowed", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := tronMonitor.Start(); err != nil {
|
||||
jsonResponse(w, false, fmt.Sprintf("启动失败: %v", err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
jsonResponse(w, true, "监听已启动", nil)
|
||||
}
|
||||
|
||||
func stopMonitor(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
jsonResponse(w, false, "Method not allowed", nil)
|
||||
return
|
||||
}
|
||||
|
||||
tronMonitor.Stop()
|
||||
jsonResponse(w, true, "监听已停止", nil)
|
||||
}
|
||||
|
||||
func addAddress(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
jsonResponse(w, false, "Method not allowed", nil)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
jsonResponse(w, false, "invalid request", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Address == "" {
|
||||
jsonResponse(w, false, "address is required", nil)
|
||||
return
|
||||
}
|
||||
|
||||
tronMonitor.AddAddress(req.Address)
|
||||
jsonResponse(w, true, "地址已添加", map[string]interface{}{
|
||||
"address": req.Address,
|
||||
})
|
||||
}
|
||||
|
||||
func removeAddress(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
jsonResponse(w, false, "Method not allowed", nil)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
jsonResponse(w, false, "invalid request", nil)
|
||||
return
|
||||
}
|
||||
|
||||
tronMonitor.RemoveAddress(req.Address)
|
||||
jsonResponse(w, true, "地址已移除", map[string]interface{}{
|
||||
"address": req.Address,
|
||||
})
|
||||
}
|
||||
|
||||
func listAddresses(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
jsonResponse(w, false, "Method not allowed", nil)
|
||||
return
|
||||
}
|
||||
|
||||
addresses := tronMonitor.GetAllAddresses()
|
||||
jsonResponse(w, true, "success", map[string]interface{}{
|
||||
"addresses": addresses,
|
||||
"count": len(addresses),
|
||||
})
|
||||
}
|
||||
|
||||
func getPayments(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
jsonResponse(w, false, "Method not allowed", nil)
|
||||
return
|
||||
}
|
||||
|
||||
paymentLock.RLock()
|
||||
events := make([]usdtman.USDTPayment, len(paymentEvents))
|
||||
copy(events, paymentEvents)
|
||||
paymentLock.RUnlock()
|
||||
|
||||
jsonResponse(w, true, "success", map[string]interface{}{
|
||||
"payments": events,
|
||||
"count": len(events),
|
||||
})
|
||||
}
|
||||
|
||||
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println("WebSocket upgrade error:", err)
|
||||
return
|
||||
}
|
||||
|
||||
clientsLock.Lock()
|
||||
clients[conn] = true
|
||||
clientsLock.Unlock()
|
||||
|
||||
defer func() {
|
||||
clientsLock.Lock()
|
||||
delete(clients, conn)
|
||||
clientsLock.Unlock()
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
_, _, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func broadcastPayment(payment *usdtman.USDTPayment) {
|
||||
message := map[string]interface{}{
|
||||
"type": "usdt_payment",
|
||||
"address": payment.Address,
|
||||
"amount": payment.Amount,
|
||||
"from": payment.From,
|
||||
"txId": payment.TxID,
|
||||
"block": payment.BlockNumber,
|
||||
"time": payment.Timestamp,
|
||||
}
|
||||
|
||||
clientsLock.RLock()
|
||||
defer clientsLock.RUnlock()
|
||||
|
||||
for conn := range clients {
|
||||
conn.WriteJSON(message)
|
||||
}
|
||||
}
|
||||
|
||||
func serveIndex(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Write([]byte(indexHTML))
|
||||
}
|
||||
|
||||
const indexHTML = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>USDTMan - USDT Monitor</title>
|
||||
<style>
|
||||
body { font-family: Arial; max-width: 1000px; margin: 20px auto; padding: 20px; }
|
||||
h1 { color: #333; }
|
||||
.section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
|
||||
button { padding: 8px 15px; margin: 5px; cursor: pointer; }
|
||||
input { padding: 8px; margin: 5px; width: 400px; }
|
||||
.success { color: green; }
|
||||
.error { color: red; }
|
||||
#log { max-height: 300px; overflow-y: auto; background: #f5f5f5; padding: 10px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🚀 USDTMan - USDT Monitor</h1>
|
||||
|
||||
<div class="section">
|
||||
<h3>监听控制</h3>
|
||||
<button onclick="startMonitor()">启动监听</button>
|
||||
<button onclick="stopMonitor()">停止监听</button>
|
||||
<div id="status"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>地址管理</h3>
|
||||
<input type="text" id="address" placeholder="输入 TRON 地址">
|
||||
<button onclick="addAddress()">添加地址</button>
|
||||
<button onclick="listAddresses()">刷新列表</button>
|
||||
<div id="addresses"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>收款记录</h3>
|
||||
<button onclick="getPayments()">刷新</button>
|
||||
<div id="payments"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>实时日志 (WebSocket)</h3>
|
||||
<div id="log"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const ws = new WebSocket('ws://' + location.host + '/ws');
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === 'usdt_payment') {
|
||||
addLog('💰 收到 USDT: ' + data.from + ' -> ' + data.amount.toFixed(6) + ' USDT (TxID: ' + data.txId + ')');
|
||||
getPayments();
|
||||
}
|
||||
};
|
||||
|
||||
function addLog(msg) {
|
||||
const log = document.getElementById('log');
|
||||
const time = new Date().toLocaleTimeString();
|
||||
log.innerHTML = '[' + time + '] ' + msg + '<br>' + log.innerHTML;
|
||||
}
|
||||
|
||||
async function startMonitor() {
|
||||
const res = await fetch('/start', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
document.getElementById('status').innerHTML = '<span class="success">' + data.message + '</span>';
|
||||
}
|
||||
|
||||
async function stopMonitor() {
|
||||
const res = await fetch('/stop', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
document.getElementById('status').innerHTML = '<span class="error">' + data.message + '</span>';
|
||||
}
|
||||
|
||||
async function addAddress() {
|
||||
const address = document.getElementById('address').value;
|
||||
if (!address) return alert('请输入地址');
|
||||
|
||||
const res = await fetch('/add-address', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ address })
|
||||
});
|
||||
const data = await res.json();
|
||||
alert(data.message);
|
||||
listAddresses();
|
||||
}
|
||||
|
||||
async function listAddresses() {
|
||||
const res = await fetch('/list-addresses');
|
||||
const data = await res.json();
|
||||
const html = data.data.addresses.map(addr =>
|
||||
'<div>' + addr + ' <button onclick="removeAddress(\'' + addr + '\')">移除</button></div>'
|
||||
).join('');
|
||||
document.getElementById('addresses').innerHTML = html || '暂无地址';
|
||||
}
|
||||
|
||||
async function removeAddress(addr) {
|
||||
const res = await fetch('/remove-address', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ address: addr })
|
||||
});
|
||||
listAddresses();
|
||||
}
|
||||
|
||||
async function getPayments() {
|
||||
const res = await fetch('/payments');
|
||||
const data = await res.json();
|
||||
if (data.data.payments && data.data.payments.length > 0) {
|
||||
const html = '<table><tr><th>时间</th><th>地址</th><th>金额</th><th>来源</th><th>TxID</th></tr>' +
|
||||
data.data.payments.map(p =>
|
||||
'<tr><td>' + new Date(p.Timestamp).toLocaleString() + '</td>' +
|
||||
'<td>' + p.Address.substring(0, 10) + '...</td>' +
|
||||
'<td>' + p.Amount.toFixed(6) + ' USDT</td>' +
|
||||
'<td>' + p.From.substring(0, 10) + '...</td>' +
|
||||
'<td><a href="https://tronscan.org/#/transaction/' + p.TxID + '" target="_blank">' +
|
||||
p.TxID.substring(0, 10) + '...</a></td></tr>'
|
||||
).join('') + '</table>';
|
||||
document.getElementById('payments').innerHTML = html;
|
||||
} else {
|
||||
document.getElementById('payments').innerHTML = '暂无记录';
|
||||
}
|
||||
}
|
||||
|
||||
listAddresses();
|
||||
getPayments();
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
Reference in New Issue
Block a user