本文主要研究一下gorm的Model

Model

gorm.io/gorm@v1.20.10/model.go// Model a basic GoLang struct which includes the following fields: ID, CreatedAt, UpdatedAt, DeletedAt

// It may be embedded into your model or you may build your own model without it

// type User struct {

// gorm.Model

// }

type Model struct {

ID uint `gorm:"primarykey"`

CreatedAt time.Time

UpdatedAt time.Time

DeletedAt DeletedAt `gorm:"index"`

}Model定义了ID、CreatedAt、UpdatedAt、DeletedAt属性

ParseField

gorm.io/gorm@v1.20.10/schema/field.gofunc (schema *Schema) ParseField(fieldStruct reflect.StructField) *Field {

var err error

field := &Field{

Name: fieldStruct.Name,

BindNames: []string{fieldStruct.Name},

FieldType: fieldStruct.Type,

IndirectFieldType: fieldStruct.Type,

StructField: fieldStruct,

Creatable: true,

Updatable: true,

Readable: true,

Tag: fieldStruct.Tag,

TagSettings: ParseTagSetting(fieldStruct.Tag.Get("gorm"), ";"),

Schema: schema,

AutoIncrementIncrement: 1,

}

for field.IndirectFieldType.Kind() == reflect.Ptr {

field.IndirectFieldType = field.IndirectFieldType.Elem()

}

fieldValue := reflect.New(field.IndirectFieldType)

// if field is valuer, used its value or first fields as data type

valuer, isValuer := fieldValue.Interface().(driver.Valuer)

//......

field.GORMDataType = field.DataType

if dataTyper, ok := fieldValue.Interface().(GormDataTypeInterface); ok {

field.DataType = DataType(dataTyper.GormDataType())

}

if v, ok := field.TagSettings["AUTOCREATETIME"]; ok || (field.Name == "CreatedAt" && (field.DataType == Time || field.DataType == Int || field.DataType == Uint)) {

if strings.ToUpper(v) == "NANO" {

field.AutoCreateTime = UnixNanosecond

} else if strings.ToUpper(v) == "MILLI" {

field.AutoCreateTime = UnixMillisecond

} else {

field.AutoCreateTime = UnixSecond

}

}

if v, ok := field.TagSettings["AUTOUPDATETIME"]; ok || (field.Name == "UpdatedAt" && (field.DataType == Time || field.DataType == Int || field.DataType == Uint)) {

if strings.ToUpper(v) == "NANO" {

field.AutoUpdateTime = UnixNanosecond

} else if strings.ToUpper(v) == "MILLI" {

field.AutoUpdateTime = UnixMillisecond

} else {

field.AutoUpdateTime = UnixSecond

}

}

//......

return field

}ParseField方法会解析field的属性,如果field的name为CreatedAt或者UpdatedAt,且dataType为Time、Int、Unit或者tag标注了AUTOCREATETIME或者AUTOUPDATETIME,则会设置field.AutoCreateTime或者field.AutoUpdateTime

TimeType

gorm.io/gorm@v1.20.10/schema/field.gotype TimeType int64

const (

UnixSecond TimeType = 1

UnixMillisecond TimeType = 2

UnixNanosecond TimeType = 3

)field.AutoCreateTime、AutoUpdateTime属性为TimeType类型,该类型有UnixSecond、UnixMillisecond、UnixNanosecond三种类型

ConvertToCreateValues

gorm.io/gorm@v1.20.10/callbacks/create.go// ConvertToCreateValues convert to create values

func ConvertToCreateValues(stmt *gorm.Statement) (values clause.Values) {

switch value := stmt.Dest.(type) {

case map[string]interface{}:

values = ConvertMapToValuesForCreate(stmt, value)

case *map[string]interface{}:

values = ConvertMapToValuesForCreate(stmt, *value)

case []map[string]interface{}:

values = ConvertSliceOfMapToValuesForCreate(stmt, value)

case *[]map[string]interface{}:

values = ConvertSliceOfMapToValuesForCreate(stmt, *value)

default:

var (

selectColumns, restricted = stmt.SelectAndOmitColumns(true, false)

curTime = stmt.DB.NowFunc()

isZero bool

)

values = clause.Values{Columns: make([]clause.Column, 0, len(stmt.Schema.DBNames))}

for _, db := range stmt.Schema.DBNames {

if field := stmt.Schema.FieldsByDBName[db]; !field.HasDefaultValue || field.DefaultValueInterface != nil {

if v, ok := selectColumns[db]; (ok && v) || (!ok && (!restricted || field.AutoCreateTime > 0 || field.AutoUpdateTime > 0)) {

values.Columns = append(values.Columns, clause.Column{Name: db})

}

}

}

switch stmt.ReflectValue.Kind() {

case reflect.Slice, reflect.Array:

stmt.SQL.Grow(stmt.ReflectValue.Len() * 18)

values.Values = make([][]interface{}, stmt.ReflectValue.Len())

defaultValueFieldsHavingValue := map[*schema.Field][]interface{}{}

if stmt.ReflectValue.Len() == 0 {

stmt.AddError(gorm.ErrEmptySlice)

return

}

for i := 0; i < stmt.ReflectValue.Len(); i++ {

rv := reflect.Indirect(stmt.ReflectValue.Index(i))

if !rv.IsValid() {

stmt.AddError(fmt.Errorf("slice data #%v is invalid: %w", i, gorm.ErrInvalidData))

return

}

values.Values[i] = make([]interface{}, len(values.Columns))

for idx, column := range values.Columns {

field := stmt.Schema.FieldsByDBName[column.Name]

if values.Values[i][idx], isZero = field.ValueOf(rv); isZero {

if field.DefaultValueInterface != nil {

values.Values[i][idx] = field.DefaultValueInterface

field.Set(rv, field.DefaultValueInterface)

} else if field.AutoCreateTime > 0 || field.AutoUpdateTime > 0 {

field.Set(rv, curTime)

values.Values[i][idx], _ = field.ValueOf(rv)

}

} else if field.AutoUpdateTime > 0 {

if _, ok := stmt.DB.InstanceGet("gorm:update_track_time"); ok {

field.Set(rv, curTime)

values.Values[0][idx], _ = field.ValueOf(rv)

}

}

}

for _, field := range stmt.Schema.FieldsWithDefaultDBValue {

if v, ok := selectColumns[field.DBName]; (ok && v) || (!ok && !restricted) {

if v, isZero := field.ValueOf(rv); !isZero {

if len(defaultValueFieldsHavingValue[field]) == 0 {

defaultValueFieldsHavingValue[field] = make([]interface{}, stmt.ReflectValue.Len())

}

defaultValueFieldsHavingValue[field][i] = v

}

}

}

}

for field, vs := range defaultValueFieldsHavingValue {

values.Columns = append(values.Columns, clause.Column{Name: field.DBName})

for idx := range values.Values {

if vs[idx] == nil {

values.Values[idx] = append(values.Values[idx], stmt.Dialector.DefaultValueOf(field))

} else {

values.Values[idx] = append(values.Values[idx], vs[idx])

}

}

}

case reflect.Struct:

values.Values = [][]interface{}{make([]interface{}, len(values.Columns))}

for idx, column := range values.Columns {

field := stmt.Schema.FieldsByDBName[column.Name]

if values.Values[0][idx], isZero = field.ValueOf(stmt.ReflectValue); isZero {

if field.DefaultValueInterface != nil {

values.Values[0][idx] = field.DefaultValueInterface

field.Set(stmt.ReflectValue, field.DefaultValueInterface)

} else if field.AutoCreateTime > 0 || field.AutoUpdateTime > 0 {

field.Set(stmt.ReflectValue, curTime)

values.Values[0][idx], _ = field.ValueOf(stmt.ReflectValue)

}

}

}

for _, field := range stmt.Schema.FieldsWithDefaultDBValue {

if v, ok := selectColumns[field.DBName]; (ok && v) || (!ok && !restricted) {

if v, isZero := field.ValueOf(stmt.ReflectValue); !isZero {

values.Columns = append(values.Columns, clause.Column{Name: field.DBName})

values.Values[0] = append(values.Values[0], v)

}

}

}

default:

stmt.AddError(gorm.ErrInvalidData)

}

}

if c, ok := stmt.Clauses["ON CONFLICT"]; ok {

if onConflict, _ := c.Expression.(clause.OnConflict); onConflict.UpdateAll {

if stmt.Schema != nil && len(values.Columns) > 1 {

columns := make([]string, 0, len(values.Columns)-1)

for _, column := range values.Columns {

if field := stmt.Schema.LookUpField(column.Name); field != nil {

if !field.PrimaryKey && (!field.HasDefaultValue || field.DefaultValueInterface != nil) && field.AutoCreateTime == 0 {

columns = append(columns, column.Name)

}

}

}

onConflict := clause.OnConflict{

Columns: make([]clause.Column, len(stmt.Schema.PrimaryFieldDBNames)),

DoUpdates: clause.AssignmentColumns(columns),

}

for idx, field := range stmt.Schema.PrimaryFields {

onConflict.Columns[idx] = clause.Column{Name: field.DBName}

}

stmt.AddClause(onConflict)

}

}

}

return values

}ConvertToCreateValues从stmt.DB.NowFunc()获取curTime,然后对于field.AutoCreateTime或者field.AutoUpdateTime大于0的,会设置curTime

setupValuerAndSetter

gorm.io/gorm@v1.20.10/schema/field.go// create valuer, setter when parse struct

func (field *Field) setupValuerAndSetter() {

//......

// Set

switch field.FieldType.Kind() {

case reflect.Bool:

field.Set = func(value reflect.Value, v interface{}) error {

switch data := v.(type) {

case bool:

field.ReflectValueOf(value).SetBool(data)

case *bool:

if data != nil {

field.ReflectValueOf(value).SetBool(*data)

} else {

field.ReflectValueOf(value).SetBool(false)

}

case int64:

if data > 0 {

field.ReflectValueOf(value).SetBool(true)

} else {

field.ReflectValueOf(value).SetBool(false)

}

case string:

b, _ := strconv.ParseBool(data)

field.ReflectValueOf(value).SetBool(b)

default:

return fallbackSetter(value, v, field.Set)

}

return nil

}

case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:

field.Set = func(value reflect.Value, v interface{}) (err error) {

switch data := v.(type) {

case int64:

field.ReflectValueOf(value).SetInt(data)

case int:

field.ReflectValueOf(value).SetInt(int64(data))

case int8:

field.ReflectValueOf(value).SetInt(int64(data))

case int16:

field.ReflectValueOf(value).SetInt(int64(data))

case int32:

field.ReflectValueOf(value).SetInt(int64(data))

case uint:

field.ReflectValueOf(value).SetInt(int64(data))

case uint8:

field.ReflectValueOf(value).SetInt(int64(data))

case uint16:

field.ReflectValueOf(value).SetInt(int64(data))

case uint32:

field.ReflectValueOf(value).SetInt(int64(data))

case uint64:

field.ReflectValueOf(value).SetInt(int64(data))

case float32:

field.ReflectValueOf(value).SetInt(int64(data))

case float64:

field.ReflectValueOf(value).SetInt(int64(data))

case []byte:

return field.Set(value, string(data))

case string:

if i, err := strconv.ParseInt(data, 0, 64); err == nil {

field.ReflectValueOf(value).SetInt(i)

} else {

return err

}

case time.Time:

if field.AutoCreateTime == UnixNanosecond || field.AutoUpdateTime == UnixNanosecond {

field.ReflectValueOf(value).SetInt(data.UnixNano())

} else if field.AutoCreateTime == UnixMillisecond || field.AutoUpdateTime == UnixMillisecond {

field.ReflectValueOf(value).SetInt(data.UnixNano() / 1e6)

} else {

field.ReflectValueOf(value).SetInt(data.Unix())

}

case *time.Time:

if data != nil {

if field.AutoCreateTime == UnixNanosecond || field.AutoUpdateTime == UnixNanosecond {

field.ReflectValueOf(value).SetInt(data.UnixNano())

} else if field.AutoCreateTime == UnixMillisecond || field.AutoUpdateTime == UnixMillisecond {

field.ReflectValueOf(value).SetInt(data.UnixNano() / 1e6)

} else {

field.ReflectValueOf(value).SetInt(data.Unix())

}

} else {

field.ReflectValueOf(value).SetInt(0)

}

default:

return fallbackSetter(value, v, field.Set)

}

return err

}

case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:

field.Set = func(value reflect.Value, v interface{}) (err error) {

switch data := v.(type) {

case uint64:

field.ReflectValueOf(value).SetUint(data)

case uint:

field.ReflectValueOf(value).SetUint(uint64(data))

case uint8:

field.ReflectValueOf(value).SetUint(uint64(data))

case uint16:

field.ReflectValueOf(value).SetUint(uint64(data))

case uint32:

field.ReflectValueOf(value).SetUint(uint64(data))

case int64:

field.ReflectValueOf(value).SetUint(uint64(data))

case int:

field.ReflectValueOf(value).SetUint(uint64(data))

case int8:

field.ReflectValueOf(value).SetUint(uint64(data))

case int16:

field.ReflectValueOf(value).SetUint(uint64(data))

case int32:

field.ReflectValueOf(value).SetUint(uint64(data))

case float32:

field.ReflectValueOf(value).SetUint(uint64(data))

case float64:

field.ReflectValueOf(value).SetUint(uint64(data))

case []byte:

return field.Set(value, string(data))

case time.Time:

if field.AutoCreateTime == UnixNanosecond || field.AutoUpdateTime == UnixNanosecond {

field.ReflectValueOf(value).SetUint(uint64(data.UnixNano()))

} else if field.AutoCreateTime == UnixMillisecond || field.AutoUpdateTime == UnixMillisecond {

field.ReflectValueOf(value).SetUint(uint64(data.UnixNano() / 1e6))

} else {

field.ReflectValueOf(value).SetUint(uint64(data.Unix()))

}

case string:

if i, err := strconv.ParseUint(data, 0, 64); err == nil {

field.ReflectValueOf(value).SetUint(i)

} else {

return err

}

default:

return fallbackSetter(value, v, field.Set)

}

return err

}

case reflect.Float32, reflect.Float64:

field.Set = func(value reflect.Value, v interface{}) (err error) {

switch data := v.(type) {

case float64:

field.ReflectValueOf(value).SetFloat(data)

case float32:

field.ReflectValueOf(value).SetFloat(float64(data))

case int64:

field.ReflectValueOf(value).SetFloat(float64(data))

case int:

field.ReflectValueOf(value).SetFloat(float64(data))

case int8:

field.ReflectValueOf(value).SetFloat(float64(data))

case int16:

field.ReflectValueOf(value).SetFloat(float64(data))

case int32:

field.ReflectValueOf(value).SetFloat(float64(data))

case uint:

field.ReflectValueOf(value).SetFloat(float64(data))

case uint8:

field.ReflectValueOf(value).SetFloat(float64(data))

case uint16:

field.ReflectValueOf(value).SetFloat(float64(data))

case uint32:

field.ReflectValueOf(value).SetFloat(float64(data))

case uint64:

field.ReflectValueOf(value).SetFloat(float64(data))

case []byte:

return field.Set(value, string(data))

case string:

if i, err := strconv.ParseFloat(data, 64); err == nil {

field.ReflectValueOf(value).SetFloat(i)

} else {

return err

}

default:

return fallbackSetter(value, v, field.Set)

}

return err

}

case reflect.String:

field.Set = func(value reflect.Value, v interface{}) (err error) {

switch data := v.(type) {

case string:

field.ReflectValueOf(value).SetString(data)

case []byte:

field.ReflectValueOf(value).SetString(string(data))

case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:

field.ReflectValueOf(value).SetString(utils.ToString(data))

case float64, float32:

field.ReflectValueOf(value).SetString(fmt.Sprintf("%."+strconv.Itoa(field.Precision)+"f", data))

default:

return fallbackSetter(value, v, field.Set)

}

return err

}

default:

fieldValue := reflect.New(field.FieldType)

switch fieldValue.Elem().Interface().(type) {

case time.Time:

field.Set = func(value reflect.Value, v interface{}) error {

switch data := v.(type) {

case time.Time:

field.ReflectValueOf(value).Set(reflect.ValueOf(v))

case *time.Time:

if data != nil {

field.ReflectValueOf(value).Set(reflect.ValueOf(data).Elem())

} else {

field.ReflectValueOf(value).Set(reflect.ValueOf(time.Time{}))

}

case string:

if t, err := now.Parse(data); err == nil {

field.ReflectValueOf(value).Set(reflect.ValueOf(t))

} else {

return fmt.Errorf("failed to set string %v to time.Time field %v, failed to parse it as time, got error %v", v, field.Name, err)

}

default:

return fallbackSetter(value, v, field.Set)

}

return nil

}

case *time.Time:

field.Set = func(value reflect.Value, v interface{}) error {

switch data := v.(type) {

case time.Time:

fieldValue := field.ReflectValueOf(value)

if fieldValue.IsNil() {

fieldValue.Set(reflect.New(field.FieldType.Elem()))

}

fieldValue.Elem().Set(reflect.ValueOf(v))

case *time.Time:

field.ReflectValueOf(value).Set(reflect.ValueOf(v))

case string:

if t, err := now.Parse(data); err == nil {

fieldValue := field.ReflectValueOf(value)

if fieldValue.IsNil() {

if v == "" {

return nil

}

fieldValue.Set(reflect.New(field.FieldType.Elem()))

}

fieldValue.Elem().Set(reflect.ValueOf(t))

} else {

return fmt.Errorf("failed to set string %v to time.Time field %v, failed to parse it as time, got error %v", v, field.Name, err)

}

default:

return fallbackSetter(value, v, field.Set)

}

return nil

}

default:

if _, ok := fieldValue.Elem().Interface().(sql.Scanner); ok {

// pointer scanner

field.Set = func(value reflect.Value, v interface{}) (err error) {

reflectV := reflect.ValueOf(v)

if !reflectV.IsValid() {

field.ReflectValueOf(value).Set(reflect.New(field.FieldType).Elem())

} else if reflectV.Type().AssignableTo(field.FieldType) {

field.ReflectValueOf(value).Set(reflectV)

} else if reflectV.Kind() == reflect.Ptr {

if reflectV.IsNil() || !reflectV.IsValid() {

field.ReflectValueOf(value).Set(reflect.New(field.FieldType).Elem())

} else {

return field.Set(value, reflectV.Elem().Interface())

}

} else {

fieldValue := field.ReflectValueOf(value)

if fieldValue.IsNil() {

fieldValue.Set(reflect.New(field.FieldType.Elem()))

}

if valuer, ok := v.(driver.Valuer); ok {

v, _ = valuer.Value()

}

err = fieldValue.Interface().(sql.Scanner).Scan(v)

}

return

}

} else if _, ok := fieldValue.Interface().(sql.Scanner); ok {

// struct scanner

field.Set = func(value reflect.Value, v interface{}) (err error) {

reflectV := reflect.ValueOf(v)

if !reflectV.IsValid() {

field.ReflectValueOf(value).Set(reflect.New(field.FieldType).Elem())

} else if reflectV.Type().AssignableTo(field.FieldType) {

field.ReflectValueOf(value).Set(reflectV)

} else if reflectV.Kind() == reflect.Ptr {

if reflectV.IsNil() || !reflectV.IsValid() {

field.ReflectValueOf(value).Set(reflect.New(field.FieldType).Elem())

} else {

return field.Set(value, reflectV.Elem().Interface())

}

} else {

if valuer, ok := v.(driver.Valuer); ok {

v, _ = valuer.Value()

}

err = field.ReflectValueOf(value).Addr().Interface().(sql.Scanner).Scan(v)

}

return

}

} else {

field.Set = func(value reflect.Value, v interface{}) (err error) {

return fallbackSetter(value, v, field.Set)

}

}

}

}

}setupValuerAndSetter方法针对time.Time或*time.Time类型的setter会根据TimeType再做时间精度处理

实例type Product struct {

gorm.Model

Code string

Price uint

}Product内嵌了gorm.Model,内置了ID、CreatedAt、UpdatedAt、DeletedAt属性,同时Create的时候会自动设置CreatedAt、UpdatedAt,Update的时候会自动更新UpdatedAt

小结

gorm定义了ID、CreatedAt、UpdatedAt、DeletedAt属性;其中Create的时候会自动设置CreatedAt、UpdatedAt,Update的时候会自动更新UpdatedAt;CreatedAt、UpdatedAt支持 UnixSecond、UnixMillisecond、UnixNanosecond三种时间精度。

doc查看原文

beego mysql 存储过程_ioioj5相关推荐

  1. 常用MySQL函数存储过程_解析MySQL存储过程、常用函数代码

    mysql存储过程的概念: 存储在数据库当中可以执行特定工作(查询和更新)的一组SQL代码的程序段. mysql函数的概念: 函数是完成特定功能的SQL语句,函数分为内置函数和自定义函数(user-d ...

  2. MySQL 存储过程传参之in, out, inout 参数用法

    存储过程传参:存储过程的括号里,可以声明参数. 语法是 create procedure p([in/out/inout] 参数名  参数类型 ..) in :给参数传入值,定义的参数就得到了值 ou ...

  3. mysql中leave和_MySQL数据库之Mysql存储过程使用LEAVE实现MSSQL存储过程中return语法

    本文主要向大家介绍了MySQL数据库之Mysql存储过程使用LEAVE实现MSSQL存储过程中return语法 ,通过具体的内容向大家展现,希望对大家学习MySQL数据库有所帮助. DELIMITER ...

  4. MySQL存储过程详解

    1.      存储过程简介   我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储 ...

  5. mysql存储过程语法及实例

    存储过程如同一门程序设计语言,同样包含了数据类型.流程控制.输入和输出和它自己的函数库. --------------------基本语法-------------------- 一.创建存储过程 c ...

  6. mycat mysql 存储过程_MyCat 学习笔记 第十三篇.数据分片 之 通过HINT执行存储过程...

    1 环境说明 VM 模拟3台MYSQL 5.6 服务器 VM1 192.168.31.187:3307 VM2 192.168.31.212:3307 VM3 192.168.31.150:  330 ...

  7. mysql 存储过程 格式化_转 mysql 存储过程初探

    https://www.cnblogs.com/qmfsun/p/4838032.html MySQL命令执行sql文件的两种方法 https://www.cnblogs.com/mark-chan/ ...

  8. hibernate mysql 存储过程_hibernate调用mysql存储过程

    hibernate调用mysql存储过程 在最近的项目中,碰到一小段数据库数据分析的程序,需要结合多张表联合查询或涉及到子查询,项目主要采用的java ee开发,使用了hibernate框架,由于这些 ...

  9. mybatis mysql 存储过程_Mysql 存储过程+Mybatis调用实现插入操作例子 | 学步园

    一. 简介:网上关于存储过程的使用有很多的例子,但大多实现的功能比较简单,由于本人对SQL语句还不是很熟悉,更别说存储过程了,所以在实现该例子的时候遇到了很多问题,现在拿给大家来分享. 二. 在本例子 ...

最新文章

  1. 数据库:数据库的连接池原理及实现
  2. 【模板】吉老师线段树
  3. 鲲鹏支持java_#化鲲为鹏,我有话说# 鲲鹏弹性云服务器配置 JAVA 环境
  4. (24)Vue.js组件—组件注册
  5. 花生油和菜籽油哪个更健康?
  6. jsp引用带参数的js文件,例如 script src=a.js?ctxpath=${base
  7. Apriori算法+python实现
  8. 中国知网 博硕士论文 PDF下载 及书签自动生成
  9. kali rpm 安装方法_解决kali linux中无法使用RPM命令
  10. springboot引入validation
  11. 网站上传服务器浏览器打不开,网站发布或自己上传程序后,网站打不开的原因有哪些?...
  12. 计算机软件需要加书名号吗,软件著作权名称需要加书名号吗-举例说明一个软件的名称放在句子里应不应该用书名号,为什么?...
  13. html页面国际化之谷歌翻译js实践,支持通过判断浏览器语言自动将中文翻译成英文
  14. 关闭WIN7交互式服务检测提示
  15. 计算机以及dos窗口常用快捷键
  16. Python制作微信自动回复机器人,打游戏时自动回复女朋友消息
  17. C++ map下标访问的问题
  18. 中山大学数学科学与计算机科学,中山大学数学与计算科学学院导师介绍:邹青松...
  19. 2021年危险化学品经营单位安全管理人员考试资料及危险化学品经营单位安全管理人员新版试题
  20. Java支付宝沙箱环境支付,官方Demo远程调试【内网穿透】

热门文章

  1. 游戏外挂的基本编写原理
  2. C#中串口通信编程 收藏
  3. SQL Server数据库管理常用SQL和T-SQL语句
  4. 16道嵌入式C语言面试题(经典) 预处理器(Preprocessor)
  5. bash-shell中使用的特殊字符总结
  6. linux中GDB详细使用手册
  7. BugkuCTF-Misc:想蹭网先解开密码
  8. 关于Spring 声明式事务处理时,throws exception不回滚的问题
  9. sendmsg recvmsg函数
  10. C语言再学习 -- 关键字void