orm

package
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 30, 2022 License: Apache-2.0 Imports: 25 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var LogFormatter = func(values ...interface{}) (messages []interface{}) {
	if len(values) > 1 {
		var (
			sql             string
			formattedValues []string
			level           = values[0]
			currentTime     = NowFunc() // "\n\033[33m[" + NowFunc().Format("2006-01-02 15:04:05") + "]\033[0m"
			source          = values[1] // fmt.Sprintf("\033[35m(%v)\033[0m", values[1])
		)

		messages = []interface{}{level, source, currentTime}

		if level == "sql" {

			messages = append(messages, float64(values[2].(time.Duration).Nanoseconds()/1e4)/100.0)

			for _, value := range values[4].([]interface{}) {
				indirectValue := reflect.Indirect(reflect.ValueOf(value))
				if indirectValue.IsValid() {
					value = indirectValue.Interface()
					if t, ok := value.(time.Time); ok {
						if t.IsZero() {
							formattedValues = append(formattedValues, fmt.Sprintf("'%v'", "0000-00-00 00:00:00"))
						} else {
							formattedValues = append(formattedValues,
								fmt.Sprintf("'%v'", t.Format("2006-01-02 15:04:05")))
						}
					} else if b, ok := value.([]byte); ok {
						if str := string(b); isPrintable(str) {
							formattedValues = append(formattedValues, fmt.Sprintf("'%v'", str))
						} else {
							formattedValues = append(formattedValues, "'<binary>'")
						}
					} else if r, ok := value.(driver.Valuer); ok {
						if value, err := r.Value(); err == nil && value != nil {
							formattedValues = append(formattedValues, fmt.Sprintf("'%v'", value))
						} else {
							formattedValues = append(formattedValues, "NULL")
						}
					} else {
						switch value.(type) {
						case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool:
							formattedValues = append(formattedValues, fmt.Sprintf("%v", value))
						default:
							formattedValues = append(formattedValues, fmt.Sprintf("'%v'", value))
						}
					}
				} else {
					formattedValues = append(formattedValues, "NULL")
				}
			}

			sql = values[3].(string)
			strValues := strings.Join(formattedValues, ", ")
			messages = append(messages, sql)
			messages = append(messages, strValues)

			messages = append(messages, values[5].(int64))
		} else {
			messages = append(messages, "")
			messages = append(messages, values[2:]...)
			messages = append(messages, "")
		}
	}

	return
}

sql日志格式

View Source
var NowFunc = func() time.Time {
	return time.Now()
}

NowFunc 当前

Functions

func CheckDbExists

func CheckDbExists(key string) bool

func CheckPassword

func CheckPassword(hashKey string, password string) bool

CheckPassword 检查密码

func CreateTable

func CreateTable(models ...interface{})

CreateTable create table for models

func CreateTableWithKey

func CreateTableWithKey(dbName string, models ...interface{})

func Encrypt

func Encrypt(text string, key string) (string, error)

Encrypt 根据Key加密一个字符串,返回加密的数据

func GenerateUpdateSQL

func GenerateUpdateSQL(st *gorm.ModelStruct, tableName string, rows interface{}, cols []string, start int, end int) (string, []interface{}, bool)

func HashPassword

func HashPassword(password string) string

HashPassword 加密密码,返回加密的数据

func Init

func Init(conf *DbSettings) error

Init 初始化

dialect -别名,如mysql,oracle Init("mysql", "GO_TESTDB", "localhost", "root", "password", 3306)

The returned DB is safe for concurrent use by multiple goroutines and maintains its own pool of idle connections. Thus, the Open function should be called just once. It is rarely necessary to close a DB.

func InitDBWithDbName

func InitDBWithDbName(conf *DbSettings, dbName string) error

InitDBWithDbName 注册多个数据库连接

func RemoveDbByKey

func RemoveDbByKey(key string)

RemoveDbByKey 移除DB实例

func SetTablePrefix

func SetTablePrefix(prefix string)

func Validate

func Validate(value interface{}, vtype string, isZero bool) error

Validate 校验 slice/slicePtr/struct/structPtr

Types

type Checker

type Checker struct{}

Checker 健康检查器

func (*Checker) Check

func (c *Checker) Check() (bool, map[string]string, string)

Check 检查可用状态

func (*Checker) Enabled

func (c *Checker) Enabled() bool

Enabled 是否启用

func (*Checker) GetName

func (c *Checker) GetName() string

GetName 获取名称

type DBLogWriter

type DBLogWriter struct {
	// contains filtered or unexported fields
}

DBLogWriter 自定义LOG,增加trace_id

func (*DBLogWriter) Println

func (t *DBLogWriter) Println(v ...interface{})

Println 写日志

func (*DBLogWriter) SetProperty

func (t *DBLogWriter) SetProperty(key, val string)

SetProperty 设置logger属性记录

type DbSettings

type DbSettings struct {
	Dialect         string
	DbName          string
	Host            string
	User            string
	Password        string
	Port            int
	Key             string
	MaxOpenConns    int
	MaxIdleConns    int
	ConnMaxLifetime int // 分钟
}

DbSettings 数据库连接字符串属性

type FiDB

type FiDB struct {
	Error error

	RowsAffected int64 // 受影响行数
	// contains filtered or unexported fields
}

FiDB 数据表操作类

func NewDB

func NewDB(ctx xcontext.XContext) *FiDB

NewQuery 获取一个查询会话(只作用于一次查询)

func NewNamedDB

func NewNamedDB(dbName string, ctx xcontext.XContext) *FiDB

Query 获取一个新的数据库连接

func NewQuery

func NewQuery() *FiDB

NewQuery 获取一个查询会话(只作用于一次查询)

func NewQueryWithDbName

func NewQueryWithDbName(dbName string) *FiDB

Query 获取一个新的数据库连接

func (*FiDB) BatchInsert

func (t *FiDB) BatchInsert(entities interface{}) error

BatchInsert 批量插入 entities 待更新的实体数组slice, ID不能为空 例子:var users []model.User qa.BatchInsert(users)

func (*FiDB) BatchUpdate

func (t *FiDB) BatchUpdate(entities interface{}, cols []string) error

BatchUpdate 批量更新,ID不能为空 entities 待更新的实体数组slice cols 待更新的数据库字段 e.g []string{"email", "address"} 例子:var users []model.User 根据ID批量更新 var items []model.TbOrderFilterLog

var item1 model.TbOrderFilterLog item1.ID = 1 item1.UUID = "xxx1" item1.FilterMode = "top" var item2 model.TbOrderFilterLog item2.ID = 2 item2.UUID = "xxx2"

items = append(items,item1) items = append(items,item2)

qa := fiorm.NewQuery() // UPDATE `mpv_order_filter_log` SET `uuid` = CASE id when 1 then 'xxx1' when 2 then 'xxx2' END, `filter_mode` = CASE id when 1 then 'top' when 2 then ” END WHERE id IN (1,2) qa.BatchUpdate(items,[]string{"uuid","filter_mode"})

func (*FiDB) Count

func (t *FiDB) Count(value interface{}) *FiDB

db.Table("deleted_users").Count(&count) // SELECT count(*) FROM deleted_users;

func (*FiDB) Create

func (t *FiDB) Create(entityPtr interface{}) error

Create insert the value into database user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()} db.Create(&user)

func (*FiDB) Delete

func (t *FiDB) Delete(row interface{}, where ...interface{}) error

qa.Where("id = ?",1).Delete(&item) DELETE FROM `mpv_order_filter_data` WHERE (id = 1)

func (*FiDB) DeleteItem

func (t *FiDB) DeleteItem(entityPtrOrSlice interface{}) error

删除多条数据 必须要有where语句 DELETE FROM `mpv_order_filter_log` WHERE (category_id = 33) qa.Where("category_id = ?",33).DeleteItem(items)

func (*FiDB) DeleteItemsByID

