init
commit
ce0b3c0c53
|
@ -0,0 +1,8 @@
|
|||
.idea
|
||||
tmp
|
||||
.linux
|
||||
.exe
|
||||
.mac
|
||||
.log
|
||||
.vscode
|
||||
*.db
|
|
@ -0,0 +1,12 @@
|
|||
package conf
|
||||
|
||||
var App = &Config{}
|
||||
|
||||
type Config struct {
|
||||
ServerVersion string
|
||||
RemoteDb string
|
||||
ServerAddr string
|
||||
WebAddr string
|
||||
SaveLog bool
|
||||
LogLevel string
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
db *gorm.DB
|
||||
)
|
||||
|
||||
func InitAdminDb() {
|
||||
dsn := `` // 保存sql记录的数据库连接
|
||||
|
||||
var err error
|
||||
db, err = gorm.Open(mysql.Open(dsn))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sqlDb, _ := db.DB()
|
||||
sqlDb.SetConnMaxIdleTime(time.Hour)
|
||||
sqlDb.SetMaxIdleConns(4)
|
||||
|
||||
err = sqlDb.Ping()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//initTable()
|
||||
|
||||
//db = db.Debug()
|
||||
}
|
||||
|
||||
func GetDB() *gorm.DB {
|
||||
return db
|
||||
}
|
||||
|
||||
var (
|
||||
logTable = `CREATE TABLE if not exists sql_query_log (
|
||||
id integer PRIMARY KEY AUTOINCREMENT,
|
||||
admin_id integer not null,
|
||||
admin_name text not null,
|
||||
admin_real_name text not null,
|
||||
query_game_id integer,
|
||||
header_game_id integer,
|
||||
ip text,
|
||||
request_path text,
|
||||
request_info text,
|
||||
unix_milli integer,
|
||||
query text,
|
||||
create_time datetime default current_timestamp not null
|
||||
)
|
||||
`
|
||||
)
|
||||
|
||||
func initTable() {
|
||||
if err := db.Exec(logTable).Error; err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
package mysqlserver
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"proxymysql/app/conf"
|
||||
"proxymysql/app/zlog"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var (
|
||||
connectionId uint32
|
||||
//serverVersion = "8.0.30-tz-mysql-proxy"
|
||||
)
|
||||
|
||||
type MysqlPacketHeader struct {
|
||||
Length uint32
|
||||
SequenceId uint8
|
||||
HeaderByte []byte
|
||||
}
|
||||
|
||||
type MysqlPacket struct {
|
||||
MysqlPacketHeader
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
func (pd *MysqlPacket) ToByte() []byte {
|
||||
res := make([]byte, len(pd.Payload)+4)
|
||||
|
||||
copy(res[:3], WriteUint24(pd.Length))
|
||||
res[3] = pd.SequenceId
|
||||
|
||||
copy(res[4:], pd.Payload)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
type ProxyConn struct {
|
||||
clientConn net.Conn
|
||||
serverConn net.Conn
|
||||
}
|
||||
|
||||
func NewProxyConn(clientConn net.Conn) *ProxyConn {
|
||||
return &ProxyConn{clientConn: clientConn}
|
||||
}
|
||||
|
||||
func (p *ProxyConn) getConnectionId() uint32 {
|
||||
num := atomic.AddUint32(&connectionId, 1)
|
||||
if num == 0 {
|
||||
atomic.StoreUint32(&connectionId, 1)
|
||||
return 1
|
||||
}
|
||||
|
||||
return num
|
||||
}
|
||||
|
||||
func (p *ProxyConn) Handle() error {
|
||||
serverConn, err := p.getServerConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.serverConn = serverConn
|
||||
|
||||
// 先等待服务端返回handshake 在进行下一步操作
|
||||
hk, err := ReadHandshakeV10(p.serverConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hk.ConnectionId = p.getConnectionId()
|
||||
hk.ServerVersion = conf.App.ServerVersion
|
||||
// 暂时去掉ssl
|
||||
hk.CapabilityFlag &^= uint32(CapabilityClientSSL)
|
||||
|
||||
// 去掉压缩
|
||||
hk.CapabilityFlag &^= uint32(CapabilityClientCanUseCompress)
|
||||
|
||||
_, err = p.clientConn.Write(hk.ToByte())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := ReadHandshakeResponse(p.clientConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
respByte := resp.ToByte()
|
||||
|
||||
_, err = serverConn.Write(respByte)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.authSwitch(p.serverConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.copyStream()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ProxyConn) copyStream() {
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
p.clientConn.Close()
|
||||
p.serverConn.Close()
|
||||
wg.Done()
|
||||
}()
|
||||
_, err := io.Copy(p.clientConn, p.serverConn)
|
||||
if err != nil {
|
||||
zlog.Errorf("serverConn -> clientConn err: %s", err)
|
||||
//errMsg := err.Error()
|
||||
//if !strings.Contains(errMsg, "use of closed network connection") {
|
||||
// fmt.Printf("serverConn -> clientConn err: %s\n", err)
|
||||
//}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
rq := NewRecordQuery()
|
||||
|
||||
defer func() {
|
||||
p.clientConn.Close()
|
||||
p.serverConn.Close()
|
||||
rq.Close()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
_, err := io.Copy(p.serverConn, io.TeeReader(p.clientConn, rq))
|
||||
|
||||
if err != nil {
|
||||
zlog.Errorf("clientConn -> serverConn err: %s", err)
|
||||
//errMsg := err.Error()
|
||||
//if !strings.Contains(errMsg, "use of closed network connection") {
|
||||
// fmt.Printf("clientConn -> serverConn err: %s\n", errMsg)
|
||||
//}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
zlog.Debug("copy stream stop")
|
||||
|
||||
}
|
||||
|
||||
func (p *ProxyConn) authSwitch(serverConn net.Conn) error {
|
||||
var isFinish bool
|
||||
|
||||
for {
|
||||
serverResult, err := ReadMysqlPacket(serverConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//fmt.Printf("serverResult: %+v\n", serverResult)
|
||||
|
||||
if len(serverResult.Payload) > 0 && (serverResult.Payload[0] == OKPacket || serverResult.Payload[0] == ErrPacket) {
|
||||
//fmt.Println("ok ----")
|
||||
isFinish = true
|
||||
//return nil
|
||||
}
|
||||
|
||||
_, err = p.clientConn.Write(serverResult.ToByte())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isFinish {
|
||||
return nil
|
||||
}
|
||||
|
||||
clientResult, err := ReadMysqlPacket(p.clientConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = serverConn.Write(clientResult.ToByte())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (p *ProxyConn) getServerConn() (net.Conn, error) {
|
||||
return net.Dial("tcp", conf.App.RemoteDb)
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
Copyright 2019 The Vitess Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package mysqlserver
|
||||
|
||||
const (
|
||||
CharsetUtf8mb4GeneralCiId uint8 = 45
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxPacketSize is the maximum payload length of a packet
|
||||
// the server supports.
|
||||
MaxPacketSize = (1 << 24) - 1
|
||||
|
||||
// protocolVersion is the current version of the protocol.
|
||||
// Always 10.
|
||||
protocolVersion = 10
|
||||
|
||||
// https://dev.mysql.com/doc/refman/en/identifier-length.html
|
||||
MaxIdentifierLength = 64
|
||||
)
|
||||
|
||||
// AuthMethodDescription is the type for different supported and
|
||||
// implemented authentication methods.
|
||||
type AuthMethodDescription string
|
||||
|
||||
// Supported auth forms.
|
||||
const (
|
||||
// MysqlNativePassword uses a salt and transmits a hash on the wire.
|
||||
MysqlNativePassword = AuthMethodDescription("mysql_native_password")
|
||||
|
||||
// MysqlClearPassword transmits the password in the clear.
|
||||
MysqlClearPassword = AuthMethodDescription("mysql_clear_password")
|
||||
|
||||
// CachingSha2Password uses a salt and transmits a SHA256 hash on the wire.
|
||||
CachingSha2Password = AuthMethodDescription("caching_sha2_password")
|
||||
|
||||
// MysqlDialog uses the dialog plugin on the client side.
|
||||
// It transmits data in the clear.
|
||||
MysqlDialog = AuthMethodDescription("dialog")
|
||||
)
|
||||
|
||||
// Capability flags.
|
||||
// Originally found in include/mysql/mysql_com.h
|
||||
const (
|
||||
// CapabilityClientLongPassword is CLIENT_LONG_PASSWORD.
|
||||
// New more secure passwords. Assumed to be set since 4.1.1.
|
||||
// We do not check this anywhere.
|
||||
CapabilityClientLongPassword = 1
|
||||
|
||||
// CapabilityClientFoundRows is CLIENT_FOUND_ROWS.
|
||||
CapabilityClientFoundRows = 1 << 1
|
||||
|
||||
// CapabilityClientLongFlag is CLIENT_LONG_FLAG.
|
||||
// Longer flags in Protocol::ColumnDefinition320.
|
||||
// Set it everywhere, not used, as we use Protocol::ColumnDefinition41.
|
||||
CapabilityClientLongFlag = 1 << 2
|
||||
|
||||
// CapabilityClientConnectWithDB is CLIENT_CONNECT_WITH_DB.
|
||||
// One can specify db on connect.
|
||||
CapabilityClientConnectWithDB = 1 << 3
|
||||
|
||||
// CLIENT_NO_SCHEMA 1 << 4
|
||||
// Do not permit database.table.column. We do permit it.
|
||||
|
||||
CapabilityClientCanUseCompress = 1 << 5
|
||||
// We do not support compression. CPU is usually our bottleneck.
|
||||
|
||||
// CLIENT_ODBC 1 << 6
|
||||
// No special behavior since 3.22.
|
||||
|
||||
// CLIENT_LOCAL_FILES 1 << 7
|
||||
// Client can use LOCAL INFILE request of LOAD DATA|XML.
|
||||
// We do not set it.
|
||||
|
||||
// CLIENT_IGNORE_SPACE 1 << 8
|
||||
// Parser can ignore spaces before '('.
|
||||
// We ignore this.
|
||||
|
||||
// CapabilityClientProtocol41 is CLIENT_PROTOCOL_41.
|
||||
// New 4.1 protocol. Enforced everywhere.
|
||||
CapabilityClientProtocol41 = 1 << 9
|
||||
|
||||
// CLIENT_INTERACTIVE 1 << 10
|
||||
// Not specified, ignored.
|
||||
|
||||
// CapabilityClientSSL is CLIENT_SSL.
|
||||
// Switch to SSL after handshake.
|
||||
CapabilityClientSSL = 1 << 11
|
||||
|
||||
// CLIENT_IGNORE_SIGPIPE 1 << 12
|
||||
// Do not issue SIGPIPE if network failures occur (libmysqlclient only).
|
||||
|
||||
// CapabilityClientTransactions is CLIENT_TRANSACTIONS.
|
||||
// Can send status flags in EOF_Packet.
|
||||
// This flag is optional in 3.23, but always set by the server since 4.0.
|
||||
// We just do it all the time.
|
||||
CapabilityClientTransactions = 1 << 13
|
||||
|
||||
// CLIENT_RESERVED 1 << 14
|
||||
|
||||
// CapabilityClientSecureConnection is CLIENT_SECURE_CONNECTION.
|
||||
// New 4.1 authentication. Always set, expected, never checked.
|
||||
CapabilityClientSecureConnection = 1 << 15
|
||||
|
||||
// CapabilityClientMultiStatements is CLIENT_MULTI_STATEMENTS
|
||||
// Can handle multiple statements per COM_QUERY and COM_STMT_PREPARE.
|
||||
CapabilityClientMultiStatements = 1 << 16
|
||||
|
||||
// CapabilityClientMultiResults is CLIENT_MULTI_RESULTS
|
||||
// Can send multiple resultsets for COM_QUERY.
|
||||
CapabilityClientMultiResults = 1 << 17
|
||||
|
||||
// CapabilityClientPluginAuth is CLIENT_PLUGIN_AUTH.
|
||||
// Client supports plugin authentication.
|
||||
CapabilityClientPluginAuth = 1 << 19
|
||||
|
||||
// CapabilityClientConnAttr is CLIENT_CONNECT_ATTRS
|
||||
// Permits connection attributes in Protocol::HandshakeResponse41.
|
||||
CapabilityClientConnAttr = 1 << 20
|
||||
|
||||
// CapabilityClientPluginAuthLenencClientData is CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA
|
||||
CapabilityClientPluginAuthLenencClientData = 1 << 21
|
||||
|
||||
// CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS 1 << 22
|
||||
// Announces support for expired password extension.
|
||||
// Not yet supported.
|
||||
|
||||
// CLIENT_SESSION_TRACK 1 << 23
|
||||
// Can set ServerSessionStateChanged in the Status Flags
|
||||
// and send session-state change data after a OK packet.
|
||||
// Not yet supported.
|
||||
CapabilityClientSessionTrack = 1 << 23
|
||||
|
||||
// CapabilityClientDeprecateEOF is CLIENT_DEPRECATE_EOF
|
||||
// Expects an OK (instead of EOF) after the resultset rows of a Text Resultset.
|
||||
CapabilityClientDeprecateEOF = 1 << 24
|
||||
|
||||
CapabilityClientQueryAttributes = 1 << 27
|
||||
)
|
||||
|
||||
// Status flags. They are returned by the server in a few cases.
|
||||
// Originally found in include/mysql/mysql_com.h
|
||||
// See http://dev.mysql.com/doc/internals/en/status-flags.html
|
||||
const (
|
||||
// a transaction is active
|
||||
ServerStatusInTrans uint16 = 0x0001
|
||||
NoServerStatusInTrans uint16 = 0xFFFE
|
||||
|
||||
// auto-commit is enabled
|
||||
ServerStatusAutocommit uint16 = 0x0002
|
||||
NoServerStatusAutocommit uint16 = 0xFFFD
|
||||
|
||||
ServerMoreResultsExists uint16 = 0x0008
|
||||
ServerStatusNoGoodIndexUsed uint16 = 0x0010
|
||||
ServerStatusNoIndexUsed uint16 = 0x0020
|
||||
// Used by Binary Protocol Resultset to signal that COM_STMT_FETCH must be used to fetch the row-data.
|
||||
ServerStatusCursorExists uint16 = 0x0040
|
||||
ServerStatusLastRowSent uint16 = 0x0080
|
||||
ServerStatusDbDropped uint16 = 0x0100
|
||||
ServerStatusNoBackslashEscapes uint16 = 0x0200
|
||||
ServerStatusMetadataChanged uint16 = 0x0400
|
||||
ServerQueryWasSlow uint16 = 0x0800
|
||||
ServerPsOutParams uint16 = 0x1000
|
||||
// in a read-only transaction
|
||||
ServerStatusInTransReadonly uint16 = 0x2000
|
||||
// connection state information has changed
|
||||
ServerSessionStateChanged uint16 = 0x4000
|
||||
)
|
||||
|
||||
// State Change Information
|
||||
const (
|
||||
// one or more system variables changed.
|
||||
SessionTrackSystemVariables uint8 = 0x00
|
||||
// schema changed.
|
||||
SessionTrackSchema uint8 = 0x01
|
||||
// "track state change" changed.
|
||||
SessionTrackStateChange uint8 = 0x02
|
||||
// "track GTIDs" changed.
|
||||
SessionTrackGtids uint8 = 0x03
|
||||
)
|
||||
|
||||
// Packet types.
|
||||
// Originally found in include/mysql/mysql_com.h
|
||||
const (
|
||||
// ComQuit is COM_QUIT.
|
||||
ComQuit = 0x01
|
||||
|
||||
// ComInitDB is COM_INIT_DB.
|
||||
ComInitDB = 0x02
|
||||
|
||||
// ComQuery is COM_QUERY.
|
||||
ComQuery = 0x03
|
||||
|
||||
// ComFieldList is COM_Field_List.
|
||||
ComFieldList = 0x04
|
||||
|
||||
// ComPing is COM_PING.
|
||||
ComPing = 0x0e
|
||||
|
||||
// ComBinlogDump is COM_BINLOG_DUMP.
|
||||
ComBinlogDump = 0x12
|
||||
|
||||
// ComSemiSyncAck is SEMI_SYNC_ACK.
|
||||
ComSemiSyncAck = 0xef
|
||||
|
||||
// ComPrepare is COM_PREPARE.
|
||||
ComPrepare = 0x16
|
||||
|
||||
// ComStmtExecute is COM_STMT_EXECUTE.
|
||||
ComStmtExecute = 0x17
|
||||
|
||||
// ComStmtSendLongData is COM_STMT_SEND_LONG_DATA
|
||||
ComStmtSendLongData = 0x18
|
||||
|
||||
// ComStmtClose is COM_STMT_CLOSE.
|
||||
ComStmtClose = 0x19
|
||||
|
||||
// ComStmtReset is COM_STMT_RESET
|
||||
ComStmtReset = 0x1a
|
||||
|
||||
//ComStmtFetch is COM_STMT_FETCH
|
||||
ComStmtFetch = 0x1c
|
||||
|
||||
// ComSetOption is COM_SET_OPTION
|
||||
ComSetOption = 0x1b
|
||||
|
||||
// ComResetConnection is COM_RESET_CONNECTION
|
||||
ComResetConnection = 0x1f
|
||||
|
||||
// ComBinlogDumpGTID is COM_BINLOG_DUMP_GTID.
|
||||
ComBinlogDumpGTID = 0x1e
|
||||
|
||||
// ComRegisterReplica is COM_REGISTER_SLAVE
|
||||
// https://dev.mysql.com/doc/internals/en/com-register-slave.html
|
||||
ComRegisterReplica = 0x15
|
||||
|
||||
// OKPacket is the header of the OK packet.
|
||||
OKPacket = 0x00
|
||||
|
||||
// EOFPacket is the header of the EOF packet.
|
||||
EOFPacket = 0xfe
|
||||
|
||||
// ErrPacket is the header of the error packet.
|
||||
ErrPacket = 0xff
|
||||
|
||||
// NullValue is the encoded value of NULL.
|
||||
NullValue = 0xfb
|
||||
)
|
||||
|
||||
// Auth packet types
|
||||
const (
|
||||
// AuthMoreDataPacket is sent when server requires more data to authenticate
|
||||
AuthMoreDataPacket = 0x01
|
||||
|
||||
// CachingSha2FastAuth is sent before OKPacket when server authenticates using cache
|
||||
CachingSha2FastAuth = 0x03
|
||||
|
||||
// CachingSha2FullAuth is sent when server requests un-scrambled password to authenticate
|
||||
CachingSha2FullAuth = 0x04
|
||||
|
||||
// AuthSwitchRequestPacket is used to switch auth method.
|
||||
AuthSwitchRequestPacket = 0xfe
|
||||
)
|
||||
|
||||
// IsNum returns true if a MySQL type is a numeric value.
|
||||
// It is the same as IS_NUM defined in mysql.h.
|
||||
func IsNum(typ uint8) bool {
|
||||
return (typ <= FieldTypeInt24 && typ != FieldTypeTimestamp) ||
|
||||
typ == FieldTypeYear ||
|
||||
typ == FieldTypeNewDecimal
|
||||
}
|
||||
|
||||
// This is the data type for a field.
|
||||
// Values taken from include/mysql/mysql_com.h
|
||||
const (
|
||||
FieldTypeDecimal uint8 = iota
|
||||
FieldTypeTiny
|
||||
FieldTypeShort
|
||||
FieldTypeLong
|
||||
FieldTypeFloat
|
||||
FieldTypeDouble
|
||||
FieldTypeNULL
|
||||
FieldTypeTimestamp
|
||||
FieldTypeLongLong
|
||||
FieldTypeInt24
|
||||
FieldTypeDate
|
||||
FieldTypeTime
|
||||
FieldTypeDateTime
|
||||
FieldTypeYear
|
||||
FieldTypeNewDate
|
||||
FieldTypeVarChar
|
||||
FieldTypeBit
|
||||
)
|
||||
const (
|
||||
FieldTypeJSON uint8 = iota + 0xf5
|
||||
FieldTypeNewDecimal
|
||||
FieldTypeEnum
|
||||
FieldTypeSet
|
||||
FieldTypeTinyBLOB
|
||||
FieldTypeMediumBLOB
|
||||
FieldTypeLongBLOB
|
||||
FieldTypeBLOB
|
||||
FieldTypeVarString
|
||||
FieldTypeString
|
||||
FieldTypeGeometry
|
||||
)
|
|
@ -0,0 +1,392 @@
|
|||
package mysqlserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"proxymysql/app/zlog"
|
||||
)
|
||||
|
||||
const (
|
||||
errAuthPluginMethod = "err_auth_plugin_method"
|
||||
nativePasswordAuthPluginMethod = "mysql_native_password"
|
||||
)
|
||||
|
||||
var DefaultHandshakeCapability uint32 = CapabilityClientLongPassword |
|
||||
CapabilityClientFoundRows |
|
||||
CapabilityClientLongFlag |
|
||||
CapabilityClientConnectWithDB |
|
||||
CapabilityClientProtocol41 |
|
||||
CapabilityClientTransactions |
|
||||
CapabilityClientSecureConnection |
|
||||
CapabilityClientMultiStatements |
|
||||
CapabilityClientMultiResults |
|
||||
CapabilityClientPluginAuth |
|
||||
CapabilityClientPluginAuthLenencClientData |
|
||||
CapabilityClientDeprecateEOF |
|
||||
CapabilityClientConnAttr |
|
||||
CapabilityClientQueryAttributes
|
||||
|
||||
type HandshakeV10 struct {
|
||||
ProtocolVersion uint8
|
||||
ServerVersion string
|
||||
AuthPluginMethod string
|
||||
ConnectionId uint32
|
||||
CharsetCollation uint8
|
||||
|
||||
ServerStatus uint16
|
||||
|
||||
CapabilityFlag uint32
|
||||
|
||||
AuthPluginData []byte
|
||||
}
|
||||
|
||||
func (hk *HandshakeV10) ToByte() []byte {
|
||||
data := make([]byte, 0)
|
||||
|
||||
// protocol version
|
||||
data = append(data, WriteByte(protocolVersion)...)
|
||||
|
||||
// server version
|
||||
data = append(data, WriteStringNull(hk.ServerVersion)...)
|
||||
|
||||
// thread id
|
||||
data = append(data, WriteUint32(hk.ConnectionId)...)
|
||||
|
||||
// auth-plugin-data-part-1 first 8 bytes of the plugin provided data (scramble)
|
||||
data = append(data, hk.AuthPluginData[:8]...)
|
||||
|
||||
// filler 0x00 byte, terminating the first part of a scramble
|
||||
data = append(data, 0x00)
|
||||
|
||||
// capability_flags_1 The lower 2 bytes of the Capabilities Flags
|
||||
data = append(data, WriteUint16(uint16(hk.CapabilityFlag))...)
|
||||
|
||||
data = append(data, WriteByte(CharsetUtf8mb4GeneralCiId)...)
|
||||
|
||||
serverStatus := ServerStatusAutocommit
|
||||
data = append(data, WriteUint16(serverStatus)...)
|
||||
|
||||
// The upper 2 bytes of the Capabilities Flags
|
||||
data = append(data, WriteUint16(uint16(hk.CapabilityFlag>>16))...)
|
||||
|
||||
// Length of auth plugin data.
|
||||
// Always 21 (8 + 13).
|
||||
data = append(data, WriteByte(21)...)
|
||||
|
||||
// reserved. All 0s.
|
||||
fillByte := make([]byte, 10)
|
||||
data = append(data, fillByte...)
|
||||
|
||||
// auth-plugin-data-part-2 Rest of the plugin provided data (scramble), $len=MAX(13, length of auth-plugin-data - 8)
|
||||
data = append(data, hk.AuthPluginData[8:]...)
|
||||
|
||||
data = append(data, WriteStringNull(hk.AuthPluginMethod)...)
|
||||
|
||||
return WithHeaderPacket(data, 0)
|
||||
}
|
||||
|
||||
type HandshakeResponse struct {
|
||||
ClientFlag uint32
|
||||
MaxPacketSize uint32
|
||||
Charset uint8
|
||||
Username string
|
||||
Password []byte
|
||||
Database string
|
||||
AuthPluginMethod string
|
||||
ClientAttrLen uint64
|
||||
ClientAttrs map[string]string
|
||||
MysqlPacketHeader
|
||||
}
|
||||
|
||||
func (hp *HandshakeResponse) ToByte() []byte {
|
||||
res := make([]byte, 0, hp.Length)
|
||||
|
||||
res = append(res, WriteUint32(hp.ClientFlag)...)
|
||||
res = append(res, WriteUint32(hp.MaxPacketSize)...)
|
||||
|
||||
res = append(res, WriteByte(hp.Charset)...)
|
||||
|
||||
fill := make([]byte, 23)
|
||||
res = append(res, fill...)
|
||||
|
||||
res = append(res, WriteStringNull(hp.Username)...)
|
||||
|
||||
if hp.ClientFlag&CapabilityClientPluginAuthLenencClientData > 0 {
|
||||
res = append(res, WriteLengthEncodedString(hp.Password)...)
|
||||
} else {
|
||||
res = append(res, uint8(len(hp.Password)))
|
||||
res = append(res, hp.Password...)
|
||||
}
|
||||
|
||||
if hp.ClientFlag&CapabilityClientConnectWithDB > 0 {
|
||||
res = append(res, WriteStringNull(hp.Database)...)
|
||||
}
|
||||
|
||||
if hp.ClientFlag&CapabilityClientPluginAuth > 0 {
|
||||
res = append(res, WriteStringNull(hp.AuthPluginMethod)...)
|
||||
}
|
||||
|
||||
if hp.ClientFlag&CapabilityClientConnAttr > 0 {
|
||||
//res = append(res, WriteLengthEncodedInt(resp.ClientAttrLen)...)
|
||||
attrByte := ClientAttrsToByte(hp.ClientAttrs)
|
||||
attrLen := len(attrByte)
|
||||
if attrLen > 0 {
|
||||
res = append(res, WriteLengthEncodedInt(uint64(attrLen))...)
|
||||
res = append(res, attrByte...)
|
||||
}
|
||||
}
|
||||
|
||||
//fmt.Printf("%+v\n", buf.Bytes())
|
||||
|
||||
return WithHeaderPacket(res, hp.SequenceId)
|
||||
}
|
||||
|
||||
func ClientAttrsToByte(clientAttrs map[string]string) []byte {
|
||||
if len(clientAttrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
attrByte := make([]byte, 0)
|
||||
|
||||
for k, v := range clientAttrs {
|
||||
keyByte := []byte(k)
|
||||
valByte := []byte(v)
|
||||
|
||||
keyEncoded := WriteLengthEncodedInt(uint64(len(keyByte)))
|
||||
|
||||
valEncoded := WriteLengthEncodedInt(uint64(len(valByte)))
|
||||
|
||||
attrByte = append(attrByte, keyEncoded...)
|
||||
attrByte = append(attrByte, keyByte...)
|
||||
|
||||
attrByte = append(attrByte, valEncoded...)
|
||||
attrByte = append(attrByte, valByte...)
|
||||
}
|
||||
|
||||
//fmt.Printf("len:%d attrByte:%+v\n", len(attrByte), attrByte)
|
||||
|
||||
return attrByte
|
||||
}
|
||||
|
||||
func ParseClientAttrs(data []byte) (map[string]string, error) {
|
||||
length := len(data)
|
||||
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
readLength := 0
|
||||
|
||||
attrs := make(map[string]string)
|
||||
|
||||
for readLength < length {
|
||||
keyLen, keyPos, ok := ReadLengthEncodedInt(buf.Bytes())
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("read attrs key err")
|
||||
}
|
||||
|
||||
readLength += int(keyLen) + keyPos
|
||||
buf.Next(keyPos)
|
||||
|
||||
key := ReadString(buf.Next(int(keyLen)))
|
||||
|
||||
valLen, valPos, ok := ReadLengthEncodedInt(buf.Bytes())
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("read attrs value err")
|
||||
}
|
||||
|
||||
readLength += int(valLen) + keyPos
|
||||
buf.Next(valPos)
|
||||
|
||||
value := ReadString(buf.Next(int(valLen)))
|
||||
|
||||
attrs[key] = value
|
||||
|
||||
//fmt.Printf("%s - %s [%d]\n", key, value, readLength)
|
||||
//break
|
||||
}
|
||||
|
||||
//fmt.Printf("parse attrs ok: %+v\n", attrs)
|
||||
|
||||
return attrs, nil
|
||||
}
|
||||
|
||||
func ReadHandshakeV10(conn io.Reader) (*HandshakeV10, error) {
|
||||
pk, err := ReadMysqlPacket(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pk.Payload[0] != protocolVersion {
|
||||
return nil, fmt.Errorf("protocol version err: %d", pk.Payload[0])
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(pk.Payload)
|
||||
|
||||
res := &HandshakeV10{}
|
||||
|
||||
res.ProtocolVersion = ReadByte(buf.Next(1))
|
||||
|
||||
// version
|
||||
versionByte, err := buf.ReadBytes(0x00)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.ServerVersion = ReadStringNull(versionByte)
|
||||
|
||||
//fmt.Println(string(versionByte), versionByte)
|
||||
|
||||
// connId
|
||||
res.ConnectionId = ReadUint32(buf.Next(4))
|
||||
//fmt.Println(binary.LittleEndian.Uint32(connId))
|
||||
|
||||
fullAuthPluginData := make([]byte, 21)
|
||||
|
||||
authPluginData1 := buf.Next(8)
|
||||
|
||||
copy(fullAuthPluginData, authPluginData1[:8])
|
||||
//fmt.Println(string(salt1), salt1)
|
||||
|
||||
// filler 0x00
|
||||
buf.Next(1)
|
||||
|
||||
cap1 := buf.Next(2)
|
||||
//fmt.Println("cap1", cap1)
|
||||
|
||||
fullCap := make([]byte, 4)
|
||||
|
||||
copy(fullCap[:2], cap1)
|
||||
|
||||
charset := buf.Next(1)
|
||||
res.CharsetCollation = ReadByte(charset)
|
||||
|
||||
serverStatus := buf.Next(2)
|
||||
res.ServerStatus = ReadUint16(serverStatus)
|
||||
|
||||
cap2 := buf.Next(2)
|
||||
copy(fullCap[2:], cap2)
|
||||
|
||||
//fmt.Printf("fullCap: %+v\n", fullCap)
|
||||
res.CapabilityFlag = ReadUint32(fullCap)
|
||||
|
||||
//res.CapFlag2 = ReadUint16(cap2)
|
||||
|
||||
// auth_plugin_data_len, reserved. All 0s.
|
||||
buf.Next(1 + 10)
|
||||
|
||||
authPluginData2, err := buf.ReadBytes(0x00)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
copy(fullAuthPluginData[8:], authPluginData2)
|
||||
res.AuthPluginData = fullAuthPluginData
|
||||
|
||||
authPluginMethod, err := buf.ReadBytes(0x00)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.AuthPluginMethod = ReadStringNull(authPluginMethod)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func ReadHandshakeResponse(conn io.Reader) (*HandshakeResponse, error) {
|
||||
pk, err := ReadMysqlPacket(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(pk.Payload)
|
||||
|
||||
clientFlag := ReadUint32(buf.Next(4))
|
||||
if clientFlag&CapabilityClientProtocol41 == 0 {
|
||||
return nil, fmt.Errorf("only support CLIENT_PROTOCOL_41")
|
||||
}
|
||||
|
||||
if clientFlag&CapabilityClientPluginAuth == 0 {
|
||||
return nil, fmt.Errorf("unsupport without ClientPluginAuth")
|
||||
}
|
||||
|
||||
res := &HandshakeResponse{}
|
||||
res.MysqlPacketHeader = pk.MysqlPacketHeader
|
||||
|
||||
res.ClientFlag = clientFlag
|
||||
res.MaxPacketSize = ReadUint32(buf.Next(4))
|
||||
|
||||
res.Charset = ReadByte(buf.Next(1))
|
||||
|
||||
buf.Next(23)
|
||||
|
||||
username, err := buf.ReadBytes(0x00)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.Username = ReadStringNull(username)
|
||||
|
||||
//fmt.Println(buf.Bytes())
|
||||
|
||||
if clientFlag&CapabilityClientPluginAuthLenencClientData > 0 {
|
||||
length, pos, ok := ReadLengthEncodedInt(buf.Bytes())
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("ReadLengthEncodedInt err")
|
||||
}
|
||||
|
||||
buf.Next(pos)
|
||||
|
||||
password := buf.Next(int(length))
|
||||
|
||||
res.Password = password
|
||||
|
||||
} else {
|
||||
length := ReadByte(buf.Next(1))
|
||||
password := buf.Next(int(length))
|
||||
|
||||
res.Password = password
|
||||
}
|
||||
|
||||
if clientFlag&CapabilityClientConnectWithDB > 0 {
|
||||
db, err := buf.ReadBytes(0x00)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.Database = ReadStringNull(db)
|
||||
}
|
||||
|
||||
if clientFlag&CapabilityClientPluginAuth > 0 {
|
||||
authPluginMethod, err := buf.ReadBytes(0x00)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.AuthPluginMethod = ReadStringNull(authPluginMethod)
|
||||
}
|
||||
|
||||
if clientFlag&CapabilityClientConnAttr > 0 {
|
||||
length, pos, ok := ReadLengthEncodedInt(buf.Bytes())
|
||||
if ok {
|
||||
res.ClientAttrLen = length
|
||||
buf.Next(pos)
|
||||
|
||||
attrsByte := make([]byte, length)
|
||||
copy(attrsByte, buf.Next(int(res.ClientAttrLen)))
|
||||
|
||||
attrs, err := ParseClientAttrs(attrsByte)
|
||||
if err != nil {
|
||||
zlog.Errorf("parse client attrs err: %s", err)
|
||||
} else {
|
||||
res.ClientAttrs = attrs
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//fmt.Printf("%+v\n", buf.Bytes())
|
||||
}
|
||||
|
||||
//fmt.Printf("%+v\n", buf.Bytes())
|
||||
|
||||
return res, nil
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
package mysqlserver
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func WithHeaderPacket(data []byte, sequenceId uint8) []byte {
|
||||
payloadLength := len(data)
|
||||
|
||||
header := make([]byte, 4)
|
||||
|
||||
header[0] = byte(payloadLength)
|
||||
header[1] = byte(payloadLength >> 8)
|
||||
header[2] = byte(payloadLength >> 16)
|
||||
header[3] = sequenceId // 序列号 Sequence ID
|
||||
|
||||
return append(header, data...)
|
||||
}
|
||||
|
||||
func WriteByte(value byte) []byte {
|
||||
return []byte{value}
|
||||
}
|
||||
|
||||
func WriteUint16(value uint16) []byte {
|
||||
data := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(data, value)
|
||||
return data
|
||||
}
|
||||
|
||||
func WriteUint24(value uint32) []byte {
|
||||
data := make([]byte, 3)
|
||||
_ = data[2] // early bounds check to guarantee safety of writes below
|
||||
data[0] = byte(value)
|
||||
data[1] = byte(value >> 8)
|
||||
data[2] = byte(value >> 16)
|
||||
return data
|
||||
}
|
||||
|
||||
func WriteUint32(value uint32) []byte {
|
||||
data := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(data, value)
|
||||
return data
|
||||
}
|
||||
|
||||
func WriteUint64(value uint64) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, value)
|
||||
return data
|
||||
}
|
||||
|
||||
func WriteString(value string) []byte {
|
||||
return []byte(value)
|
||||
}
|
||||
|
||||
func WriteStringNull(value string) []byte {
|
||||
data := make([]byte, 0, len(value)+1)
|
||||
data = append(data, []byte(value)...)
|
||||
data = append(data, 0x00)
|
||||
return data
|
||||
}
|
||||
|
||||
func ReadString(value []byte) string {
|
||||
return string(value)
|
||||
}
|
||||
|
||||
func ReadHexString(value []byte) string {
|
||||
return string(value)
|
||||
}
|
||||
|
||||
func ReadStringNull(value []byte) string {
|
||||
ln := len(value)
|
||||
if ln == 0 {
|
||||
return ""
|
||||
}
|
||||
// 剔除最后一位0x00
|
||||
return string(value[:ln-1])
|
||||
}
|
||||
|
||||
func ReadByte(value []byte) uint8 {
|
||||
return value[0]
|
||||
}
|
||||
|
||||
func ReadUint16(value []byte) uint16 {
|
||||
return binary.LittleEndian.Uint16(value)
|
||||
}
|
||||
|
||||
func ReadUint24(value []byte) uint32 {
|
||||
_ = value[2]
|
||||
return uint32(value[0]) | uint32(value[1])<<8 | uint32(value[2])<<16
|
||||
}
|
||||
|
||||
func ReadUint32(value []byte) uint32 {
|
||||
return binary.LittleEndian.Uint32(value)
|
||||
}
|
||||
|
||||
func ReadUint64(value []byte) uint64 {
|
||||
return binary.LittleEndian.Uint64(value)
|
||||
}
|
||||
|
||||
// mysql 二进制数据(长度编码)(Length Coded Binary)
|
||||
// 第一个字节值 后续字节数 长度值说明
|
||||
// 0-250 0 第一个字节值即为数据的真实长度
|
||||
// 251 0 空数据,数据的真实长度为零
|
||||
// 252 2 后续额外2个字节标识了数据的真实长度
|
||||
// 253 3 后续额外3个字节标识了数据的真实长度
|
||||
// 254 8 后续额外8个字节标识了数据的真实长度
|
||||
func ReadLengthEncodedInt(data []byte) (dataLength uint64, pos int, ok bool) {
|
||||
if len(data) == 0 {
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
pos = 0
|
||||
|
||||
switch data[pos] {
|
||||
case 0xfb:
|
||||
// 251: NULL
|
||||
return 0, 1, true
|
||||
|
||||
case 0xfc:
|
||||
// 252
|
||||
// Encoded in the next 2 bytes.
|
||||
if pos+2 >= len(data) {
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
return uint64(data[pos+1]) |
|
||||
uint64(data[pos+2])<<8, pos + 3, true
|
||||
case 0xfd:
|
||||
// 253
|
||||
// Encoded in the next 3 bytes.
|
||||
if pos+3 >= len(data) {
|
||||
return 0, 0, false
|
||||
}
|
||||
return uint64(data[pos+1]) |
|
||||
uint64(data[pos+2])<<8 |
|
||||
uint64(data[pos+3])<<16, pos + 4, true
|
||||
case 0xfe:
|
||||
// 254
|
||||
// Encoded in the next 8 bytes.
|
||||
if pos+8 >= len(data) {
|
||||
return 0, 0, false
|
||||
}
|
||||
return uint64(data[pos+1]) |
|
||||
uint64(data[pos+2])<<8 |
|
||||
uint64(data[pos+3])<<16 |
|
||||
uint64(data[pos+4])<<24 |
|
||||
uint64(data[pos+5])<<32 |
|
||||
uint64(data[pos+6])<<40 |
|
||||
uint64(data[pos+7])<<48 |
|
||||
uint64(data[pos+8])<<56, pos + 9, true
|
||||
}
|
||||
return uint64(data[pos]), pos + 1, true
|
||||
}
|
||||
|
||||
func GetLengthEncodedIntSize(value uint64) int {
|
||||
switch {
|
||||
case value < 251:
|
||||
return 1
|
||||
case value < 1<<16:
|
||||
return 3
|
||||
case value < 1<<24:
|
||||
return 4
|
||||
default:
|
||||
return 9
|
||||
}
|
||||
}
|
||||
|
||||
func WriteLengthEncodedInt(value uint64) []byte {
|
||||
data := make([]byte, GetLengthEncodedIntSize(value))
|
||||
|
||||
switch {
|
||||
case value < 251:
|
||||
data[0] = byte(value)
|
||||
|
||||
case value < 1<<16:
|
||||
data[0] = 0xfc
|
||||
data[1] = byte(value)
|
||||
data[2] = byte(value >> 8)
|
||||
|
||||
case value < 1<<24:
|
||||
data[0] = 0xfd
|
||||
data[1] = byte(value)
|
||||
data[2] = byte(value >> 8)
|
||||
data[3] = byte(value >> 16)
|
||||
|
||||
default:
|
||||
data[0] = 0xfe
|
||||
data[1] = byte(value)
|
||||
data[2] = byte(value >> 8)
|
||||
data[3] = byte(value >> 16)
|
||||
data[4] = byte(value >> 24)
|
||||
data[5] = byte(value >> 32)
|
||||
data[6] = byte(value >> 40)
|
||||
data[7] = byte(value >> 48)
|
||||
data[8] = byte(value >> 56)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func WriteLengthEncodedString(strByte []byte) []byte {
|
||||
strLen := len(strByte)
|
||||
encodedInt := WriteLengthEncodedInt(uint64(strLen))
|
||||
|
||||
data := make([]byte, 0, strLen+len(encodedInt))
|
||||
data = append(data, encodedInt...)
|
||||
data = append(data, strByte...)
|
||||
return data
|
||||
}
|
||||
|
||||
func GetAuthPluginData() []byte {
|
||||
minChar := 30
|
||||
maxChar := 127
|
||||
res := make([]byte, 21)
|
||||
|
||||
for k := range res {
|
||||
if k == 20 {
|
||||
k = 0x00 // 认证字符串以0x00结尾
|
||||
break
|
||||
}
|
||||
|
||||
res[k] = byte(rand.Intn(maxChar-minChar) + minChar)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package mysqlserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseSqlComment(t *testing.T) {
|
||||
query := "/* TzAdmin-{\"AdminId\":39,\"AdminName\":\"test\",\"AdminRealName\":\"postman调试账号\",\"QueryGameId\":666,\"HeaderGameId\":888,\"Ip\":\"219.137.24.171\",\"RequestPath\":\"/api/general/day_report/totalAccount\",\"RequestInfo\":\"Path:\\\"/api/general/day_report/totalAccount\\\" RawQuery:\\\"qw=123\\\"\",\"UnixMilli\":1702539552664}-TzAdmin */ SELECT sum(if(`index`=9, index_value, 0)) as act_account_num, sum(if(`index`=6, index_value, 0)) as reg_account_num, sum(if(`index`=5, index_value, 0)) as pay_account_num, sum(if(`index`=8, index_value, 0)) as reg_pay_account_num, sum(if(`index`=12, index_value, 0)) as first_charge_account_num, sum(if(`index`=3, index_value, 0)) as pay_amount, sum(if(`index`=7, index_value, 0)) as reg_pay_amount FROM new_tzpt.tzpingtai_report_index_all t1 WHERE t1.game_id = 666 and t1.game_id in (666,888,999,1213,10001) AND t1.channel_id IN (5, 6, 100011, 100012, 100101, 100102, 100111, 125593, 10000001, 10000004, 10000005, 10000006, 10000007, 10000008, 10000009,10000011, -1) AND t1.collect_date BETWEEN '2023-11-01' AND '2023-11-30' ORDER BY pay_amount desc"
|
||||
|
||||
s := &RecordQuery{}
|
||||
s.parseSqlComment(query)
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package mysqlserver
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func ReadMysqlPacketHeader(conn io.Reader) (*MysqlPacketHeader, error) {
|
||||
header := make([]byte, 4)
|
||||
|
||||
_, err := io.ReadFull(conn, header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//fmt.Printf("%+v\n", header)
|
||||
|
||||
return &MysqlPacketHeader{
|
||||
Length: ReadUint24(header[:3]),
|
||||
SequenceId: ReadByte(header[3:]),
|
||||
HeaderByte: header,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ReadMysqlPacketByLength(conn io.Reader, dataLength int) ([]byte, error) {
|
||||
data := make([]byte, dataLength)
|
||||
|
||||
_, err := io.ReadFull(conn, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func ReadMysqlPacket(conn io.Reader) (*MysqlPacket, error) {
|
||||
header, err := ReadMysqlPacketHeader(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//fmt.Printf("%+v\n", header)
|
||||
|
||||
data := make([]byte, header.Length)
|
||||
|
||||
_, err = io.ReadFull(conn, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := &MysqlPacket{
|
||||
Payload: data,
|
||||
}
|
||||
res.MysqlPacketHeader = *header
|
||||
|
||||
return res, nil
|
||||
}
|
|
@ -0,0 +1,275 @@
|
|||
package mysqlserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"io"
|
||||
"proxymysql/app/conf"
|
||||
"proxymysql/app/db"
|
||||
"proxymysql/app/zlog"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ io.Writer = (*RecordQuery)(nil)
|
||||
|
||||
type RecordQuery struct {
|
||||
pipeReader *io.PipeReader
|
||||
pipeWriter *io.PipeWriter
|
||||
stmtId uint32
|
||||
stmtMap map[uint32]string
|
||||
}
|
||||
|
||||
func NewRecordQuery() *RecordQuery {
|
||||
r := &RecordQuery{}
|
||||
r.pipeReader, r.pipeWriter = io.Pipe()
|
||||
r.stmtMap = make(map[uint32]string)
|
||||
go r.readQuery()
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *RecordQuery) Write(p []byte) (n int, err error) {
|
||||
r.pipeWriter.Write(p)
|
||||
|
||||
return len(p), err
|
||||
}
|
||||
|
||||
func (r *RecordQuery) Close() error {
|
||||
r.pipeWriter.Close()
|
||||
r.pipeReader.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RecordQuery) readQuery() {
|
||||
for {
|
||||
packet, err := ReadMysqlPacket(r.pipeReader)
|
||||
if err != nil {
|
||||
// errors.Is(err, io.ErrClosedPipe)
|
||||
zlog.Errorf("read pipe pack err: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(packet.Payload) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch packet.Payload[0] {
|
||||
case ComQuery:
|
||||
query := string(packet.Payload[1:])
|
||||
|
||||
zlog.Debugf("query: %s\n", query)
|
||||
|
||||
r.saveToDb(query)
|
||||
|
||||
case ComPrepare:
|
||||
query := string(packet.Payload[1:])
|
||||
|
||||
zlog.Debugf("prepare: %s\n", query)
|
||||
r.stmtId++
|
||||
r.stmtMap[r.stmtId] = query
|
||||
|
||||
r.saveToDb(query)
|
||||
|
||||
case ComStmtExecute:
|
||||
query := r.stmtMap[r.stmtId]
|
||||
|
||||
_, args := r.parseStmtArgs(strings.Count(query, "?"), packet.Payload)
|
||||
|
||||
//fmt.Printf("ComStmtExecute: %s %+v\n", query, args)
|
||||
|
||||
fullSqlQuery, err := sqlbuilder.MySQL.Interpolate(query, args)
|
||||
if err != nil {
|
||||
zlog.Errorf("ComStmtExecute builder sql err: %s", err)
|
||||
} else {
|
||||
zlog.Debugf("stmt: %s\n", fullSqlQuery)
|
||||
}
|
||||
|
||||
r.saveToDb(fullSqlQuery)
|
||||
|
||||
case ComStmtClose:
|
||||
delete(r.stmtMap, r.stmtId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type BindArg struct {
|
||||
ArgType uint8
|
||||
Unsigned uint8
|
||||
ArgValue interface{}
|
||||
}
|
||||
|
||||
func (r *RecordQuery) parseStmtArgs(argNum int, data []byte) ([]*BindArg, []any) {
|
||||
if argNum == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
skipPos := 1 + 4 + 1 + 4
|
||||
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
//fmt.Printf("%+v\n", buf.Bytes())
|
||||
|
||||
buf.Next(skipPos)
|
||||
|
||||
nullBitMapLen := (argNum + 7) / 8
|
||||
//fmt.Println(nullBitMapLen)
|
||||
|
||||
nullBitMap := buf.Next(nullBitMapLen)
|
||||
//fmt.Println("nullBitMap", nullBitMap)
|
||||
|
||||
newParamsBindFlag := ReadByte(buf.Next(1))
|
||||
//fmt.Println("newParamsBindFlag", ReadByte(newParamsBindFlag))
|
||||
|
||||
if newParamsBindFlag != 0x01 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
bindArgs := make([]*BindArg, argNum)
|
||||
args := make([]interface{}, argNum)
|
||||
|
||||
for i := 0; i < argNum; i++ {
|
||||
filedType := ReadByte(buf.Next(1))
|
||||
//fmt.Printf("filedType: %+v\n", filedType)
|
||||
|
||||
unsigned := ReadByte(buf.Next(1))
|
||||
//fmt.Printf("unsigned: %+v\n", unsigned)
|
||||
|
||||
bindArgs[i] = &BindArg{
|
||||
ArgType: filedType,
|
||||
Unsigned: unsigned,
|
||||
ArgValue: nil,
|
||||
}
|
||||
}
|
||||
|
||||
//fmt.Printf("val: %+v\n", buf.Bytes())
|
||||
|
||||
//fmt.Printf("%+v\n", nullBitMap)
|
||||
|
||||
for i := 0; i < argNum; i++ {
|
||||
nullBytePos := i / 8
|
||||
nullBitPos := i % 8
|
||||
|
||||
//fmt.Printf("nullBytePos: %08b\n", nullBitMap[nullBytePos])
|
||||
//fmt.Printf("nullBitPos: %08b\n", 1<<nullBitPos)
|
||||
|
||||
if (nullBitMap[nullBytePos] & (1 << nullBitPos)) > 0 {
|
||||
//buf.Next(1)
|
||||
bindArgs[i].ArgValue = nil
|
||||
args[i] = nil
|
||||
|
||||
//fmt.Printf("%+v\n", bindArgs[i])
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
switch bindArgs[i].ArgType {
|
||||
|
||||
case FieldTypeTiny, FieldTypeBit:
|
||||
val := ReadByte(buf.Next(1))
|
||||
|
||||
bindArgs[i].ArgValue = val
|
||||
args[i] = val
|
||||
|
||||
case FieldTypeInt24, FieldTypeLong:
|
||||
val := ReadUint32(buf.Next(4))
|
||||
|
||||
bindArgs[i].ArgValue = val
|
||||
args[i] = val
|
||||
|
||||
case FieldTypeLongLong:
|
||||
val := ReadUint64(buf.Next(8))
|
||||
|
||||
bindArgs[i].ArgValue = val
|
||||
args[i] = val
|
||||
|
||||
default:
|
||||
length, pos, ok := ReadLengthEncodedInt(buf.Bytes())
|
||||
if !ok {
|
||||
zlog.Errorf("read args err %+v", buf.Bytes())
|
||||
continue
|
||||
}
|
||||
|
||||
buf.Next(pos)
|
||||
val := string(buf.Next(int(length)))
|
||||
|
||||
bindArgs[i].ArgValue = val
|
||||
args[i] = val
|
||||
|
||||
//fmt.Printf("str: %s\n", val)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return bindArgs, args
|
||||
}
|
||||
|
||||
type SqlComment struct {
|
||||
AdminId int64
|
||||
AdminName string
|
||||
AdminRealName string
|
||||
QueryGameId int32
|
||||
HeaderGameId int32
|
||||
Ip string
|
||||
RequestPath string
|
||||
RequestInfo string
|
||||
UnixMilli int64
|
||||
Query string
|
||||
CallInfo string
|
||||
CreateTime string
|
||||
}
|
||||
|
||||
var adminCommentReg = regexp.MustCompile(`/\*\s+TzAdmin-([\s\S]+)-TzAdmin\s+\*/`)
|
||||
|
||||
func (r *RecordQuery) parseSqlComment(query string) (sc *SqlComment) {
|
||||
sc = &SqlComment{}
|
||||
sc.Query = query
|
||||
if sc.UnixMilli == 0 {
|
||||
sc.UnixMilli = time.Now().UnixMilli()
|
||||
}
|
||||
|
||||
sc.CreateTime = time.Now().Format("2006-01-02 15:04:05.000")
|
||||
|
||||
if !strings.Contains(query, " TzAdmin-") {
|
||||
return
|
||||
}
|
||||
|
||||
subMatch := adminCommentReg.FindStringSubmatch(query)
|
||||
|
||||
if len(subMatch) >= 2 {
|
||||
err := jsoniter.Unmarshal([]byte(subMatch[1]), sc)
|
||||
if err != nil {
|
||||
zlog.Warnf("解析sql admin信息失败 %s [%s]", err, subMatch[1])
|
||||
return
|
||||
}
|
||||
|
||||
_sql := strings.TrimSpace(adminCommentReg.ReplaceAllString(sc.Query, ""))
|
||||
sc.Query = _sql
|
||||
//zlog.Infof("%+v\n", sc)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RecordQuery) saveToDb(query string) {
|
||||
if conf.App.SaveLog == false {
|
||||
return
|
||||
}
|
||||
sc := r.parseSqlComment(query)
|
||||
|
||||
//sc.Query = base64.StdEncoding.EncodeToString([]byte(sc.Query))
|
||||
|
||||
err := db.GetDB().Table("sql_query_log").Create(sc).Error
|
||||
if err != nil {
|
||||
zlog.Errorf("save to db err: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
package webserver
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"proxymysql/app/conf"
|
||||
"proxymysql/app/db"
|
||||
"proxymysql/app/zlog"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ApiRes struct {
|
||||
Code int32
|
||||
Message string
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
func Start() {
|
||||
g := gin.Default()
|
||||
|
||||
g.LoadHTMLGlob("resource/view/*.tmpl")
|
||||
|
||||
g.Static("/resource", "resource")
|
||||
|
||||
g.GET("/", func(ctx *gin.Context) {
|
||||
ctx.HTML(http.StatusOK, "index.tmpl", nil)
|
||||
})
|
||||
|
||||
g.POST("/ListSqlQueryLog", func(ctx *gin.Context) {
|
||||
//time.Sleep(time.Millisecond * 200)
|
||||
type Req struct {
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
QueryInfo string `json:"query_info"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
AdminName string `json:"admin_name"`
|
||||
}
|
||||
|
||||
req := &Req{}
|
||||
err := ctx.ShouldBindJSON(req)
|
||||
if err != nil {
|
||||
zlog.Warn("params err: %s", err)
|
||||
}
|
||||
|
||||
query := db.GetDB().Table("sql_query_log a")
|
||||
query.Select("a.*")
|
||||
query.Where("a.admin_name != ''")
|
||||
|
||||
//if req.GameId >= 0 {
|
||||
// query.Where("header_game_id", req.GameId)
|
||||
//}
|
||||
|
||||
if req.QueryInfo != "" {
|
||||
query.Where("a.query like ?", "%"+req.QueryInfo+"%")
|
||||
}
|
||||
|
||||
if req.AdminName != "" {
|
||||
query.Where("a.admin_name = ?", req.AdminName)
|
||||
}
|
||||
|
||||
if req.StartDate != "" && req.EndDate != "" {
|
||||
query.Where("a.create_time between ? and ?", req.StartDate, req.EndDate+" 23:59:59.999")
|
||||
}
|
||||
|
||||
if req.Page <= 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
|
||||
if req.Limit <= 0 {
|
||||
req.Limit = 20
|
||||
}
|
||||
|
||||
query.Order("a.id desc")
|
||||
|
||||
offset := (req.Page - 1) * req.Limit
|
||||
|
||||
query.Limit(req.Limit).Offset(offset)
|
||||
|
||||
type SqlQueryLog struct {
|
||||
Id int64
|
||||
AdminId int32
|
||||
AdminName string
|
||||
AdminRealName string
|
||||
QueryGameId int32
|
||||
HeaderGameId int32
|
||||
Ip string
|
||||
RequestPath string
|
||||
RequestInfo string
|
||||
UnixMilli int64
|
||||
Query string
|
||||
CreateTime time.Time
|
||||
QueryTime string
|
||||
Project string
|
||||
CallInfo string
|
||||
MenuName string
|
||||
}
|
||||
|
||||
list := make([]*SqlQueryLog, 0)
|
||||
|
||||
err = query.Find(&list).Error
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusOK, &ApiRes{
|
||||
Code: -1,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
pathList := make([]string, 0, len(list))
|
||||
|
||||
for _, v := range list {
|
||||
pathList = append(pathList, v.RequestPath)
|
||||
//if v.Query != "" {
|
||||
//decQuery, _ := base64.StdEncoding.DecodeString(v.Query)
|
||||
//v.Query = string(decQuery)
|
||||
//}
|
||||
|
||||
v.QueryTime = v.CreateTime.Format("2006-01-02 15:04:05.000")
|
||||
//if v.UnixMilli > 0 {
|
||||
// v.QueryTime = time.UnixMilli(v.UnixMilli).Format("2006-01-02 15:04:05.000")
|
||||
//} else {
|
||||
// v.QueryTime = v.CreateTime.Format("2006-01-02 15:04:05.000")
|
||||
//}
|
||||
|
||||
if v.RequestPath != "" {
|
||||
v.Project = strings.Split(v.RequestPath, "/")[2]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
queryMenu := db.GetDB().Table("new_admin.power_menu").
|
||||
Select("name", "path").
|
||||
Where("path in ?", pathList)
|
||||
|
||||
type MenuInfo struct {
|
||||
Name string
|
||||
Path string
|
||||
}
|
||||
menuInfos := make([]*MenuInfo, 0)
|
||||
err = queryMenu.Find(&menuInfos).Error
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusOK, &ApiRes{
|
||||
Code: -1,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
menuInfoMap := make(map[string]string)
|
||||
for _, v := range menuInfos {
|
||||
menuInfoMap[v.Path] = v.Name
|
||||
}
|
||||
|
||||
for _, v := range list {
|
||||
v.MenuName = menuInfoMap[v.RequestPath]
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, &ApiRes{
|
||||
Message: "ok",
|
||||
Data: gin.H{
|
||||
"List": list,
|
||||
"TotalCount": GetSimplePageCount(req.Page, req.Limit, len(list)),
|
||||
},
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
g.POST("/SelectInfo", func(ctx *gin.Context) {
|
||||
type AdminName struct {
|
||||
AdminName string
|
||||
}
|
||||
|
||||
names := make([]*AdminName, 0)
|
||||
err := db.GetDB().Table("sql_query_log").Select("distinct admin_name").
|
||||
Where("admin_name != ''").
|
||||
Order("id desc").
|
||||
Find(&names).Error
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusOK, &ApiRes{
|
||||
Code: -1,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
res := make(map[string]interface{})
|
||||
|
||||
res["AdminNameList"] = names
|
||||
|
||||
ctx.JSON(http.StatusOK, &ApiRes{Data: res})
|
||||
})
|
||||
|
||||
zlog.Infof("web server listen on %s", conf.App.WebAddr)
|
||||
|
||||
g.Run(conf.App.WebAddr)
|
||||
|
||||
}
|
||||
|
||||
func GetSimplePageCount(pageNum int, limit int, rowsNum int) int {
|
||||
count := (pageNum-1)*limit + rowsNum
|
||||
// 查出来的条数和limit数量相等,加1让下一页按钮能够点击
|
||||
if rowsNum == limit {
|
||||
count = count + 1
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
package zlog
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
DebugLevel = "DEBUG"
|
||||
InfoLevel = "INFO"
|
||||
WarnLevel = "WARN"
|
||||
ErrorLevel = "ERROR"
|
||||
FatalLevel = "FATAL"
|
||||
)
|
||||
|
||||
var (
|
||||
levelMap = map[string]zapcore.Level{
|
||||
"DEBUG": zapcore.DebugLevel,
|
||||
"INFO": zapcore.InfoLevel,
|
||||
"WARN": zapcore.WarnLevel,
|
||||
"ERROR": zapcore.ErrorLevel,
|
||||
"FATAL": zapcore.FatalLevel,
|
||||
}
|
||||
// DefaultConfig 默认配置
|
||||
DefaultConfig = &Config{
|
||||
Level: DebugLevel,
|
||||
NeedLogFile: false,
|
||||
ConsoleWriter: os.Stdout,
|
||||
}
|
||||
|
||||
DefaultLogFile = &LogFile{
|
||||
LogFilePath: "logs",
|
||||
MaxSize: 200,
|
||||
MaxAge: 0,
|
||||
MaxBackups: 0,
|
||||
}
|
||||
globalLog = NewLogger("app", DefaultConfig)
|
||||
)
|
||||
|
||||
type (
|
||||
ZapLog struct {
|
||||
sugarLog *zap.SugaredLogger
|
||||
}
|
||||
|
||||
Config struct {
|
||||
Level string
|
||||
NeedLogFile bool
|
||||
ConsoleWriter io.Writer
|
||||
ZapOpt []zap.Option
|
||||
LogFile *LogFile
|
||||
}
|
||||
|
||||
LogFile struct {
|
||||
LogFilePath string
|
||||
MaxSize int
|
||||
MaxAge int
|
||||
MaxBackups int
|
||||
}
|
||||
)
|
||||
|
||||
// Init 覆盖默认日志
|
||||
func Init(serverName string, config *Config) {
|
||||
globalLog = NewLogger(serverName, config)
|
||||
}
|
||||
|
||||
func NewLogger(serverName string, config *Config) *ZapLog {
|
||||
if config == nil {
|
||||
config = DefaultConfig
|
||||
}
|
||||
var level zapcore.Level
|
||||
if v, ok := levelMap[strings.ToUpper(config.Level)]; ok {
|
||||
level = v
|
||||
} else {
|
||||
level = zapcore.DebugLevel
|
||||
}
|
||||
|
||||
cores := make([]zapcore.Core, 0)
|
||||
// 使用控制台输出
|
||||
if config.ConsoleWriter != nil {
|
||||
cfg := zap.NewProductionEncoderConfig()
|
||||
cfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||
cfg.ConsoleSeparator = " | "
|
||||
// 指定日志时间格式
|
||||
cfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05.000")
|
||||
cfg.EncodeCaller = zapcore.ShortCallerEncoder
|
||||
encoder := zapcore.NewConsoleEncoder(cfg)
|
||||
core := zapcore.NewCore(encoder, zapcore.AddSync(config.ConsoleWriter), level)
|
||||
cores = append(cores, core)
|
||||
}
|
||||
|
||||
if config.NeedLogFile {
|
||||
cfg := zap.NewProductionEncoderConfig()
|
||||
cfg.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
cfg.ConsoleSeparator = " | "
|
||||
// 指定日志时间格式
|
||||
cfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05.000")
|
||||
cfg.EncodeCaller = zapcore.ShortCallerEncoder
|
||||
encoder := zapcore.NewConsoleEncoder(cfg)
|
||||
core := zapcore.NewCore(encoder, zapcore.AddSync(getRollingFileWriter(serverName, config)), level)
|
||||
cores = append(cores, core)
|
||||
}
|
||||
|
||||
opts := make([]zap.Option, 0)
|
||||
if config.ZapOpt != nil {
|
||||
opts = config.ZapOpt
|
||||
} else {
|
||||
opts = append(opts, zap.AddCaller(), zap.AddCallerSkip(2))
|
||||
}
|
||||
|
||||
zl := zap.New(zapcore.NewTee(cores...), opts...)
|
||||
|
||||
return &ZapLog{
|
||||
sugarLog: zl.Sugar(),
|
||||
}
|
||||
}
|
||||
|
||||
func getRollingFileWriter(serverName string, config *Config) *lumberjack.Logger {
|
||||
if config.LogFile == nil {
|
||||
config.LogFile = DefaultLogFile
|
||||
}
|
||||
|
||||
return &lumberjack.Logger{
|
||||
Filename: filepath.Join(config.LogFile.LogFilePath, serverName+".log"),
|
||||
MaxSize: config.LogFile.MaxSize,
|
||||
MaxAge: config.LogFile.MaxAge,
|
||||
MaxBackups: config.LogFile.MaxBackups,
|
||||
LocalTime: true,
|
||||
Compress: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (z *ZapLog) Debug(args ...interface{}) {
|
||||
z.sugarLog.Debug(args...)
|
||||
}
|
||||
|
||||
func (z *ZapLog) Info(args ...interface{}) {
|
||||
z.sugarLog.Info(args...)
|
||||
}
|
||||
|
||||
func (z *ZapLog) Warn(args ...interface{}) {
|
||||
z.sugarLog.Warn(args...)
|
||||
}
|
||||
|
||||
func (z *ZapLog) Error(args ...interface{}) {
|
||||
z.sugarLog.Error(args...)
|
||||
}
|
||||
|
||||
func (z *ZapLog) Fatal(args ...interface{}) {
|
||||
z.sugarLog.Fatal(args...)
|
||||
}
|
||||
|
||||
func (z *ZapLog) Debugf(format string, args ...interface{}) {
|
||||
z.sugarLog.Debugf(format, args...)
|
||||
}
|
||||
|
||||
func (z *ZapLog) Infof(format string, args ...interface{}) {
|
||||
z.sugarLog.Infof(format, args...)
|
||||
}
|
||||
|
||||
func (z *ZapLog) Warnf(format string, args ...interface{}) {
|
||||
z.sugarLog.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (z *ZapLog) Errorf(format string, args ...interface{}) {
|
||||
z.sugarLog.Errorf(format, args...)
|
||||
}
|
||||
|
||||
func (z *ZapLog) Fatalf(format string, args ...interface{}) {
|
||||
z.sugarLog.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
func (z *ZapLog) Println(args ...interface{}) {
|
||||
z.sugarLog.Info(args...)
|
||||
}
|
||||
|
||||
func (z *ZapLog) Printf(format string, args ...interface{}) {
|
||||
z.sugarLog.Infof(format, args...)
|
||||
}
|
||||
|
||||
func (z *ZapLog) Sync() {
|
||||
z.sugarLog.Sync()
|
||||
}
|
||||
|
||||
func Debug(args ...interface{}) {
|
||||
globalLog.Debug(args...)
|
||||
}
|
||||
|
||||
func Info(args ...interface{}) {
|
||||
globalLog.Info(args...)
|
||||
}
|
||||
|
||||
func Warn(args ...interface{}) {
|
||||
globalLog.Warn(args...)
|
||||
}
|
||||
|
||||
func Error(args ...interface{}) {
|
||||
globalLog.Error(args...)
|
||||
}
|
||||
|
||||
func Fatal(args ...interface{}) {
|
||||
globalLog.Fatal(args...)
|
||||
}
|
||||
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
globalLog.Debugf(format, args...)
|
||||
}
|
||||
|
||||
func Infof(format string, args ...interface{}) {
|
||||
globalLog.Infof(format, args...)
|
||||
}
|
||||
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
globalLog.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
globalLog.Errorf(format, args...)
|
||||
}
|
||||
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
globalLog.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
func Println(args ...interface{}) {
|
||||
globalLog.Info(args...)
|
||||
}
|
||||
|
||||
func Printf(format string, args ...interface{}) {
|
||||
globalLog.Infof(format, args...)
|
||||
}
|
||||
|
||||
func GetLogger() *ZapLog {
|
||||
return globalLog
|
||||
}
|
||||
|
||||
func Flush() {
|
||||
globalLog.Sync()
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
module proxymysql
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/glebarez/sqlite v1.10.0
|
||||
github.com/huandu/go-sqlbuilder v1.25.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
go.uber.org/zap v1.24.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gorm.io/gorm v1.25.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/goleak v1.2.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/mysql v1.5.2 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/sqlite v1.23.1 // indirect
|
||||
)
|
|
@ -0,0 +1,143 @@
|
|||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
|
||||
github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c=
|
||||
github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U=
|
||||
github.com/huandu/go-sqlbuilder v1.25.0 h1:h1l+6CqeCviPJCnkEZoRGNdfZ5RO9BKMvG3A+1VuKNM=
|
||||
github.com/huandu/go-sqlbuilder v1.25.0/go.mod h1:nUVmMitjOmn/zacMLXT0d3Yd3RHoO2K+vy906JzqxMI=
|
||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs=
|
||||
gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8=
|
||||
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
@ -0,0 +1,63 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"net"
|
||||
"proxymysql/app/conf"
|
||||
"proxymysql/app/mysqlserver"
|
||||
"proxymysql/app/zlog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.StringVar(&conf.App.RemoteDb, "remote_db", "", "")
|
||||
flag.StringVar(&conf.App.ServerAddr, "server_addr", ":5306", "")
|
||||
flag.StringVar(&conf.App.WebAddr, "web_addr", "", "")
|
||||
flag.StringVar(&conf.App.ServerVersion, "server_version", "8.0.30-my-mysql-proxy", "")
|
||||
flag.BoolVar(&conf.App.SaveLog, "save_log", false, "")
|
||||
flag.StringVar(&conf.App.LogLevel, "log_level", zlog.InfoLevel, "日志级别 debug info error")
|
||||
flag.Parse()
|
||||
|
||||
cfg := zlog.DefaultConfig
|
||||
cfg.Level = conf.App.LogLevel
|
||||
zlog.Init("app", cfg)
|
||||
|
||||
if conf.App.RemoteDb == "" {
|
||||
zlog.Fatal("remote db addr not set")
|
||||
}
|
||||
|
||||
zlog.Infof("save query log: %v", conf.App.SaveLog)
|
||||
zlog.Infof("server version: %s", conf.App.ServerVersion)
|
||||
zlog.Infof("remote db: %s", conf.App.RemoteDb)
|
||||
|
||||
// todb
|
||||
//if conf.App.WebAddr != "" {
|
||||
// db.InitAdminDb()
|
||||
// go webserver.Start()
|
||||
//}
|
||||
|
||||
listen, err := net.Listen("tcp", conf.App.ServerAddr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
zlog.Infof("db server listen on: %s", conf.App.ServerAddr)
|
||||
|
||||
for {
|
||||
conn, err := listen.Accept()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
go func(conn2 net.Conn) {
|
||||
defer conn2.Close()
|
||||
|
||||
err := mysqlserver.NewProxyConn(conn2).Handle()
|
||||
if err != nil {
|
||||
zlog.Errorf("proxy conn handle err: %s", err)
|
||||
}
|
||||
|
||||
}(conn)
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,2 @@
|
|||
!function(e){"function"==typeof define&&define.amd?define(e):e()}(function(){var e,t=["scroll","wheel","touchstart","touchmove","touchenter","touchend","touchleave","mouseout","mouseleave","mouseup","mousedown","mousemove","mouseenter","mousewheel","mouseover"];if(function(){var e=!1;try{var t=Object.defineProperty({},"passive",{get:function(){e=!0}});window.addEventListener("test",null,t),window.removeEventListener("test",null,t)}catch(e){}return e}()){var n=EventTarget.prototype.addEventListener;e=n,EventTarget.prototype.addEventListener=function(n,o,r){var i,s="object"==typeof r&&null!==r,u=s?r.capture:r;(r=s?function(e){var t=Object.getOwnPropertyDescriptor(e,"passive");return t&&!0!==t.writable&&void 0===t.set?Object.assign({},e):e}(r):{}).passive=void 0!==(i=r.passive)?i:-1!==t.indexOf(n)&&!0,r.capture=void 0!==u&&u,e.call(this,n,o,r)},EventTarget.prototype.addEventListener._original=e}});
|
||||
//# sourceMappingURL=index.umd.js.map
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,139 @@
|
|||
/*! Element Plus v2.4.3 */
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ElementPlusLocaleZhCn = factory());
|
||||
})(this, (function () { 'use strict';
|
||||
|
||||
var zhCn = {
|
||||
name: "zh-cn",
|
||||
el: {
|
||||
colorpicker: {
|
||||
confirm: "\u786E\u5B9A",
|
||||
clear: "\u6E05\u7A7A"
|
||||
},
|
||||
datepicker: {
|
||||
now: "\u6B64\u523B",
|
||||
today: "\u4ECA\u5929",
|
||||
cancel: "\u53D6\u6D88",
|
||||
clear: "\u6E05\u7A7A",
|
||||
confirm: "\u786E\u5B9A",
|
||||
selectDate: "\u9009\u62E9\u65E5\u671F",
|
||||
selectTime: "\u9009\u62E9\u65F6\u95F4",
|
||||
startDate: "\u5F00\u59CB\u65E5\u671F",
|
||||
startTime: "\u5F00\u59CB\u65F6\u95F4",
|
||||
endDate: "\u7ED3\u675F\u65E5\u671F",
|
||||
endTime: "\u7ED3\u675F\u65F6\u95F4",
|
||||
prevYear: "\u524D\u4E00\u5E74",
|
||||
nextYear: "\u540E\u4E00\u5E74",
|
||||
prevMonth: "\u4E0A\u4E2A\u6708",
|
||||
nextMonth: "\u4E0B\u4E2A\u6708",
|
||||
year: "\u5E74",
|
||||
month1: "1 \u6708",
|
||||
month2: "2 \u6708",
|
||||
month3: "3 \u6708",
|
||||
month4: "4 \u6708",
|
||||
month5: "5 \u6708",
|
||||
month6: "6 \u6708",
|
||||
month7: "7 \u6708",
|
||||
month8: "8 \u6708",
|
||||
month9: "9 \u6708",
|
||||
month10: "10 \u6708",
|
||||
month11: "11 \u6708",
|
||||
month12: "12 \u6708",
|
||||
weeks: {
|
||||
sun: "\u65E5",
|
||||
mon: "\u4E00",
|
||||
tue: "\u4E8C",
|
||||
wed: "\u4E09",
|
||||
thu: "\u56DB",
|
||||
fri: "\u4E94",
|
||||
sat: "\u516D"
|
||||
},
|
||||
months: {
|
||||
jan: "\u4E00\u6708",
|
||||
feb: "\u4E8C\u6708",
|
||||
mar: "\u4E09\u6708",
|
||||
apr: "\u56DB\u6708",
|
||||
may: "\u4E94\u6708",
|
||||
jun: "\u516D\u6708",
|
||||
jul: "\u4E03\u6708",
|
||||
aug: "\u516B\u6708",
|
||||
sep: "\u4E5D\u6708",
|
||||
oct: "\u5341\u6708",
|
||||
nov: "\u5341\u4E00\u6708",
|
||||
dec: "\u5341\u4E8C\u6708"
|
||||
}
|
||||
},
|
||||
select: {
|
||||
loading: "\u52A0\u8F7D\u4E2D",
|
||||
noMatch: "\u65E0\u5339\u914D\u6570\u636E",
|
||||
noData: "\u65E0\u6570\u636E",
|
||||
placeholder: "\u8BF7\u9009\u62E9"
|
||||
},
|
||||
cascader: {
|
||||
noMatch: "\u65E0\u5339\u914D\u6570\u636E",
|
||||
loading: "\u52A0\u8F7D\u4E2D",
|
||||
placeholder: "\u8BF7\u9009\u62E9",
|
||||
noData: "\u6682\u65E0\u6570\u636E"
|
||||
},
|
||||
pagination: {
|
||||
goto: "\u524D\u5F80",
|
||||
pagesize: "\u6761/\u9875",
|
||||
total: "\u5171 {total} \u6761",
|
||||
pageClassifier: "\u9875",
|
||||
page: "\u9875",
|
||||
prev: "\u4E0A\u4E00\u9875",
|
||||
next: "\u4E0B\u4E00\u9875",
|
||||
currentPage: "\u7B2C {pager} \u9875",
|
||||
prevPages: "\u5411\u524D {pager} \u9875",
|
||||
nextPages: "\u5411\u540E {pager} \u9875",
|
||||
deprecationWarning: "\u4F60\u4F7F\u7528\u4E86\u4E00\u4E9B\u5DF2\u88AB\u5E9F\u5F03\u7684\u7528\u6CD5\uFF0C\u8BF7\u53C2\u8003 el-pagination \u7684\u5B98\u65B9\u6587\u6863"
|
||||
},
|
||||
messagebox: {
|
||||
title: "\u63D0\u793A",
|
||||
confirm: "\u786E\u5B9A",
|
||||
cancel: "\u53D6\u6D88",
|
||||
error: "\u8F93\u5165\u7684\u6570\u636E\u4E0D\u5408\u6CD5!"
|
||||
},
|
||||
upload: {
|
||||
deleteTip: "\u6309 delete \u952E\u53EF\u5220\u9664",
|
||||
delete: "\u5220\u9664",
|
||||
preview: "\u67E5\u770B\u56FE\u7247",
|
||||
continue: "\u7EE7\u7EED\u4E0A\u4F20"
|
||||
},
|
||||
table: {
|
||||
emptyText: "\u6682\u65E0\u6570\u636E",
|
||||
confirmFilter: "\u7B5B\u9009",
|
||||
resetFilter: "\u91CD\u7F6E",
|
||||
clearFilter: "\u5168\u90E8",
|
||||
sumText: "\u5408\u8BA1"
|
||||
},
|
||||
tree: {
|
||||
emptyText: "\u6682\u65E0\u6570\u636E"
|
||||
},
|
||||
transfer: {
|
||||
noMatch: "\u65E0\u5339\u914D\u6570\u636E",
|
||||
noData: "\u65E0\u6570\u636E",
|
||||
titles: ["\u5217\u8868 1", "\u5217\u8868 2"],
|
||||
filterPlaceholder: "\u8BF7\u8F93\u5165\u641C\u7D22\u5185\u5BB9",
|
||||
noCheckedFormat: "\u5171 {total} \u9879",
|
||||
hasCheckedFormat: "\u5DF2\u9009 {checked}/{total} \u9879"
|
||||
},
|
||||
image: {
|
||||
error: "\u52A0\u8F7D\u5931\u8D25"
|
||||
},
|
||||
pageHeader: {
|
||||
title: "\u8FD4\u56DE"
|
||||
},
|
||||
popconfirm: {
|
||||
confirmButtonText: "\u786E\u5B9A",
|
||||
cancelButtonText: "\u53D6\u6D88"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return zhCn;
|
||||
|
||||
}));
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,375 @@
|
|||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Title</title>
|
||||
|
||||
{{/* <script src="/resource/js/default-passive-events-index.umd.js"></script>*/}}
|
||||
|
||||
<link rel="stylesheet" href="/resource/css/element-index.css"/>
|
||||
|
||||
|
||||
<!-- Import component library -->
|
||||
<script src="/resource/js/vue.global.js"></script>
|
||||
|
||||
<script src="/resource/js/element-index.full.js"></script>
|
||||
<script src="/resource/js/element-zh-cn.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="/resource/css/vxe-style.css">
|
||||
<script src="/resource/js/xe-utils.js"></script>
|
||||
<script src="/resource/js/vxe-table.js"></script>
|
||||
|
||||
|
||||
<style>
|
||||
#app {
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="app">
|
||||
<div class="header">
|
||||
<el-form :inline="true" :model="form" class="demo-form-inline">
|
||||
<el-form-item label="时间范围" style="width: 350px">
|
||||
<el-date-picker
|
||||
v-model="form.date"
|
||||
type="daterange"
|
||||
start-placeholder="Start date"
|
||||
end-placeholder="End date"
|
||||
format="YYYY-MM-DD"
|
||||
date-format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
:clearable="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="管理员">
|
||||
<el-select v-model="form.admin_name" placeholder="选择" clearable>
|
||||
<el-option v-for="item in selectInfo.adminNameList" :label="item.AdminName"
|
||||
:value="item.AdminName"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item label="sql信息" style="width: 500px">
|
||||
<el-input v-model="form.query_info" placeholder="sql信息" clearable/>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
</el-form>
|
||||
|
||||
<div>
|
||||
<el-button type="primary" @click="listSqlQueryLog">查询</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
|
||||
<div style="margin-top: 15px; height: 750px">
|
||||
<vxe-table
|
||||
height="100%"
|
||||
stripe
|
||||
:data="tableData"
|
||||
:column-config="{resizable: true}"
|
||||
>
|
||||
<vxe-column width="80" field="Id" title="ID" show-overflow></vxe-column>
|
||||
<vxe-column width="200" field="QueryTime" title="调用时间" show-overflow></vxe-column>
|
||||
<vxe-column width="130" field="Ip" title="IP" show-overflow></vxe-column>
|
||||
<vxe-column width="100" field="AdminName" title="账号" show-overflow></vxe-column>
|
||||
<vxe-column width="100" field="AdminRealName" title="姓名" show-overflow></vxe-column>
|
||||
<vxe-column width="100" field="Project" title="项目" show-overflow></vxe-column>
|
||||
<vxe-column width="200" field="RequestPath" title="接口地址" show-overflow></vxe-column>
|
||||
<vxe-column width="200" field="MenuName" title="接口名称" show-overflow></vxe-column>
|
||||
<vxe-column field="CallInfo" title="调用路径" show-overflow></vxe-column>
|
||||
|
||||
<vxe-column width="200" field="Query" title="SQL" show-overflow>
|
||||
<template #default="{ row }">
|
||||
<span style="cursor: pointer" @click="copText(row.Query)">${ row.Query }$</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
|
||||
<vxe-column width="100" field="" title="操作">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" type="success" @click="showQuerySql(row)">查看</el-button>
|
||||
</template>
|
||||
</vxe-column>
|
||||
|
||||
|
||||
</vxe-table>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="margin-top: 15px; width: 100%; display: flex; justify-content: center">
|
||||
<el-pagination
|
||||
background
|
||||
:total="pageInfo.totalCount"
|
||||
v-model:current-page="pageInfo.currentPage"
|
||||
v-model:page-size="pageInfo.pageSize"
|
||||
layout="prev, slot, next, sizes"
|
||||
@size-change="listSqlQueryLog(true)"
|
||||
@current-change="listSqlQueryLog(false)"
|
||||
:page-sizes="[200, 600, 1000]"
|
||||
>
|
||||
<template #default>
|
||||
<ul class="el-pager">
|
||||
<li class="is-active number">${ pageInfo.currentPage }$</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="Title"
|
||||
width="30%"
|
||||
>
|
||||
<div>
|
||||
<el-input
|
||||
v-model="dialogRow.Query"
|
||||
rows="20"
|
||||
type="textarea"
|
||||
placeholder="Please input"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="dialogRow = {}; dialogVisible = false">确定</el-button>
|
||||
</template>
|
||||
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
function getDate(diff) {
|
||||
let date = new Date(); // 当前日期和时间
|
||||
|
||||
date.setDate(date.getDate() + diff)
|
||||
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0'); // 月份从0开始,需要加1,并确保是两位数
|
||||
const day = date.getDate().toString().padStart(2, '0'); // 确保是两位数的日期
|
||||
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
|
||||
const {createApp, ref, reactive, toRefs, toRef} = Vue
|
||||
|
||||
|
||||
console.log(getDate(5))
|
||||
|
||||
|
||||
const vm = createApp({
|
||||
delimiters: ['${', '}$'],
|
||||
setup() {
|
||||
const data = reactive({
|
||||
mailMessage: "",
|
||||
form: {
|
||||
game_id: "",
|
||||
query_info: "",
|
||||
date: [getDate(-15), getDate(0)],
|
||||
admin_name: "",
|
||||
},
|
||||
tableData: [],
|
||||
pageInfo: {
|
||||
currentPage: 1,
|
||||
pageSize: 200,
|
||||
},
|
||||
dialogVisible: false,
|
||||
dialogRow: {},
|
||||
selectInfo: {
|
||||
adminNameList: []
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
listSqlQueryLog()
|
||||
selectInfo()
|
||||
})
|
||||
|
||||
const resetPageInfo = () => {
|
||||
data.pageInfo.currentPage = 1
|
||||
}
|
||||
|
||||
|
||||
const selectInfo = () => {
|
||||
let loading = ElementPlus.ElLoading.service({
|
||||
fullscreen: true,
|
||||
lock: true
|
||||
})
|
||||
|
||||
fetch("/SelectInfo", {
|
||||
method: 'POST', // 或 'POST'、'PUT' 等
|
||||
})
|
||||
.then(response => {
|
||||
// 检查请求是否成功(状态码为 200 到 299)
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
// 将响应解析为 JSON
|
||||
return response.json();
|
||||
})
|
||||
.then(res => {
|
||||
// 处理返回的 JSON 数据
|
||||
if (res.Code != 0) {
|
||||
console.log(res);
|
||||
ElementPlus.ElMessage.error(res.Message)
|
||||
return
|
||||
}
|
||||
|
||||
data.selectInfo.adminNameList = res.Data.AdminNameList
|
||||
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
// 处理错误
|
||||
console.error('Fetch error:', error);
|
||||
alert(error)
|
||||
}).finally(
|
||||
() => {
|
||||
loading.close()
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
const copText = (text) => {
|
||||
var textToCopy = text;
|
||||
var textArea = document.createElement("textarea");
|
||||
textArea.value = textToCopy;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
console.log('Text copied to clipboard');
|
||||
ElementPlus.ElMessage.success("复制成功")
|
||||
} catch (err) {
|
||||
console.error('Unable to copy text', err);
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
|
||||
// navigator.clipboard.writeText(text)
|
||||
// .then(function () {
|
||||
// ElementPlus.ElMessage.success("复制成功")
|
||||
// })
|
||||
// .catch(function (err) {
|
||||
// console.error('复制失败', err);
|
||||
// ElementPlus.ElMessage.error("复制失败")
|
||||
// });
|
||||
}
|
||||
|
||||
|
||||
const showQuerySql = (row) => {
|
||||
console.log(row)
|
||||
data.dialogRow = JSON.parse(JSON.stringify(row))
|
||||
data.dialogVisible = true
|
||||
}
|
||||
|
||||
const listSqlQueryLog = (reset) => {
|
||||
if (reset === true) {
|
||||
resetPageInfo()
|
||||
}
|
||||
|
||||
let loading = ElementPlus.ElLoading.service({
|
||||
fullscreen: true,
|
||||
lock: true
|
||||
})
|
||||
|
||||
// console.log(data.form)
|
||||
// return
|
||||
let req = {
|
||||
start_date: "",
|
||||
end_date: "",
|
||||
// game_id: (/^\d+$/).test(data.form.game_id) ? Number(data.form.game_id) : -1,
|
||||
query_info: data.form.query_info,
|
||||
page: data.pageInfo.currentPage,
|
||||
limit: data.pageInfo.pageSize,
|
||||
admin_name: data.form.admin_name,
|
||||
}
|
||||
|
||||
if (data.form.date) {
|
||||
req.start_date = data.form.date[0]
|
||||
req.end_date = data.form.date[1]
|
||||
}
|
||||
|
||||
|
||||
const requestOptions = {
|
||||
method: 'POST', // 或 'POST'、'PUT' 等
|
||||
headers: {
|
||||
'Content-Type': 'application/json', // 指定请求类型为 JSON
|
||||
// 可以添加其他请求头,比如认证信息等
|
||||
},
|
||||
// 如果是 POST 或 PUT 请求,可以在 body 中添加请求数据
|
||||
|
||||
|
||||
body: JSON.stringify(req)
|
||||
};
|
||||
|
||||
// 使用 fetch 发起请求
|
||||
fetch("/ListSqlQueryLog", requestOptions)
|
||||
.then(response => {
|
||||
// 检查请求是否成功(状态码为 200 到 299)
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
// 将响应解析为 JSON
|
||||
return response.json();
|
||||
})
|
||||
.then(res => {
|
||||
// 处理返回的 JSON 数据
|
||||
if (res.Code != 0) {
|
||||
console.log(res);
|
||||
alert(res.Message)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
data.tableData = res.Data.List
|
||||
data.pageInfo.totalCount = res.Data.TotalCount
|
||||
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
// 处理错误
|
||||
console.error('Fetch error:', error);
|
||||
alert(error)
|
||||
}).finally(
|
||||
() => {
|
||||
loading.close()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(data),
|
||||
listSqlQueryLog, showQuerySql, copText
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
vm.use(ElementPlus, {locale: ElementPlusLocaleZhCn})
|
||||
vm.use(VXETable)
|
||||
vm.mount("#app")
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue