Go ORM框架 - GORM 踩坑指南


本站和网页 https://www.bbsmax.com/A/QV5Zobrndy/ 的作者无关,不对其内容负责。快照谨为网络故障时之索引,不代表被搜索网站的即时页面。

Go ORM框架 - GORM 踩坑指南
首页
Python
Java
PHP
IOS
Andorid
NodeJS
JavaScript
HTML5
Go ORM框架 - GORM 踩坑指南
rickiyang
2022-10-22
原文
今天聊聊目前业界使用比较多的 ORM 框架:GORM。GORM 相关的文档原作者已经写得非常的详细,具体可以看这里,这一篇主要做一些 GORM 使用过程中关键功能的介绍,GORM 约定的一些配置信息说明,防止大家在使用过程中踩坑。
以下示例代码都可以在 Github : gorm-demo 中找到。
GORM 官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server。
连接 MySQL 的示例:
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
func main() {
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
MySQl 驱动程序提供了一些高级配置可以在初始化过程中使用,例如:
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name
DefaultStringSize: 256, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}), &gorm.Config{})
注意到 gorm.Open(dialector Dialector, opts ...Option) 函数的第二个参数是接收一个 gorm.Config{} 类型的参数,这里就是 gorm 在数据库建立连接后框架本身做的一些默认配置,请注意这里如果没有配置好,后面你的数据库操作将会很痛苦!
GORM 提供的配置可以在初始化时使用:
type Config struct {
SkipDefaultTransaction bool
NamingStrategy schema.Namer
Logger logger.Interface
NowFunc func() time.Time
DryRun bool
PrepareStmt bool
DisableNestedTransaction bool
AllowGlobalUpdate bool
DisableAutomaticPing bool
DisableForeignKeyConstraintWhenMigrating bool
这些参数我们一个一个来说:
SkipDefaultTransaction
跳过默认开启事务模式。为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,可以在初始化时禁用它。
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
SkipDefaultTransaction: true,
})
NamingStrategy
表名称的命名策略,下面会说。GORM 允许用户通过覆盖默认的NamingStrategy来更改命名约定,这需要实现接口 Namer:
type Namer interface {
TableName(table string) string
ColumnName(table, column string) string
JoinTableName(table string) string
RelationshipFKName(Relationship) string
CheckerName(table, column string) string
IndexName(table, column string) string
默认 NamingStrategy 也提供了几个选项,如:
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: "t_", // 表名前缀,`User`表为`t_users`
SingularTable: true, // 使用单数表名,启用该选项后,`User` 表将是`user`
NameReplacer: strings.NewReplacer("CID", "Cid"), // 在转为数据库名称之前,使用NameReplacer更改结构/字段名称。
},
})
一般来说这里是一定要配置 SingularTable: true 这一项的。
Logger
允许通过覆盖此选项更改 GORM 的默认 logger。
NowFunc
更改创建时间使用的函数:
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
NowFunc: func() time.Time {
return time.Now().Local()
},
})
DryRun
生成 SQL 但不执行,可以用于准备或测试生成的 SQL:
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
DryRun: false,
})
PrepareStmt
PreparedStmt 在执行任何 SQL 时都会创建一个 prepared statement 并将其缓存,以提高后续的效率:
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
PrepareStmt: false,
})
GORM 约定配置
使用别人的框架就要受制别人的约束,在 GORM 中有很多的约定,如果你没有遵循这些约定可能你认为正常的代码跑起来会发生意想不到的问题。
模型定义
默认情况下,GORM 会使用 ID 作为表的主键。
type User struct {
ID string // 默认情况下,名为 `ID` 的字段会作为表的主键
Name string
如果你当前的表主键不是 id 字段,那么你可以通过 primaryKey标签将其它字段设为主键:
// 将 `UUID` 设为主键
type Animal struct {
ID int64
UUID string `gorm:"primaryKey"`
Name string
Age int64
如果你的表采用了复合主键,那也没关系:
type Product struct {
ID string `gorm:"primaryKey"`
LanguageCode string `gorm:"primaryKey"`
Code string
Name string
注意:默认情况下,整型 PrioritizedPrimaryField 启用了 AutoIncrement,要禁用它,您需要为整型字段关闭 autoIncrement:
type Product struct {
CategoryID uint64 `gorm:"primaryKey;autoIncrement:false"`
TypeID uint64 `gorm:"primaryKey;autoIncrement:false"`
GORM 标签
GORM 通过在 struct 上定义自定义的 gorm 标签来实现自动化创建表的功能:
type User struct {
Name string `gorm:"size:255"` //string默认长度255,size重设长度
Age int `gorm:"column:my_age"` //设置列名为my_age
Num int `gorm:"AUTO_INCREMENT"` //自增
IgnoreMe int `gorm:"-"` // 忽略字段
Email string `gorm:"type:varchar(100);unique_index"` //type设置sql类型,unique_index为该列设置唯一索引
Address string `gorm:"not null;unique"` //非空
No string `gorm:"index:idx_no"` // 创建索引并命名,如果有其他同名索引,则创建组合索引
Remark string `gorm:"default:''"` //默认值
定义完这些标签之后,你可以使用 AutoMigrate 在 MySQL 建立连接之后创建表:
func main() {
db, err := gorm.Open("mysql", "root:123456789@/test_db?charset=utf8&parseTime=True&loc=Local")
if err != nil {
fmt.Println("connect db error: ", err)
db.AutoMigrate(&model.User{})
AutoMigrate 用于自动迁移你的 schema,保持 schema 是最新的。 该 API 会创建表、缺失的外键、约束、列和索引。 如果大小、精度、是否为空可以更改,则 AutoMigrate 会改变列的类型。
出于保护数据的目的,它 不会 删除未使用的列。
默认模型
GORM 定义一个 gorm.Model 结构体,其包括字段 ID、CreatedAt、UpdatedAt、DeletedAt。
// gorm.Model 的定义
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
如果你觉得上面这几个字段名字段名是你想要的,那么你完全可以在你的模型中引入它:
type User struct {
gorm.Model
Id int64 `json:"id"`
Name string `json:"name"`
Age int32 `json:"age"`
Sex int8 `json:"sex"`
Phone string `json:"phone"`
反之如果不是你需要的,就没必要多此一举。
表名
这里是一个很大的坑。GORM 使用结构体名的 蛇形命名 作为表名。对于结构体 User,根据约定,其表名为 users。
当然我们的表名肯定不会是这样设置的,所以为什么作者要采用这种设定实在是难以捉摸。
这里有两种方式去修改表名:第一种就是去掉这个默认设置;第二种就是在保留默认设置的基础上通过重新设定表名来替换。
先说如何通过重新设定表名来替换,可以实现 Tabler 接口来更改默认表名,例如:
type Tabler interface {
TableName() string
// TableName 会将 User 的表名重写为 `user_new_name`
func (User) TableName() string {
return "user_new_name"
通过去掉默认配置上面已经有提,配置 SingularTable: true 这选项即可。
列名覆盖
默认情况下列名遵循普通 struct 的规则:
type User struct {
ID uint // 列名是 `id`
Name string // 列名是 `name`
Birthday time.Time // 列名是 `birthday`
CreatedAt time.Time // 列名是 `created_at`
如果你的列名和字段不匹配的时候,可以通过如下方式重新指定:
type Animal struct {
AnimalID int64 `gorm:"column:beast_id"` // 将列名设为 `beast_id`
Birthday time.Time `gorm:"column:day_of_the_beast"` // 将列名设为 `day_of_the_beast`
Age int64 `gorm:"column:age_of_the_beast"` // 将列名设为 `age_of_the_beast`
日期字段时间类型设置
GORM 约定使用 CreatedAt、UpdatedAt 追踪创建/更新时间。如果你定义了这种字段,GORM 在创建、更新时会自动填充。
如果想要保存 UNIX(毫/纳)秒时间戳而不是 time,只需简单地将 time.Time 修改为 int 即可:
type User struct {
CreatedAt time.Time // 在创建时,如果该字段值为零值,则使用当前时间填充
UpdatedAt int // 在创建时该字段值为零值或者在更新时,使用当前时间戳秒数填充
Updated int64 `gorm:"autoUpdateTime:nano"` // 使用时间戳填纳秒数充更新时间
Updated int64 `gorm:"autoUpdateTime:milli"` // 使用时间戳毫秒数填充更新时间
Created int64 `gorm:"autoCreateTime"` // 使用时间戳秒数填充创建时间
嵌入结构体
对于匿名字段,GORM 会将其字段包含在父结构体中,例如:
type User struct {
gorm.Model
Name string
// 等效于
type User struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string
对于正常的结构体字段,你也可以通过标签 embedded 将其嵌入,例如:
type Author struct {
Name string
Email string
type Blog struct {
ID int
Author Author `gorm:"embedded"`
Upvotes int32
// 等效于
type Blog struct {
ID int64
Name string
Email string
Upvotes int32
CRUD操作
新增相关
单行插入,gorm 会返回插入之后的主键信息:
func InsertOneUser(user model.User) (id int64, err error) {
tx := constants.GVA_DB.Create(&user)
if tx.Error != nil {
constants.GVA_LOG.Error("InsertOne err", zap.Any("err", tx.Error))
return 0, tx.Error
return user.Id, nil
批量插入,批量插入也会同步返回插入之后的主键信息:
func BatchInsertUsers(users []model.User) (ids []int64, err error) {
tx := constants.GVA_DB.CreateInBatches(users, len(users))
if tx.Error != nil {
constants.GVA_LOG.Error("BatchInsert err", zap.Any("err", tx.Error))
return []int64{}, tx.Error
ids = []int64{}
for idx, user := range users {
ids[idx] = user.Id
return ids, nil
插入冲突操作-Upsert:
如果你的表设置了唯一索引的情况下,插入可能会出现主键冲突的情况,MySQL 本身是提供了相关的操作命令 ON DUPLICATE KEY UPDATE,那么对应到 Gorm 中的函数是 Upsert:
// 在冲突时,什么都不做
constants.GVA_DB.Clauses(clause.OnConflict{DoNothing: true}).Create(&user)
// 在`id`冲突时,将列更新为默认值
constants.GVA_DB.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.Assignments(map[string]interface{}{"name": "","age":0, "sex": 1}),
}).Create(&user)
// 在`id`冲突时,将列更新为新值
constants.GVA_DB.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.AssignmentColumns([]string{"name", "age", "sex", "phone"}),
}).Create(&user)
// 在冲突时,更新除主键以外的所有列到新值。
constants.GVA_DB.Clauses(clause.OnConflict{UpdateAll: true,}).Create(&user)
删除相关
根据主键删除:
//根据 id 删除数据
func DeleteUserById(id int64) (err error) {
user := model.User{Id: id}
err = constants.GVA_DB.Delete(&user).Error
if err != nil {
constants.GVA_LOG.Error("DeleteUserById err", zap.Any("err", err))
return err
return nil
根据条件删除:
constants.GVA_DB.Where("sex = ?", 0).Delete(model.User{})
批量删除:
//根据 id 批量删除数据
func BatchDeleteUserByIds(ids []int64) (err error) {
if ids == nil || len(ids) == 0 {
return
//删除方式1
err = constants.GVA_DB.Where("id in ?", ids).Delete(model.User{}).Error
if err != nil {
constants.GVA_LOG.Error("DeleteUserById err", zap.Any("err", err))
return err
//删除方式 2
//constants.GVA_DB.Delete(model.User{}, "id in ?", ids)
return nil
对于全局删除的阻止设定
如果在没有任何条件的情况下执行批量删除,GORM 不会执行该操作,并返回 ErrMissingWhereClause 错误。对此,你必须加一些条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate 模式,例如:
// DELETE FROM `user` WHERE 1=1
constants.GVA_DB.Where("1 = 1").Delete(&model.User{})
//原生sql删除
constants.GVA_DB.Exec("DELETE FROM user")
//跳过设定
constants.GVA_DB.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&model.User{})
更新操作
全量更新 struct 的所有字段,包括零值:
//根据id更新数据,全量字段更新,即使字段是0值
func UpdateUserById(user model.User) (err error) {
err = constants.GVA_DB.Save(&user).Error
if err != nil {
constants.GVA_LOG.Error("UpdateUserById err", zap.Any("err", err))
return err
return nil
更新指定列:
//更新指定列
//update user set `columnName` = v where id = id;
func UpdateSpecialColumn(id int64, columnName string, v interface{}) (err error) {
err = constants.GVA_DB.Model(&model.User{Id: id}).Update(columnName, v).Error
if err != nil {
constants.GVA_LOG.Error("UpdateSpecialColumn err", zap.Any("err", err))
return err
return nil
更新非0值的字段:
//更新- 根据 `struct` 更新属性,只会更新非零值的字段
//update user set `columnName` = v where id = id;
//当通过 struct 更新时,GORM 只会更新非零字段。 如果您想确保指定字段被更新,你应该使用 Select 更新选定字段,或使用 map 来完成更新操作
func UpdateSelective(user model.User) (effected int64, err error) {
tx := constants.GVA_DB.Model(&user).Updates(&model.User{
Id: user.Id,
Name: user.Name,
Age: user.Age,
Sex: user.Sex,
Phone: user.Phone,
})
如果你想更新0值的字段,那么可以使用 Select 函数先选择指定的列名,或者使用 map 来完成:
//map 方式会更新0值字段
tx = constants.GVA_DB.Model(&user).Updates(map[string]interface{}{
"Id": user.Id,
"Name": user.Name,
"Age": user.Age,
"Sex": user.Sex,
"Phone": user.Phone,
})
Select 方式指定列名:
//Select 方式指定列名
tx = constants.GVA_DB.Model(&user).Select("Name", "Age", "Phone").Updates(&model.User{
Id: user.Id,
Name: user.Name,
Age: user.Age,
Sex: user.Sex,
Phone: user.Phone,
})
Select 选定所有列名:
// Select 所有字段(查询包括零值字段的所有字段)
tx = constants.GVA_DB.Model(&user).Select("*").Updates(&model.User{
Id: user.Id,
Name: user.Name,
Age: user.Age,
Sex: user.Sex,
Phone: user.Phone,
})
Select 排除指定列名:
// Select 除 Phone 外的所有字段(包括零值字段的所有字段)
tx = constants.GVA_DB.Model(&user).Select("*").Omit("Phone").Updates(&model.User{
Id: user.Id,
Name: user.Name,
Age: user.Age,
Sex: user.Sex,
Phone: user.Phone,
})
根据条件批量更新:
//根据 条件 批量更新
func BatchUpdateByIds(ids []int64, user model.User) (effected int64, err error) {
if ids == nil || len(ids) == 0 {
return
tx := constants.GVA_DB.Model(model.User{}).Where("id in ?", ids).Updates(&user)
if tx.Error != nil {
return 0, tx.Error
return tx.RowsAffected, nil
查询操作
查询是重头戏放在最后。
Gorm 提供的便捷查询:
First:获取第一条记录(主键升序)
// SELECT * FROM user ORDER BY id LIMIT 1;
constants.GVA_DB.First(&user)
获取一条记录,没有指定排序字段:
// SELECT * FROM user LIMIT 1;
constants.GVA_DB.Take(&user)
获取最后一条记录(主键降序):
// SELECT * FROM user ORDER BY id DESC LIMIT 1;
constants.GVA_DB.Last(&user)
使用主键的方式查询:
// SELECT * FROM user WHERE id = 10;
constants.GVA_DB.First(&user, 10)
// SELECT * FROM user WHERE id = 10;
constants.GVA_DB.First(&user, "10")
// SELECT * FROM user WHERE id IN (1,2,3);
constants.GVA_DB.Find(&user, []int{1,2,3})
条件查询:
// 获取第一条匹配的记录
// SELECT * FROM user WHERE name = 'xiaoming' ORDER BY id LIMIT 1;
constants.GVA_DB.Where("name = ?", "xiaoming").First(&user)
// 获取全部匹配的记录
// SELECT * FROM user WHERE name <> 'xiaoming';
constants.GVA_DB.Where("name <> ?", "xiaoming").Find(&user)
// IN
// SELECT * FROM user WHERE name IN ('xiaoming','xiaohong');
constants.GVA_DB.Where("name IN ?", []string{"xiaoming", "xiaohong"}).Find(&user)
// LIKE
// SELECT * FROM user WHERE name LIKE '%ming%';
constants.GVA_DB.Where("name LIKE ?", "%ming%").Find(&user)
// AND
// SELECT * FROM user WHERE name = 'xiaoming' AND age >= 33;
constants.GVA_DB.Where("name = ? AND age >= ?", "xiaoming", 33).Find(&user)
// Time
// SELECT * FROM user WHERE updated_at > '2021-03-10 15:44:23';
constants.GVA_DB.Where("updated_at > ?", "2021-03-10 15:44:23").Find(&user)
// BETWEEN
// SELECT * FROM user WHERE created_at BETWEEN ''2021-03-07 15:44:23' AND '2021-03-10 15:44:23';
constants.GVA_DB.Where("created_at BETWEEN ? AND ?", "2021-03-07 15:44:23", "2021-03-10 15:44:23").Find(&user)
not 条件操作:
// SELECT * FROM user WHERE NOT name = "xiaoming" ORDER BY id LIMIT 1;
constants.GVA_DB.Not("name = ?", "xiaoming").First(&user)
// Not In
// SELECT * FROM user WHERE name NOT IN ("xiaoming", "xiaohong");
constants.GVA_DB.Not(map[string]interface{}{"name": []string{"xiaoming", "xiaohong"}}).Find(&user)
// Struct
// SELECT * FROM user WHERE name <> "xiaoming" AND age <> 20 ORDER BY id LIMIT 1;
constants.GVA_DB.Not(model.User{Name: "xiaoming", Age: 20}).First(&user)
// 不在主键切片中的记录
// SELECT * FROM user WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;
constants.GVA_DB.Not([]int64{1,2,3}).First(&user)
or 操作:
// SELECT * FROM user WHERE name = 'xiaoming' OR name = 'xiaohong';
constants.GVA_DB.Where("name = ?", "xiaoming").Or("name = ?", "xiaohong").Find(&user)
// Struct
// SELECT * FROM user WHERE name = 'xiaoming' OR (name = 'xiaohong' AND age = 20);
constants.GVA_DB.Where("name = 'xiaoming'").Or(model.User{Name: "xiaohong", Age: 20}).Find(&user)
// Map
// SELECT * FROM user WHERE name = 'xiaoming' OR (name = 'xiaohong' AND age = 20);
constants.GVA_DB.Where("name = 'xiaoming'").Or(map[string]interface{}{"name": "xiaohong", "age": 20}).Find(&user)
查询返回指定字段:
如果你只要要查询特定的字段,可以使用 Select 来指定返回字段:
// SELECT name, age FROM user;
constants.GVA_DB.Select("name", "age").Find(&user)
// SELECT name, age FROM user;
constants.GVA_DB.Select([]string{"name", "age"}).Find(&user)
// SELECT COALESCE(age,'20') FROM user;
constants.GVA_DB.Table("user").Select("COALESCE(age,?)", 20).Rows()
指定排序方式:
// SELECT * FROM users ORDER BY age desc, name;
constants.GVA_DB.Order("age desc, name").Find(&users)
// 多个 order
// SELECT * FROM users ORDER BY age desc, name;
constants.GVA_DB.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY FIELD(id,1,2,3)
constants.GVA_DB.Clauses(clause.OrderBy{
Expression: clause.Expr{SQL: "FIELD(id,?)", Vars: []interface{}{[]int{1, 2, 3}}, WithoutParentheses: true},
}).Find(&model.User{})
分页查询:
// SELECT * FROM user LIMIT 10;
constants.GVA_DB.Limit(10).Find(&user)
// SELECT * FROM user OFFSET 10;
constants.GVA_DB.Offset(10).Find(&user)
// SELECT * FROM user OFFSET 0 LIMIT 10;
constants.GVA_DB.Limit(10).Offset(0).Find(&user)
分组查询-Group & Having:
// SELECT name, sum(age) as total FROM `users` WHERE name LIKE "ming%" GROUP BY `name`
constants.GVA_DB.Model(&model.User{}).Select("name, sum(age) as total").Where("name LIKE ?", "group%").Group("name").First(&result)
// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
constants.GVA_DB.Model(&model.User{}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)
Distinct 使用:
//SELECT distinct(name, age) from user order by name, age desc
constants.GVA_DB.Distinct("name", "age").Order("name, age desc").Find(&user)
事务操作
如同在 MySQL 中操作事务一样,事务的开始是以 Begin 开始,以 Commit 结束:
//事务测试
func TestGormTx(user model.User) (err error) {
tx := constants.GVA_DB.Begin()
// 注意,一旦你在一个事务中,使用tx作为数据库句柄
if err := tx.Create(&model.User{
Name: "liliya",
Age: 13,
Sex: 0,
Phone: "15543212346",
}).Error; err != nil {
tx.Rollback()
return err
if err := tx.Updates(&model.User{
Id: user.Id,
Name: user.Name,
Age: user.Age,
Sex: user.Sex,
Phone: user.Phone,
}).Error; err != nil {
tx.Rollback()
return err
tx.Commit()
return nil
以上就是关于 GORM 使用相关的操作说明以及可能会出现的问题,关于 Gorm 的使用还有一些高级特性,这里就不做全面的演示,还是先熟悉基本 api 的操作等需要用到高级特性的时候再去看看也不迟。示例代码都已经上传到 Github,大家可以下载下来练习一下。
Go ORM框架 - GORM 踩坑指南的更多相关文章
C# -- HttpWebRequest 和 HttpWebResponse 的使用 C#编写扫雷游戏 使用IIS调试ASP.NET网站程序 WCF入门教程 ASP.Net Core开发(踩坑)指南 ASP.Net Core Razor+AdminLTE 小试牛刀 webservice创建、部署和调用 .net接收post请求并把数据转为字典格式
C# -- HttpWebRequest 和 HttpWebResponse 的使用 C# -- HttpWebRequest 和 HttpWebResponse 的使用 结合使用HttpWebReq ...
Nuxt.js的踩坑指南(常见问题汇总)
本文会不定期更新在nuxt.js中遇到的问题进行汇总.转发请注明出处,尊重作者,谢谢! 强烈推荐作者文档版踩坑指南,点击跳转踩坑指南 在Nuxt的官方文档中,中文文档和英文文档都存在着不小的差异. 1 ...
Spring WebSocket踩坑指南
Spring WebSocket踩坑指南 本次公司项目中需要在后台与安卓App间建立一个长连接,这里采用了Spring的WebSocket,协议为Stomp. 关于Stomp协议这里就不多介绍了,网上 ...
树莓派4B踩坑指南 - (15)搭建在线python IDE
今天想在树莓派上自己搭一个在线的python IDE,于是找到了一篇教程--Fred913大神的从头开始制作OJ-在线IDE的搭建 自己尝试动手做了一下, 还是发现不少细节需要注意, 记录在此 如果不 ...
正则表达式 test 踩坑指南
正则表达式 test 踩坑指南 test 只能使用一次,第二次返回的是错误结果! reg = /edg|edge/g; /edg|edge/g reg.test(`edg`) true reg.tes ...
Taro 开发踩坑指南 (小程序,H5, RN)
Taro 开发踩坑指南 (小程序,H5, RN) css taro 如何展示多行文本省略号 https://www.cnblogs.com/xgqfrms/p/12569057.html UI 设计稿 ...
小程序 & taro 踩坑指南
小程序 & taro 踩坑指南 微信开发者工具, 不支持 react bug https://github.com/NervJS/taro/issues/5042 solution just ...
Java 热更新 Groovy 实践及踩坑指南
Groovy 是什么? Apache的Groovy是Java平台上设计的面向对象编程语言.这门动态语言拥有类似Python.Ruby和Smalltalk中的一些特性,可以作为Java平台的脚本语言使用 ...
小程序框架MpVue踩坑日记(一)
小程序也做了几个小功能模块了,总觉得需要总结一下,踩坑什么的还是得记录一下啊. 好吧,其实是为了方便回顾 首先,说到小程序框架,大家都知道wepy,不过,我是没用过 美团开发团队到mpvue到是个实在 ...
随机推荐
H5网页应用打包安卓App (全网最详细教程)
img { box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important } .red { color: rgba(255, 0, 0, 1) } ...
【原创】docker & kubernetes问题总结
1.entrypoint & cmd 指令的区别 这主要考察 Dockerfile 良好实践中关于容器启动时运行的命令. entrypoint 和 cmd 命令都是设置容器启动时要执行的命令, ...
MySQL 启动流程及配置管理
目录 MySQL 启动关闭流程 启动数据库 关闭数据库 MySQL 配置管理 预编译阶段 配置文件 命令行 MySQL 配置文件 配置文件的位置和优先级 配置文件的优先级测试 配置文件的生效顺序 配置 ...
3.keepalived+脚本实现nginx高可用
标题 : 3.keepalived+脚本实现nginx高可用 目录 : Nginx 序号 : 3 else exit 0 fi else exit 0 fi - 需要保证脚本有执行权限,可以使用chm ...
codeforces 1028C Rectangles【思维】
题目:戳这里 题意:有n个矩阵,求一个点(保证存在)至少在n-1个点内. 解题思路:因为矩阵与坐标轴平行,所以我们画图可以发现如果存在点满足条件,则这些点中一定有一个是矩阵的顶点.我们可以把所有顶点的 ...
关于FFT的一些理解,以及如何手工计算FFT加深理解和验证正确性
总结缺少逻辑性和系统性,主要便于自己理解和记忆 关于一维FFT的 于是复系数Cn是图像傅里叶变换的yn列向量 于是我们看到最后引入,Cn这个复系数的模来表征频率波的振幅记为Sn(即简谐波叠加的数量 然 ...
HDU 3247 Resource Archiver(AC自动机 + 状压DP + bfs预处理)题解
题意:目标串n( <= 10)个,病毒串m( < 1000)个,问包含所有目标串无病毒串的最小长度 思路:貌似是个简单的状压DP + AC自动机,但是发现dp[1 << n][ ...
Django : Security in Django
Security in Django https://docs.djangoproject.com/en/1.10/topics/security/ 1 Cross site scripting (X ...
user tracker with ETag
user tracker with ETag 用户追踪, without cookies clear cache bug 实现原理 HTTP cache hidden iframe 1px image ...
VAST助推NGK公链热度升温,日活超过以太坊!
在区块链市场,如果说过去是比特币和以太坊的时代,那么现在和未来绝对是NGK的时代. NGK公链的出现,让区块链市场看到了新的希望.它不仅仅是开放的和可编程的,而且是低Gas燃耗的,以及创新共识机制的. ...
热门专题
if嵌套语句执行顺序
VGG19模型识别自定义的手写数字
eventbus&period;post 是什么方法
py 控制台打印中文
jquery-xlsx插件
ubuntu16&period;04 rc&period;local只有一个脚本不执行
ActionChains 模拟鼠标移动指定位置点击
scrapy回调函数callback执行顺序
aps期刊revtex怎么提交
为什么存储在sessioStorage的数据读取不出来
maven&period;google&period;com 连接不上
delphi 数组作参数
newduba变成了起始页怎么改
python中数组和元组的区别
React兄弟组件之间的通信
C&num;graphics类
linux的shell脚本是空字符串
ccproxy8&period;0时而能用时而不能用
小程序 压缩服务器图片
c 获取文件的修改时间
Home
Powered By WordPress