func (t *FiDB) DeleteItemsByID(entities interface{}) error

根据ID删除多条记录 var items []model.TbOrderFilterLog DELETE FROM `mpv_order_filter_log` WHERE (`mpv_order_filter_log`.`id` IN (1,2)) qa.DeleteItemsByID(items)

func (*FiDB) DynamicBatchInsert

func (t *FiDB) DynamicBatchInsert(tableName string, entities interface{}) error

DynamicBatchInsert 批量新增自定义表 tableName 表名 entities 待更新的实体数组slice 例子:var users []CustomUser qa.DynamicBatchInsert("mpv_user",users)

func (*FiDB) DynamicBatchUpdate

func (t *FiDB) DynamicBatchUpdate(tableName string, entities interface{}, cols []string) error

DynamicBatchUpdate 批量更新自定义表 tableName 表名 entities 待更新的实体数组slice cols 待更新的数据库字段 e.g []string{"email", "address"} 例子:var users []CustomUser qa.DynamicBatchUpdate("mpv_user",users, []string{"desc", "address"})

func (*FiDB) Exec

func (t *FiDB) Exec(sql string, values ...interface{}) error

Exec execute raw sql sql := "update mpv_order_filter_log set order_mode='xxx' where id =3" // update mpv_order_filter_log set order_mode='xxx' where id =3 qa.Exec(sql)

func (*FiDB) ExecuteTextQuery

func (t *FiDB) ExecuteTextQuery(entitiesPrt interface{}, sql string, values ...interface{}) error

ExecuteTextQuery 原生语法查询 entitiesPrt 实体数组 引用类型 sql 原生SQL values SQL占位符中的变量值 例子:var userList []model.UserView da.ExecuteTextQuery(&userList,

"SELECT u.id,u.name, dept_name FROM department t ,user u WHERE u.dept_id=t.id AND u.ID >=?",12)

func (*FiDB) Find

func (t *FiDB) Find(entitiesPrt interface{}, where ...interface{}) *FiDB

db.Where("role = ?", "admin").Or("role = ?", "super_admin").Not("name = ?", "jinzhu").Find(&users)

func (*FiDB) First

func (t *FiDB) First(entityPrt interface{}, where ...interface{}) *FiDB

查找第一行数据,按照主键排序 asc SELECT * FROM `table` ORDER BY `id` ASC LIMIT 1 var item model.OrderFilterData qa.First(&item) SELECT * FROM `mpv_order_filter_data` WHERE (`id` = 100) ORDER BY `id` ASC LIMIT 1 qa.First(&item,100) SELECT * FROM `mpv_order_filter_data` WHERE (name = 'xxx') ORDER BY `id` ASC LIMIT 1 qa.First(&item,"name = ?","xxx")

func (*FiDB) ForUpdate

func (t *FiDB) ForUpdate() *FiDB

ForUpdate 行锁 例子:var users []model.User da.Table("user").Where("name = ?", "张三"). ForUpdate().Find(&users) SQL: SELECT name, birthday FROM `user` WHERE (name = '张三') FOR UPDATE

func (*FiDB) GetItemWhere

func (t *FiDB) GetItemWhere(entitiesPrt interface{}, query interface{}, args ...interface{}) error

GetItemWhere 查询符合条件的多条记录 var items []model.OrderFilterData qa := fiorm.NewQuery() SELECT * FROM `mpv_order_filter_data` WHERE (category_id = 2) qa.GetItemWhere(&items,"category_id = ?",2)

func (*FiDB) GetItemWhereFirst

func (t *FiDB) GetItemWhereFirst(entityPrt interface{}, query interface{}, args ...interface{}) error

GetItemWhereFirst 根据条件查询一条数据 entityPrt 待返回的数据实体 引用类型 query 查询条件 e.g "name =? and address = ?" args 查询条件对应的值,字符串数组 "张三","深圳" 例子: 查看第一条符合条件的记录,按ID排序 var item1 model.TbOrderFilterLog qa := fiorm.NewQuery() SELECT * FROM `mpv_order_filter_log` WHERE (category_id = 2) ORDER BY `id` ASC LIMIT 1 qa.GetItemWhereFirst(&item1,"category_id = ?",2)

