main
kzkzzzz 2024-02-04 18:17:06 +08:00
commit ce0b3c0c53
24 changed files with 75549 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.idea
tmp
.linux
.exe
.mac
.log
.vscode
*.db

12
app/conf/config.go Normal file
View File

@ -0,0 +1,12 @@
package conf
var App = &Config{}
type Config struct {
ServerVersion string
RemoteDb string
ServerAddr string
WebAddr string
SaveLog bool
LogLevel string
}

62
app/db/db.go Normal file
View File

@ -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)
}
}

195
app/mysqlserver/conn.go Normal file
View File

@ -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)
}

320
app/mysqlserver/constant.go Normal file
View File

@ -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
)

View File

@ -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
}

227
app/mysqlserver/helper.go Normal file
View File

@ -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
}

View File

@ -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)
}

55
app/mysqlserver/packet.go Normal file
View File

@ -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
}

View File

@ -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
}
}

209
app/webserver/web.go Normal file
View File

@ -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
}

243
app/zlog/zlog.go Normal file
View File

@ -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()
}

57
go.mod Normal file
View File

@ -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
)

143
go.sum Normal file
View File

@ -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=

63
main.go Normal file
View File

@ -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

View File

@ -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

View File

@ -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;
}));

15505
resource/js/vue.global.js Normal file

File diff suppressed because it is too large Load Diff

1
resource/js/vxe-table.js Normal file

File diff suppressed because one or more lines are too long

6
resource/js/xe-utils.js Normal file

File diff suppressed because one or more lines are too long

375
resource/view/index.tmpl Normal file
View File

@ -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>