自定义回调处理是最常见的接口开发实现,我们往往只需要对接口中的部分实现进行替换修改,以在原有的实现逻辑中注入自定义的逻辑实现。我们来看一个自定义回调处理的示例,我们需要将所有执行的SQL语句记录到monitor表中,以方便于进行SQL审计。

    为简化示例编写,我们这里实现了一个自定义的MySQL驱动,该驱动继承于gdb模块中已经实现的DriverMysql,并按照需要修改覆盖相应的接口方法。由于所有的SQL语句执行必定会通过DoQuery或者DoExec接口,因此我们在自定义的驱动中实现并覆盖这两个接口方法即可。

    1. package driver
    2. import (
    3. "context"
    4. "database/sql"
    5. "github.com/gogf/gf/v2/database/gdb"
    6. "github.com/gogf/gf/v2/os/gtime"
    7. )
    8. // MyDriver is a custom database driver, which is used for testing only.
    9. // For simplifying the unit testing case purpose, MyDriver struct inherits the mysql driver
    10. // gdb.DriverMysql and overwrites its functions DoQuery and DoExec.
    11. // So if there's any sql execution, it goes through MyDriver.DoQuery/MyDriver.DoExec firstly
    12. // and then gdb.DriverMysql.DoQuery/gdb.DriverMysql.DoExec.
    13. // You can call it sql "HOOK" or "HiJack" as your will.
    14. type MyDriver struct {
    15. *gdb.DriverMysql
    16. }
    17. var (
    18. // customDriverName is my driver name, which is used for registering.
    19. customDriverName = "MyDriver"
    20. )
    21. func init() {
    22. // It here registers my custom driver in package initialization function "init".
    23. // You can later use this type in the database configuration.
    24. if err := gdb.Register(customDriverName, &MyDriver{}); err != nil {
    25. panic(err)
    26. }
    27. }
    28. // New creates and returns a database object for mysql.
    29. // It implements the interface of gdb.Driver for extra database driver installation.
    30. func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
    31. return &MyDriver{
    32. &gdb.DriverMysql{
    33. Core: core,
    34. },
    35. }, nil
    36. }
    37. // DoQuery commits the sql string and its arguments to underlying driver
    38. // through given link object and returns the execution result.
    39. func (d *MyDriver) DoQuery(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (rows *sql.Rows, err error) {
    40. tsMilli := gtime.TimestampMilli()
    41. rows, err = d.DriverMysql.DoQuery(ctx, link, sql, args...)
    42. link.Exec(
    43. "INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)",
    44. gdb.FormatSqlWithArgs(sql, args),
    45. gtime.TimestampMilli()-tsMilli,
    46. gtime.Now(),
    47. err,
    48. )
    49. return
    50. }
    51. // DoExec commits the query string and its arguments to underlying driver
    52. // through given link object and returns the execution result.
    53. func (d *MyDriver) DoExec(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) {
    54. tsMilli := gtime.TimestampMilli()
    55. result, err = d.DriverMysql.DoExec(ctx, link, sql, args...)
    56. link.Exec(
    57. "INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)",
    58. gdb.FormatSqlWithArgs(sql, args),
    59. gtime.TimestampMilli()-tsMilli,
    60. gtime.Now(),
    61. err,
    62. )
    63. return
    64. }
    65. // DoFilter is a hook function, which filters the sql and its arguments before it's committed to underlying driver.
    66. // The parameter `link` specifies the current database connection operation object. You can modify the sql
    67. // string `sql` and its arguments `args` as you wish before they're committed to driver.
    68. func (d *MyDriver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
    69. //Filter sql Step1
    70. sql_new := gstr.Replace(sql, "\n", "")
    71. //Filter sql Step2
    72. sql_new = gstr.Replace(sql_new, "\t", "")
    73. //... Filter what you want ...
    74. //Filter args Step1
    75. for _, v := range args {
    76. switch v.(type) {
    77. case gdb.Map:
    78. //Do it what you wan
    79. case string:
    80. //Do it what you wan
    81. }
    82. }
    83. return sql_new, args, nil
    84. }
    85. func (d *MyDriver) ConvertDataForRecord(ctx context.Context, data interface{}) map[string]interface{} {
    86. //this hook is convert data to map[string]interface{}
    87. result := make(map[string]interface{}, 0)
    88. //like this
    89. switch data.(type) {
    90. case gdb.Map:
    91. result = gconv.Map(data)
    92. case gdb.List:
    93. for k, v := range data.(gdb.List) {
    94. result[strconv.Itoa(k)] = gconv.Map(v)
    95. }
    96. //case other type,do it what you want
    97. }
    98. return result
    99. }

    我们看到,这里在包初始化方法init中使用了gdb.Register("MyDriver", &MyDriver{})来注册了了一个自定义名称的驱动。我们也可以通过gdb.Register("mysql", &MyDriver{})来覆盖已有的框架mysql驱动为自己的驱动。

    驱动名称mysql为框架默认的DriverMysql驱动的名称。

    由于这里我们使用了一个新的驱动名称MyDriver,因此在gdb配置中的type数据库类型时,需要填写该驱动名称。以下是一个使用配置的示例:

    1. database:
    2. default:
    3. - link: "MyDriver:root:12345678@tcp(127.0.0.1:3306)/user"