func (*FiDB) GetLogWriter

func (db *FiDB) GetLogWriter() *DBLogWriter

GetLogWriter 获取日志输出对象。用户可以调用该对象的 SetProperty 方法为日志输出自定义信息

func (*FiDB) GetRecordCount

func (t *FiDB) GetRecordCount(tableName string, query interface{}, values ...interface{}) int64

获取记录总行数 tableName 表名 query 查询语句 values SQL占位符中的变量值 例子:count := da.GetRecordCount("user","name = ?","张三") SELECT count(*) FROM `user` WHERE (name = '张三')

func (*FiDB) Group

func (t *FiDB) Group(query string) *FiDB

Group specify the group method on the find rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows() rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows() db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)

func (*FiDB) Having

func (t *FiDB) Having(query interface{}, values ...interface{}) *FiDB

Having specify HAVING conditions for GROUP BY rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows() db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)

func (*FiDB) InsertItem

func (t *FiDB) InsertItem(entityPtr interface{}) error

InsertItem 插入一条记录 entityPtr 数据实体 引用类型 例子: var user model.User qa := fiorm.NewQuery() qa.InsertItem(&user)

func (*FiDB) Joins

func (t *FiDB) Joins(query string, args ...interface{}) *FiDB

Joins specify Joins conditions rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows() db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results) db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)

func (*FiDB) Limit

func (t *FiDB) Limit(limit interface{}) *FiDB

Cancel limit condition with -1 db.Limit(10).Find(&users1).Limit(-1).Find(&users2) // SELECT * FROM users LIMIT 10; (users1) // SELECT * FROM users; (users2)

func (*FiDB) Model

func (t *FiDB) Model(entityPtr interface{}) *FiDB

Model specify the model you would like to run db operations

// update all users's name to `hello`
db.Model(&User{}).Update("name", "hello")
// if user's primary key is non-blank, will use it as condition, then will only update the user's name to `hello`
db.Model(&user).Update("name", "hello")

func (*FiDB) NewRecord

func (t *FiDB) NewRecord(entity interface{}) bool

NewRecord 创建记录 user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()} db.NewRecord(user) // => 主键为空返回`true`

func (*FiDB) Not

func (t *FiDB) Not(query interface{}, args ...interface{}) *FiDB

Struct db.Not(User{Name: "jinzhu"}).First(&user) // SELECT * FROM users WHERE name <> "jinzhu";

func (*FiDB) Offset

func (t *FiDB) Offset(offset interface{}) *FiDB

Cancel offset condition with -1 db.Offset(10).Find(&users1).Offset(-1).Find(&users2) // SELECT * FROM users OFFSET 10; (users1) // SELECT * FROM users; (users2)

func (*FiDB) Or

func (t *FiDB) Or(query interface{}, args ...interface{}) *FiDB

Map db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2"}).Find(&users)

func (*FiDB) Order

func (t *FiDB) Order(value interface{}, reorder ...bool) *FiDB

ReOrder db.Order("age desc").Find(&users1).Order("age", true).Find(&users2) // SELECT * FROM users ORDER BY age desc; (users1) // SELECT * FROM users ORDER BY age; (users2)

func (*FiDB) Pagging

func (t *FiDB) Pagging(tEntity interface{}, p *Param) *Paginator

Pagging 分页

func (*FiDB) Pluck

func (t *FiDB) Pluck(column string, value interface{}) *FiDB

要返回多个列,做这样: db.Select("name, age").Find(&users)

func (*FiDB) Raw

func (t *FiDB) Raw(sql string, values ...interface{}) *FiDB

var result Result db.Raw("SELECT name, age FROM users WHERE name = ?", 3).Scan(&result)

func (*FiDB) RecordNotFound

func (t *FiDB) RecordNotFound() bool

RecordNotFound check if returning ErrRecordNotFound error var item model.TbOrderFilterLog qa := fiorm.NewQuery() qa.GetItemWhereFirst(&item,"id = ?",157) fmt.Println(qa.RecordNotFound())

func (*FiDB) Row

func (t *FiDB) Row() *sql.Row

row := db.Table("users").Where("name = ?", "jinzhu").Select("name, age").Row() // (*sql.Row) row.Scan(&name, &age)

func (*FiDB) Rows

func (t *FiDB) Rows() (*sql.Rows, error)

Raw SQL rows, err := db.Raw("select name, age, email from users where name = ?", "jinzhu").Rows() // (*sql.Rows, error) defer rows.Close() for rows.Next() { rows.Scan(&name, &age, &email) }

func (*FiDB) Scan

func (t *FiDB) Scan(dest interface{}) *FiDB

Raw SQL db.Raw("SELECT name, age FROM users WHERE name = ?", 3).Scan(&result)

func (*FiDB) ScanMap

func (t *FiDB) ScanMap(sql string, values ...interface{}) ([]map[string]string, error)

ExecuteTable 返回map

func (*FiDB) ScanObject

func (t *FiDB) ScanObject(sql string, values ...interface{}) ([]map[string]interface{}, error)

传入SQL表达式,返回map

func (*FiDB) Select

func (t *FiDB) Select(query interface{}, args ...interface{}) *FiDB

db.Table("users").Select("COALESCE(age,?)", 42).Rows() // SELECT COALESCE(age,'42') FROM users;

func (*FiDB) Set

func (t *FiDB) Set(name string, value interface{}) *FiDB

Set set setting by name, which could be used in callbacks, will clone a new db, and update its setting "gorm:insert_option", "ON CONFLICT" 存在则更新,不存在则插入 "gorm:query_option", "FOR UPDATE" 查询锁

func (*FiDB) SetLoger

func (t *FiDB) SetLoger(log logger)

func (*FiDB) Table

func (t *FiDB) Table(tableName string) *FiDB

Table 设置表名 tableName 表名

func (*FiDB) Take

func (t *FiDB) Take(out interface{}, where ...interface{}) *FiDB

随机获取一条记录 db.Take(&user) SELECT * FROM users LIMIT 1;

func (*FiDB) Update

func (t *FiDB) Update(attrs ...interface{}) error

Update 更新单个字段 var item model.TbOrderFilterLog qa := fiorm.NewQuery() // UPDATE `mpv_order_filter_log` SET `order_mode` = 'xxx' WHERE (id = 157) qa.Model(&item).Where("id = ?",157).Update("order_mode","xxx")

func (*FiDB) UpdateColumn

func (t *FiDB) UpdateColumn(attrs ...interface{}) error

UpdateColumn update attributes without callbacks, refer: https://jinzhu.github.io/gorm/crud.html#update 更新单个属性,类似于`Update` db.Model(&user).UpdateColumn("name", "hello") // UPDATE users SET name='hello' WHERE id = 111;

func (*FiDB) UpdateColumns

func (t *FiDB) UpdateColumns(entity interface{}) error

UpdateColumns update attributes without callbacks, refer: https://jinzhu.github.io/gorm/crud.html#update 更新多个属性,与“更新”类似 db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18}) // UPDATE users SET name='hello', age=18 WHERE id = 111;

func (*FiDB) UpdateItem

func (t *FiDB) UpdateItem(entityPtr interface{}, cols []string) error

UpdateItem 更新一行数据 entityPtr 数据库实体 引用类型 cols 待更新的数据库字段 e.g []string{"email", "address"} var item1 model.TbOrderFilterLog item1.ID = 1 item1.UUID = "xxx" UPDATE `mpv_order_filter_log` SET `order_mode` = 'xxxx', `uuid` = 'xxx' WHERE `id` = 1 qa.UpdateItem(&item1,[]string{"uuid","order_mode"})

func (*FiDB) Updates

func (t *FiDB) Updates(value interface{}) error

使用`RowsAffected`获取更新记录计数 db.Model(User{}).Updates(User{Name: "hello", Age: 18}).RowsAffected

func (*FiDB) Where

func (t *FiDB) Where(query interface{}, args ...interface{}) *FiDB

当使用struct查询时,GORM将只查询那些具有值的字段 SELECT * FROM `mpv_order_filter_data` WHERE (`category_id` = 2) AND (`category_name` = '优先审核') qa.Where(&model.OrderFilterData{CategoryID: 2,CategoryName:"优先审核"}).Find(&items) qa.Where(map[string]interface{}{"category_id": 2, "category_name": "优先审核"}).Find(&items)

func (*FiDB) WithLogFields

func (db *FiDB) WithLogFields(fields map[string]string) *FiDB

WithLogFields 向 sql 日志输出自定义信息

func (*FiDB) WithLogTraceID

func (db *FiDB) WithLogTraceID(traceID string) *FiDB

WithLogTraceID 向 sql 日志输出 trace_id(自定义信息)

type FiTX

type FiTX struct {
	Error error

	// 事务结束调用函数
	EndFunc func(fiTXCtx FiTXCtx)
	// contains filtered or unexported fields
}

FiTX 数据表操作类

func BeginDBTX

func BeginDBTX(ctx xcontext.XContext) *FiTX

func BeginNamedDBTX

func BeginNamedDBTX(dbName string, ctx xcontext.XContext) *FiTX

func BeginTransaction

func BeginTransaction() *FiTX

BeginTransaction 开始事务

func BeginTransactionWithDbName

func BeginTransactionWithDbName(dbName string) *FiTX

BeginTransactionWithDbName 创建多个数据库事务连接

func (*FiTX) Commit

func (t *FiTX) Commit()

func (*FiTX) EndTransaction

func (t *FiTX) EndTransaction()

EndTransaction 结束事务

func (*FiTX) NewQuery

func (t *FiTX) NewQuery() *FiDB

NewQuery 获取一个查询会话(只作用于一次查询)

func (*FiTX) RollbackTranction

func (t *FiTX) RollbackTranction()

func (*FiTX) SetLoger

func (t *FiTX) SetLoger(log logger)

region 设置日志组件

type FiTXCtx

type FiTXCtx struct {
	// 启动事务打开连接时间
	ConnectingTime time.Time
	// 启动事务打开连接成功时间
	ConnectedTime time.Time
	// 事务结束提交时间
	ExecutingTime time.Time
	// 事务结束提交完成时间
	ExecutedTime time.Time
	// 事务处理结果(Commit/Rollback)
	TXResult string
}

FiTXCtx 事务上下文数据

type LogWriter

type LogWriter interface {
	Println(v ...interface{})
}

LogWriter log writer interface

type Logger

type Logger struct {
	LogWriter
}

Logger default logger

func (Logger) Print

func (logger Logger) Print(values ...interface{})

Print format & print log

type Paginator

type Paginator struct {
	TotalRecord int `json:"total_record"` // 总记录数
	TotalPage   int `json:"total_page"`   // 总页数
	Offset      int `json:"offset"`       // 偏移量
	Limit       int `json:"limit"`        // 每页记录数
	Page        int `json:"page"`         // 当前页
	PrevPage    int `json:"prev_page"`    // 前一页
	NextPage    int `json:"next_page"`    // 下一页
}

Paginator 分页返回

type Param

type Param struct {
	Page    int      // 当前页
	Limit   int      // 每页记录数
	OrderBy []string // 排序 []string{"id asc","name desc"}
}

Param 分页参数

type TableStruct

type TableStruct struct {
	Field   string `gorm:"column:Field"`
	Type    string `gorm:"column:Type"`
	Null    string `gorm:"column:Null"`
	Key     string `gorm:"column:Key"`
	Default string `gorm:"column:Default"`
	Extra   string `gorm:"column:Extra"`
}

func GetTableStructInfo

func GetTableStructInfo(tableName string) []TableStruct

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL